A post-silicon bug without a record gets fixed twice and worked around three times, differently each time, because every team rediscovers it from symptoms. The erratum record stops that: one entry, one reproducer, one workaround reference, one severity, visible to hardware and software alike. On NanoSoC the worked example is the DMA error flag that a new job does not clear, so the interrupt stays high after recovery. This lesson writes that entry. Its one job is the record; making the workaround trustworthy is next.
A word on provenance first. Sticky ERR, the silent ignoring of LEN and KEY writes while BUSY, and the IRQ_EN copy on every control write are the DMA's specified behaviour, inherited from the UVM course, not a bug that survived verification. They are unsafe to use naively, so the hardware-software contract records them, and a design review decides whether each is a hardware defect (log an erratum) or a documented limitation (ship the driver rule). Here we log the sticky ERR as ERR-004 to practise the record; ERR-001 to ERR-003 are not shown, and ERR-001 in the checker below is an illustrative cosmetic entry.
An erratum entry has seven fields. Identity: the number and the revisions it affects. Symptom: what software sees, with a reproducer short enough to run on the bench. Root cause: the mechanism, traced to the RTL behaviour, never to a suspicion. Severity: what breaks and how often, from cosmetic to data-corrupting to hanging, stated both without and with the workaround, and the table says which is which. Workaround: the reference to the driver rule that contains it, with the conditions under which it holds. Detection: how the driver tells which silicon revision it is on. And silicon plan: erratum carried, metal fix, or respin, with the owner and the target revision. ERR-004 records the sticky error flag: starting a job clears DONE but not ERR, so after a failed transfer followed by a good one the level interrupt never drops, because the flag is the OR of both. Any driver that clears DONE alone re-enters its handler forever, because the line stays high.
Field | ERR-004 entry | Why the field matters |
|---|---|---|
Identity | ERR-004, affects all revisions | Software must know whether its chip is affected |
Symptom | Interrupt stuck high after a failed job then a good job | Bench reproducer in three register writes |
Root cause | GO clears DONE, never ERR; interrupt is the OR of both | Points at the RTL, ends speculation |
Severity | Without the workaround: moderate, interrupt storm until both flags are cleared, no silent corruption. With the driver rule: cosmetic | Prioritises the fix against the ECO prices |
Workaround | Always write 0x6 to STATUS after each job | Driver rule, proven in the next lesson |
Detection | NanoSoC has no revision or ID register, so software cannot tell silicon revisions apart: a reason to add one to the next chip | A workaround that cannot be conditioned on the revision is applied forever |
Silicon plan | Carry erratum; no ECO, workaround complete | Closes the loop or schedules it |
# The errata completeness checker: no entry ships with a blank field,
# a missing reproducer, or a severity outside the scale.
SEVERITIES = ("cosmetic", "moderate", "severe", "critical")
FIELDS = ("symptom", "cause", "workaround", "detection", "plan")
def audit(errata):
bad = []
for e in errata:
for field in FIELDS:
if not e.get(field):
bad.append((e["id"], field))
for key in ("severity_without", "severity_with"):
if e.get(key) not in SEVERITIES:
bad.append((e["id"], key))
sw, sv = e.get("severity_without"), e.get("severity_with")
if sw in SEVERITIES and sv in SEVERITIES:
if SEVERITIES.index(sv) > SEVERITIES.index(sw):
bad.append((e["id"], "workaround cannot raise severity"))
return bad
errata = [
{"id": "ERR-001", "symptom": "typo in register table", "cause": "doc",
"severity_without": "cosmetic", "severity_with": "cosmetic",
"workaround": "none needed", "detection": "n/a", "plan": "fix in doc"},
{"id": "ERR-004", "symptom": "irq stuck after fail then pass",
"cause": "GO clears DONE not ERR",
"severity_without": "moderate", "severity_with": "cosmetic",
"workaround": "write 0x6 to STATUS", "detection": "no ID register",
"plan": "carry; add ID register next chip"},
]
assert audit(errata) == []
by_sev = {}
for e in errata:
by_sev[e["severity_without"]] = by_sev.get(e["severity_without"], 0) + 1
assert by_sev == {"cosmetic": 1, "moderate": 1}, by_sev
# the checker must be able to refuse: blank detection, bad scale, no reproducer
broken = [dict(errata[1], detection=""), dict(errata[1], id="ERR-005", severity_with="trivial"),
dict(errata[1], id="ERR-006", symptom="")]
assert audit(broken) == [("ERR-004", "detection"), ("ERR-005", "severity_with"),
("ERR-006", "symptom")], audit(broken)
print("PASS: 2 errata complete; blank detection, bad severity and missing reproducer refused")Every post-silicon issue maps to an erratum entry or to a waived-check escape with its evidence note.
Each entry carries a bench reproducer short enough to rerun, not a waveform screenshot.
The severity scale is used consistently: a hang is never cosmetic, and every entry states severity both without and with the workaround.
Rerun each reproducer on the current silicon revision before publishing: an erratum fixed silently by an ECO without a record update is as harmful as a missing entry, because drivers keep paying the workaround cost. The escape-rate table from the waiver topic consumes these entries; feed it, and the granting bar for the next chip improves automatically.
No: without a reproducer nobody can confirm the workaround contains this bug rather than hiding it. The checker refuses the entry until the three-write bench sequence is attached.