7.1 测试台:搭一座会提问的法庭 本节摘要:测试台是包围被测模块的仿真顶层:时钟、复位、激励、实例化、观测五要素各就各位。激励写法从逐拍堆语句升级到事务级 task,配合并行块构造并发场景。测试台的质量决定验证的深度——它得是会提问的控方,不是念稿的传声筒。 惯犯档案里的每个结构,从这一章开始都要过堂。第一件事是搭法庭:测试台(testbench)就是仿真世界的法庭建筑——它不在综合层次里,只活在仿真器中,职责是把被测模块(DUT)围起来,喂信号、收信号、挑毛病。 五要素标准布局 这份骨架里有几处惯例值得说明。信号声明全部用 reg——测试台是仿真顶层,激励信号由过程块驱动,第 2 章的类型规则在这里同样适用。
本节摘要:测试台是包围被测模块的仿真顶层:时钟、复位、激励、实例化、观测五要素各就各位。激励写法从逐拍堆语句升级到事务级 task,配合并行块构造并发场景。测试台的质量决定验证的深度——它得是会提问的控方,不是念稿的传声筒。
惯犯档案里的每个结构,从这一章开始都要过堂。第一件事是搭法庭:测试台(testbench)就是仿真世界的法庭建筑——它不在综合层次里,只活在仿真器中,职责是把被测模块(DUT)围起来,喂信号、收信号、挑毛病。
`timescale 1ns/1ps module tb_uart_rx; // 要素一:信号声明——接 DUT 的每一根线 reg clk; reg rst_n; reg rx; wire [7:0] data; wire done; // 要素二:DUT 实例化 uart_rx #(.CLKS_PER_BIT(8)) dut ( .clk (clk), .rst_n (rst_n), .rx (rx), .data (data), .done (done) ); // 要素三:时钟 initial clk = 1'b0; always #5 clk = ~clk; // 100 MHz // 要素四:复位与激励 initial begin rst_n = 1'b0; rx = 1'b1; // 空闲态为高 #23 rst_n = 1'b1; send_byte(8'hA5); send_byte(8'h00); send_byte(8'hFF); #2000; $finish; end // 要素五:观测与自动检查(检查器独立成模块,下一节展开) uart_rx_check #(.CLKS_PER_BIT(8)) chk ( .clk(clk), .rst_n(rst_n), .rx(rx), .data(data), .done(done) ); // 事务级激励:发送一个字节 task automatic send_byte(input [7:0] b); integer i; begin rx = 1'b0; // 起始位 repeat (8) @(posedge clk); for (i = 0; i < 8; i = i + 1) begin rx = b[i]; // 低位在先 repeat (8) @(posedge clk); end rx = 1'b1; // 停止位 repeat (8) @(posedge clk); end endtask endmodule
这份骨架里有几处惯例值得说明。信号声明全部用 reg——测试台是仿真顶层,激励信号由过程块驱动,第 2 章的类型规则在这里同样适用。复位释放时刻刻意避开时钟整周期边界,让「复位期间时钟照跑」「复位释放沿与时钟相位随机」这些真实条件在仿真里也成立。分频比参数故意取小值:分频比不影响验证逻辑,小值让仿真快十几倍——测试台的原则是「能抽象就抽象,别陪硬件数拍子」。

逐拍堆语句是起步段位:延迟加赋值一路写下去,直白但不可复用,一个用例一份代码。事务级 task 是工程段位:把「发一个字节」「发起一次读事务」封装成 task,用例序列变成一行行人话——上面骨架里的 send_byte 就是示范,激励与协议细节解耦后,写用例的人不必懂位时序。
并发场景用并行块:多个激励流同时推进,模拟「两个请求者同时发起」「数据与使能几乎同时到」的真实竞争。并行块内各分支独立走自己的时序,块结束等全部完成——这是验证仲裁器、握手协议的标配手法。分支内再用顺序块加时序控制,各分支的节拍互不干扰。
随机激励是高级段位:Verilog 层有随机数系统函数可用,配合固定种子让随机序列可复现。纯 Verilog 的随机验证能力有限(受约束的随机化要等 SystemVerilog),但「边界值随机扫描」已经能抓到大量手写激励想不到的角落——7.3 节的回归组织会回到这个话题。
案例展开:把上百次手写激励压成一条循环。 背景:某接口模块早期用例手写了几百行逐拍激励,接口时序一改,所有用例手工逐行修正,维护成本失控。操作:把接口时序抽成发送与接收两个事务 task,用例主体改写成「事务序列 + 检查声明」,原有用例全部改写为 task 调用序列;接口时序变更时只改 task 内部实现。结果:接口改动后的用例维护量从按行修变成零——用例语义层不动;用例总数顺势翻倍,因为加一个用例只要几行调用。解读:测试台的复用单位是「事务」而不是「拍」,把时序知识封装进 task,用例就获得了对接口演化的免疫力——这与软件工程「封装变化」是同一条原理。变式:协议带可选参数(突发长度、等待周期)时,给 task 加参数和默认值,同一 task 覆盖协议变体,避免 task 数量膨胀。
测试台代码要遵守几条纪律,否则它会变成新的债。其一,测试台与设计代码分目录管理,明确「这部分不综合」的边界。其二,激励 task 与检查逻辑分离——检查器做成独立模块(如骨架里的检查模块),同一套检查器服务所有用例,换激励不换判决标准。其三,每个用例有明确的结束条件与退出码:仿真结束前打印通过或失败的计数,让回归脚本能机器判卷。其四,时间单位与精度在测试台头部声明清楚,所有延迟按单位写、不混用精度。
还有一条容易被忽略的:测试台本身也要做版本管理,跟设计同仓库同变更记录。项目复盘时,「这个 bug 是哪个用例漏掉的」必须能追溯到具体版本的测试台——验证资产的工程属性,在这里体现得最具体。
激励信号要给确定初值,但不一定要走复位流程。测试台在时间零点直接赋值即可——它不参与综合,没有「上电未知」的物理问题。需要模拟的只是被测设计对复位的响应,所以测试台自己关心的是「激励序列从确定的值开始」,复位信号本身只是激励的一员。
三种结束方式对应三种用例形态:定向用例跑完激励序列自然结束;随机用例到达预设的事务数或时间上限结束;异常情况由看门狗强制结束。无论哪种,结束前都要走一遍判卷输出——没有判卷的结束等于白跑。结束条件含糊是用例设计的常见缺陷,「跑多久算完」必须在写用例时就想清楚。
能,层次引用让测试台可以读取 DUT 内部任何信号——这是排查的重要工具,也是要节制使用的诱惑。内部信号观测适合「调试确认假设」,不适合「用例判卷」:判卷依赖内部信号会让测试台与实现细节耦合,实现一重构用例就失效。规矩是判卷只看接口,内部信号只在人查问题时打开。
法庭搭好了,下一节装上记录仪与自动判决:显示、监控与自检查。