ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

(25)UVM 寄存器模型集成

2021-11-23 10:02:55  阅读:200  来源: 互联网

标签:25 bus cmd uvm 寄存器 phase mcdf define 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

寄存器模型集成

在这里插入图片描述

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世界。

标签:25,bus,cmd,uvm,寄存器,phase,mcdf,define,UVM
来源: https://blog.csdn.net/qq_42419590/article/details/121456751

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有