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..b08ee6b6 100644 --- a/chipcompiler/data/workspace/sdc.py +++ b/chipcompiler/data/workspace/sdc.py @@ -11,26 +11,102 @@ if TYPE_CHECKING: from chipcompiler.data import Workspace +_SDC_HEAD_CLOCK = """\ +# 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] + +# ------------------------------------------------- +# 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] +""" + +_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] + +# ------------------------------------------------- +# 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] +""" + +_SDC_OUTPUT_LOAD = """ +# ------------------------------------------------- +# Output load (pF) - {pdk_name} pdk +# ------------------------------------------------- +set_load {sdc_load} [all_outputs] +""" + +_SDC_TAIL = """ +# ------------------------------------------------- +# 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] +""" + 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. """ - 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") + 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.writelines(sdc_content) + file.write(sdc_content) def refresh_generated_sdc(workspace: "Workspace") -> None: 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 diff --git a/test/data/test_workspace.py b/test/data/test_workspace.py index 57e795f3..34632b0e 100644 --- a/test/data/test_workspace.py +++ b/test/data/test_workspace.py @@ -162,6 +162,137 @@ 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] + +# ------------------------------------------------- +# 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 + + +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] + +# ------------------------------------------------- +# 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 ): @@ -1046,7 +1177,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(