Skip to content

UVM RAL · Chapter 14 · Industry Case Studies

Case Study: Interrupt Controller Registers

This capstone models an interrupt controller, whose defining RAL challenge is that several register views act on one shared underlying state. The controller has a pending state, one bit per source, and exposes it through multiple registers with different semantics. A status register shows the pending bits, a set register writes one to raise a bit, a clear register writes one to clear a bit, an enable register masks per source, and a masked-status view shows the effective interrupt. The crucial fact is that set, clear, and status are three views of one pending-state register, not independent registers that happen to relate. This is shared-storage aliasing at the peripheral level, and it is exactly where the signature bug lives. This case builds the model and breaks set, clear, and status modelled as independent mirrors, so a write to set is not visible when reading status.

Foundation14 min readUVM RALcase studyinterrupt controllershared stateW1S W1C

Chapter 14 · Section 14.4 · Industry Case Studies · Capstone

1. The Peripheral and Its Register Map

An interrupt controller aggregates many sources into a pending state and exposes it through several access views.

  • IRQ_STATUS (RO, offset 0x0) — the pending bits; hardware sets them on events (volatile).
  • IRQ_SET (WO/W1S, offset 0x4) — write 1 to set a pending bit (software-trigger / test).
  • IRQ_CLEAR (WO/W1C, offset 0x8) — write 1 to clear a pending bit.
  • IRQ_ENABLE (RW, offset 0xC) — per-source enable/mask.
  • IRQ_MASKED_STATUS (RO, offset 0x10) — pending AND enabled (the effective interrupt).
Interrupt controller: IRQ_SET, IRQ_CLEAR, IRQ_STATUS as three views of one shared pending state; IRQ_ENABLE masks to IRQ_MASKED_STATUSsets bitsclears bitsread hereAND enableIRQ_SET (WO/W1S)write 1 -> SET a pendingbitSHARED pending stateone bit/source;SET/CLEAR/STATUS all act onTHISIRQ_CLEAR (WO/W1C)write 1 -> CLEAR a pendingbitIRQ_STATUS (RO)READS the shared pendingbits (hardware-set)IRQ_MASKED_STATUS(RO)pending AND IRQ_ENABLE =effective interrupt12
Figure 1 — the interrupt controller: several register VIEWS onto ONE shared pending state. IRQ_STATUS reads the pending bits (hardware sets them); IRQ_SET writes 1 to SET a bit; IRQ_CLEAR writes 1 to CLEAR a bit — all acting on the SAME underlying pending-state register. IRQ_ENABLE masks per source; IRQ_MASKED_STATUS = pending AND enable. Because SET/CLEAR/STATUS are three views of one state, a write via IRQ_SET must be visible via IRQ_STATUS — modelling them as INDEPENDENT registers (independent mirrors) makes the views diverge (shared-storage aliasing, 3.4/12.5).

2. Industry Context — why the interrupt controller is the shared-state case

Interrupt controllers are ubiquitous, and every one exposes its pending state through multiple access views — separate SET/CLEAR/STATUS (or RAW/MASKED) registers that all touch one underlying state. This set-clear-status trio over shared storage is the interrupt controller's signature structure, and modelling it faithfully — as one state seen several ways, not several independent registers — is the whole challenge. It generalizes the aliasing lesson (3.4, 12.5) to a peripheral where getting it wrong is especially visible: a software-triggered interrupt (IRQ_SET) that does not appear in IRQ_STATUS breaks the most basic interrupt-handling flow.

3. The Register Model — one shared state, several views

Model the shared pending state so that SET/CLEAR effects are reflected in IRQ_STATUS (via prediction/callbacks tying the views to one state), not as independent mirrors:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// The interrupt controller: SET/CLEAR/STATUS are VIEWS of ONE pending state. Tie them together.
class intc_reg_block extends uvm_reg_block;
  `uvm_object_utils(intc_reg_block)
  rand uvm_reg IRQ_STATUS, IRQ_SET, IRQ_CLEAR, IRQ_ENABLE, IRQ_MASKED_STATUS;
  function new(string name="intc_reg_block"); super.new(name, UVM_NO_COVERAGE); endfunction
 
  virtual function void build();
    default_map = create_map("default_map", 0, 4, UVM_LITTLE_ENDIAN);
    IRQ_STATUS = uvm_reg::type_id::create("IRQ_STATUS"); IRQ_STATUS.configure(this); IRQ_STATUS.build();
    default_map.add_reg(IRQ_STATUS, 'h0);   // RO + volatile (hardware sets pending bits)
    IRQ_SET = uvm_reg::type_id::create("IRQ_SET"); IRQ_SET.configure(this); IRQ_SET.build();
    default_map.add_reg(IRQ_SET, 'h4);       // WO / W1S
    IRQ_CLEAR = uvm_reg::type_id::create("IRQ_CLEAR"); IRQ_CLEAR.configure(this); IRQ_CLEAR.build();
    default_map.add_reg(IRQ_CLEAR, 'h8);     // WO / W1C
    IRQ_ENABLE = uvm_reg::type_id::create("IRQ_ENABLE"); IRQ_ENABLE.configure(this); IRQ_ENABLE.build();
    default_map.add_reg(IRQ_ENABLE, 'hC);    // RW
    IRQ_MASKED_STATUS = uvm_reg::type_id::create("IRQ_MASKED_STATUS"); IRQ_MASKED_STATUS.configure(this); IRQ_MASKED_STATUS.build();
    default_map.add_reg(IRQ_MASKED_STATUS, 'h10);  // RO = pending AND enable
    lock_model();
  endfunction
endclass
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// A callback ties IRQ_SET/IRQ_CLEAR writes to the SHARED pending state seen via IRQ_STATUS (9.x):
class intc_shared_state_cb extends uvm_reg_cbs;
  intc_reg_block regs;
  virtual task post_write(uvm_reg_item rw);
    // On IRQ_SET write: SET those bits in IRQ_STATUS's predicted value. On IRQ_CLEAR: clear them.
    if (rw.element == regs.IRQ_SET)
      void'(regs.IRQ_STATUS.predict(regs.IRQ_STATUS.get_mirrored_value() | rw.value[0]));   // W1S -> STATUS
    else if (rw.element == regs.IRQ_CLEAR)
      void'(regs.IRQ_STATUS.predict(regs.IRQ_STATUS.get_mirrored_value() & ~rw.value[0]));  // W1C -> STATUS
  endtask
endclass
// Now a write to IRQ_SET is reflected in IRQ_STATUS's mirror — the views share ONE state.

4. Adapter, Predictor, and an Interrupt-Flow Sequence

Wire to APB (as before), and drive the basic interrupt flow: trigger, observe, clear:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// Trigger interrupt source 3 (IRQ_SET), confirm it in IRQ_STATUS, then clear it (IRQ_CLEAR).
class intc_flow_seq extends uvm_reg_sequence#(uvm_sequence#(uvm_reg_item));
  intc_reg_block regs;
  virtual task body();
    uvm_status_e s;  uvm_reg_data_t st;
    regs.IRQ_ENABLE.write(s, 32'h0000_0008);   // enable source 3
    regs.IRQ_SET.write(s, 32'h0000_0008);      // W1S: trigger source 3's pending bit
    regs.IRQ_STATUS.read(s, st);               // MUST show bit 3 set (shared state)
    regs.IRQ_CLEAR.write(s, 32'h0000_0008);    // W1C: clear source 3
    regs.IRQ_STATUS.read(s, st);               // now shows bit 3 cleared
  endtask
endclass

5. The Masked Status View

IRQ_MASKED_STATUS is pending AND enable — a derived read-only view; model it as reflecting the shared pending state gated by IRQ_ENABLE:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// IRQ_MASKED_STATUS = IRQ_STATUS (pending) AND IRQ_ENABLE. A derived RO view of the shared state.
// (predict it from pending & enable, or verify it against status & enable in the scoreboard)
uvm_reg_data_t effective = regs.IRQ_STATUS.get_mirrored_value() & regs.IRQ_ENABLE.get_mirrored_value();
// effective is what IRQ_MASKED_STATUS should read — the enabled subset of the pending bits.

6. Debugging Walkthrough — the interrupt you set that never showed

1

Modelling IRQ_SET, IRQ_CLEAR, and IRQ_STATUS as independent registers makes a write to IRQ_SET update only its own mirror, so reading IRQ_STATUS shows the bit not set — the views diverge from the one shared hardware state

ONE SHARED STATE, SEVERAL VIEWS
Buggy Code
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// SET/CLEAR/STATUS modelled as INDEPENDENT registers with INDEPENDENT mirrors, no tie between them:
// (no callback/prediction linking IRQ_SET/IRQ_CLEAR writes to IRQ_STATUS's state)
regs.IRQ_SET.write(s, 32'h0000_0008);   // updates only IRQ_SET's OWN mirror
regs.IRQ_STATUS.read(s, st);            // BUG: model expects IRQ_STATUS unchanged -> auto-compare MISMATCH
// The DUT sets bit 3 in the shared pending state (visible via IRQ_STATUS), but the MODEL's IRQ_STATUS
// mirror was never updated by the IRQ_SET write -> model says bit 3 is 0, DUT says 1 -> mismatch.
Symptom

After writing IRQ_SET to trigger source 3, reading IRQ_STATUS produces an auto-compare mismatch: the DUT shows bit 3 set (the shared pending state was updated), but the model's IRQ_STATUS mirror shows bit 3 clear (its mirror was never touched by the IRQ_SET write). The most basic interrupt flow — trigger then observe — 'fails,' even though the DUT behaves correctly. The tell: a write via one register (IRQ_SET) has a real effect visible via another register (IRQ_STATUS) in the DUT, but the model treats them as unrelated, so its IRQ_STATUS never reflects the IRQ_SET.

Root Cause

IRQ_SET, IRQ_CLEAR, and IRQ_STATUS are three views of one shared pending-state register — writing IRQ_SET sets bits in the shared state that read back through IRQ_STATUS — but they were modelled as independent registers with independent mirrors, with nothing tying them together. So the IRQ_SET write updated only IRQ_SET's mirror; the model's IRQ_STATUS mirror was never updated, and it still predicted bit 3 clear. Meanwhile the DUT's shared pending state did set bit 3 (correctly), so a frontdoor read of IRQ_STATUS returned bit 3 set, mismatching the model's stale (independent) mirror. This is shared-storage aliasing (3.4, 12.5) at the peripheral level: multiple register views onto one state must be modelled as one state, or a write through one view is invisible through the others in the model. The DUT, bus, and prediction are fine; the model structure is wrong — it represented one shared state as several independent registers, so the views cannot stay coherent. (This is the interrupt-controller analog of modelling aliased registers as independent, 12.5.)

Fix

Model the shared pending state so SET/CLEAR effects are reflected in IRQ_STATUS: tie the views together with prediction/callbacks — on an IRQ_SET write, set those bits in IRQ_STATUS's predicted value (W1S semantics); on an IRQ_CLEAR write, clear them (W1C) — the callback of Section 3. Then a write to IRQ_SET updates the shared pending value that IRQ_STATUS reads, and the views stay coherent. The lesson the interrupt-controller case teaches: when several registers are views of one shared state (an interrupt controller's IRQ_SET/IRQ_CLEAR/IRQ_STATUS over the pending state), they must be modelled as one shared state seen through several access views, not as independent registers with independent mirrors — otherwise a write through one view (IRQ_SET) is invisible through another (IRQ_STATUS) in the model, and the views diverge from the single hardware state. It is the peripheral form of shared-storage aliasing (3.4, 12.5).

7. Common Mistakes

  • Modelling SET/CLEAR/STATUS as independent registers. They are views of one shared pending state — a SET must be visible in STATUS; tie them with prediction/callbacks (3.4, 12.5).
  • Not marking IRQ_STATUS volatile. The hardware sets pending bits, so its mirror falls behind if not volatile (0.1, 11.1).
  • Treating IRQ_MASKED_STATUS as independent. It is pending AND enable — a derived view of the shared state gated by IRQ_ENABLE.
  • Modelling IRQ_SET/IRQ_CLEAR as RW. They are WO/W1S/W1C — writing 1 sets/clears; there is no meaningful read-back of the write register itself (1.5).
  • Forgetting the W1S/W1C direction. SET sets on write-1, CLEAR clears on write-1 — opposite effects on the same state.

8. Industry Best Practices

  • Model shared state once, expose it through views. SET/CLEAR/STATUS (and MASKED) reflect one pending state — tie them with prediction/callbacks.
  • Reflect SET/CLEAR writes into STATUS. A callback that sets (W1S) or clears (W1C) the shared value keeps the views coherent (9.x).
  • Mark IRQ_STATUS volatile. Hardware sets pending bits asynchronously (0.1).
  • Model MASKED_STATUS as derived. Pending AND enable — predict or check it from the shared state and IRQ_ENABLE.
  • Diagnose 'set not visible in status' as shared-state modelled independent. A write via one view invisible via another means the views were not tied to one state (12.5).

9. Interview / Review Questions

10. Key Takeaways

  • The interrupt controller's defining challenge is that several register views act on one shared pending state: IRQ_STATUS reads it, IRQ_SET (W1S) sets bits in it, IRQ_CLEAR (W1C) clears bits in it, IRQ_ENABLE masks it, IRQ_MASKED_STATUS = pending AND enable.
  • IRQ_SET/IRQ_CLEAR/IRQ_STATUS are three views of one register — a write via IRQ_SET must be visible via IRQ_STATUS — so they must be modelled as one shared state seen through several views, not independent registers.
  • The signature bug is modelling them independent (independent mirrors): a write to IRQ_SET updates only its own mirror, so IRQ_STATUS shows the bit not set and the views diverge from the single hardware state — breaking trigger-then-observe.
  • The fix ties the views to one state via prediction/callbacks: reflect IRQ_SET (set) and IRQ_CLEAR (clear) writes into IRQ_STATUS's value — the shared-storage aliasing pattern (3.4, 12.5) plus W1S/W1C side effects (1.5).
  • Mark IRQ_STATUS volatile (hardware sets pending bits), model IRQ_MASKED_STATUS as derived (pending AND enable), and diagnose 'set not visible in status' as shared state modelled independently.