3、UVM环境搭建:搭建第一个UVM验证环境

好,咱们今天来动手搭第一个UVM环境。说实话,很多新手一上来就被UVM的组件树吓住了——test、env、agent、driver、monitor……一堆东西。其实你想想看,核心就三件事:创建、配置、连接。搞明白这三步,剩下的就是重复劳动。

我个人习惯,先搭一个最简的框架跑通,再往里填东西。别一上来就想搞个完美的环境,那反而容易把自己绕进去。

3.1 从uvm_test开始:验证的入口

uvm_test是整个验证环境的总指挥。它不干具体的活,但它知道该叫谁干活、按什么顺序干。

我见过不少新手把test写得巨复杂,又是建sequence又是搞scoreboard。其实test就两个职责:创建env,启动sequence。别的别往里塞。

class my_test extends uvm_test;
    `uvm_component_utils(my_test)

    my_env env;

    function new(string name, uvm_component parent);
        super.new(name, parent);
    endfunction

    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);
        my_sequence seq;
        phase.raise_objection(this);
        seq = my_sequence::type_id::create("seq");
        seq.start(env.agent.sqr);
        phase.drop_objection(this);
    endtask
endclass

注意看,build_phase里只创建了env。agent、driver那些东西,让env去管。这叫职责分层——test只管战略,env管战术。

我的小技巧: 我习惯在test里加一个`#0`的延迟,放在raise_objection之后。有些仿真器在phase切换时会有race condition,加个#0能避免很多莫名其妙的错误。

3.2 uvm_env:验证环境的骨架

uvm_env就是你的验证环境本体。它里面装什么?agent、reference model、scoreboard、coverage collector。但第一次搭建,我建议只放一个agent就够了。跑通了再慢慢加。

env的build_phase里,要创建所有子组件。connect_phase里,要把它们连起来。这里有个坑——connect_phase的执行顺序。我曾经在项目里吃过亏:在connect_phase里访问了还没创建好的组件,结果仿真直接崩了。

class my_env extends uvm_env;
    `uvm_component_utils(my_env)

    my_agent agent;

    function new(string name, uvm_component parent);
        super.new(name, parent);
    endfunction

    function void build_phase(uvm_phase phase);
        super.build_phase(phase);
        agent = my_agent::type_id::create("agent", this);
    endfunction

    function void connect_phase(uvm_phase phase);
        // 这里可以做一些agent内部的连接
        // 但注意:agent内部的组件此时已经创建好了
    endfunction
endclass
注意: build_phase是自顶向下执行的,connect_phase是自底向上执行的。也就是说,agent的connect_phase先跑完,env的connect_phase才跑。这个顺序搞反了,你的TLM连接就会出问题。

3.3 uvm_agent:真正的干活人

agent是UVM里最常用的组件。它封装了driver、monitor、sequencer这三件套。说白了,agent就是一个小分队——driver负责发数据,monitor负责看数据,sequencer负责排任务。

我建议把agent分成两种:active和passive。active agent会驱动DUT,passive agent只观察。这个区分很重要,因为验证环境里经常需要多个agent,有些是主动的,有些是被动的。

class my_agent extends uvm_agent;
    `uvm_component_utils(my_agent)

    my_driver    driver;
    my_monitor   monitor;
    my_sequencer sqr;

    function new(string name, uvm_component parent);
        super.new(name, parent);
    endfunction

    function void build_phase(uvm_phase phase);
        super.build_phase(phase);
        monitor = my_monitor::type_id::create("monitor", this);
        if(get_is_active() == UVM_ACTIVE) begin
            driver = my_driver::type_id::create("driver", this);
            sqr    = my_sequencer::type_id::create("sqr", this);
        end
    endfunction

    function void connect_phase(uvm_phase phase);
        if(get_is_active() == UVM_ACTIVE) begin
            driver.seq_item_port.connect(sqr.seq_item_export);
        end
    endfunction
endclass

看到没?只有active agent才创建driver和sequencer。passive agent只需要monitor。这个设计很巧妙——同样的agent代码,通过一个`get_is_active()`就能切换模式。

3.4 组件之间的连接:TLM通信

组件搭好了,怎么连起来?UVM用的是TLM(Transaction Level Modeling)。说白了就是端口对接——一个组件往外发transaction,另一个组件往里收。

最常见的连接方式:

连接类型 端口 方向 典型用途
seq_item_port driver → sequencer 请求 driver向sequencer要transaction
analysis_port monitor → scoreboard 广播 monitor把采集到的数据发给多个组件
analysis_export scoreboard → monitor 接收 scoreboard接收monitor发来的数据

嗯,这里要注意:analysis_port是一对多的。一个monitor可以同时把数据发给scoreboard、coverage collector、甚至reference model。这个特性在复杂验证环境里特别有用。

3.5 完整的环境搭建流程

好,咱们把上面这些串起来,看看一个完整的搭建流程:

  1. 写test:创建env,启动sequence
  2. 写env:创建agent,后续再加其他组件
  3. 写agent:创建driver、monitor、sequencer
  4. 写driver:实现与DUT的接口驱动
  5. 写monitor:采集DUT的输入输出
  6. 写sequencer:管理sequence的调度
  7. 连接:在connect_phase里完成TLM端口对接

这个顺序是自顶向下的。我建议你也按这个顺序写代码——先写test,再写env,最后写agent和底层的driver。这样每写一层,你都知道上一层需要什么接口。

核心原则: 每个组件只做一件事,做好一件事。test只管调度,env只管组装,agent只管封装,driver只管驱动。职责清晰了,环境自然就稳了。

3.6 避坑指南

最后分享几个我踩过的坑:

  • 别在build_phase里做连接:build_phase只负责创建,connect_phase才负责连接。混在一起,调试起来会疯掉。
  • 注意phase的执行顺序:build_phase是自顶向下,connect_phase是自底向上。这个顺序决定了你什么时候能访问什么组件。
  • 别忘了`uvm_component_utils:每个组件都要注册。我刚开始学UVM时,经常忘了加这个宏,然后仿真报错说找不到组件,排查半天。
  • agent的is_active要设置:默认是UVM_ACTIVE。如果你想要passive agent,记得在test里设置`env.agent.set_is_active(UVM_PASSIVE)`。

说实话,UVM环境搭建这件事,第一次做会觉得繁琐。但当你搭过两三个项目后,就会发现——套路都是一样的。test、env、agent、driver、monitor、sequencer,这六个组件就是UVM的六脉神剑。搞明白它们怎么配合,剩下的就是填业务逻辑了。

下一章,咱们会深入driver和monitor的实现,看看它们怎么跟DUT打交道。到时候我会分享一个我在项目中遇到的时序问题——那个问题让我debug了整整三天,最后发现是driver的复位处理没做好。