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
65 changes: 65 additions & 0 deletions .github/scripts/ci_export_signoff_csv.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
#!/usr/bin/env python3
"""CI harness: CSV + FileCheck projection from a workspace built by packaged ecc.

Not a second flow engine — only tabularizes QoR/checklist/flow already on disk.
"""

import argparse
import sys
from pathlib import Path

REPO_ROOT = Path(__file__).resolve().parents[2]


def main(argv: list[str] | None = None) -> int:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--workspace", type=Path, required=True)
parser.add_argument("--out-dir", type=Path, default=Path("ci-artifacts/eda-signoff"))
parser.add_argument(
"--spec",
type=Path,
default=REPO_ROOT / "test" / "support" / "csv_profiles" / "signoff.yml",
)
args = parser.parse_args(argv)

for path in (REPO_ROOT, REPO_ROOT / "test"):
if str(path) not in sys.path:
sys.path.insert(0, str(path))

from support.csv_export import build_csv_bundle, write_csv_bundle
from support.csv_projection import write_projection
from support.csv_spec import load_csv_spec

from chipcompiler.data import load_workspace

workspace_dir = args.workspace.resolve()
if not workspace_dir.is_dir():
print(f"workspace missing: {workspace_dir}", file=sys.stderr)
return 1

out_dir = args.out_dir.resolve()
csv_dir = out_dir / "csv"
reports = out_dir / "reports"
for path in (csv_dir, reports):
path.mkdir(parents=True, exist_ok=True)

workspace = load_workspace(str(workspace_dir))
if workspace is None:
print(f"invalid workspace: {workspace_dir}", file=sys.stderr)
return 1

spec = load_csv_spec(str(args.spec.resolve()))
bundle = build_csv_bundle(workspace, spec=spec)
files = write_csv_bundle(bundle, str(csv_dir))
if not files:
print("csv export wrote no tables", file=sys.stderr)
return 1

check_path = write_projection(csv_dir, bundle.design, reports / "metrics.check.txt")
print(f"csv → {csv_dir} ({len(files)} files)")
print(f"projection → {check_path}")
return 0


if __name__ == "__main__":
raise SystemExit(main())
122 changes: 122 additions & 0 deletions .github/scripts/ci_run_ics55_gcd.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
#!/usr/bin/env python3
"""CI: run ics55 gcd rtl2gds through a packaged ``ecc`` binary.

Requires ``ECC_BIN`` (absolute path to the PyInstaller ``ecc`` executable).
Creates ``--project-dir``, copies fixture RTL, sets the PDK root, then
``ecc run --workspace default``.
"""

from __future__ import annotations

import argparse
import os
import shutil
import subprocess
import sys
from pathlib import Path

REPO_ROOT = Path(__file__).resolve().parents[2]


def _run(ecc: Path, args: list[str], *, cwd: Path | None = None) -> None:
cmd = [str(ecc), *args]
print("+", " ".join(cmd), flush=True)
completed = subprocess.run(cmd, cwd=cwd, check=False)
if completed.returncode != 0:
raise SystemExit(completed.returncode)


def main(argv: list[str] | None = None) -> int:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument(
"--ecc",
type=Path,
default=Path(os.environ["ECC_BIN"]) if os.environ.get("ECC_BIN") else None,
help="Packaged ecc binary (or set ECC_BIN)",
)
parser.add_argument(
"--project-dir",
type=Path,
default=Path("ci-artifacts/gcd"),
help="Project directory created by ecc init",
)
parser.add_argument(
"--pdk-root",
type=Path,
default=None,
help="icsprout55-pdk root (default: ../pdk/icsprout55-pdk relative to repo)",
)
parser.add_argument(
"--workspace-name",
default="default",
help="Managed workspace name passed to ecc run",
)
args = parser.parse_args(argv)

if args.ecc is None:
print("missing --ecc / ECC_BIN (packaged ecc binary required)", file=sys.stderr)
return 2
ecc = args.ecc.resolve()
if not ecc.is_file() or not os.access(ecc, os.X_OK):
print(f"ecc binary not executable: {ecc}", file=sys.stderr)
return 2

verilog = REPO_ROOT / "test" / "fixtures" / "gcd" / "gcd.v"
if not verilog.is_file():
print(f"missing fixture RTL: {verilog}", file=sys.stderr)
return 1

pdk_root = (
args.pdk_root.resolve()
if args.pdk_root is not None
else (REPO_ROOT.parent / "pdk" / "icsprout55-pdk").resolve()
)
if not pdk_root.is_dir():
# CI clones to ../pdk from the repo working directory.
alt = (Path.cwd().parent / "pdk" / "icsprout55-pdk").resolve()
pdk_root = alt if alt.is_dir() else pdk_root
if not pdk_root.is_dir():
print(f"PDK root missing: {pdk_root}", file=sys.stderr)
return 1

project_dir = args.project_dir.resolve()
if project_dir.exists():
shutil.rmtree(project_dir)
project_dir.parent.mkdir(parents=True, exist_ok=True)

# ecc init creates <name>/ relative to cwd; pass path as the project name.
_run(ecc, ["init", str(project_dir), "--plain"])

rtl_dest = project_dir / "rtl" / "gcd.v"
rtl_dest.parent.mkdir(parents=True, exist_ok=True)
shutil.copy2(verilog, rtl_dest)

_run(ecc, ["pdk", "set-root", str(pdk_root), "--project", str(project_dir), "--plain"])
_run(
ecc,
[
"run",
"--project",
str(project_dir),
"--workspace",
args.workspace_name,
"--plain",
],
)

workspace = project_dir / args.workspace_name
marker = project_dir.parent / "eda-gcd.ok"
if not (workspace / "home" / "flow.json").is_file():
if marker.exists():
marker.unlink()
print(f"EDA rtl2gds failed → missing flow.json under {workspace}", file=sys.stderr)
return 1

marker.write_text(str(workspace) + "\n", encoding="utf-8")
print(f"EDA rtl2gds ok → {workspace}")
print(f"ECC_WORKSPACE={workspace}")
return 0


if __name__ == "__main__":
raise SystemExit(main())
Loading