记分板 SV 模板:期望队列、四态比较与可靠退出状态
更新时间:2026-10-31。这是芯片设计入门的 testbench 模板篇,适合已经会写时钟、复位和简单激励,准备把「肉眼看波形」升级为自动比对的读者。记分板 SV 模板可以配合 同步 FIFO Verilog 模板 验证先入先出语义,也可以作为 两级同步寄存器 Verilog 模板 外围自检环境的一部分。
首屏答案:有序记分板就是一条期望队列加三个判断。发送交易时把期望值 push_back,收到实际输出时 pop_front 并用 !== 比较;任何不一致、意外输出或测试结束仍残留的期望都累加 errors;结尾在 errors != 0 时执行 $fatal(1, ...)。只打印 MISMATCH 却不改变进程退出状态,脚本仍可能把失败测试当成成功。
本文要回答的问题
- 为什么期望队列适合不允许乱序的接口,队头比较能发现哪些问题?
- 为什么实际值与期望值要用
!==,而不是普通的==? - 空队列收到输出、结束时仍有未消费期望,分别说明什么?
- 怎样让错误计数真正反映到仿真进程和调用脚本的退出状态?
一、记分板 SV 模板的四个组成部分
记分板(scoreboard)是保存期望结果并与被测设计输出自动比对的检查结构。下面模板验证一个单拍寄存器:输入有效后,下一拍应得到同样的数据。被测逻辑很简单,注意力可以全部放在自检骨架上;换成 FIFO 或串行总线时,只需替换交易类型和采样条件。
这张图要抓住一条闭环:左侧不是「打印日志」,而是保存可执行的判据;中间的每次输出必须消费一项期望;右侧既检查数据,也检查交易数量,最后把结果交给进程退出码。少任何一段,都可能出现日志看似完整、结论却不可信的情况。
二、有序队列与实际比对
不允许乱序的接口要求第 N 个输入对应第 N 个输出。SystemVerilog 队列的 push_back 和 pop_front 正好表达这种关系:激励进入时登记,实际输出到达时取最早的一项。若设计把后一笔提前返回,即使两个数据都在本次测试出现过,队头比较仍会立即报错。
logic [7:0] expected_q[$];
integer errors = 0;
integer checks = 0;
// 在驱动有效输入的同一个控制点登记期望。
expected_q.push_back(expected_value);
// 在输出有效且完成采样的时刻比对。
if (out_valid) begin
if (expected_q.size() == 0) begin
errors = errors + 1;
$display("ERROR unexpected actual=%02h queue_depth=0", out_data);
end else begin
expected = expected_q.pop_front();
checks = checks + 1;
if (out_data !== expected) begin
errors = errors + 1;
$display("MISMATCH actual=%02h expected=%02h check=%0d",
out_data, expected, checks);
end
end
end实际工程里应把这段监测放在明确的采样点。本例寄存器使用非阻塞赋值,监测进程在 posedge 后等待 #1,避免读到更新前的数据。队列操作只保留在一个监测进程中,也不要让多个进程同时消费同一队列,否则错误可能来自测试平台自身的竞争。
| 检查位置 | 发现的现象 | 计数动作 | 常见根因 |
|---|---|---|---|
| 输出有效时 | 期望队列为空 | errors + 1,不弹出队列 | 重复输出、幽灵输出、漏登记期望 |
| 输出有效时 | 实际值与队头不同 | errors + 1,消费该项期望 | 数据错误、顺序错误、延迟错位 |
| 测试结束时 | 队列仍有期望项 | errors + 1,报告剩余深度 | 丢输出、提前结束、激励未完成 |
表中第二行仍消费错误项,是为了让后续交易继续按原顺序比对;第三行不可省略。只检查「收到的都正确」,会让「少收到一笔」悄悄通过。
登记期望的时机也要与接口约定一致。本例在驱动有效输入时立即入队,因为寄存器保证每个有效输入都有输出;带反压的接口则应在 valid && ready 握手成立时入队,不能只看 valid。若输出可能丢弃或合并交易,应先明确协议规则,再决定期望是否出队,不能为了消掉报错直接清空队列。
期望值最好由独立参考模型产生,例如软件算法、已知测试向量或协议变换。若把被测模块的同一段组合逻辑复制进 testbench,两份相同实现可能一起错,比对仍然全绿。简单直通模块可以直接使用输入常量;复杂算法则应让参考模型与 RTL 在结构和实现路径上保持独立。
三、四态比较与最终退出状态
SystemVerilog 逻辑有 0、1、X、Z 四态。普通相等运算遇到未知位可能得到 X,而 if 不会把这种结果当作确定的不等来处理;case 不等运算符 !== 则逐位比较四态,只要任何一位不同,包括未知态差异,就返回确定的 1。因此自检数据通路默认使用 !==,不要用会忽略 X/Z 的 casex 思路掩盖问题。
比对之外,收尾统一汇总三类错误。下面代码先检查未消费期望,再决定退出方式;checks 说明真正完成了多少次比对,expected_left 则防止空测试或无输出测试假通过。
if (expected_q.size() != 0) begin
errors = errors + 1;
$display("ERROR unconsumed expected entries=%0d", expected_q.size());
end
$display("RESULT checks=%0d errors=%0d expected_left=%0d",
checks, errors, expected_q.size());
if (errors != 0)
$fatal(1, "TEST FAILED errors=%0d", errors);
else begin
$display("TEST PASSED");
$finish;
end这里最关键的是 $fatal(1, ...),不是 $display("FAIL")。在本文使用的 Icarus Verilog 中,前者让 vvp 返回 1,后者只增加一行文字。run.sh 分别保存两次 vvp 的退出码:正常组必须为 0,注错组必须非零;只有两者同时满足,脚本才报告 RUN_SH_RESULT=PASS。
调用脚本时不要写成 vvp sim.vvp || true,也不要只依赖管道末尾命令的状态,这些写法会覆盖或丢失仿真失败。正常回归应让非零状态继续向上传递;注错测试是例外,但必须检查它确实非零。如果注错组意外返回 0,说明检查器失效,整个验证脚本也应失败。
还应给完整测试加超时保护。记分板能发现仿真自然结束时的残留期望,却无法在死锁导致永远不到结尾时给出结论。外层运行器限制墙钟时间,或 testbench 设置独立的看门狗计数,才能把「没有输出」稳定地区分为失败,而不是一直等待。
四、全通过与故意注错的运行结果
实验连续跑三组数据 11、22、33。注错组仅把第三笔期望改成 32,被测设计仍输出 33,因此应出现一次 mismatch,并由仿真进程返回非零。
正常组的 checks=3 errors=0 expected_left=0 表明三笔输出都完成比对,没有残留期望,PASS_CASE_EXIT=0 是进程级成功。注错段则同时保留数据证据与状态证据,避免只看到一行失败文字却不知道调用方是否能感知。
MISMATCH actual=33 expected=32 check=3
RESULT checks=3 errors=1 expected_left=0
FATAL: tb_scoreboard.sv:79: TEST FAILED errors=1
Time: 87000 Scope: tb_scoreboard
MISMATCH_CASE_EXIT=1
RUN_SH_RESULT=PASS (normal case exited 0; injected mismatch was rejected)这段输出中的 RUN_SH_RESULT=PASS 指测试容器正确识别了预期失败,并不表示注错用例通过。真正的用例结论是上一行 MISMATCH_CASE_EXIT=1。接入持续集成时,正常测试直接使用仿真退出码;预期失败测试则应像脚本这样显式检查非零,不能统一忽略退出状态。
实验环境
- 机器/系统:Mac(x86_64)/ Darwin 25.5.0
- 工具版本:Icarus Verilog 13.0(stable,v13_0)
- 日期:2026-10-31
- 完整命令:
iverilog -g2012 -s tb_scoreboard -o .tb_scoreboard.vvp scoreboard_dut.sv tb_scoreboard.sv;vvp .tb_scoreboard.vvp;vvp .tb_scoreboard.vvp +INJECT_MISMATCH;bash run.sh
代码获取
完整被测模块、testbench 与运行脚本位于公开仓 https://github.com/derekzhuo/geek-doc.cn/tree/main/experiments/tpl-scoreboard-sv。安装 Icarus Verilog 后进入该目录执行 bash run.sh,脚本会运行正常组与注错组,并在退出时清理编译产物。
这一篇在知识体系里的位置
本篇是有序接口自检的基础模板。队列本身承载等待发送的数据时,可参考 同步 FIFO Verilog 模板;跨时钟域模块外围仍需要相同的结果检查,可结合 两级同步寄存器 Verilog 模板。若接口允许乱序,应改用事务标识索引的关联数组,不能继续只比较队头。
总结
记分板 SV 模板的可靠骨架是:发送时按序登记期望,输出时消费队头并用 !== 做四态比较,意外输出、数据不一致和未消费期望全部计入 errors,结尾用 $fatal(1, ...) 把失败传成非零退出。实测正常组三次比对通过并返回 0,故意把 33 的期望写成 32 后返回 1;只有这种「数据比对、数量完整、退出状态」三者闭环的测试,才不会把 mismatch 留成一行无人处理的假绿日志。