第 9 章 · 共 10 章
Scoreboard 与 Analysis 端口
analysis_port 的接收端——uvm_analysis_imp 与 write()——作为和 SV 第 15 章断言并列的第二种、基于软件的检查策略;以及把 agent 和 scoreboard 打包成一个 uvm_env。
第 8 章的 monitor 一直在对着空气广播——mon.ap.write(tr) 调用没有任何东西连接来接收它们。这一章要接上一个东西:一个 scoreboard,UVM 版本的"发现不一致"目标,和 SV 第 15 章(assertions)用 `uvm_error /断言教过的目标一样。断言检查的是靠近 RTL 的时序和协议层面的属性;scoreboard 在软件层面检查端到端的正确性,比对实际发生的和应该发生的——这是第二种互补的策略,不是替代品。
接收端:uvm_analysis_imp 与 write()
第 8 章只讲了 analysis 连接发送的那一半——uvm_analysis_port#(T)::write(t),由 monitor 调用。总得有东西在另一端,真正实现 write(T t),这样连接上的端口才有东西可以调用:
class my_scoreboard extends uvm_scoreboard;
`uvm_component_utils(my_scoreboard)
uvm_analysis_imp #(mux_transaction, my_scoreboard) analysis_export;
function new(string name, uvm_component parent);
super.new(name, parent);
endfunction
function void build_phase(uvm_phase phase);
super.build_phase(phase);
analysis_export = new("analysis_export", this);
endfunction
function void write(mux_transaction tr);
bit expected_y;
expected_y = tr.sel ? tr.b : tr.a;
if (tr.y !== expected_y)
`uvm_error("SB", $sformatf("mismatch: %s expected_y=%0b", tr.convert2string(), expected_y))
else
`uvm_info("SB", $sformatf("match: %s", tr.convert2string()), UVM_MEDIUM)
endfunction
endclassuvm_analysis_imp#(T, IMP) 是 analysis 连接接收端的对应物——它的第二个参数 IMP 指定了真正实现 write(T t) 的那个类(这里就是 my_scoreboard 自己),因为 analysis_imp 只是把每一次收到的 write() 调用转发给那个类自己的方法。monitor 广播给一个连接上的 analysis_imp 的每一样东西,最终都会变成对这里 write() 的一次调用。
mux2 是纯组合逻辑、无状态的,这正是这个 scoreboard 能写得这么简单的原因:因为第 8 章的 monitor 已经把激励(sel/a/b)和观测到的输出(y)打包进了同一个 transaction,write() 可以直接从刚拿到的字段里算出期望结果,不需要在多次调用之间记住任何东西。一个有状态的 DUT(任何带寄存器、FIFO、流水线的东西)就需要 scoreboard 在多次 write() 调用之间追踪历史状态——这超出了这里的范围,但值得知道这种简单性是这个组合逻辑 DUT 特有的,不是 scoreboard 的普遍特性。
把 monitor 接到 scoreboard 上,并把一切打包起来:uvm_env
这个连接本身只有一行,但它需要 agent 和 scoreboard 都已经存在——也就是说它得写在同时拥有这两者的组件里。这就是一个 uvm_env:
class my_env extends uvm_env;
`uvm_component_utils(my_env)
my_agent agent;
my_scoreboard sb;
function new(string name, uvm_component parent);
super.new(name, parent);
endfunction
function void build_phase(uvm_phase phase);
super.build_phase(phase);
agent = my_agent::type_id::create("agent", this);
sb = my_scoreboard::type_id::create("sb", this);
endfunction
function void connect_phase(uvm_phase phase);
super.connect_phase(phase);
agent.mon.ap.connect(sb.analysis_export);
endfunction
endclassagent.mon.ap.connect(sb.analysis_export) 穿过 my_agent 找到它的 mon 成员(和任何没有 local/protected 修饰的字段一样是 public 的——SV 第 10 章),把那个 monitor 的 analysis 端口接到 scoreboard 的 analysis_imp 上。从这里开始,monitor 观测到的每一个 transaction 都会自动到达 scoreboard 的 write()。
Test,再简化一次
class my_test extends uvm_test;
`uvm_component_utils(my_test)
my_env env;
function new(string name, uvm_component parent);
super.new(name, parent);
endfunction
function void build_phase(uvm_phase phase);
super.build_phase(phase);
env = my_env::type_id::create("env", this);
endfunction
task run_phase(uvm_phase phase);
my_sequence seq;
phase.raise_objection(this);
seq = my_sequence::type_id::create("seq");
seq.start(env.agent.sqr);
phase.drop_objection(this);
endtask
endclass每一章都在把 my_test 变得更简单——从直接拥有一个 driver(第 3 章),到拥有一个 driver 和 sequencer(第 7 章),到拥有一个 agent(第 8 章),再到现在只拥有一个 env。这不是偶然:随着越来越多的结构被下沉到可复用的组件里,test 自己的工作缩小成了"搭建环境,然后决定跑什么激励"——这正是一个 test 该做的事。
一个值得点名的规律:这几个基类大多什么都没加
uvm_test(第 3 章)、uvm_monitor(第 8 章)、uvm_scoreboard、以及 uvm_env(这一章的这两个)都继承自 uvm_component,而且没有加任何新成员或新行为——它们每一个都纯粹是一个命名习惯,给以后读这个层次结构的人标记出这个组件的角色。真正的两个例外是 uvm_driver#(REQ, RSP)(第 7 章,加了 seq_item_port)和 uvm_agent(第 8 章,加了 is_active 和 active/passive 这套约定)。分清哪些基类"只是标签"、哪些真正加了东西,这个习惯值得带下去——它决定了一套类层次结构是自解释的,还是每个类都得单独查一遍才知道它做了什么。
小结
- scoreboard 是和 SV 第 15 章断言并列的第二种、基于软件的检查策略——同样是"发现不一致"的目标,只是在软件层面拿 monitor 观测到的结果去比对,而不是在 RTL/协议层面检查。
uvm_analysis_imp#(T, IMP)是 analysis 连接的接收端;IMP指定了真正接收广播内容的那个类的write(T t)方法。- 因为
mux2是组合逻辑、无状态的,scoreboard 可以直接从每个 transaction 自己的字段算出期望结果——一个有状态的 DUT 就需要在多次调用之间追踪历史状态。 agent.mon.ap.connect(sb.analysis_export)应该写在同时拥有 agent 和 scoreboard 的组件里——一个uvm_env。uvm_test、uvm_monitor、uvm_scoreboard、uvm_env都继承自uvm_component且没有加任何东西——纯粹是命名习惯;uvm_driver和uvm_agent是这个系列里真正加了东西的两个基类。
uvm_analysis_imp #(mux_transaction, my_scoreboard) 里第二个类型参数具体指定的是什么?
为什么这个 scoreboard 的 write() 能直接从 tr 自己的字段算出期望结果,不需要在多次调用之间记住任何东西?
为什么 agent.mon.ap.connect(sb.analysis_export) 应该写在 my_env 的 connect_phase 里,而不是比如说 my_agent 的?
这个系列讲过的基类里,哪两个真正在 uvm_component 之上加了成员/行为,不像 uvm_test/uvm_monitor/uvm_scoreboard/uvm_env 那样?