4. UVM phase机制:phase分类与执行顺序、run_phase与task phase、phase跳转与控制

UVM的phase机制,说白了就是给验证环境装了一个「调度器」。它规定了每个组件在什么时候干活、干完活怎么交接。我刚学UVM那会儿,总觉得phase是个麻烦事——不就是顺序执行吗?后来被一个时序bug折磨了三天,才发现是自己对phase理解不到位。

嗯,咱们今天就把这个「调度器」彻底讲透。

4.1 phase的分类:你该在哪个阶段干活?

UVM把仿真过程分成了两大类phase:

  • function phase:不消耗仿真时间,说白了就是「一瞬间」完成的事。比如建环境、连组件、配参数。
  • task phase:消耗仿真时间,可以等时钟、等信号、等事件。比如驱动激励、监测数据。

我个人习惯把function phase叫做「搭台子阶段」,task phase叫做「唱戏阶段」。台子搭不好,戏肯定唱砸。

具体有哪些phase?我整理了一张表,你收藏好:

phase名称 类型 主要用途
build_phase function 创建组件、配置参数
connect_phase function 连接TLM端口、建立通信
end_of_elaboration_phase function 做最终检查、打印环境拓扑
start_of_simulation_phase function 仿真开始前的初始化
run_phase task 主仿真过程(核心)
extract_phase function 从监测器提取数据
check_phase function 检查数据完整性、协议正确性
report_phase function 生成测试报告
final_phase function 收尾工作、关闭文件

重点提醒:build_phase是自顶向下执行的,而connect_phase是自底向上执行的。这个顺序搞反了,你的TLM端口就接不上。我曾经在这个坑里爬了整整一个下午。

4.2 run_phase与task phase:到底有啥区别?

很多新手会问:run_phase不就是task phase吗?为什么还要单独拎出来讲?

其实UVM在run_phase之外,还提供了12个细分的task phase。比如:

  • pre_reset_phase / reset_phase / post_reset_phase
  • pre_configure_phase / configure_phase / post_configure_phase
  • pre_main_phase / main_phase / post_main_phase
  • pre_shutdown_phase / shutdown_phase / post_shutdown_phase

你想想看,如果你的DUT需要先复位、再配置、再发激励、最后关断,用这些细分phase是不是特别清晰?

我个人习惯是:简单的测试用run_phase就够了,复杂的场景一定要用细分phase。为什么?因为细分phase可以让你精确控制每个阶段的起止时间,而且不同组件之间天然同步。

举个例子:

class my_driver extends uvm_driver#(my_transaction);
  `uvm_component_utils(my_driver)

  // 用main_phase替代run_phase
  task main_phase(uvm_phase phase);
    phase.raise_objection(this);
    // 发送激励
    forever begin
      seq_item_port.get_next_item(req);
      drive_transaction(req);
      seq_item_port.item_done();
    end
    phase.drop_objection(this);
  endtask
endclass

小技巧:如果你用细分phase,记得在对应的phase里raise/drop objection。我曾经见过有人只在run_phase里raise objection,结果细分phase里的代码根本没执行——因为仿真早就结束了。

4.3 phase的执行顺序:谁先谁后?

UVM的phase执行顺序是固定的,你不能乱改。但理解这个顺序背后的逻辑,比死记硬背重要得多。

我画了一张图,帮你理清思路:

UVM Phase执行顺序 build_phase (自顶向下) connect_phase (自底向上) end_of_elaboration_phase start_of_simulation_phase run_phase / 细分task phases (消耗仿真时间) reset → configure → main → shutdown extract → check → report → final function phase task phase function phase

从这张图你能看到:function phase先执行,把环境搭好;然后进入task phase,跑真正的仿真;最后再回到function phase,做收尾工作。

注意:run_phase和细分task phase是并行执行的!也就是说,如果你同时在run_phase和main_phase里写了代码,它们会一起跑。这很容易导致激励冲突。我的建议是:要么只用run_phase,要么只用细分phase,别混着用。

4.4 phase跳转与控制:让仿真更灵活

有时候,你需要在仿真过程中「跳回」某个phase。比如DUT突然需要重新复位,或者配置参数需要更新。UVM提供了phase跳转机制来解决这个问题。

常用的跳转方法有:

  • phase.jump():直接跳转到指定phase。比如从main_phase跳回reset_phase。
  • phase.raise_objection() / drop_objection():控制phase的结束时间。没有raise objection,phase会瞬间结束。

我举个例子,你感受一下:

class my_monitor extends uvm_monitor;
  `uvm_component_utils(my_monitor)

  task main_phase(uvm_phase phase);
    // 监测到复位信号,跳回reset_phase
    forever begin
      @(posedge vif.reset_n);
      if (vif.reset_n == 1'b0) begin
        `uvm_info("MONITOR", "检测到复位,跳回reset_phase", UVM_MEDIUM)
        phase.jump(uvm_reset_phase::get());
      end
    end
  endtask
endclass

避坑指南:我曾经在项目里用phase.jump()做复位重试,结果发现跳转后之前的phase状态没有完全清理干净,导致一些队列里还残留着旧数据。后来我养成了一个习惯:跳转前先调用phase.reset(),把状态清干净。

另外,objection机制是phase控制的核心。记住一句话:没有objection,就没有仿真时间。如果你在run_phase里忘了raise objection,仿真器会直接跳过run_phase,你的激励代码根本不会执行。

我一般会在test层统一管理objection:

class my_test extends uvm_test;
  `uvm_component_utils(my_test)

  task run_phase(uvm_phase phase);
    phase.raise_objection(this);
    // 等待所有sequence完成
    #1000;
    phase.drop_objection(this);
  endtask
endclass

这样写的好处是,你不需要在每个driver、monitor里都去raise/drop objection,管理起来更集中。当然,如果你的场景比较复杂,也可以在组件级别分别控制。

嗯,phase机制的内容差不多就这些。记住三个核心点:分类要清楚、顺序不能乱、objection不能忘。把这三点吃透了,UVM的phase对你来说就不再是黑盒。

最后说一句:phase机制是UVM的骨架,理解它比记住API更重要。我见过太多人把UVM用成了「高级的Verilog」,就是因为没搞懂phase的调度逻辑。花点时间把这张图刻在脑子里,后面学sequence、学reg模型都会轻松很多。

可可精品资料库