uvm中搭建寄存器模型时,详细说明add_hdl_path和add_hdl_path_slice的用法
在UVM中,寄存器模型的后门访问允许直接通过层次路径访问硬件寄存器,而不是通过总线接口。这可以加速仿真,并且可以在不依赖总线功能的情况下设置和检查寄存器的值。为了使用后门访问,我们需要为寄存器模型中的每个寄存器和字段指定它们在硬件设计中的路径。这就是add_hdl_path和add_hdl_path_slice的作用。
1. add_hdl_path
add_hdl_path用于为寄存器模型中的块(uvm_reg_block)或寄存器(uvm_reg)添加硬件层次路径。这个路径是一个字符串,表示从测试平台顶层到该块或寄存器的硬件路径。
用法:
systemverilog
// 在uvm_reg_block中
function void build();
// 为整个块添加路径
add_hdl_path("top_tb.DUT.reg_block");
endfunction
// 在uvm_reg中
function void build();
// 为寄存器添加路径
add_hdl_path("reg1");
endfunction
注意:在寄存器中,路径可以是相对路径(相对于其所在块的路径)或绝对路径。通常,我们使用相对路径,因为块已经定义了顶层路径。
2. add_hdl_path_slice
add_hdl_path_slice用于为寄存器中的字段(uvm_reg_field)指定硬件路径和位范围。每个字段可能对应硬件中的不同信号,或者同一个信号的不同位。
用法:
systemverilog
function void build();
// 创建字段
field1 = uvm_reg_field::type_id::create("field1");
// 配置字段,包括位置和宽度
field1.configure(this, 8, 0, "RW", 0, 8'h00, 1, 1, 1);
// 为字段添加路径切片,指定字段在硬件中的路径、偏移和宽度
// 这里的路径是相对于寄存器路径的,或者可以是绝对路径
field1.add_hdl_path_slice("field1_signal", 0, 8);
endfunction
参数解释:
第一个参数:硬件中信号的名称(可以是层次路径)
第二个参数:字段在寄存器中的起始位
第三个参数:字段的宽度(位数)
3. 详细示例
假设我们有一个DUT,其层次结构如下:
text
top_tb
└── DUT
├── reg_block
│ ├── reg1 (32位寄存器)
│ │ ├── field1 [7:0]
│ │ ├── field2 [15:8]
│ │ └── field3 [31:16]
│ └── reg2
└── other_module
我们希望为reg1建立后门访问。reg1的字段在硬件中可能映射到不同的信号,例如:
field1 对应 DUT.reg_block.reg1_field1
field2 对应 DUT.reg_block.reg1_field2
field3 对应 DUT.reg_block.reg1_field3
寄存器模型构建:
systemverilog
class reg1 extends uvm_reg;
`uvm_object_utils(reg1)
rand uvm_reg_field field1;
rand uvm_reg_field field2;
rand uvm_reg_field field3;
function new(string name = "reg1");
super.new(name, 32, UVM_NO_COVERAGE);
endfunction
virtual function void build();
field1 = uvm_reg_field::type_id::create("field1");
field2 = uvm_reg_field::type_id::create("field2");
field3 = uvm_reg_field::type_id::create("field3");
// 配置字段
field1.configure(this, 8, 0, "RW", 0, 8'h00, 1, 1, 1);
field2.configure(this, 8, 8, "RW", 0, 8'h00, 1, 1, 1);
field3.configure(this, 16, 16, "RW", 0, 16'h0000, 1, 1, 1);
// 添加硬件路径(相对路径,相对于所在块)
// 假设寄存器块已经设置了路径 "top_tb.DUT.reg_block"
// 那么reg1的完整路径将是 "top_tb.DUT.reg_block.reg1"
add_hdl_path("reg1");
// 为每个字段添加路径切片
// 注意:这里的路径是相对于寄存器路径的,所以实际信号路径为:
// top_tb.DUT.reg_block.reg1.field1_signal
// 但如果我们希望映射到具体的信号名,可能需要使用绝对路径或相对路径的拼接。
// 实际上,add_hdl_path_slice的第一个参数可以是任意层次路径,不一定相对于寄存器。
// 以下假设每个字段都有独立的信号,并且位于寄存器的同一层次。
field1.add_hdl_path_slice("reg1_field1", 0, 8);
field2.add_hdl_path_slice("reg1_field2", 8, 8);
field3.add_hdl_path_slice("reg1_field3", 16, 16);
endfunction
endclass
class reg_block extends uvm_reg_block;
`uvm_object_utils(reg_block)
rand reg1 r1;
function new(string name = "reg_block");
super.new(name, UVM_NO_COVERAGE);
endfunction
virtual function void build();
r1 = reg1::type_id::create("r1");
r1.configure(this, null, "");
r1.build();
// 设置默认路径
set_default_hdl_path("top_tb.DUT.reg_block");
// 锁定模型
lock_model();
endfunction
endclass
4. 路径的拼接规则
UVM寄存器模型在解析后门路径时,会将寄存器块的路径、寄存器的路径和字段的路径拼接起来。默认情况下,使用点号(.)连接。但是,可以通过在调用add_hdl_path时指定分隔符来改变。
例如,在寄存器块中:
systemverilog
add_hdl_path("top_tb/DUT/reg_block", "/");
这样,路径将使用斜杠作为分隔符。
5. 使用后门访问
在测试序列中,我们可以使用后门访问来读写寄存器:
systemverilog
task body();
uvm_status_e status;
uvm_reg_data_t data;
// 后门读取
reg_model.r1.read(status, data, UVM_BACKDOOR);
// 或者明确指定路径
reg_model.r1.read(status, data, .path(UVM_BACKDOOR));
// 后门写入
reg_model.r1.write(status, 32'hdeadbeef, UVM_BACKDOOR);
endtask
6. 注意事项
后门访问依赖于仿真器提供的PLI/DPI接口,因此需要仿真器支持。
路径必须正确,否则后门访问会失败。
在寄存器模型中,路径可以是多个,这样可以在不同的抽象级别(如RTL和门级网表)之间切换。
使用后门访问时,寄存器模型会直接更新硬件信号,但不会触发硬件中由这些信号触发的进程(如always块),除非仿真器支持。
7. 调试
如果后门访问不工作,可以尝试以下方法调试:
使用print_hdl_paths()函数打印出寄存器模型中的所有路径。
确保硬件路径在仿真器中存在,并且可以被访问。
检查路径分隔符是否正确。
通过正确使用add_hdl_path和add_hdl_path_slice,我们可以灵活地配置寄存器模型的后门访问路径,从而实现对寄存器的快速读写。
UVM寄存器模型中add_hdl_path和add_hdl_path_slice的详细用法
1. 基本概念
1.1 add_hdl_path
- 作用: 为寄存器块(
uvm_reg_block)或寄存器(uvm_reg)添加硬件层次路径 - 层级: 通常在寄存器块或寄存器级使用
- 目的: 指定寄存器在DUT中的物理位置
1.2 add_hdl_path_slice
- 作用: 为寄存器字段(
uvm_reg_field)添加位域范围的硬件路径 - 层级: 在寄存器字段级使用
- 目的: 指定字段在寄存器中的具体位范围
2. 详细使用方法
2.1 在寄存器块中的使用
class my_reg_block extends uvm_reg_block;
`uvm_object_utils(my_reg_block)
// 寄存器实例
rand reg_ctrl ctrl_reg;
rand reg_status status_reg;
rand reg_data data_reg;
// 子块实例
rand sub_block_a sub_a;
rand sub_block_b sub_b;
function new(string name = "my_reg_block");
super.new(name, UVM_NO_COVERAGE);
endfunction
virtual function void build();
// 1. 设置默认硬件路径(针对整个块)
set_default_hdl_path("top_tb.DUT");
// 2. 创建并配置寄存器
ctrl_reg = reg_ctrl::type_id::create("ctrl_reg");
ctrl_reg.configure(this, null, "");
ctrl_reg.build();
// 为寄存器添加相对路径
ctrl_reg.add_hdl_path("ctrl_regs.ctrl");
status_reg = reg_status::type_id::create("status_reg");
status_reg.configure(this, null, "");
status_reg.build();
status_reg.add_hdl_path("status_regs.status");
data_reg = reg_data::type_id::create("data_reg");
data_reg.configure(this, null, "");
data_reg.build();
data_reg.add_hdl_path("data_regs.data");
// 3. 创建并配置子块
sub_a = sub_block_a::type_id::create("sub_a");
sub_a.configure(this, "sub_a_path");
sub_a.build();
// 为子块添加路径
sub_a.add_hdl_path("sub_system_a");
sub_b = sub_block_b::type_id::create("sub_b");
sub_b.configure(this, "sub_b_path");
sub_b.build();
sub_b.add_hdl_path("sub_system_b");
// 4. 添加备用路径(如门级网表)
add_hdl_path("gate_level.top_module.DUT", "GATES");
// 5. 锁定模型
lock_model();
endfunction
endclass
2.2 在寄存器中的使用
class reg_ctrl extends uvm_reg;
`uvm_object_utils(reg_ctrl)
// 字段声明
rand uvm_reg_field en; // [0] 使能位
rand uvm_reg_field mode; // [3:1] 模式选择
rand uvm_reg_field intr_en; // [4] 中断使能
rand uvm_reg_field clk_div; // [15:8] 时钟分频
rand uvm_reg_field reserved;// [31:16] 保留位
function new(string name = "reg_ctrl");
super.new(name, 32, UVM_NO_COVERAGE);
endfunction
virtual function void build();
// 1. 创建字段
en = uvm_reg_field::type_id::create("en");
mode = uvm_reg_field::type_id::create("mode");
intr_en = uvm_reg_field::type_id::create("intr_en");
clk_div = uvm_reg_field::type_id::create("clk_div");
reserved = uvm_reg_field::type_id::create("reserved");
// 2. 配置字段属性
en.configure(this, 1, 0, "RW", 0, 1'b0, 1, 1, 0);
mode.configure(this, 3, 1, "RW", 0, 3'b010, 1, 1, 0);
intr_en.configure(this, 1, 4, "RW", 0, 1'b1, 1, 1, 0);
clk_div.configure(this, 8, 8, "RW", 0, 8'h0A, 1, 1, 0);
reserved.configure(this, 16, 16, "RO", 0, 16'h0000, 1, 0, 0);
// 3. 添加硬件路径(方法1:寄存器作为整体)
// 这种配置假设整个寄存器在硬件中是一个32位信号
add_hdl_path("ctrl_reg");
// 4. 为字段添加路径切片
// 参数说明:add_hdl_path_slice(路径, 起始位, 宽度, [位序], [路径类型])
en.add_hdl_path_slice("ctrl_reg[0]", 0, 1); // 位0
mode.add_hdl_path_slice("ctrl_reg[3:1]", 1, 3); // 位3:1
intr_en.add_hdl_path_slice("ctrl_reg[4]", 4, 1); // 位4
clk_div.add_hdl_path_slice("ctrl_reg[15:8]", 8, 8); // 位15:8
reserved.add_hdl_path_slice("ctrl_reg[31:16]", 16, 16); // 位31:16
endfunction
endclass
2.3 复杂字段映射示例
class complex_reg extends uvm_reg;
`uvm_object_utils(complex_reg)
// 字段分布在不同的硬件模块中
rand uvm_reg_field fe_config; // 位于FE模块 [15:0]
rand uvm_reg_field be_config; // 位于BE模块 [31:16]
rand uvm_reg_field global_en; // 位于TOP模块 [32]
function new(string name = "complex_reg");
super.new(name, 33, UVM_NO_COVERAGE); // 33位寄存器
endfunction
virtual function void build();
fe_config = uvm_reg_field::type_id::create("fe_config");
be_config = uvm_reg_field::type_id::create("be_config");
global_en = uvm_reg_field::type_id::create("global_en");
fe_config.configure(this, 16, 0, "RW", 0, 16'h0000, 1, 1, 1);
be_config.configure(this, 16, 16, "RW", 0, 16'h0000, 1, 1, 1);
global_en.configure(this, 1, 32, "RW", 0, 1'b0, 1, 1, 1);
// 不需要为整个寄存器添加路径,因为字段分散在不同模块
// FE配置字段位于FE模块
fe_config.add_hdl_path_slice("top_tb.DUT.fe_module.config_reg[15:0]", 0, 16);
// BE配置字段位于BE模块
be_config.add_hdl_path_slice("top_tb.DUT.be_module.control_reg[15:0]", 16, 16);
// 全局使能位位于顶层
global_en.add_hdl_path_slice("top_tb.DUT.global_enable", 32, 1);
endfunction
endclass
3. 路径类型和选项
3.1 路径类型参数
class my_reg extends uvm_reg;
// ... 字段声明 ...
virtual function void build();
// ... 字段配置 ...
// 指定路径类型
// "RTL" - RTL级路径(默认)
// "GATES" - 门级网表路径
// "ALL" - 所有类型
add_hdl_path("reg_module", "RTL");
add_hdl_path("reg_module_gates", "GATES");
// 字段也可以指定路径类型
field1.add_hdl_path_slice("field1_signal", 0, 8, .kind("RTL"));
field1.add_hdl_path_slice("field1_net", 0, 8, .kind("GATES"));
endfunction
endclass
3.2 位序参数
class bit_order_reg extends uvm_reg;
rand uvm_reg_field data;
virtual function void build();
data = uvm_reg_field::type_id::create("data");
data.configure(this, 32, 0, "RW", 0, 32'h0, 1, 1, 1);
// 位序参数:0 - 小端序,1 - 大端序
// 假设硬件是大端序,但寄存器模型默认是小端序
data.add_hdl_path_slice("data_reg", 0, 32, .offset(0), .kind("RTL"), .bits(1));
endfunction
endclass
4. 路径分隔符配置
class custom_separator_block extends uvm_reg_block;
function new(string name = "custom_separator_block");
super.new(name, UVM_NO_COVERAGE);
endfunction
virtual function void build();
// 设置默认路径
set_default_hdl_path("top_tb|DUT|regs", "|");
// 创建寄存器
// ...
// 为寄存器添加路径时,使用相同分隔符
ctrl_reg.add_hdl_path("ctrl_reg", "|");
// 字段路径也会使用相同分隔符
// 最终路径:top_tb|DUT|regs|ctrl_reg|field1
endfunction
endclass
5. 多层路径和备用路径
class multi_path_reg extends uvm_reg;
`uvm_object_utils(multi_path_reg)
rand uvm_reg_field value;
function new(string name = "multi_path_reg");
super.new(name, 32, UVM_NO_COVERAGE);
endfunction
virtual function void build();
value = uvm_reg_field::type_id::create("value");
value.configure(this, 32, 0, "RW", 0, 32'h0, 1, 1, 1);
// 添加多个硬件路径(不同抽象级别)
add_hdl_path("rtl_regs.data_reg"); // RTL路径
add_hdl_path("gate_regs.data_reg", "GATES"); // 门级路径
add_hdl_path("fpga_regs.data_reg", "FPGA"); // FPGA路径(自定义类型)
// 为字段添加不同抽象级别的路径切片
value.add_hdl_path_slice("data_reg", 0, 32, .kind("RTL"));
value.add_hdl_path_slice("data_net", 0, 32, .kind("GATES"));
value.add_hdl_path_slice("data_lut", 0, 32, .kind("FPGA"));
// 设置后门访问时选择路径类型
// reg_model.set_hdl_path_root("top_tb.DUT", "RTL"); // 默认RTL
// reg_model.set_hdl_path_root("top_tb.DUT_gates", "GATES"); // 切换到门级
endfunction
endclass
6. 动态路径配置
class dynamic_path_block extends uvm_reg_block;
// 路径配置参数
string rtl_path = "top_tb.DUT";
string gates_path = "top_tb.DUT_gates";
function void set_paths(string rtl, string gates);
rtl_path = rtl;
gates_path = gates;
endfunction
virtual function void build();
// 使用变量构建路径
string full_rtl_path = {rtl_path, ".reg_block"};
string full_gates_path = {gates_path, ".reg_block"};
set_default_hdl_path(full_rtl_path);
add_hdl_path(full_gates_path, "GATES");
// 寄存器路径也使用变量
ctrl_reg.add_hdl_path({rtl_path, ".reg_block.ctrl_reg"});
ctrl_reg.add_hdl_path({gates_path, ".reg_block.ctrl_reg"}, "GATES");
endfunction
endclass
7. 验证和调试方法
7.1 检查路径有效性
// 在测试环境中检查路径
initial begin
// 等待DUT加载
#100ns;
// 检查路径是否有效
if (reg_model.check_hdl_path()) begin
`uvm_info("PATH", "All HDL paths are valid", UVM_LOW)
end else begin
`uvm_error("PATH", "Some HDL paths are invalid")
end
// 打印所有路径
reg_model.print_hdl_paths();
// 获取特定寄存器的路径
string paths[$];
reg_model.ctrl_reg.get_hdl_path(paths);
foreach(paths[i]) begin
`uvm_info("CTRL_REG_PATH", $sformatf("Path %0d: %s", i, paths[i]), UVM_LOW)
end
end
7.2 路径调试示例
class debug_reg_block extends uvm_reg_block;
virtual function void build();
// 启用详细调试信息
uvm_resource_db#(bit)::set({"REG::", get_full_name()},
"DEBUG_HDL_PATH", 1);
// 构建寄存器模型
super.build();
// 验证后门访问
test_backdoor_access();
endfunction
function void test_backdoor_access();
uvm_status_e status;
uvm_reg_data_t data;
`uvm_info("TEST", "Testing backdoor access...", UVM_LOW)
// 尝试后门读取
ctrl_reg.read(status, data, UVM_BACKDOOR);
if (status == UVM_IS_OK) begin
`uvm_info("TEST", $sformatf("Backdoor read successful: 0x%0h", data), UVM_LOW)
end else begin
`uvm_error("TEST", "Backdoor read failed")
// 打印路径帮助调试
ctrl_reg.print_hdl_paths();
end
endfunction
endclass
8. 最佳实践和注意事项
8.1 最佳实践
- 一致性: 保持RTL设计路径和UVM路径一致
- 模块化: 为每个模块定义清晰的路径前缀
- 验证: 在环境构建完成后验证所有路径
- 文档: 记录路径映射关系
- 灵活性: 使用变量或参数化路径以便重用
8.2 常见问题
- 路径不存在: 确保DUT已加载且路径正确
- 位宽不匹配: 检查字段宽度与硬件信号宽度是否一致
- 时序问题: 后门访问可能受仿真时间影响
- 抽象层级: 确认当前使用的路径类型(RTL/门级)
8.3 性能考虑
- 后门访问比前门访问快,但大量使用可能影响仿真性能
- 建议在初始化、复位测试等场景使用后门访问
- 总线协议验证应使用前门访问
这些详细用法可以帮助您正确配置UVM寄存器模型的后门访问路径,确保寄存器模型能够准确映射到硬件设计。
浙公网安备 33010602011771号