driver、monitor、scoreboard 与 virtual interface

返回 ALU 任务总览。

为什么拆层

顶层测试流程现在只协调组件:

drv.drive(tr);
observed = mon.sample();
scb.check(tr, observed);
cov.op_cg.sample(tr.op);
drv.idle();
  • driver 驱动输入,不算期望值。
  • monitor 读取接口并创建观测快照,不驱动、不判分。
  • scoreboard 按规格计算期望值并比较,维护通过/失败计数。
  • coverage 记录见过的场景,不判正确性。
  • top 管理时钟、复位、用例与组件调用顺序。

这是教学用的顺序分层平台,尚不是独立常驻线程+队列通信的完整并行环境。

任务:理解 virtual alu_if #(8) vif

接口实例在 top 中,例如 alu_if #(8) vif(clk);。类不能像模块那样实例化实体接口,便通过一个 virtual interface 变量持有实际接口的引用:

class alu_driver;
  virtual alu_if #(8) vif;
 
  function new(virtual alu_if #(8) vif);
    this.vif = vif;
    if (vif == null)
      $fatal(1, "alu_driver received a null interface");
  endfunction
endclass
 
// top 的 initial 中
drv = new(vif);

左边 this.vif 是 driver 成员;右边 vif 是构造参数;top 的 vif 是实际接口实例。三者同名但作用域不同。virtual 不创建或复制硬件接口,也不是此处的“虚函数”;它让类方法能通过句柄访问 vif.req_valid、vif.result 等信号。这里的参数化位宽 #(8) 要与实际接口匹配。

任务:在正确时刻驱动和采样

driver 在下降沿放好 req_valid/op/a/b,等待下一上升沿,再等教学用的 #1,让 DUT 非阻塞赋值完成:

@(negedge vif.clk);
vif.req_valid = 1'b1;
vif.op = tr.op;
vif.a  = tr.a;
vif.b  = tr.b;
@(posedge vif.clk);
#1;

因此 top 随后调用 mon.sample() 时直接取快照,不要再等一次上升沿。#1 不是通用的竞态解决方案;后续更复杂协议应学习 clocking block 与调度区,并统一时间精度。

任务:让 scoreboard 独立判分

核心检查形式:

expected = reference_model(sent.op, sent.a, sent.b);
if (observed.rsp_valid !== 1'b1) begin
  $error("Response is not valid");
  fail_count++;
end else if (observed.result !== expected) begin
  $error("Mismatch: expected=%0d actual=%0d", expected, observed.result);
  fail_count++;
end else begin
  pass_count++;
end

!== 会把 X/Z 也判为不符合已知期望。模型函数必须在 scoreboard 可见的作用域;不能因为 top 有同名函数,类就能直接调用。搬迁时 WIDTH 曾只定义在 top,类里不能直接使用它;当前各组件固定 8 位,整个环境尚未完全参数化。

$display/$info 用于信息,$warning 用于提醒,$error 报错后通常可继续,$fatal 适合空句柄等无法安全继续的错误,$finish 结束正常测试,$stop 用于交互暂停。$fatal(1, "...") 的 1 是诊断级别参数,不是失败次数。自定义 fail_count 仍需显式累加。

新增类文件后若仍提示找不到,先确认文件已保存、非 0 字节,package 已包含它、top 已声明句柄并调用 new();“文件存在”不等于“对象已创建”。