Skip to content
DenseChip
Learn
PracticeCode labInterview

DenseChip

Navigate

Menu

LearnPracticeCode labInterview

Tools

All toolsSV editorCode repoResume
DenseChip

Practice, courses, and chip-design tools in one focused workspace.

LearnPracticeCode labInterviewToolsLearning paths

© 2026 DenseChip

AboutRoadmapCareersInternshipsFAQContactPrivacyTerms
Skip to lesson

Course contents

  • How to Use This Course
  • The NanoSoC Tour
  • Reading Path
  • SoC Glossary10
  • SoC Metrics Reference
  • SoC Cheatsheet
  • What SoC Integration Is12
  • Architecture and Budgets21
  • Memory Maps and Address Decode26
  • Interconnect and Bus Fabrics26
  • Clock and Reset Architecture24
  • Power Architecture21
  • Interrupts, DMA and Peripherals15
    • Interrupt Controller Integration
    • Interrupt Routing and Latency
      • From DMA irq to CPU Entry
      • Latency Budgets and Deadlines
      • Spurious and Lost Interrupts
    • DMA Integration
    • Peripheral Subsystem
    • Pad Muxing and GPIO
  • Memories, Boot and Security21
  • IP Integration and Handoff21
  • Top-Level Verification and Bring-up21
  • Package, Pads and I/O18
  • Chip Signoff and Tapeout18
  • SoC Interview Prep16
  • Capstone: Integrate NanoSoC14
Loading lesson

←Interrupt Routing and Latency
  1. Topics
  2. SoC Design
  3. Interrupts, DMA and Peripherals
  4. Interrupt Routing and Latency
  5. Spurious and Lost Interrupts
On this page

    Part 7 of 14, lesson 6 of 15Part 7 of 14/Interrupts, DMA and Peripherals/Lesson 6 of 15All levels

    Spurious and Lost Interrupts

    Four signatures: the interrupt that never comes, the one that comes twice, the one that never stops, and the one that arrives with nothing to do.

    Why it matters

    This lesson has one job: teach the failure signatures so a lab symptom maps to a cause in minutes. A lost interrupt looks like a hang with healthy status bits. A doubled interrupt looks like corrupt counts. A storm looks like a system that does nothing but service one device. A spurious interrupt makes a handler run and find nothing to do. Each has a distinct mechanism, a distinct test that catches it, and a distinct fix; guessing between them wastes the bring-up week this section is trying to save.

    The core idea

    A lost interrupt means the level fell before the controller acted on it, or the event never reached the status bit. Two mechanisms dominate. The first is the missed pulse across a clock domain: the source asserted for less than the synchroniser can see, so RAW never sets while the source believes it signalled. The second is the clear-versus-set race: software writes 1 to clear a status bit in the same cycle the hardware sets it for a new event, and if the clear wins, the new event vanishes with no trace. The fix is arbitration in the hardware: set-wins. The real DMA is set-wins, because a later done <= 1 overrides the W1C done <= 0 in the same clocked block. A third cause belongs to software: a read-modify-write on a write-1-to-clear register writes back a 1 for every bit that read as 1, including bits the handler never looked at, so an event that arrived between the read and the write is cleared silently. Write only the bits you mean to clear (for the DMA, 0x6 clears DONE and ERR and nothing else).

    A doubled interrupt is the mirror image: the handler cleared the status the controller sees but not the cause, or the source re-asserts, so the level never dropped and the handler re-entered on exit. A storm is a level that never drops because the clear never took effect. On NanoSoC an APB write is not posted from software's point of view (each access completes before the next starts, as the bring-up lesson shows), so the storm comes from clearing the wrong bit (the DMA's ERR is sticky and a job start does not clear it, so 0x2 alone leaves irq high), from IRQ_EN being re-written, or from a source that really does re-assert. On a different bridge that does post writes, the clear can still be in flight when the handler returns, and a read-back after the clear flushes it. Distinguishing storm from chatter matters: a storm re-enters immediately with no new event, at handler-return speed; a chatty device re-enters at event speed with fresh status each time. Count the events per entry and the two are unmistakable.

    A spurious interrupt is an entry with nothing to service. The main hardware cause is propagation: after the source is cleared or masked, irq_cpu stays high for roughly 135 to 182 ns while the fall travels through X5, the INTC output register and X6. A DMA register access is only 125 ns, so a handler that clears the source and returns quickly can be re-entered while the CPU input is still high, and TOP reads 0x1F. That is not an error. The handler must treat TOP = 0x1F as a normal exit, and the dispatch loop of the priority lesson must do the same on its re-read.

    Signature

    Mechanism

    Caught by

    Fix

    Never comes, source pin healthy

    Pulse shorter than sync window; clear-versus-set race with clear winning; RMW on a W1C register writes back stale 1s and clears a new event

    Ratio-sweep pulse tests; directed race tests, formal; a test that sets a second event between the read and the clear

    Held levels; sticky status; set-wins arbitration; write only the bits you mean to clear.

    Comes twice per event

    Handler clears the status but not the cause, or the source re-asserts

    Count entries per event in a directed test

    Clear the cause, not just the bit; hold-off the source until drained.

    Never stops, no new events

    Wrong bit cleared (DMA ERR is sticky); IRQ_EN re-written; on a bridge that posts writes, the clear still in flight

    Software storm tests

    Write 0x6 to the DMA STATUS; read-back after clear on any posting bridge.

    Entry with TOP = 0x1F

    Clear or mask has not yet propagated through X5, the INTC output register and X6 (135.4 to 182.3 ns)

    Back-to-back clear-and-return test

    Treat TOP = 0x1F as a normal exit, never an error; no hardware fix needed.

    Worked example: the race made visible

    The script below models a W1C status bit cycle by cycle. A handler takes the interrupt when it sees the bit set and clears it three cycles later. Twenty events arrive; four of them land exactly on a clear cycle. The two arbitration orders give different entry counts, so the check can fail: a clear-wins bit enters 16 times and loses four events, while set-wins enters 20 times, once per event. A second check models the read-modify-write mistake on a two-bit status.

    example.py
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    def run(arbitration, event_cycles):
        status, clear_at, entries = 0, None, 0
        for c in range(max(event_cycles) + 10):
            hw_set = c in event_cycles
            sw_clr = (clear_at == c)
            if sw_clr:
                clear_at = None
            if hw_set and sw_clr:                    # same-cycle collision
                status = 1 if arbitration == "set-wins" else 0
            elif hw_set:
                status = 1
            elif sw_clr:
                status = 0
            if status and clear_at is None:          # handler takes the interrupt
                entries += 1
                clear_at = c + 3                     # and clears three cycles later
        return entries
    
    cycles, t = [], 0
    for i in range(20):
        cycles.append(t)
        t += 3 if i % 5 == 3 else 10                 # events 4, 9, 14, 19 hit a clear
    cycles = set(cycles)
    entries_set = run("set-wins", cycles)
    entries_clr = run("clear-wins", cycles)
    print(f"set-wins:   {entries_set} entries for {len(cycles)} events")
    print(f"clear-wins: {entries_clr} entries for {len(cycles)} events")
    assert entries_set == 20 and entries_clr == 16
    
    def rmw_clear(status_now, status_read, want):
        # RMW: write back the value read, with the wanted bit forced on.
        written = status_read | want
        return status_now & ~written                 # W1C: 1s clear
    DONE, ERR = 0x2, 0x4
    # The handler only means to clear DONE. If ERR was already set when it read
    # STATUS, the RMW writes that 1 back and clears ERR unexamined.
    assert rmw_clear(DONE | ERR, DONE | ERR, DONE) == 0        # RMW wipes unexamined ERR
    assert DONE | ERR == 0x6 and (DONE | ERR) & ~DONE == ERR   # a targeted write of DONE keeps ERR
    print("PASS: set-wins keeps all 20 events, clear-wins drops 4, RMW on W1C drops ERR")

    What the review looks for

    • Every W1C status bit documents its simultaneous set-and-clear behaviour, and set-wins is the default.

    • Handlers clear exactly the bits they handled; no read-modify-write on a W1C register.

    • Every handler ends with clear, read-back, then return, and treats TOP = 0x1F on re-entry as a normal exit.

    • The storm test exists: force the source, mask nothing, and confirm the handler runs exactly once per event.

    Verify and debug

    For a stuck interrupt, read the source status with the handler masked: still set means the clear path is broken (check ERR as well as DONE), cleared means the clear works but something re-asserts. For a missed interrupt, force the source and watch RAW: RAW never setting points at the synchroniser or the wire, RAW setting with no handler points at masking or CPU enables. For a doubled count, single-step the handler and watch whether the status bit survives its own clear; if it does, the clear is writing the wrong bit. For an entry with TOP = 0x1F, check the clear-to-return time against the 182.3 ns bound before calling it a bug.

    Interrupt storm from a level source not cleared: the handler clears DONE but leaves a sticky ERR set, so irq stays high and the same interrupt re-takes the moment the handler returns. Caught by software storm tests; fixed by writing 0x6 to STATUS and reading it back.

    If asked how an interrupt gets lost without any error flag, give the mechanisms, pulse-versus-sync, clear-versus-set and read-modify-write on W1C, with the fix for each. Then volunteer the spurious case: an entry with nothing pending is normal after a clear, because the fall takes up to 182.3 ns to reach the CPU.

    Check yourself The handler clears DONE, reads it back as 0, returns, and immediately re-enters with DONE set and new data. Storm or chatter?

    Chatter: DONE is genuinely set again with new data, so a new event arrived. A storm would re-enter with DONE clear or with the same stale event; here the device is simply producing faster than the handler drains, which is a rate problem, not a clear problem.

    Check yourself A handler is re-entered right after it returns, reads TOP = 0x1F, and finds every status bit clear. Is the hardware broken?

    No. The clear fell at the source, but the fall still has to cross X5, the INTC output register and X6, which takes 135.4 to 182.3 ns, longer than a 125 ns register access. The CPU took the interrupt on the old level. The handler should treat TOP = 0x1F as a normal exit.

    Related topics

    • Latency Budgets and Deadlines

    • DMA Completion and Clock Gating

    • Hardware Debug: Hangs and Deadlocks

    Previous

    141 / 289

    Next: DMA Integration

    Up next

    Up nextDMA Integration

    Sign in to save progress

    Practice this lessonPrevious lessonReport an error

    See also

    • From DMA irq to CPU Entry