Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ chipflow-lib is a Python library for working with the ChipFlow platform, enablin
- `chipflow.toml`: User project configuration file (must exist in `CHIPFLOW_ROOT`)
- `config_models.py`: Pydantic models defining configuration schema
- `config.py`: Configuration file parsing logic
- Key configuration sections: `[chipflow]`, `[chipflow.silicon]`, `[chipflow.simulation]`, `[chipflow.software]`, `[chipflow.test]`
- Key configuration sections: `[chipflow]`, `[chipflow.silicon]`, `[chipflow.silicon.macros]`, `[chipflow.simulation]`, `[chipflow.software]`, `[chipflow.test]`

3. **Platform Abstraction** (`platforms/`):
- `SiliconPlatform`: Targets ASIC fabrication (supports SKY130, GF180, GF130BCD, IHP_SG13G2, HELVELLYN2)
Expand Down Expand Up @@ -77,6 +77,11 @@ chipflow-lib is a Python library for working with the ChipFlow platform, enablin
- `IOModel` configures electrical characteristics (drive mode, trip point, inversion)
- Annotations attach metadata to Amaranth components for automatic pin allocation

7. **RTL Wrapping** (`chipflow/rtl/`):
- `load_wrapper_from_toml()` wraps external Verilog/SystemVerilog/SpinalHDL modules as Amaranth components from a TOML description
- `load_blackbox_wrapper()` wraps NDA / third-party hard macros declared in `[chipflow.silicon.macros]`, consuming a `*.blackbox.json` produced by the sibling [macrostrip](https://github.com/ChipFlow/macrostrip) tool
- At submit time, `SiliconPlatform._macros` is bundled into `macros.tar.gz` (manifest.json + per-macro LEF / Liberty / GDS / stub) and sent as a third multipart field alongside RTLIL and config

### Key Design Patterns

1. **Component Discovery via Configuration**:
Expand Down
13 changes: 13 additions & 0 deletions chipflow/config/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,13 +47,26 @@ class VoltageRange(SelectiveSerializationModel):
typical: Annotated[Optional[Voltage], OmitIfNone()] = None


class MacroDecl(BaseModel):
"""Declaration of an NDA / third-party hard macro packaged by
`macrostrip` (or a conformant tool).

Minimum: a path to a ``*.blackbox.json`` describing the macro. The
JSON itself carries the companion artifact paths (LEF, Liberty,
frame-view GDS, Verilog stub). Paths inside the JSON are interpreted
relative to the JSON's own directory.
"""
blackbox: Path


class SiliconConfig(BaseModel):
"""Configuration for silicon in chipflow.toml."""
process: 'Process'
package: str
power: Dict[str, Voltage] = {}
debug: Optional[Dict[str, bool]] = None
# This is still kept around to allow forcing pad locations.
macros: Dict[str, MacroDecl] = {}

class SimulationConfig(BaseModel):
"""Configuration for simulation settings."""
Expand Down
62 changes: 62 additions & 0 deletions chipflow/platform/silicon.py
Original file line number Diff line number Diff line change
Expand Up @@ -405,6 +405,7 @@ def __init__(self, config: 'Config'):
self._config = config
self._ports = {}
self._files = {}
self._macros: dict[str, dict] = {}
self._pinlock = None

@property
Expand Down Expand Up @@ -476,6 +477,67 @@ def add_file(self, filename, content):
assert isinstance(content, bytes)
self._files[str(filename)] = content

def add_macro(self, logical_name: str) -> dict:
"""Register an NDA / third-party hard macro with the platform.

Looks up ``logical_name`` in ``[chipflow.silicon.macros]``, loads
the referenced ``*.blackbox.json``, resolves its companion file
paths relative to the JSON directory, and stores the resulting
entry in ``self._macros`` for later bundling by the submit step.

Returns the stored entry. Idempotent when called repeatedly with
the same logical name (the same entry is returned).
"""
assert self._config.chipflow.silicon is not None
macros_cfg = self._config.chipflow.silicon.macros
if logical_name not in macros_cfg:
raise ChipFlowError(
f"Macro '{logical_name}' is not declared in "
f"[chipflow.silicon.macros]. Known macros: "
f"{sorted(macros_cfg.keys()) or '(none)'}"
)

if logical_name in self._macros:
return self._macros[logical_name]

from ..utils import ensure_chipflow_root
root = ensure_chipflow_root()
bb_path = Path(macros_cfg[logical_name].blackbox)
if not bb_path.is_absolute():
bb_path = (root / bb_path).resolve()
if not bb_path.exists():
raise ChipFlowError(
f"Macro '{logical_name}': blackbox JSON not found at {bb_path}"
)

import json as _json
bb = _json.loads(bb_path.read_text())
if bb.get("version") != "1":
raise ChipFlowError(
f"Macro '{logical_name}': unsupported blackbox JSON version "
f"{bb.get('version')!r} (expected '1')"
)

files: dict[str, Path] = {}
for key, rel in (bb.get("files") or {}).items():
resolved = (bb_path.parent / rel).resolve()
if not resolved.exists():
raise ChipFlowError(
f"Macro '{logical_name}': companion file '{key}' "
f"referenced by {bb_path} not found at {resolved}"
)
files[key] = resolved

entry = {
"logical_name": logical_name,
"name": bb["name"],
"blackbox_json": bb_path,
"blackbox": bb,
"files": files,
}
self._macros[logical_name] = entry
return entry

def _check_clock_domains(self, fragment, sync_domain=None):
for clock_domain in fragment.domains.values():
if clock_domain.name != "sync" or (sync_domain is not None and
Expand Down
65 changes: 49 additions & 16 deletions chipflow/platform/silicon_step.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,50 +33,82 @@


def _build_bundle_zip(
rtlil_path, config: str, project_name: str, process: str, package: str
rtlil_path,
config: str,
project_name: str,
process: str,
package: str,
macros: dict | None = None,
) -> bytes:
"""Pack the submission into a single zip with a manifest.

Layout::

manifest.json
<rtlil filename> # e.g. "top.il", taken from rtlil_path
pins.lock # the pinlock JSON
<rtlil filename> # e.g. "top.il"
pins.lock # the pinlock JSON
macros/<logical_name>/<filename> ... # only when macros are registered

``project_name`` / ``process`` / ``package`` come from chipflow.toml
(``[chipflow] project_name``, ``[chipflow.silicon] process``,
``[chipflow.silicon] package``). Consumers (logs, dashboards, the
backend's working directory naming and PDK / package selection) use
these to identify and route the design without re-parsing the pinlock.

The manifest is the only contract: consumers locate the design and
pinlock payloads via ``manifest["design_file"]`` and
``manifest["pins_lock_file"]``. Keys naming a file inside the
archive carry a ``_file`` suffix so they're distinguishable from
plain value keys (``version``, ``project``, ``process``,
``package``); the value is a zip-relative path. ``design_file`` is
named in terms of role rather than format so the same key can carry
rtlil today, or another intermediate (Verilog, FIRRTL) tomorrow,
without renaming. Future additions (e.g. macro folders) extend the
manifest without changing this function's signature on the wire.
``macros`` is the dict produced by ``SiliconPlatform._macros`` (logical
name → entry with ``name``, ``blackbox_json``, ``files``). Each macro's
companion files are written under ``macros/<logical_name>/`` and surface
in the manifest as ``manifest["macros"][<logical_name>]`` with
``_file``-suffixed paths so the backend's recursive
``_bundle_files_from_manifest`` extractor picks them up.

The manifest is the only contract: consumers locate every payload via
``_file``-suffixed keys at any nesting depth. ``design_file`` is named
in terms of role rather than format so the same key can carry rtlil
today, or another intermediate (Verilog, FIRRTL) tomorrow, without
renaming.
"""
design_arc = Path(rtlil_path).name
pins_lock_arc = "pins.lock"
manifest = {
manifest: dict = {
"version": "1",
"project": project_name,
"process": process,
"package": package,
"design_file": design_arc,
"pins_lock_file": pins_lock_arc,
}

file_entries: list[tuple[str, Path]] = [] # (archive_path, real_path)
if macros:
manifest_macros: dict = {}
for logical_name, entry in macros.items():
subdir = f"macros/{logical_name}"
macro_dict: dict = {"name": entry["name"]}
for role, src in entry["files"].items():
arc = f"{subdir}/{Path(src).name}"
# Suffix `_file` so the backend's recursive extractor finds it.
macro_dict[f"{role}_file"] = arc
file_entries.append((arc, Path(src)))

# Emit the blackbox JSON alongside its companions for completeness.
bb_arc = f"{subdir}/{Path(entry['blackbox_json']).name}"
macro_dict.setdefault("blackbox_json_file", bb_arc)
file_entries.append((bb_arc, Path(entry["blackbox_json"])))

manifest_macros[logical_name] = macro_dict

manifest["macros"] = manifest_macros

manifest_bytes = (json.dumps(manifest, indent=2) + "\n").encode("utf-8")

buf = io.BytesIO()
with zipfile.ZipFile(buf, mode="w", compression=zipfile.ZIP_DEFLATED) as zf:
zf.writestr("manifest.json", manifest_bytes)
zf.writestr(pins_lock_arc, config)
zf.write(str(rtlil_path), arcname=design_arc)
for arc, src in file_entries:
zf.write(str(src), arcname=arc)
return buf.getvalue()


Expand Down Expand Up @@ -235,15 +267,16 @@ def submit(self, rtlil_path, args):
rtlil_path, config,
self.config.chipflow.project_name,
self.config.chipflow.silicon.process.value,
self.config.chipflow.silicon.package)
self.config.chipflow.silicon.package,
self.platform._macros)

if args.dry_run:
sp.succeed(f"✅ Design `{data['projectId']}:{data['name']}` ready for submission to ChipFlow cloud!")
logger.debug(f"data=\n{json.dumps(data, indent=2)}")
logger.debug(f"files['config']=\n{config}")
bundle_path = Path(rtlil_path).parent / "bundle.zip"
bundle_path.write_bytes(bundle_bytes)
sp.info(f"Compiled submission written to `{bundle_path}` (manifest.json + rtlil + pins.lock)")
sp.info(f"Compiled submission written to `{bundle_path}` (manifest.json + rtlil + pins.lock + any macros)")
return

def network_err(e):
Expand Down
6 changes: 6 additions & 0 deletions chipflow/rtl/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@
sim.step()
"""

from chipflow.rtl.blackbox import (
BlackboxWrapper,
load_blackbox_wrapper,
)
from chipflow.rtl.wrapper import (
RTLWrapper,
VerilogWrapper, # Alias for backwards compatibility
Expand All @@ -35,7 +39,9 @@
__all__ = [
"RTLWrapper",
"VerilogWrapper",
"BlackboxWrapper",
"load_wrapper_from_toml",
"load_blackbox_wrapper",
"_generate_auto_map",
"_infer_auto_map",
"_parse_verilog_ports",
Expand Down
Loading
Loading