Why Learn the UVM Register Layer? A typical SoC has thousands of configuration and status registers spread across dozens of peripherals. Every test has to program them, and every register needs to be checked after reset and after each access. The UVM Register Abstraction Layer lets a test write reg_ctl.write(status, value) instead of building bus transactions by hand, and the same test keeps working when the bus or the address map changes.

The UVM Register Abstraction Layer (RAL) is a set of UVM base classes for modeling the registers and memories of a design. A test reads and writes registers through this model by name, and the model translates each access into transactions for whatever bus the design uses.

10 min read | Intermediate Level

What You'll Learn

  • How registers, fields, register blocks and memory maps describe a design
  • Which UVM classes model each of these: uvm_reg_field, uvm_reg, uvm_reg_block and uvm_reg_map
  • How a register write travels from a sequence to the DUT through RAL
  • Why RAL is preferred over writing bus transactions by hand

What are registers ?

Registers are storage elements in a hardware design that hold configuration settings, control information and status data. Software or firmware usually accesses them over a bus protocol such as AXI or APB, and programs them with specific values.

A register is commonly 32 bits wide and divided into several fields. Each field controls or reports one feature of the design, and can be configured by software independently of the others.

hardware-register-example

The figure above shows a single register, REG_CTL, with five functional fields. The remaining bits are reserved.

Bit OffsetNameRead/WriteWidthDescription
0EnRW1Enable. Set this bit to 1 to turn on the entire module, and to 0 to disable it
1ModeRW3Supports the following modes:
  • Low Energy : 000
  • Medium Energy : 001
  • High Speed : 010
  • Auto Adjust : 011
  • Cruise Mode : 100
  • Balanced : 101
4HaltRW1Halt. Set this bit to 1 to stop this module temporarily, and to 0 to resume
5AutoRW1Auto shutdown mode. Set this bit to 1 to start auto shutdown after 5 minutes
6ReservedRO5Reserved for future additions
11SpeedRW5Controls the speed, from 100 rpm to 2000 rpm
16ReservedRO16Reserved for future additions

The design files may contain an RTL module that implements this register.


// RTL representation

module reg_block (...);
	wire [31:0]     reg_ctl;
	
	// Declare reg variables to store register field values
	reg             ctl_en;      // Enable for the module
	reg [2:0]       ctl_mode;    // Mode 
	reg             ctl_halt;    // Halt 
	reg             ctl_auto;    // Auto shutdown 
	reg [4:0]       ctl_speed;   // Speed control
	
	assign reg_ctl = {16'b0, ctl_speed, 5'b0, ctl_auto, ctl_halt, ctl_mode, ctl_en};
	
	// Logic for getting individual fields from the bus
	...
endmodule

The concatenation in the assign statement packs the fields from bit 31 down to bit 0, so it must match the bit offsets in the table exactly.

To model these registers with UVM RAL, you create a register model class hierarchy that mirrors the design. Each register becomes a class derived from uvm_reg, and each field inside it is an instance of uvm_reg_field.

What is a register block ?

A register block is a higher-level abstraction that groups the registers and memories of one design module. It keeps related registers together, which makes large designs with many registers easier to manage and verify.

Each register in a block sits at an offset from the start of the block. For example, REG_CTL is placed at offset 0x0 because it is the module's main control register. It occupies 32 bits, or 4 bytes, so the next register, REG_STAT, which holds the design's status flags, starts at 0x4.

hardware-register-file-example

A register block class is derived from the uvm_reg_block base class. The offsets of its registers are stored in an address map, an instance of uvm_reg_map, inside the block.

What is a memory map ?

A memory map is the arrangement of memories and memory-mapped peripheral registers within the address space of an SoC. It defines the address at which each component, such as a memory region, register or peripheral, can be reached by the processor and other bus masters.

A System-on-Chip (SoC) normally has one or more processor cores, DMA engines, bus interconnects and many peripheral modules. Each peripheral has its own register block, and all of them are laid out in the memory map. The processor reaches them through a peripheral bus such as APB or Wishbone, so each peripheral needs a base address where its register block starts.

memory-map

Assume the register block described above belongs to a Timer module with a base address of 0xE7B3_0000. To access REG_CTL, the core sends a transaction to address 0xE7B3_0000, because REG_CTL has an offset of 0x0. To access REG_DMACTL, the core sends a transaction to 0xE7B3_0000 + 0xC = 0xE7B3_000C.

RegisterOffsetAbsolute address
REG_CTL0x00xE7B3_0000
REG_STAT0x40xE7B3_0004
REG_INTEN0x80xE7B3_0008
REG_DMACTL0xC0xE7B3_000C
REG_TIMER0x1C0xE7B3_001C

In RAL, the base address is given when the block's map is created, and the offsets when each register is added to it. The model then computes every absolute address for you.

Quick Check

Q: If the Timer block were moved to base address 0xE7B5_0000 in a new chip, what changes in the register model, and what stays the same?

Show Answer

Only the base address of the Timer's map changes. The register offsets and all sequences that access registers by name, such as reg_ctl.write(), stay exactly the same.

What is Register Abstraction Layer ?

The purpose of the Register Abstraction Layer is to provide a structured, standard way to model and verify registers and memory-mapped structures in a design. Register verification includes testing different access scenarios, checking field values after reset, verifying side effects of register accesses, and more.

RAL defines base classes that, once extended for a design, abstract read and write operations to its registers and memories. Because sequences use register names instead of addresses and bus items, the same register sequences can be reused from block-level to subsystem and full-chip testbenches, typically with changes only to the model's configuration and bus connection.

Design conceptUVM RAL class
Field (a group of bits)uvm_reg_field
Registeruvm_reg
Memoryuvm_mem
Register blockuvm_reg_block
Address map (base address and offsets)uvm_reg_map
Translation between register accesses and bus itemsuvm_reg_adapter
Updating the model from observed bus trafficuvm_reg_predictor

The register model can be used in a UVM sequence to write to or read from a target register.

uvm_ral_write_example

class my_sequence extends uvm_sequence;
	...

	regmodel 	m_regmodel;

	virtual task body();
		uvm_status_e 	status;     // Result of the access: UVM_IS_OK, UVM_NOT_OK, ...
		uvm_reg_data_t 	rdata;      // Value returned by read()

		// Get regmodel from config_db
		
		// Use register model handle to write into the DUT register
		m_regmodel.reg_ctl.write(status, 8'h3A);

		// Use register model handle to read from the DUT register
		m_regmodel.reg_ctl.read(status, rdata);
	endtask
endclass

In this sequence, write() sends the value 'h3A to REG_CTL. In binary, 'h3A is 0011_1010, so it sets En = 0, Mode = 101 (Balanced), Halt = 1 and Auto = 1. read() then returns the register's current value in rdata. After each call, status reports whether the bus access succeeded.

Behind the scenes, the register model looks up the address of REG_CTL in its map. An adapter converts the access into a bus item for the bus agent, which drives it to the DUT. A predictor watches the bus through the monitor and keeps the model's copy of each register up to date.

Where is RAL Used?

RAL models are almost never written by hand for real chips. They are generated from a register description, such as an IP-XACT file or a spreadsheet maintained by the design team, so the model, the RTL and the software headers all come from the same source.

Why is RAL useful ?

Writing and reading registers without RAL is cumbersome. You have to create a uvm_sequence_item with the right address and data, and execute it on the bus agent for that specific protocol. Code like this cannot be reused as easily, and there is no record of the value each register should currently hold.


class write_sequence extends uvm_sequence;
	int 	m_addr;
	int 	m_wdata;

	...
	virtual task body();
		bus_item 	m_item;

		m_item = bus_item::type_id::create("my_item");
		start_item(m_item);
		m_item.randomize() with { 	wdata == m_wdata;
									write == 1;
									addr 	== m_addr; 
								};
		finish_item(m_item);
		
		...
	endtask
endclass

RAL improves on this in several ways:

  • Access by name: sequences use reg_ctl.write() instead of addresses, so they survive changes in the address map and in the bus protocol
  • Mirrored values: the model keeps a copy of what each register should contain, so a test can check the DUT against the model at any time
  • Front-door and back-door access: a register can be accessed through the bus, or directly through the HDL path in zero simulation time
  • Built-in sequences: UVM provides ready-made sequences such as uvm_reg_hw_reset_seq and uvm_reg_bit_bash_seq that check reset values and the access rights of every bit

Where are the Built-in Register Sequences Used?

Most projects run a reset-value test and a bit-bash test on every register block as soon as the RTL is available. These tests use the generated model and UVM's built-in sequences, so they catch wrong reset values and read-only bits that are accidentally writable, without any test-specific code.

Common Mistakes

Mistake #1: A register model that does not match the RTL bit layout

If the model places Speed at bits 15:12 while the RTL uses 15:11, every access to it reads and writes the wrong bits. The test may then fail in confusing ways, or worse, pass. Generate the model and the RTL from the same register description whenever possible, and run the reset and bit-bash sequences early.

Mistake #2: Ignoring the status of a register access

Wrong: calling reg_ctl.write(status, value) and never looking at status
Right: if (status != UVM_IS_OK) `uvm_error("REG", "REG_CTL write failed")

Why? A bus error response, such as an APB PSLVERR, is reported through status. If it is not checked, the test continues as though the access succeeded.

Mistake #3: Confusing the model's value with the DUT's value

reg_ctl.get() returns the model's desired value and does not access the DUT. To see what the hardware actually contains, use read(), or mirror() to read and compare against the model in one step.

Practice Exercise

Challenge: Program REG_CTL

Using the REG_CTL layout above:

  • Calculate the 32-bit value that enables the module, selects Auto Adjust mode, leaves Halt and Auto at 0, and sets Speed to 10
  • Give the absolute address of REG_INTEN in the Timer at base 0xE7B3_0000
  • Write a sequence body that programs the value from the first step through the register model
Hint

Shift each field value to its bit offset and OR the results together. Auto Adjust is 011.

Solution

Value: Speed = 10 at bit 11 gives 10 << 11 = 'h5000. Mode = 3 at bit 1 gives 'h6. En = 1 gives 'h1. The total is 'h0000_5007.

Address: 0xE7B3_0000 + 0x8 = 0xE7B3_0008.


virtual task body ();
  uvm_status_e   status;
  uvm_reg_data_t rdata;

  m_regmodel.reg_ctl.write (status, 32'h0000_5007);    // En=1, Mode=Auto Adjust, Speed=10
  if (status != UVM_IS_OK)
    `uvm_error ("REG", "REG_CTL write failed")

  m_regmodel.reg_ctl.read (status, rdata);             // Read back to confirm
  if (rdata != 32'h0000_5007)
    `uvm_error ("REG", $sformatf ("REG_CTL readback 0x%0h, expected 0x5007", rdata))
endtask

Summary

In this tutorial, you learned:

  • Registers hold configuration and status in fields, and each field occupies a fixed range of bits
  • A register block groups registers at fixed offsets, and a memory map places each block at a base address
  • RAL models fields, registers, blocks and maps with uvm_reg_field, uvm_reg, uvm_reg_block and uvm_reg_map
  • A register write goes through the model, an adapter and the bus agent, and a predictor keeps the model's mirror up to date
  • Sequences that access registers by name keep working when addresses or bus protocols change
  • Always check the status of register accesses, and keep the model generated from the same source as the RTL
Did you find what you were looking for?

Quiz

Test Your Knowledge

1 of 5 Score: 0 / 5