Язык описания шаблонов тестовых программ¶
Язык Ruby-MT (Ruby for MicroTESK) предназначен для компактного и переиспользуемого описания функциональных тестов для микропроцессоров и других программируемых устройств. Язык представляет собой смесь языка ассемблера целевого микропроцессора (TL, Target Language) и управляющего языка высокого уровня (ML, Meta Language). При этом ML можно рассматривать как макропроцессор, поскольку в результате выполнения его конструкций генерируется текст на TL. Язык построен на основе Ruby в форме библиотечного расширения (не требуется дополнительных парсеров и т.п.).
Код на Ruby-MT описывает шаблон тестовой программы (далее для краткости шаблон). Обработка шаблона состоит из следующих шагов:
- Препроцессирование шаблона.
- Выполнение шаблона.
При выполнении шаблона, он генерирует программу путем обращения к базе данных ограничений, солверам и другим стандартным компонентам генератора через API генератора.
Интуитивное описание языка на примерах¶
Пример 1 (MIPS)¶
# В базовом шаблоне содержатся общие инструкции инициализации и завершения работы микропроцессора class MyTemplate < MIPS::Template # Главный метод теста def test() # Повторить в тестовой программе 100 раз следующую ситуацию 100.times { # Добавление в тестовую программу комментария text(''# --------------------------------------------------------------------------------''); # Загрузка в регистр reg1 содержимого памяти по адресу, содержащемуся в регистре base1 # Функция r выделяет новый регистр общего назначения ld reg1=r, 0x0(base1=r) ;; goal([l1Hit,50],[!l1Hit,50]) # Попадание в кэш-память L1 осуществляются с вероятностью 50% # Загрузка в регистр reg2 содержимого памяти по адресу, содержащемуся в регистре base2 ld reg2=r, 0x0(base2=r) ;; goal(l1Hit && !equal(base1, base2)) # Адреса первой и второй инструкции загрузки не должны совпадать ([base1] != [base2]) # Запись результата сложения содержимого регистров reg1 и reg2 в регистр res dadd res=r, reg1, reg2 ;; goal(!integerOverflow) # При сложении не должно возникать переполнения } end end
В результате обработки этого шаблона будет сгенерирована программа следующего вида.
... # -------------------------------------------------------------------------------- ld reg001_1, 0x0(base001_1) ld reg001_2, 0x0(base001_2) dadd res001, reg001_1, reg001_2 ... # -------------------------------------------------------------------------------- ld reg100_1, 0x0(base100_1) ld reg100_2, 0x0(base100_2) dadd res100, reg100_1, reg100_2 ...
В этой программе regXXX_{1,2}
, baseXXX_{1,2}
и resXXX
- это регистры общего назначения (некоторые из них совпадают друг с другом). В начале программы (возможно, и в некоторых промежуточных точках) располагается управляющий код, инициализирующий регистры и память так, чтобы удовлетворить заданным в шаблоне ограничениям.
TODO: нужно переопределить операции для тестовых ситуаций (!, &&, ||).
TODO: пока можно ограничиться случаем одной тестовой ситуации вgoal()
.
Распределение регистров¶
Для каждого типа регистров (GPR
, FPR
и т.п.) определена функция распределения регистров (например, функция r = def(GPR)
в примере выше). Эта функция имеет один целочисленный параметр - номер регистра. Если при вызове функции распределения регистров параметр не указан, функция выделяет регистр согласно некоторой стратегии распределения регистров. Например, она может возвращать один из не занятых регистров (естественно, возвращаемый регистр помечается как занятый). Поскольку регистров конечное число, не исключены случаи, когда все регистры заняты. В таких ситуациях логично выделять регистры, которые давно не использовались и попутно печатать предупреждение о нехватке регистров. При выходе из блока занятые в этом блоке регистры автоматически освобождаются. Кроме того, предусмотрена функция освобождения занятых регистров free
. Для того чтобы "застолбить" регистр reg
, нужно вызывать lock(reg)
.
Для распределения регистров в программе используются следующие правила:
1. Если в качестве регистра в шаблоне используется конкретный регистр, то этот регистр используется и в сгенерированной программе.
Например, для шаблона
ori reg=r, r0, 0x0
второй регистр инструкции ori
(регистр r0
) фиксирован. Примером программы, соответствующей этому шаблону является
ori r7, r0, 0x0 # Регистр r0 фиксирован
2. Если имена переменных, обозначающих регистры, в шаблоне совпадают (в пределах одной области видимости), то в соответствующих частях итоговой программы будет использоваться один и тот же регистр.
Например, для шаблона
2.times { add reg1=r, r, r sub r, reg1, r # Результат сложения используется в качестве вычитаемого }
первый регистр инструкции add
всегда будет совпадать со вторым регистром инструкции sub
, хотя эти регистры могут быть разными на разных итерациях:
# -------------------------------------------------------------------------------- add r2, r10, r5 sub r9, r2, r15 # Зависимость по регистру r2 # -------------------------------------------------------------------------------- add r11, r27, r9 sub r9, r11, r23 # Зависимость по регистру r11
Более сложный пример
ori reg1=r, r0, 0x0 2.times { add reg1, r, r # Результат сложения заносится в тот же регистр, что и результат вышестоящей инструкции sub r, reg1, r # Результат сложения используется в качестве вычитаемого }
В этот случае регистры reg1
на первой и второй итерациях будут совпадать между собой и будут равны первому регистру инструкции ori
.
В следующем примере зависимости по регистрам нет, поскольку области видимости переменных-регистров не пересекаются (теоретически, но маловероятно, номера регистров могут совпасть):
2.times { add reg1=r, r, r } 2.times { add reg1=r, r, r # Зависимость по регистрам не предполагается }
3. Если имена переменных, обозначающих регистры, в шаблоне различаются, либо переменные находятся в разных областях видимости, то в соответствующих частях итоговой программы возможны совпадающие регистры.
Регистров ограниченное число, поэтому пересечения по регистрам неизбежны.
Updated by Artemiy Utekhin almost 12 years ago · 1 revisions