寄存器模型

这篇具有很好参考价值的文章主要介绍了寄存器模型。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

寄存器模型的概念

寄存器配置总线:通过控制端口,配置DUT中的寄存器,DUT可以根据寄存器的值来改变其行为。

uvm_reg_field:寄存器模型中最小的单位是具体存储寄存器数值的变量。
uvm_reg:比uvm_reg_field高一个级别,但依然是比较小的单位。下图为uvm_reg_field与uvm_reg_的关系:
寄存器模型

uvm_reg_block:一个比较大的单位,在其中可以加入许多的uvm_reg,也可以加入其他的uvm_reg_block,一个寄存器模型中至少包含一个uvm_reg_block。
uvm_reg_map:每个寄存器在加入寄存器模型时都有其地址,uvm_reg_map就是存储这些地址,并将其转换成可以访问的物理地址。

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

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");
		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");
		super.new(name, 16, UVM_NO_COVERAGE);
	endfunction
endclass
  • new函数中的位数一般与系统总线的宽度一致,另外一个参数为是否要加入覆盖率的支持,这里选择不支持。
  • 每一个派生自uvm_reg的类都有一个build,所有的uvm_reg_field都在这里实例化,当reg_data实例化后,要调用reg_data.configure函数来配置这个字段。
    定义好这个寄存器后,需要在一个由reg_block派生的类中将其实例化:
class reg_model enxtends 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");
	endfucntion
	`uvm_object_utils(reg_model)
	function new(input string name = "reg_model");
		super.new(name, UVM_NO_COVERAGE);
	endfunction
endclass

同reg_reg派生的类一样,每一个由uvm_reg_block派生的类也要定义一个build函数,一般在此函数中实现所有寄存器的例化。

  • 一个uvm_reg_block中一定要对应一个uvm_reg_map,通过调用uvm_reg_block的create_map在build中将其实例化。create_map的参数中,第一个参数是名字,第二个参数是基地址,第三个参数是系统总线的宽度(byte),第四个参数是大小端,最后一个参数表示是否能够按照byte进行寻址。
  • 随后实例化invert并调用invert.configure函数。这个函数的主要功能是指定寄存器进行后门访问操作时的路径。第一个参数是此寄存器所在uvm_reg_block指针,这里填写this;第二个参数是reg_file的指针;第三个参数是此寄存器的后门访问路径,这里暂且为空。当调用完configure时,需要手动调用invert的build函数,将invert中的域实例化。
  • 最后一步是将此寄存器加入default_map中,uvm_reg_map的作用是存储所有寄存器的地址,因此必须将实例化的寄存器加入default_map中,否则无法进行前门访问操作。add_reg函数的第一个参数是要加入的寄存器,第二个参数是寄存器的地址,第三个参数是此寄存器的存取方式。

寄存器模型的集成

寄存器模型的前门访问操作可以分成读和写两种。无论是读或写,寄存器模型都会通过sequence产生一个uvm_reg_bus_op的变量,此变量中存储着操作类型(读还是写)和操作的地址,如果是写操作,还会有要写入的数据。此变量中的信息要经过一个转换器(adapter)转换后交给bus_sequencer,随后交给bus_driver,由bus_driver实现最终的前门访问读写操作。因此,必须要定义一个adapter。
寄存器模型

在adapter中需要定义两个函数:

  • reg2bus:将寄存器模型通过sequence发出的uvm_reg_bus_op型的变量转换成bus_sequencer能够接受的形式。
  • bus2reg:当监测到总线上有操作时,将收集来的transaction转换成寄存器模型能够接受的形式。
    定义好adapter后,在base_test中加入寄存器模型:
class base_test extends uvm_test;
	my_env 			env;
	my_vsqr 		v_srq;
	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::typr_id::create("v_sqr", this);
	rm = reg_model::typr_id::create("rm", this);
	rm.configure(null, " ");
	rm.build();
	rm.lock_build();
	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_predoct(1);
endfunction

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

  1. 调用configure函数,第一个参数是parent block,由于是最顶层的reg_block,因此填null;第二个参数是后门访问路径,这里传入一个空的字符串。
  2. 调用build函数,将所有的寄存器实例化。
  3. 调用lock_model函数,调用此函数后,reg_model中就不能再加入新的寄存器了。
  4. 调用reset函数,如果不调用此函数,那么reg_model中所有寄存器的值都是0。调用此函数后,所有寄存器的值都将变为设置的复位值。
    寄存器模型的前门访问操作最终都将由uvm_reg_map完成,因此在connect_phase中,需要将adapter和bus_sequencer通过set_sequencer函数告知reg_model的default_map,并将default_map设置为自动预测状态。

寄存器模型的使用

寄存器模型提供了read和write两个task,对于read:

p_rm.invert.read(status, value, UVM_FRONTDOOR);

read的第一个参数为uvm_status_e型的变量,作为一个输出,其用于表明读操作是否成功;第二个参数是读取的数值;第三个是读取的方式,可选UVM_FRONTDOOR和UVM_BACKDOOR。
对于write:

p_sequencer.p_rm.invert.write(status, 1, UVM_FRONTDOOR);

第一个参数也是uvm_status_e型的变量,用于表明写操作是否成9功;第二个参数是要写的值;第三个参数是写操作的方式,同样可选UVM_FRONTDOOR和UVM_BACKDOOR。
寄存器模型对sequence的transaction类型没有任何要求,所以可以在一个发送my_transaction的sequence中使用寄存器模型对寄存器进行读写操作。

后门访问与前门访问

  • 前门访问:通过寄存器配置总线来对DUT进行操作。在这个过程中,仿真时间是一直往前走的。
    通过adapter的bus2reg及reg2bus,可以实现uvm_reg_item与目标transaction的转换,以读操作为例,完整的流程为:
  1. 参考模型调用寄存器模型的读任务。
  2. 寄存器模型产生sequence,并产生uvm_reg_item:rw。
  3. 产生driver能够接受的transaction:bus_req=adapter.reg2bus(rw)。
  4. 把bus_req交给bus_sequencer。
  5. driver得到bus_req后驱动它,得到读取的值,并将读取值放入bus_req中,调用item_done。
  6. 寄存器模型调用adapter.bus2reg(bus_req,rw)将bus_req中的读取值传递给rw。
  7. 将rw中的读数据返回参考模型。
  • 后门访问:不通过总线进行读写操作,而是直接通过层次化的引用来改变寄存器的值。所有的后门访问都是不消耗仿真时间而只消耗运行时间的。

    可以使用interface以及DPI+VPI的方式来进行后门访问。
    UVM中使用DPI+VPI的方式来进行后门访问操作,它大体的流程是:
    1)在建立寄存器模型时将路径参数设置好。
    2)在进行后门访问的写操作时,寄存器模型调用uvm_hdl_deposit函数。
    3)进行后门访问的读操作时,调用uvm_hdl_read函数,在C/C++侧,此函数内部会调用vpi_get_value函数来对DUT中的寄存器进行读操作,并将读取值返回。

    在使用寄存器模型的后门访问功能时,需要做如下准备:
    (1)在reg_block中调用uvm_reg的configure函数时,需要设置好第三个参数:

class reg_model extends uvm_reg_block;
	rand reg_invert invert;
	rand reg_counter_high counter_high;
	rand reg_counter_low counter_low;
	virtual function void build();
		invert.configure(this, null, "invert");
		counter_high.configure(this, null, "counter[31:16]");
		counter_low.configure(this, null, "counter[15:0]");
	endfunction
endclass

(2)在将寄存器模型集成到验证平台时,需要设置好根路径hdl_root:

function void base_test::build_phase(uvm_phase phase);
...
	rm = reg_model::type_id::create("rm", this);
	rm.configure(null, " ");
	rm.build();
	rm.lock_model();
	rm.reset();
	rm.set_hdl_path_root("top_tb.my_dut");
...
endfunction

UVM会提供两类后门访问的函数,一类是read和write,一类是peek和poke,区别在于:第一类在进行操作时会模仿DUT的行为,第二类则完全不管DUT的行为。例如,对一个只读寄存器进行写操作,第一类由于要模拟DUT的只读行为所以写不进去,这时就可以用第二类去写。

p_sequencer.p_rm.counter_low.poke(status, 16'hFFFD);
p_sequencer.p_rm.counter_low.peek(status, value);

poke和peek的第一个参数表示操作是否成功,第二个参数表示读写的数据。

复杂的寄存器模型

层次化的寄存器模型

一般只会在第一级的uvm_reg_block中加入寄存器,而第二级的uvm_reg_block通常只添加uvm_reg_block,这样从整体上就能呈现出如下图中比较清晰的结构。
寄存器模型
例如,一个DUT分了三个子模块:用于控制全局的global模块、用于缓存数据的buf模块、用于接收发送以太网帧的mac模块。global模块寄存器的地址为0x0000~0x0FFF,buf部分的寄存器地址为0x1000~0x1FFF,mac部分的寄存器地址为0x2000~0x2FFF,那么可以按照如下方式定义寄存器模型:

class reg_model extends uvm_reg_block;

	rand global_blk gb_ins;
	rand buf_blk bb_ins;
	rand mac_blk mb_ins;
	virtual function void build();
		default_map = create_map("default_map", 0, 2, UVM_BIG_ENDIAN, 0);
		gb_ins = global_blk::type_id::create("gb_ins");
		gb_ins.configure(this, "");
		gb_ins.build();
		gb_ins.lock_model();
		default_map.add_submap(gb_ins.default_map, 16'h0);

		bb_ins = buf_blk::type_id::create("bb_ins");
		bb_ins.configure(this, "");
		bb_ins.build();
		bb_ins.lock_model();
		default_map.add_submap(bb_ins.default_map, 16'h1000);

		mb_ins = mac_blk::type_id::create("mb_ins");
		mb_ins.configure(this, "");
		mb_ins.build();
		mb_ins.lock_model();
		default_map.add_submap(mb_ins.default_map, 16'h2000);

	endfunction

	`uvm_object_utils(reg_model)

	function new(input string name="reg_model");
		super.new(name, UVM_NO_COVERAGE);
	endfunction

endclass

要将一个子reg_block加入父reg_block中,第一步是先实例化子reg_block。第二步是调用子reg_block的configure函数。第三步是调用子reg_block的build函数。第四步是调用子reg_block的lock_model函数。第五步则是将子reg_block的default_map以子map的形式加入父reg_block的default_map中。

reg_file

uvm_reg_file的引入主要是为了区分不同的hdl路径。

class regfile extends uvm_reg_file;
	function new(string name = "regfile");
		super.new(name);
	endfunction

	`uvm_object_utils(regfile)
endclass

class mac_blk extends uvm_reg_block;

	rand regfile file_a;
	rand regfile file_b;
	rand reg_regA regA;
	rand reg_regB regB;
	rand reg_vlan vlan;

	virtual function void build();
		default_map = create_map("default_map", 0, 2, UVM_BIG_ENDIAN, 0);
		file_a = regfile::type_id::create("file_a", , get_full_name());
		file_a.configure(this, null, "fileA");
		file_b = regfile::type_id::create("file_b", , get_full_name());
		file_b.configure(this, null, "fileB");
		regA.configure(this, file_a, "regA");
		regB.configure(this, file_b, "regB");
	endfunction
endclass

如上所示,先从uvm_reg_file派生一个类,然后在mac_blk中实例化此类,之后调用其configure函数,此函数的第一个参数是其所在的reg_block的指针;第二个参数是假设此reg_file是另外一个reg_file的父文件,那么这里就填写其父reg_file的指针(由于这里
只有这一级reg_file,因此填写null);第三个参数则是此reg_file的hdl路径。
当把reg_file定义好后,在调用寄存器的configure参数时,就可以将其第二个参数设为reg_file的指针。

存储器

在寄存器模型中加入存储器的代码如下:

class my_memory extends uvm_mem;
	function new(string name="my_memory");
		super.new(name, 1024, 16);
	endfunction

	`uvm_object_utils(my_memory)
endclass

class reg_model extends uvm_reg_block;
…
	rand my_memory mm;

	virtual function void build();
…
		mm = my_memory::type_id::create("mm", , get_full_name());
		mm.configure(this, "stat_blk.ram1024x16_inst.array");
		default_map.add_mem(mm, 'h100);
	endfunction
…
endclass
  • 首先由uvm_mem派生一个类my_memory,在其new函数中调用super.new函数。这个函数有三个参数,第一个是名字,第二个是存储器的深度,第三个是宽度。
  • 然后在reg_model的build函数中,将存储器实例化,调用其configure函数,第一个参数是所在reg_block的指针,第二个参数是此块存储器的hdl路径。
  • 最后调用default_map.add_mem函数,将此块存储器加入default_map中,从而可以对其进行前门访问操作。如果没有对此块存储器分配地址空间,那么这里可以不将其加入default_map中。在这种情况下,只能使用后门访问的方式对其进行访问。

寄存器模型对DUT的模拟

期望值与镜像值

  • 镜像值(mirrored value):寄存器模型中的一个专门的变量,用于最大可能地与DUT保持同步。
  • 期望值(desired value):除了DUT的镜像值外,寄存器模型中还有期望值。如目前DUT中invert的值为’h0,寄存器模型中的镜像值也为’h0,但是希望向此寄存器中写入一个’h1,此时’h1便是期望值。
    一种方法是直接调用write任务,将’h1写入,期望值与镜像值都更新为’h1;另外一种方法是通过set函数将期望值设置为’h1(此时镜像值依然为0),之后调用update任务,update任务会检查期望值和镜像值是否一致,如果不一致,那么将会把期望值写入DUT中,并且更新镜像值。
  • 通过get函数可以得到寄存器的期望值,通过get_mirrored_value可以得到其镜像值。
value = p_sequencer.p_rm.invert.get();
value = p_sequencer.p_rm.invert.get_mirrored_value();

常用操作

  • read&write操作:无论通过后门访问还是前门访问的方式从DUT中读取或写入寄存器的值,在操作完成后,寄存器模型都会根据读写的结果更新期望值和镜像值(二者相等)。
  • peek&poke操作:在操作完成后,寄存器模型会根据操作的结果更新期望值和镜像值(二者相等)。
  • get&set操作:set操作会更新期望值,但是镜像值不会改变。get操作会返回寄存器模型中当前寄存器的期望值。
  • update操作:这个操作会检查寄存器的期望值和镜像值是否一致,如果不一致,那么就会将期望值写入DUT中,并且更新镜像值,使其与期望值一致。
  • randomize操作:寄存器模型提供randomize接口。randomize之后,期望值将会变为随机出的数值,镜像值不会改变。但是并不是寄存器模型中所有寄存器都支持此函数。如果不支持,则randomize调用后其期望值不变。

其他用法

reg_predictor

寄存器模型

除了像上图中左边一样使用driver的返回值更新寄存器模型以外,还可以像右边一样由monitor将从总线上收集到的transaction交给寄存器模型。
在使用右边这种方式更新数据时,需要例化一个reg_predictor,并为这个reg_predictor实例化一个adapter:

class base_test extends uvm_test;reg_model rm;
	my_adapter reg_sqr_adapter;
	my_adapter mon_reg_adapter;

	uvm_reg_predictor#(bus_transaction) reg_predictor;
…
endclass

function void base_test::build_phase(uvm_phase phase);
…
	rm = reg_model::type_id::create("rm", this);
	rm.configure(null, "");
	rm.build();
	rm.lock_model();
	rm.reset();
	reg_sqr_adapter = new("reg_sqr_adapter");
	mon_reg_adapter = new("mon_reg_adapter");
	reg_predictor = new("reg_predictor", this);
	env.p_rm = this.rm;
endfunction

function void base_test::connect_phase(uvm_phase phase);
…
	rm.default_map.set_sequencer(env.bus_agt.sqr, reg_sqr_adapter);
	rm.default_map.set_auto_predict(1);
	reg_predictor.map = rm.default_map;
	reg_predictor.adapter = mon_reg_adapter;
	env.bus_agt.ap.connect(reg_predictor.bus_in);
endfunction

在connect_phase中,需要将reg_predictor和bus_agt的ap口连接在一起,并设置reg_predictor的adapter和map。只有设置了map后,才能将predictor和寄存器模型关联在一起。
当总线上只有一个master时,则上图中的左边和右边是完全等价的。如果有多个主设备,则左边会漏掉某些transaction。

mirror操作

UVM提供mirror操作,用于读取DUT中寄存器的值并将它们更新到寄存器模型中。
其有两种应用场景,一是在仿真中不断地调用它,使得到整个寄存器模型的值与DUT中寄存器的值保持一致,此时check选项是关闭的。二是在仿真即将结束时,检查DUT中寄存器的值与寄存器模型中寄存器的镜像值是否一致,这种情况下,check选项是打开的。

(mirror更新的是寄存器模型,update更新的是DUT。)文章来源地址https://www.toymoban.com/news/detail-432115.html

到了这里,关于寄存器模型的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • UVM实战_5_UVM中的寄存器模型

    提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 本章节主要介绍UVM实战第7章寄存器模型的内容 通常来说,DUT中会有一组 控制端口 ,通过控制端口,可以配置DUT中的寄存器,DUT可以根据寄存器的值来改变行为,这组控制端口就是寄存器配置总线。 暂

    2024年01月23日
    浏览(57)
  • Modbus协议的数据模型和地址模型,Modbus寄存器40001,30001是什么意思?

    在使用Modbus协议的时候,经常会遇到诸如40001、30001,10001之类的地址,这些数字代表什么含义呢? 这其实是Modbus协议的数据模型和地址模型。 Modbus协议的数据模型  数据模型是对从站设备可访问的数据进行抽象,Modbus协议的数据模型定义了四种可访问的数据: 数据区块 数据

    2024年02月05日
    浏览(46)
  • FPGA的配置状态字寄存器Status Register

    目录 简介 状态字定义       Unknown Device/Many Unknow Devices 解决办法 一般原因  Xilinx的FPGA有多种配置接口,如SPI,BPI,SeletMAP,Serial,JTAG等;如果从时钟发送者的角度分,还可以分为主动Master(即由FPGA自己发送配置时钟信号CCLK)和被动Slave(即由外部器件提供配置所需要的时

    2024年04月25日
    浏览(36)
  • STM32 寄存器配置笔记——USART配置中断接收乒乓缓存处理

           本文主要介绍如何配置USART接收中断,使用乒乓缓存的设计接收数据并将其回显在PC 串口工具上。以stm32f10为例,配置USART1 9600波特率。具体配置参考上一章节STM32 寄存器配置笔记——USART配置 打印。         乒乓缓存的设计应用场景:当后面的处理单元在工作期间,

    2024年02月20日
    浏览(52)
  • 使用VCS的ralgen命令产生UVM的寄存器模型的操作步骤

    1.根据reg.csv(寄存器的SPEC)文件生成一个reg.ralf文件,这个文件有特定的脚本生成(我目前不会),当然也可以自己手写(如果寄存器比较小)   寄存器的SPEC reg.ralf文件  2.\\\".ralf\\\"文件的格式  3.有reg.ralf文件之后,编写一个生成寄存器模型的Makefile文件 4.直接在terminal上 make genreg 便可产

    2024年02月03日
    浏览(42)
  • PAJ7620U2手势识别——配置手势数据寄存器(6)

      我们已经把所有操作寄存器配置好了,接下来就可以读取手势数据了。本章教程会带领各位读者完成对手势数据寄存器的配置,内容比较简单。   结合官方数据手册:   我们读取0x43或者0x44寄存器内的数据,挥手动作将会被这两个寄存器捕捉到,捕捉完成后读取即可

    2024年02月10日
    浏览(46)
  • 【STM32学习】定时器寄存器配置、功能工作过程详解

    STM32-定时器详解 STM32个人笔记-定时器 本文针对STM32F103系列单片机的定时器进行介绍。 主要是其常用功能的工作流程、以及寄存器的对应配置。 该图清晰的表明了各种定时器的参数与功能。其中,基础定时器基本上就只有最简单的计数到了然后触发中断的功能;通用和高级定

    2024年02月15日
    浏览(46)
  • STM32-串口通信波特率计算以及寄存器的配置详解

    处理器与外部设备通信的两种方式 并行通信 传输原理:数据各个位同时传输。 优点:速度快 缺点:占用引脚资源多 串行通信 传输原理:数据按位顺序传输 优点:占用引脚资源少 缺点:速度相对较慢 按照数据传送方向,分为: 单工 :数据传输只支持数据在一个方向上传输

    2024年02月05日
    浏览(94)
  • 【51单片机】利用【与或赋值法】优化【配置TMOD寄存器】

    前言 大家好吖,欢迎来到 YY 滴单片机系列 ,热烈欢迎! 本章主要内容面向接触过单片机的老铁 本文是YY入门【【51单片机】从零开始手把手带你【查手册】配置定时器,并完成小项目(定时器&中断的应用)(代码演示&单片机现象对照LCD闪烁)】配合博客的其中一部分,

    2024年02月19日
    浏览(40)
  • 嵌入式学习笔记——STM32的USART相关寄存器介绍及其配置

    上一篇中,对串口做了个概述,主要是介绍了串口通信的特征,异步串行全双工通信,然后就是结合串口的框图梳理了一下STM32中USART的配置流程以及发送接收数据的流程,本文将接着上篇的内容,对串口的寄存器做个介绍,然后实现一个简单的收发实验。 根据之前GPIO的经验

    2024年02月05日
    浏览(54)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包