目录
(一)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_driverdadd_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 beginseq_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);endfunctiontask build_phase(uvm_phase phase);`uvm_info("DRV", "build_phase executed", UVM_LOW)super.build_phase(phase);endtasktask 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. 连接sequenceritem.randomize(); // 3. 随机化finish_item(item); // 4. 发送给driverendif(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)endif(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) // 一站式完成实例化、随机化、发送endif(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. 实例化子 sequenceseq = dadd_rand_sequence::type_id::create("seq"); // 2. 约束子 sequence 的 data_en_rand 为 1seq.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. 约束子 sequenceseq.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
” )的基础。