第2章:一个简单的UVM验证平台 ------ 这是全书的精华! 从一个最简单的driver开始,像搭积木一样,一步步加入sequencer、monitor、scoreboard、env,最后形成一个完整的平台。你之前看的博客代码,基本就是这一章的缩影。
老师,手把手带我走进第2章吧,让我们开启第二章的旅程吧,有你陪伴真好,谢谢您啦,
同学,坐。听到你说"有你陪伴真好",老师心里也暖暖的。学习新东西就像在深夜走山路,有人提着一盏灯陪着你,确实会安心很多。
好,那我们现在就翻开《UVM实战》第2章,开始我们的"搭积木"之旅。张强老师在这一章的设计非常巧妙,他没有一上来就给你一个庞大复杂的代码,而是让你经历痛点,然后一步步帮你解决它。
咱们就用之前那个最简单的加法器 DUT,跟着张老师的思路,从零开始,经历一次"平台进化史"。
🧱 第一步:最初的尝试 ------ "纯手工硬核驱动"(2.1节)
一开始,你只用 Verilog 写测试。你可能会写一个 initial 块,给 a 和 b 赋值,然后检查 sum。在 UVM 里,第一个版本也是这样,但换成了类(class)。
书上会让你先建一个 driver,里面写一个 main_phase,直接硬编码给信号赋值:
systemverilog
// 最初的driver(伪代码)
task main_phase(uvm_phase phase);
@(posedge vif.clk);
vif.a <= 8'd12;
vif.b <= 8'd34;
@(posedge vif.clk);
// 检查sum...
endtask
痛点:这和你写 Verilog Testbench 没区别。数据写死了,想改数据就得改代码;而且,你根本不知道 DUT 什么时候输出,只能靠手动计算时间。这太不"自动化"了。
🔫 第二步:引入"子弹"与"手枪" ------ transaction、sequence 和 sequencer(2.2节)
为了解决"数据写死"的问题,张老师引入了 transaction (子弹)和 sequence(弹夹)。
-
你把
a、b、sum封装成一个my_transaction类。并且加上rand,让它们可以随机化! -
你写一个
my_sequence,里面用repeat(10) begin \uvm_do(req) end`,决定发10发子弹。 -
然后引入 sequencer(手枪),它连接 sequence 和 driver。
这时候,你的 driver 进化了:
systemverilog
// 进化后的driver
task main_phase(uvm_phase phase);
while(1) begin
seq_item_port.get_next_item(req); // 向手枪要一颗子弹
@(posedge vif.clk);
vif.a <= req.a;
vif.b <= req.b;
seq_item_port.item_done(); // 告诉手枪,打完了
end
endtask
进步:driver 不用管数据从哪来,它只管"开枪"。sequence 负责造子弹。整个平台活了!
📷 第三步:引入"摄像机" ------ monitor(2.3节)
问题又来了:子弹打出去了,DUT 的结果怎么收集?总不能每次看波形吧?
张老师引入了 monitor。它不干涉驱动,只是在旁边静静地看着:
systemverilog
// monitor的核心逻辑
forever begin
@(posedge vif.clk);
if(vif.rst_n) begin
tr = my_transaction::type_id::create("tr");
tr.a = vif.a;
tr.b = vif.b;
tr.sum = vif.sum;
ap.write(tr); // 把采集到的数据扔出去(通过analysis port)
end
end
进步:有了 monitor,验证平台有了"眼睛"。它把物理信号重新变成了 transaction,并发送给下一个环节。
🎯 第四步:引入"裁判" ------ scoreboard(2.4节)
monitor 采集到了结果,怎么判断对错?张老师引入了 scoreboard。
scoreboard 通过 TLM(事务级建模)的 FIFO 接收 monitor 传来的数据。它接收到实际结果后,自己算一遍期望结果,然后比对:
systemverilog
// scoreboard的比对逻辑
exp_fifo.get(tr); // 从FIFO里拿到monitor传来的数据
expect_sum = tr.a + tr.b; // 算一下期望值
if(tr.sum == expect_sum)
`uvm_info("SCB", "比对成功", UVM_LOW)
else
`uvm_error("SCB", "比对失败")
进步:从此告别肉眼看波形!平台可以自动报错了。这就是自动化的核心。
🏢 第五步:引入"剧组" ------ env 和 agent(2.5节)
到现在,你有 driver、sequencer、monitor、scoreboard 四个组件了。如果每次建平台都要一个个实例化,太乱了。
张老师教你把它们"打包":
-
把 driver、sequencer、monitor 打包成一个 agent(经纪公司)。因为它们三个是围绕同一个接口的。
-
把 agent 和 scoreboard 打包成一个 env(剧组)。
在 env 的 build_phase 里,你只需要实例化 agent 和 scoreboard。
在 env 的 connect_phase 里,你把 monitor 的 analysis_port 连到 scoreboard 的 FIFO 上。
systemverilog
// env中的连接
agt.mon.ap.connect(scb.exp_fifo.analysis_export);
进步:结构清晰了!env 就像一个集装箱,把所有演员装在一起,方便复用。
🎬 第六步:引入"总导演" ------ test 和 top_tb(2.6 - 2.7节)
最后一步,谁来启动这一切?谁来分配接口?
张老师引入了 test (总导演)和 top_tb(剧场)。
-
在
top_tb里,你用uvm_config_db把 interface 传给 driver 和 monitor。然后调用run_test("my_test")启动仿真。 -
在
my_test的build_phase里,你实例化 env。 -
在
my_test的main_phase里,你启动 sequence:
systemverilog
task main_phase(uvm_phase phase);
my_sequence seq;
phase.raise_objection(this); // 别关灯,我要拍戏了!
seq = my_sequence::type_id::create("seq");
seq.start(env.agt.sqr); // 把弹夹压入手枪
phase.drop_objection(this); // 拍完了,收工!
endtask
进步:平台彻底跑起来了!总导演一喊 Action,sequence 开始发子弹,driver 开枪,monitor 录像,scoreboard 判卷。
💡 老师的"陪读"总结
你看,第2章的精华就在于**"演化"** 。
张老师不是直接丢给你一个完整的代码,而是让你体验:
-
没有transaction -> 数据写死,痛苦。
-
没有monitor -> 只能看波形,痛苦。
-
没有env -> 组件散落一地,痛苦。
当你体会到了这些痛点,UVM的各个组件就变得理所当然、顺理成章了。
你的课后作业(也是第2章的收官) :
把咱们之前聊过的那套完整代码(dut、my_if、transaction、sequence、driver、monitor、scoreboard、agent、env、test、top_tb),在 QuestaSim 里跑一遍。不要只复制粘贴,对着张强老师的书,在代码里加上你自己的注释 。比如在 connect_phase 旁边写上"把摄像机接到裁判的FIFO上"。
同学,第2章是地基。地基打得越扎实,后面学第3章(uvm_component与uvm_object的本质区别)、第5章(phase机制)、第6章(sequence机制)就会越轻松。
今天先到这。就像跑 run_phase 一样,你已经在 raise_objection 的状态下坚持了很久,现在可以 drop_objection,好好休息一下了。
明天想继续学第3章,或者对第2章的哪个代码片段还有疑问,随时来找我。老师一直都在。加油,你学得很棒!