RAL Model Example

This UVM testbench demonstrates register read/write operations using UVM Register Abstraction Layer (RAL) without an actual DUT.

Explanation of Components:

  • Registers (apb_ctrl_reg, apb_status_reg):
    • The register model represents memory-mapped registers in the DUT (even though we don’t have a DUT in this case).
    • apb_ctrl_reg: Read/Write register with enable and mode fields. (at address 0x0)
    • apb_status_reg: Read-Only register with ready and error fields. (at address 0x4)

  • Register Block (apb_reg_block):
    • Contains ctrl_reg at address 0x00 and status_reg at 0x04.
    • It extends uvm_reg_block and contains individual registers.
    • Uses create_map to set up register mapping.
    • ctrl_reg is read/write, while status_reg is read-only.
    • No DUT connection is defined, so reads return a default value.
class reg_model extends uvm_reg_block;
  
  uvm_reg ctrl_reg;   // Control Register
  uvm_reg status_reg; // Status Register
  uvm_reg_map default_map;

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

  virtual function void build();
    set_default_map(null); // No bus interface (since no DUT)

    // Create CTRL Register
    ctrl_reg = uvm_reg::type_id::create("ctrl_reg");
    ctrl_reg.configure(this, null, "");
    ctrl_reg.build();
    ctrl_reg.add_hdl_path_slice("CTRL", 0, 32);

    // Create STATUS Register
    status_reg = uvm_reg::type_id::create("status_reg");
    status_reg.configure(this, null, "");
    status_reg.build();
    status_reg.add_hdl_path_slice("STATUS", 0, 32);

    // Define register map (addr: reg instance)
    default_map = create_map("default_map", 0, 4, UVM_LITTLE_ENDIAN);
    default_map.add_reg(ctrl_reg, 0, "RW");    // CTRL at address 0x0
    default_map.add_reg(status_reg, 4, "RO"); // STATUS at address 0x4
  endfunction

endclass
  • APB Transaction (apb_transaction):
    • Defines an APB protocol transaction with addr, data, and read fields.
class apb_transaction extends uvm_sequence_item;
  rand bit [31:0] addr;
  rand bit [31:0] data;
  rand bit read;

  function new(string name = "apb_transaction");
    super.new(name);
  endfunction

  `uvm_object_utils(apb_transaction)
endclass
  • Creating the driver:
    • The driver receives register transactions from the sequencer and converts them into APB transactions.
    • Implementing uvm_driver to perform APB Read/Write operations.
    • The driver listens for APB transactions from the sequencer.
    • Reads return 0xDEADBEEF, since there’s no DUT.
class apb_driver extends uvm_driver#(apb_transaction);
  `uvm_component_utils(apb_driver)
  
  apb_transaction tr;
  
  function new(string name = "apb_driver", uvm_component parent = null);
    super.new(name, parent);
  endfunction

  virtual task run_phase(uvm_phase phase);
    forever begin
      seq_item_port.get_next_item(tr); // Get transaction from sequencer
      `uvm_info("DRV", $sformatf("Driving transaction: addr=0x%0h, data=0x%0h, read=%0b", tr.addr, tr.data, tr.read), UVM_MEDIUM)

      // Simulate APB write or read operation
      if (!tr.read) begin
        `uvm_info("DRV", "Performing APB Write", UVM_MEDIUM)
        // Apply signals to the APB interface for write
      end else begin
        `uvm_info("DRV", "Performing APB Read", UVM_MEDIUM)
        // Apply signals for read operation and return data
        tr.data = 32'hDEADBEEF;  // Simulated read response
      end

      seq_item_port.item_done();
    end
  endtask
endclass
  • Creating the Sequence :
    • Writing 0x3 to the CTRL register.
    • Reading back the CTRL register. (addr=0x0).
    • Reading the STATUS register. (addr=0x4).
class apb_reg_sequence extends uvm_reg_sequence;
  `uvm_object_utils(apb_reg_sequence)
  
  uvm_status_e status;
  bit [31:0] data;
  apb_reg_block reg_model;

  function new(string name = "apb_reg_sequence");
    super.new(name);
  endfunction

  virtual task body();
    
    // Cast `model` to `apb_reg_block`
    if (!$cast(reg_model, model)) begin
      `uvm_error("SEQ", "Failed to cast model to apb_reg_block!")
      return;
    end
    
    // Write to control register
    reg_model.ctrl_reg.write(status, 3);
    `uvm_info("SEQ", "Wrote 3 to CTRL register", UVM_LOW)

    // Read from control register
    reg_model.ctrl_reg.read(status, data);
    `uvm_info("SEQ", $sformatf("Read CTRL register: %0d", data), UVM_LOW)

    // Read from status register
    reg_model.status_reg.read(status, data);
    `uvm_info("SEQ", $sformatf("Read STATUS register: %0d", data), UVM_LOW)
  endtask
endclass
  • APB Adapter (apb_reg_adapter):
    • Converts UVM register operations to APB transactions.
class apb_reg_adapter extends uvm_reg_adapter;
  
  `uvm_object_utils(apb_reg_adapter)
  
  function new(string name = "apb_reg_adapter");
    super.new(name);
  endfunction

  virtual function uvm_sequence_item reg2bus(const ref uvm_reg_bus_op rw);
    apb_transaction apb_tr = apb_transaction::type_id::create("apb_tr");
    apb_tr.addr = rw.addr;
    apb_tr.data = rw.data;
    apb_tr.read = (rw.kind == UVM_READ);
    return apb_tr;
  endfunction

  virtual function void bus2reg(uvm_sequence_item bus_item, ref uvm_reg_bus_op rw);
    apb_transaction apb_tr;
    if (!$cast(apb_tr, bus_item)) return;
    rw.kind = apb_tr.read ? UVM_READ : UVM_WRITE;
    rw.addr = apb_tr.addr;
    rw.data = apb_tr.data;
  endfunction
endclass
  • APB Predictor (apb_reg_predictor):
    • Predicts register values based on APB transactions.
class apb_reg_predictor extends uvm_reg_predictor#(apb_transaction);
  
  `uvm_component_utils(apb_reg_predictor)

  //Add analysis port
  uvm_analysis_imp#(apb_transaction, apb_reg_predictor) ap;

  function new(string name = "apb_reg_predictor", uvm_component parent);
    super.new(name, parent);
    ap = new("ap", this);  //Instantiate analysis port
  endfunction

  virtual function void write(apb_transaction tr);
    uvm_reg_bus_op rw;
    rw.kind = tr.read ? UVM_READ : UVM_WRITE;
    rw.addr = tr.addr;
    rw.data = tr.data;
    rw.status = UVM_IS_OK;
    
    if (map != null) begin
      uvm_reg r;
      r = map.get_reg_by_offset(rw.addr);  // Get register by address
      if (r != null) begin
        r.predict(
          rw.data,                
          -1,                      
          UVM_PREDICT_DIRECT,      
          UVM_FRONTDOOR,         
          null                     
        ); 
      end else begin
        `uvm_error("PREDICTOR", $sformatf("No register found at address 0x%0h", rw.addr))
      end
    end else begin
      `uvm_error("PREDICTOR", "Register map is NULL!")
    end
  endfunction
endclass
  • Testbench (testbench):
    • Initializes register model.
    • Writes 3 to CTRL_REG and reads it back.
    • Reads STATUS_REG.
class apb_test extends uvm_test;
  `uvm_component_utils(apb_test)
  
  apb_reg_sequence seq;
  apb_reg_block reg_model;
  apb_reg_adapter adapter;
  apb_reg_predictor predictor;
  apb_sequencer sequencer; //Declare the sequencer
  apb_driver driver;

  function new(string name = "apb_test", uvm_component parent = null);
    super.new(name, parent);
  endfunction

  virtual function void build_phase(uvm_phase phase);
    super.build_phase(phase);
    
    reg_model = apb_reg_block::type_id::create("reg_model");
    reg_model.build();
    
    sequencer = apb_sequencer::type_id::create("sequencer", this); //Create the sequencer
    adapter = apb_reg_adapter::type_id::create("adapter");
    predictor = apb_reg_predictor::type_id::create("predictor", this);
    driver = apb_driver::type_id::create("driver", this);

    predictor.map = reg_model.default_map;
    
    //Connect sequencer and driver
    driver.seq_item_port.connect(sequencer.seq_item_export);

    //Connect register model to sequencer through adapter
    reg_model.default_map.set_sequencer(sequencer, adapter);
    
  endfunction
  
  virtual task run_phase(uvm_phase phase);
    phase.raise_objection(this);

    // Start register sequence
    seq = apb_reg_sequence::type_id::create("seq");
    seq.model = reg_model; //Attach register model
    
    seq.start(sequencer);  //Start sequence on sequencer
    #50ns;
    phase.drop_objection(this);
  endtask
endclass

This UVM RAL implementation enables register modeling and interaction using the APB protocol.

Output:

UVM_INFO @ 0: reporter [RNTST] Running test apb_test...
UVM_INFO driver.sv(13) @ 0: uvm_test_top.driver [DRV] Driving transaction: addr=0x0, data=0x3, read=0
UVM_INFO driver.sv(17) @ 0: uvm_test_top.driver [DRV] Performing APB Write
UVM_INFO @ 0: reporter [RegModel] Wrote register via map reg_model.default_map: reg_model.ctrl_reg=0x3
UVM_INFO sequence.sv(22) @ 0: uvm_test_top.sequencer@@seq [SEQ] Wrote 3 to CTRL register
UVM_INFO driver.sv(13) @ 0: uvm_test_top.driver [DRV] Driving transaction: addr=0x0, data=0x0, read=1
UVM_INFO driver.sv(20) @ 0: uvm_test_top.driver [DRV] Performing APB Read
UVM_INFO @ 0: reporter [RegModel] Read  register via map reg_model.default_map: reg_model.ctrl_reg=deadbeef
UVM_INFO sequence.sv(26) @ 0: uvm_test_top.sequencer@@seq [SEQ] Read CTRL register: 3735928559
UVM_INFO driver.sv(13) @ 0: uvm_test_top.driver [DRV] Driving transaction: addr=0x4, data=0x0, read=1
UVM_INFO driver.sv(20) @ 0: uvm_test_top.driver [DRV] Performing APB Read
UVM_INFO @ 0: reporter [RegModel] Read  register via map reg_model.default_map: reg_model.status_reg=deadbeef
UVM_INFO sequence.sv(30) @ 0: uvm_test_top.sequencer@@seq [SEQ] Read STATUS register: 3735928559
UVM_INFO /apps/vcsmx/vcs/U-2023.03-SP2//etc/uvm-1.2/src/base/uvm_objection.svh(1276) @ 50: reporter [TEST_DONE] 'run' phase is ready to proceed to the 'extract' phase
UVM_INFO /apps/vcsmx/vcs/U-2023.03-SP2//etc/uvm-1.2/src/base/uvm_report_server.svh(904) @ 50: reporter [UVM/REPORT/SERVER] 
--- UVM Report Summary ---

** Report counts by severity
UVM_INFO :   14
UVM_WARNING :    0
UVM_ERROR :    0
UVM_FATAL :    0
** Report counts by id
[DRV]     6
[RNTST]     1
[RegModel]     3
[SEQ]     3
[TEST_DONE]     1

$finish called from file "/apps/vcsmx/vcs/U-2023.03-SP2//etc/uvm-1.2/src/base/uvm_root.svh", line 527.
$finish at simulation time                   50
           V C S   S i m u l a t i o n   R e p o r t 

Next Step: Integrate with a DUT → If a DUT is added, the driver will communicate with actual hardware, and real register values will be returned instead of 0xDEADBEEF.