测试平台
测试平台(Testbench)用于验证设计功能的正确性。本章介绍如何编写高效的测试平台。
测试平台基础
基本结构
`timescale 1ns / 1ps
module tb_example;
// 信号声明
reg clk;
reg reset;
reg [7:0] data_in;
wire [7:0] data_out;
// 实例化被测模块
dut u_dut (
.clk(clk),
.reset(reset),
.data_in(data_in),
.data_out(data_out)
);
// 时钟生成
initial begin
clk = 0;
forever #5 clk = ~clk;
end
// 测试激励
initial begin
// 初始化
reset = 1;
data_in = 0;
// 等待复位完成
#20 reset = 0;
// 测试用例
#10 data_in = 8'h55;
#10 data_in = 8'hAA;
#10 data_in = 8'hFF;
// 结束仿真
#100 $finish;
end
endmodule
时钟生成
简单时钟
// 固定周期时钟
initial begin
clk = 0;
forever #5 clk = ~clk; // 周期 10ns
end
参数化时钟
parameter CLK_PERIOD = 10; // 时钟周期(ns)
initial begin
clk = 0;
forever #(CLK_PERIOD/2) clk = ~clk;
end
占空比可调时钟
parameter HIGH_TIME = 3; // 高电平时间
parameter LOW_TIME = 2; // 低电平时间
initial begin
clk = 0;
forever begin
#HIGH_TIME clk = 1;
#LOW_TIME clk = 0;
end
end
门控时钟
reg clk_enable;
wire gated_clk;
initial begin
clk = 0;
clk_enable = 0;
forever #5 clk = ~clk;
end
assign gated_clk = clk & clk_enable;
复位生成
同步复位
initial begin
reset = 0;
@(posedge clk);
reset = 1;
@(posedge clk);
reset = 0;
end
异步复位
initial begin
reset = 1;
#100 reset = 0;
end
复位序列
initial begin
// 复位序列
reset = 1;
#100;
reset = 0;
#50;
// 可选:再次复位
reset = 1;
#20;
reset = 0;
end
测试激励
直接激励
initial begin
// 测试用例 1
data_in = 8'h00;
#10;
// 测试用例 2
data_in = 8'h55;
#10;
// 测试用例 3
data_in = 8'hAA;
#10;
// 测试用例 4
data_in = 8'hFF;
#10;
$finish;
end
循环激励
integer i;
initial begin
for (i = 0; i < 256; i = i + 1) begin
data_in = i[7:0];
#10;
end
$finish;
end
随机激励
initial begin
repeat (100) begin
data_in = $random;
#10;
end
$finish;
end
约束随机激励
integer delay;
integer data;
initial begin
repeat (50) begin
// 随机延迟
delay = $urandom_range(1, 20);
#delay;
// 约束随机数据
data = $urandom_range(0, 255);
data_in = data[7:0];
end
$finish;
end
波形输出
VCD 文件
initial begin
$dumpfile("waveform.vcd"); // 输出文件名
$dumpvars(0, tb_example); // 0 表示记录所有层次
end
选择性记录
initial begin
$dumpfile("waveform.vcd");
$dumpvars(1, tb_example); // 只记录顶层
$dumpvars(0, tb_example.u_dut); // 记录 u_dut 及其子模块
end
LXT 文件
initial begin
$dumpfile("waveform.lxt2");
$dumpvars(0, tb_example);
end
结果检查
自动检查
reg [7:0] expected;
wire [7:0] actual;
initial begin
// 测试用例 1
data_in = 8'h55;
expected = 8'hAA; // 期望输出
#10;
if (actual !== expected) begin
$display("Error: expected %h, got %h", expected, actual);
$finish;
end
// 测试用例 2
data_in = 8'hAA;
expected = 8'h55;
#10;
if (actual !== expected) begin
$display("Error: expected %h, got %h", expected, actual);
$finish;
end
$display("All tests passed!");
$finish;
end
使用任务检查
task check_result;
input [7:0] expected;
input [7:0] actual;
input [255:0] test_name;
begin
if (actual !== expected) begin
$display("[%0t] FAIL: %s - expected %h, got %h",
$time, test_name, expected, actual);
end else begin
$display("[%0t] PASS: %s", $time, test_name);
end
end
endtask
initial begin
data_in = 8'h55;
#10;
check_result(8'hAA, data_out, "Test case 1");
data_in = 8'hAA;
#10;
check_result(8'h55, data_out, "Test case 2");
$finish;
end
显示和监控
$display
initial begin
$display("Simulation started at time %0t", $time);
$display("Data: %h, %d, %b", data, data, data);
$display("String: %s", "Hello");
end
$monitor
// 持续监控信号变化
initial begin
$monitor("[%0t] clk=%b, reset=%b, data=%h",
$time, clk, reset, data_in);
end
$strobe
// 在时间步结束时打印
always @(posedge clk) begin
$strobe("[%0t] data_out = %h", $time, data_out);
end
格式化说明符
| 说明符 | 描述 |
|---|---|
| %h, %H | 十六进制 |
| %d, %D | 十进制 |
| %b, %B | 二进制 |
| %o, %O | 八进制 |
| %t, %T | 时间 |
| %s, %S | 字符串 |
| %c, %C | ASCII 字符 |
| %m, %M | 模块层次路径 |
文件操作
读取文件
reg [7:0] mem [0:255];
integer file;
initial begin
// 读取十六进制文件
$readmemh("data.hex", mem);
// 读取二进制文件
$readmemb("data.bin", mem);
// 读取部分数据
$readmemh("data.hex", mem, 0, 127); // 起始和结束地址
end
写入文件
integer file;
initial begin
file = $fopen("output.txt", "w");
$fdisplay(file, "Simulation Results");
$fdisplay(file, "Time: %0t", $time);
$fdisplay(file, "Data: %h", data_out);
$fclose(file);
end
文件模式
| 模式 | 描述 |
|---|---|
| "r" | 只读 |
| "w" | 只写(覆盖) |
| "a" | 追加 |
| "r+" | 读写 |
| "w+" | 读写(覆盖) |
高级测试技术
使用函数
function [7:0] compute_expected;
input [7:0] data_in;
begin
compute_expected = ~data_in; // 示例:期望输出是输入的反
end
endfunction
initial begin
data_in = 8'h55;
#10;
if (data_out !== compute_expected(data_in)) begin
$display("Mismatch!");
end
end
使用任务
task apply_test;
input [7:0] test_data;
begin
@(posedge clk);
data_in = test_data;
@(posedge clk);
end
endtask
initial begin
apply_test(8'h00);
apply_test(8'h55);
apply_test(8'hAA);
apply_test(8'hFF);
$finish;
end
等待条件
initial begin
// 等待信号变化
@(posedge ready);
// 等待特定值
wait(data_out == 8'h55);
// 等待超时
fork
@(posedge done);
#1000 $display("Timeout!");
join_any
disable fork; // 取消未完成的进程
end
并行测试
initial begin
fork
// 进程 1
begin
data_in = 8'h55;
#10;
end
// 进程 2
begin
control = 1;
#5;
control = 0;
end
join
// 两个进程都完成后继续
$display("Both processes completed");
end
完整测试平台示例
计数器测试
`timescale 1ns / 1ps
module tb_counter;
parameter WIDTH = 8;
reg clk;
reg reset;
reg enable;
wire [WIDTH-1:0] count;
wire carry;
// 实例化被测模块
counter #(.WIDTH(WIDTH)) u_dut (
.clk(clk),
.reset(reset),
.enable(enable),
.count(count),
.carry(carry)
);
// 时钟生成
initial begin
clk = 0;
forever #5 clk = ~clk;
end
// 波形输出
initial begin
$dumpfile("counter.vcd");
$dumpvars(0, tb_counter);
end
// 测试激励
initial begin
// 初始化
reset = 1;
enable = 0;
// 复位
#20 reset = 0;
// 测试 1:基本计数
$display("Test 1: Basic counting");
enable = 1;
repeat (10) @(posedge clk);
// 测试 2:暂停计数
$display("Test 2: Pause counting");
enable = 0;
repeat (5) @(posedge clk);
// 测试 3:继续计数
$display("Test 3: Resume counting");
enable = 1;
repeat (10) @(posedge clk);
// 测试 4:复位
$display("Test 4: Reset");
reset = 1;
@(posedge clk);
reset = 0;
if (count !== 0) begin
$display("FAIL: Reset failed, count = %h", count);
end else begin
$display("PASS: Reset successful");
end
// 测试 5:溢出
$display("Test 5: Overflow");
enable = 1;
wait(count == {WIDTH{1'b1}});
@(posedge clk);
if (count !== 0) begin
$display("FAIL: Overflow failed");
end else begin
$display("PASS: Overflow successful");
end
$display("All tests completed");
$finish;
end
endmodule
FIFO 测试
`timescale 1ns / 1ps
module tb_fifo;
parameter DATA_WIDTH = 8;
parameter DEPTH = 16;
reg clk;
reg reset;
reg wr_en;
reg rd_en;
reg [DATA_WIDTH-1:0] din;
wire [DATA_WIDTH-1:0] dout;
wire full;
wire empty;
// 实例化被测模块
sync_fifo #(
.DATA_WIDTH(DATA_WIDTH),
.DEPTH(DEPTH)
) u_dut (
.clk(clk),
.reset(reset),
.wr_en(wr_en),
.rd_en(rd_en),
.din(din),
.dout(dout),
.full(full),
.empty(empty)
);
// 时钟生成
initial begin
clk = 0;
forever #5 clk = ~clk;
end;
// 测试数据
reg [DATA_WIDTH-1:0] test_data [0:DEPTH-1];
integer i;
initial begin
// 初始化测试数据
for (i = 0; i < DEPTH; i = i + 1) begin
test_data[i] = i[DATA_WIDTH-1:0];
end
// 初始化
reset = 1;
wr_en = 0;
rd_en = 0;
din = 0;
// 复位
#20 reset = 0;
// 测试 1:写入数据
$display("Test 1: Write data");
for (i = 0; i < DEPTH; i = i + 1) begin
@(posedge clk);
wr_en = 1;
din = test_data[i];
end
@(posedge clk);
wr_en = 0;
// 检查满标志
if (full !== 1) begin
$display("FAIL: FIFO not full after writing %0d items", DEPTH);
end else begin
$display("PASS: FIFO full");
end
// 测试 2:读取数据
$display("Test 2: Read data");
for (i = 0; i < DEPTH; i = i + 1) begin
@(posedge clk);
rd_en = 1;
@(posedge clk);
if (dout !== test_data[i]) begin
$display("FAIL: Data mismatch at index %0d, expected %h, got %h",
i, test_data[i], dout);
end
end
@(posedge clk);
rd_en = 0;
// 检查空标志
if (empty !== 1) begin
$display("FAIL: FIFO not empty after reading all data");
end else begin
$display("PASS: FIFO empty");
end
$display("All tests completed");
$finish;
end
endmodule
小结
本章介绍了 Verilog 测试平台编写:
- 测试平台基础:基本结构、信号声明
- 时钟生成:简单时钟、参数化时钟、门控时钟
- 复位生成:同步复位、异步复位
- 测试激励:直接激励、循环激励、随机激励
- 波形输出:VCD 文件、选择性记录
- 结果检查:自动检查、使用任务检查
- 显示和监控:monitor、$strobe
- 文件操作:读取文件、写入文件
- 高级技术:函数、任务、并行测试
编写完善的测试平台是验证设计正确性的关键。下一章将介绍高级特性。