Skip to content
Draft
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
3 changes: 3 additions & 0 deletions chipcompiler/data/pdk.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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)),
)


Expand Down Expand Up @@ -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*",
Expand Down
22 changes: 1 addition & 21 deletions chipcompiler/data/workspace/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)
105 changes: 105 additions & 0 deletions chipcompiler/data/workspace/sdc.py
Original file line number Diff line number Diff line change
@@ -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)
133 changes: 133 additions & 0 deletions test/data/test_workspace.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
):
Expand Down
Loading