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
9 changes: 8 additions & 1 deletion chipcompiler/cli/project/effective_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -410,6 +410,10 @@ def layer_divergences(cfg, assembled: dict, entry) -> list[str]:
def _backend_leaf_keys(schema) -> tuple[str, ...]:
"""The flattened backend key names a schema's maps_to target produces."""
maps_to = schema.maps_to
# Direct config/PDK parameters are applied through their explicit target
# and intentionally have no legacy backend projection.
if maps_to is None:
return ()
if isinstance(maps_to, str):
return (maps_to,)
return tuple(".".join((subtree, leaf)) for subtree, leaf in maps_to.items())
Expand Down Expand Up @@ -439,7 +443,10 @@ def _diverging_lower_keys(overrides: dict, resolve_lower) -> tuple[list[str], se
continue
coerced, type_err = _validate_schema_type(lower_value, schema)
if type_err or coerced != override_value:
diverging.extend(leaf_keys)
# Direct config/PDK parameters have no backend leaf key. Keep the
# warning useful by identifying the canonical parameter instead
# of silently dropping the divergence or inventing a key.
diverging.extend(leaf_keys or [dotted])
return diverging, compared


Expand Down
13 changes: 0 additions & 13 deletions chipcompiler/tools/ecc/module.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
#!/usr/bin/env python
import json
import os
import shutil
from pathlib import Path
Expand Down Expand Up @@ -569,18 +568,6 @@ def feature_route_read(self, json_path: str):
def feature_route(self, json_path: str):
self.ecc.feature_route(path=path_text(json_path))

def is_rt_timing_enable(self, config: str):
if os.path.exists(config):
with open(config, encoding="utf-8") as f_reader:
json_data = json.load(f_reader)
# check if time enable
if (
json_data is not None
and json_data.get("RT", {}).get("-enable_timing", "0") == "1"
):
return True
return False

########################################################################
# RCX api
########################################################################
Expand Down
40 changes: 5 additions & 35 deletions chipcompiler/tools/ecc/runner.py
Original file line number Diff line number Diff line change
Expand Up @@ -391,7 +391,6 @@ def save_data(
ecc_module: ECCToolsModule,
*,
feature_step: bool = True,
report_timing: bool = False,
) -> bool:
"""
module is ecc module from db engine,
Expand Down Expand Up @@ -424,17 +423,6 @@ def save_data(

ecc_module.report_summary(path=step.report.db or "")

if report_timing:
ecc_module.release_sta()
ecc_module.init_sta(
output_dir=(step.data.steps or {}).get("sta", ""),
top_module=workspace.design.top_module,
lib_paths=workspace.pdk.libs,
sdc_path=workspace.pdk.sdc,
)
ecc_module.report_timing()
ecc_module.release_sta()

# update parameters
db_json = json_read(step.feature.db or "")
if len(db_json) > 0:
Expand Down Expand Up @@ -573,24 +561,14 @@ def run_routing(
if ecc_module is not None:
sub_flow.update_step(step_name=EccSubFlowEnum.load_data.value, state=StateEnum.Success)

if ecc_module.is_rt_timing_enable(
config=workspace.config.get(f"{StepEnum.ROUTING.value}", "")
):
ecc_module.release_sta()
ecc_module.init_sta(
output_dir=(step.data.steps or {}).get(StepEnum.ROUTING.value, ""),
top_module=workspace.design.top_module,
lib_paths=workspace.pdk.libs,
sdc_path=workspace.pdk.sdc,
)

# Timing-driven routing is self-contained in iRT: RTInterface builds its
# own timing engine from the shared db config (lib paths, SDC), so no
# Python-side STA lifecycle is needed before run_routing.
ecc_module.run_routing(config=workspace.config.get(f"{StepEnum.ROUTING.value}", ""))

sub_flow.update_step(step_name=EccSubFlowEnum.run_routing.value, state=StateEnum.Success)

reslut = save_data(
workspace=workspace, step=step, ecc_module=ecc_module, report_timing=False
)
reslut = save_data(workspace=workspace, step=step, ecc_module=ecc_module)

sub_flow.update_step(step_name=EccSubFlowEnum.save_data.value, state=StateEnum.Success)

Expand Down Expand Up @@ -623,7 +601,6 @@ def run_drc(workspace: Workspace, step: EccStep, ecc_module: ECCToolsModule | No
step=step,
ecc_module=ecc_module,
feature_step=False,
report_timing=False,
)
if not reslut:
return False
Expand Down Expand Up @@ -670,7 +647,6 @@ def run_lvs(workspace: Workspace, step: EccStep, ecc_module: ECCToolsModule | No
step=step,
ecc_module=ecc_module,
feature_step=False,
report_timing=False,
)
if not reslut:
return False
Expand Down Expand Up @@ -703,9 +679,7 @@ def run_filler(

sub_flow.update_step(step_name=EccSubFlowEnum.run_filler.value, state=StateEnum.Success)

reslut = save_data(
workspace=workspace, step=step, ecc_module=ecc_module, report_timing=False
)
reslut = save_data(workspace=workspace, step=step, ecc_module=ecc_module)

sub_flow.update_step(step_name=EccSubFlowEnum.save_data.value, state=StateEnum.Success)

Expand Down Expand Up @@ -746,7 +720,6 @@ def run_pre_floorplan(
step=step,
ecc_module=ecc_module,
feature_step=False,
report_timing=False,
)
sub_flow.update_step(step_name=EccSubFlowEnum.save_data.value, state=StateEnum.Success)

Expand Down Expand Up @@ -788,7 +761,6 @@ def run_post_floorplan(
step=step,
ecc_module=ecc_module,
feature_step=False,
report_timing=False,
)
sub_flow.update_step(step_name=EccSubFlowEnum.save_data.value, state=StateEnum.Success)

Expand Down Expand Up @@ -893,7 +865,6 @@ def run_rcx(workspace: Workspace, step: EccStep, ecc_module: ECCToolsModule | No
step=step,
ecc_module=ecc_module,
feature_step=False,
report_timing=False,
):
workspace.logger.error("Failed to save RCX data")
return False
Expand Down Expand Up @@ -1023,7 +994,6 @@ def run_sta(workspace: Workspace, step: EccStep, ecc_module: ECCToolsModule | No
step=step,
ecc_module=ecc_module,
feature_step=False,
report_timing=False,
)

sub_flow.update_step(step_name=EccSubFlowEnum.save_data.value, state=StateEnum.Success)
Expand Down
41 changes: 39 additions & 2 deletions chipcompiler/tools/ecc_sizer/runner.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import logging
import os
import shutil
import signal
import subprocess
from pathlib import Path

Expand All @@ -20,6 +21,41 @@ def _has_staging_outputs(step: EccStep) -> bool:
return os.path.exists(sizer_staging_def(step)) and os.path.exists(sizer_staging_verilog(step))


# Native crash banners glibc/libstdc++ write to the inherited stderr before
# aborting (e.g. "*** buffer overflow detected ***: terminated"). A kill or
# timeout prints no such line, so the banner separates tool crashes from
# external termination.
_FATAL_LOG_MARKERS = (
"*** ",
"terminate called after throwing",
)


def _termination_reason(returncode: int) -> str:
"""Classify a native exit status as a fatal signal or a tool exit code."""
if returncode >= 0:
return f"exit_code={returncode}"
signum = -returncode
try:
signame = signal.Signals(signum).name
except ValueError:
signame = "UNKNOWN"
return f"signal={signame}({signum})"


def _first_fatal_log_line(log_path: str) -> str:
"""The first native fatal banner in the step log, or an empty string."""
try:
with open(log_path, errors="replace") as log_file:
for line in log_file:
stripped = line.strip()
if stripped.startswith(_FATAL_LOG_MARKERS):
return stripped
except OSError:
pass
return ""


def _published_paths(step: EccStep) -> list[Path]:
output = step.output
if not isinstance(output, EccOutput):
Expand Down Expand Up @@ -127,10 +163,11 @@ def run_step(

if result.returncode != 0 or not _has_staging_outputs(step):
logger.error(
"Sizer failed for step %s: exit code=%d, staging present=%s",
"Sizer failed for step %s: %s, staging present=%s, fatal_log_line=%r",
step.name,
result.returncode,
_termination_reason(result.returncode),
_has_staging_outputs(step),
_first_fatal_log_line(log_path),
)
sub_flow.update_step(step_name=run_sizer_step, state=StateEnum.Imcomplete)
return StateEnum.Imcomplete
Expand Down
107 changes: 107 additions & 0 deletions test/cli/commands/test_effective_config.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import json
from pathlib import Path

import pytest

from chipcompiler.cli import main as cli_main


Expand Down Expand Up @@ -634,6 +636,111 @@ def test_run_set_overrides_invalid_manifest_value(
parameters = flow_mocks.capture["create_kwargs"]["parameters"]
assert parameters["frequency_max"] == 100.0

@pytest.mark.parametrize(
("param", "value", "expected"),
[
(
"place.random_seed",
"3001",
{"dreamplace": {"random_seed": 3001}},
),
(
"route.RT.-enable_timing",
"1",
{"route": {"RT": {"-enable_timing": "1"}}},
),
],
)
def test_run_set_direct_config_parameter_in_manifest_project(
self, tmp_path, capsys, monkeypatch, flow_mocks, manifest_stubs, param, value, expected
):
project_dir = _write_manifest_project(
manifest_stubs,
tmp_path,
monkeypatch,
100,
ecc_toml=self._hybrid_toml(project_dir=tmp_path / "proj", frequency=False),
)

rc = cli_main.run(
[
"run",
"--project",
str(project_dir),
"--from",
"Synth",
"--to",
"Synth",
"--set",
f"{param}={value}",
]
)

assert rc == 0
assert flow_mocks.capture["create_kwargs"]["parameters"]["config_overrides"] == expected

def test_run_set_direct_config_parameter_warns_on_different_toml_value(
self, tmp_path, capsys, monkeypatch, flow_mocks, manifest_stubs
):
project_dir = _write_manifest_project(
manifest_stubs,
tmp_path,
monkeypatch,
100,
ecc_toml=self._hybrid_toml(project_dir=tmp_path / "proj", frequency=False)
+ "\n[params.place]\nrandom_seed = 3000\n",
)

rc = cli_main.run(
[
"run",
"--project",
str(project_dir),
"--from",
"Synth",
"--to",
"Synth",
"--set",
"place.random_seed=3001",
"--plain",
]
)

assert rc == 0
warnings = [
record
for record in manifest_stubs.records()
if record.get("warning") == "config_layer_diverged"
]
assert len(warnings) == 1
assert "place.random_seed" in warnings[0]["keys"]

@pytest.mark.parametrize(
("section", "key", "value"),
[
("[params.place]", "random_seed", "3001"),
("[params.route.RT]", '"-enable_timing"', '"1"'),
],
)
def test_check_direct_config_param_override_in_manifest_project(
self, tmp_path, capsys, monkeypatch, manifest_stubs, section, key, value
):
project_dir = tmp_path / "proj"
toml = self._hybrid_toml(project_dir, frequency=False)
toml += f"\n{section}\n{key} = {value}\n"
_write_manifest_project(
manifest_stubs,
tmp_path,
monkeypatch,
100,
ecc_toml=toml,
)

rc = cli_main.run(["check", "--project", str(project_dir), "--plain"])

assert rc == 0
assert manifest_stubs.records()[0]["status"] == "checked"


class TestManifestBoolTypeSafety:
"""project.json run_analysis is the first bool registry parameter: its
Expand Down
Loading
Loading