From 2a26ff9c7e7b648e596633935c4a9d710476bc0a Mon Sep 17 00:00:00 2001 From: Emin Date: Fri, 11 Sep 2026 11:42:31 +0800 Subject: [PATCH 1/4] feat: generate full default SDC with PDK-driven output load Rewrite create_default_sdc as a template that adds IO delay, clock uncertainty/transition and max-fanout constraints on top of the clock definition. Clock name/port, frequency and max fanout come from workspace parameters; the new PDK field sdc_load (set to 0.001 for ics55, also readable from external pdk.json) gates the optional set_load section. Signed-off-by: Emin --- chipcompiler/data/pdk.py | 3 ++ chipcompiler/data/workspace/sdc.py | 68 +++++++++++++++++++++++------ test/data/test_workspace.py | 70 +++++++++++++++++++++++++++++- 3 files changed, 127 insertions(+), 14 deletions(-) diff --git a/chipcompiler/data/pdk.py b/chipcompiler/data/pdk.py index 9d8278b1..b12a8aec 100644 --- a/chipcompiler/data/pdk.py +++ b/chipcompiler/data/pdk.py @@ -41,6 +41,7 @@ class PDK: dont_use: list[str] = field(default_factory=list[str]) # don't use cell list abc_driver_cell: str = "" # ABC driving cell abc_load: float = 0.015 # ABC output load + sdc_load: float = 0.0 # output load (pF) for generated SDC; 0 omits set_load def __post_init__(self) -> None: self.root = optional_path(self.root) @@ -222,6 +223,7 @@ def _pdk_from_external_config(data: dict, pdk_name: str = "") -> PDK: dont_use=data.get("dont_use", []), abc_driver_cell=str(data.get("abc_driver_cell", "")), abc_load=float(data.get("abc_load", 0.015)), + sdc_load=float(data.get("sdc_load", 0.0)), ) @@ -360,6 +362,7 @@ def PDK_ICS55(pdk_root: str | Path = "") -> PDK: tie_low_port="Z", abc_driver_cell="BUFX0P5H7R", abc_load=0.015, + sdc_load=0.001, dont_use=[ "DFFSRQX*", "DFFSRX*", diff --git a/chipcompiler/data/workspace/sdc.py b/chipcompiler/data/workspace/sdc.py index e0460fce..95d75cc3 100644 --- a/chipcompiler/data/workspace/sdc.py +++ b/chipcompiler/data/workspace/sdc.py @@ -16,21 +16,63 @@ def create_default_sdc(workspace: "Workspace") -> None: """ Create SDC file based on PDK and workspace parameters. """ - sdc_content = [] - sdc_content.append("# Auto-generated SDC file\n") - sdc_content.append("\n") - sdc_content.append("set clk_name {} \n".format(workspace.parameters.data.get("clock", ""))) - sdc_content.append("set clk_port_name {}\n".format(workspace.parameters.data.get("clock", ""))) - sdc_content.append( - "set clk_freq_mhz {}\n".format(workspace.parameters.data.get("frequency_max", 100)) - ) - sdc_content.append("set clk_period [expr 1000.0 / $clk_freq_mhz]\n") - sdc_content.append("set clk_io_pct 0.2\n") - sdc_content.append("set clk_port [get_ports $clk_port_name]\n") - sdc_content.append("create_clock -name $clk_name -period $clk_period $clk_port\n") + clock = workspace.parameters.data.get("clock", "") + freq_mhz = workspace.parameters.data.get("frequency_max", 100) + max_fanout = workspace.parameters.data.get("max_fanout", 20) + + sdc_content = f"""\ +# Auto-generated SDC file + +set clk_name {clock} +set clk_port_name {clock} +set clk_freq_mhz {freq_mhz} +set clk_period [expr 1000.0 / $clk_freq_mhz] +set clk_io_pct 0.2 + +# ------------------------------------------------- +# Clock definition +# ------------------------------------------------- +set clk_port [get_ports $clk_port_name] +create_clock -name $clk_name -period $clk_period $clk_port + +# ------------------------------------------------- +# IO Delay +# ------------------------------------------------- +set clk_input [get_ports $clk_port_name] +set all_inputs_wo_clk [remove_from_collection [all_inputs] $clk_input] + +set_input_delay 0 -clock [get_clocks $clk_name] $all_inputs_wo_clk +set_output_delay 0 -clock [get_clocks $clk_name] [all_outputs] +""" + + if workspace.pdk.sdc_load > 0: + sdc_content += f""" +# ------------------------------------------------- +# Output load (pF) - {workspace.pdk.name} pdk +# ------------------------------------------------- +set_load {workspace.pdk.sdc_load} [all_outputs] +""" + + sdc_content += f""" +# ------------------------------------------------- +# Clock uncertainty & transition +# ------------------------------------------------- +set clk_uncertainty [expr $clk_period * 0.05] ;# 5% of period +set clk_transition [expr min(0.15, $clk_period * 0.03)] ;# 3%, cap 0.15ns +set input_transition [expr min(0.20, $clk_period * 0.05)] ;# 5%, cap 0.20ns + +set_clock_uncertainty $clk_uncertainty [get_clocks $clk_name] +set_clock_transition $clk_transition [get_clocks $clk_name] +set_input_transition $input_transition $all_inputs_wo_clk + +# ------------------------------------------------- +# Design-level constraints +# ------------------------------------------------- +set_max_fanout {max_fanout} [current_design] +""" with open(workspace.pdk.sdc, "w") as file: - file.writelines(sdc_content) + file.write(sdc_content) def refresh_generated_sdc(workspace: "Workspace") -> None: diff --git a/test/data/test_workspace.py b/test/data/test_workspace.py index 57e795f3..378d5860 100644 --- a/test/data/test_workspace.py +++ b/test/data/test_workspace.py @@ -162,6 +162,74 @@ def test_create_workspace_rejects_existing_non_empty_directory(tmp_path): assert workspace is None +EXPECTED_ICS55_DEFAULT_SDC = """\ +# Auto-generated SDC file + +set clk_name clk +set clk_port_name clk +set clk_freq_mhz 100 +set clk_period [expr 1000.0 / $clk_freq_mhz] +set clk_io_pct 0.2 + +# ------------------------------------------------- +# Clock definition +# ------------------------------------------------- +set clk_port [get_ports $clk_port_name] +create_clock -name $clk_name -period $clk_period $clk_port + +# ------------------------------------------------- +# IO Delay +# ------------------------------------------------- +set clk_input [get_ports $clk_port_name] +set all_inputs_wo_clk [remove_from_collection [all_inputs] $clk_input] + +set_input_delay 0 -clock [get_clocks $clk_name] $all_inputs_wo_clk +set_output_delay 0 -clock [get_clocks $clk_name] [all_outputs] + +# ------------------------------------------------- +# Output load (pF) - ics55 pdk +# ------------------------------------------------- +set_load 0.001 [all_outputs] + +# ------------------------------------------------- +# Clock uncertainty & transition +# ------------------------------------------------- +set clk_uncertainty [expr $clk_period * 0.05] ;# 5% of period +set clk_transition [expr min(0.15, $clk_period * 0.03)] ;# 3%, cap 0.15ns +set input_transition [expr min(0.20, $clk_period * 0.05)] ;# 5%, cap 0.20ns + +set_clock_uncertainty $clk_uncertainty [get_clocks $clk_name] +set_clock_transition $clk_transition [get_clocks $clk_name] +set_input_transition $input_transition $all_inputs_wo_clk + +# ------------------------------------------------- +# Design-level constraints +# ------------------------------------------------- +set_max_fanout 32 [current_design] +""" + + +def test_create_workspace_generates_default_sdc_from_parameters( + tmp_path, minimal_ics55_pdk_factory, default_ics55_parameters +): + pdk_root = minimal_ics55_pdk_factory(tmp_path / "ics55") + rtl_path = tmp_path / "gcd.v" + rtl_path.write_text("module gcd(input clk, output y); assign y = clk; endmodule\n") + + workspace_dir = tmp_path / "workspace" + create_workspace( + directory=str(workspace_dir), + origin_def="", + origin_verilog=str(rtl_path), + pdk="ics55", + parameters={**default_ics55_parameters, "max_fanout": 32}, + pdk_root=str(pdk_root), + ) + + sdc_path = workspace_dir / "origin" / "gcd.sdc" + assert sdc_path.read_text() == EXPECTED_ICS55_DEFAULT_SDC + + def test_create_workspace_persists_dynamic_flow_steps( tmp_path, minimal_ics55_pdk_factory, default_ics55_parameters ): @@ -1046,7 +1114,7 @@ def test_refresh_workspace_config_updates_generated_sdc_frequency( refresh_workspace_config(workspace) - assert "set clk_freq_mhz 250.0" in workspace.pdk.sdc.read_text(encoding="utf-8") + assert "set clk_freq_mhz 250.0" in workspace.pdk.sdc.read_text(encoding="utf-8") def test_refresh_workspace_config_preserves_external_sdc( From 2cd32f8af6dfda8efe4edf15896ba6bf81cc6b88 Mon Sep 17 00:00:00 2001 From: Emin Date: Fri, 11 Sep 2026 11:43:37 +0800 Subject: [PATCH 2/4] feat: fall back to virtual clock in default SDC for clockless designs A design without a clock port previously produced a broken 'get_ports ""' constraint; generate a __VIRTUAL_CLK__ create_clock without a source port instead so downstream tools still have a clock object as timing reference. Also restructure the SDC template as complete per-branch constants instead of hole-filled f-string assembly. Signed-off-by: Emin --- chipcompiler/data/workspace/sdc.py | 66 +++++++++++++++++++++++------- test/data/test_workspace.py | 65 +++++++++++++++++++++++++++++ 2 files changed, 116 insertions(+), 15 deletions(-) diff --git a/chipcompiler/data/workspace/sdc.py b/chipcompiler/data/workspace/sdc.py index 95d75cc3..ed3b7dbf 100644 --- a/chipcompiler/data/workspace/sdc.py +++ b/chipcompiler/data/workspace/sdc.py @@ -11,16 +11,7 @@ if TYPE_CHECKING: from chipcompiler.data import Workspace - -def create_default_sdc(workspace: "Workspace") -> None: - """ - Create SDC file based on PDK and workspace parameters. - """ - clock = workspace.parameters.data.get("clock", "") - freq_mhz = workspace.parameters.data.get("frequency_max", 100) - max_fanout = workspace.parameters.data.get("max_fanout", 20) - - sdc_content = f"""\ +_SDC_HEAD_CLOCK = """\ # Auto-generated SDC file set clk_name {clock} @@ -45,15 +36,36 @@ def create_default_sdc(workspace: "Workspace") -> None: set_output_delay 0 -clock [get_clocks $clk_name] [all_outputs] """ - if workspace.pdk.sdc_load > 0: - sdc_content += f""" +_SDC_HEAD_VIRTUAL_CLOCK = """\ +# Auto-generated SDC file + +set clk_name __VIRTUAL_CLK__ +set clk_freq_mhz {freq_mhz} +set clk_period [expr 1000.0 / $clk_freq_mhz] +set clk_io_pct 0.2 + +# ------------------------------------------------- +# Clock definition +# ------------------------------------------------- +create_clock -name $clk_name -period $clk_period + +# ------------------------------------------------- +# IO Delay # ------------------------------------------------- -# Output load (pF) - {workspace.pdk.name} pdk +set all_inputs_wo_clk [all_inputs] + +set_input_delay 0 -clock [get_clocks $clk_name] $all_inputs_wo_clk +set_output_delay 0 -clock [get_clocks $clk_name] [all_outputs] +""" + +_SDC_OUTPUT_LOAD = """ +# ------------------------------------------------- +# Output load (pF) - {pdk_name} pdk # ------------------------------------------------- -set_load {workspace.pdk.sdc_load} [all_outputs] +set_load {sdc_load} [all_outputs] """ - sdc_content += f""" +_SDC_TAIL = """ # ------------------------------------------------- # Clock uncertainty & transition # ------------------------------------------------- @@ -71,6 +83,30 @@ def create_default_sdc(workspace: "Workspace") -> None: set_max_fanout {max_fanout} [current_design] """ + +def create_default_sdc(workspace: "Workspace") -> None: + """ + Create SDC file based on PDK and workspace parameters. + + A design without a clock port gets a virtual clock instead, so + downstream tools still have a clock object as timing reference. + """ + parameters = workspace.parameters.data + freq_mhz = parameters.get("frequency_max", 100) + + clock = parameters.get("clock", "") + if clock: + sdc_content = _SDC_HEAD_CLOCK.format(clock=clock, freq_mhz=freq_mhz) + else: + sdc_content = _SDC_HEAD_VIRTUAL_CLOCK.format(freq_mhz=freq_mhz) + + if workspace.pdk.sdc_load > 0: + sdc_content += _SDC_OUTPUT_LOAD.format( + pdk_name=workspace.pdk.name, sdc_load=workspace.pdk.sdc_load + ) + + sdc_content += _SDC_TAIL.format(max_fanout=parameters.get("max_fanout", 20)) + with open(workspace.pdk.sdc, "w") as file: file.write(sdc_content) diff --git a/test/data/test_workspace.py b/test/data/test_workspace.py index 378d5860..5324318a 100644 --- a/test/data/test_workspace.py +++ b/test/data/test_workspace.py @@ -230,6 +230,71 @@ def test_create_workspace_generates_default_sdc_from_parameters( assert sdc_path.read_text() == EXPECTED_ICS55_DEFAULT_SDC +EXPECTED_ICS55_VIRTUAL_CLOCK_SDC = """\ +# Auto-generated SDC file + +set clk_name __VIRTUAL_CLK__ +set clk_freq_mhz 100 +set clk_period [expr 1000.0 / $clk_freq_mhz] +set clk_io_pct 0.2 + +# ------------------------------------------------- +# Clock definition +# ------------------------------------------------- +create_clock -name $clk_name -period $clk_period + +# ------------------------------------------------- +# IO Delay +# ------------------------------------------------- +set all_inputs_wo_clk [all_inputs] + +set_input_delay 0 -clock [get_clocks $clk_name] $all_inputs_wo_clk +set_output_delay 0 -clock [get_clocks $clk_name] [all_outputs] + +# ------------------------------------------------- +# Output load (pF) - ics55 pdk +# ------------------------------------------------- +set_load 0.001 [all_outputs] + +# ------------------------------------------------- +# Clock uncertainty & transition +# ------------------------------------------------- +set clk_uncertainty [expr $clk_period * 0.05] ;# 5% of period +set clk_transition [expr min(0.15, $clk_period * 0.03)] ;# 3%, cap 0.15ns +set input_transition [expr min(0.20, $clk_period * 0.05)] ;# 5%, cap 0.20ns + +set_clock_uncertainty $clk_uncertainty [get_clocks $clk_name] +set_clock_transition $clk_transition [get_clocks $clk_name] +set_input_transition $input_transition $all_inputs_wo_clk + +# ------------------------------------------------- +# Design-level constraints +# ------------------------------------------------- +set_max_fanout 20 [current_design] +""" + + +def test_create_workspace_generates_virtual_clock_sdc_for_clockless_design( + tmp_path, minimal_ics55_pdk_factory, default_ics55_parameters +): + pdk_root = minimal_ics55_pdk_factory(tmp_path / "ics55") + rtl_path = tmp_path / "gcd.v" + rtl_path.write_text("module gcd(input clk, output y); assign y = clk; endmodule\n") + + workspace_dir = tmp_path / "workspace" + create_workspace( + directory=str(workspace_dir), + origin_def="", + origin_verilog=str(rtl_path), + pdk="ics55", + parameters={**default_ics55_parameters, "clock": ""}, + pdk_root=str(pdk_root), + ) + + sdc_path = workspace_dir / "origin" / "gcd.sdc" + assert sdc_path.read_text() == EXPECTED_ICS55_VIRTUAL_CLOCK_SDC + + def test_create_workspace_persists_dynamic_flow_steps( tmp_path, minimal_ics55_pdk_factory, default_ics55_parameters ): From 24cdead8672ab83d0ed8bf04525bd91be9cdc2ab Mon Sep 17 00:00:00 2001 From: Emin Date: Fri, 11 Sep 2026 11:44:12 +0800 Subject: [PATCH 3/4] fix: remove unused clk_io_pct Signed-off-by: Emin --- chipcompiler/data/workspace/sdc.py | 2 -- test/data/test_workspace.py | 2 -- 2 files changed, 4 deletions(-) diff --git a/chipcompiler/data/workspace/sdc.py b/chipcompiler/data/workspace/sdc.py index ed3b7dbf..b08ee6b6 100644 --- a/chipcompiler/data/workspace/sdc.py +++ b/chipcompiler/data/workspace/sdc.py @@ -18,7 +18,6 @@ set clk_port_name {clock} set clk_freq_mhz {freq_mhz} set clk_period [expr 1000.0 / $clk_freq_mhz] -set clk_io_pct 0.2 # ------------------------------------------------- # Clock definition @@ -42,7 +41,6 @@ set clk_name __VIRTUAL_CLK__ set clk_freq_mhz {freq_mhz} set clk_period [expr 1000.0 / $clk_freq_mhz] -set clk_io_pct 0.2 # ------------------------------------------------- # Clock definition diff --git a/test/data/test_workspace.py b/test/data/test_workspace.py index 5324318a..34632b0e 100644 --- a/test/data/test_workspace.py +++ b/test/data/test_workspace.py @@ -169,7 +169,6 @@ def test_create_workspace_rejects_existing_non_empty_directory(tmp_path): set clk_port_name clk set clk_freq_mhz 100 set clk_period [expr 1000.0 / $clk_freq_mhz] -set clk_io_pct 0.2 # ------------------------------------------------- # Clock definition @@ -236,7 +235,6 @@ def test_create_workspace_generates_default_sdc_from_parameters( set clk_name __VIRTUAL_CLK__ set clk_freq_mhz 100 set clk_period [expr 1000.0 / $clk_freq_mhz] -set clk_io_pct 0.2 # ------------------------------------------------- # Clock definition From 0e51a1096890c6a02d6590505a4d23210fcd9286 Mon Sep 17 00:00:00 2001 From: Emin Date: Tue, 15 Sep 2026 10:47:59 +0800 Subject: [PATCH 4/4] chore: bump ecc-tools submodule The latest main ecc-tools has supported the sdc syntax. Signed-off-by: Emin --- chipcompiler/thirdparty/ecc-tools | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chipcompiler/thirdparty/ecc-tools b/chipcompiler/thirdparty/ecc-tools index fdaa70a9..7d0eebb4 160000 --- a/chipcompiler/thirdparty/ecc-tools +++ b/chipcompiler/thirdparty/ecc-tools @@ -1 +1 @@ -Subproject commit fdaa70a971be6c33a8dc6d695011bd9854ee18c5 +Subproject commit 7d0eebb4945cbaa69571f188dff7892fdd5b15fe