亿迅智能制造网
工业4.0先进制造技术信息网站!
首页 | 制造技术 | 制造设备 | 工业物联网 | 工业材料 | 设备保养维修 | 工业编程 |
home  MfgRobots >> 亿迅智能制造网 >  >> Industrial programming >> VHDL

VUnit 入门

VUnit 是当今最流行的开源 VHDL 验证框架之一。它结合了 Python 测试套件运行器和专用 VHDL 库来自动化您的测试平台。

为了给您提供这个免费的 VUnit 教程,VHDLwhiz 邀请了 Ahmadmunthar Zaklouta,他是本文其余部分的幕后人员,包括您可以下载并在您的计算机上运行的简单 VUnit 示例项目。

让我们转告艾哈迈德!

本教程旨在演示 VUnit 框架在您的设计验证过程中的使用。它将指导您完成设置 VUnit、创建 VUnit 测试平台、使用 VUnit 检查库以及在 ModelSim 中运行 VUnit 测试的过程。它还演示了一些验证技术。

概述

本文包含多个屏幕截图。 点击图片放大!

使用侧边栏浏览大纲 对于本教程,或者如果您使用的是移动设备,请向下滚动并单击右上角的弹出导航按钮。

要求

本教程假设此软件已安装在 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 ,实现特定电机的启动程序并驱动代表电机状态的 3 个 LED。

该接口由输入记录motor_start_in组成 具有 3 个元素(3 个开关作为输入)和一个输出记录 motor_start_out 具有 3 个元素(3 个 LED 红色、黄色、绿色作为输出)。

有些电机需要在开始时进行初始化,然后才能开始使用它们。我们的电机启动程序分为三个步骤:

  1. 加载配置
  2. 校准
  3. 旋转

启动顺序

以下是电机启动顺序和LED指示灯的含义。

  1. 打开switch_1。
  2. RED_LED 将开始闪烁 5 次。
  3. 等到 RED_LED 停止闪烁并持续亮起。
  1. 现在你可以打开switch_2了。
  2. switch_2打开时,YELLOW_LED会在5个时钟周期后开始常亮,持续10个时钟周期。之后,YELLOW_LED 和 RED_LED 将熄灭。
  1. 现在,电机可以使用了。每次 switch_3 开启时,GREEN_LED 将开始持续点亮,直到 switch_3 关闭。
  2. 任何违反此序列的行为都会导致所有 3 个 LED 持续亮起,直到所有开关都关闭,然后才能重新开始序列。

测试台开发

这部分教程由以下小节组成:

  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_libtb_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_LIBTB_LIB 这样我们就可以访问我们的 DUT 和包,如下所示:

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 是如何处理这个问题的。

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 是我们所有测试用例的测试套件。

为了创建一个测试用例,我们使用 VUnit 的 run 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 时指定的名称来调用它们 .

设置测试平台

在这里,我们首先添加与 DUT 通信所需的信号,如下所示:

--------------------------------------------------------------------------
-- 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);

测试用例开发

现在让我们回到我们的 DUT 并通过开发一些测试用例开始实际工作。我将介绍两种情况:

设计工程师场景: 从设计者的角度来看,是设计者自己进行验证。在这种情况下,通常发生在开发阶段,设计人员可以访问代码。这个场景将展示 VUnit 如何帮助我们“尽早并经常测试”。

验证工程师场景 :设计(DUT)被视为黑匣子。我们只知道外部接口和测试要求。

我们还将研究这三种验证技术:

让我们从第一种技术开始,本文稍后再回到最后两种方法。

测试用例中的驱动程序和检查程序

这是最直接的方法。 driver 和 checker 都在测试用例内部,我们在测试用例代码中实现驱动和检查操作。

假设我们开发了 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;

现在,我们想在继续开发其余功能之前验证我们的设计。

为此,我们使用 VUnit 的 run 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 的检查子程序来做到这一点。

我们知道开关_1打开后RED_LED会闪烁5次,所以我们创建了一个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)

注意 它会告诉我们是 PASS 还是 ERROR,以及该检查发生时的时间戳,以及该检查所在的文件名和行号。

另一种方法是使用 check_false 程序。在这里,我们使用它来检查 YELLOW_LED 是否为“0”:

check_false(expr => ??motor_start_out.yellow_led, 
            msg  => result("for yellow_led when switch_1 on"));

这里,我们使用 VUnit 的 result 改善消息的功能。打印输出将如下所示:

# 35001 ps - check - PASS - False check passed for yellow_led when switch_1 on 
#                           (motor_start_tb.vhdl:193)

注意 它会打印有关支票类型的额外信息:“False check pass”。

还有一种方法是使用 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 将关闭,而其他 LED 也将保持关闭状态。我们可以使用 check_equal 同时检查所有这些,如下所示:

check_equal(led_out, STD_LOGIC_VECTOR'("000"), 
            result("for led_out when switch_1 on"), warning);

注意 限定符 STD_LOGIC_VECTOR'("000") 的使用 ,因此该过程的值是不模糊的。此外,我们指定此检查的级别为 WARNING,这意味着如果此检查失败,它将发出警告而不是抛出错误。输出将如下所示:

#  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 =====");

测试用例运行

现在是时候运行我们的测试用例了。我们可以在终端或模拟器 GUI 中运行测试。

在终端中运行测试

为此,首先打开一个新终端(CMD、PowerShell、Windows 终端)并导航到 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!

我们可以看到已经运行了一个测试,它是 PASS。

注意 VUnit 创建了一个 vunit_out 项目目录中的文件夹。在这个文件夹中,有一个名为 test_output 的文件夹 有关于测试的报告。

上面,我们只得到了最终的测试结果,没有关于每个检查的细节,但是 VUnit 命令行工具提供了几个用于运行测试的开关。要获得有关模拟过程中发生的更多信息,我们可以使用详细开关 -v

python .\run.py *switch_1_on_output_check -v

详细的打印输出将如下所示:

其他有用的开关有:

-l, --list :列出所有测试用例。

--clean :先删除输出文件夹再运行测试。

--compile :例如,如果您想在不运行测试检查错误的情况下进行编译,则此开关很有用。

在模拟器 GUI 中运行测试

通常需要对波浪进行目视检查。 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 检查器库。

与之前的测试用例不同,这种方法在测试用例内部有驱动程序,在外部有检查器,但测试用例仍然控制它。

假设我们有这个验证要求:

从 DUT 部分,我们知道:

我们将使用 VUnit 的 check_stable 验证程序:

我们将使用 VUnit 的 check_next 验证程序:

check_stable :验证信号[s] 在以 start_event 开头的窗口内是否稳定 信号脉冲并以 end_event 结束 信号脉冲。

check_next :在 start_event 之后的多个时钟周期后验证信号 =‘1’ 信号脉冲。

start_eventend_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 中创建一个新的测试用例 使用 VUnit 的 run 功能如下:

ELSIF run("switch_2_on_output_check") THEN
  info("------------------------------------------------------------------");
  info("TEST CASE: switch_2_on_output_check");
  info("------------------------------------------------------------------");

我们创建一个 start_event 用于与 check_stable 一起使用的 YELLOW_LED 的低电平状态 程序如下:

WAIT UNTIL reset <= '0';
-- out of reset
enable <= '1';
pulse_high(clk, yellow_low_start_event);
WAIT FOR C_CLK_PERIOD * 3;

enable 信号激活 check_stablecheck_next 程序,我们希望从一开始就启用它们。

pulse_highmotor_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 用于与 check_stable 一起使用的 YELLOW_LED 的低电平状态 程序:

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 对于 check_stable 的 YELLOW_LED 的高电平状态 程序:

-- 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 对于 check_stable 的 YELLOW_LED 的高电平状态 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 将在 yellow_next_start_event 的 5 个时钟周期后变为高电平 .

注意: check_next 中的最后两个参数 分别是:

现在,我们可以像这样从命令行运行测试用例:

python .\run.py *switch_2_on_output_check -v

结果如下:

我们可以像这样在模拟器 GUI 中运行测试用例:

python .\run.py *switch_2_on_output_check -g

产生这个波形:

注意:start_eventend_event check_stable 的信号 包含在窗口中,当start_event='1'时引用被监控的信号[s] .

在这个测试用例中,我们将检查操作从测试用例中取出,但从测试用例中控制它们。另外,请注意我们没有检查 RED_LED 功能。

测试用例内的驱动和自检检查器

前一种方法的一个缺点是它的可读性较差。测试用例包含不感兴趣或与测试或功能无关的信息,例如 start_eventend_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;

我们提供了用于计算延迟的时钟周期和一个整数,该整数在时钟周期中指定每个开关所需的延迟。

现在我们可以在测试用例中使用这个过程,如下所示:

switch_driver(motor_start_in,C_CLK_PERIOD,3,12,20);

这将在 3 个时钟周期后打开 switch_1,然后在 12 个时钟周期后打开 switch_2,最后在 20 个时钟周期后打开 switch_3。

到目前为止,我们一直在使用 VUnit 的默认检查器。 VUnit 提供了自定义检查器的可能性。当您有一个包含大量检查的大型测试用例,并且您希望获得有关检查的统计信息,或者您不想将检查与测试台的其余部分混为一谈时,这很有用。

我们将为 RED_LED 创建一个自定义检查器并将失败的日志级别设置为 WARNING:

CONSTANT RED_CHECKER : checker_t := new_checker("red_led_checker", WARNING);

我们在 Setup VUnit 部分启用了对 RED_CHECKER 的记录通过检查:

show(get_logger(RED_CHECKER), display_handler, pass);

现在让我们转到自检检查器(或监视器)。我们将首先为RED_LED设计一个自检检查器,我们将使用如下流程:

red_monitor_process : PROCESS
BEGIN
  WAIT UNTIL reset = '0';
  pulse_high(clk, led_low_start_event);
  WAIT UNTIL motor_start_in.switch_1 = '1';
-- 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;
  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 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 设计一个自检检查器,但我将把它作为练习留给读者。但是,我将展示下一个使用 check 验证 GREEN_LED 功能的不同方法 , 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"));

当 switch_3 打开时验证 GREEN_LED 是否打开的一种方法是使用 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);

因为我们允许重叠,所以当 switch_3 为“1”时,此过程将在时钟的每个上升沿触发。t 会期望 GREEN_LED 在一个时钟周期后为“1”,这就是我们想要的。

另一种测试 GREEN_LED 功能的方法是使用时钟版本的 check 使用延迟版本的 switch_3 作为此过程启用的过程:

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 是switch_3的1个时钟周期延迟信号。因此,它会在 switch_3 为‘1’后的 1 个时钟周期内始终启用该程序,并且该程序在启用时检查 GREEN_LED 是否为‘1’。

switch_3_delayed 信号是一个时钟周期的延迟版本 switch_3。因此,它将在 switch_3 为“1”后的一个时钟周期内始终启用此过程。启用后,程序会检查 GREEN_LED 是否为“1”。

注意: switch_3_delayed 中的 AND NOT 启用 只是在我们不使用过程方法时掩盖这个信号。

最后,我们可以使用带有 VHDL 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 知识的工程师也是如此。

测试台文件中还有其他测试用例。我们可以使用以下命令启动它们:

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 还可以帮助验证工程师更快、更轻松地开发和运行测试平台。最重要的是,它具有快速轻松的学习曲线。

从这里去哪里

使用下面的表格下载示例项目!


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 入门
  11. Node-RED 和 Docker 入门
  12. 在 PLCnext 上开始使用 Go