diff --git a/chipcompiler/data/pdk.py b/chipcompiler/data/pdk.py index a1097aa5..b9398ade 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) @@ -201,6 +202,7 @@ def PDK_EXTERNAL(pdk_config: str | Path, 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)), ) @@ -311,6 +313,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/__init__.py b/chipcompiler/data/workspace/__init__.py index a78af1c2..ba15b0c2 100644 --- a/chipcompiler/data/workspace/__init__.py +++ b/chipcompiler/data/workspace/__init__.py @@ -20,6 +20,7 @@ from ..pdk import PDK, get_pdk from ..step import StateEnum, StepEnum from .layout import EccData, WorkspaceStepBase +from .sdc import create_default_sdc # The shared step type used as the annotation/constructor across the codebase. WorkspaceStep = WorkspaceStepBase @@ -1367,24 +1368,3 @@ def format_string(text: str, len=20) -> str: format_string(step.get("state", "")), format_string(step.get("runtime", "")), ) - - -def create_default_sdc(workspace: Workspace): - """ - 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 [MHz]", 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") - - with open(workspace.pdk.sdc, "w") as file: - file.writelines(sdc_content) diff --git a/chipcompiler/data/workspace/sdc.py b/chipcompiler/data/workspace/sdc.py new file mode 100644 index 00000000..66df7daf --- /dev/null +++ b/chipcompiler/data/workspace/sdc.py @@ -0,0 +1,105 @@ +#!/usr/bin/env python + +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from . 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] +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] +""" + +_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 +# ------------------------------------------------- +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"): + """ + 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 [MHz]", 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 ce2b6571..394c8935 100644 --- a/test/data/test_workspace.py +++ b/test/data/test_workspace.py @@ -538,6 +538,139 @@ def test_create_workspace_persists_pdk_root_in_parameters( assert parameters_data.get("PDK Root") == str(resolved_root) +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 + + +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_load_workspace_restores_pdk_root_from_parameters( tmp_path, minimal_ics55_pdk_factory, default_ics55_parameters ):