RAL - Register Abstraction Layer

Complete Guide to UVM Register Model Implementation

Praveen Kumar Vagala | 20 min read

1000

Introduction

The Register Abstraction Layer (RAL) is a powerful feature in UVM that provides a standardized way to model, access, and verify hardware registers. It abstracts the physical register access mechanism, enabling reusable verification code across different bus interfaces and simplifying register verification.

Table of Contents

  1. RAL Key Concepts
  2. Register Block Implementation
  3. Adapter & Predictor
  4. RAL Sequences & Operations
  5. Register Coverage
  6. Frontdoor vs Backdoor Access
  7. Integration with Testbench

1. RAL Key Concepts

Key Learning: Register model hierarchy, field types, access policies

RAL Hierarchy

+------------------------------------------+ | uvm_reg_block | | +------------------------------------+ | | | uvm_reg_map | | | | +------------+ +------------+ | | | | | uvm_reg | | uvm_reg | | | | | | +-------+ | | +-------+ | | | | | | |uvm_ | | | |uvm_ | | | | | | | |reg_ | | | |reg_ | | | | | | | |field | | | |field | | | | | | | +-------+ | | +-------+ | | | | | +------------+ +------------+ | | | +------------------------------------+ | +------------------------------------------+

Register Access Types

Access Type Description Read Behavior Write Behavior
RW Read-Write Returns current value Updates value
RO Read-Only Returns current value No effect
WO Write-Only Returns 0 Updates value
W1C Write-1-to-Clear Returns current value Clears bits written as 1
W1S Write-1-to-Set Returns current value Sets bits written as 1
RC Read-to-Clear Returns and clears No effect

2. Register Block Implementation

Key Learning: Register definition, field configuration, address mapping

Example Register Specification

We'll model a simple peripheral with the following registers:

Register Offset Fields
CTRL_REG 0x00 EN[0], MODE[2:1], IRQ_EN[3]
STATUS_REG 0x04 BUSY[0], ERROR[1], DONE[2] (RO)
DATA_REG 0x08 DATA[31:0]
IRQ_REG 0x0C IRQ_STATUS[3:0] (W1C)

Field Definition

// Control Register Fields
class ctrl_reg extends uvm_reg;
    `uvm_object_utils(ctrl_reg)
    
    rand uvm_reg_field EN;
    rand uvm_reg_field MODE;
    rand uvm_reg_field IRQ_EN;
    rand uvm_reg_field RESERVED;
    
    function new(string name = "ctrl_reg");
        super.new(name, 32, UVM_NO_COVERAGE);
    endfunction
    
    virtual function void build();
        // Create fields
        EN      = uvm_reg_field::type_id::create("EN");
        MODE    = uvm_reg_field::type_id::create("MODE");
        IRQ_EN  = uvm_reg_field::type_id::create("IRQ_EN");
        RESERVED = uvm_reg_field::type_id::create("RESERVED");
        
        // Configure: parent, size, lsb_pos, access, volatile, reset, has_reset, is_rand
        EN.configure     (this, 1,  0, "RW", 0, 1'b0, 1, 1, 0);
        MODE.configure   (this, 2,  1, "RW", 0, 2'b0, 1, 1, 0);
        IRQ_EN.configure (this, 1,  3, "RW", 0, 1'b0, 1, 1, 0);
        RESERVED.configure(this, 28, 4, "RO", 0, 28'b0, 1, 0, 0);
    endfunction
    
endclass

Status Register (Read-Only)

class status_reg extends uvm_reg;
    `uvm_object_utils(status_reg)
    
    rand uvm_reg_field BUSY;
    rand uvm_reg_field ERROR;
    rand uvm_reg_field DONE;
    
    function new(string name = "status_reg");
        super.new(name, 32, UVM_NO_COVERAGE);
    endfunction
    
    virtual function void build();
        BUSY  = uvm_reg_field::type_id::create("BUSY");
        ERROR = uvm_reg_field::type_id::create("ERROR");
        DONE  = uvm_reg_field::type_id::create("DONE");
        
        // Read-Only fields
        BUSY.configure (this, 1, 0, "RO", 1, 1'b0, 1, 0, 0);
        ERROR.configure(this, 1, 1, "RO", 1, 1'b0, 1, 0, 0);
        DONE.configure (this, 1, 2, "RO", 1, 1'b0, 1, 0, 0);
    endfunction
    
endclass

IRQ Register (Write-1-to-Clear)

class irq_reg extends uvm_reg;
    `uvm_object_utils(irq_reg)
    
    rand uvm_reg_field IRQ_STATUS;
    
    function new(string name = "irq_reg");
        super.new(name, 32, UVM_NO_COVERAGE);
    endfunction
    
    virtual function void build();
        IRQ_STATUS = uvm_reg_field::type_id::create("IRQ_STATUS");
        
        // Write-1-to-Clear field
        IRQ_STATUS.configure(this, 4, 0, "W1C", 1, 4'b0, 1, 1, 0);
    endfunction
    
endclass

Register Block

class peripheral_reg_block extends uvm_reg_block;
    `uvm_object_utils(peripheral_reg_block)
    
    rand ctrl_reg   CTRL;
    rand status_reg STATUS;
    rand uvm_reg    DATA;
    rand irq_reg    IRQ;
    
    uvm_reg_map reg_map;
    
    function new(string name = "peripheral_reg_block");
        super.new(name, UVM_NO_COVERAGE);
    endfunction
    
    virtual function void build();
        // Create registers
        CTRL   = ctrl_reg::type_id::create("CTRL");
        STATUS = status_reg::type_id::create("STATUS");
        DATA   = uvm_reg::type_id::create("DATA");
        IRQ    = irq_reg::type_id::create("IRQ");
        
        // Build and configure
        CTRL.configure(this, null, "");
        CTRL.build();
        
        STATUS.configure(this, null, "");
        STATUS.build();
        
        DATA.configure(this, null, "");
        DATA.build();
        
        IRQ.configure(this, null, "");
        IRQ.build();
        
        // Create address map
        reg_map = create_map("reg_map", 'h0, 4, UVM_LITTLE_ENDIAN);
        
        // Add registers to map
        reg_map.add_reg(CTRL,   'h00, "RW");
        reg_map.add_reg(STATUS, 'h04, "RO");
        reg_map.add_reg(DATA,   'h08, "RW");
        reg_map.add_reg(IRQ,    'h0C, "RW");
        
        // Lock model
        lock_model();
    endfunction
    
endclass

3. Adapter & Predictor

Key Learning: Bus protocol abstraction, register prediction

Register Adapter (APB Example)

class apb_reg_adapter extends uvm_reg_adapter;
    `uvm_object_utils(apb_reg_adapter)
    
    function new(string name = "apb_reg_adapter");
        super.new(name);
        
        // Support byte enables
        supports_byte_enable = 1;
        
        // Provide responses
        provides_responses = 1;
    endfunction
    
    // Convert RAL transaction to bus transaction
    virtual function uvm_sequence_item reg2bus(const ref uvm_reg_bus_op rw);
        apb_transaction txn = apb_transaction::type_id::create("txn");
        
        txn.addr    = rw.addr;
        txn.wr_en   = (rw.kind == UVM_WRITE);
        txn.wr_data = rw.data;
        
        return txn;
    endfunction
    
    // Convert bus transaction to RAL transaction
    virtual function void bus2reg(uvm_sequence_item bus_item, 
                                   ref uvm_reg_bus_op rw);
        apb_transaction txn;
        
        if (!$cast(txn, bus_item)) begin
            `uvm_fatal("CAST", "Failed to cast bus item")
        end
        
        rw.addr   = txn.addr;
        rw.kind   = txn.wr_en ? UVM_WRITE : UVM_READ;
        rw.data   = txn.wr_en ? txn.wr_data : txn.rd_data;
        rw.status = UVM_IS_OK;
    endfunction
    
endclass

Register Predictor

class reg_predictor extends uvm_reg_predictor #(apb_transaction);
    `uvm_component_utils(reg_predictor)
    
    function new(string name, uvm_component parent);
        super.new(name, parent);
    endfunction
    
endclass

💡 Adapter Key Points


4. RAL Sequences & Operations

Key Learning: Register access methods, built-in sequences

Basic RAL Operations

class ral_basic_sequence extends uvm_sequence;
    `uvm_object_utils(ral_basic_sequence)
    
    peripheral_reg_block reg_model;
    
    function new(string name = "ral_basic_sequence");
        super.new(name);
    endfunction
    
    task body();
        uvm_status_e status;
        uvm_reg_data_t data;
        
        // ========== WRITE Operations ==========
        
        // Method 1: write() - Full register write
        reg_model.CTRL.write(status, 32'h0000_000F);
        
        // Method 2: set() + update() - Modify and sync
        reg_model.CTRL.set(32'h0000_0005);
        reg_model.CTRL.update(status);
        
        // Method 3: Field-level access
        reg_model.CTRL.EN.set(1);
        reg_model.CTRL.MODE.set(2'b11);
        reg_model.CTRL.update(status);
        
        // ========== READ Operations ==========
        
        // Method 1: read() - Read from DUT
        reg_model.STATUS.read(status, data);
        `uvm_info("RAL", $sformatf("STATUS = 0x%0h", data), UVM_LOW)
        
        // Method 2: get() - Get model value (no DUT access)
        data = reg_model.CTRL.get();
        
        // Method 3: mirror() - Read DUT and update model
        reg_model.STATUS.mirror(status, UVM_CHECK);
        
        // ========== Compare/Check ==========
        
        // Compare model value with expected
        if (reg_model.CTRL.EN.get() != 1) begin
            `uvm_error("RAL", "EN field mismatch")
        end
        
    endtask
    
endclass

Built-in RAL Sequences

class ral_builtin_tests extends uvm_sequence;
    `uvm_object_utils(ral_builtin_tests)
    
    peripheral_reg_block reg_model;
    
    function new(string name = "ral_builtin_tests");
        super.new(name);
    endfunction
    
    task body();
        uvm_reg_hw_reset_seq  reset_seq;
        uvm_reg_bit_bash_seq  bit_bash_seq;
        uvm_reg_access_seq    access_seq;
        
        // ===== Reset Test =====
        // Verify all registers have correct reset values
        reset_seq = uvm_reg_hw_reset_seq::type_id::create("reset_seq");
        reset_seq.model = reg_model;
        reset_seq.start(null);
        
        // ===== Bit Bash Test =====
        // Write walking 1s and 0s to verify all bits work
        bit_bash_seq = uvm_reg_bit_bash_seq::type_id::create("bit_bash_seq");
        bit_bash_seq.model = reg_model;
        bit_bash_seq.start(null);
        
        // ===== Access Test =====
        // Verify register access types (RW, RO, etc.)
        access_seq = uvm_reg_access_seq::type_id::create("access_seq");
        access_seq.model = reg_model;
        access_seq.start(null);
        
    endtask
    
endclass

Custom Register Sequence

class peripheral_config_sequence extends uvm_reg_sequence;
    `uvm_object_utils(peripheral_config_sequence)
    
    peripheral_reg_block reg_model;
    
    // Configuration parameters
    rand bit        enable;
    rand bit [1:0]  mode;
    rand bit        irq_enable;
    
    function new(string name = "peripheral_config_sequence");
        super.new(name);
    endfunction
    
    task body();
        uvm_status_e status;
        
        `uvm_info("SEQ", $sformatf("Configuring: EN=%0b MODE=%0b IRQ_EN=%0b",
                  enable, mode, irq_enable), UVM_LOW)
        
        // Configure control register
        reg_model.CTRL.EN.set(enable);
        reg_model.CTRL.MODE.set(mode);
        reg_model.CTRL.IRQ_EN.set(irq_enable);
        reg_model.CTRL.update(status);
        
        // Verify configuration
        reg_model.CTRL.mirror(status, UVM_CHECK);
        
        // Wait for device to be ready
        poll_for_ready();
        
    endtask
    
    task poll_for_ready();
        uvm_status_e status;
        int timeout = 100;
        
        repeat (timeout) begin
            reg_model.STATUS.read(status, data);
            if (!reg_model.STATUS.BUSY.get()) begin
                `uvm_info("SEQ", "Device ready", UVM_LOW)
                return;
            end
            #10;
        end
        
        `uvm_error("SEQ", "Timeout waiting for device ready")
    endtask
    
endclass

5. Register Coverage

Key Learning: Automatic coverage, custom coverage extensions

Enable Built-in Coverage

class peripheral_reg_block extends uvm_reg_block;
    `uvm_object_utils(peripheral_reg_block)
    
    // ... register declarations ...
    
    function new(string name = "peripheral_reg_block");
        // Enable coverage
        super.new(name, build_coverage(UVM_CVR_REG_BITS | 
                                       UVM_CVR_ADDR_MAP | 
                                       UVM_CVR_FIELD_VALS));
    endfunction
    
    virtual function void build();
        // ... build registers ...
        
        // Sample coverage on register access
        CTRL.set_coverage(UVM_CVR_REG_BITS);
        STATUS.set_coverage(UVM_CVR_REG_BITS);
        
    endfunction
    
endclass

Custom Field Coverage

class ctrl_reg extends uvm_reg;
    `uvm_object_utils(ctrl_reg)
    
    rand uvm_reg_field EN;
    rand uvm_reg_field MODE;
    rand uvm_reg_field IRQ_EN;
    
    // Custom covergroup
    covergroup ctrl_cov;
        en_cp: coverpoint EN.value {
            bins disabled = {0};
            bins enabled  = {1};
        }
        
        mode_cp: coverpoint MODE.value {
            bins mode_0 = {0};
            bins mode_1 = {1};
            bins mode_2 = {2};
            bins mode_3 = {3};
        }
        
        irq_cp: coverpoint IRQ_EN.value {
            bins irq_disabled = {0};
            bins irq_enabled  = {1};
        }
        
        // Cross coverage
        mode_x_en: cross en_cp, mode_cp;
        
    endgroup
    
    function new(string name = "ctrl_reg");
        super.new(name, 32, UVM_CVR_REG_BITS);
        ctrl_cov = new();
    endfunction
    
    // Override sample method
    virtual function void sample(uvm_reg_data_t data,
                                  uvm_reg_data_t byte_en,
                                  bit is_read,
                                  uvm_reg_map map);
        super.sample(data, byte_en, is_read, map);
        ctrl_cov.sample();
    endfunction
    
endclass

6. Frontdoor vs Backdoor Access

Key Learning: Access methods, performance optimization

Access Types Comparison

Aspect Frontdoor Backdoor
Mechanism Via bus interface Direct hierarchy access
Timing Real bus cycles Zero time
Realism Full protocol No protocol
Use Case Functional verification Initialization, debug

Backdoor Access Configuration

class peripheral_reg_block extends uvm_reg_block;
    
    virtual function void build();
        // ... register creation ...
        
        // Configure backdoor paths (HDL hierarchy)
        CTRL.add_hdl_path_slice("ctrl_reg", 0, 32);
        STATUS.add_hdl_path_slice("status_reg", 0, 32);
        DATA.add_hdl_path_slice("data_reg", 0, 32);
        IRQ.add_hdl_path_slice("irq_reg", 0, 32);
        
        // Set root path
        add_hdl_path("tb.dut.reg_file");
        
    endfunction
    
endclass

Using Backdoor Access

class backdoor_test_sequence extends uvm_sequence;
    `uvm_object_utils(backdoor_test_sequence)
    
    peripheral_reg_block reg_model;
    
    task body();
        uvm_status_e status;
        uvm_reg_data_t data;
        
        // ===== Backdoor Write =====
        // Initialize registers quickly (no bus cycles)
        reg_model.CTRL.poke(status, 32'h0000_000F);
        
        // ===== Backdoor Read =====
        // Read directly from RTL
        reg_model.STATUS.peek(status, data);
        `uvm_info("BD", $sformatf("STATUS = 0x%0h", data), UVM_LOW)
        
        // ===== Mixed Access =====
        // Use backdoor for init, frontdoor for verification
        reg_model.DATA.poke(status, 32'hDEAD_BEEF);  // Fast init
        reg_model.DATA.read(status, data);           // Verify via bus
        
        if (data != 32'hDEAD_BEEF) begin
            `uvm_error("BD", "Data mismatch after backdoor write")
        end
        
    endtask
    
endclass

7. Integration with Testbench

Key Learning: Environment setup, model connection

Environment Integration

class peripheral_env extends uvm_env;
    `uvm_component_utils(peripheral_env)
    
    apb_agent           agent;
    peripheral_reg_block reg_model;
    apb_reg_adapter     adapter;
    reg_predictor       predictor;
    
    function new(string name, uvm_component parent);
        super.new(name, parent);
    endfunction
    
    function void build_phase(uvm_phase phase);
        super.build_phase(phase);
        
        // Create agent
        agent = apb_agent::type_id::create("agent", this);
        
        // Create register model
        reg_model = peripheral_reg_block::type_id::create("reg_model");
        reg_model.build();
        
        // Create adapter
        adapter = apb_reg_adapter::type_id::create("adapter");
        
        // Create predictor
        predictor = reg_predictor::type_id::create("predictor", this);
        
    endfunction
    
    function void connect_phase(uvm_phase phase);
        super.connect_phase(phase);
        
        // Connect register model to sequencer via adapter
        reg_model.reg_map.set_sequencer(agent.sqr, adapter);
        
        // Enable auto-prediction (alternative to explicit predictor)
        reg_model.reg_map.set_auto_predict(1);
        
        // OR use explicit predictor
        // predictor.map = reg_model.reg_map;
        // predictor.adapter = adapter;
        // agent.mon.ap.connect(predictor.bus_in);
        
    endfunction
    
endclass

Complete Test Example

class ral_test extends uvm_test;
    `uvm_component_utils(ral_test)
    
    peripheral_env env;
    
    function new(string name, uvm_component parent);
        super.new(name, parent);
    endfunction
    
    function void build_phase(uvm_phase phase);
        super.build_phase(phase);
        env = peripheral_env::type_id::create("env", this);
    endfunction
    
    task run_phase(uvm_phase phase);
        peripheral_config_sequence config_seq;
        ral_builtin_tests builtin_seq;
        
        phase.raise_objection(this);
        
        // Run configuration sequence
        config_seq = peripheral_config_sequence::type_id::create("config_seq");
        config_seq.reg_model = env.reg_model;
        config_seq.enable = 1;
        config_seq.mode = 2'b10;
        config_seq.irq_enable = 1;
        config_seq.start(null);
        
        // Run built-in tests
        builtin_seq = ral_builtin_tests::type_id::create("builtin_seq");
        builtin_seq.reg_model = env.reg_model;
        builtin_seq.start(null);
        
        #1000;
        phase.drop_objection(this);
    endtask
    
endclass

✅ RAL Best Practices

  1. Generate register model from IP-XACT or RALF specifications
  2. Use auto-prediction for simple testbenches
  3. Use explicit predictor when monitoring is critical
  4. Use backdoor for initialization, frontdoor for verification
  5. Enable built-in coverage for comprehensive register testing
  6. Run built-in sequences (reset, bit-bash, access) early

RAL Interview Questions

  1. What is the difference between set/get and write/read? - set/get modify model only; write/read access DUT
  2. Explain the role of the adapter. - Converts between abstract register operations and bus transactions
  3. When would you use backdoor access? - Fast initialization, accessing inaccessible registers, debug
  4. What is mirror() vs read()? - mirror() can optionally check; both update model
  5. How does the predictor work? - Observes bus transactions and updates register model accordingly

Next Steps

Continue your VLSI learning journey with the complete blog series:

Or revisit previous chapters to reinforce your learning!

← Previous: UVM Testbench Back to 01 →
#RAL #UVM #RegisterModel #Verification #SystemVerilog #Coverage #VLSI