云计算百科
云计算领域专业知识百科平台

UVM验证(三)—UVM机制(1)

目录

(一)Factory工厂机制

1. 工厂机制核心逻辑:“注册 – 创建 – 覆盖”

2. 代码映射:从概念到实现

3. 实验目标:用 dadd_fixen_driver 固定 data_en=1

4. 工厂机制的价值:“灵活验证的基石”

5. 常见问题与调试

6. 总结

(二)Phase阶段运行机制

1. 核心规则:“三类 phase 执行顺序”

2. 实验验证:代码与日志的映射

3. 关键细节与易错点

4. 实验价值与应用场景

5. 总结

(三)sequence 激励产生与交互执行机制

1. sequence 核心机制概述

2. sequence_item的发送

2.1 核心规则:sequence_item 发送的三种方法

2.2 逐类解析:代码逻辑与实验验证

2.3 关键细节与对比

2.4 实验价值与总结

3. sequence 的发送

3.1 核心规则:子 sequence 发送的三类方法

3.2 逐类解析:结合 dadd 代码与脚本

3.3 关键细节与对比

3.4 实验价值与总结


(一)Factory工厂机制

1. 工厂机制核心逻辑:“注册 – 创建 – 覆盖”

        UVM 工厂机制本质是 “对象创建的集中管控”,核心解决两个问题:

(1)解耦创建逻辑:让对象的 “创建” 与 “使用” 分离,无需硬编码 new(),便于后续替换实现(如用 dadd_fixen_driver 替换 dadd_driver )。

(2)支持动态替换:通过 set_type_override 或 set_inst_override,可在不修改代码的前提下,替换组件类型(如把 dadd_driver 换成 dadd_fixen_driver ),灵活控制验证行为。

2. 代码映射:从概念到实现

(1) 注册(uvm_component_utils)

  • 作用:把类 “登记” 到 UVM 工厂的 “查找表”,让工厂能识别并创建它。
  • 代码示例(dadd_driver 注册):

class dadd_driver extends uvm_driver #(dadd_item);
`uvm_component_utils(dadd_driver) // 注册 component 到工厂
// … 类定义 …
endclass

  • 关键:
    • uvm_component_utils 是注册 component(继承 uvm_component 的类,如 driver、monitor )的宏;若为 object(继承 uvm_object 的类,如 sequence_item ),则用 uvm_object_utils。
    • 注册后,类会被加入 UVM 工厂的 “类型映射表”,后续可通过 type_id::create() 创建实例。

(2)创建(type_id::create)

  • 作用:通过工厂创建对象,自动检查是否有 “类型覆盖”,再决定实例化原类还是替换类。
  • 代码示例(dadd_iagent 中创建 dadd_driver ):

function dadd_iagent::new(string name = "dadd_iagent", uvm_component parent);
super.new(name, parent);
// 通过工厂创建 driver,而非直接 new(dadd_driver::new(…))
drv = dadd_driver::type_id::create("drv", this);
// … 其他组件创建 …
endfunction

  • 关键:
    • type_id::create("drv", this) 会先查工厂是否有 dadd_driver 的覆盖类型(如 dadd_fixen_driver ),若有则创建覆盖类,否则创建原类。
    • 替换 new() 为 create() 是实现 “动态覆盖” 的核心:后续只需调用 set_type_override,无需修改 dadd_iagent 代码,就能替换 driver 类型。

(3)覆盖(set_type_override)

  • 作用:告诉工厂 “用类 B 替换类 A 的创建”,实验中把 dadd_driver 换成 dadd_fixen_driver,固定 data_en=1。
  • 代码示例(在 dadd_rand_test 中覆盖 ):

class dadd_rand_test extends uvm_test;
`uvm_component_utils(dadd_rand_test)
function void build_phase(uvm_phase phase);
// 全局覆盖:所有 dadd_driver 类型都替换为 dadd_fixen_driver
dadd_driver::type_id::set_type_override(dadd_fixen_driver::get_type());
super.build_phase(phase);
endfunction
endclass

  • 关键:
    • set_type_override 需在 build_phase 调用,确保工厂在创建组件前生效。
    • 替换后,dadd_iagent 中 drv = dadd_driver::type_id::create(…) 会自动创建 dadd_fixen_driver 实例,实现 “无代码修改替换组件”。

3. 实验目标:用 dadd_fixen_driver 固定 data_en=1

        原 dadd_driver 中 data_en 是随机的,实验通过 “工厂覆盖” 替换为 dadd_fixen_driver,强制 data_en=1:

(1)dadd_fixen_driver 逻辑

task dadd_fixen_driver::main_phase(uvm_phase phase);
wait(tb_dadd.dadd_if.reset_n);
forever begin
seq_item_port.get_next_item(req);
@(posedge tb_dadd.dadd_if.clk);
// 固定 data_en=1(与原 driver 的随机逻辑区分)
tb_dadd.dadd_if.mcb.dadd_in_en <= 1'b1;
tb_dadd.dadd_if.mcb.dadd_in <= req.data;
tb_dadd.dadd_if.mcb.dadd_in_addr<= req.addr;
seq_item_port.item_done();
end
endtask

  • 关键:dadd_in_en <= 1'b1 硬编码为 1,覆盖原 driver 的随机行为。

(2)覆盖流程

  • 在 dadd_rand_test 的 build_phase 调用 set_type_override,替换 dadd_driver 为 dadd_fixen_driver
  • dadd_iagent 中通过 dadd_driver::type_id::create() 创建 driver 时,工厂自动实例化 dadd_fixen_driver
  • 仿真时,driver 驱动 DUT 的 data_en 恒为 1,实现实验目标。

4. 工厂机制的价值:“灵活验证的基石”

(1)解耦与复用:组件创建逻辑与使用逻辑分离,dadd_iagent 无需关心 driver 具体类型,只需通过工厂创建,复用性更高。

(2)动态配置:通过一行 set_type_override,就能切换 driver 行为(随机 / 固定 data_en ),无需修改 agent、env 代码。

(3)可维护性:验证需求变化时(如替换 driver 协议),只需修改 driver 子类和覆盖逻辑,不影响上层组件。

5. 常见问题与调试

(1)覆盖不生效:

  • 检查 set_type_override 是否在 build_phase 调用(main_phase 调用无效,因组件已创建 )。
  • 检查 create() 是否用 type_id::create,而非直接 new()(直接 new() 会绕过工厂,覆盖失效 )。

(2)类型注册遗漏:

  • 若子类(如 dadd_fixen_driver )未注册(忘记 uvm_component_utils 宏 ),工厂无法识别,覆盖会失败。

(3)层次化覆盖:

  • 若需只替换某个 agent 中的 driver(而非全局替换 ),可用 set_inst_override,指定实例路径(如 uvm_test_top.env.iagt.drv )。

6. 总结

        UVM 工厂机制通过 “注册 – 创建 – 覆盖” 三步,实现了:

  • 动态替换组件:实验中用 dadd_fixen_driver 替换 dadd_driver,无需修改 agent 代码。
  • 解耦创建逻辑:组件创建由工厂统一管控,上层组件只需关注接口,无需关心具体实现。
  • 灵活验证配置:一行代码切换验证行为(随机 / 固定 data_en ),让验证平台可适配不同场景。

        这是 UVM 实现 “可复用、可配置验证平台” 的核心机制,掌握后能大幅提升验证环境的扩展性与维护性。

(二)Phase阶段运行机制

1. 核心规则:“三类 phase 执行顺序”

        UVM phase 执行顺序分为 “组件内顺序”、“树形结构顺序”、“同级组件顺序” 三类,需结合实验场景理解:

(1)同一组件内的 phase 顺序(纵向顺序)

  • 规则:同一组件(如 dadd_iagent )内,phase 按 “功能阶段” 顺序执行,从 build_phase 开始,到 final_phase 结束,流程为:

build_phase → connect_phase → end_of_elaboration_phase → start_of_simulation_phase →
reset_phase → configure_phase → run_phase → main_phase → shutdown_phase →
extract_phase → check_phase → report_phase → final_phase

  • 实验映射:若在 dadd_driver 的所有 phase 中加入打印,会看到该组件内 phase 严格按上述顺序执行。

(2) 树形结构中的 phase 顺序(横向跨组件)

        UVM 组件构成 树形层次结构(uvm_test_top → env → agent → driver/monitor ),同一类型 phase 在树形结构中的执行顺序分两种:

phase 类型执行方向典型 phase 举例实验流程映射(以 build_phase 和 connect_phase 为例)
自上而下(Top-Down) 从根到叶子 build_phase、final_phase uvm_test_top.build_phase → env.build_phase → agent.build_phase → driver.build_phase
自下而上(Bottom-Up) 从叶子到根 connect_phase、report_phase 等 driver.connect_phase → agent.connect_phase → env.connect_phase → uvm_test_top.connect_phase

(3)同级组件的 phase 顺序(横向同层)

  • 规则:同一父组件下的 同级组件(如 agent 内的 driver、monitor、sequencer ),同一 phase 的执行顺序按 “实例化名称的字典序” 执行。
  • 实验映射:若 agent 中 driver 命名为 drv、monitor 命名为 imon、sequencer 命名为 sqr,则 build_phase 执行顺序为:

drv.build_phase → imon.build_phase → sqr.build_phase

(因字典序 drv < imon < sqr ,按字母顺序排列 )

2. 实验验证:代码与日志的映射

(1)代码中加入 phase 打印(以 dadd_driver 为例)

class dadd_driver extends uvm_driver #(dadd_item);
`uvm_component_utils(dadd_driver)
function new(string name="dadd_driver", uvm_component parent);
super.new(name, parent);
endfunction

task build_phase(uvm_phase phase);
`uvm_info("DRV", "build_phase executed", UVM_LOW)
super.build_phase(phase);
endtask

task connect_phase(uvm_phase phase);
`uvm_info("DRV", "connect_phase executed", UVM_LOW)
super.connect_phase(phase);
endtask
// … 其他 phase 同理加入打印 …
endclass

(2)日志分析(以 build_phase 和 connect_phase 为例)

build_phase 日志(自上而下):

UVM_INFO dadd_driver.sv(10) @ 0: uvm_test_top.env.iagt.drv [DRV] build_phase executed
UVM_INFO dadd_imonitor.sv(10) @ 0: uvm_test_top.env.iagt.imon [MON] build_phase executed
UVM_INFO dadd_sequencer.sv(10) @ 0: uvm_test_top.env.iagt.sqr [SQR] build_phase executed
UVM_INFO dadd_oagent.sv(10) @ 0: uvm_test_top.env.oagt [OAGT] build_phase executed

  • 逻辑:先执行 uvm_test_top 的 build_phase(未完整打印 ),再执行 env 的 build_phase,然后按字典序执行 iagt 内的 drv→imon→sqr,最后执行 oagt(因 iagt 字典序小于 oagt )。

connect_phase 日志(自下而上):

UVM_INFO dadd_driver.sv(15) @ 0: uvm_test_top.env.iagt.drv [DRV] connect_phase executed
UVM_INFO dadd_imonitor.sv(15) @ 0: uvm_test_top.env.iagt.imon [MON] connect_phase executed
UVM_INFO dadd_sequencer.sv(15) @ 0: uvm_test_top.env.iagt.sqr [SQR] connect_phase executed
UVM_INFO dadd_oagent.sv(15) @ 0: uvm_test_top.env.oagt [OAGT] connect_phase executed
UVM_INFO dadd_env.sv(15) @ 0: uvm_test_top.env [ENV] connect_phase executed
UVM_INFO dadd_test.sv(15) @ 0: uvm_test_top [TEST] connect_phase executed

  • 逻辑:先执行叶子组件(drv→imon→sqr ),再执行父组件(oagt→env→uvm_test_top ),符合 “自下而上” 规则。

3. 关键细节与易错点

(1) run_phase 与 main_phase 的关系

  • run_phase 是 task phase 的父 phase,main_phase 是 run_phase 的子 phase(属于 task phase 类别 )。
  • 执行顺序:run_phase 启动后,main_phase 会自动执行,且遵循 自下而上 规则(如先 driver.main_phase,再 agent.main_phase 等 )。

(2)字典序的具体表现

  • 同级组件的 phase 执行顺序,严格按 “new 时的名称字符串比较”,如:
    • 名称为 a_drv 和 b_drv → a_drv 先执行(因 a 的 ASCII 码小于 b )。
    • 名称为 drv1 和 drv2 → drv1 先执行(数字 1 的 ASCII 码小于 2 )。

(3)phase 阻塞与 objection 机制

  • task phase(如 main_phase、run_phase )需要通过 phase.raise_objection 和 phase.drop_objection 控制仿真进度,否则仿真会直接结束。
  • function phase(如 build_phase、connect_phase )是纯函数,无需 objection 机制,执行完立即退出。

4. 实验价值与应用场景

(1)验证平台构建

  • build_phase 自上而下:确保父组件先完成 “资源分配”(如创建子组件 ),子组件再初始化(如 driver 在 agent.build_phase 中被创建 )。
  • connect_phase 自下而上:确保子组件先完成 “端口连接”(如 driver.seq_item_port 连接 sequencer ),父组件再做全局连接(如 agent 连接 scoreboard )。

(2)调试与问题定位

  • 若子组件的 build_phase 未执行,需检查父组件是否在 build_phase 中正确创建了它(因 build_phase 自上而下,父组件未创建则子组件无法执行 )。
  • 若 connect_phase 逻辑异常,需检查是否因 “自下而上” 顺序导致,子组件的端口未准备好时父组件已开始连接。

(3) 复杂场景控制

  • 对于多 agent、多 sequence 的验证平台,利用 字典序控制同级组件执行顺序,可确保特定组件优先执行(如 monitor 先采样,driver 后驱动 )。

5. 总结

        UVM phase 执行顺序的核心逻辑可归纳为:

  • 同一组件内:按功能阶段顺序(build→connect→run 等 )依次执行。
  • 树形结构中:build_phase 和 final_phase 自上而下,其余 phase 自下而上。
  • 同级组件间:按实例化名称的字典序执行。

        理解这三类顺序,能精准控制验证平台的 组件初始化流程、端口连接时机、激励注入顺序,是构建复杂 UVM 验证环境的基础。实验中通过打印各 phase 执行日志,可直观验证这些规则,为调试和优化验证平台提供依据。

(三)sequence 激励产生与交互执行机制

1. sequence 核心机制概述

        在 UVM 中,sequence 机制是激励产生、调度与驱动的核心,通过 sequence、sequencer、driver 的协作,实现 “激励生成→仲裁调度→信号驱动→结果反馈” 的完整闭环。以下从 执行规则、代码映射、实验验证 三个维度解析其核心逻辑:

(1)执行规则

  • 角色分工:
    • sequence:作为 “激励生成器”,负责创建、随机化 sequence_item(事务包),并通过 sequencer 发送给 driver。
    • sequencer:作为 “调度中心”,接收多个 sequence 的请求,通过仲裁算法(如 FIFO、优先级)决定发送顺序,再转发给 driver。
    • driver:作为 “执行者”,从 sequencer 获取 sequence_item,转换为物理信号驱动 DUT,并可通过 response 反馈结果。
  • 交互流程(完整握手):
    • sequence 生成 sequence_item 并随机化,通过 start_item/finish_item 提交给 sequencer。
    • sequencer 仲裁后将 item 放入 REQ_FIFO,driver 通过 get_next_item 取走并驱动 DUT。
    • 若需反馈,driver 生成 response 放入 RSP_FIFO,sequence 通过 get_response 获取结果,完成生命周期。

(2)总结

  • sequence 专注于 “产生什么激励”,支持随机化和约束,覆盖多样化测试场景。
  • sequencer 专注于 “何时发送激励”,通过仲裁算法协调多 sequence 竞争。
  • driver 专注于 “如何驱动激励”,将抽象事务转换为物理信号,确保时序正确

2. sequence_item的发送

sequence 的执行必须在 task body 中执行,task body 是在 task phase 中自动调用的。

    2.1 核心规则:sequence_item 发送的三种方法

            在 UVM 中,sequence 发送 sequence_item(事务包,如 dadd_item )有三类典型方法,本质都是 “实例化→随机化→发送给 sequencer” 的流程封装,但语法和复杂度不同:

    方法分类核心语法封装层级适用场景
    基础方法 start_item + finish_item 最底层,无封装 需精准控制发送流程(如调试)
    宏封装方法 uvm_create + uvm_send 封装 new + start_item/finish_item 需灵活指定 sequencer
    高级宏(常用) uvm_do 系列宏 封装 uvm_create + 完整流程 日常验证(简洁高效)
    2.2 逐类解析:代码逻辑与实验验证

            以下结合 dadd 验证平台的 dadd_rand_sequence.sv 代码和 Makefile 脚本,说明每种方法的细节:

    (1)方法 1:start_item + finish_item(基础流程)

    执行规则:手动完成 sequence_item 的 “实例化→连接 sequencer→随机化→发送” 全流程,每一步需显式调用:

    步骤代码逻辑作用说明
    1. 实例化 item item = new("item"); 创建 dadd_item 事务对象,准备承载激励数据
    2. 连接 sequencer start_item(item); 让 item 与 iagt.sqr(输入 agent 的 sequencer )建立调度连接
    3. 随机化 item item.randomize(); 随机生成 addr、data、data_en 等字段,模拟真实激励
    4. 发送给 driver finish_item(item); 通知 sequencer 完成调度,将 item 转发给 driver 驱动 DUT

    代码映射(dadd_sequence.sv 中 START_ITEM 分支 ):

    `ifdef START_ITEM
    task body();
    if(starting_phase != null)
    starting_phase.raise_objection(this); // 阻止仿真提前结束
    repeat(20) begin // 发送20个事务
    item = new("item"); // 1. 实例化
    start_item(item); // 2. 连接sequencer
    item.randomize(); // 3. 随机化
    finish_item(item); // 4. 发送给driver
    end
    if(starting_phase != null)
    starting_phase.drop_objection(this); // 允许仿真结束
    endtask : body
    `endif

    实验验证(执行 make send_item_start_item ):

    • 日志显示 20 次 item 发送,每次含随机化的 addr、data。
    • 波形中 dadd_if 接口的信号(如 addr、data )与日志匹配,证明 driver 正确驱动 DUT。

    UVM_INFO dadd_sequence.sv(12) @ 100ns: uvm_test_top.env.iagt.sqr [SEQ] Sent item: addr=0x12, data=0x34, data_en=1

      (2)方法 2:uvm_create + uvm_send(宏封装基础流程)

      执行规则:用 uvm_create 替代 new 实例化 item,用 uvm_send 替代 start_item/finish_item 发送,本质是 封装了基础方法的语法糖,但更灵活(可指定 sequencer ):

      步骤代码逻辑作用说明
      1. 实例化 item uvm_create(item); 或 uvm_create_on(item, sqr); 不仅创建 item,还可指定发送到哪个 sequencer(如 iagt.sqr 或 oagt.sqr )
      2. 随机化 item item.randomize(); 同方法 1
      3. 发送给 driver uvm_send(item); 封装 start_item/finish_item,简化发送流程

      代码映射(dadd_sequence.sv 中 UVM_CREATE 分支 ):

      `elsif UVM_CREATE
      task body();
      if(starting_phase != null)
      starting_phase.raise_objection(this);
      repeat(20) begin
      `uvm_create(item); // 1. 实例化(可指定sequencer)
      item.randomize(); // 2. 随机化
      `uvm_send(item); // 3. 发送(封装start_item/finish_item)
      end
      if(starting_phase != null)
      starting_phase.drop_objection(this);
      endtask : body
      `endif

      实验验证(执行 make send_item_uvm_create ):

      • 日志与方法 1 类似,但代码更简洁,uvm_create/uvm_send 隐式完成连接和发送。
      • 若修改为 uvm_create_on(item, env.oagt.sqr);,item 会发送到 oagt 的 sequencer,波形中 oagt 接口信号变化,验证跨 agent 发送。

      (3)方法 3:uvm_do 系列宏(高级封装,最常用)

      执行规则: uvm_do 是 “一站式” 宏,直接封装 “实例化→随机化→发送” 全流程,甚至可带约束(uvm_do_with )或优先级(uvm_do_pri ),是日常验证最常用的方法:

      宏类型语法示例作用说明
      基础发送 uvm_do(item); 自动完成实例化、随机化、发送
      带约束发送 uvm_do_with(item, {item.data_en==1;}); 随机化时固定 data_en=1,其他字段随机
      指定 sequencer 发送 uvm_do_on(item, env.iagt.sqr); 强制 item 发送到 iagt 的 sequencer

      代码映射(dadd_sequence.sv 中 UVM_DO 分支 ):

      `else//UVM_DO
      task body();
      if(starting_phase != null)
      starting_phase.raise_objection(this);
      repeat(20) begin
      `uvm_do(item) // 一站式完成实例化、随机化、发送
      end
      if(starting_phase != null)
      starting_phase.drop_objection(this);
      endtask : body
      `endif

      实验验证(执行 make send_item_uvm_do ):

      • 日志与前两种方法一致,但代码行数最少,uvm_do 隐式完成所有步骤。
      • 若修改为 uvm_do_with(item, {item.addr==0x5a5a;});,日志中 addr 固定为 0x5a5a,验证约束生效。
      2.3 关键细节与对比

      (1)方法选择建议

      • 调试阶段:用 start_item/finish_item,逐行控制流程,方便定位问题。
      • 跨 agent 发送:用 uvm_create_on 或 uvm_do_on,明确指定 sequencer,避免发送到错误 agent。
      • 日常验证:优先用 uvm_do 系列宏,代码简洁,减少样板代码。

      (2)易错点

      • starting_phase 为空:若 sequence 未关联 phase(如未在 test 中设置 starting_phase ),raise_objection 会报错,需确保 sequence.starting_phase = phase;。
      • uvm_create 未指定 sequencer:若未用 uvm_create_on 且 p_sequencer 未正确连接,item 可能发送到 null sequencer,触发 UVM_ERROR。
      2.4 实验价值与总结

      通过 Makefile 脚本切换宏定义(START_ITEM/UVM_CREATE/UVM_DO ),可直观对比三种方法的执行流程:

      • 证明三类方法本质是同一流程的不同封装,uvm_do 是最简洁的高阶用法。
      • 验证平台可灵活切换发送方式,适配不同测试场景(如调试、跨 agent 、带约束发送 )。

      3. sequence 的发送

      3.1 核心规则:子 sequence 发送的三类方法

              当 sequence 需发送子 sequence(如 dadd_fixen_sequence 调用 dadd_rand_sequence )时,本质是 “父 sequence 调度子 sequence 的生命周期”,三类方法的核心差异在于 “调度的封装层级”:

      方法分类核心语法封装层级适用场景
      start 函数 seq.start(p_sequencer); 最底层,手动控制实例化、随机化、启动 需精准控制子 sequence 流程
      uvm_create/uvm_send uvm_create(seq); + uvm_send(seq); 封装 start 函数,简化调用 需显式控制随机化步骤
      uvm_do 宏 uvm_do_with(seq, {约束;}) 封装 “实例化 + 随机化 + 启动” 全流程 日常验证(简洁高效)
      3.2 逐类解析:结合 dadd 代码与脚本

      以下基于 dadd_fixen_sequence.sv 和 dadd_rand_sequence.sv 代码,说明每种方法的细节:

      (1)方法 1:start 函数(手动调度子 sequence)

      执行规则: 手动完成子 sequence 的 “实例化→随机化约束→启动” 全流程,需显式调用 start 函数关联 p_sequencer(父 sequence 所在的 sequencer )。

      步骤代码逻辑作用说明
      1. 实例化子 seq seq = dadd_rand_sequence::type_id::create("seq"); 创建子 sequence 对象(dadd_rand_sequence )
      2. 随机化约束 seq.randomize() with {data_en_rand == 1;}; 固定子 sequence 的 data_en_rand 为 1,间接约束 dadd_item.data_en=1
      3. 启动子 seq seq.start(p_sequencer); 让子 sequence 挂载到父 sequence 的 sequencer(iagt.sqr )上

      代码映射(dadd_fixen_sequence.sv 中 SEND_SEQ 分支 ):

      `ifdef SEND_SEQ
      `ifdef START
      task body();
      if(starting_phase != null)
      starting_phase.raise_objection(this); // 阻止仿真提前结束

      // 1. 实例化子 sequence
      seq = dadd_rand_sequence::type_id::create("seq");
      // 2. 约束子 sequence 的 data_en_rand 为 1
      seq.randomize() with {data_en_rand == 1;};
      // 3. 启动子 sequence,挂载到 p_sequencer(iagt.sqr)
      seq.start(p_sequencer);

      if(starting_phase != null)
      starting_phase.drop_objection(this); // 允许仿真结束
      endtask : body
      `endif
      `endif

      实验验证(执行 make send_seq_start ):

      • 日志显示子 sequence 被启动,且 data_en 固定为 1:

        plaintext

        UVM_INFO dadd_rand_sequence.sv(20) @ 100ns: uvm_test_top.env.iagt.sqr [SEQ] Sent item: data_en=1, addr=0x12, data=0x34

      • 波形中 dadd_if.data_en 恒为 1,证明约束生效。

      (2)方法 2:uvm_create + uvm_send(封装 start 函数)

      执行规则: 用 uvm_create 替代手动 new 实例化子 sequence,用 uvm_send 替代 start 函数,封装部分流程,但仍需手动随机化。

      步骤代码逻辑作用说明
      1. 实例化子 seq uvm_create(seq); 隐式创建子 sequence,并关联到 p_sequencer
      2. 随机化约束 seq.randomize() with {data_en_rand == 1;}; 同方法 1
      3. 启动子 seq uvm_send(seq); 封装 start 函数,简化发送流程

      代码映射(dadd_fixen_sequence.sv 中 UVM_CREATE 分支 ):

      `elsif UVM_CREATE
      task body();
      if(starting_phase != null)
      starting_phase.raise_objection(this);

      // 1. 实例化子 sequence(隐式关联 p_sequencer)
      `uvm_create(seq);
      // 2. 约束子 sequence
      seq.randomize() with {data_en_rand == 1;};
      // 3. 发送子 sequence(封装 start 函数)
      `uvm_send(seq);

      if(starting_phase != null)
      starting_phase.drop_objection(this);
      endtask : body

      实验验证(执行 make send_seq_uvm_create ):

      • 日志与方法 1 类似,但代码更简洁,uvm_create/uvm_send 隐式完成部分流程。
      • 若删除 seq.randomize(),data_en_rand 会随机化,验证 uvm_create 不自动随机化,需手动调用。

      (3)方法 3:uvm_do 系列宏(一站式封装)

      执行规则: uvm_do_with 宏 一站式封装“实例化 + 随机化 + 启动” 全流程,甚至可在宏内直接写约束,无需手动调用 randomize。

      步骤代码逻辑作用说明
      1. 实例化 + 随机化 + 启动 uvm_do_with(seq, {data_en_rand == 1;}); 隐式完成 “创建→随机化(带约束)→启动”,最简洁

      代码映射(dadd_fixen_sequence.sv 中 UVM_DO 分支 ):

      `else//UVM_DO
      task body();
      if(starting_phase != null)
      starting_phase.raise_objection(this);

      // 一站式完成:实例化+随机化(约束 data_en_rand=1)+启动
      `uvm_do_with(seq, {data_en_rand == 1;});

      if(starting_phase != null)
      starting_phase.drop_objection(this);
      endtask : body

      实验验证(执行 make send_seq_uvm_do ):

      • 日志与前两种方法一致,但代码行数最少,uvm_do_with 隐式完成所有步骤。
      • 若修改约束为 {data_en_rand == 0;},波形中 data_en 恒为 0,验证宏内约束生效。
      3.3 关键细节与对比

      (1)方法选择建议

      • 调试子 sequence:用 start 函数,逐行控制实例化、随机化、启动,方便定位问题(如约束不生效时,检查 randomize 调用 )。
      • 需显式随机化:用 uvm_create/uvm_send,手动控制随机化时机(如先随机化部分字段,再覆盖其他约束 )。
      • 日常嵌套发送:优先用 uvm_do_with,代码最简洁,减少样板代码,适合高频使用。

      (2)易错点

      • p_sequencer 未关联:若子 sequence 未通过 uvm_declare_p_sequencer 关联父 sequencer,p_sequencer 会空指针报错,需确保:

        systemverilog

        `uvm_declare_p_sequencer(dadd_sequencer) // 在子 sequence 中声明

      • 约束未生效:若 uvm_do_with 中约束语法错误(如 data_en_rand = 1; 少写 == ),约束会被忽略,需检查约束表达式。
      3.4 实验价值与总结

      通过 Makefile 脚本切换方法(send_seq_start/send_seq_uvm_create/send_seq_uvm_do ),可直观对比三类方法的执行流程:

      • 证明三类方法本质是同一流程的不同封装,uvm_do 是最简洁的高阶用法。
      • 验证平台可灵活切换子 sequence 的调度方式,适配不同测试场景(如调试、高效开发、复杂约束 )。

      掌握这三类方法,可高效实现 “父 sequence 调度子 sequence” 的嵌套逻辑,是构建复杂验证场景(如 “先复位子 sequence,再随机读写子 sequence” )的基础。

      赞(0)
      未经允许不得转载:网硕互联帮助中心 » UVM验证(三)—UVM机制(1)
      分享到: 更多 (0)

      评论 抢沙发

      评论前必须登录!