Collect a consistent IP integration handoff
An IP handoff should identify one exact release, state its interface and clock assumptions, and show how its register definitions stay consistent across RTL, firmware and verification. A list of filenames is insufficient when those files describe different revisions. Start integration with reproducible inputs and explicit acceptance checks, then distinguish missing collateral from a check that actually failed.
Ask for a release that can be identified
Record the release identifier, source commit, licence, supported configurations and hashes of the actual delivered files. The identifier needs to cover the collateral as well as the RTL. A new driver with an old register package is a different integration input, even if the RTL directory still has the same name. Keep a change summary beside that identifier so the recipient can see which assumptions need another check.
Separate required files from convenient extras. A synthesis configuration may need a particular memory model, generated include or macro definition. A simulation configuration may use a different model for that same memory. Name both roles and their selection rules instead of treating whichever file appears first in a directory as authoritative. Reproducibility depends on the chosen configuration, not merely on preserving every file you received.
The reproducible handoff guide shows how to inventory input bytes and commands. Use that inventory to detect changes; use the checks below to decide whether those inputs form a usable integration package. A successful hash comparison establishes identity, while an interface test establishes only the behavior it observes. Neither replaces the other.
Agree on the integration contract
Use a checklist with an evidence location and an unresolved decision for each item. “Provided” should mean an identifiable file or published specification, rather than an expectation that someone can produce it later.
| Area | Collect | Check before accepting |
|---|---|---|
| Release | Commit, licence, manifest and change summary | All collateral belongs to the selected release |
| Interface | Protocol issue, widths, parameters and limits | Both sides agree on legal transactions |
| Registers | Addresses, access semantics and reset values | RTL, firmware and verification agree |
| Clocks | Definitions, relationships and constraints | Every relevant endpoint has an intended timing relationship |
| Reset | Polarity, timing, sequencing and affected state | Release behavior matches the surrounding system |
| Verification | Tests, assertions, coverage goals and exclusions | Results identify the tested configuration and known gaps |
For a bus interface, write down the address unit as well as the address width. A byte address and a word index can have the same numerical width but disagree about every offset. For transaction IDs, record which side allocates them, how many distinct values are supported and how responses are associated with outstanding requests. For queue capacity, distinguish a documented limit from what a convenient default test happens to exercise.
Do not silently truncate an interface to make ports connect. If the producer can issue more outstanding work than the consumer can retain, matching signal names will not create backpressure or response tracking. The handshake and deadlock guide exercises these mismatches separately. Capture the chosen parameter values in the release record so those checks can be repeated for the integrated configuration.
Keep one register description and check every consumer
The original example uses a small JSON description with three byte offsets: CONTROL at zero, STATUS at four and LIMIT at eight. It also records read/write access and reset values. Python generates an RTL constants package, a C header and verification metadata, then reads those outputs back to compare them with the description. JSON is the exchange representation here; the standard library supplies its parser and serializer1.
This description is intentionally small. It is not an IP-XACT file, a SystemRDL implementation or a complete register generator. Its validation accepts only this example’s aligned 32-bit register layout and defined access categories. Rejecting other inputs keeps the check’s meaning visible instead of appearing to support semantics it never implements.
Save the following exact script as check_registers.py. Download the original description and the commands and expected sequence into the same working directory. The generated RTL constants, C header and verification metadata are also provided for inspection.
# SPDX-License-Identifier: MIT
import argparse
import json
import re
from pathlib import Path
def load_description(path):
spec = json.loads(path.read_text())
if spec['schema'] != 'example-registers-1':
raise ValueError('unsupported schema')
rows = spec['registers']
if spec['data_width'] != 32 or spec['address_width'] != 8:
raise ValueError('this example requires 32-bit data and 8-bit addresses')
names, offsets = set(), set()
for row in rows:
name, offset = row['name'], row['offset']
if not re.fullmatch(r'[A-Z][A-Z0-9_]*', name):
raise ValueError('invalid register name')
if name in names or offset in offsets or offset % 4:
raise ValueError('duplicate or unaligned register')
if not 0 <= offset < 256 or row['access'] not in ('ro', 'rw'):
raise ValueError('unsupported register definition')
if not 0 <= row['reset'] < 2 ** 32:
raise ValueError('reset does not fit data width')
names.add(name)
offsets.add(offset)
return rows
def render(rows):
rtl = ['// SPDX-License-Identifier: MIT', 'package register_offsets;']
header = ['/* SPDX-License-Identifier: MIT */', '#pragma once']
model = {}
for row in rows:
name, value = row['name'], row['offset']
rtl.append(f" localparam logic [7:0] {name} = 8'h{value:02x};")
header.append(f'#define {name}_OFFSET 0x{value:02x}u')
model[name] = {'offset': value, 'access': row['access'], 'reset': row['reset']}
rtl.append('endpackage')
return {
'offsets.sv': '\n'.join(rtl) + '\n',
'offsets.h': '\n'.join(header) + '\n',
'verification.json': json.dumps(model, indent=2, sort_keys=True) + '\n'
}
def check(root, rows):
expected = {r['name']: r['offset'] for r in rows}
patterns = {
'offsets.sv': r"localparam logic \[7:0\] ([A-Z0-9_]+) = 8'h([0-9a-f]+);",
'offsets.h': r'#define ([A-Z0-9_]+)_OFFSET 0x([0-9a-f]+)u'
}
errors = []
for name, pattern in patterns.items():
found = re.findall(pattern, (root / name).read_text())
actual = {key: int(value, 16) for key, value in found}
if len(found) != len(actual):
errors.append(f'{name}: duplicate definitions')
if actual != expected:
errors.append(f'{name}: offsets differ: expected {expected}; got {actual}')
actual_model = json.loads((root / 'verification.json').read_text())
expected_model = {r['name']: {k: r[k] for k in ('offset', 'access', 'reset')}
for r in rows}
if actual_model != expected_model:
errors.append('verification.json: register metadata differs')
for error in errors:
print('FAIL: ' + error)
if not errors:
print(f'PASS: {len(rows)} registers agree across RTL, C and verification metadata')
return 1 if errors else 0
def main():
parser = argparse.ArgumentParser()
parser.add_argument('action', choices=['generate', 'check', 'drift'])
parser.add_argument('description', type=Path)
parser.add_argument('directory', type=Path)
args = parser.parse_args()
rows = load_description(args.description)
if args.action == 'generate':
args.directory.mkdir(parents=True, exist_ok=True)
for name, text in render(rows).items():
(args.directory / name).write_text(text)
print('WROTE: RTL, C and verification register descriptions')
return 0
if args.action == 'drift':
path = args.directory / 'offsets.h'
text = path.read_text()
old = '#define STATUS_OFFSET 0x04u'
if old not in text:
raise ValueError('expected original STATUS offset before deliberate drift')
path.write_text(text.replace(old, '#define STATUS_OFFSET 0x0cu'))
print('CHANGED: only C STATUS offset from 4 to 12')
return 0
return check(args.directory, rows)
if __name__ == '__main__':
raise SystemExit(main())
Walk through the deliberate drift
First, generate writes the three consumer descriptions from the same input. check then reports that all three register offsets agree. It separately compares the verification metadata’s access and reset fields. The RTL and C outputs in this example contain offsets only, so a passing result does not claim that they implement the access modes or reset values.
Next, drift changes only the C header’s STATUS offset from four to twelve. The input description, RTL constants and verification metadata remain unchanged. The next check returns a failing exit code and prints both the expected and actual maps. That failure identifies the inconsistent consumer directly; it is more useful than reporting that an unspecified file hash changed.
FAIL: offsets.h: offsets differ: expected {'CONTROL': 0, 'STATUS': 4, 'LIMIT': 8}; got {'CONTROL': 0, 'STATUS': 12, 'LIMIT': 8}
Finally, generate restores all outputs from the unchanged description. The next check passes. Regeneration is appropriate in this controlled example because the description is the accepted source. In an actual handoff, investigate whether a changed register was intentional before overwriting it. An approved specification change may require updating the generator input, tests and software together, rather than restoring an older offset.
The checker also rejects duplicate names or offsets and an unaligned register in its input validation. Those branches are readable in the script; they were not separately exercised in the reported six-command sequence. Keep the observed drift result distinct from code inspection. Adding tests for additional definitions is a separate extension of this example, not a property of the recorded pass.
Treat register behavior as another test
Offset agreement cannot detect a read-clear register that fails to clear, a write-one-to-clear field implemented as ordinary storage or a reset that leaves state unchanged. Those are behavioral requirements. Connect each one to a test or property that drives the relevant bus operation and checks the visible result. Also decide what happens to reserved bits, partial writes and accesses that cross the implemented address range.
A verification model generated from the same mistaken description can agree perfectly with mistaken RTL. The useful independent input is a reviewed requirement, such as which status bit clears on which operation. Use the machine-readable description to prevent accidental divergence, then use that independent requirement to judge correctness. The coverage guide explains why executing a register access does not by itself check its intended meaning.
This example does not compile the generated C header or simulate the generated RTL package. It checks text and structured metadata in a deliberately constrained format. A release gate can add compilation, elaboration and bus-level tests, but it should name those additional steps and retain their actual outputs rather than relabelling this consistency check as an integration test.
Use IP-XACT for the metadata it standardizes
IEEE lists 1685-2022 as the active IP-XACT standard, published on 28 February 2023. Its scope includes component and system metadata, bus interfaces and connections, address maps, register descriptions and file sets, together with XML schemas and semantic consistency rules2. Record that edition when exchanging a description; “IP-XACT” alone leaves the schema and conversion assumptions unresolved.
Accellera’s current download page lists the 2022 user guide, recommended vendor extensions and a current schema archive dated 13 February 2025. It also explains that the normative standard, the schema and the user guide serve different purposes3. A schema validation result therefore needs its schema version, and an integration review needs more than the fact that an XML parser accepted the file.
The working group describes IP-XACT as integration metadata and distinguishes that scope from behavioral characteristics of the IP4. Do not treat a compliant description as evidence of correct bus responses, reset sequencing, timing closure or software compatibility. Those need their own checks. The original JSON example here demonstrates a consistency technique; no IP-XACT schema, conversion utility or conformance flow was executed.
Make the remaining gaps visible
Use the open-IP evidence comparison to inspect recorded collateral, and the IP resource finder to navigate the same source-backed records. A missing field there means it was not verified in that compilation. Reopen the exact repository and commit relevant to your release before treating a directory row as a delivery promise.
For each unresolved item, record the affected configuration, the evidence still needed and what work depends on it. A missing timing constraint and a failing bus checker are different problems with different next steps. Keeping that distinction lets the next engineer reproduce what is known without inheriting an unwritten assumption that every integration question was already settled.
Not covered on this page
- IP-XACT conformance or conversion execution.
- Bus-level register behavior and software-driver compilation.
- Timing closure, power intent or physical implementation.
- Approval of any particular third-party IP release.
Sources
-
Python JSON documentation, Python 3.12 documentation checked 24 October 2026; example executed with CPython 3.12.14. ↩
-
IEEE 1685-2022, active-standard listing and scope, published 28 February 2023; checked 24 October 2026. ↩
-
Accellera IP-XACT downloads, 2022 guide and extensions, current schema listing dated 13 February 2025; checked 24 October 2026. ↩
-
Accellera IP-XACT working-group scope, checked 24 October 2026. ↩
Code on this page is original and MIT licensed.
How we test: methodology. Found a mistake? Corrections.