从零搭建 ALU SystemVerilog 验证平台
这是一份任务索引:先看每项任务与关键答案,需要原理、完整代码片段和排错过程时再点进细节页。配套源码见 ALU 仓库。文中“已完成”表示当前工程中已实现或已实测;早期阶段的 PASS 数仅记录当时的进度。
当前验收基线
8 位 ALU;17 条定向测试与 20 条约束随机测试。种子 12345、67890、24212 各为
PASS=37 FAIL=0;合法操作码覆盖率为 100%。三条 SVA 均曾通过独立反例注入触发。这些结果不等于已经穷尽所有输入和时序。
一、让测试自己判对错
- 搭建最小自检平台。 任务:接入时钟、复位、激励、参考模型和自动比较。关键答案:
expected = reference_model(op,a,b),用!==比较实际结果;最初六例通过。功能与边界 - 读懂复位赋值。 问题:
result <= '0;是什么?关键答案:'0随目标位宽填满零,8 位result得到8'b0000_0000。功能与边界 - 加测试与新运算。 任务:加入
7+9、op=5左移及参考模型;覆盖移位量 0、1、8。关键代码:result <= a << b;,只保留结果低 8 位。功能与边界 - 解释溢出与回绕。 答案:
8'hff + 1得 0,3-5以 8 位无符号形式打印为 254。XOR 被故意改为 OR 时,f0 ^ 5a = aa,错误结果f0 | 5a = fa,检查器能报不匹配。功能与边界
二、让每笔测试成为 transaction
- 定义事务并替换定向测试。 任务:给
alu_transaction放入op/a/b,tr = new(3'd0, 8'd7, 8'd9); drive_and_check(tr);;14 条定向测试曾全部通过。每笔新建对象比反复改同一对象更适合以后并行传递。事务与包 - 排除
tr附近的语法错误。 答案:检查前面的类型名是否准确写成alu_transaction;“报错指向tr”不代表tr自身写错。事务与包 - 解决类型不可见。 答案:在
alu_pkg包内用include纳入类文件,top 中import alu_pkg::*;。只调整独立文件的编译顺序不保证跨编译单元可见。事务与包 - 安排编译顺序。 先编译 interface,再编译 assertions 与 package,最后 top;
+incdir+../tb是头文件搜索目录,../rtl/simple_alu.sv才是源文件参数。事务与包
三、拆分验证平台
- 抽出 driver。 用
virtual alu_if #(8) vif保存真实接口的引用;下降沿驱动请求,下一上升沿之后采样。virtual在这里表示类持有接口句柄,不是复制一套接口。分层平台与 virtual interface - 抽出 monitor。
observed = mon.sample();创建输出快照;monitor 不驱动、不计算期望值、不判分。分层平台与 virtual interface - 抽出 scoreboard。
scb.check(sent, observed)用独立模型比较,并维护pass_count/fail_count;函数从 top 搬进类后要处理WIDTH作用域。分层平台与 virtual interface - 区分
$error与$fatal。 一次数据不匹配可$error后计数、继续收集;空句柄等无法安全继续的问题用$fatal。两者都不会自动替自定义fail_count加一。分层平台与 virtual interface
四、从定向测试走向随机与覆盖率
- 保留原有 14 条测试再加随机。 不用随机替换定向边界;在定向部分后
repeat (20),每次new()、检查randomize()返回值、再发送。约束随机 - 收束约束。 合法随机操作码为 0~5;左移时把
b限在 0~8。op=6/7通过定向测试独立覆盖,不被随机合法操作约束抹去。约束随机 - 看懂种子。
-sv_seed 12345固定可复现的随机序列;-sv seed不是同一个选项。换种子仍要每次重新run -all。约束随机 - 解释操作码 100%。
bins each_op[] = {[3'd0:3'd5]};只说明六种合法操作都出现过;删掉随机测试仍可能是 100%,因为定向测试已经用过它们。功能覆盖率 - 规划覆盖率闭环。 左移量 0/1~7/8、加法进位、复位时机等应有单独覆盖点;覆盖率记录“见过”,记分板判断“正确”。功能覆盖率
五、检查时序并验证检查器
-
接入 SVA。 top 实例
alu_assertions checks,将.result(vif.result)与其他信号连接;模块中并不存在名为alu_assertion checks的源代码短语。断言、复位与回归 -
判断用
|=>还是|->。 请求在某上升沿被采样,DUT 非阻塞赋值的响应要到之后才可见;下一个采样沿检查rsp_valid,因此这里用req_valid |=> rsp_valid。断言、复位与回归 -
补无凭空响应与空闲保持。
!req_valid |=> !rsp_valid;!req_valid |=> $stable(result)。复位期间用disable iff (!rst_n)关闭正常协议检查。断言、复位与回归 -
消除复位释放竞争。 在
@(negedge clk)拉高rst_n;异步拉低后先检查rsp_valid/result为零,再继续运行。另以非零结果后复位确认运行中复位。断言、复位与回归 -
补非法操作码。 定向发送
op=6/7,确认默认result='0;另用6+9=15先制造非零结果再复位。当前 17 条定向测试+20 条随机测试共 37 条。功能与边界 -
做 SVA 反例验证。 分别强制
rsp_valid=0、rsp_valid=1、空闲期更改result;目标断言分别在 45、55、55 ns 报错。force仅作用于本次仿真。断言、复位与回归 -
做三种子回归。 对每个种子加载、运行、卸载仿真,汇总 PASS/FAIL;
-onfinish stop在加载设计时设置,quit -sim只卸载当前仿真,最后quit -f退出 Questa。断言、复位与回归 -
保存可回溯基线。 将 ALU 项目源码推到 GitHub 仓库;提交前区分源码与仿真生成的
work、波形、日志,不能把“远端有代码”误当成测试证据。环境与运行
六、工具与排错问答
- PowerShell、Questa GUI 与
.do。 脚本执行策略可用run.bat绕过;.do用#注释而非//;GUI 可打开项目,不必逐个打开.sv。环境与运行 -novopt与 Full optimization。-novopt已弃用;优化不会改源码或自动修 error。GUI 的主优化开关与优化级别是两回事。环境与运行$finish、quit -f与查看结果。 前者结束测试,后者退出仿真器;交互调试可停在 Transcript,批处理通过日志和回归摘要查看结果。环境与运行- 编译或编辑失效时先看第一条错误。 确认文件确实保存、当前调用的是 Questa 而非另一套
vsim、源码被真实编译且没有被旧work库掩盖。断言、复位与回归
关于“完成”
本系列记录的是已实施的 ALU 学习任务和验收证据。操作码覆盖率不是需求覆盖率;三种子通过与三条断言能抓住选定反例,也不能证明设计绝无缺陷。