class mcdf_bus_trans extends uvm_sequence_item;
rand bit[1:0] cmd;
rand bit[7:0] addr;
rand bit[31:0] wdata;
bit[31:0] rdata; //没有用rand是因为它应从总线读出或观察
`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); //component类
endfunction
task run_phase(uvm_phase phase);
forever begin //注意forever
mon_trans();
end
endtask
task mon_trans();
mcdf_bus_trans t;
@(posedge vif.clk);
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
begin
@(posedge vif.clk);
#10ns;
t.rdata = vif.rdata;
ap.write(t);
end
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 req, rsp;
reset_listener();
forever begin
seq_item_port.get_next_item(tmp);
void'($cast(req, tmp));
`uvm_info("DRV", $sformatf("got a item \n %s", req.sprint()), UVM_LOW)
void'($cast(rsp, req.clone()));
rsp.set_sequence_id(req.get_sequence_id());
rsp.set_transaction_id(req.get_transaction_id());
driver_bus(rsp);
seq_item_port.item_done(rsp);
`uvm_info("DRV", $sformatf("sent a item \n %s", req.sprint()), UVM_LOW)
end
endtask
task reset_listener();
fork
forever begin
@(negedge vif.rstn) driver_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(t);
default: `uvm_error("DRIVE", "invalid mcdf command type received!")
endcase
endtask
task drive_write(mcdf_bus_trans t);
@(posedge vif.clk);
vif.cmd <= t.cmd;
vif.addr <= t.addr;
vif.wdata <= t.wdata;
endtask
task drive_read(mcdf_bus_trans t);
@(posedge vif.clk);
vif.cmd <= t.cmd;
vif.addr <= t.addr;
@(posedge vif.clk);
#10ns;
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
:
mcdf_bus_trans
包括可随机化的数据成员cmd
、addr
、wdata
和不可随机化的rdata
。rdata
没有声明为rand
类型是因为它应从总线读出或观察,不应随机化。mcdf_bus_monitor
会观测总线,其后通过analysis port
写出到目标analysis组件。mcdf_bus_driver
主要实现了总线驱动和复位功能,通过模块化的方法reset_listener()
、driver_bus()
、drive_write()
、drive_read()
、drive_idle()
可以解析三种命令模式IDLE
、WRITE
、READ
,并且在READ
模式下将读回的数据通过item_done(rsp)
写回到sequencer
和sequence
一侧。`define IDLE 2'b00
`define WRITE 2'b01
`define READ 2'b10
`define SLVO_RW_ADDR 8'h00
`define SLV1_RW_ADDR 8'h04
`define SLV2_RW_ADDR 8'h08
`define SLVO_R_ADDR 8'h10
`define SLV1_R_ADDR 8'h14
`define SIV2_R_ADDR 8'h18
`define SLVO_RN_REG 0
`define SLV1_RW_REG 1
`define SLV2_RN_REG 2
`define SLVO_R_REG 3
`define SLV1_R_REG 4
`define SIV2_R_REG 5
module ctrl_regs(
clk_i,rstn_i,
cmd_i, cmd_addr_i,cmd_data_i, cmd_data_o,
s1v0_len_o,slv1_len_o,slv2_len_o,
slv0_prio_o,slv1_prio_o,slv2_prio_o,
slv0_margin_i,slv1_margin_i,slv2_margin_i,
slv0_en_o,slv1_en_o,slv2_en_o);
input clk_i, rstn_i;
input [1:0] cmd_i;
input [7:0] cmdaddr_i;
input [31:0] cmd_data_i;
input [7:0] slvo_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] reg [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 [`SLV1_RW_REG] <= 32'h00000007;
regs [`SLV2_RW_REG] <= 32'h00000007;
regs [`SLV0_R_REG] <= 32'h00000010;
regs [`SLV1_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 [`SLV0_RW_REG] [5:0] <= cmd_data_i;
`SLV1_RW_ADDR: regs [`SLV1_RW_REG] [5:0] <= cmd_data_i;
`SLV2_RW_ADDR: regs [`SLV2_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];
`SLV1_RW_ADDR: cmd_data_reg <= regs[`SLV1_RW_REG];
`SLV2_RW_ADDR: cnd_data_reg <= regs[`SLV2_RW_REG];
`SLV0_R_ADDR: cmd_data_reg <= regs[`SLV0_R_REG];
`SLV1_R_ADDR: cmd_data_reg <= regs[`SLV1_R_REG];
`SLV2_R_ADDR: cmd_data-reg <= regs[`SLV2_R_REG];
endcase
end
regs[`SLV0_R_REG][7:0] <= slv0_margin_i;
regs[`SLV1_R_REG][7:0] <= slv1_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[`SLV0_RW_REG][5:3];
assign s1v1_len_o = regs[`SLV1_RW_REG][5:3];
assign slv2_len_o = regs[`SLV2_RW_REG][5:3];
assign slv0_prio_o = regs[`SLV0_RW_REG][2:1];
assign slv1_prio_o = regs[`SLV1_RW_REG][2:1];
assign slv2_prio_o = regs[`SLV2_RW_REG][2:1];
assign slv0_en_o = regs[`SLV0_RW_REG][0];
assign slv1_en_o = regs[`SLV1_RW_REG][0];
assign slv2_en_o = regs[`SLV2_RW_REG][0];
endmodule:ctrl_regs
adapter
。每一个总线对应的adapter
所完成的桥接功能即是在uvm_reg_bus_op
和总线transaction
之间的转换,在开发某一个总线adapter
类型时,需要实现下面几点:
uvm_reg_bus_op
与总线transaction
中各自的数据映射。reg2bus()
和bus2reg()
两个函数实现两种transaction
的数据映射。byte
访问,可以使能supports_byte_enable
;如果总线UVC要返回response
数据则应使能provides_response
。mcdf_bus_driver
在读数时会将读回的数据填入到RSP
并返回至sequencer
,因此需要在adapter
中使能provides_responses
。由此使得bus2reg()
函数调用时得到的数据是总线返回时的transaction
,如果总线UVC不支持返回RSP
(没有调用put_response(RSP)
或者item_done(RSP)
),那么不应该置此位,否则adapter
将会使得验证环境挂起。默认情况下上述两个成员的复位值都是0。Adapter示例
class reg2mcdf_adapter extends uvm_reg_adapter;
`uvm_object_utils(reg2mcdf_adapter)
function new(string name = "mcdf_bus_trans");
super.new(name);
provides_response = 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; //返回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 the correct type")
return;
end
rw.kind = (t.cmd == `WRITE) ? UVM_WRITE : UVM_READ;
rw.kind = t.addr;
rw.data = (t.cmd == `WRITE) ? t.wdata : t.rdata;
rw.status = UVM_IS_OK;
endfunction
endclass
provides_response
,这是因为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_sequencer
传入到mcdf_bus_driver
。这里的transaction
传输是后台隐式调用的,不用自己发起。uvm_reg_adapter::bus2reg()
是从mcdf_bus_driver()
将数据写回至mcdf_bus_sequence
,而一直保持监听的reg2mcdf_adapter
一旦从sequencer
获取了RSP(mcdf_bus_trans)
之后,就将自动调用bus2reg()
函数。bus2reg()
函数的功能与reg2bus()
相反,完成了从mcdf_bus_trans
到uvm_reg_bus_op
的内容映射。完成映射之后,更新的uvm_reg_bus_op
数据最终返回至寄存器操作场景层。reg2bus()
,继而发起总线事务,而在完成事务发回反馈之后,又需要调用bus2reg()
,将总线的数据返回至寄存器操作层面。mcdf_rgm
、总线UVC mcdf_bus_agent
和桥接reg2mcdf_adapter
之后,就需要考虑如何将adapter
集成到验证环境中去:
mcdf_rgm
的集成,倾向于顶层传递的方式,即最终从test层传入寄存器模型句柄。这种方式有利于验证环境mcdf_bus_env
的闭合性,在后期不同test
如果要对rgm
做不同的配置,都可以在顶层例化,而后通过uvm_config_db
传递。build()
函数。需要注意uvm_reg_block
是uvm_object
类型,因此其预定义的build()
函数不会自动执行,需单独调用。auto prediction
的方式,因此调用了函数set_auto_predict()
。connect
阶段还需要将寄存器模型的map
组件与bus_sequencer
和adapter
连接。这样才能将map
(寄存器信息)、sequencer
(总线侧激励驱动)和adapter
(寄存器级别和硬件总线级别的桥接)关联在一起。也只有通过这一步,adapter
的桥接功能才可以工作。Adapter集成示例
class mcdf_bus_env extends uvm_env;
mcdf_bus_agent agent;
mcdf_rgm rgm;
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("NEWRGM", "created rgm instance locally", UVM_LOW)
end
/*rgm.build()会创建各个uvm_reg,进而对uvm_reg作config,
还会调用各个uvm_reg中的build(),去例化每个uvm_reg中的field,去配置各个field*/
rgm.build();
rgm.map.set_auto_predict();
reg2mcdf = reg2mcdf::type_id::create("reg2mcdf");
endfunction
function void connect_phase(uvm_phase phase);
rgm.map.set_sequencer(agent.sequencer, reg2mcdf); //notice
endfunction
endclass
class test1 extends uvm_test;
mcdf_rgm rgm;
mcdf_bus_env env;
`uvm_component_utils(test1)
...
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
利用寄存器模型,可以更方便地对寄存器做操作,分为两种访问寄存器的方式,即前门访问和后门访问:
UVM DPI
(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_examnle_seq extends uvm_reg_seqnence;
mcdf_rgn 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.chn10_ctrl_reg.read(status, data, UVM_FRONTDOOR, .parent(this));
rgm.chn10_ctrl_req.write(status, 'h11, UVM_FRONTDOOR, .parent(this));
rgm.chnl0_ctrl_reg.read(status, data, UVM_FRONTDOOR, .parent(this));
//pre-defined methods access
read_reg(rgm.chnl1_ctrl_reg, status, data, UVM_FRONTDOOR);
write_reg(rgm.chnl1_ctrl_reg, status, 'h22, UVM_FRONTDOOR);
read_reg(rgm.chnll_ctrl_reg, status, data, UVM_FRONTDOOR);
endtask
endclass
class medf_rgm extends uvm_reg_block;
...//寄存器成员和map声明
virtual function build();
...//寄存器成员和map创建
//关联寄存器模型和HDL
add_hdl_path("req_backdoor_access.dut");
chn10_ctrl_reg.add_hdl_path_slice($sformatf("regs[%0d]",`SLV0_RW_REG), 0, 32);
chn11_ctrl_reg.add_hdl_path_slice($sformatf("regs[%0d]",`SLV1_RW_REG), 0, 32);
chn12_ctrl_reg.add_hdl_path_slice($sformatf("regs[%0d]",`SLV2_RW_REG), 0, 32);
chn10_stat_req.add_hdl_path_slice($sformatf("regs[%0d]",`SLV0_R_REG), 0, 32);
chn11_stat_req.add_hdl_path_slice($sformatf("regs[%0d]",`SLV1_R_REG), 0, 32);
chn12_stat_req.add_hdl_path_slice($sformatf("regs[%0d]",`SLV2_R_REG), 0, 32);
lock_model();
endfunction
endclass
uvm_reg_block::add_hdl_path()
将寄存器模型关联到DUT一端,而通过uvm_reg::add_hdl_path_slice
完成将寄存器模型各个寄存器成员与HDL一侧的地址映射。build()
函数最后一句以lock_model()
结尾,该函数的功能是结束地址映射关系并保证模型不会被其它用户修改。uvm_reg
或uvm_reg_sequence
自带的方法进行后门访问,后门访问也有几类方法提供:
uvm_reg::read()/write()
和uvm_reg_sequence::read_reg()/write_reg()
,在调用该方法时需要注明UVM_BACKDOOR
的访问方式。uvm_reg::peek()/poke()
两个方法,也分别对应了读取寄存器(peek)和修改寄存器(poke)两种操作,而用户无需指定访问方式为UVM_BACKDOOR
,因为这两个方法本来就只针对于后门访问。后门访问示例:
class modf_example_seq extends uvm_reg_sequence;
mcdf_rgm rgm;
`uvm_object_utils(mcdf_example_seq)
`uvm_declare_p_sequencer(mcdf_bus_sequencer)
...
task body();
uvn_status_e status;
uvm_reg_data_t data;
if(!uvm_config_db#(mcdf_rgm)::get(null, get_full_name(), "ram", rgm)) begin
`uvm_error("GETRGM", "no top-down RGM handle is assianed")
end
//reqister model access write()/read()
rgm.chnl0_ctrl_req.read(status, data, UVM_BACKDOOR, .parent(this));
rgm.chnl0_ctrl_req.write(status, 'h11, UVM_BACKDOOR, .parent(this));
rgm.chnl0_ctrl_req.read(status, data, UVM_BACKDOOR, .parent(this));
//register model access poke()/peek()
rgm.chnl1_ctrl_reg.peek(status, data, .parent(this));
rgm.chnl1_ctrl_reg.poke(status, 'h22, .parent(this));
rgm.chnl1_ctrl_reg.peek(status, data, .parent(this));
//pre-defined methods read_reg()/write_reg()
read_reg(rgm.chn12_ctrl_req, status, data, UVM_BACKDOOR);
write_reg(rgm.chn12_ctrl_req, status, 'h22, UVM_BACKDOOR);
read_reg(rgm.chn12_ctrl_req, status, data, UVM_BACKDOOR);
//pre-defined methods peek_reg()/poke_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