4. UVM核心组件(下):uvm_sequencer、uvm_scoreboard、uvm_env、uvm_test
好,咱们接着聊UVM的核心组件。上一章我们把driver、monitor、agent这几个干活的主力给捋了一遍。今天要讲的这四个——sequencer、scoreboard、env、test,说白了就是「调度中心」、「判卷老师」、「总装车间」和「总指挥」。
我个人习惯把这四个组件叫做「上层建筑」。为什么?因为它们不直接跟DUT打交道,但整个验证平台的运转逻辑全靠它们撑着。你想想看,没有sequencer,你的sequence往哪儿发?没有scoreboard,你比对谁去?没有env和test,你连个像样的测试用例都组织不起来。
核心要点:这四个组件共同构成了UVM验证平台的「骨架」和「大脑」。sequencer负责调度,scoreboard负责判决,env负责组装,test负责启动。
4.1 uvm_sequencer:序列的调度官
sequencer这个东西,说白了就是个「交通警察」。它的任务就是接收sequence发来的transaction,然后按顺序或者按优先级发给driver。
我在项目中遇到过一个问题:多个sequence同时往同一个sequencer上挂,结果driver收到的transaction乱成一锅粥。后来才发现,sequencer默认的仲裁策略是FIFO,但如果你不设置优先级,它就按注册顺序来。嗯,这里要注意——sequencer的仲裁机制是可以自定义的。
class my_sequencer extends uvm_sequencer #(my_transaction);
`uvm_component_utils(my_sequencer)
function new(string name, uvm_component parent);
super.new(name, parent);
endfunction
endclass
你看,定义一个sequencer其实很简单。关键参数是那个#(my_transaction),它告诉sequencer:你要调度的transaction类型是什么。
个人经验:我建议你在定义sequencer时,尽量把transaction类型参数化。这样以后复用起来特别方便。我曾经在一个项目中,就因为transaction类型写死了,结果换个DUT就得重写一个sequencer,血泪教训啊。
4.2 uvm_scoreboard:验证的判卷老师
scoreboard,直译过来就是「计分板」。但在UVM里,它的作用远不止计分——它负责比对DUT的输出是否和预期一致。
你想想看,driver往DUT里灌数据,monitor从DUT里抓数据。谁来判断对错?就是scoreboard。它通常从reference model拿到预期值,再从monitor拿到实际值,然后一比对——对了就pass,错了就报错。
class my_scoreboard extends uvm_scoreboard;
`uvm_component_utils(my_scoreboard)
uvm_analysis_imp #(my_transaction, my_scoreboard) mon_imp;
uvm_analysis_imp #(my_transaction, my_scoreboard) ref_imp;
function new(string name, uvm_component parent);
super.new(name, parent);
mon_imp = new("mon_imp", this);
ref_imp = new("ref_imp", this);
endfunction
function void write_mon(my_transaction tr);
// 从monitor收到的实际值
actual_queue.push_back(tr);
endfunction
function void write_ref(my_transaction tr);
// 从reference model收到的预期值
expected_queue.push_back(tr);
endfunction
endclass
避坑指南:我曾经犯过一个低级错误——scoreboard里只比对了一次就认为测试通过了。结果后来发现,DUT有流水线延迟,实际值和预期值根本不在同一个时间点到达。所以,一定要处理好时序对齐。我现在的习惯是:在scoreboard里加一个FIFO,先把数据缓存起来,等两边都到齐了再比对。
4.3 uvm_env:验证平台的组装车间
env,全称是environment。它的作用就是「搭积木」——把agent、scoreboard、reference model这些组件全部例化并连接起来。
我个人习惯把env分成两层:一层是sub_env(比如每个接口对应一个sub_env),另一层是顶层的top_env。这样做的最大好处是——复用。你想想看,如果每个接口的验证环境都独立封装,换个项目直接拿来用就行了。
class my_env extends uvm_env;
`uvm_component_utils(my_env)
my_agent agt;
my_scoreboard scb;
my_reference_model ref;
function void build_phase(uvm_phase phase);
super.build_phase(phase);
agt = my_agent::type_id::create("agt", this);
scb = my_scoreboard::type_id::create("scb", this);
ref = my_reference_model::type_id::create("ref", this);
endfunction
function void connect_phase(uvm_phase phase);
agt.mon.mon_port.connect(scb.mon_imp);
ref.ref_port.connect(scb.ref_imp);
endfunction
endclass
关键点:env的connect_phase里做的是「连线」工作。把monitor的analysis port连到scoreboard的analysis imp,把reference model的输出也连到scoreboard。这样数据流就通了。
4.4 uvm_test:验证的启动按钮
test,就是测试用例的顶层容器。它负责创建env,启动sequence,控制仿真流程。
我记得刚学UVM时,总觉得test就是个空壳子,没什么好写的。后来才发现——test才是整个验证平台的「总开关」。不同的test可以配置不同的参数、挂载不同的sequence、甚至替换不同的agent。
class my_test extends uvm_test;
`uvm_component_utils(my_test)
my_env env;
my_sequence seq;
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);
phase.raise_objection(this);
seq = my_sequence::type_id::create("seq");
seq.start(env.agt.sqr);
phase.drop_objection(this);
endtask
endclass
实用技巧:我建议你在test里加一个config_db的设置环节。比如在build_phase之前,先通过set_config_int来配置interface的虚接口。这样不同的test可以指向不同的DUT实例,做多实例验证时特别方便。
4.5 核心逻辑关系图
说了这么多,这四个组件到底怎么配合?我画了一张图,你看一眼就明白了:
从这张图你能看到:test创建并启动env,env内部例化agent和scoreboard。agent里包含sequencer,sequencer负责调度sequence。最终,数据从sequencer流向driver,再从monitor流回scoreboard进行比对。
4.6 实战中的一些建议
最后,我把自己这些年踩过的坑和积累的经验总结一下:
- sequencer的仲裁策略:默认是SEQ_ARB_FIFO,但如果你有多个sequence需要抢占资源,建议改成SEQ_ARB_STRICT_FIFO或SEQ_ARB_RANDOM。我一般用SEQ_ARB_STRICT_FIFO,保证高优先级的sequence先执行。
- scoreboard的比对时机:别在write函数里直接比对。先把数据存到队列里,等仿真结束或者某个检查点再统一比对。这样能避免时序问题导致的误报。
- env的层次化设计:我建议每个接口对应一个sub_env,顶层env只做连接。这样换接口时只需要改sub_env,顶层基本不动。
- test的配置管理:用config_db来传递参数,别在test里硬编码。这样不同的test可以复用同一个env,只是配置不同而已。
特别提醒:千万不要在scoreboard里做复杂的时序计算。我曾经见过有人把整个reference model的逻辑塞进scoreboard里,结果scoreboard变得又大又慢,调试起来痛苦得要命。记住——scoreboard只做比对,不做计算。计算的事情交给reference model去做。
好了,这四个组件就讲到这里。你想想看,有了sequencer、scoreboard、env和test,一个完整的UVM验证平台是不是就搭起来了?下一章我们聊聊UVM的phase机制——那个才是真正让UVM「活起来」的东西。
可可精品资料库