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
  • Memories, Boot and Security21
    • Memory Subsystem Integration
    • ECC and Error Handling
      • Parity Versus ECC
      • Error Reporting Paths
      • Scrubbing and First Access
    • External Memory Controller Integration
    • Boot ROM and Boot Modes
    • eFuse and OTP
    • Secure Boot and Root of Trust
    • Security Architecture
  • 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

←ECC and Error Handling
  1. Topics
  2. SoC Design
  3. Memories, Boot and Security
  4. ECC and Error Handling
  5. Error Reporting Paths
On this page

    Part 8 of 14, lesson 5 of 21Part 8 of 14/Memories, Boot and Security/Lesson 5 of 21All levels

    Error Reporting Paths

    From a flipped bit to software: correctable counters, uncorrectable interrupts, and what the fabric returns.

    Why it matters

    This lesson has one job: make sure no error disappears between the memory and the software that must handle it. A corrected error nobody counts hides a dying SRAM until a double error arrives. An uncorrectable error that returns as ordinary data corrupts the DMA stream silently. The reporting path has three branches, one per severity, and each must end somewhere software or hardware acts on.

    The core idea

    Correctable errors increment a saturating 8-bit counter, and a sticky per-bank status bit records which bank corrected; only when the count reaches a threshold (a NanoSoC teaching value, 255 by default) does a maintenance interrupt fire so software can retire a sick line. Uncorrectable errors on a CPU fetch trap the CPU: the interconnect answers the read with SLVERR so the fault looks like any other slave refusal, and a sticky status bit names the bank. Uncorrectable errors on DMA data cannot trap a fetcher, and NanoSoC has no way to mark one word as bad: the 32-bit stream carries only data, valid, ready and last, with no poison sideband. What happens instead is that the SBR read engine sees the SLVERR response on the AXI read, sets STATUS.ERR and raises irq_sbr. The driver must therefore treat SBR ERR as an instruction to discard the whole destination buffer, and it must recover the DMA, which stays BUSY waiting for words that will never arrive, by a reset or by a shorter retry. Where a stream does carry a poison bit, the protocol is Poison and Containment and the response codes are Error Responses. The aggregator below, a compiling teaching model, collects per-bank flags with write-1-to-clear status. Its status registers are set-wins: if a new event and a software clear land in the same cycle, the event survives, which is the behaviour the interrupts section teaches as the default. Clear-wins would lose the event.

    nano_err_agg.sv
    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
    // NanoSoC teaching values: error aggregator skeleton.
    // One flag pair per bank, one combined interrupt, write-1-to-clear status.
    // Correctable: sticky bank flag plus a saturating counter; the interrupt
    // fires only when the counter reaches THRESH. Uncorrectable: sticky flag,
    // interrupt at once. Status clears are write-1-to-clear and SET-WINS.
    module nano_err_agg #(
      parameter int THRESH = 255
    ) (
      input  logic       clk,
      input  logic       rst_n,
      input  logic [1:0] corr_i,
      input  logic [1:0] uncorr_i,
      input  logic [1:0] status_w1c,
      input  logic       cnt_clr,
      output logic [1:0] status_corr,
      output logic [1:0] status_uncorr,
      output logic [7:0] corr_count,
      output logic       irq
    );
      logic [8:0] sum;
      assign sum = {1'b0, cnt_clr ? 8'h00 : corr_count} + corr_i[0] + corr_i[1];
      always_ff @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
          status_corr   <= 2'h0;
          status_uncorr <= 2'h0;
          corr_count    <= 8'h00;
        end else begin
          status_corr   <= (status_corr   & ~status_w1c) | corr_i;
          status_uncorr <= (status_uncorr & ~status_w1c) | uncorr_i;
          corr_count    <= sum[8] ? 8'hFF : sum[7:0];
        end
      end
      assign irq = (corr_count >= 8'(THRESH)) | (|status_uncorr);
    endmodule

    The worked example

    Thresholds turn counts into policy, and the counter width and the threshold must be chosen together from the upset rate, not copied from another project. The method: software reviews and clears the counter once per review period; the threshold is the baseline number of corrections expected in one period times a margin, so the interrupt fires only when the bank is worse than normal; and the counter must be wide enough to reach the threshold without saturating first. The script applies it with illustrative values (a 7-day review period and a margin of 4) and shows the default 255 is only right for one band of rates: for a very quiet bank it never fires within a year, and for a busy one the 8-bit counter saturates below the threshold that policy needs. The second part reproduces the same-cycle collision of a new event and a clear under both update styles.

    example.py
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    import math
    REVIEW_DAYS, MARGIN, COUNTER_MAX = 7, 4, 255
    def policy(baseline_per_day):
        thresh = max(1, math.ceil(baseline_per_day * REVIEW_DAYS * MARGIN))
        return thresh, thresh <= COUNTER_MAX
    for baseline in (0.5, 5, 9, 10, 100):
        thresh, fits = policy(baseline)
        days_to_default = 255 / baseline
        print(f"baseline {baseline}/day: threshold {thresh}, fits 8-bit counter: {fits}, default 255 fires after {days_to_default:.1f} days at baseline")
    assert policy(0.5) == (14, True)
    assert policy(9) == (252, True)
    assert policy(10) == (280, False), "an 8-bit counter saturates at 255, below the policy threshold"
    assert 255 / 0.5 > 365, "default threshold never fires within a year on a quiet bank"
    def update(status, event, w1c, set_wins):
        return (status & ~w1c) | event if set_wins else (status | event) & ~w1c
    assert update(0b1, 0b1, 0b1, set_wins=True) == 0b1, "set-wins keeps the new event"
    assert update(0b1, 0b1, 0b1, set_wins=False) == 0b0, "clear-wins loses it"
    print("PASS: threshold = baseline x period x margin; 255 only suits baselines up to 9 per day; set-wins keeps a colliding event")

    What the review looks for

    • Every error output of every protected memory ends at a counter, an interrupt or a fabric error response; no error signal dangles, and status updates are set-wins.

    • The CPU-fetch path returns SLVERR with the faulting address logged, matching the decode error table's secure rows.

    • The DMA-data path ends at SBR STATUS.ERR and irq_sbr, the driver discards the destination buffer on ERR, and the recovery of the stalled DMA is written down.

    Verify and debug

    Inject one correctable error and confirm the counter moves with no interrupt; inject up to the threshold and confirm the maintenance interrupt; inject a correctable error in the same cycle as a status clear and confirm the flag stays set; inject an uncorrectable on a CPU fetch and confirm SLVERR plus the sticky bank flag; inject one on DMA data and confirm the SBR sees SLVERR, sets STATUS.ERR and raises irq_sbr while the DMA stays BUSY. If an injected fault vanishes, the reporting enable for that path is still at reset.

    DMA error swallowed: an error response is dropped and the transfer reports success, or the SBR error is raised but the driver keeps the half-written buffer. Symptom: silent data corruption, caught only by end-to-end data checks. Fix: propagate every error to status and interrupt, discard the destination on ERR, and add a software timeout as backstop for the DMA that stays BUSY (NanoSoC has no watchdog block; a watchdog would be an extension).

    If asked where an uncorrectable SRAM error goes, answer with the consumer: fetch errors trap via SLVERR, DMA-data errors reach the SBR as SLVERR and surface as STATUS.ERR plus irq_sbr, and both set sticky bits. NanoSoC has no poison bit on its stream, so the driver discards the buffer. One error, two paths, no silent branch.

    Check yourself A correctable error counter never moves in a fault-injection test, but reads still correct. What is miswired?

    The correction path works but the counting tap does not: the corr_i line to the aggregator is tied off or unconnected. Correction without counting is the failure mode that hides a dying bank.

    Related topics

    • Parity Versus ECC

    • Next: Scrubbing and First Access

    • Protocol First Principles: Poison and Containment

    • Cache Coherency: ECC, Poison and Recovery

    Previous

    155 / 289

    Next: Scrubbing and First Access

    Up next

    Up nextScrubbing and First Access

    Sign in to save progress

    Practice this lessonPrevious lessonReport an error