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_blockanduvm_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.
The figure above shows a single register, REG_CTL, with five functional fields. The remaining bits are reserved.
| Bit Offset | Name | Read/Write | Width | Description |
|---|---|---|---|---|
| 0 | En | RW | 1 | Enable. Set this bit to 1 to turn on the entire module, and to 0 to disable it |
| 1 | Mode | RW | 3 | Supports the following modes:
|
| 4 | Halt | RW | 1 | Halt. Set this bit to 1 to stop this module temporarily, and to 0 to resume |
| 5 | Auto | RW | 1 | Auto shutdown mode. Set this bit to 1 to start auto shutdown after 5 minutes |
| 6 | Reserved | RO | 5 | Reserved for future additions |
| 11 | Speed | RW | 5 | Controls the speed, from 100 rpm to 2000 rpm |
| 16 | Reserved | RO | 16 | Reserved 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.
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.
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.
| Register | Offset | Absolute address |
|---|---|---|
| REG_CTL | 0x0 | 0xE7B3_0000 |
| REG_STAT | 0x4 | 0xE7B3_0004 |
| REG_INTEN | 0x8 | 0xE7B3_0008 |
| REG_DMACTL | 0xC | 0xE7B3_000C |
| REG_TIMER | 0x1C | 0xE7B3_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 concept | UVM RAL class |
|---|---|
| Field (a group of bits) | uvm_reg_field |
| Register | uvm_reg |
| Memory | uvm_mem |
| Register block | uvm_reg_block |
| Address map (base address and offsets) | uvm_reg_map |
| Translation between register accesses and bus items | uvm_reg_adapter |
| Updating the model from observed bus traffic | uvm_reg_predictor |
The register model can be used in a UVM sequence to write to or read from a target register.
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_seqanduvm_reg_bit_bash_seqthat 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_blockanduvm_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