Project

General

Profile

Actions

Язык описания шаблонов тестовых программ » History » Revision 71

« Previous | Revision 71/89 (diff) | Next »
Alexander Kamkin, 10/04/2011 06:49 PM


Язык Ruby-MT описания шаблонов тестовых программ

Язык Ruby-MT (Ruby for MicroTESK) предназначен для компактного и переиспользуемого описания функциональных тестов для микропроцессоров и других программируемых устройств. Язык представляет собой смесь языка ассемблера целевого микропроцессора (TL, Target Language) и управляющего языка высокого уровня (ML, Meta Language). При этом ML можно рассматривать как макропроцессор, поскольку в результате выполнения его конструкций генерируется текст на TL. Язык построен на основе Ruby в форме библиотечного расширения (не требуется дополнительных парсеров и т.п.).

Код на Ruby-MT описывает шаблон тестовой программы (далее для краткости шаблон). Обработка шаблона состоит из следующих шагов:

  1. Препроцессирование шаблона.
  2. Выполнение шаблона.

При выполнении шаблона, он генерирует программу путем обращения к базе данных ограничений, солверам и другим стандартным компонентам генератора через API генератора.

Интуитивное описание языка на примерах

Пример 1 (MIPS)

class MyTemplate < MipsTemplate
    # В базовом шаблоне содержатся общие инструкции инициализации микропроцессора
    def initialize()
        super()
    end

    # Главный метод теста
    def test()
        # Повторить в тестовой программе 100 раз следующую ситуацию
        100.times {
            # Добавление в тестовую программу комментария
            comment(''# --------------------------------------------------------------------------------'');
            # Загрузка в регистр reg1 содержимого памяти по адресу, содержащемуся в регистре base1
            # Функция r выделяет новый регистр общего назначения
            ld reg1=r, 0x0(base1=r)  ;; goal([L1Hit,50])                    # Попадание в кэш-память L1 осуществляются с вероятностью 50%
            # Загрузка в регистр reg2 содержимого памяти по адресу, содержащемуся в регистре base2
            ld reg2=r, 0x0(base2=r)  ;; goal(L1Hit && !Equal(base1, base2)) # Адреса первой и второй интрукции загрузки не должны совпадать
            # Запись результата сложения содержимого регистров reg1 и reg2 в регистр res
            dadd res=r, reg1, reg2   ;; goal(!IntegerOverflow)              # При сложении не должно возникать переполнения
        }
    end

    # В базовом шаблоне содержатся общие инструкции завершения работы микропроцессора
    def finalize()
        super.finalize()
    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 - это регистры общего назначения (некоторые из них совпадают друг с другом). В начале программы (возможно, и в некоторых промежуточных точках) располагается управляющий код, инициализирующий регистры и память так, чтобы удовлетворить заданным в шаблоне ограничениям (это наиболее интеллектуальная часть обработки шаблона).

Распределение регистров

Для распределения регистров в программе используются следующие правила:

1. Если в качестве регистра в шаблоне используется конкретный регистр, а не переменная, то этот регистр используется и в сгенерированной программе.

Например, для шаблона

ori @r, r0, 0x0

второй регистр инструкции ori (регистр r0) фиксирован. Примером программы, соответствующей этому шаблону является

ori r7, r0, 0x0 # Регистр r0 фиксирован

2. Если имена переменных, обозначающих регистры, в шаблоне совпадают (в пределах одной области видимости), то в соответствующих частях итоговой программы будет использоваться один и тот же регистр.

Например, для шаблона

2.times {
    add @r1, @r2, @r3
    sub @r4, @r1, @r5 # Результат сложения используется в качестве вычитаемого
}

первый регистр инструкции add всегда будет совпадать со вторым регистром инструкции sub, хотя эти регистры могут быть разными на разных итерациях:

# --------------------------------------------------------------------------------
add r2,  r10, r5
sub r9,  r2,  r15 # Зависимость по регистру r2
# --------------------------------------------------------------------------------
add r11, r27, r9
sub r9,  r11, r23 # Зависимость по регистру r11

Более сложный пример

ori @r1, r0, 0x0
2.times {
    add @r1, @r2, @r3 # Результат сложения заносится в тот же регистр, что и результат вышестоящей инструкции
    sub @r4, @r1, @r5 # Результат сложения используется в качестве вычитаемого
}

В этот случае регистры @r1 на первой и второй итерациях будут совпадать между собой и будут равны первому регистру инструкции ori.

А следующем примере никакой зависимости по регистрам нет, поскольку области видимости переменных-регистров не пересекаются:

2.times {
    add @r1, @r2, @r3
}
2.times {
    add @r1, @r2, @r3 # Зависимости по регистрам нет
}

3. Если имена переменных, обозначающих регистры, в шаблоне различаются, либо переменные находятся в разных областях видимости, либо для обозначения регистра используется символ *, то в соответствующих частях итоговой программы возможны совпадающие регистры.

Регистров ограниченное число, поэтому пересечения по регистрам неизбежны. По возможности, внутри одного блока используются разные регистры. Стратегию распределения регистров нужно обдумать.

Updated by Alexander Kamkin over 8 years ago · 71 revisions