Register Layer

目录

 

Introduction

Register Model

Register Environment

Connecting register env 

UVM Register Model Example 

UVM Register Backdoor Access


Introduction

UVM中的寄存器层类(Register Layer class)是用来创建高层次(high-level)基于对象(object-oriented)的模型,这些模型对应着DUT中的寄存器和存储单元。寄存器层定义了众多的基本类(base class)用来对DUT的寄存器进行抽象的读写操作(abstract read and write operation).在我们了解UVM的寄存器层之前,先来看看在DUT中的寄存器是如何定义以及实现其功能的。

许多设计中都可以通过外围总线(peripheral bus)来控制寄存器,这些寄存器可以被设置为不同的值从而使得硬件以不同的模式工作。例如上面的一个32bits的寄存器reg_ctl,在该寄存器中存在着多个变量(field),每一个变量都可以配置为不同的值用来配置不同的操作,如下表所示:

一个寄存器通常为32位,由多个变量构成,变量(field)通过uvm_reg_field来定义,如上面的speed、auto等;寄存器通过继承uvm_reg来定义,一个寄存器中至少要包含一个field;

下面来解释寄存器块(register block)的概念.寄存器块就是上述一系列类似的寄存器的集合 ,如下图:

每一个寄存器中都包含不同的变量(field),例如上面的reg_ctl存在于地址0x0的位置,由于它是4byte的寄存器,所以下一个寄存器的地址从0x4开始,如reg_stat;寄存器块通过继承uvm_reg_block来定义,每一个uvm_reg_block都至少包括一个uvm_reg,也可以加入其它的uvm_reg_block;

一个SOC系统通常包括一个或多个处理核(processor core)、DMA引擎(engine)、互连的内部总线和许多外围模块(peripheral module).每一个外围模块都有一个与之关联的寄存器块,这些寄存器块就存在于memory map中。memory map类似于一张很大的表(table),表中为处理器总线(processor bus)上的各种存储单元和寄存器定义了地址范围。处理器可以通过外围总线如APB总线来访问这些存储单元。每一个外围模块都在memory map中有一个存储单元,与之对应的寄存器块就存在于其中memory map保存了所有寄存器的地址(一般为偏移地址,而不是绝对地址),通过继承uvm_reg_map来定义,当寄存器模型通过前门访问(frontdoor)方式来实现对寄存器的读或写操作时,map就会将地址转换为绝对地址,启动一个读或写的sequence,并将结果返回;系统一般定义好了一个default_map,只需要在寄存器块中将其实例化就可以了;一个uvm_reg_block通常至少有一个map!

假设上面的地址0xE78F_0000为模块timer design的寄存器块,为了访问寄存块中的reg_stat寄存器,则访问地址需要变为0xE78F_0000+0x4.

总结

  • uvm_reg_field是寄存器模型的最小单位,和DUT的每个register里的bit filed对应;
  • uvm_reg 和dut中每个register对应,其宽度一般和总线位宽一致,里面可以包含多个uvm_reg_field;
  • uvm_reg_block里包含uvm_reg,一般一个最底层模块级的DUT的所有寄存器,具有相同的基地址,会放在一个reg_block中;uvm_reg_block内也可包含其他低层次的reg_block;
  • reg_block里有含有uvm_reg_map类的对象default_map,进行地址映射,以及用来完成寄存器前后门访问操作;
  • 一个寄存器模型必须包含一个reg_block;
  • 一个reg_block可以包含多个reg_map, 从而实现一个reg_block应用到不同总线,或不同地址段上;
  • uvm_mem是对dut中memory进行建模使用的;

这些类均是继承自uvm_object类,一个完整的register model, 均有这些层次化的register元素构成,放到顶层的reg block中,一个reg block可以包含子block,register,register file和memories,如下图所示:

 

 

Register Model

现在我们可以用UVM RAL(register abstraction layer) 类来定义各种单独的变量、寄存器以及寄存器块。寄存器模型(register model)包括了寄存器以及各种变量,也就是说它描述了寄存器类的层次结构,我们可以通过寄存器模型的对象来对DUT进行读和写操作。在模型中,每一个寄存器都与实际DUT中的寄存器对应;模型中实际上存在两种类型的变量:

(1)Desired Value 

desired value就是我们想让DUT中的寄存器所拥有的值,它一般保存在寄存器模型中,然后通过update()方法将该值赋给DUT中对应的寄存器,如下图:

(2)Mirrored Value

DUT中对应寄存器值的改变都会通过read()方法表现在寄存器模型的 Mirrored Value 中,因此每次DUT中寄存器值的更改我们都可以直接得知,如下图:

创建寄存器类(register)

寄存器类继承于uvm_reg,对于寄存器中的每一个变量都当做对象来处理,因此需要定义句柄和创建对象;寄存器中的变量(field)都属于uvm_reg_field类,可以直接在寄存器中定义并创建变量对象。我们以前面的reg_ctl寄存器为例,代码如下:

class reg_ctl extends uvm_reg;
  rand uvm_reg_field  En;//定义变量句柄
  rand uvm_reg_field  Mode;
  rand uvm_reg_field  Halt;
  rand uvm_reg_field  Auto;
  rand uvm_reg_field  Speed;

`uvm_object_utils(reg_ctl)

function new (string name = "reg_ctl");
    super.new (name, 32, UVM_NO_COVERAGE);
  endfunction

virtual function void build ();
 
    // 创建每一个变量的对象
    this.En     = uvm_reg_field::type_id::create ("En");
    this.Mode   = uvm_reg_field::type_id::create ("Mode");
    this.Halt   = uvm_reg_field::type_id::create ("Halt");
    this.Auto   = uvm_reg_field::type_id::create ("Auto");
    this.Speed  = uvm_reg_field::type_id::create ("Speed");
 
    // 配置每一个变量
    this.En.configure (this, 1, 0, "RW", 0, 1'h0, 1, 1, 1);
    this.Mode.configure (this, 3, 1, "RW", 0, 3'h2, 1, 1, 1);
    this.Halt.configure (this, 1, 4, "RW", 0, 1'h1, 1, 1, 1);
    this.Auto.configure (this, 1, 5, "RW", 0, 1'h0, 1, 1, 1);
    this.Speed.configure (this, 5, 11, "RW", 0, 5'h1c, 1, 1, 1);
  endfunction
endclass

new()方法的参数有3个,第一个参数是寄存器的类名,第二个参数为寄存器的位宽,这个数字一般与系统总线的宽度一致,第三个参数为该寄存器是否要加入覆盖率的支持,可选的值如下:

每一个继承于uvm_reg的寄存器类都必须有一个build()方法build()方法中需要创建变量对象,并且进行配置该方法需要手动调用

创建变量(field)对象的格式是和其他对象相同的,都是使用uvm_reg_field::type_id::create()方法;

创建完变量的对象后,还需要对变量对象进行配置,配置方法configure()的几个参数的定义如下:

function void configure(
                            uvm_reg        parent,//变量所处的寄存器,一般为this
                            int unsigned   size,//变量的位宽
                            int unsigned   lsb_pos,//变量的最低位在寄存器中的序号,序号从0开始
                            string         access,//该变量的获取方式,UVM共支持25种方式
                            bit            volatile,//变量是否是易失的,一般设为0
                            uvm_reg_data_t reset,//复位后的默认值
                            bit            has_reset,//是否有复位值,要配合上面的参数使用,一般设为1,表示带复位值
                            bit            is_rand,//表示变量是否可以随机化,0表示不可以,则一直是复位值;1表示可以
                            bit            individually_accessible//表示这个变量是否可以单独存取,1表示可以
                        );

类似的,我们可以定义其他寄存器类,如下:

class reg_stat extends uvm_reg;
  ...
endclass
 
class reg_dmactl extends uvm_reg;
  ...
endclass
 
...

创建寄存器块类(register block)

所有的寄存器都是属于一个寄存器块的,因此我们需要创建一个寄存器块类来包括上面所定义的寄存器.寄存器块通过继承uvm_reg_block来定义,每一个派生自uvm_reg_block都必须要有一个build()方法,该方法需要手动调用,在它的build()方法中需要为每个寄存器创建对象,如下:

class reg_block extends uvm_reg_block;
  rand   reg_ctl   m_reg_ctl;//定义随机的寄存器句柄
  rand   reg_stat   m_reg_stat;
  rand   reg_inten   m_reg_inten;
 
  function new (string name = "reg_block");
    super.new (name, UVM_NO_COVERAGE);
  endfunction
 
  virtual function void build ();
 
    // Create an instance for every register
    this.default_map = create_map ("", 0, 4, UVM_LITTLE_ENDIAN, 0);//创建默认的 memory map对象
    this.m_reg_ctl = reg_ctl::type_id::create ("m_reg_ctl", , get_full_name);
    this.m_reg_stat = reg_stat::type_id::create ("m_reg_stat", , get_full_name);
    this.m_reg_inten = reg_inten::type_id::create ("m_reg_inten", , get_full_name);
 
    // 配置每一个寄存器
    this.m_reg_ctl.configure (this, null, "");
    this.m_reg_stat.configure (this, null, "");
    this.m_reg_inten.configure (this, null, "");
 
    // 调用每个寄存器的build()方法,为其中的变量创建对象
    this.m_reg_ctl.build();
    this.m_reg_stat.build();
    this.m_reg_inten.build();
 
    //将寄存器添加到默认的memory map
    this.default_map.add_reg (this.m_reg_ctl, `UVM_REG_ADDR_WIDTH'h0, "RW", 0);
    this.default_map.add_reg (this.m_reg_stat, `UVM_REG_ADDR_WIDTH'h4, "RO", 0);
    this.default_map.add_reg (this.m_reg_inten, `UVM_REG_ADDR_WIDTH'h8, "RW", 0);
  endfunction
endclass

 对寄存器进行实例化使用creat,包括三个参数,第一个参数为实例名,第二个参数为空,第三个参数一般为get_full_name;

对寄存器进行配置的configure()方法与上面对变量的configure方法不同,它的几个参数定义如下:

function void configure (
   	uvm_reg_block 	blk_parent,//寄存器所在的寄存器块名	  	
   	uvm_reg_file 	regfile_parent	 = 	null,//reg_file指针,一般为null
   	string 	hdl_path	 = 	""//hdl路径,一般为空""
)

一个uvm_reg_block一定要与一个uvm_reg_map对应,系统其实已经有了一个声明好的default_map,只需要在build()中将其实例化即可,注意这里map的实例化不是使用new,而是creat_map(),该方法有多个参数,如下:

virtual function uvm_reg_map create_map(
   		string 	name,//map的名字,使用default_map时为空	  	
   		uvm_reg_addr_t 	base_addr,//基地址,所有的寄存器都会对该地址有一个偏移量	  	
   	int 	unsigned 	n_bytes,//map所使用的系统总线的宽度,单位是byte	  	
   		uvm_endianness_e 	endian,//大小端	  	
   		bit 	byte_addressing	 = 	1//是否可以按字节寻址,1表示可以
)

向default_map中添加寄存器用到add_reg()方法,该方法几个参数的含义如下:

virtual function void add_reg (
   	uvm_reg 	rg,//要加入的寄存器实例名	  	
   	uvm_reg_addr_t 	offset,//寄存器地址相对于map基地址的偏移量	  	
   	string 	rights	 = 	"RW",//寄存器的存取方式
   	bit 	unmapped	 = 	0,//0表示在map中有地址;1表示不占用任何物理地址,此时下面的参数不能为null
   	uvm_reg_frontdoor 	frontdoor	 = 	null
)

 寄存器块的对象就是一个寄存器模型,并且可以对寄存器模型中的所有寄存器进行读写操作;到目前为止,我们仅仅讨论了寄存器模型如何定义,并没有涉及到DUT中的寄存器如何进行读写操作。为了通过外部总线对DUT中的寄存器进行读写操作,我们需要向DUT 发送相关的数据类(transaction),这将在后面进行讨论。

Register Environment

前面我们介绍了如何创建寄存器模型与实际设计的寄存器对应,现在我们来看寄存器环境(register environment)的组件,以及如何对寄存器进行读写操作。

寄存器环境主要有4个重要组件:

(1)寄存器模型,用来反映DUT中寄存器的值;

(2)agent,基于总线协议将总线事务(bus transaction)驱动入DUT;

(3)adapter,将读写声明从模型转换为基于协议的总线事务,或者将总线事务转换为寄存器模型事务;

(4)predictor,了解总线活动并更新寄存器模型中的Mirrored Value以匹配DUT中寄存器的内容;

寄存器模型的前门访问(frontdoor)操作分为读和写两种。对于uvm_reg来说,存在内建的read()和write()方法来对DUT的寄存器进行读写操作,如下:

class reg_ctl extends uvm_reg;
  ...
endclass
 
reg_ctl.write (status, addr, wdata);     // Write wdata to addr
reg_ctl.read  (status, addr, rdata);     // Read rdata from addr

无论读还是写,寄存器模型都会通过sequence创建一个uvm_reg_bus_op变量,此变量中存储着操作类型(读还是写)以及操作地址,如果是写操作还会有写入的数据。此变量中的信息要经过一个转换器(adapter)转换后交给bus_sequencer,随后交给bus_driver,最终实现前门访问的读写操作

uvm_reg_bus_op是一个结构体(struct),其内容如下:

typedef struct {
 
  uvm_access_e     kind;     // 操作的类型: UVM_READ/UVM_WRITE
  uvm_reg_addr_t     addr;     // 总线的地址,默认64位
  uvm_reg_data_t     data;     // 读或写的数据,默认64位
  int          n_bits;   // 转换的位数
  uvm_reg_byte_en   byte_en;   // 启用总线的字节使能
  uvm_status_e     status;    // Result of transaction: UVM_IS_OK, UVM_HAS_X, UVM_NOT_OK
 
} uvm_reg_bus_op;

注意adpter是双向的,在其中需要定义好两个函数:一个是reg2bus,作用是将寄存器模型通过sequence发出的 uvm_reg_bus_op转换为bus_sequencer能够接受的形式;另一个是bus2reg,作用是当检测到总线上有操作时,将收集到的transaction转换为寄存器模型能够接受的形式,以便寄存器模型能够更新相应的寄存器值。

adapter通过继承uvm_reg_adapter 产生,uvm_reg_adapter中有内建的reg2bus和bus2reg两个方法,用户自定义的adapter可以覆盖这两个内建的方法。例子如下:

// apb_adapter is inherited from "uvm_reg_adapter"
class reg2apb_adapter extends uvm_reg_adapter;
   `uvm_object_utils (apb_adapter)
 
   // Set default values for the two variables based on bus protocol
   // APB does not support either, so both are turned off
   function new(string name="apb_adapter");
      super.new(name);
      supports_byte_enable = 0;//如果总线协议支持字节启用,则设置为1
      provides_responses = 0; //如果总线驱动程序提供了单独的响应项,则设置为1
   endfunction
 
  // This function accepts a register item of type "uvm_reg_bus_op" and assigns
  // address, data and other required fields to the bus protocol sequence_item
   virtual function uvm_sequence_item reg2bus (const ref uvm_reg_bus_op rw);//将寄存器模型 rw 转换为总线事务,也就是sequence item
      bus_pkt pkt = bus_pkt::type_id::create ("pkt");
      pkt.write = (rw.kind == UVM_WRITE) ? 1: 0;
      pkt.addr  = rw.addr;
      pkt.data  = rw.data;
      `uvm_info ("adapter", $sformatf ("reg2bus addr=0x%0h data=0x%0h kind=%s", pkt.addr, pkt.data, rw.kind.name), UVM_DEBUG) 
      return pkt; 
   endfunction
 
  // This function accepts a bus sequence_item and assigns address/data fields to
  // the register item
   virtual function void bus2reg (uvm_sequence_item bus_item, ref uvm_reg_bus_op rw);
      bus_pkt pkt;
 
      // bus_item is a base class handle of type "uvm_sequence_item" and hence does not
      // contain addr, data properties in it. Hence bus_item has to be cast into bus_pkt
      if (! $cast (pkt, bus_item)) begin//将sequence item转换为寄存器模型 rw
         `uvm_fatal ("reg2apb_adapter", "Failed to cast bus_item to pkt")
      end
 
      rw.kind = pkt.write ? UVM_WRITE : UVM_READ;
      rw.addr = pkt.addr;
      rw.data = pkt.data;
      rw.status = UVM_IS_OK;  // APB does not support slave response
      `uvm_info ("adapter", $sformatf("bus2reg : addr=0x%0h data=0x%0h kind=%s status=%s", rw.addr, rw.data, rw.kind.name(), rw.status.name()), UVM_DEBUG)
   endfunction
endclass 

predictor

用来监视总线事务,连接到monitor,将从monitor获取的总线事务通过adpter转换为寄存器模型事务,并将寄存器的值映射到Mirrored Value;其定义如下:

// uvm_reg_predictor class definition
 
class uvm_reg_predictor #(type BUSTYPE=int) extends uvm_component;
    `uvm_component_param_utils(uvm_reg_predictor#(BUSTYPE))
 
  uvm_analysis_imp #(BUSTYPE, uvm_reg_predictor #(BUSTYPE)) bus_in;//从此端口接收并处理BUSTYPE类型的总线事务,该分析端口连接monitor
    uvm_reg_map map;//将总线地址转换为相应的寄存器或内存句柄
    uvm_reg_adapter adapter;//将总线事务转换为寄存器模型事务 也就是uvm_reg_bus_op
 
  function new (string name="uvm_reg_predictor",uvm_component parent=null)
    super.new(name,parent);
  endfunction
endclass

我们可以直接使用 uvm_reg_predictor作为predictor,而无需自己继承和定义派生的predictor,只需要改变所处理的总线事务类型BUSTYPE就可以了。在environment中加入predictor的步骤如下:

//1.定义一个参数化的predictor,这里处理的总线事务类型(sequence item)为bus_pkt
// Here "bus_pkt" is the sequence item sent by the target monitor to this predictor
uvm_reg_predictor #(bus_pkt)     m_apb_predictor;

//2.在environment的build_phase中创建对象
virtual function void build_phase(uvm_phase phase);
  super.build_phase(phase);
  m_apb_predictor = uvm_reg_predictor#(bus_pkt)::type_id::create("m_apb_predictor", this);
endfunction

//在connect_phase中将总线模型的uvm_reg_map连接到predictor的map、将agent的分析端口连接到predictor的分析端口,将adapter连接到predictor的adapter

virtual function void connect_phase(uvm_phase phase);
  super.connect_phase(phase);
  // 1. Provide register map to the predictor
    m_apb_predictor.map       = m_ral_model.default_map;
 
    // 2. Provide an adapter to help convert bus packet into register item
    m_apb_predictor.adapter   = m_apb_adapter;
 
    // 3. Connect analysis port of target monitor to analysis implementation of predictor
  m_apb_agent.ap.connect(m_apb_predictor.bus_in);//这里分析端口实际上连接的是monitor的
endfunction

 一个完整的environment结构如下:

class reg_env extends uvm_env;
   `uvm_component_utils (reg_env)
   function new (string name="reg_env", uvm_component parent);
      super.new (name, parent);
   endfunction
 
   //uvm_agent             m_agent;         // 总线bus_agent
   ral_my_design                  m_ral_model;           // 寄存器模型
   reg2apb_adapter                m_apb_adapter;        // adapter用于总线事务和寄存器模型事务的转换
   uvm_reg_predictor #(bus_pkt)   m_apb_predictor;     // predictor用于将从monitor捕获的总线事务转换为寄存器模型事务
 
   virtual function void build_phase (uvm_phase phase);
      super.build_phase (phase);
      m_ral_model       = ral_my_design::type_id::create ("m_ral_model", this);
      m_apb_adapter     = m_apb_adapter :: type_id :: create ("m_apb_adapter");
      m_apb_predictor    = uvm_reg_predictor #(bus_pkt) :: type_id :: create ("m_apb_predictor", this);
 
      m_ral_model.build ();//调用寄存器模型的build方法,用于创建寄存器对象
      m_ral_model.lock_model ();
      uvm_config_db #(ral_my_design)::set (null, "uvm_test_top", "m_ral_model", m_ral_model);
   endfunction
 
   virtual function void connect_phase (uvm_phase phase);
      super.connect_phase (phase);
      m_apb_predictor.map       = m_ral_model.default_map;
      m_apb_predictor.adapter   = m_apb_adapter;
      //m_agent.ap.connect(m_apb_predictor.bus_in);
   endfunction   
endclass

上面调用了lock_model()方法来将寄存器模型锁定,以防止其他组件修改寄存器模型的结构; 

Connecting register env 

到目前为止,我们已经完成了寄存器环境的组件,但是在一个environment中会存在许多个agent,究竟哪一个用于寄存器环境的组件呢?来看下面的例子:

class my_env extends uvm_env;
   `uvm_component_utils (my_env)
 
   my_agent       m_agent;   
   reg_env        m_reg_env;
 
   function new (string name = "my_env", uvm_component parent);
      super.new (name, parent);
   endfunction
 
   virtual function void build_phase (uvm_phase phase);
      super.build_phase (phase);
      m_agent = my_agent::type_id::create ("m_agent", this);
      m_reg_env = reg_env::type_id::create ("m_reg_env", this);
   endfunction
 
   virtual function void connect_phase (uvm_phase phase);
      super.connect_phase (phase);
      m_agent.m_mon.mon_ap.connect (m_reg_env.m_apb2reg_predictor.bus_in);//连接monitor和predictor
      m_reg_env.m_ral_model.default_map.set_sequencer (m_agent.m_seqr, m_reg_env.m_reg2apb);//这个连接没弄明白?
   endfunction
 
endclass

其中调用map中的set_sequencer()方法,是为了将map与sequencer以及adapter连接起来,其参数定义如下:

virtual function void set_sequencer (
   	uvm_sequencer_base 	sequencer,	  	
   	uvm_reg_adapter 	adapter	 = 	null
)

该方法必须在执行sequence之前调用! 

对于寄存器,寄存器模型提供了两个基本的方法write和read.若要在reference model中读取寄存器,则可以使用read方法.例子如下:

p_rm.invert.read(status, value, UVM_FRONTDOOR);//p_rm为寄存器块,invert为寄存器块中的寄存器

 read方法的定义如下:

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
)

通常只用前3个参数,第一个参数是 uvm_status_e型的变量,这是一个输出,用以表明读操作是否成功;第二个参数是读取的数值,也是一个输出,它是一个uvm_reg_data_t型的变量;第三个参数是读取的方式,分为前门访问(UVM_FRONTDOOR)和后门访问(UVM_BACKDOOR);

下面举一个write方法的例子:

p_sequencer.p_rm.invert.write(status, 1, UVM_FRONTDOOR);//p_rm为寄存器块,invert为寄存器

write方法的定义如下: 

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
)

通常只用前3个参数,第一个参数是 uvm_status_e型的变量,这是一个输出,用以表明读操作是否成功;第二个参数是写入的数值,是一个输入,它是一个uvm_reg_data_t型的变量;第三个参数是读取的方式,分为前门访问(UVM_FRONTDOOR)和后门访问(UVM_BACKDOOR); 

UVM Register Model Example 

下面的设计有以下4个寄存器:

下面的代码并不是全部的DUT,我们为了方便只写出了寄存器部分:

module traffic (  input          pclk,
                  input          presetn,
                  input [31:0]   paddr,
                  input [31:0]   pwdata,
                  input          psel,
                  input          pwrite,
                  input          penable,
 
                  // Outputs
                  output [31:0]  prdata);
 
   reg [3:0]      ctl_reg;    // profile, blink_red, blink_yellow, mod_en RW
   reg [1:0]      stat_reg;   // state[1:0] 
   reg [31:0]     timer_0;    // timer_g2y[31:20], timer_r2g[19:8], timer_y2r[7:0] RW
   reg [31:0]     timer_1;    // timer_g2y[31:20], timer_r2g[19:8], timer_y2r[7:0] RW
 
   reg [31:0]     data_in;
   reg [31:0]     rdata_tmp;
 
   // Set all registers to default values
   always @ (posedge pclk) begin
      if (!presetn) begin
         data_in <= 0;
         ctl_reg  <= 0; 
         stat_reg <= 0; 
         timer_0  <= 32'hcafe_1234; 
         timer_1  <= 32'hface_5678;
      end
   end
 
   // Capture write data
   always @ (posedge pclk) begin
      if (presetn & psel & penable) 
         if (pwrite) 
            case (paddr)
               'h0   : ctl_reg <= pwdata;
               'h4   : timer_0 <= pwdata;
               'h8   : timer_1 <= pwdata;
               'hc   : stat_reg <= pwdata;
            endcase
   end
 
   // Provide read data
   always @ (penable) begin
      if (psel & !pwrite) 
         case (paddr)
            'h0 : rdata_tmp <= ctl_reg;
            'h4 : rdata_tmp <= timer_0;
            'h8 : rdata_tmp <= timer_1;
            'hc : rdata_tmp <= stat_reg;
         endcase
   end
 
   assign prdata = (psel & penable & !pwrite) ? rdata_tmp : 'hz;
 
endmodule

定义接口,接口的总线标准为APB,简单起见我们没有设置时钟块以及modport:

 

interface bus_if (input pclk);
   logic [31:0]   paddr;
   logic [31:0]   pwdata;
   logic [31:0]   prdata;
   logic          pwrite;
   logic          psel;
   logic          penable;
   logic          presetn;
endinterface

定义寄存器模型:首先定义寄存器,然后再定义寄存器块

// Register definition for the register called "ctl"
class ral_cfg_ctl extends uvm_reg;
  rand uvm_reg_field mod_en;      // Enables the module
  rand uvm_reg_field bl_yellow;   // Blinks yellow
  rand uvm_reg_field bl_red;      // Blinks red
    rand uvm_reg_field profile;     // 1 : Peak, 0 : Off-Peak
 
  `uvm_object_utils(ral_cfg_ctl)
 
  function new(string name = "traffic_cfg_ctrl");
    super.new(name, 32, build_coverage(UVM_NO_COVERAGE));
  endfunction: new
 
  // Build all register field objects
  virtual function void build();
    this.mod_en     = uvm_reg_field::type_id::create("mod_en",,   get_full_name());
    this.bl_yellow  = uvm_reg_field::type_id::create("bl_yellow",,get_full_name());
    this.bl_red     = uvm_reg_field::type_id::create("bl_red",,   get_full_name());
    this.profile    = uvm_reg_field::type_id::create("profile",,  get_full_name());
 
    // configure(parent, size, lsb_pos, access, volatile, reset, has_reset, is_rand, individually_accessible); 
    this.mod_en.configure(this, 1, 0, "RW", 0, 1'h0, 1, 0, 0);
    this.bl_yellow.configure(this, 1, 1, "RW", 0, 3'h4, 1, 0, 0);
    this.bl_red.configure(this, 1, 2, "RW", 0, 1'h0, 1, 0, 0);
    this.profile.configure(this, 1, 3, "RW", 0, 1'h0, 1, 0, 0);
  endfunction
endclass 
 
// Register definition for the register called "stat"
class ral_cfg_stat extends uvm_reg;
  uvm_reg_field state;    // Current state of the design
 
  `uvm_object_utils(ral_cfg_stat)
  function new(string name = "ral_cfg_stat");
    super.new(name, 32, build_coverage(UVM_NO_COVERAGE));
  endfunction
 
  virtual function void build();
    this.state = uvm_reg_field::type_id::create("state",, get_full_name());
 
    // configure(parent, size, lsb_pos, access, volatile, reset, has_reset, is_rand, individually_accessible); 
    this.state.configure(this, 2, 0, "RO", 0, 1'h0, 0, 0, 0);
  endfunction
endclass
 
// Register definition for the register called "timer"
class ral_cfg_timer extends uvm_reg;
  uvm_reg_field timer;     // Time for which it blinks
 
  `uvm_object_utils(ral_cfg_timer)
  function new(string name = "traffic_cfg_timer");
    super.new(name, 32,build_coverage(UVM_NO_COVERAGE));
  endfunction
 
  virtual function void build();
     this.timer = uvm_reg_field::type_id::create("timer",,get_full_name());
 
    // configure(parent, size, lsb_pos, access, volatile, reset, has_reset, is_rand, individually_accessible); 
     this.timer.configure(this, 32, 0, "RW", 0, 32'hCAFE1234, 1, 0, 1);
     this.timer.set_reset('h0, "SOFT");//congigure中设置的复位值为HARD复位,这里是soft复位
  endfunction
endclass

 将上面的寄存器整合到一个寄存器块中建立寄存器模型:

class ral_block_traffic_cfg extends uvm_reg_block;
  rand ral_cfg_ctl    ctrl;       // RW
  rand ral_cfg_timer  timer[2];   // RW
         ral_cfg_stat   stat;       // RO
 
  `uvm_object_utils(ral_block_traffic_cfg)
 
  function new(string name = "traffic_cfg");
    super.new(name, build_coverage(UVM_NO_COVERAGE));
  endfunction
 
  virtual function void build();
    this.default_map = create_map("", 0, 4, UVM_LITTLE_ENDIAN, 0);
    this.ctrl = ral_cfg_ctl::type_id::create("ctrl",,get_full_name());
    this.ctrl.configure(this, null, "");
    this.ctrl.build();
    this.default_map.add_reg(this.ctrl, `UVM_REG_ADDR_WIDTH'h0, "RW", 0);
 
 
    this.timer[0] = ral_cfg_timer::type_id::create("timer[0]",,get_full_name());
    this.timer[0].configure(this, null, "");
    this.timer[0].build();
    this.default_map.add_reg(this.timer[0], `UVM_REG_ADDR_WIDTH'h4, "RW", 0);
 
    this.timer[1] = ral_cfg_timer::type_id::create("timer[1]",,get_full_name());
    this.timer[1].configure(this, null, "");
    this.timer[1].build();
    this.default_map.add_reg(this.timer[1], `UVM_REG_ADDR_WIDTH'h8, "RW", 0);
 
    this.stat = ral_cfg_stat::type_id::create("stat",,get_full_name());
    this.stat.configure(this, null, "");
    this.stat.build();
    this.default_map.add_reg(this.stat, `UVM_REG_ADDR_WIDTH'hc, "RO", 0);
  endfunction 
endclass 
 
// The register block is placed in the top level model class definition
class ral_sys_traffic extends uvm_reg_block;
  rand ral_block_traffic_cfg cfg;
 
  `uvm_object_utils(ral_sys_traffic)
  function new(string name = "traffic");
    super.new(name);
  endfunction
 
  function void build();
      this.default_map = create_map("", 0, 4, UVM_LITTLE_ENDIAN, 0);//默认的map
      this.cfg = ral_block_traffic_cfg::type_id::create("cfg",,get_full_name());
      this.cfg.configure(this, "tb_top.pB0");//寄存器块的配置方法只有两个参数,第二个参数为HDL代码的层次路径
      this.cfg.build();
      this.default_map.add_submap(this.cfg.default_map, `UVM_REG_ADDR_WIDTH'h0);
  endfunction
endclass

定义adapter转换器:

class reg2apb_adapter extends uvm_reg_adapter;
   `uvm_object_utils (reg2apb_adapter)
 
   function new (string name = "reg2apb_adapter");
      super.new (name);
   endfunction
 
   virtual function uvm_sequence_item reg2bus (const ref uvm_reg_bus_op rw);
      bus_pkt pkt = bus_pkt::type_id::create ("pkt");
      pkt.write = (rw.kind == UVM_WRITE) ? 1: 0;
      pkt.addr  = rw.addr;
      pkt.data  = rw.data;
      `uvm_info ("adapter", $sformatf ("reg2bus addr=0x%0h data=0x%0h kind=%s", pkt.addr, pkt.data, rw.kind.name), UVM_DEBUG) 
      return pkt; 
   endfunction
 
   virtual function void bus2reg (uvm_sequence_item bus_item, ref uvm_reg_bus_op rw);
      bus_pkt pkt;
      if (! $cast (pkt, bus_item)) begin
         `uvm_fatal ("reg2apb_adapter", "Failed to cast bus_item to pkt")
      end
 
      rw.kind = pkt.write ? UVM_WRITE : UVM_READ;
      rw.addr = pkt.addr;
      rw.data = pkt.data;
      `uvm_info ("adapter", $sformatf("bus2reg : addr=0x%0h data=0x%0h kind=%s status=%s", rw.addr, rw.data, rw.kind.name(), rw.status.name()), UVM_DEBUG)
   endfunction
endclass

定义agent,首先要定义sequence item、sequence、sequencer、driver和monitor

// Declare a sequence_item for the APB transaction 
class bus_pkt extends uvm_sequence_item;//定义sequence item
   rand bit [31:0]  addr;
   rand bit [31:0]  data;
   rand bit         write;
 
   `uvm_object_utils_begin (bus_pkt)
      `uvm_field_int (addr, UVM_ALL_ON)
      `uvm_field_int (data, UVM_ALL_ON)
      `uvm_field_int (write, UVM_ALL_ON)
   `uvm_object_utils_end
 
   function new (string name = "bus_pkt");
      super.new (name);
   endfunction
 
   constraint c_addr { addr inside {0, 4, 8};}
endclass
 
// Drives a given apb transaction packet to the APB interface
class my_driver extends uvm_driver #(bus_pkt);//定义driver
   `uvm_component_utils (my_driver)
 
   bus_pkt  pkt;
 
   virtual bus_if    vif;
 
   function new (string name = "my_driver", uvm_component parent);
      super.new (name, parent);
   endfunction
 
   virtual function void build_phase (uvm_phase phase);
      super.build_phase (phase);
      if (! uvm_config_db#(virtual bus_if)::get (this, "*", "bus_if", vif))
         `uvm_error ("DRVR", "Did not get bus if handle")
   endfunction
 
   virtual task run_phase (uvm_phase phase);
      bit [31:0] data;
 
      vif.psel <= 0;
      vif.penable <= 0;
      vif.pwrite <= 0;
      vif.paddr <= 0;
      vif.pwdata <= 0;
      forever begin
         seq_item_port.get_next_item (pkt);
         if (pkt.write)
            write (pkt.addr, pkt.data);//写
         else begin
            read (pkt.addr, data);//读
            pkt.data = data;
         end
         seq_item_port.item_done ();
      end
   endtask
 
   virtual task read (  input bit    [31:0] addr, 
                        output logic [31:0] data);
      vif.paddr <= addr;
      vif.pwrite <= 0;
      vif.psel <= 1;
      @(posedge vif.pclk);
      vif.penable <= 1;
      @(posedge vif.pclk);
      data = vif.prdata;//将接口的数据读出
      vif.psel <= 0;
      vif.penable <= 0;
   endtask
 
   virtual task write ( input bit [31:0] addr,
                        input bit [31:0] data);
      vif.paddr <= addr;
      vif.pwdata <= data;//将数据写入到接口中
      vif.pwrite <= 1;
      vif.psel <= 1;
      @(posedge vif.pclk);
      vif.penable <= 1;
      @(posedge vif.pclk);
      vif.psel <= 0;
      vif.penable <= 0;
   endtask
endclass
 
// Monitors the APB interface for any activity and reports out
// through an analysis port
class my_monitor extends uvm_monitor;//定义monitor
   `uvm_component_utils (my_monitor)
   function new (string name="my_monitor", uvm_component parent);
      super.new (name, parent);
   endfunction
 
   uvm_analysis_port #(bus_pkt)  mon_ap;//定义分析端口TLM
   virtual bus_if                vif;
 
   virtual function void build_phase (uvm_phase phase);
      super.build_phase (phase);
      mon_ap = new ("mon_ap", this);
      uvm_config_db #(virtual bus_if)::get (null, "uvm_test_top.*", "bus_if", vif);
   endfunction
 
   virtual task run_phase (uvm_phase phase);
      fork
         forever begin
            @(posedge vif.pclk);
            if (vif.psel & vif.penable & vif.presetn) begin
               bus_pkt pkt = bus_pkt::type_id::create ("pkt");
               pkt.addr = vif.paddr;
               if (vif.pwrite)
                  pkt.data = vif.pwdata;
               else
                  pkt.data = vif.prdata;
               pkt.write = vif.pwrite;
               mon_ap.write (pkt);//调用分析端口的write()方法向外数据
            end 
         end
      join_none
   endtask
endclass
 
// The agent puts together the driver, sequencer and monitor
class my_agent extends uvm_agent;
   `uvm_component_utils (my_agent)
   function new (string name="my_agent", uvm_component parent);
      super.new (name, parent);
   endfunction
 
   my_driver                  m_drvr;
   my_monitor                 m_mon;
   uvm_sequencer #(bus_pkt)   m_seqr;//使用uvm_sequencer 
 
   virtual function void build_phase (uvm_phase phase);
      super.build_phase (phase);
      m_drvr = my_driver::type_id::create ("m_drvr", this);
      m_seqr = uvm_sequencer#(bus_pkt)::type_id::create ("m_seqr", this);
      m_mon = my_monitor::type_id::create ("m_mon", this);
   endfunction
 
   virtual function void connect_phase (uvm_phase phase);
      super.connect_phase (phase);
      m_drvr.seq_item_port.connect (m_seqr.seq_item_export);//连接driver和sequencer的分析端口
   endfunction
endclass

定义寄存器模型的环境:

// Register environment class puts together the model, adapter and the predictor
class reg_env extends uvm_env;
   `uvm_component_utils (reg_env)
   function new (string name="reg_env", uvm_component parent);
      super.new (name, parent);
   endfunction
 
   ral_sys_traffic                m_ral_model;         // Register Model
   reg2apb_adapter                m_reg2apb;           // Convert Reg Tx <-> Bus-type packets
   uvm_reg_predictor #(bus_pkt)   m_apb2reg_predictor; // Map APB tx to register in model
   my_agent                       m_agent;             // Agent to drive/monitor transactions
 
   virtual function void build_phase (uvm_phase phase);
      super.build_phase (phase);
      m_ral_model          = ral_sys_traffic::type_id::create ("m_ral_model", this);
      m_reg2apb            = reg2apb_adapter :: type_id :: create ("m_reg2apb");
      m_apb2reg_predictor  = uvm_reg_predictor #(bus_pkt) :: type_id :: create ("m_apb2reg_predictor", this);
 
      m_ral_model.build ();
      m_ral_model.lock_model ();
      uvm_config_db #(ral_sys_traffic)::set (null, "uvm_test_top", "m_ral_model", m_ral_model);
   endfunction
 
   virtual function void connect_phase (uvm_phase phase);
      super.connect_phase (phase);
      m_apb2reg_predictor.map       = m_ral_model.default_map;
      m_apb2reg_predictor.adapter   = m_reg2apb;
   endfunction   
endclass

现在我们已经有了一个寄存器环境和一个APB agent,我们在environment中将两者连接:

class my_env extends uvm_env;
   `uvm_component_utils (my_env)
 
   my_agent       m_agent;   
   reg_env        m_reg_env;
 
   function new (string name = "my_env", uvm_component parent);
      super.new (name, parent);
   endfunction
 
   virtual function void build_phase (uvm_phase phase);
      super.build_phase (phase);
      m_agent = my_agent::type_id::create ("m_agent", this);
      m_reg_env = reg_env::type_id::create ("m_reg_env", this);
      uvm_reg::include_coverage ("*", UVM_CVR_ALL);
   endfunction
 
   // Connect analysis port of monitor with predictor, assign agent to register env
   // and set default map of the register env
   virtual function void connect_phase (uvm_phase phase);
      super.connect_phase (phase);
      m_reg_env.m_agent = m_agent;
      m_agent.m_mon.mon_ap.connect (m_reg_env.m_apb2reg_predictor.bus_in);
      m_reg_env.m_ral_model.default_map.set_sequencer(m_agent.m_seqr, m_reg_env.m_reg2apb);
   endfunction
 
endclass

除此之外,我们还需要一个sequence来进行复位操作,如下:

class reset_seq extends uvm_sequence;
   `uvm_object_utils (reset_seq)
   function new (string name = "reset_seq");
      super.new (name);
   endfunction
 
   virtual bus_if    vif; 
 
   task body ();
      if (!uvm_config_db #(virtual bus_if) :: get (null, "uvm_test_top.*", "bus_if", vif)) 
         `uvm_fatal ("VIF", "No vif")
 
      `uvm_info ("RESET", "Running reset ...", UVM_MEDIUM);
      vif.presetn <= 0;
      @(posedge vif.pclk) vif.presetn <= 1;
      @ (posedge vif.pclk);
   endtask
endclass

前面建立了一个test library以用来保存一个base test,从中可以派生出子test,如下:

class base_test extends uvm_test;
   `uvm_component_utils (base_test)
 
   my_env         m_env;
   reset_seq      m_reset_seq;
   uvm_status_e   status;
 
   function new (string name = "base_test", uvm_component parent);
      super.new (name, parent);
   endfunction
 
   // Build the testbench environment, and reset sequence
   virtual function void build_phase (uvm_phase phase);
      super.build_phase (phase);
      m_env = my_env::type_id::create ("m_env", this);
      m_reset_seq = reset_seq::type_id::create ("m_reset_seq", this);
   endfunction
 
   // In the reset phase, apply reset
   virtual task reset_phase (uvm_phase phase);
      super.reset_phase (phase);
      phase.raise_objection (this);
      m_reset_seq.start (m_env.m_agent.m_seqr);
      phase.drop_objection (this);
   endtask
endclass
 
class reg_rw_test extends base_test;
   `uvm_component_utils (reg_rw_test)
   function new (string name="reg_rw_test", uvm_component parent);
      super.new (name, parent);
   endfunction
 
   // Note that main_phase comes after reset_phase, and is performed when
   // DUT is out of reset. "reset_phase" is already defined in base_test
   // and is always called when this test is started
   virtual task main_phase(uvm_phase phase);
      ral_sys_traffic   m_ral_model;
      uvm_status_e      status;
      int               rdata;
 
      phase.raise_objection(this);
 
      m_env.m_reg_env.set_report_verbosity_level (UVM_HIGH);
 
      // Get register model from config_db
      uvm_config_db#(ral_sys_traffic)::get(null, "uvm_test_top", "m_ral_model", m_ral_model);
 
      // Write 0xcafe_feed to the timer[1] register, and read it back
      m_ral_model.cfg.timer[1].write (status, 32'hcafe_feed);
      m_ral_model.cfg.timer[1].read (status, rdata);
 
      // Set 0xface as the desired value for timer[1] register
      m_ral_model.cfg.timer[1].set(32'hface);
      `uvm_info(get_type_name(), $sformatf("desired=0x%0h mirrored=0x%0h", m_ral_model.cfg.timer[1].get(), m_ral_model.cfg.timer[1].get_mirrored_value()), UVM_MEDIUM)
 
      // Predict that current value of timer[1] is 0xcafe_feed and check it is true
      m_ral_model.cfg.timer[1].predict(32'hcafe_feed);
      m_ral_model.cfg.timer[1].mirror(status, UVM_CHECK);
 
      // Set desired value of the field "bl_yellow" in register ctrl to 1
      // Then start bus transactions by calling "update" to update DUT with 
      // desired value
      m_ral_model.cfg.ctrl.bl_yellow.set(1);
      m_ral_model.cfg.update(status);
 
    // Attempt to write into a RO register "stat" with some value
      m_ral_model.cfg.stat.write(status, 32'h12345678);
      phase.drop_objection(this);
   endtask
 
  // Before end of simulation, allow some time for unfinished transactions to
  // be over
  virtual task shutdown_phase(uvm_phase phase);
    super.shutdown_phase(phase);
    phase.raise_objection(this);
    #100ns;
    phase.drop_objection(this);
  endtask
endclass

UVM Register Backdoor Access

前门访问和后面访问的区别

  • 前门访问是通过物理总线向dut发起寄存器访问操作,消耗仿真时间
  • 后面操作不通过物理总线,不消耗仿真时间

前门访问过程
以write为例:

  • 当调用寄存器的write()任务后,产生uvm_reg_item类型的transaction:rw,之后调用uvm_reg::do_write()。
  • 在uvm_reg_map中,调用reg_adapter.reg2bus将rw转换成bus driver对应的transaction。
  • 把transaction交给sequencer,最终由bus driver驱动到对应的bus interface上。
  • bus monitor在bus interface上检测到bus transaction 。
  • reg_predictor会调用reg_adapter.bus2reg将该bus transaction转换成uvm_reg_item。
  • 从driver中返回的req会转换成uvm_reg_item类型,如防止sequencer的response队列溢出,需要在adapter中设置provides_reponses.
  • 寄存器模型根据返回的uvm_reg_item来更新寄存器的value,m_mirrored和m_desired三个值
     

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值