A waiver granted in month three for a synchroniser that is correct in month three can be wrong by month nine, because the clock ratio changed, the library cell arrived, or the path was re-timed around the exception. Without expiry, waivers outlive the facts that justified them, and the tapeout review inherits excuses nobody remembers. Ageing forces each waiver to be re-justified or retired on a schedule. Its one job is the clock attached to every excuse; pricing the excuses that were wrong is next.
Every waiver carries three dates: granted, review-by, and expires. The review-by date arrives first and demands re-confirmation of the evidence against the current design; the expiry date retires the waiver automatically if nobody re-confirms, returning the violation to the open list. The intervals are teaching values the project sets once: proven-safe waivers review every 90 days and expire at 180, false violations review every 30 days because checker versions change the noise, bounded-risk waivers review every 90 days with the integrator present. An ECO touching the waived path expires its waivers immediately, whatever the calendar says, because the evidence described a design that no longer exists. The rule needs a key: each waiver records the violation identifier, the signal or path pattern it covers and the baseline it was granted on, and the checker compares the waived pattern against the changed-object list of every ECO and expires the matches. A waiver recorded as a free-text note cannot be matched to a netlist change.
# Waiver ageing: bucket every live waiver by days to expiry and
# force the review list. Days and path patterns are illustrative; this is
# the same waiver table the manifest checker reads.
import fnmatch
DAY = 150 # days since project start (week 22)
REVIEWS = {"false": 30, "proven-safe": 90, "bounded-risk": 90}
EXPIRES = {"false": 90, "proven-safe": 180, "bounded-risk": 180}
waivers = [ # id, class, granted day, path pattern the waiver covers
("W-009", "false", 120, "nano_rstpwr/u_rst_sync_*"),
("W-014", "proven-safe", 60, "nano_intc/u_sync_irq_*"),
("W-021", "proven-safe", 140, "nano_apb_cdc/u_req_*"),
("W-027", "bounded-risk", 100, "nano_sbr/u_err_path/*"),
]
def ageing(day, eco_changed=()):
report = {}
for wid, kind, granted, pattern in waivers:
age = day - granted
left = EXPIRES[kind] - age
status = "due-review" if age >= REVIEWS[kind] else "fresh"
touched = any(fnmatch.fnmatch(p, pattern) for p in eco_changed)
if left <= 0 or touched:
status = "expired"
report[wid] = (age, left, status)
return report
report = ageing(DAY)
assert report["W-009"] == (30, 60, "due-review"), report["W-009"]
assert report["W-014"] == (90, 90, "due-review"), report["W-014"]
assert report["W-021"] == (10, 170, "fresh"), report["W-021"]
assert report["W-027"] == (50, 130, "fresh"), report["W-027"]
due = sorted(w for w, r in report.items() if r[2] != "fresh")
assert due == ["W-009", "W-014"], due
# expiry days: grant day plus the class lifetime
assert {w: g + EXPIRES[k] for w, k, g, _ in waivers} == {
"W-009": 210, "W-014": 240, "W-021": 320, "W-027": 280}
# an ECO that touches the synchroniser expires only the waiver it matches
eco = ageing(DAY, eco_changed=["nano_intc/u_sync_irq_dma/q0"])
assert eco["W-014"][2] == "expired"
assert [w for w, r in eco.items() if r[2] == "expired"] == ["W-014"]
# and the calendar alone expires W-009 on day 210
assert ageing(210)["W-009"][2] == "expired"
print("PASS: 4 waivers aged, review list W-009 and W-014, ECO expires only W-014")No waiver in the manifest is past expiry; expired means open, and open violations block the agenda.
The review-by list for the next 30 days has named owners, starting with the two the calculator flags above (W-009 and W-014).
Every ECO since the last review names the waivers it expired, matched by path pattern against its changed-object list.
Run the calculator nightly beside the manifest checker: it reads the same waiver table, so a waiver edited in one place and not the other fails loudly. When a review-by date arrives, the owner re-attaches evidence against the current revision; re-confirmation without fresh evidence is a new violation of process, and the integrator treats it as one.
# not-run: the ageing rule card, NanoSoC teaching values (illustrative)
false violation: review every 30 days, expires at 90
proven safe: review every 90 days, expires at 180
bounded risk: review every 90 days, expires at 180
ECO touches path: waiver expires immediately, no calendar appliesRe-confirm with fresh evidence against the current revision before expiry: rerun the check, re-attach the waveform, reset the dates. Letting it lapse turns a good waiver into an open violation the night before the review.