Record a reproducible IC design handoff
A reproducible IC design handoff records the exact inputs, tool versions, commands and expected observations needed to repeat a particular result. A file inventory with hashes can detect a changed or missing input; it cannot establish that the inventory contains everything required for implementation. Keep those two questions separate when moving a block between engineers or design stages.
Define the result before collecting files
Start with a sentence that another engineer can check. For this example, the result is that an original counter reaches three after three enabled clock edges following reset. That requires the RTL, a testbench, a top-level selection, a language mode and the simulator command. A photograph of a waveform would not contain those ingredients. A successful exit without a stated check would not explain what the test established.
An implementation handoff has a different boundary. It might also need timing constraints, library corners, technology files, memory models and scripts that resolve those references. Put the expected stage in the handoff description before choosing required input roles. Otherwise, a recipient can reasonably interpret a simulation-only bundle as a package ready for synthesis, or assume that a named constraint file was actually used.
The example deliberately includes a clock constraint as an inventoried artifact. The simulation command does not read it. That distinction is visible both in the configuration and in the recorded result. A handoff should explain an artifact’s role without implying that every tool in the flow consumed every file. An omitted consumption step is a gap to investigate, not a property supplied by the filename.
Use relative identities and explicit roles
The small example project contains counter.sv, tb.sv, clock.sdc and handoff.json. The configuration lists three input roles: RTL, testbench and constraints. It also supplies the top name, exact reported tool versions and command arguments. These are the selected inputs to the manifest, rather than every incidental file under the directory.
Relative paths make the inventory readable after the project moves. They also reveal whether two files with the same basename occupy different logical locations. The script rejects absolute input paths, parent traversal, duplicate entries and symlink inputs. These are deliberate scope restrictions for this small manifest format. A production flow that needs generated files or external libraries should represent those relationships explicitly instead of silently following a workstation-specific link.
The output manifest sits outside the input directory. Keeping it separate avoids inviting a later directory-wide inventory to include its own previous output. The script sorts entries by path and serializes keys consistently, so a stable configuration and identical input bytes produce the same manifest text. Python’s JSON module provides the sorted-key output option used here1.
The complete manifest script
Save this as manifest.py. Give it the example-project directory and an output filename. The --check mode compares the newly computed snapshot with the existing manifest without replacing it. The example configuration, RTL, testbench and constraint are original files for this example.
# SPDX-License-Identifier: MIT
import argparse
import hashlib
import json
from pathlib import Path
def snapshot(root, config):
entries = []
seen = set()
for item in config["inputs"]:
name, role = item["path"], item["role"]
relative = Path(name)
if relative.is_absolute() or ".." in relative.parts or name in seen:
raise ValueError("invalid or duplicate input: " + name)
seen.add(name)
path = root / relative
if any(part.is_symlink() for part in [path, *path.parents] if part != root):
raise ValueError("symlink input is not supported: " + name)
if not path.is_file():
raise ValueError("missing input: " + name)
data = path.read_bytes()
entries.append({
"path": name,
"role": role,
"bytes": len(data),
"sha256": hashlib.sha256(data).hexdigest()
})
roles = {item["role"] for item in entries}
if not {"rtl", "constraints", "testbench"}.issubset(roles):
raise ValueError("require rtl, constraints and testbench roles")
if not config.get("commands") or not config.get("tools"):
raise ValueError("record commands and exact tool versions")
return {
"schema": "ic-handoff-1",
"top": config["top"],
"tools": config["tools"],
"commands": config["commands"],
"inputs": sorted(entries, key=lambda item: item["path"]),
"limits": config.get("limits", [])
}
def main():
parser = argparse.ArgumentParser()
parser.add_argument("directory", type=Path)
parser.add_argument("manifest", type=Path)
parser.add_argument("--check", action="store_true")
args = parser.parse_args()
root = args.directory.resolve()
try:
config = json.loads((root / "handoff.json").read_text())
result = snapshot(root, config)
text = json.dumps(result, sort_keys=True, indent=2) + "\n"
if args.check:
if args.manifest.read_text() != text:
raise ValueError("manifest differs from current inputs or configuration")
print("PASS: manifest matches " + str(len(result["inputs"])) + " inputs")
else:
if args.manifest.resolve().is_relative_to(root):
raise ValueError("write the manifest outside the input directory")
args.manifest.write_text(text)
print("WROTE: " + str(len(result["inputs"])) + " inputs")
return 0
except (OSError, ValueError, KeyError, TypeError) as error:
print("FAIL: " + str(error))
return 1
if __name__ == "__main__":
raise SystemExit(main())
Run python manifest.py example-project manifest.json to create the record, then python manifest.py example-project manifest.json --check to check the current inputs against it. Run the configured compiler command from the project directory when reproducing the RTL observation. Keep the generated simulator executable outside a source inventory unless the handoff explicitly needs to preserve that executable as a separate artifact.
Read each check as a separate result
The snapshot function reads the bytes of each named file, records their length and computes a SHA-256 digest. Python’s hashlib module exposes SHA-256 for byte sequences2. In this example, the digest is an identity check: it lets the recipient see whether the supplied bytes match the recorded bytes. It does not prove who authored the file, who approved it or whether its design intent is correct.
The required-role check catches a configuration with no RTL, testbench or constraint role. It does not inspect the contents deeply enough to establish that a file is valid HDL or valid constraints. A file named clock.sdc could contain an inappropriate period and still be faithfully inventoried. That is why the manifest accompanies an execution record and a design review, rather than replacing either.
The tool and command fields are also explicit declarations. The manifest script does not execute arbitrary command strings from a received configuration. In the recorded example, a separate run invoked Icarus using the stated language generation and top module. Its command-line documentation describes those selections3. Record the invocation you actually used; a remembered default can change between projects even when the tool version stays the same.
The script treats missing configuration keys and unreadable files as failures. A useful transfer stops before claiming an inventory is complete when one of its named inputs cannot be read. The error message identifies the missing path so the recipient can resolve that specific dependency. It avoids substituting a similarly named file found elsewhere on the machine.
Unchanged inputs reproduce the inventory
The initial run printed WROTE: 3 inputs. A check against that manifest printed PASS: manifest matches 3 inputs. Creating the manifest again from the unchanged project produced byte-identical output. This establishes the behavior of the printed manifest generator for the selected inputs and configuration.
The separate original RTL simulation printed PASS count=3, followed by the simulator’s finish message. Its testbench applied reset, drove three enabled clock edges and checked the counter before finishing. The manifest record and the RTL observation answer different questions: one identifies the files and declared environment; the other checks a small piece of behavior under those inputs.
Keep the literal output and return code alongside a summary. A copied “pass” sentence is less useful when the recipient cannot tell whether it came from the simulator, a wrapper or a human note. The measurement record includes each command’s arguments, return code and output, together with script and input hashes. The output transcript is evidence about this example, not a general acceptance criterion for IC handoffs.
A changed input must invalidate the saved record
For the changed-RTL case, the test added a comment to counter.sv and ran check mode against the saved manifest. The command returned one and printed FAIL: manifest differs from current inputs or configuration. Even a comment changes the byte identity. That is intentional: the manifest does not attempt to decide which source changes are behaviorally harmless.
When a legitimate revision is accepted into a project, generate a new record rather than editing the old hash by hand. Associate the new record with the intended revision and preserve the earlier execution evidence. The recipient should be able to tell whether a changed source was rerun, merely repackaged, or included as an unexecuted input for a later stage.
The same comparison also covers the selected top, commands, tool declarations and limits because those values are serialized into the snapshot. Additional unused configuration keys are outside this small schema. If a new option can change the result, add it to the defined snapshot structure and exercise a case that demonstrates the resulting difference. A general “configuration included” statement is less precise than a list of the fields that are actually compared.
A missing dependency must stay visible
For the missing-constraint case, the test temporarily removed clock.sdc. The script returned one and printed FAIL: missing input: clock.sdc. Restoring the file and the original RTL produced PASS: manifest matches 3 inputs. The positive, negative and restored cases show that this check responds to the intended changes rather than always emitting the same status.
This test does not make the constraint operational. It proves only that an explicitly declared input cannot disappear unnoticed from this inventory. At the next stage, the engineer running synthesis or timing analysis must record whether the tool accepted and applied that constraint. A successful file hash cannot resolve a clock name that the next tool does not recognize.
For a larger handoff, keep one row per external model or library selection, with the provider’s identifier, release or commit, licence reference and local file identity. Record generated inputs separately from their generators and source inputs. That makes it possible to distinguish a missing deliverable from an unavailable environment, and a changed generated output from a changed recipe.
Record run-specific choices beside the command when they affect behavior. A seed, a selected test, an include directory or an elaboration parameter can change what executes without changing the top-level source file. For a variable supplied through the environment, record the relevant value or a documented non-secret selection name. Do not copy an entire workstation environment into a shared package: unrelated credentials and personal paths are not reproduction inputs. The objective is a deliberate, inspectable set of dependencies rather than an indiscriminate machine snapshot.
Attach evidence to the next engineering decision
The recipient’s first decision is whether the documented result is reproducible enough to use as a starting point. Their next decision is whether the package contains the additional artifacts needed for the planned stage. Use the EDA handoff table to compare documented formats, and the open IP evidence table to find which integration records were actually located in each pinned repository.
A helpful handoff ends with an explicit unresolved list: an unavailable model, an unverified constraint, a simulator-only test or an environment that another engineer has not yet recreated. Those statements are actionable because they name the missing work. Avoid replacing them with a single readiness score that mixes file presence, test results and implementation sign-off.
Not covered on this page
- Timing closure, synthesis equivalence or physical sign-off.
- Executing commercial tools or redistributing third-party libraries.
- Authenticating the author or establishing rights from a checksum.
- Replacing project-specific review of constraints and design intent.
Sources
Code on this page is original and MIT licensed.
How we test: methodology. Found a mistake? Corrections.