UVM тест таблицы sin/cos
Оглавление
2 Описание тестируемого компонента
3 Описание тестового окружения
Список условных обозначений, сокращений и терминов
DUT – Design Under Test
HDL – Hardware Description Language
TLM – Transaction-Level Modeling
UVM – Universal Verification Methodology
VHDL – Very High Speed Integrated Circuit Hardware Description Language
1 Аннотация
В данном руководстве описывается пример построения тестового окружения с использованием UVM для проверки компонента, описанного при помощи HDL.
В качестве тестируемого компонента (DUT) используется таблица синуса/косинуса, описанная на языке VHDL.
Схема подключения тестируемого компонента к тестовому окружению показана на рисунке 1.1.
Рисунок 1.1 – Схема тестового окружения
В простейшем случае тестовое окружение содержит в себе:
- генератор входных воздействий для подачи сигнала на DUT;
- некий эталон, функциональность которого должен реализовывать DUT;
- блок сравнения результатов, полученных с эталона и с выхода DUT.
По результатам работы тестового окружения делается вывод о том, насколько тестируемый компонент соответствует эталонной модели.
2 Описание тестируемого компонента
Тестируемый компонент представляет собой табличную реализацию функций sin(x), cos(x)
в целочисленной арифметике. Графики данных функций представлены на рисунке 2.1
Рисунок 2.1 – Графики функций sin(x), cos(x)
Интерфейс DUT представлен в таблице 2.1.
Таблица 2.1 – Порты DUT
Name | Dir | Type | Description |
iCLK | in | std _ logic | тактовый сигнал |
iPHASE _ V | in | std _ logic | входная значимость |
iPHASE | in | [12]unsigned | фаза |
oSINCOS _ V | out | std _ logic | выходная значимость |
oSIN | out | [16]signed | синус |
oCOS | out | [16]signed | косинус |
Вход фазы iPHASE, сопровождаемый значимостью iPHASE _ V, представляет собой 12-битное беззнаковое число в диапазоне [0...4095].
Выходы синуса oSIN и косинуса oCOS представляют собой 16-битные знаковые числа в диапазоне [−32768...32767] и сопровождаются значимостью oSINCOS _ V.
Временная диаграмма работы DUT представлена на рисунке 2.2.
Рисунок 2.2 – Временная диаграмма DUT
Приведенная диаграмма не учитывает задержку между приемом и выдачей данных, а также особенности тактирования |
Функциональная схема тестируемого компонента показана на рисунке 2.3
Рисунок 2.3 – Функциональная схема DUT
Ядро компонента – память, в которой хранятся значения синуса/косинуса. Для заполнения памяти используется функция инициализации.
Помимо этого в компоненте используются триггеры для входных и выходных сигналов. Сигнал значимости транслируется со входа на выход с учетом используемых триггеров.
3 Описание тестового окружения
3.1 Верхний уровень
Верхним уровнем тестового окружения в данном примере является файл tb _ top.sv. Создаем файл с таким названием и следующим содержанием:
|
Здесь нет ничего интересного, кроме объявленного тактового сигнала.
Сюда необходимо подключить тестируемый компонет, для этого создадим файл sincos _ if.sv, в котором опишем следующий интерфейс:
|
Данный интерфейс схож с интерфейсом тестируемого компонента (см. 2). Дополним файл tb _ top.sv кодом подключения DUT:
|
Особенности сопряжения интерфейсов при симуляции смешанных (VHDL/Verilog) исходников читайте в документации к используемому вами симулятору. |
На текущем этапе наше тестовое окружение имеет вид, показанный на рисунке 3.1.
Рисунок 3.1 – Тестовое окружение
На одном клоке далеко не уедешь, поэтому самое время добавить немножко UVM.
3.2 Тест
Первое, что необходимо сделать для создания тестового окружения по UVM, это создать тест. Для этого используется класс uvm _ test.
Создадим файл sincos _ test _ default.svh со следующим содержанием:
|
Здесь для нашего теста объявлен класс, наследующий класс uvm _ test. Чтобы тест можно было запустить, в нем, как минимум, должен быть объявлен конструктор (функция new()).
Поскольку это ООП, то во всех классах должна быть реализована функция-конструкторкласса. |
Для удобства подключения UVM компонентов, которые мы будем реализовывать, создадим пакет sincos _ package.sv и добавим в него наш тест:
|
Теперь наш тест можно запустить из созданного ранее тестового окружения. Для этого дополним файл tb _ top.sv следующим кодом:
|
На текущем этапе наше тестовое окружение имеет вид, показанный на рисунке 3.2.
Рисунок 3.2 – Тестовое окружение с пустым тестом
Здесь мы видим наш тестируемый компонент, подключенный через интерфейс; тактовый сигнал, поступающий на этот интерфейс и пустой тест, который никак не взаимодействует с DUT.
Чтобы связать тестируемый компонент с тестом, нужно передать в тест интерфейс, к которому подключен DUT. Для этого модернизируем файлы tb _ top.sv и sincos _ test _ default.svh:
|
|
После этого тестовое окружение примет вид, показанный на рисунке 3.3.
Рисунок 3.3 – Тестовое окружение с пустым тестом и подключенным интерфейсом
Теперь мы можем запускать пустой UVM тест и у него есть связь с нашим тестируемым компонентом.
3.3 Транзакции
Для дальнейшего описания тестового окружения нам нужно определить в каком виде по нашему тесту будут передаваться данные, как из них будут формироваться входные воздействия на DUT, в каком виде будут считываться выходные сигналы, с чем будет работать эталонная модель и так далее.
Для упрощения этих задач в UVM существуют транзакции, для описания которых используется класс uvm _ sequence _ item.
Создадим файл sincos _ seqi.svh, в котором опишем транзакцию для нашего примера:
|
Здесь объявлен класс, наследующий класс uvm _ sequence _ item.
В транзакции используются следующие переменные:
- phase _ v – массив, предназначенный для формирования сигнала значимости. Данная переменная объявлена как «rand» для возможности использования рандомизации при создании новых транзакций;
- phase – предназначена для передачи значений фазы. Также объявлена как «rand»;
- sin – предназначена для передачи значений синуса;
- cos – предназначена для передачи значений косинуса.
Чтобы ограничить значения, которые могут принимать переменные при рандомизации, используются ограничения c _ phase _ v (ограничивает размер массива и гарантирует, что только один из его элементов будет равен единице) и c _ phase (ограничивает значения фаз).
Помимо этого в транзакции реализованы две функции: do _ compare() и convert2string().
Функция do _ compare() имеет следующую реализацию:
|
Функция предназначена для сравнения двух транзакций и возвращает 1, если они равны. В противном случае – 0.
Функция convert2string() имеет следующую реализацию:
|
Функция предназначена для представления транзакции в виде строки для дальнейшего использования, например, в выводе логов.
Теперь нужно добавить описанную транзакцию в пакет sincos _ package.sv:
|
Здесь же добавляем uvm _ sequencer, который будет работать с нашими транзакциями.
3.4 Драйвер
Теперь, когда у нас есть описание транзакций для нашего теста, можно рассмотреть компонент, который будет преобразовывать их в сигналы описанного ранее интерфейса. Для реализации такого компонента используется класс uvm _ driver – драйвер.
Создадим файл sincos _ drvr.svh, в котором опишем драйвер для нашего примера:
|
Здесь объявлен класс, наследующий класс uvm _ driver. Класс содержит интерфейс sincos _ if _ h и драйвер взаимодействует с ним в процессе выполнения run _ phase.
Так же в run _ phase реализуется механизм TLM: осуществляется запрос транзакции, ее обработка и завершение.
Получив очередную транзакцию, драйвер выполняет цикл для каждого бита из массива phase _ v. Переключение элементов массива происходит по тактовому сигналу интерфейса sincos _ if _ h, при этом каждый бит выставляется на вход iphase _ v. При обнаружении ненулевого бита, драйвер выставляет значение фазы phase транзакции на вход iphase интерфейса.
Добавляем драйвер в пакет sincos _ package.sv:
|
3.5 Монитор
Компонент монитор, по сути, выполняет функцию, противоположную драйверу: он анализирует сигналы интерфейса и на основе анализа формирует транзакции. Для реализации такого компонента используется класс uvm _ monitor.
Создадим файл sincos _ mont.svh, в котором опишем монитор для нашего примера:
|
Здесь объявлен класс, наследующий класс uvm _ monitor. Класс содержит интерфейс sincos _ if _ h и монитор взаимодействует с ним в процессе выполнения run _ phase.
По тактовому сигналу монитор проверяет входную (iphase _ v) значимость интерфейса sincos _ if _ h. Если она не равна нулю, то формируется новая входная транзакция (sincos _ seqi _ i), которая записывается в порт анализа (sincos _ aprt _ i).
Аналогично, по выходной значимости (osincos _ v), формируется выходная транзакция (sincos _ seqi _ o), записываемая в (sincos _ aprt _ o).
Добавляем монитор в пакет sincos _ package.sv:
|
Здесь же добавляем uvm _ analysis _ port, который будет работать с нашими транзакциями.
3.6 Scoreboard
Поскольку прямой перевод слова «scoreboard» (табло) здесь не очень подходит, далее будет использоваться английское написание.
Данный компонент выполняет две функции:
- формирование эталонной транзакции на основе данных, полученных из анализа входных сигналов интерфейса sincos _ if _ h;
- сравнивание эталонной транзакции с данными, полученными из анализа выходных сигналов интерфейса sincos _ if _ h.
Создадим файл sincos _ scrb.svh, в котором опишем scoreboard для нашего примера:
|
Здесь объявлен класс, наследующий класс uvm _ scoreboard. Класс содержит порты анализа для входных (sincos _ aprt _ i) и выходных (sinco _ aprt _ o) транзакций. При записи данных в эти порты вызываются функции write _ i() и write _ o() соответственно:
|
При вызове функции write _ i входная транзакция записывается в очередь sincos _ seqi _ queue _ i. При вызове функции write _ o выходная транзакция записывается в очередь sincos _ seqi _ queue _ o, после чего вызывается функция processing():
|
Из очереди входных транзакций считывается sincos _ seqi _ i и для ее значения фазы phase рассчитываются эталонные значения sin/cos путем вызова функций get _ ideal _ sin()/get _ ideal _ cos().
Из очереди выходных транзакций считывается sincos _ seqi _ o и ее значение фазы phase копируется из входной транзакции (для простоты сравнения).
После этого формируется строка для вывода значений двух транзакций, выполняется их сравнение и результат выводится в лог.
Таким образом, в простейшем случае, можно оценить результаты работы теста путем чтения лога.
Добавляем scoreboard в пакет sincos _ package.sv:
|
3.7 Агент
Компоненты класса uvm _ agent используются для группировки других компонентов, работающих с одним интерфейсом. Для нашего примера мы сгруппируем написанные ранее драйвер, монитор и scoreboard.
Создадим файл sincos _ agnt.svh:
|
Здесь объявлен класс, наследующий класс uvm _ agent. Класс содержит интерфейс sincos _ if _ h, секвенсер, драйвер, монитор и scoreboard.
В функции connect _ phase интерфейс передается в драйвер и монитор, драйвер подключается к секвенсеру, порты анализа монитора подключаются к соответствующим портам анализа scoreboard.
Полученный агент имеет структуру, представленную на рисунке 3.4.
Рисунок 3.4 – Структура класса-агента
Не забываем добавить агент в пакет sincos _ package.sv:
|
Осталось встроить полученный компонент в тест, который мы создали в 3.2 и добавить к нему генератор транзакций (sequence).
3.8 Генератор транзакций
Генератор транзакций предназначен для формирования, очереди транзакций по некоторому алгоритму. Для реализации такого компонента используется класс uvm _ sequence.
Создадим файл sincos _ seqc _ default.svh, в котором опишем генератор для нашего теста sincos _ test _ default:
|
Здесь объявлен класс, наследующий класс uvm _ sequence. Описанный генератор в цикле выполняет следующие действия:
- создает новую транзакцию;
- рандомизирует ее;
- заканчивает транзакцию.
3.9 Итог
Встраиваем описанные нами компоненты в тест, для этого возвращаемся к файлу sincos _ test _ default.svh:
|
Здесь мы добавили агент и генератор транзакций. Интерфейс, полученный ранее из верхнего уровня, передается в агент, в результате чего все его компоненты, использующие этот интерфейс, оказываются подключенными к DUT.
Остается только запустить генератор, использовав для него секвенсер, расположенный внутри агента.
В результате наше тестовое окружение имеет вид, показанный на рисунке 3.5.
Рисунок 3.5 – Итоговое тестовое окружение
Теперь после запуска теста генератор начнет выдавать транзакции, передавать их в агент, после чего они будут переданы драйверу (по запросу от него). Драйвер сформирует временную диаграмму на вход DUT. DUT выдаст сигналы на выход. Мониторы считают вход и выход тестируемого компонента, сформируют транзакции и передадут их в scoreboard. Scoreboard, в свою очередь, определит, насколько работа DUT соответствует эталону.
4 Послесловие
Исходные коды для рассмотренного примера выложены в репозиторий.
Для дальнейшего изучения UVM и подходов к написанию крутых тестовых окружений рекомендую почитать следующие источники:
- Accellera UVM Class Reference Manual 1.2 – официальное описание библиотеки UVM
- Accellera UVM Users Guide 1.2 – обширное описание техник применения компонентов UVM
- Salemi R., The UVM Primer - An Introduction to the Universal Verification Methodology(2013) Собственно, данный пример был создан на основе примеров из этой книги.Spear C., SystemVerilog for Verification A Guide to Learning the Testbench Language Features(2012)
- Verification Academy – сайт содержит больше количество информации по верификации, для полного доступа нужна регистрация на корпоративную почту
- Гитхаб – можно найти примеры
Если вы дочитали до этого момента, то респект.
Скачать статью в формате PDF (залогиньтесь) |
Поддержать автора | Поддержать проект fpga-systems |