Разработка метода автоматизации конструирования генераторов тестовых программ для микропроцессоров
Функциональная верификация микропроцессоров. Анализаторы формальных спецификаций. Генераторы кода и библиотеки моделирования. Техники генерации тестовых программ. Спецификации архитектуры, язык описания шаблонов. Размещение команд и данных в памяти.
Рубрика | Программирование, компьютеры и кибернетика |
Вид | диссертация |
Язык | русский |
Дата добавления | 11.06.2018 |
Размер файла | 2,7 M |
Отправить свою хорошую работу в базу знаний просто. Используйте форму, расположенную ниже
Студенты, аспиранты, молодые ученые, использующие базу знаний в своей учебе и работе, будут вам очень благодарны.
· За основу должен быть взят популярный язык высокого уровня. Здесь хорошо подходят сценарные языки такие, как Ruby [78], Python [79] и Perl [80], часто используемые инженерами-верификаторами для автоматизации различных задач.
· Формат, используемый для описания сущностей языка ассемблера (команды, данные и директивы), должен быть максимально приближен к используемому в настоящих ассемблерных программах.
· Языковые конструкции для описания сущностей языка ассемблера должны динамически настраиваться в соответствии с архитектурой тестируемого микропроцессора.
· Конструкции для задания используемых техник генерации должны быть независимыми от конкретных техник и применимы для любых из поддерживаемых техник.
· Должна поддерживаться возможность описания шаблонов, основанных на совместном использовании различных техник генерации.
· Язык должен позволять описывать отдельные задачи генерации в виде библиотек, на основе которых можно было бы создавать описания более сложных шаблонов.
После анализа различных возможных вариантов реализации языка описания тестовых шаблонов, было принято решение взять за основу язык программирования Ruby [78]. Данный язык позволит удовлетворить всем вышеперечисленным требованиям. Его основным преимуществом являются мощные средства метапрограммирования [81], которые значительно упрощают создание предметно-ориентированных языков. Язык поддерживает динамическое создание методов, определение блоков кода, интерпретируемых в особом контексте, и перегрузку операторов.
Таким образом, язык описания тестовых шаблонов наследует все возможности языка Ruby, расширяя его дополнительными конструкциями для описания свойств тестовых программ. Предоставляемые им конструкции можно разделить на статические и динамические. Статические конструкции используются для задания техник генерации, используемых для построения описанной шаблоном программы, и являются частью реализации ядра генератора. Динамические конструкции используются для описания сущностей ассемблера тестируемого микропроцессора и создаются динамически на основе информации, извлеченной из формальных спецификаций и описываемой метаданными, при помощи средств метапрограммирования языка Ruby.
Шаблоны тестовых программ представляют собой классы на языке Ruby, наследующие библиотечный базовый класс Template, который реализует конструкции описания свойств тестовых программ. Структура тестовых шаблонов показана в примере 11.
Пример 11. Структура тестовых шаблонов
01: 02: 03: 04: 05: 06: 07: 08: 09: 10: 11: 12: 13: 14: 15: 16: |
require ENV['TEMPLATE'] class MyTemplate < Template def initialize super # Настройки генерации end def pre # Пролог тестовой программы end def post # Эпилог тестовой программы end def run # Тестовые примеры и код диспетчеризации end end |
Шаблоны тестовых программ определяют четыре основных метода: (1) initialize, который задает настройки генератора; (2) pre, который содержит описание пролога тестовой программы; (3) post, который содержит описание эпилога тестовой программы; (4) run, который содержит описание тестовых примеров и кода диспетчеризации. Помимо перечисленных методов позволяется объявлять дополнительные методы, которые будут вызываться из основных методов. Это позволяет структурировать описания тестовых программ и обеспечить повторное использование кода. Общая стратегия повторного использования кода шаблонов такая же, как и для большинства языков высокого уровня. Ruby позволяет создавать базовые классы, которые могут содержать код, описывающий свойства, наследуемые несколькими шаблонами. Другой подход - создавать классы, предоставляющие утилитные методы, предназначенные для решения отдельных задач и используемые различными шаблонами.
Тестовые примеры описываются при помощи конструкций block и ее разновидностей. На основе отдельного блока может быть построено несколько тестовых примеров в зависимости от его структуры и используемых параметров. Также можно задавать сколько раз блок должен быть обработан. Это позволяет получить несколько различных тестовых примеров на основе последовательностей, использующих методы рандомизации. Таким образом описания тестовых примеров имеют следующий формат: block(attrs) { body }.run N, где attrs - набор атрибутов, задающих способы построения последовательностей команд и их обработки, body - тело блока, содержащее команды и вложенные блоки, на основе которых строятся последовательности команд, и N - необязательный параметр, задающий сколько раз блок будет обработан (по умолчанию равен 1).
2.2.4 Описание последовательностей команд
Последовательности команд, составляющие тестовые примеры, описываются в виде блоков, которые можно определить рекурсивно. Таким образом, блок представляет собой или элементарный блок или упорядоченный набор вложенных блоков. Под элементарным блоком понимается отдельная команда. При этом неэлементарный блок может быть пустым. Блоки описываются при помощи конструкции block {…}. Ниже приведен пример вложенной структуры из блоков, использующих команды MIPS.
Пример 12. Блоки, описывающие последовательности команд
01: 02: 03: 04: 05: 06: 07: 08: 09: 10: 11: 12: 13: |
block { block { add t0, t1, t2 add t3, t4, t5 } block { sub t0, t1, t2 sub t3, t4, t5 } block { slt t0, t1, t2 slt t3, t4, t5 } |
Блок верхнего уровня Вложенный блок Элементарный блок |
Каждый блок описывает одну или несколько последовательностей команд. Элементарный блок задает одну последовательность, состоящую из единственной команды. Неэлементарный блок задает набор последовательностей, построенных на основе последовательностей, описанных вложенными блоками. Существует три частных случая блоков: (1) sequence (последовательность), (2) atomic (атомарная последовательность) и (3) iterate (блок итерирования).
Блок типа sequence можно считать элементарным: он возвращает одну последовательность, состоящую из команд, указанных в блоке. Пример такого блока приведен ниже.
Пример 13. Блок, описывающий одну последовательность команд
01: 02: 03: 04: 05: |
sequence { add t0, t1, t2 add t3, t4, t5 add t6, t7, t8 } |
Одна последовательность |
Блок типа atomic является разновидностью блока типа sequence. Его итератор также возвращает одну единственную последовательность, состоящую из команд, указанных в блоке. Важное отличие заключается в том, что эта последовательность является неделимой. Т.е. она никогда не будет перемешана с другими командами.
Блок типа iterate предназначен для итерирования по последовательностям, которые возвращают вложенные блоки. Число возвращаемых блоком последовательностей равно общему числу всех последовательностей, возвращаемых вложенными блоками. Пример приведен ниже.
Пример 14. Блок, описывающий итерирование по вложенным элементам
01: 02: 03: 04: 05: |
iterate { add t0, t1, t2 add t3, t4, t5 add t6, t7, t8 } |
Три последовательности из одной команды |
Для неэлементарных блоков задаются атрибуты, которые определяют способ построения последовательностей на основе вложенных блоков. Каждый из атрибутов связан с той или иной техникой построения последовательностей, реализованной генератором. Набор техник и набор атрибутов являются расширяемыми. Пример неэлементарного блока, который описывает последовательности команд, построенные на основе вложенных блоков, приведен ниже.
Пример 15. Блок, описывающий последовательности, построенные на основе вложенных блоков
01: 02: 03: 04: 05: 06: 07: 08: 09: 10: |
block( :combinator => 'diagonal', :permutator => 'trivial', :compositor => 'catenation', :rearranger => 'trivial', :obfuscator => 'trivial') { iterate { A11; A21; A31 } iterate { sequence { B11, B12 }; sequence { B21, B22 } } iterate { sequence { C11; C12; C13 } } } |
Блок описывает последовательности команд, построенные на основе вложенных блоков: {A11, B11, B12, C11, C12, C13}, {A21, B21, B22, C11, C12, C13}, {A31, B11, B12, C11, C12, C13} Блок описывает: {A11}, {A21}, {A31} Блок описывает: {B11, B12}, {B21, B22} Блок описывает: {C11, C12, C13} |
Техники построения последовательностей команд будут подробно описаны в разделе, посвященном генераторам тестовых программ.
2.2.5 Описание правил выбора регистров
Регистры, используемые командами тестового примера, могут задаваться жестко или выбираться в соответствии с определенной стратегией. Возможность динамического выбора регистров - важное требование для случайной и комбинаторной генерации. При динамическом выборе указывается имя регистрового массива и критерий выбора конкретного регистра. При выборе учитываются три критерия: (1) диапазон возможных значений для номера регистра; (2) история использования регистров; (3) применяемая стратегия выбора. По умолчанию диапазон номеров регистров определяется формальными спецификациями. Его можно изменить, указав подмножество, которое необходимо исключить, или подмножество, из которого необходимо выбрать. История использования регистров предоставляется средой генерации, и ее можно модифицировать при помощи специальных конструкций языка шаблонов. Стратегии выбора определяют, каким образом информация о диапазоне значений и использованных регистрах будет использована. Ниже приведен список поддерживаемых в настоящий момент стратегий, который при необходимости может быть расширен пользовательскими реализациями:
· random - выбрать случайный регистр;
· free - выбрать неиспользуемый в данном тестовом примере регистр;
· used - выбрать регистр, который уже используется;
· try_free - выбрать неиспользуемый регистр или, если такового не имеется, вернуть один из используемых.
Рассмотрим, как осуществляется управление выбором регистров в языке тестовых шаблонов. Регистры могут быть жестко заданы, используя их имена или номера. Для динамического выбора регистров, номер должен быть задан как случайное значение (функция rand) или как неизвестное значение (символ “_”). Для неизвестного значения стратегия выбора регистра задается при помощи атрибута select, параметризованного именем стратегии (по умолчанию используется стратегия random). При этом можно использовать дополнительный атрибут exclude, чтобы исключить некоторые регистры из диапазона, или атрибут retain, чтобы задать пользовательский диапазон. Помимо этого конкретным стратегиям могут быть переданы пользовательские атрибуты, позволяющие настроить их поведение. Т.к. синтаксис сложных стратегий выбора регистров может быть достаточно запутанным, для упрощения кода их описания можно поместить в специализированные Ruby-функции. Пример 16 демонстрирует использование описанных конструкций выбора регистров (архитектура MIPS).
Пример 16. Конструкции языка тестовых шаблонов для выбора регистров
01: 02: 03: 04: 05: 06: 07: |
add t0, t1, t2 add r(8), r(9), r(10) add r(rand(0, 31)), r(rand(0, 31)), r(rand(0, 31)) add r(_), r(_), r(_) add r(_ select(`free')), r(_ select(`free')), t2 add r(_ select(`free'), :exclude => [at, v0, v1]), t1, t2 add r(_ select(`free'), :retain => [at, v0, v1]), t1, t2 |
Регистры жестко заданы по именам Регистры жестко заданы по номерам Случайный выбор из указанного диапазона Случайный выбор из диапазона, заданного в nML Выбор неиспользуемых регистров Выбор неиспользуемых регистров кроме указанных Выбор неиспользуемых регистров из указанных |
Кроме того язык тестовых шаблонов предоставляет функции free_register и free_all_registers, позволяющие поместить занятые регистры в список неиспользуемых.
При динамическом выборе регистров часто возникает необходимость задать зависимости по регистрам. Для этой цели могут использоваться Ruby-переменные, при помощи которых можно передать один и тот же объект, описывающий правило динамического выбора регистра, нескольким командам. В таком случае выбор регистра будет осуществляться при первом обращении, а выбранный регистр будет использоваться остальными командами. Другой вариант решения данной задачи - при мощи дополнительного атрибута передавать стратегии выбора уникальный идентификатор. В случае если, выбор для данного идентификатора уже сделан, стратегия будет возвращать существующее значение, в противном случае будет строиться новое значение. Такой подход полезен для тестовых примеров, построенных путем комбинирования составных частей, описанных в разных шаблонах и независящих друг от друга. Пример 17 иллюстрирует оба подхода.
Пример 17. Задание зависимостей при динамическом выборе регистров
01: 02: 03: 04: |
add r1=r(_), t1, t2 add r(_), r1, t3 add r(_ select(`random'), :id=>'r1'), t1, t2 add r(_), r(_ select(`random'), :id=>'r1'), t3 |
Зависимость типа “чтение после записи” задана при помощи переменой r1 Зависимость типа “чтение после записи” задана при помощи атрибута id, значение которого - `r1' |
2.2.6 Описание тестовых ситуаций
Значения входных аргументов команд генерируются в соответствии с тестовыми ситуациями, связанными с этими командами. Тестовые ситуации могут быть различных типов и обрабатываться компонентами, реализующими различные техники генерации. При этом формат их описания в шаблонах остается единым. Для задания привязки тестовой ситуации к команде используется функция situation, в которую передается идентификатор ситуации и набор используемых ей атрибутов. В примере 18 демонстрируется использование данной функции.
Пример 18. Задание тестовых ситуаций для команд
01: 02: 03: 04: |
add t0, t1, t2 do situation(`zero') end add t3, t4, t5 do situation(`random', :dist=>int_dist) end |
Значения входных регистров равны нулю Значения входных регистров - случайные числа, сгенерированные на основе распределения, заданного атрибутом dist |
Если команда имеет непосредственные аргументы, значения которых должны быть сгенерированы в результате обработки заданной тестовой ситуации, то они задаются при помощи символа “_”.
Если для команды не задана тестовая ситуация и значения ее входных аргументов не определены в данном тестовом примере, для них будут сгенерированы случайные числа из диапазона, соответствующего их типу данных. Кроме этого при помощи функции set_default_situation можно задать тестовые ситуации, используемые по умолчанию для отдельных команд или групп команд.
Использование данной функции демонстрируется в примере 19.
Пример 19. Задание тестовой ситуации по умолчанию для команды ADD
01: 02: 03: |
set_default_situation 'add' do situation('my_situation') end |
Для всех команд ADD будет использоваться ситуация my_situation |
Последовательность команд с заданными для них тестовыми ситуациями может обрабатываться различными способами. Например, могут выбираться различные комбинации решений для отдельных тестовых ситуаций. Таким образом, одна последовательность команд может порождать несколько тестовых примеров. При этом несколько методов обработки тестовых ситуаций могут использоваться совместно. В качестве примера можно привести задачу построения тестов, покрывающих различные пути в заданной структуре переходов, базовые блоки которой содержат команды, вызывающие различные комбинации событий, связанных с доступом к памяти. Настройки компонентов, реализующих стратегии обработки тестовых ситуаций, задаются при помощи атрибута engines. В примере 20 показан блок, задающий настройки для стратегии обработки ситуаций, связанных с потоком управления и памятью.
Пример 20. Задание стратегий обработки тестовых ситуаций
01: 02: 03: 04: 05: 06: 07: 08: 09: 10: |
sequence( :engines => { :combinator => 'diagonal', :branch => {:branch_exec_limit => 3, :trace_count_limit => -1}, :memory => {:classifier => 'event-based', :page_mask => 0x0fff)} }) { ... } |
Способ объединения результатов работы компонентов Настройки для компонента branch Настройки для компонента memory |
Назначение данных настроек не имеет значения для языка описания тестовых шаблонов т.к. различные реализации этих компонентов будут иметь свои наборы настроек. Язык же предоставляет возможность задавать любые наборы настроек.
2.2.7 Описание инициализирующего кода и встроенных проверок
Тестовые данные, сгенерированные в результате обработки тестовых ситуаций, необходимо поместить в регистры или память. Для этой цели создается специальный инициализирующий код. Он строится на основе сгенерированных данных и правил, описанных в шаблонах. Данные правила называются препараторами (preparator). Они определяются в методе pre тестового шаблона. Поддерживаются следующие виды препараторов:
· препараторы для регистров;
· препараторы для регистров на основе потоков данных;
· препараторы для памяти.
Набор препараторов может быть расширен путем написания соответствующих библиотек. Далее поддерживаемые препараторы будут рассмотрены более подробно.
Кроме этого тестовые примеры могут включать в себя встроенные проверки, которые проверяют состояние микропроцессора после выполнения тестового воздействия. Данные проверки строятся автоматически на основе информации о состоянии микропроцессора, предоставленной эмулятором, и правил, заданных в тестовых шаблонах. Эти правила называются компараторами (comparator) и содержат описания команд, используемых для создания встроенных проверок. По своим функциональным возможностям и синтаксису компараторы аналогичны препараторам. Два основных отличия заключаются в том, что они используются для сравнения текущих значений с эталонными и в случае несовпадения могут осуществлять переход в обработчику, отвечающему за создание сообщения от ошибке.
Препараторы для регистров
Данный тип препараторов описывает последовательности команд, которые помещают данные в регистры определенного типа, доступ к которым описан некоторыми режимами адресации. Т.к. для устранения избыточности важно, чтобы размер препараторов был минимальным, поддерживается возможность переопределения препараторов для отдельных случаев (определенные номера регистров, маски значений). Препараторы для регистров определяются при помощи конструкции preparator, которая использует следующие атрибуты для описания условий, при которых они будут применены:
· target - имя режима адресации, описывающего доступ к инициализируемому регистру;
· mask (необязательный атрибут) - маска, которой должны соответствовать данные, помещаемые в регистр;
· arguments (необязательный атрибут) - значения аргументов режима адресации (например, номер регистра), для которых применим данный препаратор;
· name (необязательный атрибут) - имя, которое уникально идентифицирует препаратор и используется для разрешения неоднозначностей в случаях когда, применимы несколько препараторов.
Кроме этого с целью улучшить тестовое покрытие поддерживается возможность определять несколько вариантов одного и того же препаратора, которые будут выбираться случайным образом в соответствии с заданным распределением. Они описываются при помощи конструкции variant, использующей два необязательных атрибута: (1) name, который задает уникальный идентификатор, используемый для выбора конкретного варианта, и (2) bias, который задает вес данного варианта.
Код препараторов использует ключевые слова target и value для обозначения инициализируемого регистра и записываемого в него значения соответственно. В случаях, когда необходимо использовать отдельные поля значения value, диапазон этих полей задается при помощи необязательных параметров. В примере 21 описывается препаратор для архитектуры MIPS32, который инициализирует 32-битрые регистры общего назначения R двумя способами (используя непосредственные значения и данные из памяти) и включает два дополнительных особых случая (для значений 0 и -1).
Пример 21. Препаратор для регистров общего назначения архитектуры MIPS32
01: 02: 03: 04: 05: 06: 07: 08: 09: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: |
preparator(:target => 'R') { variant(:bias => 25) { data { label :preparator_data word value } la at, :preparator_data lw target, 0, at } variant(:bias => 75) { lui target, value(16, 31) ori target, target, value(0, 15) } } preparator(:target => 'R', :mask => '00000000') { xor target, zero, zero } preparator(:target => 'R', :mask => 'FFFFFFFF') { nor target, zero, zero } |
Препаратор для регистров R по умолчанию Регистр инициализируется при помощи команд чтения данных из памяти. Вес варианта: 25 Регистр инициализируется при помощи команд, использующих непосредственные аргументы. Вес варианта: 75 Особый случай для значения, равного 0 Особый случай для значения, равного -1 |
Язык тестовых шаблонов позволяет явным образом вставлять инициализирующий код в тестовые программы. Это позволяет сократить объем тестовых шаблонов и создавать сложные препараторы, основанные на повторном использовании имеющихся описаний. Для этой цели используется функция prepare, которая принимает в качестве параметров инициализируемый регистр, присваиваемое ему значение, а также необязательные атрибуты name и variant, позволяющие при необходимости выбрать конкретный вариант конкретного препаратора. Например, чтобы вставить в тестовую программу код, помещающий значение 0xDEADBEEF в регистр t0, можно использовать следующий код:
Пример 22. Вставка препаратора для регистра тестовую программу
01: |
prepare t0, 0xDEADBEEF |
Будет вставлен код, записывающий 0xDEADBEEF в регистр t0 |
Компараторы для регистров
Компараторы для регистров определяется аналогично препараторам. С точки зрения синтаксиса все разница заключается в том, что вместо ключевого слова preparator используется ключевое слово comparator. Ниже приведен пример компаратора для регистров общего назначения архитектуры MIPS32, включающего особый случай для эталонного значения равного нулю.
Пример 23. Компаратор для регистров общего назначения архитектуры MIPS32
01: 02: 03: 04: 05: 06: 07: 08: 09: |
comparator(:target => 'R') { prepare target, value bne at, target, :check_failed nop } comparator(:target => 'R', :mask => "00000000") { bne zero, target, :check_failed nop } |
Препараторы для регистров на основе потоков данных
Препараторов, записывающих значения в регистры, недостаточно для решения некоторых задач. К таким задачам относится построение инициализирующего кода для команд условного перехода, исполняемых в цикле. Данная задача решается при помощи препараторов для регистров на основе потока данных. Они позволяют записывать и читать данные из потоков (массивов, хранящихся в памяти). Эти препараторы используют два регистра: один хранит адрес, используемый для чтения данных, а другой - прочитанные или записанные данные. При таком подходе поток сначала заполняется данными, а потом они считываются в процессе выполнения тестового примера.
Препараторы данного вида описываются при помощи конструкции stream_preparator, которая использует атрибуты data_source и index_source для задания типов регистров, хранящих данные и адрес. Препаратор включает в себя три секции: (1) init, которая содержит код инициализации регистра адреса начальным адресом потока; (2) read, которая содержит код чтения следующего элемента из потока и инкремента адреса; (3) write, которая содержит код записи нового элемента в поток и инкремента адреса. Внутри этих секций используются ключевые слова start_label, index_source и data_source для обозначения метки, указывающей на начало потока данных, и регистров адреса и данных соответственно. Пример 24 демонстрирует описание препаратора на основе потока данных для архитектуры ARMv8.
Пример 24. Препаратор для регистров на основе потока данных для архитектуры ARMv8
01: 02: 03: 04: 05: 06: 07: 08: 09: 10: 11: 12: 13: |
stream_preparator(:data_source => 'REG', :index_source => 'REG') { init { adr index_source, start_label } read { ldar data_source, index_source add index_source, index_source, 8, 0 } write { stlr data_source, index_source add index_source, index_source, 8, 0 } } |
Препараторы для памяти
Еще один тип поддерживаемых препараторов - препараторы для памяти, описывающие код размещения данных по заданному адресу. Язык тестовых шаблонов позволяет определить несколько вариантов препаратора данного типа, которые будет использоваться для блоков данных разного размера. Препараторы для памяти описываются при помощи конструкции memory_preparator, которая использует атрибут size для задания размера размещаемого в памяти блока данных. В коде препаратора используются ключевые слова address и data для обозначения адреса размещения и размещаемых данных соответственно. Пример 25 содержит код, размещающий в памяти 64-битные блоки данных, для архитектуры MIPS32.
Пример 25. Препаратор, размещающих 64-битные блоки данных в памяти
01: 02: 03: 04: 05: 06: 07: |
memory_preparator(:size=>64) { la t0, address prepare t1, data(31, 0) sw t1, 0, t0 prepare t1, data(63, 32) sw t1, 4, t0 } |
2.2.8 Описание правил рандомизации
Многие задачи генерации предполагают случайный выбор на основе заданного распределения. Для этой цели язык тестовых шаблонов предоставляет следующие конструкции:
· range - описывает выбираемые значения и их веса, которые задаются атрибутами value и bias соответственно. Значения могут принимать одну из следующих форм: (1) одиночное значение; (2) диапазон значений; (3) массив значений; (4) распределение значений.
· dist - описывает распределения в виде набора значений, описанных конструкцией range. Если веса значений не заданы, это означает равномерное распределение.
Пример 26 демонстрирует использование описанных выше конструкций для описания распределения целых чисел.
Пример 26. Описание случайных распределений целых чисел
01: 02: 03: 04: 05: 06: 07: 08: 09: |
simple_dist = dist( range(:value => 0, :bias => 25), range(:value => 1..2, :bias => 25), range(:value => [3, 5, 7], :bias => 50) ) composite_dist = dist( range(:value=> simple_dist, :bias => 80), range(:value=> [4, 6, 8], :bias => 20) ) |
Описанные распределения можно использовать для построения значений непосредственных аргументов команд или значений используемых ими регистров. Пример 27 демонстрирует возможные способы их использования.
Пример 27. Использование случайных распределений целых чисел для генерации значений
01: 02: 03:: |
addi t0, t1, rand(simple_dist) addi t2, t3, _ do situation('random', :dist => simple_dist) end prepare t4, rand(simple_dist) |
Непосредственное значение Тестовая ситуация Инициализирующий код |
Случайный выбор на основе вероятностных распределений может использоваться также для выбора тестовых ситуаций, применяемых к командам. Для этой цели описывается распределение, в котором в качестве значений выступают ситуации, и которое связывается с командой при помощи конструкции random_situation. Данный подход показан в примере 28.
Пример 28. Случайный выбор тестовых ситуаций на основе заданного распределения
01: 02: 03: 04: 05: |
sit_dist = dist(range(:value => situation('overflow')), range(:value => situation('normal')), range(:value => situation('zero')), range(:value => situation('random', :dist => int_dist))) add t1, t2, t3 do random_situation(sit_dist) end |
Кроме этого случайным образом могут выбираться отдельные команды и целые блоки команд. Случайный выбор команд связан с понятием групп команд. Они описываются в формальных спецификациях и используются в тестовых шаблонах, когда необходимо выбрать случайную команду заданной группы. Группы команд также можно определять в тестовых шаблонах. Для этого описывается распределение для имен команд, на основе которого при помощи функции define_group определяется группа. Определение и использование группы команд показано в примере 29.
Пример 29. Определение и использование группы команд
01: 02: 03: 04: 05: |
alu_dist = dist(range(:value => 'add', :bias => 40), range(:value => 'sub', :bias => 30), range(:value => ['and', 'or', 'nor', 'xor'], :bias => 30)) define_group('alu', alu_dist) alu t0, t1, t2 |
Описание распределение Определение группы Случайная команда из группы |
Для случайного выбора последовательностей команд определяется распределение, в котором в качестве значений выступают последовательности команд. После этого при помощи функции random_sequence задается случайный выбор последовательности. В примере 30 демонстрируется случайный выбор последовательностей команд.
Пример 30. Случайный выбор последовательностей команд
01: 02: 03: 04: |
seq_dist = dist(range(:value => lambda do sequence{...} end, :bias => 50), range(:value => lambda do sequence{...} end, :bias => 35), range(:value => lambda do sequence{...} end, :bias => 15)) random_sequence(seq_dist) |
Описанные выше подходы к описанию правил случайной генерации можно применять в комбинации для построения сложных тестовых примеров.
2.2.9 Описание размещения команд и данных в памяти
Ассемблерные программы содержат директивы, позволяющие задать адреса размещения их данных и команд в памяти. Язык тестовых шаблонов предоставляет аналогичные конструкции. В настоящий момент поддерживаются директивы: (1) org, которая задает адрес размещения в виде смещения от базового адреса, и (2) align, которая задает выравнивание для адреса размещения. Смещение можно также задавать как смещение от адреса конца предыдущего размещения, используя атрибут delta. Помимо этого язык предоставляет функцию label, которая позволяет определить метки для адресов тестовой программы. Пример 31 демонстрирует использование директив размещения и меток.
Пример 31. Использование директив размещения и меток
01: 02: 03: 04: 05: |
org 0x00001000 label :start add t0, t1, t2 align 4 add t0, t1, t2 |
Начальное смещение 0x00001000 Объявление метки start Выравнивание адреса на 16 байт |
Данные описываются внутри блока data. Формат описания данных основан на типах данных, объявленных в формальных спецификациях. Пример 32 иллюстрирует определение блока данных для архитектуры MIPS32.
Пример 32. Определение блока данных
01: 02: 03: 04: 05: 06: 07: |
data { org 0x00001000 label :byte_values byte 1, 2, 3, 4 label :word_values word 0xDEADBEEF, 0xBAADF00D } |
2.2.10 Описание структуры переходов между тестовыми примерами
Сложные сценарии тестирования могут предполагать выполнение тестовых примеров в произвольном порядке, циклическое выполнение или выполнение на разных ядрах микропроцессора. За организацию структуры переходов между тестовыми примерами отвечает код диспетчеризации (dispatching code), который размещается снаружи блоков, описывающих тестовые примеры. Этот код содержит глобальные метки и команды, осуществляющие переход по глобальным меткам, объявленных в других частях тестового шаблона. При этом тестовые примеры имеют единственную точку входа и не осуществляют глобальных переходов. В примере 33 показан код, организующий выполнение двух тестовых примеров на разных ядрах (архитектура ARMv8).
Пример 33. Описание тестовых примеров, выполняемых на разных ядрах
01: 02: 03: 04: 05: 06: 07: 08: 09: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: |
mrs x1, mpidr_el1 cmp x1, 0, 0 b ne, :pe1_label label :pe0_label sequence { ... }.run b_imm :end_label label :pe1_label sequence { ... }.run b_imm :end_label org 0x2100 label :end_label nop |
Код диспетчеризации: передает управление на метку pe0_label для ядра 0 и на метку pe1_label для ядра 1 Тестовый пример для ядра 0 Тестовый пример для ядра 1 Код диспетчеризации: сюда выполняется переход для обоих ядер после завершения выполнения тестовых примеров |
2.2.11 Расширяемость языка описания шаблонов тестовых программ
Расширяемость генератора тестовых программ предполагает расширяемость языка, используемого для описания шаблонов. Важно заметить, что расширение функциональных возможностей генератора не обязательно требует добавления новых языковых конструкций. Многие конструкции используют наборы атрибутов, которые задают свойства описываемых сущностей. В таких случаях расширение языка сводится, к добавлению новых атрибутов или использованию новых значений для уже имеющихся атрибутов. Такой подход применим для решения следующих задач:
· поддержка новых способов построения последовательностей команд;
· поддержка новых стратегий выбора регистров;
· поддержка новых типов тестовых ситуаций;
· поддержка новых способов комбинирования тестовых ситуаций;
· поддержка новых способов обработки тестовых примеров.
Однако некоторые задачи могут потребовать создания новых языковых конструкций. К таким задачам относятся:
· поддержка препараторов и компараторов нового типа;
· поддержка возможности описания пользовательских ограничений.
Для решения этих задач необходимо разработать Ruby-классы, реализующие соответствующие методы и добавить их в список классов, наследуемых базовым классом Template. При этом блочные конструкции реализуются как методы, принимающие в качестве параметра лямбда-функцию, которая содержит код, находящийся внутри блока. Это позволит анализировать его в отдельном контексте независимо от других конструкций языка. Таким образом, новые конструкции не будут конфликтовать с уже имеющимися.
2.3 Архитектура генераторов тестовых программ
Генераторы тестовых программ состоят из архитектурно-независимого ядра и модели микропроцессора, конструируемой на основе формальных спецификаций. Генерация осуществляется ядром, которое получает всю необходимую для этого информацию из модели и шаблонов тестовых программ. Ядро включает в себя несколько основных компонентов, каждый из которых отвечает за решение отдельной задачи. Архитектура генератора тестовых программ показана на рисунке 5.
Рисунок 5. Архитектура генератора тестовых программ
Процесс генерации включает в себя две основные стадии: (1) анализ шаблона с целью построения его внутреннего представления и (2) обработка внутреннего представления с целью генерации тестовых программ. Эти задачи решаются при помощи анализатора (template analyzer) и обработчика (template processor) шаблонов соответственно. В процессе обработки внутреннего представления строятся последовательности команд, удовлетворяющие заданным для них структурным и поведенческим свойствам, из которых составляется тестовая программа. За удовлетворение структурных свойств отвечает итератор последовательностей (sequence iterator), который строит множество последовательностей, соответствующих описанной структуре. Построенные последовательности передаются обработчику последовательностей (sequence processor), задача которого заключается в удовлетворении заданных для них поведенческих свойств. Для этого формируются ограничения на входные данные команд, которые должны быть выполнены для достижения тех или иных тестовых ситуаций. На основе ограничений генерируются данные и строится код, размещающий их регистрах или памяти. Основные компоненты генератора и механизмы их взаимодействия подробно рассмотрены в следующих разделах.
2.3.1 Анализатор шаблонов тестовых программ
Анализатор шаблонов тестовых программ (template analyzer) обрабатывает шаблоны с целью построения их внутреннего представления. Так как шаблоны представляют собой классы на языке Ruby, их обработка сводится к исполнению их кода, в результате которого конструируются объекты внутреннего представления. Анализатор шаблонов тестовых программ включает в себя следующие основные компоненты: (1) интерпретатор языка Ruby; (2) библиотеки Ruby-модулей, описывающие конструкции языка; (3) классы, описывающие объекты внутреннего представления; (4) фабрики, вызываемые Ruby-модулями для построения объектов внутреннего представления. Процесс анализа шаблона состоит из следующих шагов:
1. интерпретатор загружает Ruby-класс, описывающий шаблон;
2. в процессе загрузки класса вызывается библиотечный код, создающий на основе метаданных из модели микропроцессора динамические методы, позволяющие описывать свойства элементов конкретной архитектуры;
3. вызываются методы загруженного Ruby-класса, которые содержат код, вызывающий статические и динамические методы библиотечных модулей, отвечающие за создание объектов внутреннего представления;
4. вызываемые методы библиотечных модулей обращаются к фабрикам, которые конструируют объекты внутреннего представления.
Внутреннее представление шаблона отражает структуру генерируемой тестовой программы. Оно включает в себя последовательность объектов, описывающих его части (пролог, эпилог, тестовые примеры и код диспетчеризации), называемых блоками. Они соответствуют блочным конструкциям языка шаблонов, используемых для описания тестовых примеров. При этом пролог, эпилог и код диспетчеризации, не использующие блочные конструкции, рассматриваются как элементарные блоки. Помимо блоков, внутреннее представление включает в себя объекты, описывающие препараторы и компараторы.
Конструирование внутреннего представления осуществляются следующими методами класса шаблона, которые вызываются в указанном порядке:
· pre - конструирует внутреннее представление пролога тестовой программы, а также всех препараторов и компараторов;
· post - конструирует внутреннее представление эпилога тестовой программы;
· run - конструирует внутреннее представление тестовых примеров и кода диспетчеризации.
Блоки внутреннего представления содержат в себе списки объектов, описывающих команды или вложенные блоки. Кроме этого каждых блок снабжен атрибутами, задающими способ построения последовательностей команд. Объекты, описывающие команды, которые принято называть абстрактными командами. Они используют следующие атрибуты: (1) имя команды; (2) передаваемые ей аргументы; (3) заданная для нее тестовая ситуация; (4) объект метаданных, описывающий ее свойства.
Аргументами команды может быть другая команда (для VLIW-команд), режим адресации (addressing mode) или непосредственное значение (immediate value). Режимы адресации имеют набор атрибутов, аналогичный командам, с тем отличием, что все аргументы являются непосредственными значениями. Непосредственные аргументы команд и режимов адресации могут иметь фиксированное значение или их значение может генерировать по определенным правилам.
Помимо команд блоки могут содержать в себя следующие типы объектов: метки; секции данных; директивы ассемблера; текстовые сообщения; специальные инструкции для управления работой генератора, и т.д. Они задаются как атрибуты команд. Это позволяет зафиксировать их местоположение относительно соседних команд. Также для них могут создаваться специальные псевдокоманды, содержание только объекты перечисленных типов.
Анализатор является архитектурно-независимым. Он применим к любой архитектуре, описанной моделью микропроцессора (создаваемые им объекты внутреннего представления основаны на матаданных модели). Анализатор также является расширяемым. Это означает, что в язык описания шаблонов можно добавлять конструкции для задания новых свойств генерируемых программ. Для этого необходимо разработать и зарегистрировать следующие компоненты: (1) Ruby-модуль, описывающий новую конструкцию; (2) фабрику, конструирующую соответствующий объект внутреннего представления, и (3) классы, на основе которых будет построен этот объект.
2.3.2 Обработчик внутреннего представления
Обработчик шаблонов тестовых программ (template processor) осуществляет генерацию тестовых программ путем обработки блоков внутреннего представления, построенных анализатором шаблонов. Блоки обрабатываются по отдельности в определенном порядке. В результате их обработки строятся фрагменты кода (последовательности команд), удовлетворяющие заданным структурным и поведенческим свойствам, из которых затем составляются тестовые программы.
Код, построенный в результате обработки блока, исполняются на эмуляторе. Это позволяет отслеживать текущее состояние микропроцессора в процессе генерации. Такая возможность необходима для построения тестовых примеров с учетом текущего состояния микропроцессора, создания встроенных проверок и контроля корректности построенного кода. Также это позволяет обеспечить обработку блоков в соответствии с порядком исполнения описываемых ими фрагментов кода. Это важно, так как между ними могут быть зависимости по состоянию микропроцессора.
Процесс генерации тестовых программ обработчиком тестовых шаблонов включает следующие стадии:
1. Построение кода пролога, эпилога и секций диспетчеризации (имеющих фиксированные адреса), а также размещение пролога, секций диспетчеризации и всех глобальных данных в памяти эмулятора. При этом фиксируются адреса тестовых примеров, которые будут размещаться следом за построенными секциями кода.
2. Исполнение построенного кода на эмуляторе с целью подготовки начального состояния, необходимого для генерации тестовых примеров. Исполнение приостанавливается при достижении конца построенного кода.
3. Генерация кода тестового примера, адрес размещения которого был достигнут в процессе исполнения построенного ранее кода, и размещение его в памяти эмулятора.
4. Запуск исполнения построенного тестового примера. Исполнение приостанавливается при достижении адреса его конца.
5. Построение кода встроенных проверок на основе информации о текущем состоянии микропроцессора, полученной из эмулятора. При этом построенный код размещается в памяти эмулятора и исполняется.
6. Запуск исполнения с последней точки остановки. При достижении адреса начала еще не построенного тестового примера, выполняется переход к стадии 3. При достижении адреса начала кода диспетчеризации, который еще не был размещен в памяти (адрес не был зафиксирован), он размещается в памяти, а стадия 6 повторяется.
7. Размещение в памяти и исполнение эпилога.
8. Печать сгенерированного кода в виде программы на языке ассемблера.
Построение кода для блоков, описывающих различные части тестовых программ, осуществляется по-разному. По способы обработки блоки можно разделить на два типа: (1) со статической структурой и (2) с динамической структурой. К первому типу относятся пролог, эпилог и код диспетчеризации. Каждый из них порождает единственную последовательность команд, которая имеет жестко заданную структуру и использует фиксированные значения аргументов команд. Ко второму типу относятся блоки, описывающие тестовые примеры. Они порождают одну или несколько последовательностей команд, удовлетворяющих заданным структурным и поведенческим свойствам.
Для блоков со статической структурой процесс построения последовательности команд выглядит следующим образом. На основе блока строится абстрактная последовательность команд, состоящая из абстрактных команд, порядок которых полностью соответствует шаблонному описанию. О такой последовательности говорят, что она имеет фиксированный адрес, если в ее начале располагаются директивы ассемблера (такие как .org), задающие ее смещение. Построенная абстрактная последовательность передается специальной фабрике, реализуемой моделью микропроцессора, которая строит на ее основе конкретную последовательность команд. Такая последовательность состоит из конкретных команд, реализуемых эмулятором, которые можно исполнить и сохранить в ассемблерном и бинарном форматах.
Обработка блоков с динамической структурой несколько сложнее. На основе блоков данного типа строится одна или несколько абстрактных последовательностей (способы их построения будут рассмотрены в следующем подразделе). Затем осуществляется обработка этих последовательностей, которая включает в себя следующие стадии: (1) выбор регистров, используемых командами; (2) построение ограничений для тестовых ситуаций, связанных с командами; (3) генерация входных данных для команд посредством разрешения ограничений; (4) построение инициализирующего кода для сгенерированных данных. В результате обработки сначала получаются уточненные абстрактные последовательности (их число может быть больше числа оригинальных последовательностей), а затем на их основе строятся конкретные последовательности. Стадии построения этих последовательностей будут более подробно рассмотрены далее.
Построенные конкретные последовательности помещаются в память эмулятора и исполняются, а после завершения обработки всех блоков печатаются в виде тестовой программы.
2.3.3 Итератор последовательностей команд
Построение абстрактных последовательностей на основе блоков внутреннего представления осуществляется следующим образом. Каждый блок реализует итератор абстрактных последовательностей команд: итераторы неэлементарных блоков строятся на основе итераторов вложенных блоков; итератор элементарного блока возвращает одну последовательность, состоящую из единственной команды. Для блоков задаются атрибуты, которые определяют способ построения итератора последовательностей блока из итераторов вложенных блоков. Поддерживаются следующие атрибуты:
· combinator - техника комбинирования последовательностей, возвращаемых итераторами вложенных блоков;
· permutator - техника модификации комбинации последовательностей;
· compositor - техника композиции последовательностей;
· rearranger - техника перегруппировки последовательностей;
· obfuscator - техника модификации последовательности.
Построение итератора последовательностей для неэлементарного блока осуществляется путем последовательного применения перечисленных техник. Перечисленные техники реализуются в виде компонентов, которые имеют названия комбинатор, пермутатор, композитор, перегруппировщик и обфускатор соответственно. Может существовать несколько различных реализаций одной и той же техники. Набор компонентов может быть расширен пользовательскими реализациями.
Для демонстрации функций перечисленных техник будет использоваться описание шаблонное блока из примера 34.
Пример 34. Блок, параметризированный методами построения последовательностей
01: 02: 03: 04: 05: 06: 07: 08: 09: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: |
# несколько комбинированных последовательностей block( :combinator => `combinator-name', :permutator => `permutator-name', :compositor => `compositor-name', :rearranger => `rearranger-name', :obfuscator => `obfuscator-name') { # 3 последовательности длины 1: {A11}, {A21} и {A31} iterate { # Блок A A11, A21, A31, } # 2 последовательности длины 2: {B11, B12} и {B21, B22} iterate { # Блок B sequence { B11, B12 } sequence { B21, B22 } } # 1 последовательность длины 3: {C11, C12, C13} iterate { # Блок C sequence { C11, C12, C13 } } } |
Комбинаторы
Комбинатор - это компонент, осуществляющий комбинирование последовательностей, возвращаемых итераторами вложенных блоков. Он принимает на вход упорядоченный набор итераторов последовательностей и выдает на выход итератор по комбинациям, построенным из этих последовательностей. Комбинацией последовательностей - это кортеж из нескольких последовательностей (их число совпадает с числом вложенных блоков). Поддерживаются следующие комбинаторы (возможные значения combinator-name): (1) diagonal (используется по умолчанию); (2) product; (3) random.
Комбинатор diagonal синхронно итерирует последовательности вложенных блоков. Комбинирование завершается, когда исчерпываются все вложенные итераторы. Каждый раз, когда вложенный итератор исчерпывается, он инициализируется повторно. Число комбинаций, выдаваемых комбинатором diagonal, равно max(Ni,...,Nk), где Ni - число последовательностей, возвращаемых итератором i-ого блока. На рисунке 6 показаны комбинации, возвращаемые комбинатором diagonal для блока, описанного в примере 34.
#1 |
#2 |
#3 |
|
Рисунок 6. Комбинации, возвращаемые комбинатором diagonal
Комбинатор product строит все возможные комбинации последовательностей, возвращаемых итераторами вложенных блоков. Число комбинаций, выдаваемых комбинатором product, равно , где Ni -число последовательностей, возвращаемых итератором i-ого блока. На рисунке 7 показаны комбинации, возвращаемые комбинатором product.
#1 |
#2 |
#3 |
#4 |
#5 |
#6 |
|
Рисунок 7. Комбинации, возвращаемые комбинатором product
Комбинатор random выдает одну случайную комбинацию последовательностей, возвращаемых итераторами вложенных блоков. Число комбинаций, выдаваемых комбинатором , равно 1. На рисунке 8 показывается один из возможных вариантов комбинации, которую может возвратить комбинатор random.
...Подобные документы
Применение тестовых заданий на уроках информатики. Основные виды тестовых заданий. Подбор тестовых заданий по темам курса информатики. Программные продукты для разработки и создания тестовых заданий. Общие правила оформления компьютерных тестовых заданий.
курсовая работа [2,2 M], добавлен 28.09.2011Методика разработки внешних спецификаций программ, основанных на использовании HIPO-технологии проектирования программ. Приобретение практических навыков определения и оформления внешних спецификаций программ. Схема состава разложения и IPO-диаграммы.
лабораторная работа [45,6 K], добавлен 15.03.2009Формирование требований к подсистеме генерации тестовых заданий в открытой системе дистанционного образования, проектирование подсистемы генерации тестовых заданий в открытой системе дистанционного обучения, реализация пользовательского интерфейса.
курсовая работа [3,3 M], добавлен 28.08.2012Логические функции и структура микропроцессоров, их классификация. История создания архитектуры микропроцессоров x86 компании AMD. Описание К10, система обозначений процессоров AMD. Особенности четырёхъядерных процессоров с микроархитектурой К10 и К10.5.
курсовая работа [28,9 K], добавлен 17.06.2011Технологии производства микропроцессоров: основные этапы производства. Выращивание диоксида кремния и создание проводящих областей и тестирование. Особенности производства микропроцессоров. Производство подложек, легирование, диффузия, фотолитография.
курсовая работа [4,7 M], добавлен 26.03.2009Программа как совокупность данных и команд, предназначенных для функционирования ЭВМ и других компьютерных устройств. Этапы создания программ: каскад, инкремент, эволюция. Порядок написания исходного кода и его компиляция. Сборка статической библиотеки.
презентация [119,4 K], добавлен 05.01.2014Автоматизация учета закупки лекарственных препаратов в аптеке. Разработка базы данных и прикладных программ для работы с ней. Анализ предметной области и формирование требований пользователей. Выбор архитектуры программно-технологической реализации.
курсовая работа [4,1 M], добавлен 10.09.2015Унифицированный язык моделирования UML. Проектирование и документирование программных систем. Листинги кода проектируемой программы, сгенерированные RationalRose. Модель информационной подсистемы для управления, учета, контроля и ведения библиотеки.
курсовая работа [1,3 M], добавлен 22.06.2011Исследование и оценка возможностей работы со следующими разделами библиотеки приложения Simulink пакета программ Matlab: Source, Sinks, Continuous, Math Operation. Функции по представлению полученных в результате моделирования данных в графическом виде.
лабораторная работа [438,9 K], добавлен 23.09.2022Степень переносимости исходного кода между различными платформами. Первый язык программирования высокого уровня, имеющий транслятор. Программа Fortran, ее версии, отличия от других программ. Составление программ на языке программирования Fortran.
курсовая работа [45,5 K], добавлен 04.06.2014Анализ существующих систем автоматизации документооборота. Выбор шаблона проектирования. Microsoft SQL Server как комплексная высокопроизводительная платформа баз данных. Язык программирования C#. Разработка интерфейса и иллюстрация работы системы.
дипломная работа [2,5 M], добавлен 19.07.2014Основные характеристики микропроцессоров: тактовая частота, кэш память, дополнительные инструкции, разрядность, архитектура, количество ядер. История развития микропроцессоров, главные фирмы-производители. Разработка программы работы с массивом.
курсовая работа [139,4 K], добавлен 24.06.2011Понятие, виды и функции тестов, компьютерное тестирование. Государственные стандарты создания компьютерных тестов и практическая реализация комплекса генерации тестов: СУБД и язык программирования, пользовательский интерфейс, экономическая эффективность.
дипломная работа [2,1 M], добавлен 29.06.2012Основные составляющие компьютерной системы. История развития, особенности применения микропроцессоров. Устройство и работа D-триггера. Принципиальная электрическая схема, директивы, операторы и описание программы для микропроцессоров, виды отладчиков.
методичка [2,9 M], добавлен 27.11.2011Объем двухпортовой памяти, расположенной на кристалле, для хранения программ и данных в процессорах ADSP-2106x. Метод двойного доступа к памяти. Кэш-команды и конфликты при обращении к данным по шине памяти. Пространство памяти многопроцессорной системы.
реферат [28,1 K], добавлен 13.11.2009Изучение понятия архивации, сжатия файлов с целью экономии памяти и размещения сжатых данных в одном архивном файле. Описания программ, выполняющих сжатие и восстановление сжатых файлов в первоначальном виде. Основные преимущества программ-упаковщиков.
контрольная работа [534,7 K], добавлен 11.01.2015Нахождение наибольшего элемента массива, расположенного во внутренней памяти, сохранение его значения в стеке. Описание используемых команд: запись из внутренней памяти в аккумулятор данных, безусловный переход по метке. Составление кода программы.
лабораторная работа [41,4 K], добавлен 18.11.2014Язык Паскаль - процедурно-ориентированный язык высокого уровня, его основные достоинства. Разработка программ для решения задач. Выбор метода обработки информации. Форма представления исходных данных. Разработка алгоритма, его описание, листинг программы.
курсовая работа [3,6 M], добавлен 17.02.2013Семь поколений процессоров. Технология производства микропроцессоров. Сравнительные характеристики процессоров AMD и Intel на ядре Clarkdale. Квазимеханические решения на основе нанотрубок. Одновременная работа с Firefox и Windows Media Encoder.
дипломная работа [2,2 M], добавлен 11.06.2012Понятия и принцип работы процессора. Устройство центрального процессора. Типы архитектур микропроцессоров. Однокристальные микроконтроллеры. Секционные микропроцессоры. Процессоры цифровой обработки сигналов. Эволюция развития микропроцессоров Intel.
реферат [158,8 K], добавлен 25.06.2015