VUnit 入门
VUnit 是当今最流行的开源 VHDL 验证框架之一。它结合了 Python 测试套件运行器和专用 VHDL 库来自动化您的测试平台。
为了给您提供这个免费的 VUnit 教程,VHDLwhiz 邀请了 Ahmadmunthar Zaklouta,他是本文其余部分的幕后人员,包括您可以下载并在您的计算机上运行的简单 VUnit 示例项目。
让我们转告艾哈迈德!
本教程旨在演示 VUnit 框架在您的设计验证过程中的使用。它将指导您完成设置 VUnit、创建 VUnit 测试平台、使用 VUnit 检查库以及在 ModelSim 中运行 VUnit 测试的过程。它还演示了一些验证技术。
概述
本文包含多个屏幕截图。 点击图片放大!
使用侧边栏浏览大纲 对于本教程,或者如果您使用的是移动设备,请向下滚动并单击右上角的弹出导航按钮。
要求
本教程假设此软件已安装在 Windows 机器上:
- 英特尔 ModelSim
- 如何免费安装 ModelSim,请参阅本文。
- ModelSim 应该在您的 PATH 中。
- Python 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
- 否则,您可以从 GitHub 以 Zip 格式下载并解压到 C 盘:
- 下载 VUnit。
- 否则,您可以从 GitHub 以 Zip 格式下载并解压到 C 盘:
- 安装 VUnit:
- 打开终端并导航到
C:\vunit
并输入以下命令:
- 打开终端并导航到
python setup.py install
- 配置 VUnit:
- 如下向您的系统添加环境变量。
VUNIT_MODELSIM_PATH
:您机器中 ModelSim 的路径。VUNIT_SIMULATOR
:模型模拟
下载示例项目
您可以使用下面的表格下载示例项目和 VHDL 代码。
将 Zip 解压缩到 C:\vunit_tutorial .
简介
VUnit 是用于 HDL 的测试框架,它通过提供测试驱动的工作流“尽早和经常测试”以及用于测试运行自动化和管理的工具箱来简化验证过程。它是一个高级框架,具有广泛丰富的功能,但易于使用和适应。它是完全开源的,可以很容易地融入传统的测试方法中。
VUnit由两个主要组件组成:
- Python 库: 提供有助于测试自动化、管理和配置的工具。
- VHDL 库: 一组有助于执行常见验证任务的库。
VHDL 部分由六个库组成,如下图所示。本教程将使用日志和检查库。
被测设计
本教程中使用的设计,命名为 motor_start
,实现特定电机的启动程序并驱动代表电机状态的 3 个 LED。
该接口由输入记录motor_start_in
组成 具有 3 个元素(3 个开关作为输入)和一个输出记录 motor_start_out
具有 3 个元素(3 个 LED 红色、黄色、绿色作为输出)。
有些电机需要在开始时进行初始化,然后才能开始使用它们。我们的电机启动程序分为三个步骤:
- 加载配置
- 校准
- 旋转
启动顺序
以下是电机启动顺序和LED指示灯的含义。
- RED_LED 表示加载配置 .
- 打开switch_1。
- RED_LED 将开始闪烁 5 次。
- 等到 RED_LED 停止闪烁并持续亮起。
- 黄色 LED 表示加载校准 .
- 现在你可以打开switch_2了。
- switch_2打开时,YELLOW_LED会在5个时钟周期后开始常亮,持续10个时钟周期。之后,YELLOW_LED 和 RED_LED 将熄灭。
- 绿色 LED 表示电机正在旋转。
- 现在,电机可以使用了。每次 switch_3 开启时,GREEN_LED 将开始持续点亮,直到 switch_3 关闭。
- 任何违反此序列的行为都会导致所有 3 个 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
这样我们就可以访问我们的 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 是如何处理这个问题的。
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
是我们所有测试用例的测试套件。
为了创建一个测试用例,我们使用 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 检查器库。
与之前的测试用例不同,这种方法在测试用例内部有驱动程序,在外部有检查器,但测试用例仍然控制它。
假设我们有这个验证要求:
- 在 switch_1 处于 ON 且 RED_LED 亮起时打开 switch_2 后验证输出。
从 DUT 部分,我们知道:
- 当 switch_2 开启时,YELLOW_LED 将在 5 个时钟周期后开始持续点亮,持续 10 个时钟周期,之后,YELLOW_LED 和 RED_LED 将关闭。
我们将使用 VUnit 的 check_stable
验证程序:
- YELLOW_LED 从一开始就为“0”,直到 switch_2 开启。
- YELLOW_LED 在 10 个时钟周期内为“1”。
我们将使用 VUnit 的 check_next
验证程序:
- 在 switch_2 开启 5 个时钟周期后,YELLOW_LED 为“1”。
check_stable :验证信号[s] 在以 start_event
开头的窗口内是否稳定 信号脉冲并以 end_event
结束 信号脉冲。
check_next :在 start_event
之后的多个时钟周期后验证信号 =‘1’ 信号脉冲。
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
中创建一个新的测试用例 使用 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_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
用于与 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
中的最后两个参数 分别是:
allow_overlapping
:允许第二个start_event
在第一个的 expr 之前是“1”。allow_missing_start
:允许 expr 为 '1' 而没有start_event
.
现在,我们可以像这样从命令行运行测试用例:
python .\run.py *switch_2_on_output_check -v
结果如下:
我们可以像这样在模拟器 GUI 中运行测试用例:
python .\run.py *switch_2_on_output_check -g
产生这个波形:
注意:start_event
和 end_event
check_stable
的信号 包含在窗口中,当start_event='1'
时引用被监控的信号[s] .
在这个测试用例中,我们将检查操作从测试用例中取出,但从测试用例中控制它们。另外,请注意我们没有检查 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);
这将在 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设计一个自检检查器,我们将使用如下流程:
- 等待switch_1打开并添加一个
start_event
对于所有 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';
- 我们使用与第一个测试用例相同的 FOR LOOP 进行 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 设计一个自检检查器,但我将把它作为练习留给读者。但是,我将展示下一个使用 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 还可以帮助验证工程师更快、更轻松地开发和运行测试平台。最重要的是,它具有快速轻松的学习曲线。
从这里去哪里
- VUnit 文档
- VUnit 博客
- VUnit gitter
使用下面的表格下载示例项目!
VHDL