Skip to content
Merged
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
10 changes: 10 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,16 @@ jobs:
- name: Pytest
run: uv run --no-sync pytest test/ --ignore=test/examples/test_soc.py --cov=chipcompiler --cov-report=

- name: Upload integration flow logs on failure
if: failure()
uses: actions/upload-artifact@v4
with:
name: integration-flow-logs
if-no-files-found: ignore
path: |
test/examples/*/home/flow.json
test/examples/**/log/**

- name: Publish coverage summary
if: always()
run: |
Expand Down
95 changes: 3 additions & 92 deletions chipcompiler/data/home.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,43 +9,12 @@

from .checklist import Checklist

# home_json = {
# "parameters" : "",
# "flow" : "",
# "layout" : "",
# "monitor" : {
# "step" : [],
# "memory" : [],
# "runtime" : [],
# "instance" : [],
# "frequency" : []
# },
# "metrics":{
# "instance dist." : "",
# "layer via dist." : "",
# "layer wire dist." : "",
# "pin dist." : "",
# "drc dist." : "",
# "CTS skew map" : ""
# },
# "checklist" : ""
# }
home_json = {
"parameters": "",
"flow": "",
"layout": "",
"checklist": "",
"metrics": {},
"monitor": {"step": [], "memory": [], "runtime": [], "instance": [], "frequency": []},
}

_monitor_keys = ("step", "memory", "runtime", "instance", "frequency")
_monitor_defaults = {
"step": "",
"memory": "",
"runtime": "",
"instance": 0,
"frequency": 0.0,
}


Expand All @@ -59,33 +28,20 @@ def _normalize_home_data(data: dict) -> tuple[dict, bool]:

if isinstance(data, dict):
for key, value in data.items():
if key == "monitor":
changed = True
continue
normalized[key] = value

if not isinstance(normalized.get("metrics"), dict):
normalized["metrics"] = {}
changed = True

if not isinstance(normalized.get("monitor"), dict):
normalized["monitor"] = _default_home_data()["monitor"]
changed = True

for key in home_json:
if key not in normalized:
normalized[key] = _default_home_data()[key]
changed = True

for key in _monitor_keys:
if not isinstance(normalized["monitor"].get(key), list):
normalized["monitor"][key] = []
changed = True

monitor_length = max(len(normalized["monitor"][key]) for key in _monitor_keys)
for key in _monitor_keys:
missing_count = monitor_length - len(normalized["monitor"][key])
if missing_count > 0:
normalized["monitor"][key].extend([_monitor_defaults[key]] * missing_count)
changed = True

if isinstance(data, dict) and normalized != data:
changed = True

Expand Down Expand Up @@ -204,51 +160,6 @@ def set_metrics_drc_dist(self, image_path: Path):
def set_metrics_cts_skew_map(self, image_path: Path):
self._set_metric("CTS skew map", image_path)

def update_monitor(
self,
step: str,
sub_step: str,
memory: str,
runtime: str,
instance: int = 0,
frequency: float = 0.0,
):
def mutator(data: dict) -> bool:
target_instance = instance
target_frequency = frequency

# if not set, use last value
if target_instance == 0:
instance_values = data["monitor"]["instance"]
target_instance = instance_values[-1] if len(instance_values) > 0 else 0
if target_frequency == 0.0:
frequency_values = data["monitor"]["frequency"]
target_frequency = frequency_values[-1] if len(frequency_values) > 0 else 0.0

step_name = f"{step} - {sub_step}"
for i, existing_step in enumerate(data["monitor"]["step"]):
if existing_step == step_name:
changed = (
data["monitor"]["memory"][i] != memory
or data["monitor"]["runtime"][i] != runtime
or data["monitor"]["instance"][i] != target_instance
or data["monitor"]["frequency"][i] != target_frequency
)
data["monitor"]["memory"][i] = memory
data["monitor"]["runtime"][i] = runtime
data["monitor"]["instance"][i] = target_instance
data["monitor"]["frequency"][i] = target_frequency
return changed

data["monitor"]["step"].append(step_name)
data["monitor"]["memory"].append(memory)
data["monitor"]["runtime"].append(runtime)
data["monitor"]["instance"].append(target_instance)
data["monitor"]["frequency"].append(target_frequency)
return True

self._update(mutator)

def set_checklist(self, checklist_path: Path):
path = checklist_path
if not path.exists():
Expand Down
9 changes: 8 additions & 1 deletion chipcompiler/data/workspace/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -396,6 +396,11 @@ def _flag_to_int(value: Any) -> int:
StepEnum.NETLIST_OPT.value,
("max_fanout",),
),
WorkspaceConfigParameterMapping(
"Max fanout",
StepEnum.CTS.value,
("max_fanout",),
),
WorkspaceConfigParameterMapping(
"Bottom layer",
"db",
Expand Down Expand Up @@ -733,8 +738,9 @@ def refresh_workspace_config(workspace: Workspace) -> None:
f"Netlist opt config missing or corrupt: "
f"{workspace.config[f'{StepEnum.NETLIST_OPT.value}']}"
)
max_fanout = workspace.parameters.data.get("Max fanout", 32)
fixfanout["insert_buffer"] = workspace.pdk.buffers[0] if len(workspace.pdk.buffers) > 0 else ""
fixfanout["max_fanout"] = workspace.parameters.data.get("Max fanout", 32)
fixfanout["max_fanout"] = max_fanout
json_write(workspace.config[f"{StepEnum.NETLIST_OPT.value}"], fixfanout)

filler_path = workspace.config[f"{StepEnum.FILLER.value}"]
Expand All @@ -754,6 +760,7 @@ def refresh_workspace_config(workspace: Workspace) -> None:
f"CTS config missing or corrupt: {workspace.config[f'{StepEnum.CTS.value}']}"
)
cts["buffer_type"] = workspace.pdk.buffers
cts["max_fanout"] = max_fanout
json_write(workspace.config[f"{StepEnum.CTS.value}"], cts)

router = json_read(workspace.config[f"{StepEnum.ROUTING.value}"])
Expand Down
67 changes: 50 additions & 17 deletions chipcompiler/engine/signoff.py
Original file line number Diff line number Diff line change
Expand Up @@ -199,10 +199,19 @@ def add_file(
)

db_config = self._read_json(config_dir / "db_ecc.json")
configured_filelist = getattr(self.workspace.design, "input_filelist", None)
flow_starts_at_floorplan = self._flow_starts_at_floorplan(flow_data)
configured_filelist = (
None
if flow_starts_at_floorplan
else getattr(self.workspace.design, "input_filelist", None)
)
origin_rtl = resolve_initial_rtl(
configured_filelist,
getattr(self.workspace.design, "origin_verilog", None),
(
None
if flow_starts_at_floorplan
else getattr(self.workspace.design, "origin_verilog", None)
),
workspace_dir / "origin",
)
if origin_rtl is not None:
Expand Down Expand Up @@ -273,8 +282,10 @@ def add_file(
# Like synthesis, a configured input_filelist is always a filelist;
# runtime-created ones are suffixless (origin/filelist), so the
# suffix check alone would miss them.
is_filelist = origin_rtl.suffix.lower() in FILELIST_SUFFIXES or (
configured_filelist is not None and origin_rtl == Path(configured_filelist)
is_filelist = (
origin_rtl.suffix.lower() in FILELIST_SUFFIXES
or origin_rtl.name == "filelist"
or (configured_filelist is not None and origin_rtl == Path(configured_filelist))
)
if is_filelist:
# Workspace creation copies filelist sources into origin/ keeping
Expand Down Expand Up @@ -354,13 +365,14 @@ def add_file(
required=True,
)

synthesis_verilog = self._synthesis_output_verilog()
add_file(
role="synthesis.verilog",
source=synthesis_verilog,
destination=f"synthesis/{design}.v.gz",
required=True,
)
if not flow_starts_at_floorplan:
synthesis_verilog = self._synthesis_output_verilog()
add_file(
role="synthesis.verilog",
source=synthesis_verilog,
destination=f"synthesis/{design}.v.gz",
required=True,
)

add_file(
role="final.design.verilog",
Expand Down Expand Up @@ -557,7 +569,6 @@ def add_file(
"sdc": f"initial/{design}.sdc",
"parameters": "initial/parameters.json",
},
"synthesis": {"verilog": f"synthesis/{design}.v.gz"},
"config": "config/",
"harden": {
"gds": f"harden/{design}.gds",
Expand All @@ -576,6 +587,8 @@ def add_file(
"missing_optional": missing_optional,
"warnings": warnings,
}
if not flow_starts_at_floorplan:
summary["synthesis"] = {"verilog": f"synthesis/{design}.v.gz"}
summary_path = package_dir / "summary.json"

manifest = {
Expand All @@ -602,13 +615,19 @@ def add_file(
manifest_path.write_text(json.dumps(manifest, indent=2))

readme_path = package_dir / "README.md"
input_verilog_description = "- Mapped synthesis netlist is under `synthesis/`.\n"
if flow_starts_at_floorplan:
input_verilog_description = (
"- Original imported RTL is under `initial/` because this flow "
"starts at Floorplan.\n"
)
readme_path.write_text(
f"# {design} Signoff Package\n\n"
f"- Workspace: {workspace_dir.resolve()}\n"
f"- Status: {summary['status']}\n"
"- Mapped synthesis netlist is under `synthesis/`.\n"
"- Harden outputs are under `harden/`.\n"
"- Final physical resources are under `final/`.\n"
+ f"- Workspace: {workspace_dir.resolve()}\n"
+ f"- Status: {summary['status']}\n"
+ input_verilog_description
+ "- Harden outputs are under `harden/`.\n"
+ "- Final physical resources are under `final/`.\n"
)

if options.archive and (ok or options.allow_incomplete):
Expand Down Expand Up @@ -874,6 +893,20 @@ def _required_step_states(self, flow_data: dict) -> dict:
}
return {step: state_by_step.get(step, "") for step in required}

def _flow_starts_at_floorplan(self, flow_data: dict) -> bool:
"""Whether this workspace intentionally omits synthesis before Floorplan."""
steps = flow_data.get("steps", [])
if not isinstance(steps, list):
return False
first_step = next(
(step for step in steps if isinstance(step, dict) and step.get("name")),
None,
)
return bool(
first_step
and str(first_step.get("name", "")).strip().lower() == StepEnum.FLOORPLAN.value.lower()
)

def _refresh_workspace_analysis(self, workspace_dir: Path) -> list[SignoffPackageIssue]:
"""Rebuild current V3 analysis and checklist snapshots for completed steps."""
flow_data = self.workspace.flow.data or self._read_json(
Expand Down
2 changes: 1 addition & 1 deletion chipcompiler/thirdparty/ecc-tools
Submodule ecc-tools updated 102 files
10 changes: 0 additions & 10 deletions chipcompiler/tools/ecc/configs/fixfanout_ecc.json
Original file line number Diff line number Diff line change
@@ -1,14 +1,4 @@
{
"file_path": {
"design_work_space": "",
"sdc_file": "",
"lib_files": [],
"lef_files": [],
"def_file": "",
"output_def": "",
"report_file": "",
"gds_file": ""
},
"insert_buffer": "",
"max_fanout": 30
}
4 changes: 4 additions & 0 deletions chipcompiler/tools/ecc/module.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@
from chipcompiler.tools.ecc.sta_artifacts import discard_sta_run_outputs, publish_sta_artifacts
from chipcompiler.utility.path import path_text, path_texts

# ecc-tools loggers terminate the host process on error by default; embedded in
# Python they must raise instead so failures surface as Python exceptions.
os.environ.setdefault("ECC_LOGGER_THROW_ON_ERROR", "1")

# Path arguments to the native-wrapper methods are normalized via path_text(),
# so they accept a Path, a str, or None (a step group field is Path | None).
PathArg: TypeAlias = str | Path | None
Expand Down
10 changes: 0 additions & 10 deletions chipcompiler/tools/ecc/subflow.py
Original file line number Diff line number Diff line change
Expand Up @@ -204,14 +204,4 @@ def update_step(self, step_name: str, state: str | StateEnum, info: dict | None

publish_subflow_stage(self.workspace, self.workspace_step, step_dict)

# update home page monitor
self.workspace.home.update_monitor(
step=self.workspace_step.name,
sub_step=step_name,
memory=str(peak_memory),
runtime=runtime,
instance=info.get("instance", 0),
frequency=info.get("frequency", 0),
)

break
6 changes: 0 additions & 6 deletions chipcompiler/tools/ecc_sizer/subflow.py
Original file line number Diff line number Diff line change
Expand Up @@ -103,10 +103,4 @@ def update_step(

publish_subflow_stage(self.workspace, self.workspace_step, step_dict)

self.workspace.home.update_monitor(
step=self.workspace_step.name,
sub_step=step_name,
memory=str(peak_memory),
runtime=runtime,
)
break
8 changes: 0 additions & 8 deletions chipcompiler/tools/yosys/subflow.py
Original file line number Diff line number Diff line change
Expand Up @@ -113,12 +113,4 @@ def update_step(self, step_name: str, state: str | StateEnum, info: dict | None

publish_subflow_stage(self.workspace, self.workspace_step, step_dict)

# update home page monitor
self.workspace.home.update_monitor(
step=self.workspace_step.name,
sub_step=step_name,
memory=str(peak_memory),
runtime=runtime,
)

break
Loading
Loading