Промышленное производство
Промышленный Интернет вещей | Промышленные материалы | Техническое обслуживание и ремонт оборудования | Промышленное программирование |
home  MfgRobots >> Промышленное производство >  >> Industrial programming >> VHDL

Начало работы с VUnit

VUnit — одна из самых популярных на сегодняшний день сред проверки VHDL с открытым исходным кодом. Он сочетает в себе средство запуска тестов Python со специальной библиотекой VHDL для автоматизации ваших тестовых стендов.

Чтобы дать вам это бесплатное руководство по VUnit, VHDLwhiz привлекает Ахмадмунтара Заклуту, автора остальной части этой статьи, включая простой пример проекта VUnit, который вы можете загрузить и запустить на своем компьютере.

Давайте дадим слово Ахмаду!

Этот учебник призван продемонстрировать использование платформы VUnit в процессе проверки вашего проекта. Он проведет вас через процесс настройки VUnit, создания тестового стенда VUnit, использования библиотеки проверки VUnit и запуска тестов VUnit в ModelSim. Он также демонстрирует некоторые методы проверки.

Обзор

Эта статья содержит несколько скриншотов. Нажимайте на изображения, чтобы увеличить их!

Используйте боковую панель для навигации по структуре для этого руководства или прокрутите вниз и нажмите всплывающую кнопку навигации в правом верхнем углу, если вы используете мобильное устройство.

Требования

В этом руководстве предполагается, что это программное обеспечение установлено на компьютере с Windows:

Также предполагается наличие базовых знаний VHDL и навыков работы с ModelSim.

Установка

git clone --recurse-submodules https://github.com/VUnit/vunit.git
 
python setup.py install

Скачать пример проекта

Вы можете загрузить пример проекта и код VHDL, используя форму ниже.

Распакуйте ZIP-файл в папку C:\vunit_tutorial. .

Введение

VUnit — это среда тестирования для HDL, которая упрощает процесс проверки, предоставляя управляемый тестами рабочий процесс «тестировать раньше и чаще» и набор инструментов для автоматизации и администрирования тестирования. Это продвинутая платформа с обширными богатыми функциями, но при этом простая в использовании и адаптации. Он полностью с открытым исходным кодом и может быть легко включен в традиционные методологии тестирования.

VUnit состоит из двух основных компонентов:

Часть VHDL состоит из шести библиотек, как показано на диаграмме ниже. В этом руководстве будут использоваться библиотеки регистрации и проверки.

Тестируемый дизайн

Дизайн, использованный в этом руководстве, называется motor_start. , реализует процедуру запуска для конкретного двигателя и управляет тремя светодиодами, отображающими состояние двигателя.

Интерфейс состоит из входной записи motor_start_in. с 3 элементами (3 переключателя в качестве входов) и выходной записью motor_start_out с 3 элементами (3 светодиода КРАСНЫЙ, ЖЕЛТЫЙ, ЗЕЛЕНЫЙ в качестве выходов).

Некоторым двигателям требуется инициализация в начале, прежде чем вы сможете начать их использовать. Наша процедура запуска двигателя состоит из трех этапов:

  1. Загружается конфигурация
  2. Калибровка
  3. Поворот

Последовательность запуска

Далее следует последовательность запуска двигателя и значение светодиодных индикаторов.

  1. Включите переключатель_1.
  2. RED_LED начнет мигать 5 раз.
  3. Подождите, пока RED_LED перестанет мигать и загорится постоянно.
<старт ="4">
  • Теперь вы можете включить переключатель_2.
  • Когда переключатель switch_2 включен, YELLOW_LED начнет постоянно гореть через 5 тактов, продолжая 10 тактов. И после этого YELLOW_LED и RED_LED погаснут.
  • <старт ="6">
  • Теперь двигатель готов к использованию. Каждый раз, когда переключатель_3 включается, ЗЕЛЕНЫЙ_светодиод будет постоянно гореть до тех пор, пока переключатель_3 не будет выключен.
  • Любое нарушение этой последовательности приведет к тому, что все 3 светодиода будут постоянно гореть до тех пор, пока все переключатели не будут выключены, после чего последовательность можно будет запустить снова.

    Разработка тестового стенда

    Эта часть руководства состоит из следующих подразделов:

    1. Настройка исполняемого скрипта Python
    2. Настройка каркаса VUnit
    3. Настройка тестового стенда

    Настройка скрипта запуска 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

    1. Готовые контейнеры для кода:начало работы с инструментами автоматизации процессов в облаке
    2. Начало работы с керамической 3D-печатью
    3. Знакомство с основными красителями!
    4. Начало работы с TJBot
    5. Начало работы с RAK 831 Lora Gateway и RPi3
    6. Начало работы со шлюзом RAK831 LoRa и RPi3
    7. Приступаем к делу с помощью Интернета вещей
    8. Начало работы с ИИ в страховании:вводное руководство
    9. Учебник по Arduino 01:Начало работы
    10. Начало работы с My.Cat.com