如何实现复杂的分层序列(hierarchical sequence)?
解读
国内SoC验证团队普遍采用UVM,面试时问“分层序列”并不是想听背诵class语法,而是考察三件事:
- 能否把真实业务场景拆成“高-中-低”三层激励,并说清楚每层到底干什么;
- 能否用UVM factory、config_db、sequence library机制把三层拼起来,同时保证可复用、可随机、可回归;
- 能否在性能与可维护性之间做权衡——比如顶层sequence要不要直接启动底层driver sequence?要不要用virtual sequence做跨时钟域同步?这些决定直接影响 nightly regression 的周转时间。
因此,回答时要先给“分层”一个清晰的业务定义,再给出可落地的代码框架,最后交代调试和性能优化经验。面试官最爱追问:“如果今天加一条新feature,你改哪一层?回归要跑多久?”答到这一步,才证明你真正做过复杂系统。
知识点
- UVM sequence 分类:flat sequence、hierarchical sequence、virtual sequence;
uvm_do/uvm_create/uvm_send 宏与uvm_sequence_utils 注册机制;- Factory override 与 config_db 传递,实现“同一场景不同配置”;
- Sequence 的 body() 与 pre_body()/post_body() 回调,用于插入同步点;
- Grab/ungrab 和 lock/unlock 机制,解决多主口仲裁;
- Phase ready/phase ended 事件,跨时钟域sequence握手;
- Sequence 性能:random stability、objection 机制、仿真器对 allocate/clone 的优化;
- 国内主流回归平台(自研flow 或 Jenkins + LSF)对 long sequence 的 timeout 策略。
答案
下面给出一套在国内28 nm AI 芯片ISP子系统验证中量产过的“三阶”分层序列实现思路,可直接迁移到PCIe、DDR、NoC等复杂接口。
-
业务分层
1.1 场景层(scenario sequence)
‑ 描述“一次完整的ISP算法加速”:DMA搬入→中断→CPU下发寄存器→ISP运算→DMA搬出。
‑ 只关心数据长度、帧格式、中断间隔,不关心AXI burst怎么拆。
1.2 事务层(transaction sequence)
‑ 把场景拆成可随机的事务:AXI4-IN write、AXI4-OUT read、寄存器配置、中断等待。
‑ 通过config_db拿到基地址、max burst length、outstanding depth,随机产生合法事务。
1.3 微指令层(micro sequence)
‑ 对应driver能识别的最小粒度:一次AXI burst、一次寄存器写、一次时钟沿等待。
‑ 与DUT时序强相关,可插入wait_cycle、back-to-back、slave ready拉低等corner。 -
代码骨架
// 微指令层
class axi_single_burst_seq extends uvm_sequence #(axi_item);
rand bit [9:0] burst_len;
constraint c_len { burst_len inside {[1:256]}; }
`uvm_object_utils(axi_single_burst_seq)
task body();
req = axi_item::type_id::create("req");
start_item(req);
assert(req.randomize with {len == local::burst_len;});
finish_item(req);
endtask
endclass
// 事务层
class dma_in_transaction_seq extends uvm_sequence;
rand int unsigned num_burst;
constraint c_num { num_burst inside {[1:32]}; }
`uvm_object_utils(dma_in_transaction_seq)
task body();
axi_single_burst_seq burst_seq;
repeat(num_burst) begin
`uvm_do(burst_seq) // 宏会自动randomize
end
endtask
endclass
// 场景层(virtual sequence,不生产item,只协调子序列)
class isp_algorithm_scenario_vseq extends uvm_sequence;
`uvm_declare_p_sequencer(uvm_virtual_sequencer)
dma_in_transaction_seq dma_in_seq;
dma_out_transaction_seq dma_out_seq;
reg_config_seq cfg_seq;
event isp_done;
task body();
// 1. 配置寄存器
`uvm_do(cfg_seq)
// 2. 启动DMA搬入
fork
`uvm_do(dma_in_seq)
join_none
// 3. 等待中断,再启动搬出
@(p_sequencer.isp_interrupt);
`uvm_do(dma_out_seq)
// 4. 检查scoreboard
#1us;
endtask
endclass
-
工厂替换与随机控制
在test层通过factory override把dma_in_transaction_seq换成dma_in_stress_seq,无需改动scenario层即可实现压力模式;
用uvm_config_db#(int)::set(this, "*", "num_burst", 64)在顶层随机控制事务数量,保证回归多样性。 -
跨时钟域握手
场景层使用uvm_event或uvm_barrier,把ISP中断同步到AXI时钟域,避免sequence在fast clock域空转造成仿真性能损失。 -
性能与调试
‑ 微指令层关闭objection,防止每发一个burst都加/减objection,仿真速度提升20%;
‑ 在scenario层统一raise/drop objection,保证仿真器能正确统计run_phase结束点;
‑ 每层sequence在pre_body()打印uvm_info("SEQ_TRACE", $sformatf("Enter %s", get_full_name()), UVM_MEDIUM),配合Verdi UVM-ML调试,可直接在波形里看到sequence树状调用。
拓展思考
- 如果DUT支持多通道并行ISP,场景层需要再抽象一个“通道调度序列”,用fork...join_any实现通道间随机抢占,如何确保scoreboard不丢序?
- 当验证平台迁移到硬件仿真(Palladium),sequence仍跑在软件仿真器,item通过DPI-C送到硬件侧,分层序列的随机种子如何保持deterministic?
- 国内项目普遍要求“一晚跑完所有子系统用例”,若scenario层sequence过长导致超时,你会把哪一层拆成sub-sequence并加checkpoint,从而支持断点续跑?
- 形式验证(Formal)兴起后,部分corner改用SVA+Formal证明,分层序列里哪些微指令序列可以被Formal替代?如何与动态仿真保持互补?