diff --git a/chipcompiler/data/parameter.py b/chipcompiler/data/parameter.py index 45b6cad..5336254 100644 --- a/chipcompiler/data/parameter.py +++ b/chipcompiler/data/parameter.py @@ -197,6 +197,74 @@ ] } } +SG13G2_PARAMETERS_TEMPLATE = { + "PDK": "sg13g2", + "Design": "", + "Top module": "", + "Die": { + "Size": [], + "Area": 0 + }, + "Core": { + "Size": [], + "Area": 0, + "Bounding box": "", + "Utilitization": 0.65, + "Margin": [17.5, 17.5], + "Aspect ratio": 1 + }, + "Max fanout": 20, + "Target density": 0.65, + "Target overflow": 0.1, + "Global right padding": 0, + "Cell padding x": 0, + "Routability opt flag": 1, + "Clock": "", + "Frequency max [MHz]": 100, + "Bottom layer": "Metal2", + "Top layer": "Metal5", + "Floorplan": { + "Tap distance": 0, + "Auto place pin": { + "layer": "Metal3", + "width": 300, + "height": 600, + "sides": [] + }, + "Tracks": [ + {"layer": "Metal1", "x start": 0, "x step": 420, "y start": 0, "y step": 420}, + {"layer": "Metal2", "x start": 0, "x step": 480, "y start": 0, "y step": 480}, + {"layer": "Metal3", "x start": 0, "x step": 420, "y start": 0, "y step": 420}, + {"layer": "Metal4", "x start": 0, "x step": 480, "y start": 0, "y step": 480}, + {"layer": "Metal5", "x start": 0, "x step": 420, "y start": 0, "y step": 420}, + ] + }, + "PDN": { + "IO": [ + {"net name": "VDD", "direction": "INOUT", "is power": True}, + {"net name": "VSS", "direction": "INOUT", "is power": False} + ], + "Global connect": [ + {"net name": "VDD", "instance pin name": "VDD", "is power": True}, + {"net name": "VSS", "instance pin name": "VSS", "is power": False} + ], + "Grid": { + "layer": "Metal1", + "power net": "VDD", + "power ground": "VSS", + "width": 0.44, + "offset": 0 + }, + "Stripe": [ + {"layer": "Metal4", "power net": "VDD", "ground net": "VSS", "width": 1.6, "pitch": 20, "offset": 1}, + {"layer": "Metal5", "power net": "VDD", "ground net": "VSS", "width": 1.6, "pitch": 20, "offset": 1} + ], + "Connect layers": [ + {"layers": ["Metal1", "Metal5"]}, + {"layers": ["Metal4", "Metal5"]} + ] + } +} ICS55_DESIGN_PARAMETERS = { "gcd": { @@ -227,20 +295,19 @@ def save_parameter(parameter : Parameters) -> bool: return json_write(file_path=parameter.path, data=parameter.data) -def get_parameters(pdk_name : str = "", path : str = "") -> Parameters: - """ - Return the Parameters instance based on the given pdk name. - """ +def get_parameters(pdk_name: str = "", path: str = "") -> Parameters: if os.path.isfile(path): return load_parameter(path) - + parameters = Parameters() parameters.path = path - + match pdk_name.lower(): case "ics55": parameters.data = deepcopy(ICS55_PARAMETERS_TEMPLATE) - + case "sg13g2": + parameters.data = deepcopy(SG13G2_PARAMETERS_TEMPLATE) + return parameters def get_design_parameters(pdk_name : str, design : str = "", path : str = "") -> Parameters: diff --git a/chipcompiler/data/pdk.py b/chipcompiler/data/pdk.py index 77c55ca..e7b264e 100644 --- a/chipcompiler/data/pdk.py +++ b/chipcompiler/data/pdk.py @@ -56,6 +56,8 @@ def get_pdk(pdk_name : str, pdk_root: str = "") -> PDK: pdk_name_normalized = (pdk_name or "").strip().lower() if pdk_name_normalized == "ics55": pdk = PDK_ICS55(pdk_root=pdk_root) + elif pdk_name_normalized == "sg13g2": + pdk = PDK_SG13G2(pdk_root=pdk_root) else: pdk = PDK(name=pdk_name_normalized) pdk.validate() @@ -132,3 +134,53 @@ def PDK_ICS55(pdk_root: str = "") -> PDK: ) return pdk + +def PDK_SG13G2(pdk_root: str = "") -> PDK: + resolved_root = os.path.abspath(os.path.expanduser( + (pdk_root or "").strip() + or os.environ.get("CHIPCOMPILER_SG13G2_PDK_ROOT", "").strip() + or os.environ.get("SG13G2_PDK_ROOT", "").strip() + )) + + tech_path = "{}/libs.ref/sg13g2_stdcell/lef/sg13g2_tech.lef".format(resolved_root) + lef_paths = [ + "{}/libs.ref/sg13g2_stdcell/lef/sg13g2_stdcell.lef".format(resolved_root) + ] + lib_paths = [ + "{}/libs.ref/sg13g2_stdcell/lib/sg13g2_stdcell_typ_1p20V_25C.lib".format(resolved_root) + ] + + pdk = PDK( + name="sg13g2", + version="1.0", + root=resolved_root, + tech=tech_path if os.path.isfile(tech_path) else "", + lefs=[path for path in lef_paths if os.path.isfile(path)], + libs=[path for path in lib_paths if os.path.isfile(path)], + site_core="CoreSite", + buffers=[ + "sg13g2_buf_1", + "sg13g2_buf_2", + "sg13g2_buf_4", + "sg13g2_buf_8", + "sg13g2_buf_16" + ], + fillers=[ + "sg13g2_fill_1", + "sg13g2_fill_2", + "sg13g2_decap_4", + "sg13g2_decap_8" + ], + tie_high_cell="sg13g2_tiehi", + tie_high_port="L_HI", + tie_low_cell="sg13g2_tielo", + tie_low_port="L_LO", + dont_use=[ + "sg13g2_lgcp_1", + "sg13g2_sighold", + "sg13g2_slgcp_1", + "sg13g2_dfrbp_2" + ] + ) + + return pdk \ No newline at end of file diff --git a/test/test_data_pdk.py b/test/test_data_pdk.py index cf1f1eb..0b5a21c 100644 --- a/test/test_data_pdk.py +++ b/test/test_data_pdk.py @@ -65,3 +65,89 @@ def test_get_pdk_raises_on_missing_pdk_files(tmp_path): with pytest.raises(ValueError, match="PDK validation failed"): get_pdk("ics55", pdk_root=str(invalid_root)) + + +#SG13G2 helpers and tests + +def _create_minimal_sg13g2_pdk(root: Path) -> Path: + """Create the minimal SG13G2 directory tree required by get_pdk().""" + tech_path = root / "libs.ref" / "sg13g2_stdcell" / "lef" / "sg13g2_tech.lef" + tech_path.parent.mkdir(parents=True, exist_ok=True) + tech_path.write_text("VERSION 5.8 ;\n") + + lef_path = root / "libs.ref" / "sg13g2_stdcell" / "lef" / "sg13g2_stdcell.lef" + lef_path.write_text("VERSION 5.8 ;\n") + + lib_path = root / "libs.ref" / "sg13g2_stdcell" / "lib" / "sg13g2_stdcell_typ_1p20V_25C.lib" + lib_path.parent.mkdir(parents=True, exist_ok=True) + lib_path.write_text("library(test) { }\n") + + return root + + +def test_get_pdk_sg13g2_prefers_explicit_root_over_env(tmp_path, monkeypatch): + explicit_root = _create_minimal_sg13g2_pdk(tmp_path / "explicit") + env_root = _create_minimal_sg13g2_pdk(tmp_path / "env") + monkeypatch.setenv("CHIPCOMPILER_SG13G2_PDK_ROOT", str(env_root)) + + pdk = get_pdk("sg13g2", pdk_root=str(explicit_root)) + + expected_root = str(explicit_root.resolve()) + assert pdk.root == expected_root + assert pdk.tech.startswith(expected_root) + assert all(path.startswith(expected_root) for path in pdk.lefs + pdk.libs) + + +def test_get_pdk_sg13g2_uses_namespaced_env(tmp_path, monkeypatch): + env_root = _create_minimal_sg13g2_pdk(tmp_path / "env") + monkeypatch.setenv("CHIPCOMPILER_SG13G2_PDK_ROOT", str(env_root)) + monkeypatch.delenv("SG13G2_PDK_ROOT", raising=False) + + pdk = get_pdk("sg13g2") + + assert pdk.root == str(env_root.resolve()) + + +def test_get_pdk_sg13g2_uses_legacy_env_when_namespaced_missing(tmp_path, monkeypatch): + legacy_root = _create_minimal_sg13g2_pdk(tmp_path / "legacy") + monkeypatch.delenv("CHIPCOMPILER_SG13G2_PDK_ROOT", raising=False) + monkeypatch.setenv("SG13G2_PDK_ROOT", str(legacy_root)) + + pdk = get_pdk("sg13g2") + + assert pdk.root == str(legacy_root.resolve()) + + +def test_get_pdk_sg13g2_raises_on_missing_pdk_files(tmp_path, monkeypatch): + invalid_root = tmp_path / "broken_sg13g2" + invalid_root.mkdir(parents=True, exist_ok=True) + monkeypatch.setenv("CHIPCOMPILER_SG13G2_PDK_ROOT", str(invalid_root)) + + with pytest.raises(ValueError, match="PDK validation failed"): + get_pdk("sg13g2") + + +def test_get_pdk_sg13g2_cell_config(tmp_path, monkeypatch): + pdk_root = _create_minimal_sg13g2_pdk(tmp_path / "sg13g2") + monkeypatch.setenv("CHIPCOMPILER_SG13G2_PDK_ROOT", str(pdk_root)) + + pdk = get_pdk("sg13g2") + + assert pdk.name == "sg13g2" + assert pdk.site_core == "CoreSite" + assert pdk.tie_high_cell == "sg13g2_tiehi" + assert pdk.tie_high_port == "L_HI" + assert pdk.tie_low_cell == "sg13g2_tielo" + assert pdk.tie_low_port == "L_LO" + assert "sg13g2_buf_1" in pdk.buffers + assert "sg13g2_fill_1" in pdk.fillers + assert "sg13g2_lgcp_1" in pdk.dont_use + + +def test_get_pdk_sg13g2_case_insensitive(tmp_path, monkeypatch): + pdk_root = _create_minimal_sg13g2_pdk(tmp_path / "sg13g2") + monkeypatch.setenv("CHIPCOMPILER_SG13G2_PDK_ROOT", str(pdk_root)) + + pdk = get_pdk("SG13G2") + + assert pdk.name == "sg13g2" diff --git a/test/test_design_parameters.py b/test/test_design_parameters.py index b2fbac6..f9f5727 100644 --- a/test/test_design_parameters.py +++ b/test/test_design_parameters.py @@ -44,3 +44,67 @@ def test_ics55_template_has_dreamplace_padding_defaults(): assert parameters.data["Cell padding x"] == 600 assert parameters.data["Routability opt flag"] == 1 + + +#SG13G2 parameter tests + +def test_get_parameters_sg13g2_returns_template(): + parameters = get_parameters("sg13g2") + + assert parameters.data["PDK"] == "sg13g2" + assert parameters.data["Design"] == "" + assert parameters.data["Top module"] == "" + assert parameters.data["Clock"] == "" + assert parameters.data["Frequency max [MHz]"] == 100 + + +def test_get_parameters_sg13g2_returns_independent_copies(): + first = get_parameters("sg13g2") + second = get_parameters("sg13g2") + + first.data["Design"] = "mutated" + first.data["Floorplan"]["Tracks"][0]["x step"] = 999 + + assert second.data["Design"] == "" + assert second.data["Floorplan"]["Tracks"][0]["x step"] == 420 + + +def test_sg13g2_template_has_correct_layer_names(): + parameters = get_parameters("sg13g2") + + assert parameters.data["Bottom layer"] == "Metal2" + assert parameters.data["Top layer"] == "Metal5" + + +def test_sg13g2_template_has_correct_core_defaults(): + parameters = get_parameters("sg13g2") + + assert parameters.data["Core"]["Utilitization"] == 0.65 + assert parameters.data["Core"]["Margin"] == [17.5, 17.5] + assert parameters.data["Target density"] == 0.65 + + +def test_sg13g2_template_has_dreamplace_padding_defaults(): + parameters = get_parameters("sg13g2") + + assert parameters.data["Cell padding x"] == 0 + assert parameters.data["Routability opt flag"] == 1 + + +def test_sg13g2_template_pdn_has_two_power_nets(): + parameters = get_parameters("sg13g2") + + io_nets = parameters.data["PDN"]["IO"] + assert len(io_nets) == 2 + net_names = [n["net name"] for n in io_nets] + assert "VDD" in net_names + assert "VSS" in net_names + + +def test_get_design_parameters_sg13g2_returns_base_template(): + """SG13G2 has no design-specific overrides, so any design name returns the base template.""" + parameters = get_design_parameters("sg13g2", "gcd") + + assert parameters.data["PDK"] == "sg13g2" + assert parameters.data["Design"] == "" + assert parameters.data["Top module"] == "" diff --git a/test/test_tools.py b/test/test_tools.py index 6854165..117bd2e 100644 --- a/test/test_tools.py +++ b/test/test_tools.py @@ -51,7 +51,42 @@ def test_ics55_gcd(): engine_flow.run_steps() +def test_sg13g2_gcd(): + workspace_dir="{}/test/examples/sg13g2_gcd_tool".format(root) + + input_def = "" + input_verilog = "{}/test/fixtures/gcd/gcd.v".format(root) # RTL file + parameters=get_design_parameters("sg13g2", "gcd") + parameters.data["Design"] = "gcd" + parameters.data["Top module"] = "gcd" + parameters.data["Clock"] = "clk" + + pdk_root = "{}/ihp-sg13g2".format(root) + pdk = get_pdk("sg13g2", pdk_root=pdk_root) + + workspace = create_workspace( + directory=workspace_dir, + origin_def=input_def, + origin_verilog=input_verilog, + pdk=pdk, + parameters=parameters + ) + + + engine_flow = EngineFlow(workspace=workspace) + if not engine_flow.has_init(): + from chipcompiler.rtl2gds import build_rtl2gds_flow + steps = build_rtl2gds_flow() + for step, tool, state in steps: + engine_flow.add_step(step=step, tool=tool, state=state) + + engine_flow.create_step_workspaces() + + engine_flow.run_steps() + + if __name__ == "__main__": test_ics55_gcd() + test_sg13g2_gcd() exit(0) diff --git a/test/test_workspace.py b/test/test_workspace.py index e3c10cc..998f482 100644 --- a/test/test_workspace.py +++ b/test/test_workspace.py @@ -54,7 +54,7 @@ def test_create_workspace_persists_pdk_root_in_parameters(tmp_path): assert workspace.pdk.root == resolved_root assert workspace.parameters.data.get("PDK Root") == resolved_root - parameters_data = json.loads((workspace_dir / "parameters.json").read_text()) + parameters_data = json.loads((workspace_dir / "home" / "parameters.json").read_text()) assert parameters_data.get("PDK Root") == resolved_root @@ -80,3 +80,79 @@ def test_load_workspace_restores_pdk_root_from_parameters(tmp_path): assert loaded.pdk.root == resolved_root assert loaded.parameters.data.get("PDK Root") == resolved_root assert all(path.startswith(resolved_root) for path in loaded.pdk.libs) + + +#SG13G2 workspace tests + +def _create_minimal_sg13g2_pdk(root: Path) -> Path: + """Create the minimal SG13G2 directory tree required by get_pdk().""" + tech_path = root / "libs.ref" / "sg13g2_stdcell" / "lef" / "sg13g2_tech.lef" + tech_path.parent.mkdir(parents=True, exist_ok=True) + tech_path.write_text("VERSION 5.8 ;\n") + + lef_path = root / "libs.ref" / "sg13g2_stdcell" / "lef" / "sg13g2_stdcell.lef" + lef_path.write_text("VERSION 5.8 ;\n") + + lib_path = root / "libs.ref" / "sg13g2_stdcell" / "lib" / "sg13g2_stdcell_typ_1p20V_25C.lib" + lib_path.parent.mkdir(parents=True, exist_ok=True) + lib_path.write_text("library(test) { }\n") + + return root + + +def _sg13g2_default_parameters() -> dict: + return { + "PDK": "sg13g2", + "Design": "gcd", + "Top module": "gcd", + "Clock": "clk", + "Frequency max [MHz]": 100, + } + + +def test_create_workspace_sg13g2_persists_pdk_root_in_parameters(tmp_path): + pdk_root = _create_minimal_sg13g2_pdk(tmp_path / "sg13g2") + 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" + workspace = create_workspace( + directory=str(workspace_dir), + origin_def="", + origin_verilog=str(rtl_path), + pdk="sg13g2", + parameters=_sg13g2_default_parameters(), + pdk_root=str(pdk_root), + ) + + assert workspace is not None + resolved_root = str(pdk_root.resolve()) + assert workspace.pdk.root == resolved_root + assert workspace.parameters.data.get("PDK Root") == resolved_root + + parameters_data = json.loads((workspace_dir / "home" / "parameters.json").read_text()) + assert parameters_data.get("PDK Root") == resolved_root + + +def test_load_workspace_sg13g2_restores_pdk_root_from_parameters(tmp_path): + pdk_root = _create_minimal_sg13g2_pdk(tmp_path / "sg13g2") + 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="sg13g2", + parameters=_sg13g2_default_parameters(), + pdk_root=str(pdk_root), + ) + + loaded = load_workspace(str(workspace_dir)) + + assert loaded is not None + resolved_root = str(pdk_root.resolve()) + assert loaded.pdk.root == resolved_root + assert loaded.parameters.data.get("PDK Root") == resolved_root + assert all(path.startswith(resolved_root) for path in loaded.pdk.libs)