(25)UVM 寄存器模型集成

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

寄存器模型集成

(25)UVM 寄存器模型集成

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

(25)UVM 寄存器模型集成

  • 该类在构建函数中使能了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世界。
上一篇:SpringCloud H版系列10--Bus消息中心


下一篇:RK3308 ADC 调试