4. Sequence调试技巧
Sequence调试,说白了就是搞清楚你的激励到底是怎么跑起来的。很多同学问我:「为什么我的sequence没执行?」「为什么随机出来的数据不对?」——这些问题,我几乎每天都在帮人看。
今天我就把Sequence调试的看家本领,掰开揉碎了讲给你听。
4.1 Sequence启动与执行流程跟踪
先说说Sequence是怎么启动的。很多人以为调用了start()就完事了,其实背后有一套完整的流程。
核心流程:
- 调用
seq.start(sequencer)→ 注册到sequencer - sequencer仲裁 → 拿到授权
- 执行
body()→ 生成item - item发送到driver → 完成握手
我在项目中遇到过最坑的一次:sequence调用了start(),但driver死活收不到item。查了两天,最后发现是sequencer的仲裁模式设成了SEQ_ARB_FIFO,而我的sequence优先级太低,一直被别的sequence插队。
嗯,这里要注意:调试启动流程,第一件事就是看sequencer的仲裁日志。
// 打开UVM的调试信息
uvm_top.set_report_verbosity_level(UVM_HIGH);
// 或者针对sequencer单独开
sequencer.set_report_verbosity_level(UVM_DEBUG);
你会看到类似这样的输出:
UVM_INFO @ 1000ns: sequencer [SEQ_ARB] Starting arbitration...
UVM_INFO @ 1000ns: sequencer [SEQ_ARB] Granting to seq_1 (priority=100)
UVM_INFO @ 1000ns: sequencer [SEQ_REQ] seq_1 is now active
看到这些日志,你就能清楚地知道sequence什么时候被选中、什么时候开始执行。
我的调试习惯:在body()函数的第一行加个`uvm_info,打印当前时间戳和sequence的名字。这样一眼就能看出sequence是否真的启动了。
4.2 `uvm_do` 宏的展开与调试
很多人喜欢用`uvm_do宏,觉得方便。但方便的背后,是调试的噩梦。为什么?因为宏展开后,你根本看不到中间发生了什么。
我曾经帮一个同事调bug,他的sequence里用了`uvm_do_with,但item的约束死活不生效。我让他把宏展开看看,结果发现——他写错了约束变量名。
`uvm_do宏展开后大概是这样:
// 原始代码
`uvm_do(req)
// 展开后
begin
req = my_item::type_id::create("req");
start_item(req);
assert(req.randomize());
finish_item(req);
end
看到没?start_item、randomize、finish_item三步,被宏隐藏了。一旦出问题,你根本不知道是哪一步挂了。
避坑指南:调试阶段,我建议你手动展开`uvm_do,写成三步式。这样可以在每一步之间加打印,精确定位问题。
// 调试版:手动展开
req = my_item::type_id::create("req");
`uvm_info("SEQ_DBG", $sformatf("Before start_item, time=%0t", $time), UVM_MEDIUM)
start_item(req);
`uvm_info("SEQ_DBG", $sformatf("Before randomize, time=%0t", $time), UVM_MEDIUM)
assert(req.randomize());
`uvm_info("SEQ_DBG", $sformatf("Before finish_item, time=%0t", $time), UVM_MEDIUM)
finish_item(req);
这样你就能看到每一步的执行时间,以及item在随机化前后的状态。
4.3 Sequence item 的随机化与约束调试
随机化调试,是Sequence调试里最头疼的部分。你想想看,一个item有几十个字段,每个字段都有约束,随机化失败了你都不知道是哪个约束冲突了。
我个人的经验是:先隔离,再调试。
第一步:检查约束是否可解。
// 在sequence里单独测试随机化
my_item item = my_item::type_id::create("item");
if (!item.randomize()) begin
`uvm_error("RAND_FAIL", "Randomization failed!")
// 打印所有约束
item.print();
end
第二步:如果随机化失败,用get_constraint_solver()查看冲突。
// 查看约束求解器的状态
if (!item.randomize()) begin
string solver_info;
item.get_constraint_solver().get_constraints(solver_info);
`uvm_info("SOLVER", solver_info, UVM_LOW)
end
第三步:对于复杂的约束,我建议你写一个独立的测试用例,只测试这个item的随机化。
实战案例:有一次我调试一个DMA控制器的sequence,item里有地址对齐约束。随机化总是失败,我查了三天,最后发现是地址范围约束和字节对齐约束冲突了——地址范围是[0x1000, 0x1003],但对齐要求4字节,这根本无解。
遇到这种情况,要么放宽地址范围,要么降低对齐要求。没有别的办法。
4.4 知识体系总览
下面这张图,是我总结的Sequence调试知识体系。你可以把它当作调试时的检查清单。
4.5 实用调试技巧汇总
| 调试场景 | 推荐方法 | 注意事项 |
|---|---|---|
| Sequence没启动 | 检查sequencer仲裁日志 | 确认优先级和仲裁模式 |
| Item没发送 | 手动展开`uvm_do | 在start_item前加打印 |
| 随机化失败 | 隔离测试+约束求解器 | 检查约束是否可解 |
| 约束不生效 | 打印随机化前后的值 | 确认约束变量名正确 |
最后说一句:调试Sequence,说白了就是「分而治之」。把大问题拆成小问题,每一步都验证。别嫌麻烦,我见过太多人因为偷懒,一个bug调了一周。
好了,这一章的内容就到这里。记住我今天讲的三个核心点:启动流程要跟踪、uvm_do要展开、随机化要隔离。下次遇到Sequence的问题,按这个思路走,基本都能搞定。