UVM实战 卷I学习笔记10——UVM中的寄存器模型(2)


简单的寄存器模型

只有一个寄存器的寄存器模型

为DUT建立寄存器模型:这个DUT非常简单,只有一个寄存器invert。要为其建造寄存器模型, 首先要从uvm_reg派生一个invert类

class reg_invert extends uvm_reg;
	rand uvm_reg_field reg_data;
	virtual function void build();
		reg_data = uvm_reg_field::type_id::create("reg_data");
		// parameter: parent, size, lsb_pos, access, volatile, reset value, 
		//has_reset, is_rand, individually accessible
		reg_data.configure(this, 1, 0, "RW", 1, 0, 1, 1, 0);
endfunction
	`uvm_object_utils(reg_invert)
	function new(input string name="reg_invert");
		//parameter: name, size, has_coverage
		super.new(name, 16, UVM_NO_COVERAGE);
	endfunction
endclass

new函数中要将invert寄存器的宽度作为参数传递给super.new函数。这里的宽度并不是指这个寄存器的有效宽度,而是指这个寄存器中总共的位数。如对于一个16位的寄存器,其中可能只使用了8位,那么这里要填写的是16。这个数字一般与系统总线的宽度一致。super.new中另一个参数是是否要加入覆盖率的支持,这里选择UVM_NO_COVERAGE,即不支持。

每个派生自uvm_reg的类都有一个build,这与uvm_component的build_phase并不一样,它不会自动执行而需手工调用所有uvm_reg_field都在这里实例化。当reg_data实例化后,要调用data.configure函数来配置这个字段

  • 第一个参数就是此域(uvm_reg_field)的父辈,也即此域位于哪个寄存器中,这里是填写this。
  • 第二个参数是此域的宽度,由于DUT中invert的宽度为1,所以这里为1。
  • 第三个参数是此域的最低位在整个寄存器中的位置,从0开始计数。假如一个寄存器如下图所示,其低3位和高5位没有使用,只有一个有效宽度为8位的字段,那么在调用configure时第二个参数就要填写8,第三个参数则要填写3,因为此reg_field是从第4位开始的。
    在这里插入图片描述
  • 第四个参数表示此字段的存取方式。 UVM共支持如下25种存取方式:(需要时搜索即可)
    1) RO: 读写此域都无影响。
    2) RW: 会尽量写入, 读取时对此域无影响。
    3) RC: 写入时无影响, 读取时会清零。
    4) RS: 写入时无影响, 读取时会设置所有的位。
    5) WRC: 尽量写入, 读取时会清零。
    6) WRS: 尽量写入, 读取时会设置所有的位。
    7) WC: 写入时会清零, 读取时无影响。
    8) WS: 写入时会设置所有的位, 读取时无影响。
    9) WSRC: 写入时会设置所有的位, 读取时会清零。
    10) WCRS: 写入时会清零, 读取时会设置所有的位。
    11) W1C: 写1清零, 写0时无影响, 读取时无影响。
    12) W1S: 写1设置所有的位, 写0时无影响, 读取时无影响。
    13) W1T: 写1入时会翻转, 写0时无影响, 读取时无影响。
    14) W0C: 写0清零, 写1时无影响, 读取时无影响。
    15) W0S: 写0设置所有的位, 写1时无影响, 读取时无影响。
    16) W0T: 写0入时会翻转, 写1时无影响, 读取时无影响。
    17) W1SRC: 写1设置所有的位, 写0时无影响, 读清零。
    18) W1CRS: 写1清零, 写0时无影响, 读设置所有位。
    19) W0SRC: 写0设置所有的位, 写1时无影响, 读清零。
    20) W0CRS: 写0清零, 写1时无影响, 读设置所有位。
    21) WO: 尽可能写入, 读取时会出错。
    22) WOC: 写入时清零, 读取时出错。
    23) WOS: 写入时设置所有位, 读取时会出错。
    24) W1: 在复位( reset) 后, 第一次会尽量写入, 其他写入无影响, 读取时无影响。
    25) WO1: 在复位后, 第一次会尽量写入, 其他的写入无影响, 读取时会出错。

寄存器的种类多种多样,如上25种存取方式有时并不能满足用户的需求,这时就需要自定义寄存器的模型。(有点离谱)

  • 第五个参数表示是否是易失的(volatile),这个参数一般不会使用。
  • 第六个参数表示此域上电复位后的默认值
  • 第七个参数表示此域是否有复位,一般的寄存器或者寄存器的域都有上电复位值,因此这里一般也填写1。
  • 第八个参数表示此域是否可以随机化。这主要用于对寄存器进行随机写测试,如果选择0,那么此域将不会随机化而一直是复位值,否则将会随机出一个数值。该参数当且仅当第四个参数为RW、WRC、WRS、WO、W1、WO1时才有效。
  • 第九个参数表示此域是否可以单独存取

定义好此寄存器后,需要在一个由reg_block派生的类中将其实例化:

class reg_model extends uvm_reg_block;
	rand reg_invert invert;
	virtual function void build();
		default_map = create_map("default_map", 0, 2, UVM_BIG_ENDIAN, 0);
		invert = reg_invert::type_id::create("invert", , get_full_name());
		invert.configure(this, null, "");
		invert.build();
		default_map.add_reg(invert, 'h9, "RW");
	endfunction
	`uvm_object_utils(reg_model)
	function new(input string name="reg_model");
		super.new(name, UVM_NO_COVERAGE);
	endfunction
endclass
  • 同uvm_reg派生的类一样,每个由uvm_reg_block派生的类也要定义build函数实现所有寄存器的实例化
  • 一个uvm_reg_block中一定要对应一个uvm_reg_map,系统已经有声明好的default_map,只需要在build中将其实例化。这个实例化的过程并不是直接调用uvm_reg_map的new函数,而是通过调用uvm_reg_block的create_map来实现,create_map有众多的参数:名字、基地址、系统总线的宽度(单位是byte)、大小端,最后一个参数表示是否能够按照byte寻址
  • 随后实例化invert并调用invert.configure函数。这个函数的主要功能是指定寄存器进行后门访问操作时的路径。第一个参数是此寄存器所在uvm_reg_block的指针,这里填写this,第二个是reg_file的指针,这里暂时填写null,第三个是此寄存器的后门访问路径,这里暂且为空。
  • 当调用完configure时,需要手动调用invert的build函数,将invert中的域实例化
  • 最后一步是将此寄存器加入default_map中uvm_reg_map的作用是存储所有寄存器的地址, 因此必须将实例化的寄存器加入default_map中,否则无法进行前门访问操作。add_reg函数的三个参数分别是:要加入的寄存器、寄存器的地址(这里是16’h9)、此寄存器的存取方式。到此为止, 一个简单的寄存器模型已经完成。

总结:

  • uvm_reg_field是最小的单位,是具体存储寄存器数值的变量可直接用这个类。
  • uvm_reg是一个“空壳子”,或用专业名词来说它是一个纯虚类,因此是不能直接使用的,必须由其派生一个新类,在这个新类中至少加入一个uvm_reg_field,然后这个新类才可以使用
  • uvm_reg_block是用于组织大量uvm_reg的大容器
  • 打个比方:uvm_reg是个小瓶子,必须装上药丸(uvm_reg_field)才有意义,装药丸的过程就是定义派生类的过程,而uvm_reg_block是个大箱子,可以放许多小瓶子(uvm_reg),也可以放其他稍微小一点的箱子(uvm_reg_block)。整个寄存器模型就是一个大箱子(uvm_reg_block)。

*将寄存器模型集成到验证平台中

寄存器模型的前门访问方式工作流程如图所示,其中图a为读操作,图b为写操作:
在这里插入图片描述
寄存器模型的前门访问操作分为读和写两种。无论是读或写,寄存器模型都会通过sequence产生一个uvm_reg_bus_op的变量,此变量存储操作类型(读还是写)和操作的地址,如果是写操作,还有要写入的数据。此变量中的信息要经过转换器(adapter)转换后交给bus_sequencer,随后交给bus_driver,由其实现最终的前门访问读写操作

因此,必须要定义好一个转换器。如下例为一个简单的转换器的代码:

class my_adapter extends uvm_reg_adapter;
	string tID = get_type_name();
	`uvm_object_utils(my_adapter)
	function new(string name="my_adapter");
		super.new(name);
	endfunction : new
	function uvm_sequence_item reg2bus(const ref uvm_reg_bus_op rw);
		bus_transaction tr;
		tr = new("tr");
		tr.addr = rw.addr;
		tr.bus_op = (rw.kind == UVM_READ) ? BUS_RD: BUS_WR;
		if (tr.bus_op == BUS_WR)
			tr.wr_data = rw.data;
		return tr;
	endfunction : reg2bus
	function void bus2reg(uvm_sequence_item bus_item, ref uvm_reg_bus_op rw);
		bus_transaction tr;
		if(!$cast(tr, bus_item)) begin
			`uvm_fatal(tID,
			"Provided bus_item is not of the correct type. Expecting bus_trans action")
			return;
		end
		rw.kind = (tr.bus_op == BUS_RD) ? UVM_READ : UVM_WRITE;
		rw.addr = tr.addr;
		rw.byte_en = 'h3;
		rw.data = (tr.bus_op == BUS_RD) ? tr.rd_data : tr.wr_data;
		rw.status = UVM_IS_OK;
	endfunction : bus2reg
endclass : my_adapter

转换器要定义好两个函数:reg2bus——将寄存器模型通过sequence发出的uvm_reg_bus_op型的变量转换成bus_sequencer能够接受的形式bus2reg——当监测到总线上有操作时,将收集的transaction转换成寄存器模型能够接受的形式,以便寄存器模型能够更新相应寄存器的值

寄存器模型发起读操作的数值如何返回给寄存器模型:由于总线的特殊性,bus_driver驱动总线进行读操作时能获取要读的数值,如果它将此值放入从bus_sequencer获得的bus_transaction中时,那么bus_transaction中就会有读取的值,此值经过adapter的bus2reg函数的传递,最终被寄存器模型获取,过程如图a所示。由于没有实际的transaction传递,所以从driver到adapter使用了虚线。

转换器写好之后,就可以在base_test中加入寄存器模型了:

class base_test extends uvm_test;
	my_env env;
	my_vsqr v_sqr;
	reg_model rm;
	my_adapter reg_sqr_adapter;
	…
endclass
	function void base_test::build_phase(uvm_phase phase);
		super.build_phase(phase);
		env = my_env::type_id::create("env", this);
		v_sqr = my_vsqr::type_id::create("v_sqr", this);
		rm = reg_model::type_id::create("rm", this);
		//4 processes
		rm.configure(null, "");
		rm.build();
		rm.lock_model();
		rm.reset();
		reg_sqr_adapter = new("reg_sqr_adapter");
		env.p_rm = this.rm;
	endfunction
	function void base_test::connect_phase(uvm_phase phase);
		super.connect_phase(phase);
		v_sqr.p_my_sqr = env.i_agt.sqr;
		v_sqr.p_bus_sqr = env.bus_agt.sqr;
		v_sqr.p_rm = this.rm;
		rm.default_map.set_sequencer(env.bus_agt.sqr, reg_sqr_adapter);
		rm.default_map.set_auto_predict(1);
	endfunction

要将寄存器模型集成到base_test中,至少需要在base_test中定义两个成员变量:reg_model和reg_sqr_adapter。将所有用到的类在build_phase中实例化。实例 化后reg_model还要做四件事:

  • 第一是调用configure函数,其第一个参数是parent block,由于是最顶层的reg_block,因此填写null,第二个是后门访问路径,这里传入一个空的字符串;
  • 第二是调用build函数将所有寄存器实例化
  • 第三是调用lock_model函数,调用此函数后,reg_model中就不能再加入新的寄存器了
  • 第四是调用reset函数,如果不调用此函数,那么reg_model中所有寄存器的值都是0,调用此函数后,所有寄存器的值都将变为设置的复位值

寄存器模型的前门访问操作最终都由uvm_reg_map完成,因此在connect_phase中需要将转换器和bus_sequencer通过set_sequencer函数告知reg_model的default_map,并将default_map设置为自动预测状态

*在验证平台中使用寄存器模型

当寄存器模型被建立好后可以在sequence和其他component中使用。以在参考模型中使用为例,需要在参考模型中有一个寄存器模型的指针:

class my_model extends uvm_component;
	…
	reg_model p_rm;
	…
endclass

在base_test代码中已经为env的p_rm赋值,因此只需要在env中将p_rm传递给参考模型即可:

function void my_env::connect_phase(uvm_phase phase);
	…
	mdl.p_rm = this.p_rm;
endfunction

对于寄存器,寄存器模型提供了两个基本的任务:read和write。若要在参考模型中读取寄存器,使用read任务:

task my_model::main_phase(uvm_phase phase);
	my_transaction tr;
	my_transaction new_tr;
	uvm_status_e status;
	uvm_reg_data_t value;
	super.main_phase(phase);
	p_rm.invert.read(status, value, UVM_FRONTDOOR);
	while(1) begin
		port.get(tr);
		new_tr = new("new_tr");
		new_tr.copy(tr);
		//`uvm_info("my_model", "get one transaction, copy and print it:", UV M_LOW)
		//new_tr.print();
		if(value)
			invert_tr(new_tr);
		ap.write(new_tr);
	end
endtask

read任务的原型如下所示:

extern virtual task read(output uvm_status_e status,
						output uvm_reg_data_t value,
						input uvm_path_e path = UVM_DEFAULT_PATH,
						input uvm_reg_map map = null,
						input uvm_sequence_base parent = null,
						input int prior = -1,
						input uvm_object extension = null,
						input string fname = "",
						input int lineno = 0);

它有多个参数,常用的是前三个参数。第一个是uvm_status_e型的变量(输出),用于表明读操作是否成功;第二个是读取的数值(输出);第三个是读取的方式,可选UVM_FRONTDOOR和UVM_BACKDOOR

由于参考模型一般不会写寄存器,因此对于write任务,以在virtual sequence进行写操作为例说明。在sequence中使用寄存器模型,通常通过p_sequencer的形式引用。需要首先在sequencer中有一个寄存器模型的指针,在上面base_test代码中已经为v_sqr.p_rm赋值了。因此可以直接以如下方式进行写操作:

class case0_cfg_vseq extends uvm_sequence;
	…
	virtual task body();
		uvm_status_e status;
		uvm_reg_data_t value;
		…
		p_sequencer.p_rm.invert.write(status, 1, UVM_FRONTDOOR);
		…
	endtask
endclass

write任务的原型为:

extern virtual task write(output uvm_status_e status,
						input uvm_reg_data_t value,
						input uvm_path_e path = UVM_DEFAULT_PATH,
						input uvm_reg_map map = null,
						input uvm_sequence_base parent = null,
						input int prior = -1,
						input uvm_object extension = null,
						input string fname = "",
						input int lineno = 0);

其参数有很多个,与read类似,常用的只有前三个。第一个为uvm_status_e型的变量(输出),用于表明写操作是否成功。第二个要写的值(输入),第三个是写操作的方式,可选UVM_FRONTDOOR和UVM_BACKDOOR

寄存器模型对sequence的transaction类型没有任何要求。因此,可以在一个发送my_transaction的sequence中使用寄存器模型对寄存器进行读写操作。

  • 1
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
UVM提供了uvm_reg_backdoor类,用于在测试访问寄存器的内部实现。这个类可以让我们在测试使用不同的方式来访问寄存器,以验证寄存器的功能和性能。 uvm_reg_backdoor类主要有两个方法: - `void read(uvm_reg_item rw)`:读取寄存器的值,将结果存储在rw.value。 - `void write(uvm_reg_item rw)`:写入寄存器的值,将值存储在rw.value。 其,`uvm_reg_item`是一个包含寄存器地址、写入/读取值等信息的uvm序列化对象。 要使用uvm_reg_backdoor类,我们需要创建一个新类,继承自uvm_reg_backdoor。在新类的构造函数,我们需要调用基类的构造函数,并通过该函数将要访问的寄存器作为参数传递。 下面是一个使用uvm_reg_backdoor类的示例: ```systemverilog class my_reg_backdoor extends uvm_reg_backdoor; `uvm_object_utils(my_reg_backdoor) function new(string name = "my_reg_backdoor"); super.new(name); endfunction virtual function void read(uvm_reg_item rw); // 从寄存器读取值 endfunction virtual function void write(uvm_reg_item rw); // 将值写入寄存器 endfunction endclass ``` 在测试,我们可以使用uvm_reg_backdoor类的实例来访问寄存器。例如: ```systemverilog my_reg_backdoor my_bd = new; uvm_reg_item rw = new; rw.element = my_reg; rw.kind = UVM_REG; rw.path = UVM_FRONTDOOR; rw.offset = 0; rw.value[0] = 0x1234; my_bd.write(rw); // 从寄存器读取值 my_bd.read(rw); $display("value = %h", rw.value[0]); ``` 使用uvm_reg_backdoor类可以方便地访问寄存器的内部实现,从而进行更全面和深入的验证。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值