Skip to content

Manifest (ip.toml) — manifest.py + scaffold.py

The IP core manifest: identity, dependencies, source filesets, and build targets. The analogue of Cargo.toml / package.json / a FuseSoC .core file. Pure module (parsing uses the stdlib tomllib, no third-party TOML dependency).

The ip.toml format

Every core carries an ip.toml at its root:

schema      = 1                 # optional ip.toml format version (default 1)

[package]
vendor      = "acme"            # required ─┐
library     = "comm"            # required  ├ VLNV identity
name        = "uart"            # required  │
version     = "1.2.0"           # required ─┘ (per scheme: SemVer / calver / monotonic / opaque token)
scheme      = "semver"          # optional version scheme: "semver" (default) | "calver" | "monotonic" | "opaque"
description = "AXI-Lite UART"
license     = "Apache-2.0"      # SPDX id
authors     = ["Jane Doe <jane@acme.com>"]
top         = "uart_top"        # default top-level unit
keywords    = ["uart", "axi"]

[dependencies]
# "vendor:library:name" = "<constraint>"
"acme:common:fifo" = "^1.0.0"

[resolution]                    # optional; how to handle an incompatible conflict
on-conflict = "fail_on_conflict"  # "fail_on_conflict" (default) | "use_latest" | "isolate_namespaces"

[filesets.rtl]
files = ["rtl/uart_top.sv", "rtl/uart_rx.sv"]
type  = "systemVerilogSource"   # IP-XACT fileType vocabulary

[filesets.tb]
files  = ["tb/uart_tb.sv"]
type   = "systemVerilogSource"
depend = ["rtl"]                # other filesets this one needs

[targets.sim]
toolflow = "verilator"          # which backend (see backends.md)
filesets = ["rtl", "tb"]        # must reference defined filesets
top      = "uart_tb"            # overrides package.top for this target

Only [package] (with the four identity keys) is required; everything else is optional. Unknown fields are ignored. The keys map onto the dataclasses below.

Data model

Manifest is a frozen dataclass with these fields:

Field Type Notes
vlnv Vlnv parsed from the four identity keys
description, license str metadata
authors, keywords tuple[str, ...]
top str \| None default top unit
dependencies tuple[Dependency, ...]
filesets dict[str, Fileset] keyed by fileset name
targets dict[str, Target] keyed by target name
schema_version int the ip.toml format version (MANIFEST_SCHEMA_VERSION, default 1)
version_scheme "semver" \| "opaque" how this core's versions are interpreted ([package].scheme, default semver)
conflict_policy ConflictPolicy how the resolver handles an incompatible conflict ([resolution] on-conflict, default fail_on_conflict)
ref (property) PackageRef version-less key

The optional top-level schema key declares the ip.toml format version (default 1). A manifest written for a newer schema than this hdlpkg understands is rejected with a clear ManifestError rather than mis-parsed — the migration path the format needs once it freezes at 1.0. (Note the two distinct keys: top-level schema is the format version; [package].scheme is the version scheme.)

The [package].scheme key selects the version scheme: semver (default; valid SemVer), calver (ordered numeric 2024.1, year-as-major), monotonic (an ordered revision r3, one shared group), or opaque (an uninterpreted token, pinned exactly). The [resolution] on-conflict key sets the conflict policy used when an incompatible conflict arises. Both are validated and an unsupported value raises ManifestError.

Supporting value types:

  • Dependencyref: PackageRef + constraint: VersionConstraint. str(dep) renders "vendor:library:name = <constraint>".
  • Filesetname, files: tuple[str, ...], type: str (default "systemVerilogSource"), depend: tuple[str, ...] (other filesets it pulls in — honored by tool-flow generation, see backends). A files entry may be a literal path, a glob (rtl/**/*.vhd, ** recurses), or a directory (packs every file under it); expansion happens at pack/gen time against the core directory, with matches sorted for deterministic output (the manifest keeps the patterns as written).
  • Targetname, toolflow: str, filesets: tuple[str, ...], top: str | None.

Loading & validation

Constructor Description
Manifest.from_str(text) -> Manifest Parse from a TOML string.
Manifest.from_path(path) -> Manifest Parse from an ip.toml file.
Manifest.from_dict(data) -> Manifest Validate an already-parsed mapping.

Validation is strict and the error message always names the offending field:

  • [package] must exist and carry vendor/library/name/version; the version must be valid SemVer (or, under scheme = "opaque", a valid opaque token) and the segments valid VLNV parts.
  • Each dependency key must parse as a PackageRef and its value as a VersionConstraint.
  • Each fileset must have a files list of strings (each a literal path, a glob, or a directory; expanded at pack/gen time — an entry matching no file is then an error).
  • Each target must have a toolflow, and every fileset it references must be defined — a dangling reference is rejected with the list of known filesets.

All failures raise ManifestError (see exceptions).

Scaffolding a starter manifest (scaffold.py, behind hdlpkg init)

scaffold.py renders a fresh, valid ip.toml from a few fields. It is pure (no I/O); the CLI layer owns prompting and writing.

  • ScaffoldOptions — a frozen value type with vendor/library/name (validated as VLNV segments), version: Version, optional description/license/ top (defaults to the core name). Build from strings with ScaffoldOptions.create(...) (parses the version; default 0.1.0). Properties: effective_top, vlnv.
  • render_manifest(options) -> str — emits a complete manifest with one rtl fileset and one sim (Verilator) target. The output round-trips through Manifest, so a freshly scaffolded core passes hdlpkg validate immediately.

Example

from hdlpkg import Manifest
from hdlpkg.scaffold import ScaffoldOptions, render_manifest

text = render_manifest(ScaffoldOptions.create("acme", "comm", "uart"))
m = Manifest.from_str(text)
assert str(m.vlnv) == "acme:comm:uart:0.1.0"
assert "rtl" in m.filesets and "sim" in m.targets