Skip to content

UVM RAL · Chapter 14 · Industry Case Studies

Case Study: Mini-SoC RAL Environment

This final capstone composes the GPIO, timer, UART, and interrupt-controller register blocks from the earlier case studies into one hierarchical top-level register model for a mini-SoC. A top block holds each peripheral as a sub-block placed at its own base offset, so a peripheral register resolves at the SoC level to base plus its local offset. Because a real SoC exposes registers on more than one bus, the model is multi-bus: the CPU reaches peripherals over APB while a DMA or debug controller reaches some over AXI-Lite, all from one shared model distributed through the config database. The lesson ends with the signature integration bug, where two peripheral sub-blocks are placed at bases whose address ranges overlap, so a register in one peripheral silently aliases a register in another.

Foundation15 min readUVM RALcase studySoChierarchicalsub-block base

Chapter 14 · Section 14.6 · Industry Case Studies · Capstone Finale

1. The SoC and Its Composition

The mini-SoC integrates the peripherals into a hierarchical register model — a top block with each peripheral as a sub-block at its own base range.

  • soc_reg_block — the top-level block; contains the peripheral sub-blocks.
  • GPIO at base 0x0000 (block size 0x100) — occupies 0x0000..0x00FF.
  • TIMER at base 0x0100 (block size 0x100) — occupies 0x0100..0x01FF.
  • UART at base 0x0200 (block size 0x100) — occupies 0x0200..0x02FF.
  • INTC at base 0x0300 (block size 0x100) — occupies 0x0300..0x03FF.
  • Reached by the CPU over APB and (some) by a DMA/debug controller over AXI-Lite — multi-bus (12.7).
Mini-SoC hierarchical register model: soc_reg_block with GPIO, TIMER, UART, INTC sub-blocks at 0x000/0x100/0x200/0x300GPIO @0x000GPIO @0x000sub-block · 0x000-0x0FFsub-block · 0x000-0x0FFTIMER @0x100TIMER @0x100sub-block · 0x100-0x1FFsub-block · 0x100-0x1FFUART @0x200UART @0x200sub-block · 0x200-0x2FFsub-block · 0x200-0x2FFINTC @0x300INTC @0x300sub-block · 0x300-0x3FFsub-block · 0x300-0x3FFsoc_reg_blocksoc_reg_blockhierarchical top · APB + AXI-Lite mapshierarchical top · APB + AXI-Lite maps
Figure 1 — the mini-SoC hierarchical register model. A top-level soc_reg_block composes each peripheral (GPIO/TIMER/UART/INTC) as a SUB-BLOCK added to the top map with add_submap at its BASE. Each sub-block occupies a RANGE (base .. base + block size, e.g. 0x100 each), so a peripheral register at sub-offset O resolves to base+O at the SoC level. Neighbouring peripherals must be placed BEYOND the previous block's footprint (GPIO 0x000, TIMER 0x100, ...), or their ranges OVERLAP and registers across peripherals alias (3.5/8.2 at SoC scale). The SoC is multi-bus: CPU via APB, DMA/debug via AXI-Lite (12.7), one shared model (13.4).

2. Industry Context — the SoC RAL is a composition of block RALs

Real register verification is hierarchical: each IP block has its own register model, and the SoC register model composes them — the SoC team does not re-model each peripheral, it instantiates the block models as sub-blocks at their SoC base addresses. So SoC RAL is fundamentally an integration exercise, and its bugs are integration bugs: sub-block base collisions, per-bus map/adapter wiring, and one-shared-model distribution — the concerns of Chapters 3, 12.6, 12.7, and 13.4 applied together. Getting the composition right — each peripheral's range non-overlapping, each reachable on the intended bus, all sharing one model — is the SoC RAL job, and it is the fitting capstone because it exercises the whole track at once.

3. Composing the Top-Level Model — sub-blocks at non-overlapping base ranges

Build the top block, instantiate each peripheral sub-block, and add_submap each at a base beyond the previous block's footprint:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// The mini-SoC top block composes peripheral SUB-BLOCKS at non-overlapping base RANGES.
class soc_reg_block extends uvm_reg_block;
  `uvm_object_utils(soc_reg_block)
  rand gpio_reg_block  gpio;
  rand timer_reg_block timer;
  rand uart_reg_block  uart;
  rand intc_reg_block  intc;
  function new(string name="soc_reg_block"); super.new(name, UVM_NO_COVERAGE); endfunction
 
  virtual function void build();
    default_map = create_map("apb_map", 0, 4, UVM_LITTLE_ENDIAN);   // CPU/APB view
 
    gpio = gpio_reg_block::type_id::create("gpio"); gpio.configure(this); gpio.build();
    default_map.add_submap(gpio.default_map, 'h000);    // GPIO occupies 0x000-0x0FF
 
    timer = timer_reg_block::type_id::create("timer"); timer.configure(this); timer.build();
    default_map.add_submap(timer.default_map, 'h100);   // TIMER at 0x100 (BEYOND GPIO's 0x100 footprint)
 
    uart = uart_reg_block::type_id::create("uart"); uart.configure(this); uart.build();
    default_map.add_submap(uart.default_map, 'h200);    // UART at 0x200
 
    intc = intc_reg_block::type_id::create("intc"); intc.configure(this); intc.build();
    default_map.add_submap(intc.default_map, 'h300);    // INTC at 0x300
 
    lock_model();   // build then LOCK the composed SoC model
  endfunction
endclass

4. Multi-Bus Maps — CPU (APB) and DMA/debug (AXI-Lite)

Add a second map (AXI-Lite) so a DMA/debug master reaches the same shared registers on a different bus (12.7):

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// A SECOND per-bus map: the DMA/debug controller reaches the SAME registers over AXI-Lite (12.7).
axi_map = create_map("axi_map", 'h8000, 4, UVM_LITTLE_ENDIAN);   // AXI-Lite view (its own base)
axi_map.add_submap(gpio.default_map, 'h8000);   // same shared GPIO registers, AXI address view
// ... connect apb_map -> APB agent via APB adapter; axi_map -> AXI-Lite agent via AXI-Lite adapter (14.5).
// One SHARED model, per-bus maps+adapters. An access specifies WHICH map (.map(apb_map) / .map(axi_map)).

5. Distributing and Accessing the SoC Model

Distribute the one SoC model via config_db (13.4); access any peripheral's registers through the hierarchy:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// Distribute the ONE SoC model (13.4); access peripheral registers via the hierarchy + specify the map.
uvm_config_db#(soc_reg_block)::set(this, "*", "soc_reg_model", soc_reg_model);
// ...
uvm_status_e s;  uvm_reg_data_t v;
soc.gpio.DIR.write(s, 32'hF, .map(soc.apb_map));      // GPIO DIR via CPU/APB path (resolves to 0x004)
soc.timer.LOAD.write(s, 32'h1000, .map(soc.apb_map)); // TIMER LOAD (resolves to 0x100 + 0x0 = 0x100)
soc.uart.LCR.read(s, v, .map(soc.apb_map));           // UART LCR (resolves to 0x200 + 0xC = 0x20C)
// Each peripheral register resolves to base + sub-offset; the map selects the bus (12.7).

6. Debugging Walkthrough — two peripherals at overlapping bases

1

Placing two peripheral sub-blocks at bases whose address ranges overlap makes a register in one peripheral alias a register in another, so accesses to one silently reach the other

SUB-BLOCK BASES MUST NOT OVERLAP (RANGE, NOT POINT)
Buggy Code
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// TIMER is placed at a base that OVERLAPS GPIO's range. GPIO occupies 0x000-0x0FF (block size 0x100),
// but TIMER is placed at 0x080 -- INSIDE GPIO's range:
default_map.add_submap(gpio.default_map,  'h000);   // GPIO: 0x000-0x0FF
default_map.add_submap(timer.default_map, 'h080);   // BUG: TIMER at 0x080 overlaps GPIO's 0x080-0x0FF
// TIMER's registers (0x080 + sub-offset) collide with GPIO's upper registers -> cross-peripheral aliasing.
// e.g. TIMER.LOAD (sub 0x0 -> 0x080) aliases GPIO's register at 0x080.
Symptom

Accessing a timer register also affects a GPIO register (and vice-versa): writing TIMER.LOAD changes what a GPIO register at 0x080 reads back, and the two peripherals interfere. Low GPIO registers (below 0x080) and low timer registers work, but the overlapping region corrupts across peripherals. It looks like bizarre cross-peripheral coupling, but each peripheral's own model is correct — the SoC composition placed their address ranges on top of each other. The tell: the interference is between two different peripherals and tracks the overlapping address region.

Root Cause

Each peripheral sub-block occupies a range, not a point: GPIO at base 0x000 with a 0x100 block size spans 0x000..0x0FF. TIMER was added at base 0x080inside GPIO's range — so the two sub-blocks' address ranges overlap on 0x080..0x0FF, and registers in that region alias across the peripherals (TIMER's register at sub-offset 0x0 resolves to 0x080, the same SoC address as GPIO's register at 0x080). The composition author placed TIMER's base without accounting for GPIO's full footprint (0x100), exactly the memory-span mistake (8.2) but at the sub-block level: a block occupies base .. base + size, and the next block must sit beyond that, not inside it. It is not a per-register bug (each peripheral model is internally correct), not a bus/adapter bug, and not aliasing within one peripheral — it is a SoC-integration bug: two whole peripherals placed at overlapping base ranges. The correct placement (GPIO 0x000, TIMER 0x100, each 0x100 apart) leaves each peripheral its own non-overlapping range. This is the map-overlap lesson (3.5, 8.2) scaled up from registers/memories to peripheral blocks.

Fix

Place each peripheral sub-block at a base beyond the previous block's full footprint: with 0x100-sized blocks, GPIO 0x000, TIMER 0x100, UART 0x200, INTC 0x300 — each occupying its own non-overlapping 0x100 range. Then audit the SoC map for any sub-block whose range falls inside another's. The lesson the mini-SoC capstone teaches: a SoC register model composes peripheral sub-blocks, each occupying a range (base .. base + block_size), so neighbouring sub-blocks must be placed beyond each other's footprint — a base chosen without room for the neighbouring block's size makes two peripherals' ranges overlap and their registers alias across peripherals; it is the map-overlap bug (3.5, 8.2) at SoC/sub-block scale. Compose the SoC by reserving each peripheral's full range, exactly as you place a memory by its footprint (8.2) or registers without overlap (3.5) — the same discipline, one level up.

7. Common Mistakes

  • Placing sub-blocks at overlapping base ranges. Each peripheral occupies base .. base + size; a base inside another block's range aliases registers across peripherals — place beyond the footprint (3.5, 8.2).
  • Ignoring block size when choosing bases. A 'round number a bit later' can fall inside the previous block — reserve each peripheral's full range.
  • Re-modelling peripherals at the SoC level. Compose the existing block models as sub-blocks (add_submap); do not re-write them.
  • Not making the SoC model multi-bus where needed. If a DMA/debug master accesses registers, add a per-bus map+adapter (12.7).
  • Not distributing one shared SoC model. Create one SoC model and distribute via config_db; every component gets it (13.4).

8. Industry Best Practices

  • Compose, don't re-model. Instantiate peripheral block models as sub-blocks at their SoC bases with add_submap (Chapter 3).
  • Reserve each peripheral's full range. Non-overlapping base .. base + block_size; place neighbours beyond the footprint (3.5, 8.2).
  • Make the SoC multi-bus where the hardware is. Per-bus maps and adapters over one shared model; specify the map per access (12.7).
  • Distribute one shared SoC model. Via config_db; every component gets it (13.4).
  • Run the bring-up gates on the composed model. Locked, HDL paths (per-instance roots, 12.6), predictors, adapter status, resets, coverage (11.6).

9. Interview / Review Questions

10. Key Takeaways

  • The mini-SoC RAL is the synthesis capstone: compose the peripheral block models (GPIO/Timer/UART/INTC) into one hierarchical top-level model (add_submap each at its base), reached multi-bus (CPU/APB + DMA/AXI-Lite, 12.7) as one shared model distributed via config_db (13.4).
  • SoC RAL is a composition, not a re-model: instantiate existing block models as sub-blocks at their SoC bases; a peripheral register resolves to base + sub_offset (Chapter 3).
  • Sub-block base placement is a range problem: each peripheral occupies base .. base + block_size, so neighbours must sit beyond the footprint — a base chosen without room for the neighbour's size makes two peripherals' ranges overlap and their registers alias across peripherals (3.5, 8.2 at SoC scale).
  • The signature SoC-integration bug is exactly that overlapping sub-block bases — cross-peripheral aliasing — fixed by reserving each peripheral's full range (0x000/0x100/0x200/0x300 for 0x100-sized blocks).
  • The capstone unifies the whole track — composition, base ranges, multi-bus, shared model, per-instance backdoor, bring-up gates — and validating a composed SoC RAL is the coding-guideline review (13.6) applied to a system: non-overlapping ranges, per-bus maps with verified status, one shared model, per-peripheral fidelity, proven paths, and gates.