目录
[1. 工厂机制核心逻辑:"注册 - 创建 - 覆盖"](#1. 工厂机制核心逻辑:“注册 - 创建 - 覆盖”)
[2. 代码映射:从概念到实现](#2. 代码映射:从概念到实现)
[3. 实验目标:用 dadd_fixen_driver 固定 data_en=1](#3. 实验目标:用 dadd_fixen_driver 固定 data_en=1)
[4. 工厂机制的价值:"灵活验证的基石"](#4. 工厂机制的价值:“灵活验证的基石”)
[5. 常见问题与调试](#5. 常见问题与调试)
[6. 总结](#6. 总结)
[1. 核心规则:"三类 phase 执行顺序"](#1. 核心规则:“三类 phase 执行顺序”)
[2. 实验验证:代码与日志的映射](#2. 实验验证:代码与日志的映射)
[3. 关键细节与易错点](#3. 关键细节与易错点)
[4. 实验价值与应用场景](#4. 实验价值与应用场景)
[5. 总结](#5. 总结)
[(三)sequence 激励产生与交互执行机制](#(三)sequence 激励产生与交互执行机制)
[1. sequence 核心机制概述](#1. sequence 核心机制概述)
[2. sequence_item的发送](#2. sequence_item的发送)
[2.1 核心规则:sequence_item 发送的三种方法](#2.1 核心规则:sequence_item 发送的三种方法)
[2.2 逐类解析:代码逻辑与实验验证](#2.2 逐类解析:代码逻辑与实验验证)
[2.3 关键细节与对比](#2.3 关键细节与对比)
[2.4 实验价值与总结](#2.4 实验价值与总结)
[3. sequence 的发送](#3. sequence 的发送)
[3.1 核心规则:子 sequence 发送的三类方法](#3.1 核心规则:子 sequence 发送的三类方法)
[3.2 逐类解析:结合 dadd 代码与脚本](#3.2 逐类解析:结合 dadd 代码与脚本)
[3.3 关键细节与对比](#3.3 关键细节与对比)
[3.4 实验价值与总结](#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
注册):
cpp
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
):
cpp
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
中覆盖 ):
cpp
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
逻辑
cpp
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
为例)
cpp
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
分支 ):
cpp
`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
分支 ):
cpp
`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
分支 ):
cpp
`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
分支 ):
cpp
`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
分支 ):
cpp
`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
分支 ):
cpp
`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
" )的基础。
