Начало работы с VUnit
VUnit — одна из самых популярных на сегодняшний день сред проверки VHDL с открытым исходным кодом. Он сочетает в себе средство запуска тестов Python со специальной библиотекой VHDL для автоматизации ваших тестовых стендов.
Чтобы дать вам это бесплатное руководство по VUnit, VHDLwhiz привлекает Ахмадмунтара Заклуту, автора остальной части этой статьи, включая простой пример проекта VUnit, который вы можете загрузить и запустить на своем компьютере.
Давайте дадим слово Ахмаду!
Этот учебник призван продемонстрировать использование платформы VUnit в процессе проверки вашего проекта. Он проведет вас через процесс настройки VUnit, создания тестового стенда VUnit, использования библиотеки проверки VUnit и запуска тестов VUnit в ModelSim. Он также демонстрирует некоторые методы проверки.
Обзор
Эта статья содержит несколько скриншотов. Нажимайте на изображения, чтобы увеличить их!
Используйте боковую панель для навигации по структуре для этого руководства или прокрутите вниз и нажмите всплывающую кнопку навигации в правом верхнем углу, если вы используете мобильное устройство.
Требования
В этом руководстве предполагается, что это программное обеспечение установлено на компьютере с Windows:
- Intel ModelSim
- Пожалуйста, обратитесь к этой статье, чтобы узнать, как установить ModelSim бесплатно.
- ModelSim должен быть в вашем PATH.
- Питон 3.6 или выше.
- Загрузить Python
- Python должен быть указан в PATH.
- GIT (необязательно).
- Загрузить GIT
- Терминал Windows (необязательно)
- Загрузить терминал Windows
Также предполагается наличие базовых знаний VHDL и навыков работы с ModelSim.
Установка
- Получение VUnit:
- Если у вас есть GIT, вы можете клонировать его с GitHub на диск C:
git clone --recurse-submodules https://github.com/VUnit/vunit.git
- В противном случае вы можете загрузить его в виде Zip-файла с GitHub и распаковать на диск C:
- Загрузить VUnit.
- В противном случае вы можете загрузить его в виде Zip-файла с GitHub и распаковать на диск C:
- Установка VUnit:
- Откройте терминал и перейдите к
C:\vunit
и введите следующую команду:
- Откройте терминал и перейдите к
python setup.py install
- Настройка VUnit:
- Добавьте переменные среды в свою систему, как показано ниже.
VUNIT_MODELSIM_PATH
:путь к ModelSim на вашем компьютере.VUNIT_SIMULATOR
:МодельSim
Скачать пример проекта
Вы можете загрузить пример проекта и код VHDL, используя форму ниже.
Распакуйте ZIP-файл в папку C:\vunit_tutorial. .
Введение
VUnit — это среда тестирования для HDL, которая упрощает процесс проверки, предоставляя управляемый тестами рабочий процесс «тестировать раньше и чаще» и набор инструментов для автоматизации и администрирования тестирования. Это продвинутая платформа с обширными богатыми функциями, но при этом простая в использовании и адаптации. Он полностью с открытым исходным кодом и может быть легко включен в традиционные методологии тестирования.
VUnit состоит из двух основных компонентов:
- Библиотека Python: предоставляет инструменты, помогающие в автоматизации тестирования, администрировании и настройке.
- Библиотека VHDL: набор библиотек, помогающих выполнять стандартные задачи проверки.
Часть VHDL состоит из шести библиотек, как показано на диаграмме ниже. В этом руководстве будут использоваться библиотеки регистрации и проверки.
Тестируемый дизайн
Дизайн, использованный в этом руководстве, называется motor_start
. , реализует процедуру запуска для конкретного двигателя и управляет тремя светодиодами, отображающими состояние двигателя.
Интерфейс состоит из входной записи motor_start_in
. с 3 элементами (3 переключателя в качестве входов) и выходной записью motor_start_out
с 3 элементами (3 светодиода КРАСНЫЙ, ЖЕЛТЫЙ, ЗЕЛЕНЫЙ в качестве выходов).
Некоторым двигателям требуется инициализация в начале, прежде чем вы сможете начать их использовать. Наша процедура запуска двигателя состоит из трех этапов:
- Загружается конфигурация
- Калибровка
- Поворот
Последовательность запуска
Далее следует последовательность запуска двигателя и значение светодиодных индикаторов.
- RED_LED представляет собой загрузку конфигурации .
- Включите переключатель_1.
- RED_LED начнет мигать 5 раз.
- Подождите, пока RED_LED перестанет мигать и загорится постоянно.
- ЖЕЛТЫЙ_СВЕТОДИОД представляет собой загрузку калибровки .
- ЗЕЛЕНЫЙ_светодиод указывает, что двигатель вращается.
Разработка тестового стенда
Эта часть руководства состоит из следующих подразделов:
- Настройка исполняемого скрипта Python
- Настройка каркаса VUnit
- Настройка тестового стенда
Настройка скрипта запуска Python run.py
Каждому проекту VUnit нужен скрипт Python верхнего уровня run.py
который выступает в качестве точки входа в проект и определяет все исходные коды проекта VHDL, испытательного стенда и библиотеки.
Этот файл обычно существует в каталоге проекта. Дерево каталогов, используемое в этом руководстве, выглядит следующим образом:
В run.py
файл, нам нужно сделать три вещи:
1 — получить путь, по которому существует этот файл, и указать путь для файлов проекта и тестового стенда.
# ROOT ROOT = Path(__file__).resolve().parent # Sources path for DUT DUT_PATH = ROOT / "design" # Sources path for TB TEST_PATH = ROOT / "testbench"
2 – Создайте экземпляр VUnit.
Это создаст экземпляр VUnit и назначит его переменной с именем VU
. . Затем мы можем использовать VU
для создания библиотек и различных задач.
# create VUnit instance VU = VUnit.from_argv() VU.enable_location_preprocessing()
3. Создайте библиотеки и добавьте в них источники VHDL.
Мне нравится отделять часть дизайна от части тестового стенда. Поэтому создадим две библиотеки:design_lib
и tb_lib
.
# create design library design_lib = VU.add_library("design_lib") # add design source files to design_lib design_lib.add_source_files([DUT_PATH / "*.vhdl"]) # create testbench library tb_lib = VU.add_library("tb_lib") # add testbench source files to tb_lib tb_lib.add_source_files([TEST_PATH / "*.vhdl"])
Остальная часть файла представляет собой конфигурацию ModelSim для использования wave.do
файл, если он существует.
Примечание. здесь я использую *.vhdl
расширение. Возможно, вам придется изменить его, если вы используете *.vhd
. .
Если вам нравится эта рабочая структура, то вам вообще не нужно менять этот файл. Всякий раз, когда вы начинаете новый проект, просто скопируйте его в каталог вашего проекта. Однако, если вы предпочитаете другую структуру каталогов, вам необходимо изменить пути в соответствии с вашей рабочей структурой.
Теперь всякий раз, когда мы используем этот файл, VUnit будет автоматически сканировать тестовые стенды VUnit в вашем проекте, определять порядок компиляции, создавать библиотеки и компилировать в них исходные коды, а также при необходимости запускать симулятор со всеми или определенными тестовыми примерами.
Разве это не здорово? 😀
Настройка скелета VUnit
Чтобы создать тестовую среду VUnit, нам нужно добавить определенный код в наш файл тестовой среды motor_start_tb
. , как описано в этом подразделе.
1 — добавьте библиотеки следующим образом.
Во-первых, нам нужно добавить библиотеку VUnit VUNIT_LIB
и его контекст:VUNIT_CONTEXT
, чтобы у нас был доступ к функциям VUnit следующим образом:
LIBRARY VUNIT_LIB; CONTEXT VUNIT_LIB.VUNIT_CONTEXT;
Во-вторых, нам нужно добавить библиотеки дизайна и тестового стенда DESIGN_LIB
и TB_LIB
чтобы у нас был доступ к нашему тестируемому устройству и пакетам следующим образом:
LIBRARY DESIGN_LIB; USE DESIGN_LIB.MOTOR_PKG.ALL; LIBRARY TB_LIB; USE TB_LIB.MOTOR_TB_PKG.ALL;
DUT имеет два пакета; один для дизайна:motor_pkg
а другой для элементов тестового стенда motor_tb_pkg
. Это тривиальные пакеты, которые я создал, потому что обычно именно так структурированы большие проекты. Я хочу показать, как VUnit справляется с этим.
motor_start
иmotor_pkg
будет скомпилирован вDESIGN_LIB
.motor_start_tb
иmotor_tb_pkg
будет скомпилирован вTB_LIB
.
2 – добавьте конфигурацию бегуна к объекту следующим образом:
ENTITY motor_start_tb IS GENERIC(runner_cfg : string := runner_cfg_default); END ENTITY motor_start_tb;
runner_cfg
— это общая константа, которая позволяет средству запуска тестов Python управлять тестовым стендом. То есть мы можем запускать тесты из среды python. Этот универсальный шаблон является обязательным и не подлежит изменению.
3. Добавьте скелет тестового стенда VUnit в наш тестовый стенд следующим образом:
ARCHITECTURE tb OF motor_start_tb IS test_runner : PROCESS BEGIN -- setup VUnit test_runner_setup(runner, runner_cfg); test_cases_loop : WHILE test_suite LOOP -- your testbench test cases here END LOOP test_cases_loop; test_runner_cleanup(runner); -- end of simulation END PROCESS test_runner; END ARCHITECTURE tb;
test_runner
является основным управляющим процессом для тестового стенда. Он всегда начинается с процедуры test_runner_setup
и завершается процедурой test_runner_cleanup
. Симуляция живет между этими двумя процедурами. test_cases_loop
это наши тестовые наборы, в которых происходят все наши тестовые случаи.
Чтобы создать тестовый пример, мы используем run
VUnit. в операторе If следующим образом:
IF run("test_case_name") THEN -- test case code here ELSIF run("test_case_name") THEN -- test case code here END IF;
Затем мы можем запустить все или определенные тестовые примеры из среды Python, просто вызвав их с именем, которое мы указали в вызове run
. .
Настройка тестового стенда
Здесь мы начинаем с добавления необходимых сигналов для связи с тестируемым устройством, как показано ниже:
-------------------------------------------------------------------------- -- TYPES, RECORDS, INTERNAL SIGNALS, FSM, CONSTANTS DECLARATION. -------------------------------------------------------------------------- -- CONSTANTS DECLARATION -- -- simulation constants CONSTANT C_CLK_PERIOD : time := 10 ns; -- INTERNAL SIGNALS DECLARATION -- -- DUT interface SIGNAL clk : STD_LOGIC := '0'; SIGNAL reset : STD_LOGIC := '1'; SIGNAL motor_start_in : MOTOR_START_IN_RECORD_TYPE := (switch_1 => '0', switch_2 => '0', switch_3 => '0'); SIGNAL motor_start_out : MOTOR_START_OUT_RECORD_TYPE; -- simulation signals SIGNAL led_out : STD_LOGIC_VECTOR(2 DOWNTO 0) := (OTHERS => '0');
Примечание. Рекомендуется инициализировать сигналы начальным значением.
Затем создайте DUT следующим образом:
-------------------------------------------------------------------------- -- DUT INSTANTIATION. -------------------------------------------------------------------------- motor_start_tb_inst : ENTITY DESIGN_LIB.motor_start(rtl) PORT MAP( clk => clk, reset => reset, motor_start_in => motor_start_in, motor_start_out => motor_start_out );
Примечание. Я сгруппировал входные и выходные порты в записи. Я нахожу это полезным в больших проектах, потому что это делает сущности и экземпляры менее загроможденными.
И, наконец, диск clk
, reset
и led_out
как показано здесь:
-------------------------------------------------------------------------- -- SIGNAL DEFINITION OF UNUSED OUTPUT PORTS AND FIXED SIGNALS. -------------------------------------------------------------------------- led_out(0) <= motor_start_out.red_led; led_out(1) <= motor_start_out.yellow_led; led_out(2) <= motor_start_out.green_led; -------------------------------------------------------------------------- -- CLOCK AND RESET. -------------------------------------------------------------------------- clk <= NOT clk after C_CLK_PERIOD / 2; reset <= '0' after 5 * (C_CLK_PERIOD / 2);
Разработка тестовых случаев
Теперь давайте вернемся к нашему тестируемому устройству и начнем реальную работу, разработав несколько тестовых примеров. Я представлю два сценария:
Сценарий инженера-конструктора: с точки зрения разработчика, верификацию осуществляет сам разработчик. В этом сценарии, который обычно происходит на этапе разработки, дизайнер может получить доступ к коду. Этот сценарий покажет, как VUnit помогает нам «тестировать раньше и чаще».
Сценарий инженера по проверке :конструкция (ТУ) рассматривается как черный ящик. Нам известен только внешний интерфейс и требования к тестированию.
Мы также рассмотрим эти три метода проверки:
- Драйвер и средство проверки в тестовом наборе.
- Драйвер и контролируемая проверка в тестовом наборе.
- Драйвер в тестовом наборе и самопроверка.
Давайте начнем с первого метода и вернемся к последним двум методам позже в этой статье.
Драйвер и чекер в тестовом примере
Это самый простой подход. Драйвер и чекер находятся внутри самого тест-кейса, мы реализуем операции вождения и проверки внутри кода тест-кейса.
Предположим, что мы разработали функциональность RED_LED, как показано ниже:
WHEN SWITCH_1_ON => IF (motor_start_in.switch_1 = '0' OR motor_start_in.switch_2 = '1' OR motor_start_in.switch_3 = '1') THEN state = WAIT_FOR_SWITCHES_OFF; ELSIF (counter = 0) THEN led_s.red_led <= '1'; state <= WAIT_FOR_SWITCH_2; ELSE led_s.red_led <= NOT led_s.red_led; END IF;
А теперь мы хотим проверить наш дизайн, прежде чем переходить к разработке остальной функциональности.
Для этого мы используем run
VUnit. функция внутри test_suite
чтобы создать тестовый пример для проверки вывода включения switch_1, как показано ниже:
IF run("switch_1_on_output_check") THEN info("------------------------------------------------------------------"); info("TEST CASE: switches_off_output_check"); info("------------------------------------------------------------------"); -- code for your test case here.
Это создаст тестовый пример с именем «switch_1_on_output_check»
Примечание. info
— это процедура VUnit из библиотеки журналов, которая выводит на экран стенограммы и терминал. Мы будем использовать его для отображения результатов теста.
Теперь мы напишем код для этого тестового примера. Для этого мы будем использовать подпрограммы проверки VUnit.
Мы знаем, что RED_LED мигнет 5 раз после включения switch_1, поэтому мы создаем цикл VHDL For и выполняем проверку внутри него.
check
процедура выполняет проверку конкретных параметров, которые мы предоставляем. У него много вариантов, и здесь я использовал несколько из них для демонстрации.
check(expr => motor_start_out.red_led = '1', msg => "Expect red_led to be ON '1'");
Здесь он проверит, равен ли RED_LED «1» в этот момент времени моделирования, и выведет сообщение на консоль:
# 35001 ps - check - PASS - red_led when switch_1 on (motor_start_tb.vhdl:192)
Примечание что он сообщает нам, является ли это ПРОШЕЛОМ или ОШИБКОЙ, и отметкой времени, когда эта проверка произошла, наряду с именем файла и номером строки, где эта проверка.
Другой способ — использовать check_false
процедура. Здесь мы используем его для проверки того, что YELLOW_LED равен «0»:
check_false(expr => ??motor_start_out.yellow_led, msg => result("for yellow_led when switch_1 on"));
Здесь мы используем result
VUnit. функция для улучшения сообщения. Распечатка будет выглядеть так:
# 35001 ps - check - PASS - False check passed for yellow_led when switch_1 on # (motor_start_tb.vhdl:193)
Примечание что он печатает дополнительную информацию о типе проверки:«Пройдена ложная проверка».
Еще один способ — использовать check_equal
. . Здесь мы используем его для проверки того, что GREEN_LED равен «0»:
check_equal(got => motor_start_out.green_led, expected => '0', msg => result("for green_led when switch_1 on"));
Здесь мы предоставили дополнительный параметр «0» для сравнения. Полученная распечатка:
# 35001 ps - check - PASS - Equality check passed for green_led when switch_1 on - # Got 0. (motor_start_tb.vhdl:194)
Теперь мы знаем, что после одного тактового цикла RED_LED выключится, и другие светодиоды тоже останутся выключенными. Мы можем использовать check_equal
чтобы проверить их все одновременно, как показано ниже:
check_equal(led_out, STD_LOGIC_VECTOR'("000"), result("for led_out when switch_1 on"), warning);
Примечание использование квалификатора STD_LOGIC_VECTOR'("000")
, поэтому значения не являются неоднозначными для процедуры. Кроме того, мы указали уровень этой проверки как ПРЕДУПРЕЖДЕНИЕ, что означает, что если эта проверка не пройдена, будет выдано предупреждение, а не ошибка. Вывод будет выглядеть так:
# 45001 ps - check - PASS - Equality check passed for led_out when switch_1 on - # Got 000 (0). (motor_start_tb.vhdl:197)
Это код для полного теста:
WAIT UNTIL reset <= '0'; WAIT UNTIL falling_edge(clk); motor_start_in.switch_1 <= '1'; -- turn on switch_1 FOR i IN 0 TO 4 LOOP WAIT UNTIL rising_edge(clk); WAIT FOR 1 ps; check(expr => motor_start_out.red_led = '1', msg => "Expect red_led to be ON '1'"); check_false(expr => ??motor_start_out.yellow_led, msg => result("for yellow_led when switch_1 on")); check_equal(got => motor_start_out.green_led, expected => '0', msg => result("for green_led when switch_1 on")); WAIT UNTIL rising_edge(clk); WAIT FOR 1 ps; check_equal(led_out, STD_LOGIC_VECTOR'("000"), result("for led_out when switch_1 on"), warning); END LOOP; info("===== TEST CASE FINISHED =====");
Выполняется тест
Теперь пришло время запустить наш тестовый пример. Мы можем запускать тесты в терминале или в графическом интерфейсе симулятора.
Запуск теста в терминале
Для этого откройте новый терминал (CMD, PowerShell, Windows Terminal) и перейдите к vunit_tutorial. каталог, в котором run.py
файл находится.
Чтобы запустить тестовый пример, просто введите:
python .\run.py *switch_1_on_output_check
VUnit скомпилирует все файлы VHDL в правильном порядке и проанализирует их в поисках тестовых стендов VUnit. Затем VUnit просматривает эти файлы в поисках run
. с именем контрольного примера «switch_1_on_output_check», чтобы выполнить его.
Примечание. мы помещаем подстановочный знак * перед тестовым набором, чтобы он соответствовал его полному имени, а именно:
tb_lib.motor_start_tb.switch_1_on_output_check
Мы можем сделать это, потому что интерфейс командной строки VUnit принимает подстановочные знаки.
Результирующая распечатка после моделирования:
Starting tb_lib.motor_start_tb.switch_1_on_output_check Output file: C:\vunit_tutorial\vunit_out\test_output\tb_lib.motor_start_tb. switch_1_on_output_check_6df3cd7bf77a9a304e02d3e25d028a92fc541cf5\output.txt pass (P=1 S=0 F=0 T=1) tb_lib.motor_start_tb.switch_1_on_output_check (1.1 seconds) ==== Summary ========================================================== pass tb_lib.motor_start_tb.switch_1_on_output_check (1.1 seconds) ======================================================================= pass 1 of 1 ======================================================================= Total time was 1.1 seconds Elapsed time was 1.1 seconds ======================================================================= All passed!
Мы видим, что один тест прошел успешно.
Примечание что VUnit создал vunit_out папка в каталоге проекта. Внутри этой папки есть папка с именем test_output. у которого есть отчеты о тестах.
Выше мы получили только окончательный результат теста без подробностей о каждой проверке, но инструмент командной строки VUnit предоставляет несколько переключателей для запуска тестов. Чтобы получить больше информации о том, что происходит во время моделирования, мы можем использовать подробный переключатель -v
. :
python .\run.py *switch_1_on_output_check -v
Подробная распечатка будет выглядеть так:
Другие полезные переключатели:
-l, --list
:список всех тестовых случаев.
--clean
:сначала удалите выходную папку, а затем запустите тест.
--compile
:этот переключатель полезен, если вы хотите компилировать без запуска тестов, например, для проверки на наличие ошибок.
Выполнение теста в графическом интерфейсе симулятора
Часто требуется визуальный осмотр волны. VUnit обеспечивает автоматизированный способ запуска тестов в симуляторе с помощью переключателя GUI -g
. . VUnit выполнит всю компиляцию и запустит ModelSim (настроенный нами симулятор) с запрошенным тестовым примером и добавит сигналы в окно волны, учитывая, что wave.do
файл доступен.
python .\run.py *switch_1_on_output_check -g
Теперь VUnit запустит ModelSim для этого конкретного теста, откроет окно сигнала и добавит сигналы, поскольку у меня уже есть motor_start_tb_wave.do
. внутри волн папка.
Примечание. Вы можете настроить сигнал по своему усмотрению, но вы должны сохранить файл формата сигнала внутри waves. папка с этим соглашением об именах testbench_file_name_wave.do
чтобы его можно было загрузить, когда VUnit запускает ModelSim.
Предположим, вы изменили свой код после обнаружения ошибок и хотите повторно запустить этот тестовый пример. В этом случае вы можете ввести в окне стенограммы ModelSim:vunit_restart
, Это заставит VUnit перекомпилировать, перезапустить и повторно запустить симуляцию.
Драйвер и контролируемая проверка в тестовом примере
На данный момент мы узнали, как настроить тестовую среду VUnit, а также разработать и запустить тестовый пример. В этом разделе мы разработаем дополнительные тестовые примеры с точки зрения инженера по проверке, используя другой подход к проверке и библиотеку проверки VUnit.
В отличие от предыдущего тестового примера, в этом подходе драйвер находится внутри тестового набора, а средство проверки — снаружи, но тестовый набор по-прежнему контролирует его.
Предположим, у нас есть это требование проверки:
- Проверьте вывод после включения переключателя_2, когда переключатель_1 включен, а RED_LED горит.
Из раздела DUT мы знаем, что:
- Когда переключатель_2 включен, ЖЕЛТЫЙ_СВЕТОДИОД начнет постоянно гореть через 5 тактов в течение 10 тактов, а после этого ЖЕЛТЫЙ и КРАСНЫЙ_СВЕТОДИОДЫ погаснут.
Мы будем использовать check_stable
VUnit. процедура, чтобы убедиться, что:
- ЖЕЛТЫЙ_СВЕТОДИОД имеет значение «0» с самого начала, пока переключатель_2 не будет включен.
- YELLOW_LED =1 для 10 тактов.
Мы будем использовать check_next
VUnit. процедура, чтобы убедиться, что:
- ЖЕЛТЫЙ_СВЕТОДИОД становится равным «1» через 5 тактов после включения switch_2.
check_stable :убедитесь, что сигнал [ы] стабилен внутри окна, которое начинается с start_event
сигнальный импульс и заканчивается end_event
сигнальный импульс.
проверить_следующее :убедитесь, что сигнал =‘1’ через несколько тактов после start_event
сигнальный импульс.
start_event
и end_event
сигналы будут контролироваться из тестового примера.
Начнем с добавления необходимых сигналов для check_stable
. и check_next
процедуры следующим образом:
-- VUnit signals SIGNAL enable : STD_LOGIC := '0'; -- for yellow_led SIGNAL yellow_next_start_event : STD_LOGIC := '0'; SIGNAL yellow_low_start_event : STD_LOGIC := '0'; SIGNAL yellow_low_end_event : STD_LOGIC := '0'; SIGNAL yellow_high_start_event : STD_LOGIC := '0'; SIGNAL yellow_high_end_event : STD_LOGIC := '0';
Затем мы создаем новый тестовый пример внутри test_suite
. используя run
VUnit работать следующим образом:
ELSIF run("switch_2_on_output_check") THEN info("------------------------------------------------------------------"); info("TEST CASE: switch_2_on_output_check"); info("------------------------------------------------------------------");
Создаем start_event
для низкого состояния YELLOW_LED для использования с check_stable
процедура следующая:
WAIT UNTIL reset <= '0'; -- out of reset enable <= '1'; pulse_high(clk, yellow_low_start_event); WAIT FOR C_CLK_PERIOD * 3;
enable
сигнал активирует check_stable
и check_next
процедуры, и мы хотим включить их с самого начала.
pulse_high
это тривиальная процедура из motor_tb_pkg
который ожидает следующего нарастающего фронта тактового сигнала и выдает импульсный сигнал в течение одного тактового цикла. В данном случае это yellow_ low_start_event
.
Теперь мы включаем switch_1 и ждем, пока RED_LED не загорится постоянно, а затем включаем switch_2:
-- turn ON switch_1 motor_start_in.switch_1 <= '1'; -- wait until RED_LED finished WAIT FOR C_CLK_PERIOD * 12; -- turn ON switch_2 motor_start_in.switch_2 <= '1';
Теперь мы знаем, что через 5 тактов YELLOW_LED будет «1». Поэтому мы создаем start_event
для YELLOW_LED для использования с check_next
процедура:
-- after 5 clk cycles YELLOW_LED will light -- next_start_event for YELLOW_LED high pulse_high(clk, yellow_next_start_event); -- 1st clk cycle
Точно так же мы создаем end_event
для низкого состояния YELLOW_LED для использования с check_stable
процедура:
WAIT FOR C_CLK_PERIOD * 3; -- end event for YELLOW_LED low pulse_high(clk, yellow_low_end_event); -- 5th clk cycle
Теперь YELLOW_LED будет высоким в течение 10 тактов. Поэтому мы создаем start_event
для высокого состояния YELLOW_LED для check_stable
процедура:
-- YELLOW_LED is high for 10 clk cycles -- start event for YELLOW_LED high yellow_high_start_event <= '1'; WAIT UNTIL rising_edge(clk); -- 1st clk cycle yellow_high_start_event <= '0';
Здесь я не использовал pulse_high
процедура, потому что я хочу, чтобы импульс возникал сейчас, а не на следующем заднем фронте.
И мы создаем end_event
для высокого состояния YELLOW_LED для check_stable
после 8 тактов следующим образом:
WAIT FOR C_CLK_PERIOD * 8; -- end event for YELLOW_LED pulse_high(clk, yellow_high_end_event); -- 10th clk cycle
На этом тестовый пример закончен. Нам нужно только добавить вызовы процедур проверки после такого процесса:
---------------------------------------------------------------------- -- Related YELLOW_LED check ---------------------------------------------------------------------- -- check that YELLOW_LED is low from start until switch_2 is ON check_stable(clock => clk, en => enable, start_event => yellow_low_start_event, end_event => yellow_low_end_event, expr => motor_start_out.yellow_led, msg => result("YELLOW_LED Low before switch_2"), active_clock_edge => rising_edge, allow_restart => false); -- check that YELLOW_LED is high after switch_2 is ON check_next(clock => clk, en => enable, start_event => yellow_next_start_event, expr => motor_start_out.yellow_led, msg => result("for yellow_led is high after 5 clk"), num_cks => 5, allow_overlapping => false, allow_missing_start => true); -- check that YELLOW_LED is high after for 10 clk cycle check_stable(clk, enable, yellow_high_start_event, yellow_high_end_event, motor_start_out.yellow_led, result("for YELLOW_LED High after switch_2"));
Примечание. allow_restart
параметр в check_stable
Процедура позволяет запустить новое окно, если новый start_event
происходит перед end_event
.
Примечание. мы ставим 5 в check_next
процедура, потому что YELLOW_LED будет высоким после 5 тактовых циклов yellow_next_start_event
.
Примечание. последние два параметра в check_next
являются:
allow_overlapping
:разрешить второйstart_event
перед выражением для первого из них будет «1».allow_missing_start
:разрешить expr быть «1» безstart_event
.
Теперь мы можем запустить тестовый пример из командной строки следующим образом:
python .\run.py *switch_2_on_output_check -v
И результат будет следующим:
Мы можем запустить тестовый пример в графическом интерфейсе симулятора следующим образом:
python .\run.py *switch_2_on_output_check -g
В результате получается эта форма волны:
Примечание:start_event
и end_event
сигналы для check_stable
включаются в окно, а отслеживаемые сигналы указываются, когда start_event='1'
.
В этом тестовом примере мы убрали операции проверки из тестового примера, но контролировали их из тестового примера. Также обратите внимание, что мы не проверяли функциональность RED_LED.
Драйвер в тестовом примере и самопроверка
Одним из недостатков предыдущего подхода является то, что он менее удобочитаем. Тестовый пример содержит информацию, которая не представляет интереса или не связана с тестом или функциональностью, например, start_event
и end_event
сигналы. Мы хотим скрыть все эти детали, иметь в тестовом примере только драйвер и сделать самопроверяющийся чекер.
Начнем с разработки драйвера.
Процедуры VHDL являются хорошими кандидатами для этого. Если вы не знаете, как использовать процедуру VHDL, ознакомьтесь с этим руководством.
Ниже приведена процедура switch_driver
от motor_tb_pkg
.
PROCEDURE switch_driver( SIGNAL switches : OUT MOTOR_START_IN_RECORD_TYPE; CONSTANT clk_period : IN TIME; CONSTANT sw1_delay : IN INTEGER; CONSTANT sw2_delay : IN INTEGER; CONSTANT sw3_delay : IN INTEGER) IS BEGIN IF (sw1_delay = 0) THEN WAIT FOR clk_period * sw1_delay; switches.switch_1 <= '1'; ELSIF (sw1_delay = -1) THEN switches.switch_1 <= '0'; END IF; IF (sw2_delay = 0) THEN WAIT FOR clk_period * sw2_delay; switches.switch_2 <= '1'; ELSIF (sw2_delay = -1) THEN switches.switch_2 <= '0'; END IF; IF (sw3_delay = 0) THEN WAIT FOR clk_period * sw3_delay; switches.switch_3 <= '1'; ELSIF (sw3_delay = -1) THEN switches.switch_3 <= '0'; END IF; END PROCEDURE switch_driver;
Мы предоставляем тактовый период для расчета задержек и целое число, указывающее желаемую задержку для каждого переключения в тактовых периодах.
- Естественные значения (>=0) означают:включить переключатель после (
clk_period
*sw_delay
). - Значение -1 означает:выключите переключатель.
- Все остальные отрицательные значения означают:ничего не делать.
Теперь мы можем использовать эту процедуру внутри тестового примера следующим образом:
switch_driver(motor_start_in,C_CLK_PERIOD,3,12,20);
Это включит переключатель_1 через 3 такта, затем переключатель_2 включится через 12 тактов и, наконец, включит переключатель_3 через 20 тактов.
До сих пор мы использовали средство проверки VUnit по умолчанию. VUnit предоставляет возможность иметь пользовательскую проверку. Это полезно, когда у вас есть большой тестовый набор с большим количеством проверок, и вы хотите получить статистику о проверке или не хотите, чтобы проверка смешивалась с остальной частью тестового стенда.
Мы создадим пользовательскую проверку для RED_LED и установим уровень журнала ошибок на ПРЕДУПРЕЖДЕНИЕ:
CONSTANT RED_CHECKER : checker_t := new_checker("red_led_checker", WARNING);
И включаем логирование прохождения проверок для RED_CHECKER в разделе Setup VUnit:
show(get_logger(RED_CHECKER), display_handler, pass);
Теперь перейдем к самопроверяющемуся чекеру (или монитору). Сначала мы разработаем самопроверяющееся средство проверки для RED_LED и будем использовать для этого следующий процесс:
- Подождите, пока переключатель_1 включится, и добавьте
start_event
для всех светодиодов низкий уровень:
red_monitor_process : PROCESS BEGIN WAIT UNTIL reset = '0'; pulse_high(clk, led_low_start_event); WAIT UNTIL motor_start_in.switch_1 = '1';
- Мы используем тот же цикл FOR из первого теста для мигания RED_LED и добавляем
start_event
для RED_LED высокий уровень:
-- RED_LED is blinking FOR i IN 0 TO 4 LOOP IF (motor_start_in.switch_1 = '1' AND motor_start_in.switch_2 = '0' AND motor_start_in.switch_3 = '0') THEN WAIT UNTIL rising_edge(clk); WAIT FOR 1 ps; check(RED_CHECKER, motor_start_out.red_led = '1', result("for red_led blink high")); WAIT UNTIL rising_edge(clk); WAIT FOR 1 ps; check(RED_CHECKER, motor_start_out.red_led = '0', result("for red_led blink low")); -- RED_LED is constantly lighting start event IF (i = 4) THEN -- finish blinking WAIT UNTIL rising_edge(clk); WAIT FOR 1 ps; check(RED_CHECKER, motor_start_out.red_led = '1', result("for red_led blink low")); pulse_high(clk, red_high_start_event); END IF; ELSE -- perform check for error (All led high until all switches are off) END IF; END LOOP;
- Теперь мы ждем включения switch_2, а затем добавляем
end_event
для RED_LED высокий уровень:
IF (motor_start_in.switch_2 /= '1') THEN WAIT UNTIL motor_start_in.switch_2 = '1'; END IF; WAIT UNTIL rising_edge(clk); WAIT FOR C_CLK_PERIOD * 14; -- RED_LED is constantly lighting end event pulse_high(clk, red_high_end_event); END PROCESS red_monitor_process;
- Теперь мы добавляем
check_stable
процедуры для высокого и низкого уровня RED_LED:
-- check that RED_LED is low from start until switch_1 is ON -- Note the use of motor_start_in.switch_1 as end_event check_stable(RED_CHECKER, clk, enable, led_low_start_event, motor_start_in.switch_1, motor_start_out.red_led, result("RED_LED low before switch_1")); -- check that RED_LED is low after switch_2 is ON check_stable(RED_CHECKER, clk, enable, red_high_start_event, red_high_end_event, motor_start_out.red_led, result("RED_LED high after switch_1"));
Давайте проверим это на следующем тестовом примере:
ELSIF run("red_led_output_self-check") THEN info("---------------------------------------------------------------"); info("TEST CASE: red_led_output_self-check"); info("---------------------------------------------------------------"); WAIT UNTIL reset = '0'; -- turn switch_1 on after 3 clk cycles and switch_2 after 13 clk cycles switch_driver(motor_start_in,C_CLK_PERIOD,3,13,-1); WAIT FOR C_CLK_PERIOD * 3; -- dummy wait info("===== TEST CASE FINISHED =====");
Мы запускаем симуляцию следующим образом:
python .\run.py *red_led* -v
Результат будет:
Примечание что чекер теперь red_led_checker
.
Мы можем использовать тот же подход, чтобы спроектировать самопроверяющуюся программу проверки для YELLOW_LED, но я оставлю это в качестве упражнения для читателя. Однако я покажу следующие различные способы проверки функциональности GREEN_LED с помощью check
. , check_stable
, check_next
и check_equal
процедуры.
Чтобы убедиться, что GREEN_LED выключен с самого начала до первого включения switch_3, мы просто используем check_stable
процедура:
-- check that GREEN_LED is low from start until switch_3 is ON. check_stable(clk, enable, led_low_start_event, motor_start_in.switch_3, motor_start_out.green_led, result("GREEN_LED low before switch_3"));
Один из способов убедиться, что GREEN_LED горит, когда переключатель_3 включен, — использовать check_next
процедура. Мы вызываем его с помощью switch_3 как start_event
, назначьте 1 тактовый цикл для num_cks
и разрешить перекрытие:
-- check that GREEN_LED is high using check_next check_next(clock => clk, en => green_next_en, start_event => motor_start_in.switch_3, expr => motor_start_out.green_led, msg => result("for green_led high using check_next"), num_cks => 1, allow_overlapping => true, allow_missing_start => false);
Поскольку мы разрешили перекрытие, эта процедура будет запускаться при каждом переднем фронте тактового сигнала, когда переключатель_3 равен «1». Мы не ожидаем, что GREEN_LED будет равен «1» после одного тактового цикла, и это то, что нам нужно.
Еще один способ проверить функциональность GREEN_LED — использовать синхронизированную версию check
. процедура с отложенной версией switch_3 как Enable для этой процедуры:
switch_3_delayed <= motor_start_in.switch_3'delayed(C_CLK_PERIOD + 1 ps) AND NOT enable; check(clock => clk, en => switch_3_delayed, expr => motor_start_out.green_led, msg => result("for green_led high using delayed"));
switch_3_delayed
представляет собой задержанный на 1 такт сигнал переключателя_3. Таким образом, эта процедура всегда будет включена через 1 такт после того, как переключатель_3 будет равен «1», и процедура проверит, что GREEN_LED равен «1», когда она включена.
switch_3_delayed
сигнал представляет собой версию switch_3 с задержкой на один такт. Таким образом, эта процедура всегда будет включена через один такт после того, как переключатель_3 будет равен «1». Когда эта функция включена, процедура проверяет, что GREEN_LED имеет значение «1».
Примечание. И НЕ включить в switch_3_delayed
просто для того, чтобы замаскировать этот сигнал, когда мы не используем процессный подход.
Наконец, мы можем использовать специальный процесс с циклом WHDL While для проверки GREEN_LED:
green_monitor_process : PROCESS BEGIN WAIT UNTIL enable = '1' AND motor_start_in.switch_1 = '1' AND motor_start_in.switch_2 = '1' AND motor_start_in.switch_3 = '1'; WHILE motor_start_in.switch_3 = '1' LOOP WAIT UNTIL rising_edge(clk); WAIT FOR 1 ps; check_equal(led_out, STD_LOGIC_VECTOR'("100"), result("for led_out when switch_3 on")); END LOOP; END PROCESS green_monitor_process;
Теперь мы разрабатываем тестовый пример для GREEN_LED следующим образом:
ELSIF run("switch_3_on_output_check") THEN info("-------------------------------------------------------------"); info("TEST CASE: switch_3_on_output_check"); info("-------------------------------------------------------------"); info("check using a clocked check PROCEDURES"); WAIT UNTIL reset = '0'; switch_driver(motor_start_in,C_CLK_PERIOD,3,12,20); WAIT FOR C_CLK_PERIOD * 5; switch_driver(motor_start_in,C_CLK_PERIOD,-2,-2,-1); WAIT FOR C_CLK_PERIOD * 3; -- dummy wait info("-------------------------------------------------------------"); info("check using check_next PROCEDURES"); green_next_en <= '1'; switch_driver(motor_start_in,C_CLK_PERIOD,-2,-2,10); WAIT FOR C_CLK_PERIOD * 5; switch_driver(motor_start_in,C_CLK_PERIOD,-2,-2,-1); WAIT FOR C_CLK_PERIOD * 3; -- dummy wait green_next_en <= '0'; info("-------------------------------------------------------------"); info("check using check_equal process"); enable <= '1'; switch_driver(motor_start_in,C_CLK_PERIOD,-2,-2,10); WAIT FOR C_CLK_PERIOD * 5; switch_driver(motor_start_in,C_CLK_PERIOD,-2,-2,-1); WAIT FOR C_CLK_PERIOD * 10; -- dummy wait info("===== TEST CASE FINISHED =====");
После написания тестового случая вы можете запустить его и проверить различные результаты.
Мы видим, что тестовые примеры в подходе с самопроверкой намного читабельнее даже для инженеров, не знакомых с VHDL.
В файле testbench есть и другие тестовые примеры. Мы можем запустить их с помощью этой команды:
python .\run.py *motor_start_tb* --list
И это вывод, напечатанный на консоли:
tb_lib.motor_start_tb.switches_off_output_check tb_lib.motor_start_tb.switch_1_on_output_check tb_lib.motor_start_tb.switch_2_on_output_check tb_lib.motor_start_tb.red_led_output_self-check tb_lib.motor_start_tb.switch_3_on_output_check tb_lib.motor_start_tb.switch_2_error_output_check Listed 6 tests
Мы можем запустить все тестовые примеры, набрав:
python .\run.py
И результат выглядит следующим образом:
==== Summary ============================================================= pass tb_lib.motor_start_tb.switch_1_on_output_check (0.4 seconds) pass tb_lib.motor_start_tb.switch_2_on_output_check (0.4 seconds) pass tb_lib.motor_start_tb.red_led_output_self-check (0.4 seconds) pass tb_lib.motor_start_tb.switch_3_on_output_check (0.4 seconds) fail tb_lib.motor_start_tb.switches_off_output_check (0.9 seconds) fail tb_lib.motor_start_tb.switch_2_error_output_check (0.4 seconds) ========================================================================== pass 4 of 6 fail 2 of 6 ========================================================================== Total time was 2.9 seconds Elapsed time was 2.9 seconds ========================================================================== Some failed!
Обзор
Платформа VUnit предоставляет расширенные функции для автоматизации выполнения тестов и богатые библиотеки для разработки тестовых стендов. Кроме того, это позволяет инженерам-проектировщикам заранее и часто тестировать свою конструкцию.
VUnit также помогает инженерам по верификации разрабатывать и запускать испытательные стенды быстрее и проще. Самое главное, что у него быстрая и легкая кривая обучения.
Что делать дальше
- Документация VUnit
- Блог VUnit
- Гиттер VUnit
Загрузите пример проекта, используя форму ниже!
VHDL
- Готовые контейнеры для кода:начало работы с инструментами автоматизации процессов в облаке
- Начало работы с керамической 3D-печатью
- Знакомство с основными красителями!
- Начало работы с TJBot
- Начало работы с RAK 831 Lora Gateway и RPi3
- Начало работы со шлюзом RAK831 LoRa и RPi3
- Приступаем к делу с помощью Интернета вещей
- Начало работы с ИИ в страховании:вводное руководство
- Учебник по Arduino 01:Начало работы
- Начало работы с My.Cat.com