寄存器模型简介
1.
通常来说,DUT中会有一组控制端口,通过控制端口,可以配置DUT中的寄存器,DUT可以根据寄存器的值来改变其行为。这组控制端口就是寄存器配置总线。
在没有寄存器模型之前,只能启动sequence通过前门(FRONTDOOR)访问的方式来读取寄存器,局限较大,在scoreboard(或者其他component)中难以控制。而有了寄存器模型之后,scoreboard只与寄存器模型打交道,无论是发送读的指令还是获取读操作的返回值,都可以由寄存器模型完成。有了寄存器模型后,可以在任何耗费时间的phase中使用寄存器模型以前门访问或后门(BACKDOOR)访问的方式来读取寄存器的值,同时还能在某些不耗费时间的phase(如check_phase)中使用后门访问的方式来读取寄存器的值。
前门访问与后门访问是两种寄存器的访问方式。所谓前门访问,指的是通过模拟cpu在总线上发出读指令,进行读写操作。在这个过程中,仿真时间($time函数得到的时间)是一直往前走的。
UVM寄存器模型的本质就是重新定义了验证平台与DUT的寄存器接口,使验证人员更好地组织及配置寄存器,简化流程、减少工作量。
2.
uvm_reg_field:这是寄存器模型中的最小单位。
uvm_reg:它比uvm_reg_field高一个级别,但是依然是比较小的单位。一个寄存器中至少包含一个uvm_reg_field。
uvm_reg_block:它是一个比较大的单位,在其中可以加入许多的uvm_reg,也可以加入其他的uvm_reg_block。一个寄存器模型中至少包含一个uvm_reg_block。
uvm_reg_map:每个寄存器在加入寄存器模型时都有其地址,uvm_reg_map就是存储这些地址,并将其转换成可以访问的物理地址。
当寄存器模型使用前门访问方式来实现读或写操作时,uvm_reg_map就会将地址转换成绝对地址,启动一个读或写的sequence,并将读或写的结果返回。在每个reg_block内部,至少有一个(通常也只有一个)uvm_reg_map。
简单的寄存器模型
1.
假设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, 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); endfunctionendclass
在new函数中,要将invert寄存器的宽度作为参数传递给super.new函数。这里的宽度并不是指这个寄存器的有效宽度,而是指这个寄存器中总共的位数。如对于一个16位的寄存器,其中可能只使用了8位,那么这里要填写的是16,而不是8。这个数字一般与系统总线的宽度一致。super.new中另外一个参数是是否要加入覆盖率的支持,这里选择UVM_NO_COVERAGE,即不支持。
每一个派生自uvm_reg的类都有一个build,这个build与uvm_component的build_phase并不一样,它不会自动执行,而需要手工调用,与build_phase相似的是所有的uvm_reg_field都在这里实例化。当reg_data实例化后,要调用data.configure函数来配置这个字段。
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:在复位后,第一次会尽量写入,其他的写入无影响,读取时会出错。
第五个参数表示是否是易失的(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);endfunctionendclass
同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而不是bit,第四个参数是大小端,最后一个参数表示是否能够按照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)。
2.
寄存器模型的前门访问操作可以分成读和写两种。无论是读或写,寄存器模型都会通过sequence产生一个uvm_reg_bus_op
的变量