UVM Register Model
一:如何构建reg model
step1:首先例化并配置某个寄存器的各个filed
1 class slv_en_reg extends uvm_reg; 2 ...... 3 virtual function void build(); 4 en = uvm_reg_field::type_id::create("en"); // 例化该field。 5 en.configure(this, 4, 0, "RW", 0, 'h0, 1, 1, 0); // 指明filed的属性如:宽度,LSB,是否可随机,是否可读写等。 6 reserved = uvm_reg_field::type_id::create("reserved"); 7 reserved.configure(this, 28, 4, "RO", 0, 'h0, 1, 0, 0); 8 endfunction 9 10 endclass
step2:然后例化并配置各个寄存器:
1 class mcdf_rgm extends uvm_reg_block; 2 ...... 3 virtual function void build(); 4 slv_en = slv_en_reg::type_id::create("slv_en"); 5 slv_en.configure(this); 6 slv_en.build(); 7 8 parity_err_clr = parity_err_clr_reg::type_id::create("parity_err_clr"); 9 parity_err_clr.configure(this); 10 parity_err_clr.build(); 11 12 slv_id = slv_id_reg::type_id::create("slv_id"); 13 slv_id.configure(this); 14 slv_id.build(); 15 endfunction 16 endclass
step3:添加寄存器的属性
为了支持frontdoor access:
1 map = create_map("map", 'h0, 4, UVM_LITTLE_ENDIAN); // 首先例化一个map,指明map的基地址位'h0. 2 map.add_reg(slv_en, 32'h00, "RW"); // 然后将寄存器slv_en的偏移地址32'h00和访问属性RW添加到map中; 3 map.add_reg(parity_err_clr, 32'h04, "RW"); // 然后将寄存器parity_err_clr的偏移地址32'h04和访问属性添加RW到map中; 4 map.add_reg(slv_id, 32'h08, "RW"); // 然后将寄存器slv_id的偏移地址32'h08和访问属性RW添加到map中;
为了支持backdoor access:
1 add_hdl_path("tb.dut.inst_reg_if"); // 首先添加reg module的RTL路径. 2 slv_en.add_hdl_path_slice("ctrl_mem[0]", 0, 32); // 然后添加寄存器slv_en的RTL路径. 3 parity_err_clr.add_hdl_path_slice("ctrl_mem[1]", 0, 32); // 然后添加寄存器parity_err_clr的RTL路径. 4 slv_id.add_hdl_path_slice("ctrl_mem[2]", 0, 32); // 然后添加寄存器slv_id的RTL路径. 5 lock_model();
二:reg model的集成
三:reg model的常用方法
首先需要说明的是:UVM提供了不同层次的类来支持reg model的开发,主要的类如下:
- uvm_reg_block
- uvm_reg
- uvm_reg_field
不同的类都有read()/write()/get()/set()等方法,但是只有底层的uvm_reg_filed有value/m_desired/m_mirrored等值。以uvm_reg的set()方法为例,set()的作用是修改m_desired值,由于uvm_reg中没有m_desired值,所以uvm_reg的set()方法实际上是调用了uvm_reg_field的set()方法,从而修改uvm_reg_field中的m_desired值。
1 function void uvm_reg::set(uvm_reg_data_t value, 2 string fname = "", 3 int lineno = 0); 4 m_fname = fname; 5 m_lineno = lineno; 6 7 foreach (m_fields[i]) 8 m_fields[i].set((value >> m_fields[i].get_lsb_pos()) & 9 ((1 << m_fields[i].get_n_bits()) - 1)); 10 endfunction: set
因此这里重点说明uvm_reg_filed。
uvm_reg_field的属性
在深入了解寄存器访问方法之前,让我们看看如何存储寄存器值。如寄存器抽象中所示,uvm_reg_field是表示寄存器的位的最低寄存器抽象层。uvm_reg_field使用多个属性来存储各种寄存器字段值:
- m_reset[“HARD”]:硬件复位值(hard reset),m_reset应与DUT的复位值相匹配。
- m_mirrored: TB认为DUT中应该设置的值。
- m_desired: TB想要设置给DUT的值。
- value: 将要采样的值存储在功能覆盖率中,或者当该字段被随机化时将value约束。
请注意,在这些属性中,只有值属性是公共的。 其他属性是本地的,因此我们无法直接从类外访问它们。 稍后我们将向您介绍如何使用寄存器访问方法访问这些本地属性。
uvm_reg_field的属性
configure()
我们在创建uvm_reg_field后执行的第一件事是配置它。在寄存器抽象中,我们如下配置flavor字段。
一旦完成configure(),这个field就被关联到了它所在的register,同时这个filed的所有信息都被存储到uvm_reg_field提供的下属变量中。
1 flavor = uvm_reg_field::type_id::create( "flavor" ); // flavor是一个field的名字 2 flavor.configure( .parent ( this ), 3 .size ( 3 ), 4 .lsb_pos ( 0 ), 5 .access ( "RW" ), 6 .volatile ( 0 ), 7 .reset ( 0 ), 8 .has_reset ( 1 ), 9 .is_rand ( 1 ), 10 .individually_accessible( 0 ) );
如果has_reset参数为1,则复位参数的值将被视为“HARD”复位值。如果has_reset值为0,则复位值将被忽略。复位值应与DUT的复位状态相匹配。如果您想在配置后修改复位值,可以使用set_reset()方法。
1 flavor.set_reset( .value( 0 ), .kind( "HARD" ) ); // kind == "HARD" by default
configure()和set_reset()方法如何工作
reset()
如果m_reset [kind]存在,reset()方法将重置寄存器字段的属性。默认类型是“HARD”。如果m_reset [kind]不存在,则reset()方法不执行任何操作。请注意,reset()方法不会复位DUT中的寄存器。它只复位寄存器字段对象的属性,包括m_mirrored,m_desired和value。
1 flavor.reset( .kind( "HARD" ) ); // kind == "HARD" by default
reset()方法如何工作
set()
set()方法设置寄存器字段的期望值。set()方法不会将值设置为DUT中的寄存器的值。它只将值设置为m_desired和寄存器字段对象的值属性。要真正将值设置为DUT中的寄存器,请使用write()或update()方法。
1 flavor.set( .value( 1 ) );
reset()方法如何工作
get()
get()方法获取寄存器字段的期望值。get()方法不会从DUT中的寄存器获取值。它只获取m_desired属性的值。要实际从DUT获取值,请使用read()或mirror()方法。与get()方法类似,还有两个get_*方法访问本地属性。 get_reset()检索m_reset [kind]属性的值,而get_mirrored_value()方法检索m_mirrored属性的值。
1 uvm_reg_data_t desired_value = flavor.get(); 2 uvm_reg_data_t reset_value = flavor.get_reset( .kind( "HARD" ) ); // kind == "HARD" by default 3 uvm_reg_data_t mirrored_value = flavor.get_mirrored_value();
get()方法如何工作
randomize()
randomize()方法是一个SystemVerilog方法。它随机化一个寄存器字段对象的值属性。随机化后,post_randomize()方法将value属性的值复制到m_desired属性。请注意,如果value属性的rand_mode为OFF,则pre_randomize()方法会将m_desired的值复制到value属性。
1 assert( flavor.randomize() );
randomize()方法如何工作
write()
write()方法实际上向DUT写入一个值。
1 uvm_status_e status; 2 flavor.write( .status( status ), .value( 1 ) );
write()方法涉及多个步骤。
- 创建与写入操作对应的uvm_reg_item对象。
- uvm_reg_adapter将写入操作转换为相应的总线事务。
- uvm_driver执行到DUT的总线事务。
- uvm_monitor捕获总线事务。
- uvm_reg_predictor要求uvm_reg_adapter将总线事务转换为相应的寄存器操作。
- 寄存器操作转换为uvm_reg_item。
- uvm_reg_item用于更新value,m_mirrored和m_desired属性。
请注意,如果在配置寄存器字段时个别可访问参数为0,则包含该字段的整个寄存器会被写入,因为该字段不可单独访问。在这种情况下,m_mirrored值将用作其他字段的写入值。
write()方法如何工作
read()
read()方法实际上从DUT中读取一个寄存器值。
1 uvm_status_e status; 2 uvm_reg_data_t value; 3 4 flavor.read( .status( status ), .value( value ) );
与write()方法类似,read()方法涉及多个步骤。
- 创建与读操作对应的uvm_reg_item对象。
- uvm_reg_adapter将读取操作转换为相应的总线事务。
- uvm_driver执行到DUT的总线事务。
- uvm_reg_apapter将读取数据的总线事务转换为寄存器操作。
- read()方法将读取值返回给调用者。
- 同时,uvm_monitor捕获总线事务。
- uvm_reg_predictor要求uvm_reg_adapter将总线事务转换为相应的寄存器操作。
- 寄存器操作转换为uvm_reg_item。
- uvm_reg_item用于更新值,m_mirrored和m_desired属性。
请注意,如果在配置寄存器字段时individually_accessible参数为0,则会读取包含字段的整个寄存器。在这种情况下,也会为其他字段更新m_mirrored值。
read()方法如何工作
update()
update()方法实际上是向DUT写入一个寄存器值。 update()方法属于uvm_reg类。 uvm_reg_field类没有update()方法。
1 uvm_status_e status; 2 3 jb_recipe_reg.update( .status( status ) );
write()方法和update()方法之间的区别是:
- write()方法将一个值作为其参数,而update()方法使用m_desired属性的值作为待写入的值。
- 只有当m_mirrored和m_desired不相等时,update()方法才写入该值。
- update()方法在内部调用write(.value(m_desired))。因此更新后,m_mirrored的值也会更新。
update()方法如何工作
mirror()
mirror()方法实际上是从DUT读取一个寄存器。
1 uvm_status_e status; 2 3 flavor.mirror( .status( status ), .check( UVM_CHECK ) );
read()方法和mirror()方法之间的区别是:
- read()方法返回寄存器值给调用者,而mirror()方法不返回寄存器值。mirror()方法只更新m_mirrored属性的值。
- 如果check参数的值为UVM_CHECK,则mirror()方法将读取值与m_desired进行比较。
请注意,UVM类库文档指出,它将读取值与镜像值进行比较,但如果您查看uvm-1.1c代码库的uvm_reg.svh的第2,944行,它实际上会与所需的值进行比较,而不是针对镜像值。2014年4月11日:uvm-1.1d代码库已纠正此问题。mirror()将读取值与镜像值进行比较。如果您对此修复感兴趣,请参阅uvm_reg.svh的第2,951行。)
关于检查的另一个警告是,如果在配置寄存器字段时将volatile参数设置为1,则即使将check参数设置为UVM_CHECK,也不会检查寄存器字段。这是因为我们无法确定性地预测寄存器字段的值,因为它可能在DUT中被更改(易失性)。
mirror()方法在内部调用do_read()方法。这是与read()方法内部调用的相同方法。因此,除m_mirrored属性外,mirror()方法还会更新值和m_desired属性。
mirror()方法如何工作
predict()
predict()方法更新镜像值。
1 flavor.predict( .value( 1 ) );
predict()方法也会更新值和m_desired属性。
predict()方法如何工作
Summary
下表总结了每种方法如何更新寄存器字段对象的属性。
Method | m_reset["HARD"] | value | m_desired | m_mirrored | DUT |
---|---|---|---|---|---|
configure |
set the value of val |
||||
set_reset(val) |
set the value of val |
||||
reset() |
copy the value of m_reset |
copy the value of m_reset |
copy the value of m_reset |
||
set(val) |
set the value of val |
set the value of val |
|||
get_reset() |
return the value of m_reset |
||||
get() |
return the value of m_desired |
||||
get_mirrored_value() |
return the value of m_mirrored |
||||
randomize() |
randomize | copy the value of value |
|||
write(.value(val)) |
set the value of val |
set the value of val |
set the value of val |
write the value of val |
|
read(.value(val)) |
set the read value | set the read value | set the read value | read the register | |
update() |
set the value of m_desired |
set the value of m_desired |
set the value of m_desired |
write the value of m_desired |
|
mirror() |
set the read value | set the read value | set the read value | read the register | |
predict |
set the value of val |
set the value of val |
set the value of val |
在这篇文章中,我们只介绍了所谓的前门访问。我们将在单独的帖子中介绍后门访问。我希望本教程能帮助您理解寄存器的访问方法。