测试程序的基本概念
-
采用硬件描述语言完成电路模块的建模后,使用EDA工具对其进行逻辑综合,以确定它是否存在语法错误。即便语法正确,也不代表电路能够正常工作。为了确认代码是否能够按照我们的预期工作,还需对其进行功能验证。
-
仿真(Simulation)是进行电路验证的主要手段,它可以及早发现所存在的设计问题,降低设计风险,节约设计成本。仿真验证的框架如图1所示。
图1 仿真验证的框架
-
仿真是通过编写 测试程序(testbench)完成的。它是用于测试待测模块(Device Under Test, DUT),功能是否正确的一段硬件描述语言代码,但它是不可综合的,由激励信号、DUT和输出响应三个部分组成。
-
测试的方法:对待测模块的输入端口施加激励信号,该激励信号应尽可能包含所有可能的输入组合(即测试覆盖率高),让待测模块计算相应的输出响应,然后将计算出的输入响应和我们的预期输出进行比较,检测他们是否正确。结果比较的时候,可以通过打印关键信号信息、观察波形图、自动化比对等多种方式进行,设计者需根据实际情况,选择更便于判断结果是否正确的比较方式。
-
测试程序本身就是一段硬件描述语言代码,但由于它是不需要综合的,因此可使用硬件描述语言所提供的一切语法要素和语言结构,如分支结构、循环结构、任务、函数等。
测试程序的模板
- 测试程序的模板如下所示,通常由4部分组成。
module testbench_name(); // testbench为顶层模块,不会被其它模块例化,因此不需要定义任何端口
// 信号定义
// 模块实例化
// 施加测试激励
// 显示输出结果(可以不添加任何显示打印语句,只生成波形图即可)
endmodule
①“信号定义”用于定义连接待测模块输入/输出端口的信号变量,或其他必要的中间信号。注意,连接输入端口的信号通常声明为reg类型(Verilog代码)或logic类型(SystemVerilog代码),因为对输入施加激励时,需要在过程块中赋值;连接输出端口的信号通常声明为wire类型(Verilog代码)或logic类型(SystemVerilog代码)。
②“模块实例化”用于实例化待测模块。 ③“施加测试激励”一般利用initial或always过程块向输入端口传入测试向量。 ④“显示输出结果”可通过系统任务,如$display、$monitor等,打印必要的信号值。
- 一个测试程序的实例如图2所示。其中待测模块为sillyfunction。
激励信号
- 在硬件描述语言中,施加激励主要有三种方法: ① 通过initial过程块施加(线性)激励。 ② 通过always过程块施加(循环)激励,主要用于产生时钟信号,详见第五关知识点。 ③ 通过文件施加激励,主要用于自动化测试。
- 对组合逻辑电路进行测试时,在initial块中施加激励,通过“[#延迟量]”列出值需要改变的信号即可,如下示例代码所示。注意,intial块只执行一次。
initial begin
data_bus = 8'h00; // 时刻0发生赋值
addr = 8'h3f; // 时刻0发生赋值
#10 data_ bus = 8'h45; // 时刻10发生赋值
#15 data_ bus = 8'hff; // 时刻25发生赋值
end
- 在一个测试程序中可以包含多个initial块,并且它们都是同时并行执行,因此需要特别注意,不要在多个initial块中,在同一个仿真时刻对同一个信号赋值,否则将产生是冲突,如下示例代码所示。
initial begin
data_bus = 8’h00; addr = 8’h3f;
#10 data_ bus = 8'h45; // 时刻10发生赋值
end
initial begin
#10 data_ bus = 8’hff; // 错误,发生冲突
end
输出响应
-
在硬件描述语言中,输出响应是指在向待测模块的输入端施加激励后,通过观察输出的结果,并与预期结果进行比较,以验证电路功能是否正确。这一过程可通过观测波形图或借助硬件描述语言提供的一系列系统任务显示输出结果来实现。
-
显示信号值的系统任务包括:$display和$monitor,相当于C语言的printf函数,输出变量的值显示在控制台之上,语法格式如下。
$display (“显示格式控制符”, <输出变量(信号)列表>);
$monitor (“显示格式控制符”, <输出变量(信号)列表>);
$display ($time, " a = %b b = %d c = %b y = %h", a, b, c, y);
$monitor ($time, " a = %b b = %d c = %b y = %h", a, b, c, y);
其中,$time用于获取仿真时间,它将直接返回一个整数时间值,如5ns等。%b、%d、%h表示以何种进制打印信号值,所支持的进制格式如下表所示。
%h | %o | %d | %b | %c | %s | %t | %m |
---|---|---|---|---|---|---|---|
16进制 | 8进制 | 10进制 | 2进制 | ASCII码 | 字符串 | 时间 | 模块名 |
- $display和$monitor的区别在于前者只有执行到该语句时才进行显示操作,而后者就像一个监视器,只要输出变量列表中的某个变量发生变化,就执行一次显示操作,后者使用更方便。如果只想在特定时间点打印,可使用前者;如果想随时监控信号变化,可使用后者。