UVM--寄存器模型的集成

寄存器模型的集成

接下来需要考虑选择与DUT寄存器接口一致的总线UVC, 该UVC会提供硬件级别的访问方式。 要完成一次硬件级别的总线传输, 往往需要考虑给出地址、 数据队列、 访问方式等, 而寄存器模型可以使得硬件级别的抽象级上升到寄存器级别。由此带来最直观的好处在于, 以往由具体地址来指定寄存器的方式, 将由寄存器名称来替代, 同时寄存器模型封装的一些函数使得可以对域做直接操作,这一升级使得转变后的测试序列更易读。 而伴随着项目变化, 无论寄存器基地址如何变化, 寄存器级别实现的配置序列都要比硬件级别的序列具备更好的维护性。

从激励的流向来看, 寄存器序列(而不是总线序列)将带有目标寄存器的相关信息存放到uvm_reg_item实例中, 送往adapter。

adapter在接收到uvm_reg_item之后, 从中抽取出总线UVC所需的信息 , 同时生成总线UVC需要的bus_seq_item类型。 在完成了数据内容抽取和二次写入之后,bus_seq_item由adapter送往总线UVC。

总线UVC从bus_seq _item获取地址、 数据、 操作模式等信息之后发起总线的读写访问 。如果总线上有反馈信号标示访问是否成功,则该标示应当由总线sequencer按照response item的路径返回至adapter,adapter也应对该反馈信号做出处理。这一反馈路径在读访问时也会将总线读回的数据返同至adapter, 并最终作为返回值交回到与寄存器操作有关的方法。

寄存器模型生成时并不随之生成adapter。因为adapter的转换上层是UVM的寄存器标准包, 转换下层却可能是不同的总线 UVC。 考虑到总线协议的不同带来的总线sequence item 的不同, 以及不同公司开发的同一种总线UVC也存在不小差异, 因此adapter开发的责任就落到了总线UVC身上。而实际情况是,大多数商业总线UVC目前并没有自带寄存器adapter, 而多数自研的总线UVC也并术顾及到adapter的开发 。

现阶段要集成寄存器模型,恐怕仍然需要掌握实现 adapter 的一些基本技巧, 同时理解 adapter 充当不同抽象层转化媒介的原理。 我们本节的材料依然是 MCDF 寄存器模块, 以及较为简单的寄存器访问总线 UVC 和对应的 adapter。希望通过本节的内容, 读者可以惯得依靠 adapter 实现的前门 (front-door) 访问和后门 (back-door) 访问两种方式。

总线UVC的实现

MCDF 访问寄存器的总线接口时序较为简单。 控制寄存器接口首先需要在每一个时钟解析cmd, cmd 为写指令时, 把数据 cmd_data_in 写入 cmd_addr 对应的寄存器中; cmd 为读指令时, 从 cmd_addr 对应的寄存器中读取数据, 在下一个周期, cmd_addr 对应的寄存器数据被输送至 cmd_data_out 接口。

class mcdf_bus_trans extends uvm_sequence_item;
rand bit[l:0] cmd; 
rand bit[7:0] addr; 
rand bit [31: O] wdata; 
bit[31:0] rdata;
`uvm_object_utils_begin (mcdf_bus_trans)
...
`uvm_object_utils_end 
...
endclass 

class mcdf_bus_sequencer extends uvm_sequencer;
virtual mcdf_if vif; 
`uvm_component_utils(mcdf_bus_sequencer)
...
function void build_phase(uvm_phase phase);
if(!uvm_config_db#(virtual mcdf_if)::get(this, "", "vif", vif)) begin
`uvm_error(''GETVIF", "no virtual interface is assigned")
end 
endfunction 
endclass 

class mcdf_bus_monitor extends uvm_monitor;
virtual mcdf_if vif; 
`uvm_analysis_port #(mcdf_bus_trans) ap; 
`uvm_component_utils(mcdf_bus_monitor) 
...
function void build_phase(uvm_phase phase);
if(!uvm_config_db#(virtual mcdf_if)::get(this, "", "vif", vif)) begin
`uvm_error("GETVIF", "no virtual interface is assigned") 
end 
ap = new("ap", this);
endfunction 
task run_phase(uvm_phase phase);
forever begin
mon_trans(); 
end
endtask
task mon_trans();
mcdf_bus_trans t;
@ (posedge vif. elk);
if(vif.cmd ==`WRITE) begin
t= new();
t.cmd ='WRITE;
t.addr = vif.addr; 
t.wdata = vif.wdata; 
ap.write(t);
end 
else if(vif.cmd ==`READ) begin
t= new();
t.cmd =`READ; 
t.addr = vif.addr;
fork
@(posedge vif.clk); 
#l0ps;
t. rdata = vif. rdata; 
ap.write(t);
join_none
end
endtask
endclass

class mcdf_bus_driver extends uvm_driver;
virtual mcdf_if vif; 
`uvm_component_utils(mcdf_bus_driver) 
...
function void build_phase(uvm_phase phase);
if(!uvm_config_db#(virtual mcdf_if)::get(this, "", "vif", vif)) begin
`uvm _error ("GETVIF", "no virtual interface is assigned")
end
endfunction
task run_phase(uvm_phase phase);
REQ tmp;
mcdf_bus_trans reg, rsp; 
reset_listener(); 
forever begin
seq_item_port.get_next_item(tmp);
void'($cast (reg, tmp)) ;
`uvm_info("DRV", $sformatf("got a item \n %s", reg.sprint()), UVM_LOW) 
void'($cast (rsp, reg. clone()));
rsp.set_sequence_id(req.get_sequence_id());
rsp.set_transaction_id (req.get_transaction_id()); 
drive_bus(rsp); 
seq_item_port.item_done(rsp); 
`uvm_info ("ORV", $sformatf ("sent a i七em \n %s", rsp. sprint()), UVM LOW) 
end 
endtask 
task reset_listener(); 
fork
forever begin 
@ (negedge vif. rstn) 
drive_idle (); 
end
join_none 
endtask 
task drive_bus(mcdf_bus_trans t); 
case(t.cmd)
`WRITE: drive_write(t); 
`READ: drive_read(t);
`IDLE: drive_idle(l);
default: `uvm_error("DRIVE", "invalid mcdf command type received!")
endcass
endtask 
task drive_write(mcdf_bus_trans t);
@(poseuge vif.clk); 
vif.cmd <= t.cmd; 
vif.addr <= t.addr; 
vif.wdata <= t.wdata;
endtask 
task drive_read(mcdf_bus_trans t);
@ (posedge vif. elk) ; 
vif.cmd <= t.cmd; 
vif.addr <= t.addr;
@ (posedge vif.elk) ;
#l0ps; 
t.rdata = vif.rdata;
endtask 
task drive_idle(bit is_sync = 0);
if(is_sync) @(posedge vif.clk); 
vif.cmd <= 'h0; 
vif.addr <= 'h0; 
vif.wdata <= 'h0;
endtask 
endclass 
+
class mcdf_bus_agent extends uvm_agent;
mcdf_bus_driver driver; 
mcdf_bus_sequencer sequencer; 
mcdf_bus_monitor monitor; 
`uvm_component_utils(mcdf_bus_agent)
...
function void build_phase(uvm_phase phase);
driver= mcdf_bus_driver::type_id::create("driver", this); 
sequencer= mcdf_bus_sequencer::type_id::create("sequencer", this);
monitor= mcdf_bus_monitor::type_id::create("monitor", this);
endfunction 
function void connect_phase(uvm_phase phase);
driver.seq_item_port.connect(sequencer.seq_item_export); 
endfunction 
endclass 

上面给出的代码襄括了 mcdf_bus_ agent 的所有组件: sequence item、 sequencer、 driver 、 monitor 和 agent 。我们对这些代码的部分实现给出解释:

•    rncdf _bus_ trans 包括了可随机化的数据成员 cmd 、 addr 、 wdata 和不可随机化的 rdata 。 rdata 之所以没有声明为 rand 类型, 是因为它应从总线读出或观察, 不应随机化。
•    mcdf_bus_monitor 会观测总线, 其后通过 analysis port 写出到目标 analysis 组件, 在本节中它稍后将连接到 uvm_reg_predictor。
•    mcdf_bus_driver 主要实现了总线驱动和复位功能, 通过模块化的方法 reset_listener()、 drive_bus()、 drive_write()、 drive_read()和 drive_idle()可以解析 mcdf_bus_trans 中的三种命令模式 IDLE 、 WRITE 和READ, 并且在 READ 模式下,将读回的数据通过 item_done(rsp) 写回到 sequencer 和 sequence 侧。 建议读者在通过 clone()命令创建 RSP 对象后, 通过 set_ sequence _id()和 set_transaction _id()两个函数保证REQ和 RSP 的中保留的ID 信息一致。

MCDF寄存器模块代码

下面给出实现后的 MCDF 寄存器 RTL 设计代码:

`define IDLE 2'b00 
`define WRITE 2'b0l 
`define READ 2'bl0 
`define SLV0_RW_ADDR 8'b00 
`define SLV1_RW_ADDR 8'b04 
`define SLV2_RW_ADDR 8'b08
`define SLV0_R_ADDR 8'b10 
`define SLV1_R_ADDR 8'b14 
`define SLV2_R_ADDR 8'b18
`define SLV0_RW_REG 0
`define SLV1_RW_REG 1
`define SLV2_RW_REG 2
`define SLV0_R_REG 3
`define SLV1_R_REG 4
`define SLV2_R_REG 5

module ctrl_regs(
clk_i,rstn_i,
cmd_i,cmd_addr_i,cmd_data_i,cmd_data_o, 
slv0_len_o,slvl_len_o,slv2_len_o,
slv0_prio_o,slvl_prio_o,slv2_prio_o,
slv0_margin_i,slvl_margin_i,slv2_margin_i, 
slv0_en_o,slvl_en_o,slv2_en_o); 
input clk_i,rstn_i; 
input [1: 0] cmd_i; 
input [7:0] cmd_addr_i; 
input [31:0] cmd_data_i; 
input [7:0] slv1_margin_i; 
input [7:0] slvl_margin_i; 
input [7:0] slv2_margin_i; 
output [31:0] cmd_data_o;
output [2:0] slv0_len_o;
output [2:0] slv1_len_o;
output [2:0] slv2_len_o;
output [1:0] slv0_prio_o;
output [1:0] slv1_prio_o;
output [1:0] slv2_prio_o;
output slv0_en_o;
output slv1_en_o;
output slv2_en_o;
reg [31:0] regs (5:0]; 
reg [31:0] cmd_data_reg; 
always@ (posedge clk_i or negedge rstn_i) begin
if(!rstn_i) begin
regs [`SLV0_RW_REG] <= 32'h00000007; 
regs [`SLVl_RW_REG) <= 32'h00000007;
regs [`SLV2_RW_REG) <= 32'h00000007;
regs [`SLV0_R_REG] <= 32'h00000010; 
regs [`SLVl_R_REG) <= 32'h00000010;
regs [`SLV2_R_REG) <= 32'h00000010;
end 
else begin
if (cmd_i=='WRITE) begin
case(cmd_addr_i)
`SLV0_RW_ADDR: regs[ SLVO_RW_REG][5:0]<= cmd_data_i; 
`SLVl_RW_ADDR: regs[ SLVl_RW_REG][5:0]<= cmd_data_i;
`SLV2_RW_ADDR: regs[ SLVl_RW_REG][5:0]<= cmd_data_i;
endcase 
end 
else if(cmd_i ==`READ) begin
case(cmd_addr_i) 
`SLV0_RW_ADDR: cmd_data_reg <= regs [SLV0_RW_REG]; 
`SLVl_RW_ADDR: cmd_data_reg <= regs [SLVl_RW_REG];
`SLV2_RW_ADDR: cmd_data_reg <= regs [SLV2_RW_REG];
`SLV0_R_ADDR: cmd_data_reg <= regs [SLV0_RW_REG]; 
`SLVl_R_ADDR: cmd_data_reg <= regs [SLVl_RW_REG];
`SLV2_R_ADDR: cmd_data_reg <= regs [SLV2_RW_REG];
endcase 
end
regs [`SLVO_R_REG] (7:0] <= slvO_margin_i ;
regs [`SLVl_R_REG] (7:0] <= slvl_margin_i ;
regs [`SLV2_R_REG] (7:0] <= slv2_margin i ;
end
end
assign cmd_data_o = cmd_data_reg;
assign slv0_len_o = regs ['SLVO_RW_REG] [5:3];
assign slv1_len_o = regs ['SLVO_RW_REG] [5:3];
assign slv2_len_o = regs ['SLVO_RW_REG] [5:3];
assign slv0_prio_o = regs['SLVO_RW_REG] [2:1];
assign slv1_prio_o = regs['SLVO_RW_REG] [2:1];
assign slv2_prio_o = regs['SLVO_RW_REG] [2:1];
assign slv0_en_o = regs['SLVO_RW_REG] [0];
assign slv1_en_o = regs['SLVO_RW_REG] [0];
assign slv2_en_o = regs['SLVO_RW_REG] [0];
endmodule: ctrl_regs

上面的设计中采用宏替代一些寄存器的序列号和地址, 在稍后的寄存器模型映射硬件寄存器路径上也使用了这些宏。 这么做使得设计和验证采用同一套宏,在后期寄存器地址、 位置等修改时更易维护环境, 保持设计和验证两侧的一致,另外, 采用宏(或 parameter) 的可读性也更好。

Adapter的实现

在具备了 MCDF 总线UVC 之后, 需要实现 adapter。每个总线对应的 adapter 完成的桥接功能就是在uvm_reg_bus_op (寄存器操作的 transaction) 和总线 transaction (这里指mcdf _bus_ trans 之间)的转换。 用户在开发某一个总线adapter 类型时, 需要实现下面几点:

•    uvm_reg_ bus_op 与总线transaction 中各自的数据映射。
•    实现reg2bus()和bus2reg()两个函数,这两个函数即实现了两种 transaction的数据映射。
•    如果总线支持 byte 访问, 可以使能 supports_byte_ enable; 如果总线 UVC 要返回 response 数据, 则应当使能provides_responses 。在本例中, mcdf_bus_driver 在读数时会将读回的数据填入到 RSP 并返回至 sequencer, 因此需要在 adapter 中使能 provides _responses。由此使得 bus2reg() 函数调用时得到的数据是总线返回时的 transaction , 但读者需要注意如果总线 UVC 不支持返回 RSP (没有调用 put_response(RSP) 或 item_ done(RSP)), 那么不应该置此位, 否则 adapter将会使得验证环境挂起。 默认情况下, 上述的两个成员的复位值都是0 。

uvm_reg_bus_op 类的成员包含 6 个域。

从下面给出的 MCDF 桥接类 reg2mcdf_adapter 的实现来看,该类在构建函数中使能了provide _responses, 这是因为 mcdf_bus_driver 在发起总线访问之后会将 RSP 并返回至sequencer 。 reg2bus()完成的桥接场景是, 如果用户在寄存器级别做了操作, 那么寄存器级别操作的信息 uvm_reg_bus_op 会被记录, 同时调用 uvm_reg_ adapter: :reg2bus()函数 。 在完成了将uvm_ reg_bus_op 的信息映射到 mcdf_ bus_ trans 之后,函数将mcdf_bus_trans 实例返回。 而在返回 mcdf_bus_ trans 之后,该实例将通过 mcdf_bus_sequencer 传入到 mcdf_bus_driver。这里的 transaction 传输是后台隐式调用的, 不需要读者自已发起。 寄存器无论读写都应知道总线操作后的状态返回,读操作时也要知道总线返回的读数据,因此 uvm_reg_ adapter:: bus2reg()即是从 mcdf_bus_driver() 将数据写回至 mcdf_bus_sequencer , 而一直保持监听的reg2mcdf _ adapter一旦从 sequencer 获取了 RSP(mcdf_ bus _trans)之后,就将自动调用 bus2reg()函数。该函数的功能与 reg2bus()相反,完成了从mcdf_bus_ trans 到 uvm_reg_bus_op 的内容映射。在完成映射之后,更新的 uvm_reg_bus_op 数据最终返回至寄存器操作层面。寄存器的操作, 无论是读操作还是写操作, 都要经历调用 reg2bus(), 继而发起总线事务, 在完成:J,:务发回反馈后调用 bus2reg(), 将总线的数据返回至寄存器操作层面。

class reg2mcdf_adapter extends uvm_reg_adapter;
`uvm_object_utils(reg2mcdf_adapter) 
function new(string name= "mcdf_bus_trans"); 
super.new(name);
provides_responses = 1; 
endfunction
function uvm_sequence_item reg2bus(const ref uvm_reg_bus_op rw);
mcdf_bus_trans t = mcdf_bus_trans::type_id::create("t"); 
t.cmd = (rw.kind == UVM_WRITE) ? `WRITE: `READ;
t.addr = rw.addr;
t.wdata= rw.data;//映射
return t;
endfunction 
function void bus2reg (uvm_sequence_item bus_item, ref uvm_reg_bus_op rw);
mcdf_bus_trans t;
if (!$cast(t, bus_item)) begin
`uvm_fatal("NOT MCDF BUS TYPE","Provided bus item is not of thecorrect 七ype")
return; 
end 
rw.kind = (t.cmd ==`WRITE) ? UVM_WRITE: UVM_READ; 
rw.addr = t.addr; 
rw.data = (t.cmd ==`WRITE) ? t.wdata:t.rdata;
rw.status = UVM_IS_OK; 
endfunction 
endclass 

Adapter 的集成

在具备了寄存器模型 mcdf_rgm、总线 UVC mcdf_bus_agent 和桥接 reg2mcdf_ adapter 之后, 就需要考虑如何将 adapter 集成到验证环境中去了, 下面给出了 adapter 集成例码。 在这段例码中, 注意下面几点:

•    对于 mcdf_rgm 的集成, 我们倾向于顶层传递的方式, 即最终从 test 层传入寄存器模型句柄。 这种方式有利于验证环境 mcdf_bus_env 的闭合性, 在后期不同 test 对 rgm 做不同的配置时可以在顶层例化, 而后通过 uvm_ config_ db 来传递。
•    寄存器模型在创建之后要显式调用 build()函数。 需要注意 uvm_reg_block 是 uvm_object 类型, 因此其预定义的 build()函数并不自动执行, 还需要单独调用。
•    在还未集成 predictor 之前, 我们采用了 auto prediction 的方式, 因此调用了函数 set_auto_ predict()。
•    在顶层环境的 connect 阶段中, 需要将寄存器模型的 map 组件与 bus sequencer 和 adapter 连接。 这么做的必要性在于将 map (寄存器信息)、 sequencer (总线侧激励驱动)和 adapter (寄存器级别和硬件总线级别的桥接)关联在一起。 也只有通过这一步, adapter 的桥接功能才可以工作。

reg2mcdf_adapter reg2mcdf;
`uvm_component_utils(mcdf_bus_env) 
function void build_phase(uvm_phase phase);
agent = mcdf_bus_agent::type_id::create ("agent", this); 
if(!uvm_config_db#(mcdf_rgm)::get(this, "", "rgm", rgm)) begin
`uvm_info("GETRGM", "no top-down RGM handle is assigned", UVM_LOW)
rgm = mcdf_rgm::type_id::create ("rgm", this);
`uvm_info("GETRGM", "created rgm instance locally", UVM_LOW)
end
rgm.build();
rgm.map.set_auto_predict ();
reg2mcdf = reg2mcdf_adapter::type_id::create ("reg2mcdf");
endfunction
function void connect_phase(uvm_phase phase);
rgm.map.set_sequencer(agent.sequencer, reg2mcdf); 
endfunction
endclass
class testl extends uvm_test; 
mcdf_rgm rgm;
mcdf_bus_env env;
`uvm_component_utils(testl)
...
function void build_phase(uvm_phase phase);
rgm = mcdf_rgm::type_id::create("rgm", this);
uvm_config_db# (mcdf_rgm) :: set(this, "env*", "rgm", rgm) ;
env = mcdf_bus_env:: type_id:: create ("env", this);
endfunction
task run_phase(uvm_phase phase);
...
endtask
endclass 

前门访问 

利用寄存器模型可以更方便地对寄存器做操作。两种访问寄存器的方式是前门访问 (front-door)和后门访问(back-door)。前门访问,顾名思义指的是在寄存器模型上做的读写操作,通过总线UVC实现总线上的物理时序访问,是真实的物理操作;而后门访问,指的是利用UVMDPI (uvm_hdl_ read()、uvm_ hdl_ deposit()), 将寄存器的操作直接作用到DUT内的寄存器变批,而不通过物理总线访问。

接下来给出一段前门访问的例码,下面的sequence,继承于uvm_reg_ sequence。uvm _reg_ sequence除了具备一般uvm_ sequence的预定义方法外,还具有与寄存器操作相关的方法。在下面对寄存器操作的例码中,可以看到两种方式:

第一种即uvm_reg:: read()/write()。在传递时,用户需要注意将参数path指定为UVM_ FRONTDOOR。uvm_reg: :read()/write()方法可传入的参数较多,除了status和 value两个参数需要传入,其他参数如果不指定,可采用默认值。

第二种即uvm_reg_ sequence: :read _reg()/write _reg()。在使用时,也需要将path指定为UVM FRONTDOOR。

class mcdf_example_seq extends uvm_reg_sequence;
mcdf_rgm rgm; 
`uvm_object_utils(mcdf_example_seq)
uvm_declare_p_sequencer(mcdf_bus_sequencer)
...
taskbody();
uvm_status_e status; 
uvm_reg_data_t data; 
if (! uvm_config_db# (mcdf _rgm) : : get (null, get_full_name (), "rgm", 
    rgm)) begin
`uvm_error("GETRGM","no top-down RGM handle is assigned") 
end 
// register model access write() I read() 
rgm.chnl0_ctrl_reg.read (status, data, UVM_FRONTDOOR, .parent (this)); 
rgm.chnl0_ctrl_reg.write(status,'hll, UVM_FRONTDOOR, .parent(this)); 
rgm.chnl0_ctrl_reg.read (status, data, UVM_FRONTDOOR, .parent (this)); 
//pre-defined methods access 
read_reg (rgm.chnll_ctrl_reg, status, data, UVM_FRONTDOOR); 
write_reg(rgm.chnll_ctrl_reg, status,'h22, UVM_FRONTDOOR); 
read reg (rgm.chnll_ctrl_reg, status, data, UVM_FRONTDOOR);
endtask 
endclass

 后门访问

在进行后门访问时, 首先要确保寄存器模型在建立时将各个寄存器映射到了 DUT一侧的 HDL 路径。 下面的例码即实现了寄存器模型与 DUT 各个寄存器的路径映射:

class mcdf_rgm extends uvm_reg_block;
...//寄存器成员和map声明 virtual function build(); 
virtual function build(); 
...//寄存器成员和map创建
//关联寄存器模型和HDL
add_hdl_path("reg_backdoor_access.dut"); 
chnl0_ctrl_reg.add_hdl_path_slice($sformatf ("regs [%0d] ",`SLVO_RW_REG), 
     0, 32); 
chnll_ctrl_reg.add_hdl_path_slice($sformatf("regs[%0d]",'SLVl_RW_REG), 
     0, 32);
chnl2_ctrl_reg.add_hdl_path_slice($sformatf("regs[%0d]", SLV2_ RW _ REG) , 
     0, 32); 
chnlO_stat_reg.add_hdl_path_slice($sformatf ("regs [%0d] ",、SLVO_R_REG ),
     0, 32); 
chnll _stat_ reg. add_hdl_path_slice($sformatf("regs[%0d]", SLVl_R_REG ),	
     0, 32); 
chnl2_stat_reg.add_hdl_path_slice($sformatf ("regs [%Oct]", ·SLV2_R_REG),
     0, 32); 
lock_model ();
endfunction 
endclass 

例码中通过 uvm_reg_ block: :add_ hdl _path()将寄存器模型关联到了 DUT一端,而通过uvm _reg: :add_ hdl _path_ slice 完成了将寄存器模型各个寄存器成员与 HDL一侧地址的映射。例如,稍后对寄存器 SLVO_RW_REG 进行后门访问时, UVM DPI 函数通过寄存器 HDL 路径 "reg_backdoor_access.dut.regs[0]" 映射到正确的寄存器位置, 继而对其进行读值或修改。 另外, 寄存器模型 build()函数最后以 lock_model()结尾, 该函数的功能是结束地址映射关系, 并且保证模型不会被其他用户修改。

在寄存器模型完成了 HDL 路径映射后, 我们才可以利用 uvm_reg 或 uvm_reg_ sequence自带的方法进行后门访问。 下面给出一段后门访问的例码, 类似于前门访问, 后门访问也有几类方法提供:

•    uvm _reg:: read()/write(), 在调用该方法时需要注明 UVM_BACKDOOR 的访问方式。
•    uvm _reg_ sequence: :read _reg()/write _reg(), 在使用时也需要注明 UVM_BACKDOOR 的访问方式。
•    另外, uvm_reg: :peek()/poke()两个方法,也分别对应了读取寄存器 (peek) 和修改寄存器 (poke) 两种操作, 而用户无须指定访问方式为 UVM_BACKDOOR, 因为这两个方法本来就只针对于后门访问的。

class mcdf_example_seq extends uvm_reg_sequence;
mcdf_rgm rgm;
`uvm_object_utils(mcdf_example_seq)
`uvm_declare_p_sequencer(mcdf_bus_sequencer)
...
task body();
uvm_status_e status; 
uvm_reg_data_t data; 
if(! uvm_config_db# (mcdf_rgm)::get(null, get_full_name (), "rgm",
   rgm)) begin 
`uvm_error("GETRGM", "no top-down RGM handle is assigned")
end 
// register model access write()/read() 
rgm.chnl0_ctrl_reg.read (status, data, UVM_BACKDOOR, .parent(this)); rgm.chnlO_ctrl_reg.write(sta七us,'hll, UVM_BACKDOOR, .parent(this)); rgm.chnlO_ctrl_reg.read (status, data, UVM_BACKDOOR, .parent(this)); 
// register model access poke()/peed() 
rgm.chnll_ctrl_reg.peek (status, data, .parent(this)); 
rgm.chnll_ctrl_reg.poke (status, 'h22, .parent(this)); 
rgm.chnll_ctrl_reg.peek (status, data, .parent(this)); 
// pre-defined methods read_reg () /write_reg () 
read_reg (rgm.chnl2_ctrl_reg, status, data, UVM_BACKDOOR); write_reg(rgm.chnl2_ctrl_reg, status, 'h22, UVM_BACKDOOR); 
read_reg (rgm.chnl2_ctrl_reg, status, data, UVM_BACKDOOR); 
// pre-defined methods peek_reg()lpoke_reg() 
peek_reg (rgm.chnl2_ctrl_reg, status, data); 
poke_reg (rgm.chnl2_ctrl_reg, status, 'h33); 
peek_reg (rgm.chnl2_ctrl_reg, status, data);
endtask 
endclass 

前门访问和后门访问的比较

后门访问较前门访问更便捷、 更快一些, 但单纯依赖后门访问也不能称之为 “正道 ”。实际上, 利用寄存器模型的前门访问和后门访问混合方式, 对寄存器验证的完备性更有帮助。 下面给出一些实际应用的场景:

通过前门访问的方式,先验证寄存器访问的物理通路工作正常,并且有专门的寄存器测试的前门访问序列来遍历所有的寄存器。在前门访问被验证充分的前提下, 可以在后续测试中使用后门访问来节省访问多个寄存器的时间。

如果DUT实现了一些特殊寄存器, 例如只能写一次次的寄存器等, 我们建议用物理方式去访问以确保反映真实的硬件行为。

寄存器随机设置的精髓不在于随机可设置的域值, 而是为了考虑日常不可预期的场景, 先通过后门访问随机化整个寄存器列表(在一定的随机限制下), 随后再通过前门访问来配置寄存器。这么做的好处在于, 不再只是通过设置复位之后的寄存器这种更有确定性的场景,而是通过让测试序列一开始的寄存器值都随机化来模拟无法预期的硬件配置前场景,而在稍后设置了必要的寄存器之后, 再来看是否会有意想不到的边界情况发生。

有的时候, 即便通过先写再读的方式来测试一个寄存器, 也可能存在地址不匹配的情况。 比如寄存器A地址本应该为0x10, 寄存器B地址本应该为0x20; 而在硬件实现中, 寄存器A对应的地址为0x20, 寄存器B对应的地址为0x10。 像这种错误, 即便通过先写再读的方式也无法有效测试出来, 那么不妨在通过前门配置寄存器A之后,再通过后门访问来判断HDL地址映射的寄存器A变量值是否改变,最后通过前门访问来读取寄存器A的值上述的方式是在前门测试的基础之上又加入了中途的后门访问和数值比较, 可以发现地址映射到错误寄存器的问题。

对于一些状态寄存器, 在一些时候外界的激励条件修改会依赖这些状态寄存器,并且在时序上的要求也可能很严格。 例如, 上面MCDF的寄存器中有一组状态寄存器表示各个 channel 中 FIFO 的余量, 而 channel 中 FIFO 的余量对于激励驱动的行为也很重要。 无论是前门访问还是后门访问, 都可能无法第一时间反映 FIFO 当前时刻的余量。因此对于要求更严格的测试场景,除了需要前门和后门来访问寄存器, 也需要映射一些重要的信号来反映更即时的信息。

  • 2
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
UVM-Info是一种用于在Universal Verification Methodology(UVM)中进行消息传递和报告的结构。它提供了一种机制,用于在测试环境中传递信息、警告和错误消息,并将其报告给用户。 UVM-Info结构由以下几个主要组成部分组成: 1. `uvm_info`类:这是UVM-Info结构的主要类,用于创建和管理消息。它提供了一些方法,如`uvm_info::set_id()`用于设置消息的唯一标识符,`uvm_info::set_file()`用于设置消息所在的文件名,`uvm_info::set_line()`用于设置消息所在的行号等。 2. `uvm_report_object`类:这是一个基类,用于派生出具体的报告对象类。它提供了一些方法,如`uvm_report_object::uvm_report_info()`用于创建并发送信息消息,`uvm_report_object::uvm_report_warning()`用于创建并发送警告消息,`uvm_report_object::uvm_report_error()`用于创建并发送错误消息等。 3. `uvm_report_handler`类:这是一个报告处理器类,用于管理和过滤UVM-Info结构中的消息。它提供了一些方法,如`uvm_report_handler::set_actions()`用于设置不同类型消息的处理动作,`uvm_report_handler::set_severity_id()`用于设置消息的严重程度和唯一标识符等。 4. `uvm_report_catcher`类:这是一个报告捕获器类,用于捕获和处理UVM-Info结构中的消息。它提供了一些方法,如`uvm_report_catcher::catch_info()`用于捕获信息消息,`uvm_report_catcher::catch_warning()`用于捕获警告消息,`uvm_report_catcher::catch_error()`用于捕获错误消息等。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

创芯人

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值