UVM RAL Example DMA

UVM Register Model Example

In this section will see an example that shows one of the ways to access DUT registers with the UVM RAL Model.

Let’s consider a DMA design which consists of registers in it and reg_interface is used to access the registers.

Below is the block diagram of DMA.

UVM RAL Example
UVM RAL Example

Below are the DMA registers,

  • INTR
  • CTRL
  • IO ADDR
  • MEM ADDR

Address of each register and register field description is given below,

Reg Name Address   Field Description
INTR h400 31:16 15:0
MASK STATUS
CTRL h404 31:10 9 8:1 0
RESVD IO_MEM W_COUNT START_DMA
IO_ADDR h408 31:0
ADDRESS
MEM_ADDR h40C 31:0
ADDRESS

Below is the testbench block diagram,

UVM TestBench Register Access
UVM TestBench Register Access

The testbench components are,

  • Environment
    • DMA Agent
      • Driver
      • Monitor
      • Sequencer and Sequences
    • RAL Model
      • DMA Reg package
      • Adapter

UVM Tetsbench prior knowledge is required before going through this section. For detailed steps on writing UVM Testbench refer to UVM Testbench Architecture.

UVM RAL Model creation involves the below steps,

  • Writing register classes
  • Writing register package
  • Instantiation of register classes in register package
  • Writing Adapter class
  • Integrating register package and adapter in environment
  • Accessing registers with RAL

Writing register classes

Writing reg class for INTR

Below is the register filed details of INTR reg.

Reg Name Address Field Description
INTR h400 31:16 15:0
MASK STATUS
  • Register class is written by extending the uvm_reg class
class intr extends uvm_reg;
  `uvm_object_utils(intr)

  //---------------------------------------
  // Constructor 
  //---------------------------------------   
  function new (string name = "intr");
    super.new(name,32,UVM_NO_COVERAGE); //32 -> Register Width
  endfunction

endclass
  • Declare the fields with type uvm_reg_field
  //---------------------------------------
  // fields instance 
  //--------------------------------------- 
  rand uvm_reg_field status;
  rand uvm_reg_field mask;
  • Create the fields
  function void build;   
    // Create bitfield
    status = uvm_reg_field::type_id::create("status");
    mask   = uvm_reg_field::type_id::create("mask");   
  endfunction
  • Configure the fields with access type, bit width and etc,
    // Create bitfield
    status = uvm_reg_field::type_id::create("status");   
    // Configure
    status.configure(.parent(this), 
                     .size(16), 
                     .lsb_pos(0), 
                     .access("RW"),  
                     .volatile(0), 
                     .reset(0), 
                     .has_reset(1), 
                     .is_rand(1), 
                     .individually_accessible(0)); 

    mask = uvm_reg_field::type_id::create("mask");   
    mask.configure(.parent(this), 
                     .size(16), 
                     .lsb_pos(16), 
                     .access("RW"),  
                     .volatile(0), 
                     .reset(0), 
                     .has_reset(1), 
                     .is_rand(1), 
                     .individually_accessible(0)); 
  • Complete register class
class intr extends uvm_reg;
  `uvm_object_utils(intr)
   
  //---------------------------------------
  // fields instance 
  //--------------------------------------- 
  rand uvm_reg_field status;
  rand uvm_reg_field mask;

  //---------------------------------------
  // Constructor 
  //---------------------------------------   
  function new (string name = "intr");
    super.new(name,32,UVM_NO_COVERAGE); //32 -> Register Width
  endfunction

  //---------------------------------------
  // build_phase - 
  // 1. Create the fields
  // 2. Configure the fields
  //---------------------------------------  
  function void build; 
    
    // Create bitfield
    status = uvm_reg_field::type_id::create("status");   
    // Configure
    status.configure(.parent(this), 
                     .size(16), 
                     .lsb_pos(0), 
                     .access("RW"),  
                     .volatile(0), 
                     .reset(0), 
                     .has_reset(1), 
                     .is_rand(1), 
                     .individually_accessible(0)); 
    // Below line is equivalen to above one   
    // status.configure(this, 32,       0,   "RW",   0,        0,        1,        1,      0); 
    //                  reg, bitwidth, lsb, access, volatile, reselVal, hasReset, isRand, fieldAccess
    
    mask = uvm_reg_field::type_id::create("mask");   
    mask.configure(.parent(this), 
                     .size(16), 
                     .lsb_pos(16), 
                     .access("RW"),  
                     .volatile(0), 
                     .reset(0), 
                     .has_reset(1), 
                     .is_rand(1), 
                     .individually_accessible(0));    
    endfunction
endclass

Writing reg class for CTRL

This register has multiple fields, each field has to be declared, created and configured.

class ctrl extends uvm_reg;
  `uvm_object_utils(ctrl)
   
  //---------------------------------------
  // fields instance 
  //--------------------------------------- 
  rand uvm_reg_field start_dma;
  rand uvm_reg_field w_count;
  rand uvm_reg_field io_mem;
  rand uvm_reg_field reserved;

  //---------------------------------------
  // Constructor 
  //---------------------------------------
  function new (string name = "ctrl");
    super.new(name,32,UVM_NO_COVERAGE); //32 -> Register Width
  endfunction

  //---------------------------------------
  // build_phase - 
  // 1. Create the fields
  // 2. Configure the fields
  //---------------------------------------  
  function void build; 
    start_dma = uvm_reg_field::type_id::create("start_dma");   
    start_dma.configure(.parent(this), 
                        .size(1), 
                        .lsb_pos(0),  
                        .access("RW"),   
                        .volatile(0),  
                        .reset(0),  
                        .has_reset(1),  
                        .is_rand(1),  
                        .individually_accessible(0));  
    
    w_count = uvm_reg_field::type_id::create("w_count");   
    w_count.configure(.parent(this),  
                      .size(8),  
                      .lsb_pos(1),  
                      .access("RW"),   
                      .volatile(0),  
                      .reset(0),  
                      .has_reset(1),  
                      .is_rand(1),  
                      .individually_accessible(0));     
            
    io_mem = uvm_reg_field::type_id::create("io_mem");   
    io_mem.configure(.parent(this),  
                     .size(1),  
                     .lsb_pos(9),  
                     .access("RW"),    
                     .volatile(0),   
                     .reset(0),   
                     .has_reset(1),   
                     .is_rand(1),   
                     .individually_accessible(0));   
            
    reserved = uvm_reg_field::type_id::create("reserved");   
    reserved.configure(.parent(this),  
                       .size(22),  
                       .lsb_pos(10),  
                       .access("RW"),     
                       .volatile(0),    
                       .reset(0),    
                       .has_reset(1),    
                       .is_rand(1),    
                       .individually_accessible(0));        
    endfunction
endclass

Writing reg class for IO_Addr

class io_addr extends uvm_reg;
  `uvm_object_utils(io_addr)
   
  //---------------------------------------
  // fields instance 
  //--------------------------------------- 
  rand uvm_reg_field addr;

  //---------------------------------------
  // Constructor 
  //---------------------------------------   
  function new (string name = "io_addr");
    super.new(name,32,UVM_NO_COVERAGE); //32 -> Register Width
  endfunction

  //---------------------------------------
  // build_phase - 
  // 1. Create the fields
  // 2. Configure the fields
  //---------------------------------------  
  function void build; 
    
    // Create bitfield
    addr = uvm_reg_field::type_id::create("addr");   
    // Configure
    addr.configure(.parent(this), 
                   .size(32), 
                   .lsb_pos(0),  
                   .access("RW"),   
                   .volatile(0),  
                   .reset(0),  
                   .has_reset(1),  
                   .is_rand(1),  
                   .individually_accessible(0));   
    endfunction
endclass

Writing reg class for Mem_Addr

class mem_addr extends uvm_reg;
  `uvm_object_utils(mem_addr)
   
  //---------------------------------------
  // fields instance 
  //--------------------------------------- 
  rand uvm_reg_field addr;

  //---------------------------------------
  // Constructor 
  //---------------------------------------   
  function new (string name = "mem_addr");
    super.new(name,32,UVM_NO_COVERAGE); //32 -> Register Width
  endfunction

  //---------------------------------------
  // build_phase - 
  // 1. Create the fields
  // 2. Configure the fields
  //---------------------------------------  
  function void build; 
    
    // Create bitfield
    addr = uvm_reg_field::type_id::create("addr");   
    // Configure
    addr.configure(.parent(this), 
                   .size(32), 
                   .lsb_pos(0),  
                   .access("RW"),   
                   .volatile(0),  
                   .reset(0),  
                   .has_reset(1),  
                   .is_rand(1),  
                   .individually_accessible(0));   
    endfunction
endclass

Writing register package

register package is written by extending the uvm_reg_block

class dma_reg_model extends uvm_reg_block;
  `uvm_object_utils(dma_reg_model)
  
  //---------------------------------------
  // Constructor 
  //---------------------------------------
  function new (string name = "");
    super.new(name, build_coverage(UVM_NO_COVERAGE));
  endfunction
endclass

Instantiation of register classes in register package

  • Instantiating the register classes
  //---------------------------------------
  // register instances 
  //---------------------------------------
  rand intr  reg_intr; 
  rand ctrl  reg_ctrl;
  rand io_addr  reg_io_addr;
  rand mem_addr reg_mem_addr;
  • Creating, building and configuring the register instances
    reg_intr = intr::type_id::create("reg_intr");
    reg_intr.build();
    reg_intr.configure(this);
 
    reg_ctrl = ctrl::type_id::create("reg_ctrl");
    reg_ctrl.build();
    reg_ctrl.configure(this);
    
    reg_io_addr = io_addr::type_id::create("reg_io_addr");
    reg_io_addr.build();
    reg_io_addr.configure(this);
  
    reg_mem_addr = mem_addr::type_id::create("reg_mem_addr");
    reg_mem_addr.build();
    reg_mem_addr.configure(this);
  • Creating the memory map
default_map = create_map("my_map", 0, 4, UVM_LITTLE_ENDIAN); 
  • Adding registers to memory map
    default_map.add_reg(reg_intr , 'h0, "RW"); // reg, offset, access
    default_map.add_reg(reg_ctrl , 'h4, "RW");
    default_map.add_reg(reg_io_addr , 'h8, "RW");
    default_map.add_reg(reg_mem_addr, 'hC, "RW");
  • Complete register package class
class dma_reg_model extends uvm_reg_block;
  `uvm_object_utils(dma_reg_model)
  
  //---------------------------------------
  // register instances 
  //---------------------------------------
  rand intr  reg_intr; 
  rand ctrl  reg_ctrl;
  rand io_addr  reg_io_addr;
  rand mem_addr reg_mem_addr;
  
  //---------------------------------------
  // Constructor 
  //---------------------------------------
  function new (string name = "");
    super.new(name, build_coverage(UVM_NO_COVERAGE));
  endfunction

  //---------------------------------------
  // Build Phase 
  //---------------------------------------
  function void build;
    
    //---------------------------------------
    //reg creation
    //---------------------------------------
    reg_intr = intr::type_id::create("reg_intr");
    reg_intr.build();
    reg_intr.configure(this);
 
    reg_ctrl = ctrl::type_id::create("reg_ctrl");
    reg_ctrl.build();
    reg_ctrl.configure(this);
    
    reg_io_addr = io_addr::type_id::create("reg_io_addr");
    reg_io_addr.build();
    reg_io_addr.configure(this);
  
    reg_mem_addr = mem_addr::type_id::create("reg_mem_addr");
    reg_mem_addr.build();
    reg_mem_addr.configure(this);
    
    //---------------------------------------
    //Memory map creation and reg map to it
    //---------------------------------------
    default_map = create_map("my_map", 0, 4, UVM_LITTLE_ENDIAN);
    default_map.add_reg(reg_intr , 'h0, "RW");  // reg, offset, access
    default_map.add_reg(reg_ctrl , 'h4, "RW");
    default_map.add_reg(reg_io_addr , 'h8, "RW");
    default_map.add_reg(reg_mem_addr, 'hC, "RW");
    
    lock_model();
  endfunction
endclass

Writing Adapter Class

  • An adapter class is written by extending the uvm_reg_adapter
class dma_adapter extends uvm_reg_adapter;
  `uvm_object_utils (dma_adapter)

  //---------------------------------------
  // Constructor 
  //--------------------------------------- 
  function new (string name = "dma_adapter");
      super.new (name);
   endfunction

endclass
  • Writing reg2bus method
  //---------------------------------------
  // reg2bus method 
  //--------------------------------------- 
  function uvm_sequence_item reg2bus(const ref uvm_reg_bus_op rw);
    dma_seq_item tx;    
    tx = dma_seq_item::type_id::create("tx");
    
    tx.wr_en = (rw.kind == UVM_WRITE);
    tx.addr  = rw.addr;
    if (tx.wr_en)  tx.wdata = rw.data;
    if (!tx.wr_en) tx.rdata = rw.data;

    return tx;
  endfunction
  • Writing bus2reg method
  //---------------------------------------
  // bus2reg method 
  //--------------------------------------- 
  function void bus2reg(uvm_sequence_item bus_item, ref uvm_reg_bus_op rw);
    dma_seq_item tx;
    
    assert( $cast(tx, bus_item) )
      else `uvm_fatal("", "A bad thing has just happened in my_adapter")

    rw.kind = tx.wr_en ? UVM_WRITE : UVM_READ;
    rw.addr = tx.addr;
    rw.data = tx.rdata;
    
    rw.status = UVM_IS_OK;
  endfunction
  • Complete adapter class
class dma_adapter extends uvm_reg_adapter;
  `uvm_object_utils (dma_adapter)

  //---------------------------------------
  // Constructor 
  //--------------------------------------- 
  function new (string name = "dma_adapter");
      super.new (name);
   endfunction
  
  //---------------------------------------
  // reg2bus method 
  //--------------------------------------- 
  function uvm_sequence_item reg2bus(const ref uvm_reg_bus_op rw);
    dma_seq_item tx;    
    tx = dma_seq_item::type_id::create("tx");
    
    tx.wr_en = (rw.kind == UVM_WRITE);
    tx.addr  = rw.addr;
    if (tx.wr_en)  tx.wdata = rw.data;
    if (!tx.wr_en) tx.rdata = rw.data;

    return tx;
  endfunction

  //---------------------------------------
  // bus2reg method 
  //--------------------------------------- 
  function void bus2reg(uvm_sequence_item bus_item, ref uvm_reg_bus_op rw);
    dma_seq_item tx;
    
    assert( $cast(tx, bus_item) )
      else `uvm_fatal("", "A bad thing has just happened in my_adapter")

    rw.kind = tx.wr_en ? UVM_WRITE : UVM_READ;
    rw.addr = tx.addr;
    rw.data = tx.rdata;

    rw.status = UVM_IS_OK;
  endfunction
endclass

Integrating register package and adapter in environment

This involves below steps,

  • Instantiating the regmodel and adapter
  dma_reg_model  regmodel;   
  dma_adapter    m_adapter;
  • Creating the register model and adapter
    regmodel = dma_reg_model::type_id::create("regmodel", this);
    regmodel.build();
    m_adapter = dma_adapter::type_id::create("m_adapter",, get_full_name());
  • Connecting the adapter sequencer with agent sequencer
    regmodel.default_map.set_sequencer( .sequencer(dma_agnt.sequencer), 
                                        .adapter(m_adapter) );
  • Setting the base address to regmodel address map
    regmodel.default_map.set_base_addr('h400);
  • Complete env code
class dma_model_env extends uvm_env;
  
  //---------------------------------------
  // agent and scoreboard instance
  //---------------------------------------
  dma_agent      dma_agnt;
  
  //---------------------------------------
  // Reg Model and Adapter instance
  //---------------------------------------  
  dma_reg_model  regmodel;   
  dma_adapter    m_adapter;
  
  `uvm_component_utils(dma_model_env)
  
  //--------------------------------------- 
  // constructor
  //---------------------------------------
  function new(string name, uvm_component parent);
    super.new(name, parent);
  endfunction : new

  //---------------------------------------
  // build_phase - create the components
  //---------------------------------------
  function void build_phase(uvm_phase phase);
    super.build_phase(phase);
    dma_agnt = dma_agent::type_id::create("dma_agnt", this);
    
    //---------------------------------------
    // Register model and adapter creation
    //---------------------------------------
    regmodel = dma_reg_model::type_id::create("regmodel", this);
    regmodel.build();
    m_adapter = dma_adapter::type_id::create("m_adapter",, get_full_name());
  endfunction : build_phase
  
  //---------------------------------------
  // connect_phase - connecting regmodel sequencer and adapter with 
  //                             mem agent sequencer and adapter
  //---------------------------------------
  function void connect_phase(uvm_phase phase); 
    regmodel.default_map.set_sequencer( .sequencer(dma_agnt.sequencer), .adapter(m_adapter) );
    regmodel.default_map.set_base_addr('h400);        
  endfunction : connect_phase

endclass : dma_model_env

Accessing registers with RAL

Write to the register

  • Write to the register is performed by calling the register write method
  • Data to be written to the registers is passed as an argument to the write method

Syntax:

reg_model.rag_name.write(status, write_data)

Read from the register

  • Read from the register is performed by calling the register read method
  • The read method returns the Data

Syntax:

reg_model.rag_name.read(status, read_data)

Access to the registers,

    //Write to the Registers
    regmodel.reg_intr.write(status, 32'h1234_1234);
    regmodel.reg_ctrl.write(status, 32'h1234_5678);
    regmodel.reg_io_addr.write(status, 32'h1234_9ABC);
    regmodel.reg_mem_addr.write(status, 32'h1234_DEF0);
    
    //Read from the register
    regmodel.reg_intr.read(status, rdata);
    regmodel.reg_ctrl.read(status, rdata);
    regmodel.reg_io_addr.read(status, rdata);
    regmodel.reg_mem_addr.read(status, rdata);

Complete Sequence Code

class dma_reg_seq extends uvm_sequence;

  `uvm_object_utils(dma_reg_seq)
  
  dma_reg_model regmodel;
  
  //---------------------------------------
  // Constructor 
  //---------------------------------------    
  function new (string name = ""); 
    super.new(name);    
  endfunction
  
  //---------------------------------------
  // Sequence body 
  //---------------------------------------      
  task body;  
    uvm_status_e   status;
    uvm_reg_data_t incoming;
    bit [31:0]     rdata;
    
    if (starting_phase != null)
      starting_phase.raise_objection(this);
    
    //Write to the Registers
    regmodel.reg_intr.write(status, 32'h1234_1234);
    regmodel.reg_ctrl.write(status, 32'h1234_5678);
    regmodel.reg_io_addr.write(status, 32'h1234_9ABC);
    regmodel.reg_mem_addr.write(status, 32'h1234_DEF0);
    
    //Read from the registers
    regmodel.reg_intr.read(status, rdata);
    regmodel.reg_ctrl.read(status, rdata);
    regmodel.reg_io_addr.read(status, rdata);
    regmodel.reg_mem_addr.read(status, rdata);
      
    if (starting_phase != null)
      starting_phase.drop_objection(this);  
    
  endtask
endclass

Test case

register sequence is declared, created and started from the test case.

class dma_reg_test extends dma_model_base_test;

  `uvm_component_utils(dma_reg_test)
  
  //---------------------------------------
  // sequence instance 
  //--------------------------------------- 
  dma_reg_seq reg_seq;

  //---------------------------------------
  // constructor
  //---------------------------------------
  function new(string name = "dma_reg_test",uvm_component parent=null);
    super.new(name,parent);
  endfunction : new

  //---------------------------------------
  // build_phase
  //---------------------------------------
  virtual function void build_phase(uvm_phase phase);
    super.build_phase(phase);

    // Create the sequence
    reg_seq = dma_reg_seq::type_id::create("reg_seq");
  endfunction : build_phase
  
  //---------------------------------------
  // run_phase - starting the test
  //---------------------------------------
  task run_phase(uvm_phase phase);
    
    phase.raise_objection(this);
      if ( !reg_seq.randomize() ) `uvm_error("", "Randomize failed")
      //Setting sequence in reg_seq
      reg_seq.regmodel       = env.regmodel;
      reg_seq.starting_phase = phase;
      reg_seq.start(env.dma_agnt.sequencer); 
    phase.drop_objection(this);
    
    //set a drain-time for the environment if desired
    phase.phase_done.set_drain_time(this, 50);
  endtask : run_phase
  
endclass : dma_reg_test

Simulator Output

UVM_INFO @ 0: reporter [RNTST] Running test dma_reg_test...
UVM_INFO /playground_lib/uvm-1.2/src/base/uvm_spell_chkr.svh(123) @ 0:
----------------------------------------------------------------
Name Type Size Value
----------------------------------------------------------------
uvm_test_top dma_reg_test - @1912
 env dma_model_env - @1980
 dma_agnt dma_agent - @2031
 driver dma_driver - @2299
 rsp_port uvm_analysis_port - @2371
 seq_item_port uvm_seq_item_pull_port - @2334
 monitor dma_monitor - @2189
 item_collected_port uvm_analysis_port - @2266
 sequencer dma_sequencer - @2402
 rsp_export uvm_analysis_export - @2447
 seq_item_export uvm_seq_item_pull_imp - @2857
 arbitration_queue array 0 - 
 lock_queue array 0 - 
 num_last_reqs integral 32 'd1 
 num_last_rsps integral 32 'd1 
----------------------------------------------------------------
Design WR addr 400 Data 12341234
Design WR addr 404 Data 12345678
Design WR addr 408 Data 12349abc
Design WR addr 40c Data 1234def0
Design RD addr 400 Data 12341234
Design RD addr 404 Data 12345678
Design RD addr 408 Data 12349abc
Design RD addr 40c Data 1234def0
UVM_INFO /playground_lib/uvm-1.2/src/base/uvm_objection.svh(1271) @ 245: reporter [TEST_DONE] 
UVM_INFO dma_test.sv(58) @ 245: uvm_test_top [dma_reg_test] ---------------------------------------
UVM_INFO dma_test.sv(59) @ 245: uvm_test_top [dma_reg_test] ---- TEST PASS ----
UVM_INFO dma_test.sv(60) @ 245: uvm_test_top [dma_reg_test] ---------------------------------------
UVM_INFO /playground_lib/uvm-1.2/src/base/uvm_report_server.svh(847) @ 245: reporter [UVM/REPORT/SERVER] 
--- UVM Report Summary ---

** Report counts by severity
UVM_INFO : 7
UVM_WARNING : 0
UVM_ERROR : 0
UVM_FATAL : 0
** Report counts by id
[RNTST] 1
[TEST_DONE] 1
[UVM/CONFIGDB/SPELLCHK] 1
[UVM/RELNOTES] 1
[dma_reg_test] 3

Click to execute on   

Write and Read Waveform:

UVM RAL Register Write Read
UVM RAL Register Write Read

Write Waveform:

UVM RAL Register Write
UVM RAL Register Write

Read Waveform:

UVM RAL Register Read
UVM RAL Register Read

❮ Previous Next ❯

UVM RAL Usage Model

UVM RAL Usage Model

The previous section described the UVM RAL base classes. This section explains using UVM RAL base classes with reference to the design elements. i.e mapping UVM RAL elements with design elements.

Let’s consider the design architecture of the CPU. In this section, In order to understand the use of each UVM RAL element, first, we will map each RAL element with the CPU design elements.

  • The architecture of the CPU shows that it consists of multiple modules
    • uvm_reg_block shall be used to represent each design module
  • each module consists of many registers
    • uvm_reg_file shall group the module registers
  • each module consists of registers
    • uvm_reg shall be used to implement the design register
  • each register consists of one or multiple fields
    • uvm_reg_field type shall be used for field implementation

Below block diagram is the Direct Memory Access (DMA) design block containing four registers, which have two, three, one and one fields respectively, an internal memory, and external memory.

Design with Registers
Design with Registers

Below block diagram shows the UVM RAL element equivalent to the design element.

UVM RAL for Above Design
UVM RAL for Above Design

RAL Building blocks

  • Register block
  • Register file
  • Register
  • Register Filed

Each is explained in detail below,

Register Block

The reg block is written by extending the uvm_reg_block.

A block corresponds to a design component/hierarchy with its own interface(s), registers, register files, memories, and sub-blocks.

In simple words, a block can be referred to as a design module with its own interface(s), registers, register files, memories, and sub-blocks. And it will be having unique address decoding to route the access to it.

A register model is an instance of a register block, which may contain any number of registers, register files, memories, and other blocks.

UVM Reg Block
UVM Reg Block

Register File

The reg file is written by extending the uvm_reg_file.

The reg file shall be used to group the number of registers or register files. below block diagram shows the register file consists of reg_file_0, reg_file_1 and reg_file_2. and each register file consists of a set of registers. ( registers are shown only in reg_file_2, Assume that there are registers in reg_file_0 and reg_file_1 )

UVM Register File
UVM Register File

Register

The uvm register class is written by extending the uvm_reg.

A register represents a set of fields that are accessible as a single entity.

Each register contains any number of fields, which mirror the values of the corresponding elements in hardware.

UVM Register
UVM Register

Register Field

The register field is declared with the type uvm_reg_filed.

Fields represent a contiguous set of bits. All data values are modeled as fields.  A field is contained within a single register but may have different access policies. access policies are explained in the next sections.

UVM Reg Field
UVM Reg Field

Complete Register Model block diagram

UVM Register Model
UVM Register Model

❮ Previous Next ❯

UVM RAL Methods

Register Access Methods

UVM RAL library classes have builtin methods implemented in it, these methods can be used for accessing the registers. These methods are referred to as Register Access Methods.

The register model has methods to read, write, update and mirror DUT registers and register field values, these methods are called API (application programming interface).

APIs can either use front door access or back door access to DUT registers and register fields.

  • Front door access involves using the bus interface and it is associated with the timing
  • Back door access uses simulator database access routines and this happens in zero simulation time

API methods

read and write

  • read() returns and updates the value of the DUT register.
  • write() writes and updates the value of the DUT register.
  • both read and write can be used for front door access or back door access.
  • In read or write value will be updated by the bus predictor on completion of the front door read or write cycle and automatically in back door read or write cycle.
UVM RAL FrontDoor Access
UVM RAL FrontDoor Access
UVM RAL BackDoor Access
UVM RAL BackDoor Access

peek and poke

  • peek() reads the DUT register value using a backdoor
  • poke() writes a value to DUT register using backdoor
UVM RAL Peek Poke
UVM RAL Peek Poke

set and get

  • set() and get() writes and reads directly to the desired value.
  • set and get methods operates on the register model desired value, not accesses to DUT register value. The desired value can be updated to the DUT using the update method.
UVM RAL Get Set
UVM RAL Get Set

update

  • if there is a difference between desired value and mirrored value,update() will initiate a write to register.update method can be used after the set method.
UVM RAL Update
UVM RAL Update

mirror

  • mirror() reads the updated DUT register values. The mirroring can be performed in the front door or back door( peek() ).
UVM RAL Mirror
UVM RAL Mirror

randomize

  • randomize() randomizes register or field values with or without constraints.as per the requirement register values can be modified in post_randomize().after randomization update() can be used to update the DUT register values.
UVM RAL Randomize
UVM RAL Randomize

reset

reset()  sets the register desired and mirrored value to the pre-defined reset value.

Method Front door access Back door access
read() RF RF
write() RF RF
peek() RF RF
poke() RF RF
set() RF RF
get() RF RF
update() BR BR
mirror() BR BR
randomize() BRF BRF
reset() BRF BRF

register access methods at a different level
– Block  – Register  – Field

❮ Previous Next ❯

UVM RAL Adapter

UVM Register Model Adapter

With the UVM Register model, we do design register access, i.e WRITE to the design register or READ from the design register by calling RAL methods. Finally, these transactions have to be placed to design bus, this will be done by RAL component Adapter.

The RAL adapter acts as a converter between the RAL model and Interface. It converts transactions of RAL methods to Interface/Bus transactions.

  • The Adapter converts between register model read, write methods and the interface-specific transactions
  • The transaction adapter is implemented by extending the uvm_reg_adapter class and implementing the reg2bus() and bus2reg() methods

UVM RAL reg2bus

  • reg2bus method converts the RAL transactions to Interface (bus) transactions

UVM RAL bus2reg

  • bus2reg method converts the Interface (bus) transactions to RAL transactions
class tb_env extends uvm_env;
  reg_model                     regmodel;
  uvm_reg_predictor#(ahb_trans) ahb2reg_predictor;
  reg2ahb_adapter               reg_adapter;
  ahb_agent                     ahb;

  virtual function void build_phase(uvm_phase phase);
    ahb2reg_predictor = new(“ahb2reg_predictor”, this);
  endfunction

  virtual function void connect_phase(uvm_phase phase);
    if (regmodel.get_parent() == null) begin
      reg_adapter = reg2ahb_adapter::type_id::create(“reg_adapter”,,get_full_name());
      ...
      ahb2reg_predictor.map     = regmodel.AHB;
      ahb2reg_predictor.adapter = reg_adapter;
      ahb.monitor.ap.connect(ahb2reg_predictor.bus_in);
    end
    ...
  endfunction
  ...
endclass

❮ Previous Next ❯

UVM RAL Predictor

UVM Register Model Predictor

We know that the UVM Register model maintains the latest design register values in it. but how Register model will get to know the latest values?

This will be done by the UVM RAL component Predictor.

UVM RAL Predictor predicts the register access done through the register model and updates the RAL Model registers. UVM RAL provides the base class uvm_reg_predictor. uvm_reg_predictor updates the register model based on observed transactions published by a monitor.

  • Any register access done through the register model is predicted and updated inherently by the base classes (implicit prediction)
  • Prediction of the register values in the model can also be done explicitly using an external predictor which also takes care of interface bus transactions not occurring through the register model (explicit prediction)

Implicit prediction

  • Implicit prediction only requires the integration of the register model with one or more bus sequencers
  • Updates to the mirror are predicted automatically (i.e., implicitly) by the register model after the completion of each read, write, peek, or poke operation

Explicit prediction

  • Explicit prediction requires the register model to be integrated with both the bus sequencers and corresponding bus monitors
  • In this mode, the implicit prediction is turned off and all updates to the mirror are predicted externally (i.e., explicitly) by a uvm_reg_predictor component, one for each bus interface

❮ Previous Next ❯

UVM REG EXAMPLE WITHOUT RAL

Register Access without RAL Model

In this section will see an example that shows one of the ways to access DUT registers without the UVM RAL Model.

Let’s consider a DMA design which consists of registers in it and reg_interface is used to access the registers.

Below is the block diagram of DMA.

UVM RAL Example
UVM RAL Example

Below are the DMA registers,

  • INTR
  • CTRL
  • IO ADDR
  • MEM ADDR

Address of each register and register field description is given below,

Reg Name Address Field Description
INTR h400 31:16 15:0
MASK STATUS
CTRL h404 31:10 9 8:1 0
RESVD IO_MEM W_COUNT START_DMA
IO_ADDR h408 31:0
ADDRESS
MEM_ADDR h40C 31:0
ADDRESS

Below is the testbench block diagram,

UVM TestBench Register Access
UVM TestBench Register Access

The testbench component DMA agent is used to access the reg_interface.
DMA Agent consists of,

  • Driver
  • Monitor
  • Sequencer
  • Sequences (Write sequence and Read Sequence)

In testbench,

  • Write to the register is done by calling the write sequence
  • Read of the register is done by calling the read sequence

This testbench is UVM based testbench. For ease understanding, only the sequence and test case is explained in this section. For detailed steps on writing UVM Testbench refer to UVM Testbench Architecture.

Base Sequence

class dma_sequence extends uvm_sequence#(dma_seq_item);
  
  `uvm_object_utils(dma_sequence)
  
  //--------------------------------------- 
  //Constructor
  //---------------------------------------
  function new(string name = "dma_sequence");
    super.new(name);
  endfunction
  
  `uvm_declare_p_sequencer(dma_sequencer)
  
  //---------------------------------------
  // create, randomize and send the item to driver
  //---------------------------------------
  virtual task body();
   repeat(2) begin
    req = dma_seq_item::type_id::create("req");
    wait_for_grant();
    req.randomize();
    send_request(req);
    wait_for_item_done();
   end 
  endtask
endclass

Write Sequence

class write_sequence extends uvm_sequence#(dma_seq_item);
  bit [31:0] t_addr,t_data;
  
  `uvm_object_utils(write_sequence)
   
  //--------------------------------------- 
  //Constructor
  //---------------------------------------
  function new(string name = "write_sequence");
    super.new(name);
  endfunction
  
  virtual task body();
    `uvm_do_with(req,{req.wr_en==1;req.addr==t_addr;req.wdata==t_data;})
  endtask
endclass

Read Sequence

class read_sequence extends uvm_sequence#(dma_seq_item);
  bit [31:0] t_addr;
  
  `uvm_object_utils(read_sequence)
   
  //--------------------------------------- 
  //Constructor
  //---------------------------------------
  function new(string name = "read_sequence");
    super.new(name);
  endfunction
  
  virtual task body();
    `uvm_do_with(req,{req.wr_en==0;req.addr==t_addr;})
  endtask
endclass

Register Accessing:

Write or Read operation to any SFR is done by calling the write or read sequence respectively.

Write Operation:

    //Write to register INTR
    wr_seq.t_addr = 32'h400;
    wr_seq.t_data = 32'hFFFF_0F0F;
    wr_seq.start(env.dma_agnt.sequencer);
    
    //Write to register CTRL
    wr_seq.t_addr = 32'h404;
    wr_seq.t_data = 32'h1234_5678;
    wr_seq.start(env.dma_agnt.sequencer);
    
    //Write to register IO_ADDR
    wr_seq.t_addr = 32'h408;
    wr_seq.t_data = 32'hABCD_EF12;
    wr_seq.start(env.dma_agnt.sequencer);
    
    //Write to register MEM_ADDR
    wr_seq.t_addr = 32'h40C;
    wr_seq.t_data = 32'h9731_2345;
    wr_seq.start(env.dma_agnt.sequencer);

Read Operation:

    //Read from register INTR
    rd_seq.t_addr = 32'h400;
    rd_seq.start(env.dma_agnt.sequencer);
    
    //Read from register CTRL
    rd_seq.t_addr = 32'h404;
    rd_seq.start(env.dma_agnt.sequencer);
    
    //Read from register IO_ADDR
    rd_seq.t_addr = 32'h408;
    rd_seq.start(env.dma_agnt.sequencer);
    
    //Read from register MEM_ADDR
    rd_seq.t_addr = 32'h40C;
    rd_seq.start(env.dma_agnt.sequencer);  

In the above code, Register Address is assigned for register access. for the complex designs, the number of registers will be more. readability and debug will be difficult. to overcome this we can use define for register address.

Register address defines

`define INTR_SFR_ADDR     32'h400
`define CTRL_SFR_ADDR     32'h404
`define IO_ADDR_SFR_ADDR  32'h408
`define MEM_ADDR_SFR_ADDR 32'h40C

Write and Read operation using defines

Write Operation:

    wr_seq.t_addr = `INTR_SFR_ADDR;
    wr_seq.t_data = 32'hFFFF_0F0F;
    wr_seq.start(env.dma_agnt.sequencer);
    
    wr_seq.t_addr = `CTRL_SFR_ADDR;
    wr_seq.t_data = 32'h1234_5678;
    wr_seq.start(env.dma_agnt.sequencer);
    
    wr_seq.t_addr = `IO_ADDR_SFR_ADDR;
    wr_seq.t_data = 32'hABCD_EF12;
    wr_seq.start(env.dma_agnt.sequencer);
    
    wr_seq.t_addr = `MEM_ADDR_SFR_ADDR;
    wr_seq.t_data = 32'h9731_2345;
    wr_seq.start(env.dma_agnt.sequencer);

Read Operation:

    rd_seq.t_addr = `INTR_SFR_ADDR;
    rd_seq.start(env.dma_agnt.sequencer);
    
    rd_seq.t_addr = `CTRL_SFR_ADDR;
    rd_seq.start(env.dma_agnt.sequencer);
    
    rd_seq.t_addr = `IO_ADDR_SFR_ADDR;
    rd_seq.start(env.dma_agnt.sequencer);
    
    rd_seq.t_addr = `MEM_ADDR_SFR_ADDR;
    rd_seq.start(env.dma_agnt.sequencer);  

Accessing registers from TestCase

class dma_reg_test extends uvm_test;

  `uvm_component_utils(dma_reg_test)
  
  //---------------------------------------
  // env instance 
  //--------------------------------------- 
  dma_model_env env;
  
  //---------------------------------------
  // sequence instance 
  //--------------------------------------- 
  write_sequence wr_seq;
  read_sequence  rd_seq;

  //---------------------------------------
  // constructor
  //---------------------------------------
  function new(string name = "dma_reg_test",uvm_component parent=null);
    super.new(name,parent);
  endfunction : new

  //---------------------------------------
  // build_phase
  //---------------------------------------
  virtual function void build_phase(uvm_phase phase);
    super.build_phase(phase);

    // Create the env
    env = dma_model_env::type_id::create("env", this);
    
    // Create the sequence
    wr_seq = write_sequence::type_id::create("wr_seq");
    rd_seq = read_sequence::type_id::create("rd_seq");
  endfunction : build_phase

  //---------------------------------------
  // end_of_elobaration phase
  //---------------------------------------  
  virtual function void end_of_elaboration();
    //print's the topology
    print();
  endfunction
  
  //---------------------------------------
  // run_phase - starting the test
  //---------------------------------------
  task run_phase(uvm_phase phase);    
    phase.raise_objection(this);
    
    wr_seq.t_addr = `INTR_SFR_ADDR;
    wr_seq.t_data = 32'hFFFF_0F0F;
    wr_seq.start(env.dma_agnt.sequencer);
    
    wr_seq.t_addr = `CTRL_SFR_ADDR;
    wr_seq.t_data = 32'h1234_5678;
    wr_seq.start(env.dma_agnt.sequencer);
    
    wr_seq.t_addr = `IO_ADDR_SFR_ADDR;
    wr_seq.t_data = 32'hABCD_EF12;
    wr_seq.start(env.dma_agnt.sequencer);
    
    wr_seq.t_addr = `MEM_ADDR_SFR_ADDR;
    wr_seq.t_data = 32'h9731_2345;
    wr_seq.start(env.dma_agnt.sequencer);

    rd_seq.t_addr = `INTR_SFR_ADDR;
    rd_seq.start(env.dma_agnt.sequencer);
    
    rd_seq.t_addr = `CTRL_SFR_ADDR;
    rd_seq.start(env.dma_agnt.sequencer);
    
    rd_seq.t_addr = `IO_ADDR_SFR_ADDR;
    rd_seq.start(env.dma_agnt.sequencer);
    
    rd_seq.t_addr = `MEM_ADDR_SFR_ADDR;
    rd_seq.start(env.dma_agnt.sequencer);    
    phase.drop_objection(this);
  endtask : run_phase
  
endclass : dma_reg_test

Simulator Output

UVM_INFO @ 0: reporter [RNTST] Running test dma_reg_test...
----------------------------------------------------------------
Name Type Size Value
----------------------------------------------------------------
uvm_test_top dma_reg_test - @1881
 env dma_model_env - @1949
 dma_agnt dma_agent - @2017
 driver dma_driver - @2128
 rsp_port uvm_analysis_port - @2200
 seq_item_port uvm_seq_item_pull_port - @2163
 monitor dma_monitor - @2048
 item_collected_port uvm_analysis_port - @2095
 sequencer dma_sequencer - @2231
 rsp_export uvm_analysis_export - @2276
 seq_item_export uvm_seq_item_pull_imp - @2686
 arbitration_queue array 0 - 
 lock_queue array 0 - 
 num_last_reqs integral 32 'd1 
 num_last_rsps integral 32 'd1 
----------------------------------------------------------------
UVM_INFO /playground_lib/uvm-1.2/src/base/uvm_objection.svh(1271) @ 195: reporter [TEST_DONE] 
UVM_INFO /playground_lib/uvm-1.2/src/base/uvm_report_server.svh(847) @ 195: reporter 
--- UVM Report Summary ---

** Report counts by severity
UVM_INFO : 3
UVM_WARNING : 0
UVM_ERROR : 0
UVM_FATAL : 0
** Report counts by id
[RNTST] 1
[TEST_DONE] 1
[UVM/RELNOTES] 1 

Click to execute on   

Write and Read Waveform:

UVM Register WR and RD Operation
UVM Register WR and RD Operation

Write Waveform:

UVM Register WR
UVM Register WR

Read Waveform:

UVM Register RD
UVM Register RD

❮ Previous Next ❯

Integrating RAL to Bus Agent

Integrating RAL to Agent

Once after the RAL implementation, RAL has to be connected with the Bus Agent. This section describes connecting RAL with the sequencer and monitor of the bus.

UVM RAL TestBench
UVM RAL TestBench

Integrating Bus Sequencers

All integration approaches require a register model to be configured with one or more bus sequencers.

The register model becomes a property of a uvm_reg_sequence subtype that executes

  • Directly on a bus sequencer, if there is only one bus interface providing access to the DUT registers
  • As a virtual sequence, if there are one or more bus interfaces providing access to the DUT registers;
  • As a register sequence running

Integrating the Register Model with a Bus Monitor

By default, the register model updates its mirror copy of the register values implicitly. Every time a register is read or written through the register model, its mirror value is updated.

If other agents on the bus interface perform read and write transactions outside the context of the register model, the register model must learn of these bus operations to update its mirror accordingly.

  • Integration is accomplished by first instantiating a uvm_reg_predictor component
  • uvm_reg_predictor component is then connected to the bus monitor’s analysis port
class tb_env extends uvm_env;
  reg_model                     regmodel;
  uvm_reg_predictor#(ahb_trans) ahb2reg_predictor;
  reg2ahb_adapter               reg_adapter;
  ahb_agent                     ahb;

  virtual function void build_phase(uvm_phase phase);
    ahb2reg_predictor = new(“ahb2reg_predictor”, this);
  endfunction

  virtual function void connect_phase(uvm_phase phase);
    if (regmodel.get_parent() == null) begin
      reg_adapter = reg2ahb_adapter::type_id::create(“reg_adapter”,,get_full_name());
      ...
      ahb2reg_predictor.map     = regmodel.AHB;
      ahb2reg_predictor.adapter = reg_adapter;
      ahb.monitor.ap.connect(ahb2reg_predictor.bus_in);
    end
    ...
  endfunction
  ...
endclass

❮ Previous Next ❯

UVM RAL Base Classes

UVM RAL Model types, enums and utility classes

This section describes UVM RAL model types, enums and utility classes.

UVM RAL types

uvm_reg_data_t

2-state data value with `UVM_REG_DATA_WIDTH bits

uvm_reg_data_logic_t

4-state data value with `UVM_REG_DATA_WIDTH bits

uvm_reg_addr_t

2-state address value with `UVM_REG_ADDR_WIDTH bits

uvm_reg_addr_logic_t

4-state address value with `UVM_REG_ADDR_WIDTH bits

uvm_reg_byte_en_t

2-state byte_enable value with `UVM_REG_BYTENABLE_WIDTH bits

uvm_reg_cvr_t

Coverage model value set with `UVM_REG_CVR_WIDTH bits

uvm_hdl_path_slice

Slice of an HDL path

UVM RAL Enums

uvm_status_e

Return status for register operations

  • UVM_IS_OK
    • Operation completed successfully
  • UVM_NOT_OK
    • Operation completed with error
  • UVM_HAS_X
    • Operation completed successfully bit had unknown bits.

uvm_path_e

Path used for register operation

  • UVM_FRONTDOOR
    • Use the front door
  • UVM_BACKDOOR
    • Use the back door
  • UVM_PREDICT
    • Operation derived from observations by a bus monitor via the uvm_reg_predictor class
  • UVM_DEFAULT_PATH
    • Operation specified by the context

uvm_check_e

Read-only or read-and-check

  • UVM_NO_CHECK
    • Read only
  • UVM_CHECK
    • Read and check

uvm_endianness_e

Specifies byte ordering

  • UVM_NO_ENDIAN
    • Byte ordering not applicable
  • UVM_LITTLE_ENDIAN
    • Least-significant bytes first in consecutive addresses
  • UVM_BIG_ENDIAN
    • Most-significant bytes first in consecutive addresses
  • UVM_LITTLE_FIFO
    • Least-significant bytes first at the same address
  • UVM_BIG_FIFO
    • Most-significant bytes first at the same address

uvm_elem_kind_e

Type of element being read or written

  • UVM_REG
    • Register
  • UVM_FIELD
    • Field
  • UVM_MEM
    • Memory location

uvm_access_e

Type of operation begin performed

  • UVM_READ
    • Read operation
  • UVM_WRITE
    • Write operation

uvm_hier_e

Whether to provide the requested information from a hierarchical context.

  • UVM_NO_HIER
    • Provide info from the local context
  • UVM_HIER
    • Provide info based on the hierarchical context

uvm_predict_e

How the mirror is to be updated

  • UVM_PREDICT_DIRECT
    • Predicted value is as-is
  • UVM_PREDICT_READ
    • Predict based on the specified value having been read
  • UVM_PREDICT_WRITE
    • Predict based on the specified value having been written

uvm_coverage_model_e

Coverage models available or desired.  Multiple models may be specified by bitwise
OR’ing individual model identifiers.

  • UVM_NO_COVERAGE
    • None
  • UVM_CVR_REG_BITS
    • Individual register bits
  • UVM_CVR_ADDR_MAP
    • Individual register and memory addresses
  • UVM_CVR_FIELD_VALS
    • Field values
  • UVM_CVR_ALL
    • All coverage models

uvm_reg_mem_tests_e

Select which pre-defined test sequence to execute. Multiple test sequences may be selected by bit-wise OR’ing their respective symbolic values.

  • UVM_DO_REG_HW_RESET
    • Run uvm_reg_hw_reset_seq
  • UVM_DO_REG_BIT_BASH
    • Run uvm_reg_bit_bash_seq
  • UVM_DO_REG_ACCESS
    • Run uvm_reg_access_seq
  • UVM_DO_MEM_ACCESS
    • Run uvm_mem_access_seq
  • UVM_DO_SHARED_ACCESS
    • Run uvm_reg_mem_shared_access_seq
  • UVM_DO_MEM_WALK
    • Run uvm_mem_walk_seq
  • UVM_DO_ALL_REG_MEM_TESTS
    • Run all of the above

Test sequences, when selected, are executed in the order in which they are specified
above.
❮ Previous Next ❯