diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f2b62a2a..36f6518b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -86,7 +86,7 @@ jobs: { echo '## Coverage Report' echo '```' - uv run --no-sync coverage report 2>&1 || echo 'Coverage report is unavailable.' + uv run --no-sync coverage report echo '```' } >> "$GITHUB_STEP_SUMMARY" diff --git a/chipcompiler/data/home.py b/chipcompiler/data/home.py index 9e606760..1a370fec 100644 --- a/chipcompiler/data/home.py +++ b/chipcompiler/data/home.py @@ -9,12 +9,43 @@ 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, } @@ -28,20 +59,33 @@ 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 @@ -160,6 +204,51 @@ 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(): diff --git a/chipcompiler/data/workspace/__init__.py b/chipcompiler/data/workspace/__init__.py index 5b534366..95482d3c 100644 --- a/chipcompiler/data/workspace/__init__.py +++ b/chipcompiler/data/workspace/__init__.py @@ -396,11 +396,6 @@ 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", @@ -738,9 +733,8 @@ 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"] = max_fanout + fixfanout["max_fanout"] = workspace.parameters.data.get("Max fanout", 32) json_write(workspace.config[f"{StepEnum.NETLIST_OPT.value}"], fixfanout) filler_path = workspace.config[f"{StepEnum.FILLER.value}"] @@ -760,7 +754,6 @@ 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}"]) diff --git a/chipcompiler/engine/signoff.py b/chipcompiler/engine/signoff.py index 678bf65a..cad96c3f 100644 --- a/chipcompiler/engine/signoff.py +++ b/chipcompiler/engine/signoff.py @@ -199,19 +199,10 @@ def add_file( ) db_config = self._read_json(config_dir / "db_ecc.json") - 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) - ) + configured_filelist = getattr(self.workspace.design, "input_filelist", None) origin_rtl = resolve_initial_rtl( configured_filelist, - ( - None - if flow_starts_at_floorplan - else getattr(self.workspace.design, "origin_verilog", None) - ), + getattr(self.workspace.design, "origin_verilog", None), workspace_dir / "origin", ) if origin_rtl is not None: @@ -282,10 +273,8 @@ 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 origin_rtl.name == "filelist" - or (configured_filelist is not None and origin_rtl == Path(configured_filelist)) + is_filelist = origin_rtl.suffix.lower() in FILELIST_SUFFIXES or ( + configured_filelist is not None and origin_rtl == Path(configured_filelist) ) if is_filelist: # Workspace creation copies filelist sources into origin/ keeping @@ -365,14 +354,13 @@ def add_file( 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, - ) + 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", @@ -569,6 +557,7 @@ 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", @@ -587,8 +576,6 @@ 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 = { @@ -615,19 +602,13 @@ 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" - + input_verilog_description - + "- Harden outputs are under `harden/`.\n" - + "- Final physical resources are under `final/`.\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" ) if options.archive and (ok or options.allow_incomplete): @@ -893,20 +874,6 @@ 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( diff --git a/chipcompiler/thirdparty/ecc-tools b/chipcompiler/thirdparty/ecc-tools index b412a127..a7344be1 160000 --- a/chipcompiler/thirdparty/ecc-tools +++ b/chipcompiler/thirdparty/ecc-tools @@ -1 +1 @@ -Subproject commit b412a1277be9f35614fe5131fdefc65d9d049116 +Subproject commit a7344be1c39000b2ce701e12e483dd2428362058 diff --git a/chipcompiler/tools/ecc/configs/fixfanout_ecc.json b/chipcompiler/tools/ecc/configs/fixfanout_ecc.json index 37018d4f..15c27523 100644 --- a/chipcompiler/tools/ecc/configs/fixfanout_ecc.json +++ b/chipcompiler/tools/ecc/configs/fixfanout_ecc.json @@ -1,4 +1,14 @@ { + "file_path": { + "design_work_space": "", + "sdc_file": "", + "lib_files": [], + "lef_files": [], + "def_file": "", + "output_def": "", + "report_file": "", + "gds_file": "" + }, "insert_buffer": "", "max_fanout": 30 } \ No newline at end of file diff --git a/chipcompiler/tools/ecc/subflow.py b/chipcompiler/tools/ecc/subflow.py index d528539f..ba9509a4 100644 --- a/chipcompiler/tools/ecc/subflow.py +++ b/chipcompiler/tools/ecc/subflow.py @@ -204,4 +204,14 @@ 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 diff --git a/chipcompiler/tools/ecc_sizer/subflow.py b/chipcompiler/tools/ecc_sizer/subflow.py index dfee297b..efbc8a55 100644 --- a/chipcompiler/tools/ecc_sizer/subflow.py +++ b/chipcompiler/tools/ecc_sizer/subflow.py @@ -103,4 +103,10 @@ 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 diff --git a/chipcompiler/tools/yosys/subflow.py b/chipcompiler/tools/yosys/subflow.py index eca2cf7b..a62cce8b 100644 --- a/chipcompiler/tools/yosys/subflow.py +++ b/chipcompiler/tools/yosys/subflow.py @@ -113,4 +113,12 @@ 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 diff --git a/test/conftest.py b/test/conftest.py index 30f71a26..0755ac90 100644 --- a/test/conftest.py +++ b/test/conftest.py @@ -22,7 +22,7 @@ def _load_complete_ics55_pdk_available(): PDK_REQUIRED_TESTS = { f"{FILELIST_INTEGRATION_PREFIX}::test_workspace_with_filelist": "", f"{FILELIST_INTEGRATION_PREFIX}::test_workspace_with_nested_filelist": "", - "test/integration/test_harden_flow.py::test_ics55_gcd": "", + "test/integration/test_harden_flow.py::test_ics55_gcd": "../icsprout55-pdk", "test/integration/test_rcx_flow.py::test_ics55_gcd": "", "test/integration/test_rtl2gds_flow.py::test_ics55_gcd": "", } diff --git a/test/data/test_home_data.py b/test/data/test_home_data.py index c4031547..7689571f 100644 --- a/test/data/test_home_data.py +++ b/test/data/test_home_data.py @@ -15,12 +15,15 @@ def test_init_writes_complete_schema_for_missing_file(tmp_path): home = HomeData() home.init(path) - assert _read_json(path) == { - "parameters": "", - "flow": "", - "layout": "", - "checklist": "", - "metrics": {}, + data = _read_json(path) + assert data["layout"] == "" + assert data["metrics"] == {} + assert data["monitor"] == { + "step": [], + "memory": [], + "runtime": [], + "instance": [], + "frequency": [], } @@ -39,18 +42,104 @@ def test_init_repairs_partial_home_json_preserving_existing_values(tmp_path): home = HomeData() home.init(path) - assert _read_json(path) == { - "parameters": "/ws/home/parameters.json", - "flow": "/ws/home/flow.json", - "layout": "", - "checklist": "/ws/home/checklist.json", - "metrics": {}, + data = _read_json(path) + assert data["flow"] == "/ws/home/flow.json" + assert data["checklist"] == "/ws/home/checklist.json" + assert data["parameters"] == "/ws/home/parameters.json" + assert data["layout"] == "" + assert data["metrics"] == {} + assert data["monitor"] == { + "step": [], + "memory": [], + "runtime": [], + "instance": [], + "frequency": [], } +def test_update_monitor_repairs_partial_home_json(tmp_path): + path = tmp_path / "home.json" + path.write_text(json.dumps({"metrics": {}})) + + home = HomeData() + home.init(path) + home.update_monitor( + step="Floorplan", + sub_step="place", + memory="12M", + runtime="3s", + instance=42, + frequency=100.0, + ) + + data = _read_json(path) + assert data["monitor"]["step"] == ["Floorplan - place"] + assert data["monitor"]["memory"] == ["12M"] + assert data["monitor"]["runtime"] == ["3s"] + assert data["monitor"]["instance"] == [42] + assert data["monitor"]["frequency"] == [100.0] + + +def test_update_monitor_repairs_short_monitor_columns_preserving_history(tmp_path): + path = tmp_path / "home.json" + path.write_text( + json.dumps( + { + "monitor": { + "step": ["Floorplan - place"], + "memory": [], + "runtime": [], + "instance": [], + "frequency": [], + } + } + ) + ) + + home = HomeData() + home.init(path) + data = _read_json(path) + assert data["monitor"]["step"] == ["Floorplan - place"] + assert data["monitor"]["memory"] == [""] + assert data["monitor"]["runtime"] == [""] + assert data["monitor"]["instance"] == [0] + assert data["monitor"]["frequency"] == [0.0] + + home.update_monitor( + step="Floorplan", + sub_step="place", + memory="12M", + runtime="3s", + instance=42, + frequency=100.0, + ) + + data = _read_json(path) + assert data["monitor"]["step"] == ["Floorplan - place"] + assert data["monitor"]["memory"] == ["12M"] + assert data["monitor"]["runtime"] == ["3s"] + assert data["monitor"]["instance"] == [42] + assert data["monitor"]["frequency"] == [100.0] + + +def test_instances_do_not_share_nested_monitor_lists(tmp_path): + first_path = tmp_path / "first.json" + second_path = tmp_path / "second.json" + + first = HomeData() + first.init(first_path) + second = HomeData() + second.init(second_path) + + first.update_monitor("Synthesis", "yosys", "10M", "1s") + + assert _read_json(first_path)["monitor"]["step"] == ["Synthesis - yosys"] + assert _read_json(second_path)["monitor"]["step"] == [] + + def test_set_metrics_repairs_missing_metrics(tmp_path): path = tmp_path / "home.json" - path.write_text(json.dumps({})) + path.write_text(json.dumps({"monitor": {"step": []}})) home = HomeData() home.init(path) @@ -58,6 +147,7 @@ def test_set_metrics_repairs_missing_metrics(tmp_path): data = _read_json(path) assert data["metrics"]["pin dist."] == "/tmp/pin.png" + assert data["monitor"]["step"] == [] def test_setters_do_not_rewrite_healthy_current_values(tmp_path): @@ -97,16 +187,25 @@ def _set_parameters(path, value): home.set_parameters(value) -def test_concurrent_home_updates_preserve_schema(tmp_path): +def _update_monitor(path): + home = HomeData() + home.init(path) + home.update_monitor("Floorplan", "place", "12M", "3s", instance=42, frequency=100.0) + + +def test_concurrent_home_updates_preserve_schema_and_monitor_rows(tmp_path): path = tmp_path / "home.json" home = HomeData() home.init(path) home.set_layout(Path("/ws/Floorplan_ecc/output/layout.png")) home.set_metrics_pin_dist(Path("/ws/Floorplan_ecc/output/pin.png")) + home.update_monitor("Synthesis", "yosys", "10M", "1s", instance=10, frequency=50.0) + processes = [ Process(target=_set_flow, args=(path, Path("/ws/home/flow.json"))), Process(target=_set_checklist, args=(path, Path("/ws/home/checklist.json"))), Process(target=_set_parameters, args=(path, Path("/ws/home/parameters.json"))), + Process(target=_update_monitor, args=(path,)), ] for process in processes: @@ -114,10 +213,12 @@ def test_concurrent_home_updates_preserve_schema(tmp_path): for process in processes: process.join(timeout=10) - assert [process.exitcode for process in processes] == [0, 0, 0] + assert [process.exitcode for process in processes] == [0, 0, 0, 0] data = _read_json(path) assert data["layout"] == "/ws/Floorplan_ecc/output/layout.png" assert data["metrics"]["pin dist."] == "/ws/Floorplan_ecc/output/pin.png" + assert data["monitor"]["step"] == ["Synthesis - yosys", "Floorplan - place"] + assert data["monitor"]["memory"] == ["10M", "12M"] assert data["flow"] == "/ws/home/flow.json" assert data["checklist"] == "/ws/home/checklist.json" assert data["parameters"] == "/ws/home/parameters.json" diff --git a/test/data/test_workspace.py b/test/data/test_workspace.py index 0a29c615..6d13f865 100644 --- a/test/data/test_workspace.py +++ b/test/data/test_workspace.py @@ -77,7 +77,7 @@ def test_create_workspace_returns_path_fields_and_persists_string_paths( origin_def="", origin_verilog=rtl_path, pdk="ics55", - parameters={**default_ics55_parameters, "Max fanout": 37}, + parameters=default_ics55_parameters, pdk_root=pdk_root, ) @@ -101,10 +101,6 @@ def test_create_workspace_returns_path_fields_and_persists_string_paths( assert flow_config["ConfigPath"]["idb_path"] == str(workspace.config["db"]) assert isinstance(flow_config["ConfigPath"]["idb_path"], str) - cts = json_read(workspace.config[StepEnum.CTS.value]) - assert cts["max_fanout"] == 37 - assert cts["buffer_type"] == workspace.pdk.buffers - def test_create_workspace_rejects_existing_non_empty_directory(tmp_path): workspace_dir = tmp_path / "workspace" @@ -728,10 +724,6 @@ def test_refresh_workspace_config_updates_all_parameter_derived_fields( params["Routability opt flag"] = 0 json_write(parameter_path, params) - cts = json_read(workspace.config[StepEnum.CTS.value]) - cts["skew_bound"] = "0.13" - json_write(workspace.config[StepEnum.CTS.value], cts) - refresh_workspace_config(workspace) fixfanout = json_read(workspace.config["fixFanout"]) @@ -739,13 +731,9 @@ def test_refresh_workspace_config_updates_all_parameter_derived_fields( db = json_read(workspace.config["db"]) floorplan = json_read(workspace.config[StepEnum.FLOORPLAN.value]) routing = json_read(workspace.config["route"]) - cts = json_read(workspace.config[StepEnum.CTS.value]) dreamplace = json_read(workspace.config["dreamplace"]) assert fixfanout["max_fanout"] == 91 - assert cts["max_fanout"] == 91 - assert cts["buffer_type"] == workspace.pdk.buffers - assert cts["skew_bound"] == "0.13" assert filler == {"-min_filler_width": 1} assert db["LayerSettings"]["routing_layer_1st"] == "MET3" assert routing["RT"]["-bottom_routing_layer"] == "MET3" @@ -849,31 +837,6 @@ def test_sync_workspace_config_to_parameters_updates_routing_layers_and_refreshe assert db["LayerSettings"]["routing_layer_1st"] == "MET4" -def test_sync_workspace_config_to_parameters_propagates_cts_max_fanout( - tmp_path, minimal_ics55_pdk_factory, default_ics55_parameters -): - workspace_dir, workspace = _create_loaded_ics55_workspace( - tmp_path, - "workspace_cts_max_fanout", - minimal_ics55_pdk_factory, - default_ics55_parameters, - ) - cts_path = workspace.config[StepEnum.CTS.value] - cts = json_read(cts_path) - cts["max_fanout"] = 48 - json_write(cts_path, cts) - - assert sync_workspace_config_to_parameters(workspace, cts_path) is True - refresh_workspace_config(workspace) - - parameters = json_read(workspace_dir / "home" / "parameters.json") - fixfanout = json_read(workspace.config[StepEnum.NETLIST_OPT.value]) - cts = json_read(cts_path) - assert parameters["Max fanout"] == 48 - assert fixfanout["max_fanout"] == 48 - assert cts["max_fanout"] == 48 - - def test_sync_workspace_config_to_parameters_preserves_routability_flag_string_coercion( tmp_path, minimal_ics55_pdk_factory, default_ics55_parameters ): @@ -966,6 +929,13 @@ def test_prepare_workspace_for_rerun_deletes_old_artifacts_and_resets_home_state home = json_read(home_path) home["layout"] = str(step_dir / "output" / "gcd_floorplan.png") home["metrics"] = {"instances dist.": str(step_dir / "feature" / "floorplan.db.inst_dist.png")} + home["monitor"] = { + "step": ["Floorplan - init"], + "memory": ["1"], + "runtime": ["2"], + "instance": [3], + "frequency": [4.0], + } json_write(home_path, home) flow_path = workspace_dir / "home" / "flow.json" @@ -1063,6 +1033,7 @@ def create_step_workspaces(self): assert reset_home["checklist"] == str(checklist_path) assert reset_home["layout"] == "" assert reset_home["metrics"] == {} + assert reset_home["monitor"]["step"] == [] reset_flow = json_read(flow_path) assert reset_flow["steps"][0]["state"] == "Unstart" diff --git a/test/test_signoff_package.py b/test/test_signoff_package.py index 037593f6..17849d5c 100644 --- a/test/test_signoff_package.py +++ b/test/test_signoff_package.py @@ -255,37 +255,6 @@ def test_collect_signoff_package_requires_synthesis_verilog(tmp_path): ) -def test_collect_signoff_package_uses_origin_rtl_for_floorplan_start(tmp_path): - workspace_dir = _make_signoff_workspace(tmp_path) - (workspace_dir / "Synthesis_yosys" / "output" / "gcd_Synthesis.v.gz").unlink() - _write( - workspace_dir / "origin" / "gcd.v", - "module gcd; // original imported RTL\nendmodule\n", - ) - flow = json.loads((workspace_dir / "home" / "flow.json").read_text()) - flow["steps"].insert( - 0, - {"name": "Floorplan", "tool": "ecc", "state": StateEnum.Success.value}, - ) - _write_json(workspace_dir / "home" / "flow.json", flow) - engine_flow = _make_engine_flow(workspace_dir) - external_rtl = tmp_path / "external" / "gcd.v" - _write(external_rtl, "module gcd; // outside the workspace\nendmodule\n") - engine_flow.workspace.design.origin_verilog = external_rtl - - result = engine_flow.collect_signoff_package(SignoffPackageOptions(archive=True)) - - assert result.ok is True - package_dir = Path(result.package_dir) - assert (package_dir / "initial" / "gcd.v").read_text() == ( - "module gcd; // original imported RTL\nendmodule\n" - ) - assert not (package_dir / "synthesis" / "gcd.v.gz").exists() - summary = json.loads((package_dir / "summary.json").read_text()) - assert summary["initial"]["verilog"] == "initial/gcd.v" - assert "synthesis" not in summary - - def test_collect_signoff_package_tolerates_missing_sta_power_report(tmp_path): # Workspaces completed before power collection have no per-corner power.rpt; # it is packaged when present but must not be required for export.