Mid-project, the DMA team fixes a corner-case bug and ships version 2.4.0 over 2.3.0. The fix touches the STATUS clear timing by half a cycle, the interconnect timing was closed against the old behaviour, and the driver relied on reading DONE in the same cycle the last beat appears. One quiet redelivery invalidates timing, software and verification at once. The way out is to make version bumps boring: a numbering contract that says what changed, a notice that says what to recheck, and a checker that enforces it.
Versions have three parts and each part is a promise. The major part changes only when the interface changes: ports added, removed or redefined, register offsets moved, protocol changed. Absorbing a major bump means re-verifying the interface and regenerating headers, and it is a project decision, never a quiet swap. The minor part changes when behaviour or features change behind a stable interface: a bug fix, a new mode, a timing change. Absorbing it means rerunning the block's tests in the integrated top and rechecking anything that depended on the old behaviour. The patch part changes for documentation, constraint or collateral-only updates with no RTL change; the checker below compares the file revisions of the two deliveries and refuses a patch bump whose RTL revisions differ, and only an unchanged RTL makes the patch bump a rebuild rather than a re-verification.
Every bump arrives with a change notice naming four things: what changed and why, which interfaces or behaviours are affected, what the integrator must recheck, and the earliest version the notice applies to. The notice for the STATUS-timing fix would name the STATUS register, the DONE-in-same-cycle behaviour the NanoSoC driver relies on, and demand a rerun of the DMA job test plus a timing recheck of the status path. A bump without that notice is refused at the gate, which is the next lesson.
def parse(ver):
major, minor, patch = (int(p) for p in ver.split("."))
return major, minor, patch
def rtl_changed(old_files, new_files):
"""Files whose revision differs; inputs map path -> revision."""
return {f: r for f, r in new_files.items()
if f.endswith(".sv") and old_files.get(f) != r}
def absorb(old, new, old_files, new_files):
# Returns the recheck level the integrator owes after the bump.
om, on, op = parse(old)
nm, nn, np = parse(new)
assert (nm, nn, np) > (om, on, op), "version must move forward"
if nm != om:
return "major: re-verify interface, regenerate headers, project decision"
if nn != on:
return "minor: rerun block tests in the top, recheck dependents"
changed = rtl_changed(old_files, new_files)
if changed:
raise ValueError("mislabelled patch bump, RTL changed: " + ", ".join(changed))
return "patch: rebuild only, collateral changed"
V23 = {"dma_dut.sv": "r41", "nano_dma.sdc": "r7"}
V231 = {"dma_dut.sv": "r41", "nano_dma.sdc": "r8"} # only the constraints moved
V231_BAD = {"dma_dut.sv": "r42", "nano_dma.sdc": "r8"} # RTL moved under a patch number
assert absorb("2.3.0", "3.0.0", V23, V231).startswith("major")
assert absorb("2.3.0", "2.4.0", V23, V231_BAD).startswith("minor")
assert absorb("2.3.0", "2.3.1", V23, V231).startswith("patch")
try:
absorb("2.3.0", "2.3.1", V23, V231_BAD)
raise SystemExit("mislabelled patch bump was not caught")
except ValueError as e:
assert "dma_dut.sv" in str(e)
FIELDS = ("what_changed", "affected", "recheck", "applies_from")
def notice_gaps(n):
return [f for f in FIELDS if not n.get(f)]
good = {"what_changed": "STATUS clear timing", "affected": "DONE same-cycle rule",
"recheck": "rerun job test, recheck status path", "applies_from": "2.4.0"}
assert notice_gaps(good) == []
assert notice_gaps({k: v for k, v in good.items() if k != "recheck"}) == ["recheck"]
print("PASS: revisions drive the recheck level; mislabelled patch and incomplete notice are caught")The version string recorded in the integration manifest matches every file stamp from the previous lesson.
The change notice names affected interfaces and the exact rechecks owed; the rechecks are scheduled, not assumed.
A major bump has a project-level approval with regenerated headers and interface tests; a minor bump has its rerun results attached.
When a regression appears after a redelivery, diff the RTL file revisions before debugging the failure: a patch bump with changed RTL is a mislabelled minor and the whole recheck level was wrong. Keep the previous version's integration results so the rerun has a baseline; a minor bump whose rerun produces new warnings has changed more than its notice admits. For the conceptual background see Versioning and Compatibility.
No. A collateral-only change is exactly what the patch part exists to record; reusing the version string means two different deliveries share one identity and no checker can tell them apart. Demand the patch bump and the file-revision diff.