(11)UVM 寄存器模型集成(上)
控制寄存器接口首先需要在每一个时钟解析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("GETVIE","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.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 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());
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);
case(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
总线UVC的解析
·示例囊括了mdf_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寄存器设计代码
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)begin //write R&W register
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
这篇笔记参考《UVM实战》、《芯片验证漫游指南》和某验证视频整理而成,仅作学习心得交流,如果涉及侵权烦请请告知,我将第一时间处理。