UVM学习笔记(五)寄存器


寄存器有关的设计流程。
寄存器模型的相关类。
如何将寄存器模型集成到现有环境,与总线UVC桥接,与DUT模型绑定。
寄存器模型的常用方法和预定义的sequence。
寄存器测试和功能覆盖率的实际用例。

一、reg_model的实现

uvm_reg_field 用来针对寄存器功能域来构建对应的比特位
uvm_reg 与寄存器相匹配,其内部可以例化和配置多个uvm_reg_field对象(uvm_object)
uvm_mem 匹配硬件存储模型
uvm_reg_map 用来指定寄存器列表中各个寄存器的偏移地址、访问属性以及对应的总线
uvm_reg_block 可以容纳多个寄存器(uvm_reg)、存储器(uvm_mem)和寄存器列表(uvm_reg_map)

//1. 在定义单个寄存器时,需要将寄存器的各个域整理出来,在创建之后还应当通过uvm_reg_field::configure()函数来进一步配置各自属性。
class ctrl_reg extends uvm_reg;
	`uvm_object_utils(ctrl_reg)
	uvm_reg_field reserved;
	rand uvm_reg_field pkt_len;//为什么要随机化
	rand uvm_reg_field prio_level ;
	rand uvm_reg_field chnl_en;
	function new(string name = "ctrl_reg");
		super.new(name, 32, UVM_NO_COVERAGE);//
	endfunction
	virtual function build();//
		//对各个field创建
		reserved = uvm_reg_field::type_id::create("reserved") ;
		pkt_len = uvm_reg_field::type_id::create("pkt_len") ;
		prio_level = uvm_reg_field::type_id::create("prio_level") ;
		chnl_en = uvm_reg_field::type_id::create ("chnl_en") ;
		//对各个field进行创建,位数,起点为,读写,reset值
		reserved.configure(this, 26, 6,"RO", 0, 26'h0, 1, 0, 0);//配置各个reg_field属性
		pkt_len.configure(this,3,3,"RW", 0, 3'h0, 1, 1, 0);
		prio_level.configure(this, 2, 1, "RW", 0, 2'h3, 1, 1, 0);
		chnl_en.configure(this, 1, 0, "RW", 0, 1'h0, 1, 1, 0);
	endfunction
endclass

class stat_reg extends uvm_reg;
	`uvm_object_utils(stat_reg)
	uvm_reg_field reserved;
	rand uvm_reg_field fifo_avail ;
	function new(string name = "stat_reg") ;
		super.new(name, 32, UVM_NO_COVERAGE) ;
	endfunction
	virtual function build() ;
		reserved = uvm_reg_field::type_id::create("reserved") ;
		fifo_avail = uvm_reg_field::type_id::create ("fifo_avail") ;
		reserved.configure(this, 24, 8, "RO", 0, 24'h0, 1, 0, 0) ;
		fifo_avail.configure(this, 8, 0, "RO", 0, 8'h0, 1, 1, 0) ;
	endfunction
endclass
//uvm_reg和uvm_mem分别对应着硬件中独立的寄存器或者存储,一个uvm_reg_block可以用来模拟一个功能模块的寄存器模型;map的作用一方面用来表示寄存器和存储对应的偏移地址,同时由于一个reg_block可以包含多个map,各个map可以分别对应不同总线或者不同地址段。在reg_block中创建了各个uvm_reg之后,需要调用uvm_reg::configure()去配置各个uvm_reg实例的属性。
class mcdf_rgm extends uvm_reg_block ;
	`uvm_object_utils(mcdf_rgm)
	rand ctrl_reg chn10_ctrl_reg ;
	rand ctrl_reg chn11_ctrl_reg;
	rand ctr1_reg chn12_ctrl_reg;
	rand stat_reg chn10_stat_reg;
	rand stat_reg chn11_stat_reg;
	rand stat_reg chn12_stat_reg;
	uvm_reg_map map;
	function new(string name = "mcdf_rgm") ;
		super.new(name, UVM_NO_COVERAGE) ;
	endfunction
	virtual function build();
		chn10_ctr1_reg = ctrl_reg::type_id::create("chn10_ctr1_reg");//例化
		//创建了各个uvm_reg之后,需要调用uvm_reg::configure()配置各个uvm_reg实例的属性。
		chn10_ctrl_reg.configure(this);//将uvm_reg与当前的uvm_reg_block关联起来
		chn10_ctrl_reg.build();//创建每个reg_field,需要自己调用,没有自动创建
		chn11_ctrl_reg = ctrl_reg::type_id::create("chn11_ctrl_reg");
		chn11_ctrl_reg.configure(this);
		chn11_ctrl_reg.build();
		chn12_ctrl_reg = ctr1_reg::type_id::create ("chn12_ctr1_reg");
		chn12_ctrl_reg.configure(this);
		chn12_ctr1_reg.bui1d() ;
		chn10_stat_reg = stat_reg::type_id::create("chn10_stat_reg");
		chn10_stat_reg.configure(this) ;
		chn10_stat_reg.build() ;
		chn11_stat_reg = stat_reg::type_id::create("chnl1_stat_reg") ;
		chn11_stat_reg.configure(this);
		chn11_stat_reg.build();
		chn12_stat_reg = stat_reg::type_id::create("chn12_stat_reg") ;
		chn12_stat_reg.configure(this);
		chn12_stat_reg.build();
		//map的作用一方面用来表示寄存器和存储对应的偏移地址,同时由于一个reg_block可以包含多个map,各个map可以分别对应不同总线或者不同地址段。
		// map name, offset, number of bytes, endianess
		map = create_map("map", 'h0, 4, UVM_LITTLE_ENDIAN);//base_addr + offset_addr
		//uvm_reg_map也会在uvm_reg_block中例化,在例化之后需要通过uvm_reg_map::add_reg()函数来添加各个uvm_reg对应的偏移地址和访问属性等。只有规定了这些属性,才可以在稍后的前门访问(frontdoor) 中给出正确的地址。

		map.add_reg(chn10_ctrl_reg, 32'h00000000, "RW") ;//
		map.add_reg(chn11_ctr1_reg, 32'h00000004, "RW") ;
		map.add_reg(chn12_ctrl_reg, 32'h00000008, "RW") ;
		map.add_reg(chn10_stat_reg, 32'h00000010, "RO") ;
		map.add_reg(chn11_stat_reg, 32'h00000014, "RO") ;
		map.add_reg(chn12_stat_reg, 32'h00000018, "RO") ;
		lock_model();//模型建立后锁住,不再改变
	endfunction
endclass: mcdf_rgm

二、总线bus_agent的实现

//1.trans
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
//2.sequencer
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
//3.monitor:ap除了连接到scoreboard上,还会连接到uvm_reg_predictor上
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);//new,不是object
	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//写,将接口的cmd,addr,data都写入,并发送出去
			t = new();
			t.cmd = `WRITE;
			t.addr = vif.addr;
			t.wdata = vif.wdata;
			ap.write(t);
		end
		else if(vif.cmd == `READ) begin//读,先将接口的addr放入trans,等下个周期的数据写入,并发送出去
			t = new();
			t.cmd = `READ;
			t.addr = vif.addr;
			fork
				begin
					@(posedge vif.cl k);
					#10ps;
					t.rdata = vif.rdata;
					ap.write(t);
				end
			join_none//join_none避免错过下一拍的addr
		end
	endtask
endclass: mcdf_bus_monitor
//4.driver:实现总线的驱动和复位
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 8s", req.sprint()), UVM_LOW)
			void'($cast(rsp,req.clone()));
			rsp.set_sequence_id(req.get_sequence_id());//sequence_id
			rsp.set_transaction_id(req.get_transaction_id());//transaction_id
			drive_bus(rsp);//驱动
			seq_item_port.item_done(rsp);
			`uvm_info("DRV", $sformatf("sent a item \n 8s", 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
//5.agent
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的REG硬件实现

`define IDLE 		 2'b00//cmd
`define WRITE 		 2'b01
`define READ 		 2'b10
`define SLV0_RW_ADDR 8'h00//addr
`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

module ctr1_regs(
	clk_i,rstn i,
	cmd_i,cmd_addr_i,cmd_data_i,cmd_data_o,
	s1v0_len_o,s1v1_len_o,s1v2_len_o,
	s1v0_prio_o,s1v1_prio_o,s1v2_prio_o,
	slv0_margin_i,s1v1_margin_i,s1v2_margin_i,
	s1v0_en_o,s1v1_en_o,s1v2_en_o);
	
	input clk_i,rstn_i;//系统信号
	input[1:0] cmd_i;//访问寄存器cmd
	input[7:0] cmd_addr_i;//访问寄存器addr
	input[31:0] cmd_data_i;//访问寄存器data
	input[7:0] slv0_margin_i;//三个chnl表示fifo余量
	input[7:0] slv1_margin i;
	input[7:0] s1v2_margin_i;
	output[31:0] cmd_data_o;//访问寄存器data_out
	//三个输出
	output[2:0] s1v0_len_o;//输出给formatter
	output[2:0]	s1v1_len_o;
	output[2:0] slv2_len_o;
	output[1:0] s1v0_prio_o;//输出给arbiter
	output[1:0] s1v1_prio_o;
	output[1:0] s1v2_prio_o;
	output s1v0_en_o;//输出给chnl
	output s1v1_en_o;
	output s1v2_en_o;
	reg[31:0] regs[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)//对读写寄存器的低6位操作
					`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)//读出6个寄存器的数据
					`SLV0_RW_ADDR: cmd_data_reg <= regs[`SLV0_RW_REG];
					`SLV1_RW_ADDR: cmd_data_reg <= regs[`SLV1_RW_REG];
					`SLV2_RW_ADDR: cmd_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;//每一拍都将寄存器余量反应到只读寄存器里,有1拍延迟
			regs[`SLV1_R_REG][7:0] <= s1v1_margin_i;
			regs[`SLV2_R_REG][7:0] <= s1v2_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 s1v2_len_o = regs[`SLV2_RW_REG][5:3];
assign s1v0_prio_o = regs[`SLV0_RW_REG][2:1];
assign s1v1_prio_o = regs[`SLV1_RW_REG][2:1];
assign s1v2_prio_o = regs[`SLV2_RW_REG][2:1];
assign s1v0_en_o = regs[`SLV0_RW_REG][0];
assign s1v1_en_o = regs[`SLV1_RW_REG][0];
assign s1v2_en_o = regs[`SLV2_RW_REG][0];
endmodule: ctr1_regs

四、Adapter & Predictor实现

4.1 Adapter实现

adapter所完成的桥接功能即是在uvm_reg_bus_op和总线transaction之间的转换。
●实现reg2bus()和bus2reg()两个函数,这两个函数即实现了两种transaction的数据映射。
●如果总线支持byte访问,可以使能supports_byte_enable; 如果总线UVC要返回response数据,则应当使能provides_responses。在本例中,mcdf_bus_driver在读数时会将读回的数据填入到RSP并返回至sequencer,因此需要在adapter中使能provides_responses。 由此使得bus2reg()函数调用时得到的数据是总线返回时的transaction,但如果总线UVC不支持返回RSP(没有调用put_response(RSP)或者item_done(RSP)) ,那么不应该置此位,否则adapter将会使得验证环境挂起。默认情况下,上述的两个成员的复位值都是0。
●uvm_reg_bus_op:
在这里插入图片描述

class reg2mcdf_adapter extends uvm_reg_adapter ;
	
	`uvm_object_utils(reg2mcdf_adapter)//object
	function new(string name = "mcdf_bus_trans");
		super.new(name);
		provides_responses = 1; //有response;driver到sequencer有一个response,继而给到adapter
	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

4.2 Adapter集成

class mcdf_bus_env extends uvm_env ;

	mcdf_bus_agent agent;//bus_agent
	mcdf_rgm rgm; //register_model
	reg2mcdf_adapter reg2mcdf ;//adapter
	`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//get了一个reference model
			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,configure,build
		rgm.map.set_auto_predict();//
		reg2mcdf = reg2mcdf_adapter::type_id::create("reg2mcdf");//adapter在env中
	endfunction
	
	function void connect_phase(uvm_phase phase);
		rgm.map.set_sequencer(agent.sequencer, reg2mcdf);
		//与reg_model中的map做关联,访问到所有的寄存器,map与adapter关联,adapter与sequencer关联,三者关联 
	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

五、前门访问&后门访问

5.1 前门访问

在寄存器模型上做读写操作,最终会通过总线来实现总线上的物理时序访问
1. uvm_reg::read()/write()。 在传递时,用户需要注意将参数path指定为UVM_FRONTDOOR。
2. uvm_reg::read()/write()方法可传入的参数较多,除了status和value两个参数需要传入,其它参数如果不指定,可采用默认值。
3. uvm_reg_sequence::read_reg()/write_reg()。 在使用时,也需要将path指定为UVM_ FRONTDOOR。
class mcdf_example_seq extends uvm_reg_sequence;
	mcdf_rgm 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//1.拿到register_model的句柄
			`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_reg.write(status,'h11, UVM FRONTDOOR, .parent(this));
		rgm.chn10_ctrl_reg.read(status, data, UVM_FRONTDOOR,.parent(this));
		// pre-defined me thods access
		read_reg(rgm.chn11_ctr1_reg,status, data, UVM_FRONTDOOR);
		write_reg(rgm.chn11_ctrl_reg, status, 'h22, UVM_FRONTDOOR);
		read_reg(rgm.chn11_ctrl_reg, status, data, UVM_FRONTDOOR);
	endtask
endclass

5.2 后门访问

利用UVM_DPI(uvm_hdl_read()、uvm_hdl_deposit()、将寄存器的操作直接作用到DUT内的寄存器变量,而不通过物理总线访问。

  1. uvm_reg::read()/write()。
  2. uvm_reg::peek()/poke()。
class mcdf_rgm extends uvm_reg_block;
	...//寄存器成员和map声明
	virtual function build();
		...//寄存器成员和map创建
		//关联寄存器模型和HDL
		add_hdl_path("reg_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_ctr1_reg.add_hdl_path_slice($sformatf("regs [%0d]", `SLV2_RW_REG), 0, 32);
		chn10_stat_reg.add_hdl_path_slice($sformatf("regs [%0d]", `SLV0_R_REG ), 0, 32);
		chn11_stat_reg.add_hdl_path_slice($sformatf("regs [%0d]", `SLV1_R_REG ), 0, 32);
		chn12_stat_reg.add_hdl_path_slice($sformatf("regs [%0d]", `SLV2_R_REG ), 0, 32);
		lock_model();//防止外部修改模型
	endfunction
endclass
class mcdf_example_seq extends uvm_reg_sequence;

	mcdf_rgm rgm;
	`uvm_object_utils(mcdf_examp1e_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(nu11, 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_BACKDOOR,.parent(this));
		rgm.chnl0_ctrl_reg.write(status, 'h11, UVM_BACKDOOR,.parent(this));
		rgm.chn10_ctrl_reg.read(status, data, UVM_BACKDOOR,.parent(this));
		// register model access poke() /peed()
		rgm.chn11_ctrl_reg.peek(status, data, .parent(this));
		rgm.chn11_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_reg, status, data, UVM_BACKDOOR);
		write_reg(rgm.chn12_ctrl_reg, status, 'h22, UVM_BACKDOOR);
		read_reg(rgm.chn12_ctrl_reg, status, data, UVM_BACKDOOR);
		// pre-defined methods peek_reg()/poke_reg()
		peek_reg(rgm.chn12_ctrl_reg,status, data);
		poke_reg(rgm.chn12_ctrl_reg, status, 'h33);
		peek_reg(rgm.chn12_ctrl_reg, status, data);
	endtask
endclass

5.3对比

在这里插入图片描述

六、寄存器各种方法

6.1 mirror、desired、actual value

mirror: 前门观察总线或者后门通过自动预测给出;表示当前硬件的已知状态值
desired:利用寄存器模型修改软件对象值,利用该值更新硬件值
actual: 镜像值可能和actual不一致,状态寄存器的两个值无法保持同步更新;若其他访问寄存器的通路修改了寄存器,但可能没有检测那一路的总线,也会存在无法及时更新

6.2 prediction分类

6.2.1自动预测:uvm_reg_map::set_auto_predict()

  1. 如果用户没有在环境中集成独立的predictor,而是利用寄存器的操作来自动记录每一次寄存器的读写数值,并在后台自动调用predict()方法的话,这种方式被称之为自动预测。
  2. 如果出现了其它一些sequence直接在总线层面上对寄存器进行操作(跳过寄存器级别的write()/read()操作),或者通过其它总线来访问寄存器等这些额外的情况,都无法自动得到寄存器的镜像值和预期值。

6.2.2 显示预测:explicit(更准确)

  1. 在物理总线上通过监视器来捕捉总线事务,并将捕捉到的事务传递给外部例化的predictor,该predictor由UVM参数化类uvm_reg_predictor(component)例化并集成在顶层环境中。
  2. 在集成的过程中需要将adapter与map的句柄也并传递给predictor,同时将monitor采集的事务通过analysis_port接入到predictor一侧。
  3. 这种集成关系可以使得,monitor一旦捕捉到有效事务,会发送给predictor,再由其利用adapter的桥接方法,实现事务信息转换,并将转化后的寄存器模型有关信息更新到map中。
  4. 默认情况下,系统将采用显式预测的方式,这就要求集成到环境中的总线monitor需要具备捕捉事务的功能和对应的analysis_port,以便于同predictor连接。
class mcdf_bus_env extends uvm_env;
	
	mcdf_bus_agent agent;
	mcdf_rgm rgm;
	reg2mcdf_adapter reg2mcdf;
	uvm_reg_predictor #(mcdf_bus_trans) mcdf2reg_predictor;//匹配类型
	`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//获取register model句柄
			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();
		reg2mcdf = reg2mcdf_adapter::type_id::create("reg2mcdf");
		mcdf2reg_predictor = uvm_reg_predictor#(mcdf_bus_trans)::type_id::create( "mcdf2reg_predcitor" ,
		this);//参数类型一致
		mcdf2reg_predictor.map = rgm.map;//map和adapter的指针传入
		mcdf2reg_predictor.adapter = reg2mcdf;
	endfunction
	
	function void connect_phase(uvm_phase phase);
		rgm.map.set_sequencer(agent.sequencer, reg2mcdf);
		agent.monitor.ap.connect(mcdf2reg_predictor.bus_in);//默认的analysis port
	endfunction
endclass

6.3 uvm_reg的访问方法

6.3.1 uvm_reg_block\uvm_reg\uvm_reg_feild方法

在这里插入图片描述

6.3.2 uvm_reg_sequence方法

  1. 对于前门访问的read()和write(),在总线事务完成时,镜像值和期望值才会更新为与总线上相同的值,这种预测方式是显式预测。
  2. 对于后门访问模式下的peek()和poke()以及read()和write(),由于不通过总线,默认采取自动预测的方式,因此在零时刻方法调用返回后,镜像值和期望值也相应修改
    针对寄存器对象的方法
    在这里插入图片描述

6.3.3 reset()/get_reset()

  1. 硬件在复位触发时,会将内部寄存器值复位,而寄存器模型在捕捉到复位事件时,为了保持同硬件行为一致,也应当
    对其复位。这里复位的对象是寄存器模型,而不是硬件。
	@(negedge p_sequencer.vif.rstn) ;
	rgm.reset() ; // register block reset for mirrored value and desired value
	rgm.chn10_ctrl_reg.reset() ; // register level reset
	rgm.chn10_ctr1_reg.pkt_len.reset() ; // register field reset
  1. 在复位之后,可以通过读取寄存器模型的复位值(与寄存器描述文件一致),与前门访问获取的寄存器复位值进行比较,以此来判断硬件各个寄存器的复位值是否按照寄存器描述去实现。这里的get_reset()方法指的也是寄存器模型的复位值,而不是硬件
// register model reset value get and check
rstval = rgm.chn10_ctrl_reg.get_reset();
rgm.chn10_ctrl_reg.read(status, data, UVM_ BACKDOOR, .parent (this));
if (rstval != data)
	`uvm_error ( "RSTERR","reset value read is not the desired reset value")

6.3.3 mirror()

  1. mirror()不会返回读回的数值,但是会将对应的镜像值修改。
  2. 在修改镜像值之前,用户还可以选择是否将读回的值与模型中的原镜像值进行比较。
  3. 对于配置寄存器,可以采用检查上一次的配置是否生效,又或者对于状态寄存器,可以选择只更新镜像值不做比较,这是因为状态寄存器随时可能被硬件内部逻辑修改。
    // get register value and check
    rgm.chn10_ctrl_reg.mirror(status, UVM_CHECK, UVM_FRONTDOOR, .parent(this)) ;//check 在更新镜像值之前,首先将读回的值与上一次镜像值做了比对,随后再更新镜像值

6.3.4 set()和update()对寄存器做批量修改

1.set()方法的对象是寄存器模型自身,通过set()可以修改期望值,而在寄存器配置时先对其模型随机化,再配置个别寄存器或者域,当寄存器的期望值与镜像值不相同时,可以通过update()方法来将不相同的寄存器通过前门门访问或者后门访问的方式做全部修改
2. 这种set()和update()的方式较write()和poke()的写寄存器方式更为灵活的是,它可以实现随机化寄存器配置值(先随机化寄存器模型,后将随机值结合某些域的指定值写入到寄存器), 继而模拟更多不可预知的寄存器应用场景,另外update()强大的批量操作寄存器功能使得修改寄存器更为便捷。

// randomize register mode1, set register/ field value and update to
// hardware actual value
void'(rgm.chnl0_ctrl_reg.randomize());
rgm.chn10_ctr1_reg.pkt_len.set('h3);
rgm.chn10_ctrl_reg.update(status, UVM FRONTDOOR,.parent(this));//对单个reg进行检查和更新
void'(rgm.chn11_ctrl_reg.randomize());
rgm.chn10_ctrl_reg.set('h22);//6个寄存器的desired value进行检查和更新
rgm.update(status, UVM_FRONTDOOR, .parent(this));

6.4 uvm_mem

● UVM寄存器模型也可以用来对存储建模。uvm_mem类可以用来模拟RW (读写)、RO(只读)和WO(只写)类型的存储,并且可以配置存储模型的数据宽度和地址范围。
● uvm_mem没有镜像值和期望值。
● uvm_mem可以提供的功能就是利用自带的方法去访问硬件存储。
● 与uvm_reg相比,uvm_mem不但拥有常规的访问方法read()、write()、peek()和poke(),也提供了burst_read()和burst_write()。busrt操作不但可以更高速通过总线BURST方式连续存储,也是为了贴合实际访问存储中的场景。
● 要实现BURST访问形式,需要考虑下面这些因素:
a. 目前挂载的总线UVC是否支持BURST形式访问,例如APB不能支持BURST访问模式。
b. 与read()、write()方法相比,burst_read()和burst_write()的参数列表中的一项uvm_reg_data_t value[]采用的是数组形式,不再是单一变量,即表示用户可以传递多个数据。而在后台,这些数据首先需要装载到uvm_reg_item对象中,装载时value数组可以直接写入,另外两个成员需要分别指定为element_kind = UVM_MEM,kind = UVM_BURST_READ
c. 在adapter实现中,也需要考虑到存储模型BURST访问的情形,实现四种访问类型的转换,即UVM_READ、UVM_WRITE、UVM_BURST_READ和UVM_BURST_WRITE。
d. 对于UVM_READ和UVM_WRITE的桥接,已经在寄存器模型访问中实现,而UVM_BURST_READ和UVM_BURST_WRITE的转换,往往需要考虑写入的数据长度,例如长度是否是4、8、16或者其它。
e. 对于更为复杂的BURST形式,例如AHB支持WRAP模式,AXI支持out-of-order模式等,如果需要实现更多的协议配置要求,那么推荐直接在总线UVC层面去驱动。这样做的灵活性更大,且更能充分全面的测试存储接口的协议层完备性。

6.5 内建(built-in) sequences

在这里插入图片描述
在这里插入图片描述

class mcdf_example_seq extends uvm_reg_sequence;
	mcdf_rgm 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;
		uvm_reg_hw_reset_seq reg_rst_seq = new();//1.检查寄存器模型复位值与硬件复位值一致性
		uvm_reg_bit_bash_seq reg_bit_bash_seq = new();//2.检查所有支持读写访问的域
		uvm_reg_access_seq reg_acc_seq = new();//3.对所有uvm_reg执行前门写后门读和后门写前门读,寄存器地址映射关系
		if(!uvm_config_db#(mcdf_rgm)::get(null, get_full_name() ,"rgm", rgm)) begin//get register model句柄
			`uvm_error("GETRGM", "no top-down RGM handle is assigned" )
		end
		// wait reset asserted and release
		@(negedge p_sequencer.vif.rstn);
		@(posedge P_sequencer.vif.rstn);
		`uvm_info("BLTINSEQ", "register reset sequence started", UVM_LOW)
		reg_rst_seq.model = rgm;//传入reg_block句柄
		reg_rst_seq.start(m_sequencer);//开始执行,sequencer为bus_sequencer
		`uvm_info("BLTINSEQ", "register reset sequence finished", UVM_LOW)
		`uvm_info("BLTINSEQ","register bit bash sequence started" ,UVM_LOW)
		// reset hardware register and register model
		reg_bit_bash_seq.model = rgm;
		reg_bit_bash_seq.start(m_sequencer);
		`uvm_info("BLTINSEQ", "register bit bash sequence finished", UVM_LOW)
		`uvm_info("BLTINSEQ", "register access sequence started", UVM_LoW)
		// reset hardware register and register model
		reg_acc_seq.model = rgm;
		reg_acc_seq.start(m_sequencer);
		`uvm_info("BLTINSEQ", "register access sequence finished", UVM_LOW)
	endtask
endclass

6.5 排除寄存器

在做测试时,时钟等寄存器不能直接写01,需要排除出去

class mcdf_rgm extends uvm_reg_block;
	virtual function build();
		// disable built-in seq attributes
		uvm_resource_db# (bit)::set({"REG::",this.chn10_stat_reg.get_full_name() } ,"NO REG ACCESS_TEST" ,1);//排除状态寄存器
		uvm_resource_db# (bit)::set({"REG::",this.chn11_stat_reg.get_fu11_name() } ,"NO_REG_ACCESS_TEST" ,1);
		uvm_resource_db# (bit)::set({"REG::",this.chn12_stat_reg.get_full_name() } ,"NO_REG ACCESS_TEST" ,1);
	endfunction
endclass

七、寄存器应用

覆盖率收集
get_coverage(UVM_CVR_FIELD_VALS)//是否允许覆盖率采样
has_coverage(UVM_CVR_FIELD_VALS)//has_coverage有条件例化covergroup

7.1 覆盖率自动收集

class ctrl_reg extends uvm_reg;

	`uvm_object_utils(ctrl_reg)
	uvm_reg_field reserved;
	rand uvm_reg_field pkt_len; 
	rand uvm_reg_field prio_leve1;
	rand uvm_reg_field chnl_en; 
	
	covergroup value_cg;
		option.per_instance = 1;
		//4个field的coverpoint关心的value
		//reserved: coverpoint reserved.value[25:0];
		pkt_len: coverpoint pkt_len.value[2:0];
		prio_level: coverpoint prio_level.value[1:0];
		chn1_en: coverpoint chnl_en.value[0:0];
	endgroup
	
	function new(string name = "ctrl_reg");
		super.new(name,32, UVM_CVR_ALL);
		set_coverage(UVM_CVR_FIELD_VALS);
		if(has_coverage(UVM_CVR_FIELD_VALS)) begin//has_coverage有条件例化covergroup
			value_cg = new();
		end
	endfunction
	
	virtual function build();
		reserved = uvm_reg_field::type_id::create("reserved");
		pkt_len = uvm_reg_field::type_id::create("pkt_len");
		prio_level = uvm_reg_field::type_id::create("prio_level");
		chnl_en = uvm_reg_field::type_id::create("chnl_en");
		reserved.configure(this, 26,6,"RO", 0,26'h0,1, 0,0);
		pkt_len.configure(this, 3,3,"RW", 0,3'h0,1,1, 0);
		prio_level.configure(this, 2, 1,"RW", 0, 2'h3,1, 1, 0);
		chnl_en.configure(this, 1, 0,"RW", 0,1'h1,1, 1, 0);
	endfunction
	//采样部分,实现的时候形式要保持一致
	//read()\write()方法的回调函数,需要自己填充使之自动采样数据
	//read()\write()->sample->sample_value->coverage
	function void sample(
		uvm_reg_data_t data,
		uvm_reg_data_t byte_en,
		bit	is_read,
		uvm_reg_map	map);
		super.sample(data, byte_en, is_read, map);
		sample_values();
	endfunction
	//供外部调用的方法
	function void sample_values();//采样的值
		super.sample_values();
		if(get_coverage(UVM_CVR_FIELD_VALS)) begin//是否允许覆盖率采样
			value_cg.sample();
		end
	endfunction
endclass

7.2 自定义covergroup进行覆盖率收集

class mcdf_coverage extends uvm_subscriber #(mcdf bus trans);//只监听monitor
	mcdf_rgm rgm;
	`ucm_component_utils(mcdf_coverage)
	
	covergroup reg_value_cg;
		option.per_instance = 1;
		CH0LEN: coverpoint rgm.chnl0_ctrl_reg.pkt_len.value[2:0] {bins len[] =	{0,1,2,3,[4:7]};}//mirror value
		CH0PRI: coverpoint rgm.chn10_ctrl_reg.prio_level.value[1:0];
		CH0CEN: coverpoint rgm.chnl0_ctrl_reg.chnl_en.value[0:0];
		CH1LEN: coverpoint rgm.chnll_ctrl_reg.pkt len.value[2:0] {bins len[] =	{0,1,2,3,[4:7]};}
		CH1PRI: coverpoint rgm.chnl1_ctrl_reg.prio_level.value[1:0];
		CH1CEN: coverpoint rgm.chnl1_ctrl_reg.chnl_en.value[0:0];
		CH2LEN: coverpoint rgm.chn12__ctrl_reg.pkt_len.value[2:0] {bins len[]=	{0,1,2,3,[4:7]};}
		CH2PRI: coverpoint rgm.chn12_ctrl_reg.prio_level.value[1:0];
		CH2CEN: coverpoint rgm.chn12_ctrl_reg.chnl_en.value[0:0];
		CH0AVL: coverpoint rgm.chn10_stat_reg.fifo_avail.value[7:0] {bins avail[] = {0,1,[2:7], [8:55], [56:61], 62, 63};}//7个bin
		CH1AVL: coverpoint rgm.chn11_stat_reg.fifo_avail.value[7:0] {bins avail[] = {0,1,[2:7], [8:55], [56:61], 62, 63};}
		CH2AVL: coverpoint rgm.chn12_stat_reg.fifo_avail.value[7:0] {bins avail[] = {0,1,[2:7], [8:55], [56:61], 62, 63};}
		LEN_COMB: cross CH0LEN, CH1LEN, CH2LEN;
		PRI_COMB: cross CH0PRI, CH1PRI, CH2PRI;
		CEN COMB: cross CH0CEN, CH1CEN, CH2CEN;
		AVL_COMB: cross CH0AVL, CH1AVL, CH2AVL;
	endgroup
	
	function new(string name, uvm_component parent);
		super.new(name,parent);
		reg_value_cg = new();
	endfunction
	
	function void build_phase(uvm_phase phase);
		if(!uvm_config_db#(mcdf_rgm)::get(this, "","rgm", rgm)) begin
			`uvm_info("GETRGM", "no top-down RGM handle is assigned", UVM_LOW)
		end
	endfunction
	
	function void write(T t);//有analysis port,要实现write
		reg_value_cg.sample();//bus_monitor写入后,进行采样
	endfunction
endclass

7 创建寄存器.raif文件

表单->.ralf->.sv->集成到环境中,调用
为tcl格式

7.1 表单

在这里插入图片描述

7.2 .ralf文件语法

在这里插入图片描述
在这里插入图片描述

7.3 生成.sv

用ralgen将RAL文件内容转换为UVM注册类:
ralgen -uvm -t host_regmodel host.ralf
生成ral_host_regmodel.sv

7.4 集成到环境中

7.4.1 uvm_reg_sequence

class host_ral_sequence extends uvm_reg_sequence #(uvm_sequence #(host_data));//1.父类为uvm_reg_sequence
  ral_block_host_regmodel regmodel;//2.声明
  
  `uvm_object_utils(host_ral_sequence)

  function new(string name = "host_ral_sequence");
    super.new(name);
    `uvm_info("TRACE", $sformatf("%m"), UVM_HIGH);
  endfunction

  virtual task body();
    uvm_status_e status;
    uvm_reg_data_t data;
    `uvm_info("TRACE",$sformatf("%m"),UVM_HIGH)

    if (starting_phase != null)
       starting_phase.raise_objection(this);
    
    regmodel.PORT_LOCK.read(.status(status), .value(data), .path(UVM_FRONTDOOR), .parent(this));//3.调用,可以不用输入地址了
    `uvm_info("RAL READ", $sformatf("PORT_LOCK= %2h",data),UVM_MEDIUM)
    regmodel.PORT_LOCK.write(.status(status), .value('1), .path(UVM_FRONTDOOR), .parent(this));
    `uvm_info("RAL WRITE", $sformatf("PORT_LOCK= %2h",'1),UVM_MEDIUM)
    regmodel.PORT_LOCK.read(.status(status), .value(data), .path(UVM_FRONTDOOR), .parent(this));
    `uvm_info("RAL READ", $sformatf("PORT_LOCK= %2h",data),UVM_MEDIUM)

    if (starting_phase != null)
       starting_phase.drop_objection(this);
 endtask
endclass

7.4.2 uvm_env 创建实例

//build
if (regmodel == null)
	begin//create
	string hdl_path;//for backdoor
	if(!uvm_config_db#(string)::get(this,"","hdl_path", hdl_path)) begin
		`uvm_warning("HOSTCFG", "HDL path for backdoor is not set");
	end 
	regmodel = ral_block_host_regmodel::type_id::create("regmodel", this);//instance
	regmodel.build();//instance reg_block;
	regmodel.lock_model();//Lock and create map
	regmodel.set_hdl_path_root(hdl_path);//config
	regmodel.reset();//reset mirror and desired value
end
//connect
reg_adapter adapter;
adapter = reg_adapter::type_id::create("adapter", this);
regmodel.default_map.set_sequencer(h_agent.seqr, adapter);
  • 5
    点赞
  • 41
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小鱼爱学习,每天好心情

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值