跳到主要内容

测试平台

测试平台(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, %CASCII 字符
%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 文件、选择性记录
  • 结果检查:自动检查、使用任务检查
  • 显示和监控displaydisplay、monitor、$strobe
  • 文件操作:读取文件、写入文件
  • 高级技术:函数、任务、并行测试

编写完善的测试平台是验证设计正确性的关键。下一章将介绍高级特性。