从零搭建 ALU SystemVerilog 验证平台

这是一份任务索引:先看每项任务与关键答案,需要原理、完整代码片段和排错过程时再点进细节页。配套源码见 ALU 仓库。文中“已完成”表示当前工程中已实现或已实测;早期阶段的 PASS 数仅记录当时的进度。

当前验收基线

8 位 ALU;17 条定向测试与 20 条约束随机测试。种子 12345、67890、24212 各为 PASS=37 FAIL=0;合法操作码覆盖率为 100%。三条 SVA 均曾通过独立反例注入触发。这些结果不等于已经穷尽所有输入和时序。

一、让测试自己判对错

  1. 搭建最小自检平台。 任务:接入时钟、复位、激励、参考模型和自动比较。关键答案:expected = reference_model(op,a,b),用 !== 比较实际结果;最初六例通过。功能与边界
  2. 读懂复位赋值。 问题:result <= '0; 是什么?关键答案:'0 随目标位宽填满零,8 位 result 得到 8'b0000_0000。功能与边界
  3. 加测试与新运算。 任务:加入 7+9、op=5 左移及参考模型;覆盖移位量 0、1、8。关键代码:result <= a << b;,只保留结果低 8 位。功能与边界
  4. 解释溢出与回绕。 答案:8'hff + 1 得 0,3-5 以 8 位无符号形式打印为 254。XOR 被故意改为 OR 时,f0 ^ 5a = aa,错误结果 f0 | 5a = fa,检查器能报不匹配。功能与边界

二、让每笔测试成为 transaction

  1. 定义事务并替换定向测试。 任务:给 alu_transaction 放入 op/a/b,tr = new(3'd0, 8'd7, 8'd9); drive_and_check(tr);;14 条定向测试曾全部通过。每笔新建对象比反复改同一对象更适合以后并行传递。事务与包
  2. 排除 tr 附近的语法错误。 答案:检查前面的类型名是否准确写成 alu_transaction;“报错指向 tr”不代表 tr 自身写错。事务与包
  3. 解决类型不可见。 答案:在 alu_pkg 包内用 include 纳入类文件,top 中 import alu_pkg::*;。只调整独立文件的编译顺序不保证跨编译单元可见。事务与包
  4. 安排编译顺序。 先编译 interface,再编译 assertions 与 package,最后 top;+incdir+../tb 是头文件搜索目录,../rtl/simple_alu.sv 才是源文件参数。事务与包

三、拆分验证平台

  1. 抽出 driver。 用 virtual alu_if #(8) vif 保存真实接口的引用;下降沿驱动请求,下一上升沿之后采样。virtual 在这里表示类持有接口句柄,不是复制一套接口。分层平台与 virtual interface
  2. 抽出 monitor。 observed = mon.sample(); 创建输出快照;monitor 不驱动、不计算期望值、不判分。分层平台与 virtual interface
  3. 抽出 scoreboard。 scb.check(sent, observed) 用独立模型比较,并维护 pass_count/fail_count;函数从 top 搬进类后要处理 WIDTH 作用域。分层平台与 virtual interface
  4. 区分 $error 与 $fatal。 一次数据不匹配可 $error 后计数、继续收集;空句柄等无法安全继续的问题用 $fatal。两者都不会自动替自定义 fail_count 加一。分层平台与 virtual interface

四、从定向测试走向随机与覆盖率

  1. 保留原有 14 条测试再加随机。 不用随机替换定向边界;在定向部分后 repeat (20),每次 new()、检查 randomize() 返回值、再发送。约束随机
  2. 收束约束。 合法随机操作码为 0~5;左移时把 b 限在 0~8。op=6/7 通过定向测试独立覆盖,不被随机合法操作约束抹去。约束随机
  3. 看懂种子。 -sv_seed 12345 固定可复现的随机序列;-sv seed 不是同一个选项。换种子仍要每次重新 run -all。约束随机
  4. 解释操作码 100%。 bins each_op[] = {[3'd0:3'd5]}; 只说明六种合法操作都出现过;删掉随机测试仍可能是 100%,因为定向测试已经用过它们。功能覆盖率
  5. 规划覆盖率闭环。 左移量 0/1~7/8、加法进位、复位时机等应有单独覆盖点;覆盖率记录“见过”,记分板判断“正确”。功能覆盖率

五、检查时序并验证检查器

  1. 接入 SVA。 top 实例 alu_assertions checks,将 .result(vif.result) 与其他信号连接;模块中并不存在名为 alu_assertion checks 的源代码短语。断言、复位与回归

  2. 判断用 |=> 还是 |->。 请求在某上升沿被采样,DUT 非阻塞赋值的响应要到之后才可见;下一个采样沿检查 rsp_valid,因此这里用 req_valid |=> rsp_valid。断言、复位与回归

  3. 补无凭空响应与空闲保持。 !req_valid |=> !rsp_valid;!req_valid |=> $stable(result)。复位期间用 disable iff (!rst_n) 关闭正常协议检查。断言、复位与回归

  4. 消除复位释放竞争。 在 @(negedge clk) 拉高 rst_n;异步拉低后先检查 rsp_valid/result 为零,再继续运行。另以非零结果后复位确认运行中复位。断言、复位与回归

  5. 补非法操作码。 定向发送 op=6/7,确认默认 result='0;另用 6+9=15 先制造非零结果再复位。当前 17 条定向测试+20 条随机测试共 37 条。功能与边界

  6. 做 SVA 反例验证。 分别强制 rsp_valid=0、rsp_valid=1、空闲期更改 result;目标断言分别在 45、55、55 ns 报错。force 仅作用于本次仿真。断言、复位与回归

  7. 做三种子回归。 对每个种子加载、运行、卸载仿真,汇总 PASS/FAIL;-onfinish stop 在加载设计时设置,quit -sim 只卸载当前仿真,最后 quit -f 退出 Questa。断言、复位与回归

  8. 保存可回溯基线。 将 ALU 项目源码推到 GitHub 仓库;提交前区分源码与仿真生成的 work、波形、日志,不能把“远端有代码”误当成测试证据。环境与运行

六、工具与排错问答

  1. PowerShell、Questa GUI 与 .do。 脚本执行策略可用 run.bat 绕过;.do 用 # 注释而非 //;GUI 可打开项目,不必逐个打开 .sv。环境与运行
  2. -novopt 与 Full optimization。 -novopt 已弃用;优化不会改源码或自动修 error。GUI 的主优化开关与优化级别是两回事。环境与运行
  3. $finish、quit -f 与查看结果。 前者结束测试,后者退出仿真器;交互调试可停在 Transcript,批处理通过日志和回归摘要查看结果。环境与运行
  4. 编译或编辑失效时先看第一条错误。 确认文件确实保存、当前调用的是 Questa 而非另一套 vsim、源码被真实编译且没有被旧 work 库掩盖。断言、复位与回归

关于“完成”

本系列记录的是已实施的 ALU 学习任务和验收证据。操作码覆盖率不是需求覆盖率;三种子通过与三条断言能抓住选定反例,也不能证明设计绝无缺陷。