Tutorials

Learn More

Let’s understand how the register model is constructed, integrate it with the verification environment, and access the DUT register using read and write methods.

The design has four registers as control, interrupt status, mask status, debug, etc.

Registers in DUT

Register description

Register Name

Address

Range

Access

Reset

Field Name

Control

‘h0

[31:0]

32‘h5

[31:3]

RO

29’h0

Reserved

2

RW

1’h1

parity_en

1

RW

1’h0

dbg_en

0

RW

1’h1

mod_en

Register Name

Address

Range

Access

Reset

Field Name

Interrupt Status

‘h4

[31:0]

32‘h0

[31:2]

RO

30’h0

Reserved

1

W1C

1’h0

r_axi_err

0

W1C

1’h0

w_axi_err

Register Name

Address

Range

Access

Reset

Field Name

Interrupt Mask

‘h8

[31:0]

32‘h0

[31:2]

RO

30’h0

Reserved

1

RW

1’h0

r_axi_err_msk

0

RW

1’h0

w_axi_err_msk

Register Name

Address

Range

Access

Reset

Field Name

Debug

‘hc

[31:0]

32‘h0

[31:2]

RO

30’h0

Reserved

1

RO

1’h0

r_axi_resp

0

RO

1’h0

w_axi_resp

Testbench block diagram

Testbench with register model

Testbench hierarchy

--------------------------------------------------------------
Name                       Type                    Size  Value
--------------------------------------------------------------
uvm_test_top               reg_test                -     @1878
 env_o                    env                     -     @1944
   agt                    agent                   -     @1976
     drv                  driver                  -     @2301
       rsp_port           uvm_analysis_port       -     @2332
       seq_item_port      uvm_seq_item_pull_port  -     @2238
     mon                  monitor                 -     @3013
       item_collect_port  uvm_analysis_port       -     @3063
     seqr                 seqcr                   -     @2365
       rsp_export         uvm_analysis_export     -     @2422
       seq_item_export    uvm_seq_item_pull_imp   -     @2982
       ...
--------------------------------------------------------------

Now, let’s see how the register model, adapter is created and integrated with the testbench environment.

Overall steps

  1. Create a register (RAL) model
  2. Write an adapter/Predictor class
  3. Integrate register model and adapter with the testbench
  4. Access registers using RAL methods.

Step 1: Steps to create a register (RAL) model

Step A: Write register classes

Write register classes (derived from uvm_reg) for all registers in DUT

Control register:

class ral_control_reg extends uvm_reg;
  rand uvm_reg_field rsvd;
  rand uvm_reg_field parity_en;
  rand uvm_reg_field dbg_en;
  rand uvm_reg_field mod_en;
  
  `uvm_object_utils(ral_control_reg)
  function new(string name = "ral_control_reg");
    super.new(name, 32, build_coverage(UVM_NO_COVERAGE));
  endfunction
  
  virtual function void build();
    rsvd = uvm_reg_field::type_id::create("rsvd");
    parity_en = uvm_reg_field::type_id::create("parity_en");
    dbg_en = uvm_reg_field::type_id::create("dbg_en");
    mod_en = uvm_reg_field::type_id::create("mod_en");
    
    rsvd.configure     (this, 29, 3, "RO", 0, 1'b0, 1, 1, 0);
    parity_en.configure(this,  1, 2, "RW", 0, 1'b1, 1, 1, 0);
    dbg_en.configure   (this,  1, 1, "RW", 0, 1'b0, 1, 1, 0);
    mod_en.configure   (this,  1, 0, "RW", 0, 1'b1, 1, 1, 0);
  endfunction
endclass

Interrupt status register:

class ral_intr_sts_reg extends uvm_reg;
  rand uvm_reg_field rsvd;
  rand uvm_reg_field r_axi_err;
  rand uvm_reg_field w_axi_err;
  
  `uvm_object_utils(ral_intr_sts_reg)
  function new(string name = "ral_intr_sts_reg");
    super.new(name, 32, build_coverage(UVM_NO_COVERAGE));
  endfunction
  
  virtual function void build();
    rsvd = uvm_reg_field::type_id::create("rsvd");
    r_axi_err = uvm_reg_field::type_id::create("r_axi_err");
    w_axi_err = uvm_reg_field::type_id::create("w_axi_err");
    
    rsvd.configure        (this, 30, 2, "RO", 0, 1'b0, 1, 1, 0);
    r_axi_err.configure   (this,  1, 1, "W1C", 0, 1'b0, 1, 1, 0);
    w_axi_err.configure   (this,  1, 0, "W1C", 0, 1'b0, 1, 1, 0);
  endfunction
endclass

Interrupt mask register:

class ral_intr_msk_reg extends uvm_reg;
  rand uvm_reg_field rsvd;
  rand uvm_reg_field r_axi_err_msk;
  rand uvm_reg_field w_axi_err_msk;
  
  `uvm_object_utils(ral_intr_msk_reg)
  function new(string name = "ral_intr_msk_reg");
    super.new(name, 32, build_coverage(UVM_NO_COVERAGE));
  endfunction
  
  virtual function void build();
    rsvd = uvm_reg_field::type_id::create("rsvd");
    r_axi_err_msk = uvm_reg_field::type_id::create("r_axi_err_msk");
    w_axi_err_msk = uvm_reg_field::type_id::create("w_axi_err_msk");
    
    rsvd.configure          (this, 30, 2, "RO", 0, 1'b0, 1, 1, 0);
    r_axi_err_msk.configure (this,  1, 1, "RW", 0, 1'b0, 1, 1, 0);
    w_axi_err_msk.configure (this,  1, 0, "RW", 0, 1'b1, 1, 1, 0);
  endfunction
endclass

Debug register:

class ral_debug_reg extends uvm_reg;
  rand uvm_reg_field rsvd;
  rand uvm_reg_field r_axi_resp;
  rand uvm_reg_field w_axi_resp;
  
  `uvm_object_utils(ral_debug_reg)
  function new(string name = "ral_debug_reg");
    super.new(name, 32, build_coverage(UVM_NO_COVERAGE));
  endfunction
  
  virtual function void build();
    rsvd = uvm_reg_field::type_id::create("rsvd",);
    r_axi_resp = uvm_reg_field::type_id::create("r_axi_resp");
    w_axi_resp = uvm_reg_field::type_id::create("w_axi_resp");
    
    rsvd.configure      (this, 30, 2, "RO", 0, 1'b0, 1, 1, 0);
    r_axi_resp.configure(this,  1, 1, "RO", 0, 1'b0, 1, 1, 0);
    w_axi_resp.configure(this,  1, 0, "RO", 0, 1'b0, 1, 1, 0);
  endfunction
endclass

Step B: Write a register block

 Write a register block (derived from uvm_reg_block) that captures all the registers.

class module_reg extends uvm_reg_block;
  rand ral_control_reg  control_reg;
  rand ral_intr_sts_reg intr_sts_reg;
  rand ral_intr_msk_reg intr_msk_reg;
  rand ral_debug_reg    debug_reg;
  
  `uvm_object_utils(module_reg)
  function new(string name = "module_reg");
    super.new(name);
  endfunction
  
  virtual function void build();
    control_reg = ral_control_reg::type_id::create("control_reg");
    control_reg.configure(this, null);
    control_reg.build();
    
    intr_sts_reg = ral_intr_sts_reg::type_id::create("intr_sts_reg");
    intr_sts_reg.configure(this, null);
    intr_sts_reg.build();
    
    intr_msk_reg = ral_intr_msk_reg::type_id::create("intr_msk_reg");
    intr_msk_reg.configure(this, null);
    intr_msk_reg.build();
    
    debug_reg = ral_debug_reg::type_id::create("debug_reg");
    debug_reg.configure(this, null);
    debug_reg.build();
    
    default_map = create_map("", `UVM_REG_ADDR_WIDTH'h0, 4, UVM_LITTLE_ENDIAN, 1);
    
    this.default_map.add_reg(control_reg,  `UVM_REG_ADDR_WIDTH'h0, "RW");
    this.default_map.add_reg(intr_sts_reg, `UVM_REG_ADDR_WIDTH'h4, "RW");
    this.default_map.add_reg(intr_msk_reg, `UVM_REG_ADDR_WIDTH'h8, "RW");
    this.default_map.add_reg(debug_reg,    `UVM_REG_ADDR_WIDTH'hc, "RW");
  endfunction
endclass

Step C: Write a top-level register block

Write a top-level register block that captures multiple register blocks known as register models.

class RegModel_SFR extends uvm_reg_block;
  rand module_reg mod_reg;
  
  uvm_reg_map axi_map;
  `uvm_object_utils(RegModel_SFR)
  
  function new(string name = "RegModel_SFR");
    super.new(name, .has_coverage(UVM_NO_COVERAGE));
  endfunction
  
  virtual function void build();
    default_map = create_map("axi_map", 'h0, 4, UVM_LITTLE_ENDIAN, 0);
   
    mod_reg = module_reg::type_id::create("mod_reg");
    mod_reg.configure(this);
    mod_reg.build();
    default_map.add_submap(this.mod_reg.default_map, 0);
  endfunction
endclass

Note: In this example, we have only one block module_reg class. Hence, this step is optional and module_reg can be a top level register model.

Step 2: Write an adapter class

class reg_axi_adapter extends uvm_reg_adapter;
  `uvm_object_utils(reg_axi_adapter)
  
  function new(string name = "reg_axi_adapter");
    super.new(name);
  endfunction
  
  virtual function uvm_sequence_item reg2bus (const ref uvm_reg_bus_op rw);
    seq_item bus_item = seq_item::type_id::create("bus_item");
    bus_item.addr = rw.addr;
    bus_item.data = rw.data;
    bus_item.rd_or_wr = (rw.kind == UVM_READ) ? 1: 0;
    
    `uvm_info(get_type_name, $sformatf("reg2bus: addr = %0h, data = %0h, rd_or_wr = %0h", bus_item.addr, bus_item.data, bus_item.rd_or_wr), UVM_LOW);
    return bus_item;
  endfunction
  
  virtual function void bus2reg (uvm_sequence_item bus_item, ref uvm_reg_bus_op rw);
    seq_item bus_pkt;
    if(!$cast(bus_pkt, bus_item))
      `uvm_fatal(get_type_name(), "Failed to cast bus_item transaction")

    rw.addr = bus_pkt.addr;
    rw.data = bus_pkt.data;
    rw.kind = (bus_pkt.rd_or_wr) ? UVM_READ: UVM_WRITE;
  endfunction
endclass

Step 3: Integrate register model and adapter with the testbench

class env extends uvm_env;
  `uvm_component_utils(env)
  agent agt;
  reg_axi_adapter adapter;
  RegModel_SFR reg_model;
  function new(string name = "env", uvm_component parent = null);
    super.new(name, parent);
  endfunction
  
  function void build_phase(uvm_phase phase);
    super.build_phase(phase);
    agt = agent::type_id::create("agt", this);
    adapter = reg_axi_adapter::type_id::create("adapter");
    reg_model = RegModel_SFR::type_id::create("reg_model");
    reg_model.build();
    reg_model.reset();
    reg_model.lock_model();
    reg_model.print();
    
    uvm_config_db#(RegModel_SFR)::set(uvm_root::get(), "*", "reg_model", reg_model);
  endfunction
  
  function void connect_phase(uvm_phase phase);
    super.connect_phase(phase);
    reg_model.default_map.set_sequencer( .sequencer(agt.seqr), .adapter(adapter) );
    reg_model.default_map.set_base_addr('h0);        
    //regmodel.add_hdl_path("tb_top.DUT");
  endfunction
endclass

Register Model hierarchy

--------------------------------------------------------------------------------
Name                 Type              Size  Value                             
--------------------------------------------------------------------------------
reg_model            RegModel_SFR      -     @2008                             
 mod_reg            module_reg        -     @2030                             
   control_reg      ral_control_reg   -     @2055                             
     mod_en         uvm_reg_field     ...    RW control_reg[0:0]=1'h1         
     dbg_en         uvm_reg_field     ...    RW control_reg[1:1]=1'h0         
     parity_en      uvm_reg_field     ...    RW control_reg[2:2]=1'h1         
     rsvd           uvm_reg_field     ...    RO control_reg[31:3]=29'h00000000
   intr_sts_reg     ral_intr_sts_reg  -     @2092                             
     w_axi_err      uvm_reg_field     ...    W1C intr_sts_reg[0:0]=1'h0       
     r_axi_err      uvm_reg_field     ...    W1C intr_sts_reg[1:1]=1'h0       
     rsvd           uvm_reg_field     ...    RO intr_sts_reg[31:2]=30'h00000000
   intr_msk_reg     ral_intr_msk_reg  -     @2116                             
     w_axi_err_msk  uvm_reg_field     ...    RW intr_msk_reg[0:0]=1'h1        
     r_axi_err_msk  uvm_reg_field     ...    RW intr_msk_reg[1:1]=1'h0        
     rsvd           uvm_reg_field     ...    RO intr_msk_reg[31:2]=30'h00000000
   debug_reg        ral_debug_reg     -     @2140                             
     w_axi_resp     uvm_reg_field     ...    RO debug_reg[0:0]=1'h0           
     r_axi_resp     uvm_reg_field     ...    RO debug_reg[1:1]=1'h0           
     rsvd           uvm_reg_field     ...    RO debug_reg[31:2]=30'h00000000  
   uvm_reg_map      uvm_reg_map       -     @2164                             
     endian                           ...   UVM_LITTLE_ENDIAN                 
     control_reg    ral_control_reg   ...   @2055 +'h0                        
     intr_sts_reg   ral_intr_sts_reg  ...   @2092 +'h4                        
     intr_msk_reg   ral_intr_msk_reg  ...   @2116 +'h8                        
     debug_reg      ral_debug_reg     ...   @2140 +'hc                        
 axi_map            uvm_reg_map       -     @2021                             
   endian                             ...   UVM_LITTLE_ENDIAN                 
   uvm_reg_map      uvm_reg_map       -     @2164                             
     endian                           ...   UVM_LITTLE_ENDIAN                 
     control_reg    ral_control_reg   ...   @2055 +'h0                        
     intr_sts_reg   ral_intr_sts_reg  ...   @2092 +'h4                        
     intr_msk_reg   ral_intr_msk_reg  ...   @2116 +'h8                        
     debug_reg      ral_debug_reg     ...   @2140 +'hc                        
--------------------------------------------------------------------------------

Step 4: Access registers using RAL methods

reg_model.mod_reg.control_reg.write(status, 32'h1234_1234);
reg_model.mod_reg.control_reg.read(status, read_data);
    
reg_model.mod_reg.intr_msk_reg.write(status, 32'h5555_5555);
reg_model.mod_reg.intr_msk_reg.read(status, read_data);
 
reg_model.mod_reg.debug_reg.write(status, 32'hAAAA_AAAA);
reg_model.mod_reg.debug_reg.read(status, read_data);

Execute Complete code

Output:

UVM_INFO base_seq.sv(27) @ 0: uvm_test_top.env_o.agt.seqr@@rseq [reg_seq] Reg seq: Inside Body
UVM_INFO reg2axi_adapter.sv(14) @ 0: reporter [reg_axi_adapter] reg2bus: addr = 0, data = 12341234, rd_or_wr = 0
UVM_INFO driver.sv(37) @ 6: uvm_test_top.env_o.agt.drv [driver] waddr = 0, wdata = 12341234
UVM_INFO reg2axi_adapter.sv(14) @ 6: reporter [reg_axi_adapter] reg2bus: addr = 0, data = 0, rd_or_wr = 1
UVM_INFO driver.sv(30) @ 14: uvm_test_top.env_o.agt.drv [driver] raddr = 0, rdata = 12341234
UVM_INFO reg2axi_adapter.sv(14) @ 14: reporter [reg_axi_adapter] reg2bus: addr = 8, data = 55555555, rd_or_wr = 0
UVM_INFO driver.sv(37) @ 22: uvm_test_top.env_o.agt.drv [driver] waddr = 8, wdata = 55555555
UVM_INFO reg2axi_adapter.sv(14) @ 22: reporter [reg_axi_adapter] reg2bus: addr = 8, data = 0, rd_or_wr = 1
UVM_INFO driver.sv(30) @ 30: uvm_test_top.env_o.agt.drv [driver] raddr = 8, rdata = 55555555
UVM_INFO reg2axi_adapter.sv(14) @ 30: reporter [reg_axi_adapter] reg2bus: addr = c, data = aaaaaaaa, rd_or_wr = 0
UVM_INFO driver.sv(37) @ 38: uvm_test_top.env_o.agt.drv [driver] waddr = c, wdata = aaaaaaaa
UVM_INFO reg2axi_adapter.sv(14) @ 38: reporter [reg_axi_adapter] reg2bus: addr = c, data = 0, rd_or_wr = 1
UVM_INFO driver.sv(30) @ 46: uvm_test_top.env_o.agt.drv [driver] raddr = c, rdata = aaaaaaaa
UVM_INFO base_test.sv(19) @ 46: uvm_test_top [reg_test] End of testcase