UVM 寄存器模型集成
文章目录
- MCDF访问寄存器的总线接口时序较为简单。控制寄存器接口首先需要在每一个时钟解析cmd。当cmd为写指令时,即需要把数据cmd_data_in写入到cmd_addr对应的寄存器中。当cmd为读指令时,即需要从cmd_addr对用的寄存器中读取数据,在下一个周期,cmd_addr对应的寄存器数据被输送至cmd_data_out接口。
总线UVC实现代码
- 下面给出一端8位地址线,32位数据线的总线UVC实现代码。
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;
`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_vif 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.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)
#10ps;
t.rdata=vif.rdata;
ap.write(t);
end
join_none
end
endtask
endclass:mcdf_bus_monitor
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 tep;
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_transcation_id(req.get_transaction_id());
drive_bus(rsp);
seq_item_port.item_done(rsp);
`uvm_info("DRV",$sformatf("sent a item\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);
cast(t.cmd)
`WRITE:drive_write(t);
`READ:drive_read(t);
`IDLE:drive_idle(1);
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);
#10ps;
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组件,在本节稍后会连接到uvm_reg_predictor。
- mcdf_bus_driver主要实现了总线驱动和复位功能,通过模块化的方法reset_listener()、drive_bus()、drive_write()、drive_read()和drive_idle()可以解析三种命令模式IDLE、WRITE、READ,并且在READ模式下,将读回的数据通过item_done(rsp)写回到sequencer和sequence一侧。建议读者在通过clone()命令创建rsp对象后,通过set_sequence_id()和set_transaction_id()两个函数保证REQ和RSP中保留的ID信息一致。
MCDF寄存器设计代码
MCDF寄存器设计代码:
param_def.v
`define ADDR_WIDTH 8
`define CMD_DATA_WIDTH 32
`define WRITE 2'b10 //Register operation command
`define READ 2'b01
`define IDLE 2'b00
`define SLV0_RW_ADDR 8'h00 //Register address
`define SLV1_RW_ADDR 8'h04
`define SLV2_RW_ADDR 8'h08
`define SLV0_R_ADDR 8'h10
`define SLV1_R_ADDR 8'h14
`define SLV2_R_ADDR 8'h18
`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
`define FIFO_MARGIN_WIDTH 8
`define PRIO_WIDTH 2
`define PRIO_HIGH 2
`define PRIO_LOW 1
`define PAC_LEN_WIDTH 3
`define PAC_LEN_HIGH 5
`define PAC_LEN_LOW 3
reg.v
module ctrl_regs( clk_i,
rstn_i,
cmd_i,
cmd_addr_i,
cmd_data_i,
cmd_data_o,
slv0_pkglen_o,
slv1_pkglen_o,
slv2_pkglen_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;
input rstn_i;
input [1:0] cmd_i;
input [`ADDR_WIDTH-1:0] cmd_addr_i;
input [`CMD_DATA_WIDTH-1:0] cmd_data_i;
input [`FIFO_MARGIN_WIDTH-1:0] slv0_margin_i;
input [`FIFO_MARGIN_WIDTH-1:0] slv1_margin_i;
input [`FIFO_MARGIN_WIDTH-1:0] slv2_margin_i;
reg [`CMD_DATA_WIDTH-1:0] mem [5:0];
reg [`CMD_DATA_WIDTH-1:0] cmd_data_reg;
output [`CMD_DATA_WIDTH-1:0] cmd_data_o;
output [`PAC_LEN_WIDTH-1:0] slv0_pkglen_o;
output [`PAC_LEN_WIDTH-1:0] slv1_pkglen_o;
output [`PAC_LEN_WIDTH-1:0] slv2_pkglen_o;
output [`PRIO_WIDTH-1:0] slv0_prio_o;
output [`PRIO_WIDTH-1:0] slv1_prio_o;
output [`PRIO_WIDTH-1:0] slv2_prio_o;
output slv0_en_o;
output slv1_en_o;
output slv2_en_o;
always @ (posedge clk_i or negedge rstn_i) //Trace fifo's margin
begin
if (!rstn_i)
begin
mem [`SLV0_R_REG] <= 32'h00000020; //FIFO's depth is 32
mem [`SLV1_R_REG] <= 32'h00000020;
mem [`SLV2_R_REG] <= 32'h00000020;
end
else
begin
mem [`SLV0_R_REG] <= {24'b0,slv0_margin_i};
mem [`SLV1_R_REG] <= {24'b0,slv1_margin_i};
mem [`SLV2_R_REG] <= {24'b0,slv2_margin_i};
end
end
always @ (posedge clk_i or negedge rstn_i) //write R&W register
begin
if (!rstn_i)
begin
mem [`SLV0_RW_REG] = 32'h00000007;
mem [`SLV1_RW_REG] = 32'h00000007;
mem [`SLV2_RW_REG] = 32'h00000007;
end
else if (cmd_i== `WRITE) begin
case(cmd_addr_i)
`SLV0_RW_ADDR: mem[`SLV0_RW_REG]<= {26'b0,cmd_data_i[`PAC_LEN_HIGH:0]};
`SLV1_RW_ADDR: mem[`SLV1_RW_REG]<= {26'b0,cmd_data_i[`PAC_LEN_HIGH:0]};
`SLV2_RW_ADDR: mem[`SLV2_RW_REG]<= {26'b0,cmd_data_i[`PAC_LEN_HIGH:0]};
endcase
end
end
always@ (posedge clk_i or negedge rstn_i) // read R&W, R register
if(!rstn_i)
cmd_data_reg <= 32'b0;
else if(cmd_i == `READ)
begin
case(cmd_addr_i)
`SLV0_RW_ADDR: cmd_data_reg <= mem[`SLV0_RW_REG];
`SLV1_RW_ADDR: cmd_data_reg <= mem[`SLV1_RW_REG];
`SLV2_RW_ADDR: cmd_data_reg <= mem[`SLV2_RW_REG];
`SLV0_R_ADDR: cmd_data_reg <= mem[`SLV0_R_REG];
`SLV1_R_ADDR: cmd_data_reg <= mem[`SLV1_R_REG];
`SLV2_R_ADDR: cmd_data_reg <= mem[`SLV2_R_REG];
endcase
end
assign cmd_data_o = cmd_data_reg;
assign slv0_pkglen_o = mem[`SLV0_RW_REG][`PAC_LEN_HIGH:`PAC_LEN_LOW];
assign slv1_pkglen_o = mem[`SLV1_RW_REG][`PAC_LEN_HIGH:`PAC_LEN_LOW];
assign slv2_pkglen_o = mem[`SLV2_RW_REG][`PAC_LEN_HIGH:`PAC_LEN_LOW];
assign slv0_prio_o = mem[`SLV0_RW_REG][`PRIO_HIGH:`PRIO_LOW];
assign slv1_prio_o = mem[`SLV1_RW_REG][`PRIO_HIGH:`PRIO_LOW];
assign slv2_prio_o = mem[`SLV2_RW_REG][`PRIO_HIGH:`PRIO_LOW];
assign slv0_en_o = mem[`SLV0_RW_REG][0];
assign slv1_en_o = mem[`SLV1_RW_REG][0];
assign slv2_en_o = mem[`SLV2_RW_REG][0];
endmodule
寄存器模型集成
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 the correct type")
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
- 该类在构建函数中使能了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()函数。
bus2reg()函数的功能与reg2bus()相反,完成了从mcdf_bus_trans到uvm_reg_bus_op的内容映射。在完成映射之后,更新的uvm_reg_bus_op数据最终返回至寄存器操作场景层。
对于寄存器操作,无论读操作还是写操作,都需要经历调用reg2bus),继而发起总线事务,而在完成总线事务发回反馈之后,又需要调用bus2reg(),将总线的数据返回至寄存器操作层面。
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("GETVIF","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();
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
register model和adapter都是object
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
关注作者
- 自述
作者是一位中科大数字设计专业的研究生,水平有限,如有错误,请大家指正,想要与大家一同进步。 - 经历
曾获得国家奖学金,“高教社杯”数学建模国家二等奖等 - 陆续更新:
1.与UVM验证相关的system verilog后续内容;
2.与verilog数字设计相关的一些基础模块设计,例如FIFO,UART,I2C等的书写。
3.保研与竞赛经历等 - 微信公众号
欢迎大家关注公众号“数字IC小白的日常修炼”,期待与大家一同仗剑遨游数字IC世界。