The DMA on NanoSoC arrived with more than its RTL: an 8-bit address port with no byte-write support, a WAIT_STATES parameter that changes APB timing from two clocks to three, an active-high level interrupt with sticky-until-cleared semantics, and a reset that asserts asynchronously but releases nowhere in particular. Every one of those facts lives in a different deliverable, and the integrator who receives only the RTL rediscovers each of them as a bug. The remedy is the complete list, so that nothing is rediscovered.
A delivery is eight groups, each owned by a different discipline, each checked by a different tool. The RTL group carries the synthesizable sources plus the exact file list and the parameter values used, because a file list with a surprise file is a different block. The constraint group carries block-level timing constraints and the clock definitions it assumed, which the top level will rewrite rather than reuse. The power-intent group carries the power-domain description and the isolation and retention strategy for the block. The DFT group carries the scan protocol description, wrapper-cell definitions and test-mode behaviour. The power-intent group is a machine-readable power-intent file (UPF is the public format). The verification group carries the test report for the exact delivered version and configuration, not for its cousin. The software group carries the register specification in machine-readable form plus reset values. The CDC and reset group carries crossing documentation and waiver requests with evidence. And the documentation group carries the integration guide: clocks, resets, power, interrupts and errata as the block author understands them.
Group | Must contain | NanoSoC example |
|---|---|---|
RTL | Sources, file list, parameter values used | dma_dut with WAIT_STATES = 1, 8-bit paddr |
Constraints | Block timing constraints, assumed clocks | 96 MHz system-clock assumption for the DMA |
Power intent | Machine-readable power-intent file: domains, isolation, retention needs | nano_dma.upf: DMA in the switchable domain, no retention |
DFT | Scan protocol description, wrapper cells, test modes | Scan clock and reset muxing requirements |
Verification | Report for this version and configuration | WAIT_STATES = 1 coverage, error-injection results |
Software | Machine-readable register map, reset values | CTRL, STATUS, LEN, KEY, COUNT with W1C rules |
CDC and reset | Crossing list, synchroniser assumptions, waivers | Async assert with no internal release synchroniser |
Documents | Integration guide, errata, version manifest | GO-while-busy and stale-ERR behaviour in writing |
The checker below is the standing guard for the list: it takes the eight groups, marks what arrived, and returns a nonzero status on anything missing. It also checks the version stamp agreement that the next lesson relies on: every file in the delivery must name the same version, or the delivery is two deliveries stapled together. The last lines run it on a corrected delivery to show that it can pass as well as fail.
REQUIRED = ["rtl", "constraints", "power_intent", "dft", "verification",
"software", "cdc_reset", "documents"]
STAMP = "dma-2.3.0" # every file must carry this version string
delivery = {
"rtl": ("dma_dut.sv", "dma-2.3.0"),
"constraints": ("nano_dma.sdc", "dma-2.3.0"),
"power_intent": (None, None), # nano_dma.upf not delivered this time
"dft": ("nano_dma.scan_protocol.txt", "dma-2.3.0"),
"verification": ("dma_verif_report.md", "dma-2.3.0"),
"software": ("dma_regs.csv", "dma-2.3.0"),
"cdc_reset": ("dma_crossings.txt", "dma-2.2.0"), # stale stamp
"documents": ("dma_integration_guide.md", "dma-2.3.0"),
}
def check(d):
missing = sorted(g for g in REQUIRED if d[g][0] is None)
stale = sorted(g for g in REQUIRED if d[g][0] is not None and d[g][1] != STAMP)
status = 1 if (missing or stale) else 0 # nonzero status fails the build
return missing, stale, status
missing, stale, status = check(delivery)
assert missing == ["power_intent"], missing
assert stale == ["cdc_reset"], stale
assert status == 1
coverage = (len(REQUIRED) - len(missing)) / len(REQUIRED)
assert abs(coverage - 0.875) < 0.001, coverage
# the redelivery: power intent arrives, crossing list restamped
fixed = dict(delivery, power_intent=("nano_dma.upf", "dma-2.3.0"),
cdc_reset=("dma_crossings.txt", "dma-2.3.0"))
assert check(fixed) == ([], [], 0)
print("PASS: 7 of 8 groups present, 1 missing, 1 stale stamp, status 1; corrected delivery gives status 0")The checker output for the exact version string under review, with zero missing groups and zero stale stamps.
Parameter values recorded against the verification report: the report must cover WAIT_STATES = 1 because that is what ships.
Behavioural rules that no structural format carries, such as LEN writes ignored while busy, written in the integration guide and the driver contract.
Run the checker in the build on every redelivery; a redelivery that drops a file must fail loudly rather than silently reuse the old one. When a simulation surprise traces to the block, read the integration guide first: if the behaviour is documented, the bug is in the integration; if it is not, file it as missing collateral and demand the redelivery the next lesson prices.
Refuse, or accept only with a documented rerun: the APB timing differs by a whole clock, and every access the interconnect times assumed the two-to-three-cycle behaviour. A report for the wrong configuration is missing collateral with a nicer name.