UVM RAL – Register Block (uvm_reg_block)

Building an address map for registers and memory in UVM RAL.

Why Do We Need a Register Block?

After implementing registers and memory in the verification environment, the next abstraction level is the register block.

A register block is responsible for:

  • Holding all register instances
  • Holding all memory instances
  • Managing address mapping
  • Creating the complete DUT address space

In simple terms, the register block connects registers to their addresses.

SoC Addressing Concept

In an SoC, all peripherals share a common base address. Each peripheral has a unique offset address.

Final address = Base Address + Offset

Example:

  • Base Address = 0
  • Register 1 Offset = 0
  • Register 2 Offset = 4

Final addresses:

  • Register 1 → 0 + 0 = 0
  • Register 2 → 0 + 4 = 4

UVM RAL uses the same principle when building an address map.

Step 1 – Implement the Registers

Assume our DUT contains two 32-bit registers.

Register 1

class reg1 extends uvm_reg;
  `uvm_object_utils(reg1)

  rand uvm_reg_field ctrl;

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

  function void build;
    ctrl = uvm_reg_field::type_id::create("ctrl");

    ctrl.configure(
      .parent(this),
      .size(32),
      .lsb_pos(0),
      .access("RW"),
      .volatile(0),
      .reset('h0),
      .has_reset(1),
      .is_rand(1),
      .individually_accessible(1)
    );
  endfunction
endclass

Register 2

class reg2 extends uvm_reg;
  `uvm_object_utils(reg2)

  rand uvm_reg_field data;

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

  function void build;
    data = uvm_reg_field::type_id::create("data");

    data.configure(
      .parent(this),
      .size(32),
      .lsb_pos(0),
      .access("RW"),
      .volatile(0),
      .reset('h0),
      .has_reset(1),
      .is_rand(1),
      .individually_accessible(1)
    );
  endfunction
endclass

Step 2 – Create the Register Block

The register block extends uvm_reg_block.


class top_reg_block extends uvm_reg_block;
  `uvm_object_utils(top_reg_block)

  rand reg1 reg1_inst;
  rand reg2 reg2_inst;

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

  function void build;

    // Create Register 1
    reg1_inst = reg1::type_id::create("reg1_inst");
    reg1_inst.build();
    reg1_inst.configure(this);

    // Create Register 2
    reg2_inst = reg2::type_id::create("reg2_inst");
    reg2_inst.build();
    reg2_inst.configure(this);

    // Create Address Map
    default_map = create_map(
      "default_map",    // Map name
      0,                // Base address
      4,                // Bus width (bytes)
      UVM_LITTLE_ENDIAN // Endianness
    );

    // Add registers to map
    default_map.add_reg(reg1_inst, 'h0, "RW");
    default_map.add_reg(reg2_inst, 'h4, "RW");

    // Finalize model
    lock_model();

  endfunction
endclass

Important Concepts

  • configure(this) is mandatory for every register instance.
  • create_map() defines base address, bus width, and endian type.
  • add_reg() requires:
    • Register instance
    • Offset address
    • Access rights (R / W / RW)
  • Field-level access policy (inside configure()) works together with map-level access rights.
  • lock_model() is mandatory and prevents further structural changes.
Summary
  • Register block connects registers to addresses.
  • Base address + offset defines final address.
  • create_map() builds the address space.
  • add_reg() maps registers to offsets.
  • lock_model() finalizes the structure.

Run This Example on EDAPlayground

Click the button below to simulate the UVM Register Block example directly in EDAPlayground.

▶ Run UVM Register Block Example

Simulator: Questa / Xcelium (UVM Enabled)