diff --git a/chipcompiler/data/parameter.py b/chipcompiler/data/parameter.py index a11f6985..d65e76ca 100644 --- a/chipcompiler/data/parameter.py +++ b/chipcompiler/data/parameter.py @@ -5,6 +5,148 @@ from copy import deepcopy from dataclasses import dataclass, field +# ================= 1. IHP SG13G2 (Explicit) ================= +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": "clk", + "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"]}] + } +} + +# ================= 2. GF180MCU (Explicit) ================= +GF180_PARAMETERS_TEMPLATE = { + "PDK": "gf180mcu", + "Design": "", "Top module": "", + "Die": {"Size": [], "Area": 0}, + "Core": { + "Size": [], "Area": 0, "Bounding box": "", + "Utilitization": 0.50, "Margin": [20, 20], "Aspect ratio": 1 + }, + "Max fanout": 20, + "Target density": 0.45, + "Target overflow": 0.1, + "Global right padding": 0, + "Cell padding x": 0, + "Routability opt flag": 1, + "Clock": "clk", + "Frequency max [MHz]": 100, + "Bottom layer": "Metal1", + "Top layer": "Metal5", + "Floorplan": { + "Tap distance": 0, + "Auto place pin": {"layer": "Metal3", "width": 400, "height": 800, "sides": []}, + "Tracks": [ + {"layer": "Metal1", "x start": 0, "x step": 480, "y start": 0, "y step": 480}, + {"layer": "Metal2", "x start": 0, "x step": 560, "y start": 0, "y step": 560}, + {"layer": "Metal3", "x start": 0, "x step": 480, "y start": 0, "y step": 480}, + {"layer": "Metal4", "x start": 0, "x step": 560, "y start": 0, "y step": 560}, + {"layer": "Metal5", "x start": 0, "x step": 480, "y start": 0, "y step": 480}, + ] + }, + "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.6, "offset": 0}, + "Stripe": [ + {"layer": "Metal4", "power net": "VDD", "ground net": "VSS", "width": 2.0, "pitch": 25, "offset": 1}, + {"layer": "Metal5", "power net": "VDD", "ground net": "VSS", "width": 2.0, "pitch": 25, "offset": 1} + ], + "Connect layers": [{"layers": ["Metal1", "Metal5"]}, {"layers": ["Metal4", "Metal5"]}] + } +} +# ================= 3. SKY130 (Explicit) ================= +SKY130_PARAMETERS_TEMPLATE = { + "PDK": "sky130", + "Design": "", "Top module": "", + "Die": {"Size": [], "Area": 0}, + "Core": { + "Size": [], "Area": 0, "Bounding box": "", + "Utilitization": 0.40, "Margin": [10, 10], "Aspect ratio": 1 + }, + "Max fanout": 20, + "Target density": 0.40, + "Target overflow": 0.1, + "Global right padding": 0, + "Cell padding x": 0, + "Routability opt flag": 1, + "Clock": "clk", + "Frequency max [MHz]": 100, + "Bottom layer": "met1", + "Top layer": "met5", + "Floorplan": { + "Tap distance": 0, + "Auto place pin": {"layer": "met3", "width": 300, "height": 600, "sides": []}, + "Tracks": [ + {"layer": "met1", "x start": 0, "x step": 340, "y start": 0, "y step": 340}, + {"layer": "met2", "x start": 0, "x step": 460, "y start": 0, "y step": 460}, + {"layer": "met3", "x start": 0, "x step": 340, "y start": 0, "y step": 340}, + {"layer": "met4", "x start": 0, "x step": 460, "y start": 0, "y step": 460}, + {"layer": "met5", "x start": 0, "x step": 340, "y start": 0, "y step": 340}, + ] + }, + "PDN": { + "IO": [ + {"net name": "VPWR", "direction": "INOUT", "is power": True}, + {"net name": "VGND", "direction": "INOUT", "is power": False} + ], + "Global connect": [ + {"net name": "VPWR", "instance pin name": "VPWR", "is power": True}, + {"net name": "VGND", "instance pin name": "VGND", "is power": False} + ], + "Grid": {"layer": "met1", "power net": "VPWR", "power ground": "VGND", "width": 0.48, "offset": 0}, + "Stripe": [ + {"layer": "met4", "power net": "VPWR", "ground net": "VGND", "width": 1.6, "pitch": 20, "offset": 1}, + {"layer": "met5", "power net": "VPWR", "ground net": "VGND", "width": 1.6, "pitch": 20, "offset": 1} + ], + "Connect layers": [{"layers": ["met1", "met5"]}, {"layers": ["met4", "met5"]}] + } +} ICS55_PARAMETERS_TEMPLATE = { "PDK":"ICS55", "Design":"", @@ -101,6 +243,73 @@ } } +# ================= 1. IHP SG13G2 Design Parameters ================= +SG13G2_DESIGN_PARAMETERS = { + "gcd": { + "Design": "gcd", + "Top module": "gcd", + "Clock": "clk", + "Frequency max [MHz]": 100, + }, + "aes_cipher_top": { + "Design": "aes", + "Top module": "aes_cipher_top", + "Clock": "clk", + "Frequency max [MHz]": 125, + }, + "picorv32a": { + "Design": "picorv32", + "Top module": "picorv32a", + "Clock": "clk", + "Frequency max [MHz]": 50, + } +} + +# ================= 2. GF180MCU Design Parameters ================= +GF180_DESIGN_PARAMETERS = { + "gcd": { + "Design": "gcd", + "Top module": "gcd", + "Clock": "clk", + "Frequency max [MHz]": 50, + }, + "aes_cipher_top": { + "Design": "aes", + "Top module": "aes_cipher_top", + "Clock": "clk", + "Frequency max [MHz]": 100, + }, + "picorv32a": { + "Design": "picorv32", + "Top module": "picorv32a", + "Clock": "clk", + "Frequency max [MHz]": 33, + } +} + +# ================= 3. SKY130 Design Parameters ================= +SKY130_DESIGN_PARAMETERS = { + "gcd": { + "Design": "gcd", + "Top module": "gcd", + "Clock": "clk", + "Frequency max [MHz]": 100, + }, + "aes_cipher_top": { + "Design": "aes", + "Top module": "aes_cipher_top", + "Clock": "clk", + "Frequency max [MHz]": 100, + }, + "picorv32a": { + "Design": "picorv32", + "Top module": "picorv32a", + "Clock": "clk", + "Frequency max [MHz]": 50, + } +} + + ICS55_DESIGN_PARAMETERS = { "gcd": { "Design": "gcd", @@ -142,6 +351,10 @@ def get_parameters(pdk_name: str = "", path: str = "") -> Parameters: parameters.data = deepcopy(ICS55_PARAMETERS_TEMPLATE) case "sg13g2": parameters.data = deepcopy(SG13G2_PARAMETERS_TEMPLATE) + case "gf180mcu" | "gf180": + parameters.data = deepcopy(GF180_PARAMETERS_TEMPLATE) + case "sky130": + parameters.data = deepcopy(SKY130_PARAMETERS_TEMPLATE) return parameters @@ -150,14 +363,31 @@ def get_design_parameters(pdk_name : str, design : str = "", path : str = "") -> Return parameters resolved by PDK and optional design name. """ parameters = get_parameters(pdk_name, path) - if not design or pdk_name.lower() != "ics55": + if not design: return parameters - design_info = ICS55_DESIGN_PARAMETERS.get(design.lower()) - if design_info is None: - return parameters + pdk_low = pdk_name.lower() + design_low = design.lower() + design_info = None + + # Match the PDK to its specific design parameter dictionary + match pdk_low: + case "ics55": + design_info = ICS55_DESIGN_PARAMETERS.get(design_low) + case "sg13g2" | "ihp130": + # Assuming you created SG13G2_DESIGN_PARAMETERS dictionary + design_info = SG13G2_DESIGN_PARAMETERS.get(design_low) + case "gf180mcu" | "gf180": + # Assuming you created GF180_DESIGN_PARAMETERS dictionary + design_info = GF180_DESIGN_PARAMETERS.get(design_low) + case "sky130": + # Assuming you created SKY130_DESIGN_PARAMETERS dictionary + design_info = SKY130_DESIGN_PARAMETERS.get(design_low) + + # If design info was found for that specific PDK, update the parameters + if design_info is not None: + parameters.data.update(design_info) - parameters.data.update(design_info) return parameters def update_parameters(parameters_src : dict, parameters_target : dict) -> dict: diff --git a/chipcompiler/data/pdk.py b/chipcompiler/data/pdk.py index f91734f0..4ecd295a 100644 --- a/chipcompiler/data/pdk.py +++ b/chipcompiler/data/pdk.py @@ -52,19 +52,23 @@ def validate(self) -> None: msg = "PDK validation failed:\n " + "\n ".join(errors) logger.error(msg) raise ValueError(msg) - + def get_pdk(pdk_name : str, pdk_root: str = "") -> PDK: - """ - Return the PDK instance based on the given pdk name. - """ 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) + elif pdk_name_normalized == "sky130": + pdk = PDK_SKY130(pdk_root=pdk_root) + elif pdk_name_normalized == "gf180mcu": + pdk = PDK_GF180MCU(pdk_root=pdk_root) else: pdk = PDK(name=pdk_name_normalized) - pdk.validate() + + if pdk.name in ("ics55", "sg13g2", "sky130", "gf180mcu"): + pdk.validate() + return pdk def PDK_ICS55(pdk_root: str = "") -> PDK: @@ -81,7 +85,7 @@ def PDK_ICS55(pdk_root: str = "") -> PDK: )) stdcell_dir = "{}/IP/STD_cell/ics55_LLSC_H7C_V1p10C100".format(resolved_root) - tech_path = "{}/prtech/techLEF/N551P6M_ecos.lef".format(resolved_root) + tech_path = "{}/prtech/techLEF/N551P6M.lef".format(resolved_root) lef_paths = [ "{}/ics55_LLSC_H7CR/lef/ics55_LLSC_H7CR_ecos.lef".format(stdcell_dir), "{}/ics55_LLSC_H7CL/lef/ics55_LLSC_H7CL_ecos.lef".format(stdcell_dir) @@ -229,4 +233,112 @@ def PDK_SG13G2(pdk_root: str = "") -> PDK: ] ) + return pdk + +def PDK_GF180MCU(pdk_root: str = "") -> PDK: + resolved_root = os.path.abspath(os.path.expanduser( + (pdk_root or "").strip() + or os.environ.get("CHIPCOMPILER_GF180_PDK_ROOT", "").strip() + or os.environ.get("GF180_PDK_ROOT", "").strip() + )) + + # Standard paths for pruned GF180 distributions + tech_path = "{}/lef/gf180mcu_7t_tech.lef".format(resolved_root) + lef_paths = [ + "{}/lef/gf180mcu_fd_sc_mcu7t5v0.lef".format(resolved_root) + ] + lib_paths = [ + "{}/lib/gf180mcu_fd_sc_mcu7t5v0__ss_125C_1p65V.lib".format(resolved_root) + ] + + pdk = PDK( + name="gf180mcu", + 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="GF_018_7_5_MC_TYP", + tap_cell="gf180mcu_fd_sc_mcu7t5v0__filltap", + end_cap="gf180mcu_fd_sc_mcu7t5v0__endcap", + buffers=[ + "gf180mcu_fd_sc_mcu7t5v0__buf_1", + "gf180mcu_fd_sc_mcu7t5v0__buf_2", + "gf180mcu_fd_sc_mcu7t5v0__buf_4", + "gf180mcu_fd_sc_mcu7t5v0__buf_8", + "gf180mcu_fd_sc_mcu7t5v0__buf_16" + ], + fillers=[ + "gf180mcu_fd_sc_mcu7t5v0__fill_1", + "gf180mcu_fd_sc_mcu7t5v0__fill_2", + "gf180mcu_fd_sc_mcu7t5v0__fill_4", + "gf180mcu_fd_sc_mcu7t5v0__fill_8", + "gf180mcu_fd_sc_mcu7t5v0__fill_16", + "gf180mcu_fd_sc_mcu7t5v0__fill_32" + ], + tie_high_cell="gf180mcu_fd_sc_mcu7t5v0__tieh", + tie_high_port="Z", + tie_low_cell="gf180mcu_fd_sc_mcu7t5v0__tiel", + tie_low_port="ZN", + dont_use=[ + "gf180mcu_fd_sc_mcu7t5v0__ant*", + "gf180mcu_fd_sc_mcu7t5v0__lat*", + "gf180mcu_fd_sc_mcu7t5v0__dff*" # Example: if you want to restrict DFFs + ] + ) + + return pdk + +def PDK_SKY130(pdk_root: str = "") -> PDK: + resolved_root = os.path.abspath(os.path.expanduser( + (pdk_root or "").strip() + or os.environ.get("CHIPCOMPILER_SKY130_PDK_ROOT", "").strip() + or os.environ.get("SKY130_PDK_ROOT", "").strip() + )) + + # Standard paths for sky130 high-density (hd) library + tech_path = "{}/lef/sky130_fd_sc_hd.tech.lef".format(resolved_root) + lef_paths = [ + "{}/lef/sky130_fd_sc_hd.lef".format(resolved_root) + ] + lib_paths = [ + "{}/lib/sky130_fd_sc_hd__tt_025C_1v80.lib".format(resolved_root) + ] + + pdk = PDK( + name="sky130", + 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="unithd", # Standard site name for sky130_fd_sc_hd + tap_cell="sky130_fd_sc_hd__filltap_2", + end_cap="sky130_fd_sc_hd__decap_3", + buffers=[ + "sky130_fd_sc_hd__buf_1", + "sky130_fd_sc_hd__buf_2", + "sky130_fd_sc_hd__buf_4", + "sky130_fd_sc_hd__buf_8", + "sky130_fd_sc_hd__buf_12", + "sky130_fd_sc_hd__buf_16" + ], + fillers=[ + "sky130_fd_sc_hd__fill_1", + "sky130_fd_sc_hd__fill_2", + "sky130_fd_sc_hd__fill_4", + "sky130_fd_sc_hd__fill_8" + ], + # Sky130 uses the 'conb' (connect bias) cell for both tie high and low + tie_high_cell="sky130_fd_sc_hd__conb_1", + tie_high_port="HI", + tie_low_cell="sky130_fd_sc_hd__conb_1", + tie_low_port="LO", + dont_use=[ + "sky130_fd_sc_hd__ant*", # Exclude antenna cells from logic + "sky130_fd_sc_hd__lpflow*", # Exclude power management cells for basic flow + "sky130_fd_sc_hd__clkbuf_1" # Usually too small for clock trees + ] + ) + return pdk \ No newline at end of file diff --git a/test/test_data_pdk.py b/test/test_data_pdk.py index 467edbcd..80d7bf67 100644 --- a/test/test_data_pdk.py +++ b/test/test_data_pdk.py @@ -9,7 +9,7 @@ def _create_minimal_ics55_pdk(root: Path) -> Path: """Create the minimal ICS55 directory tree required by get_pdk().""" - tech_path = root / "prtech" / "techLEF" / "N551P6M_ecos.lef" + tech_path = root / "prtech" / "techLEF" / "N551P6M.lef" tech_path.parent.mkdir(parents=True, exist_ok=True) tech_path.write_text("VERSION 5.8 ;\n") @@ -151,3 +151,137 @@ def test_get_pdk_sg13g2_case_insensitive(tmp_path, monkeypatch): pdk = get_pdk("SG13G2") assert pdk.name == "sg13g2" + + +def _create_minimal_gf180mcu_pdk(root: Path) -> Path: + """Create the minimal GF180MCU directory tree required by get_pdk().""" + tech_path = root / "lef" / "gf180mcu_7t_tech.lef" + tech_path.parent.mkdir(parents=True, exist_ok=True) + tech_path.write_text("VERSION 5.8 ;\n") + + lef_path = root / "lef" / "gf180mcu_fd_sc_mcu7t5v0.lef" + lef_path.write_text("VERSION 5.8 ;\n") + + lib_path = root / "lib" / "gf180mcu_fd_sc_mcu7t5v0__ss_125C_1p65V.lib" + lib_path.parent.mkdir(parents=True, exist_ok=True) + lib_path.write_text("library(test) { }\n") + + return root + + +def _create_minimal_sky130_pdk(root: Path) -> Path: + """Create the minimal SKY130 directory tree required by get_pdk().""" + tech_path = root / "lef" / "sky130_fd_sc_hd.tech.lef" + tech_path.parent.mkdir(parents=True, exist_ok=True) + tech_path.write_text("VERSION 5.8 ;\n") + + lef_path = root / "lef" / "sky130_fd_sc_hd.lef" + lef_path.write_text("VERSION 5.8 ;\n") + + lib_path = root / "lib" / "sky130_fd_sc_hd__tt_025C_1v80.lib" + lib_path.parent.mkdir(parents=True, exist_ok=True) + lib_path.write_text("library(test) { }\n") + + return root + + +def test_get_pdk_gf180mcu_prefers_explicit_root_over_env(tmp_path, monkeypatch): + explicit_root = _create_minimal_gf180mcu_pdk(tmp_path / "explicit") + env_root = _create_minimal_gf180mcu_pdk(tmp_path / "env") + monkeypatch.setenv("CHIPCOMPILER_GF180_PDK_ROOT", str(env_root)) + + pdk = get_pdk("gf180mcu", 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_gf180mcu_uses_namespaced_env(tmp_path, monkeypatch): + env_root = _create_minimal_gf180mcu_pdk(tmp_path / "env") + monkeypatch.setenv("CHIPCOMPILER_GF180_PDK_ROOT", str(env_root)) + monkeypatch.delenv("GF180_PDK_ROOT", raising=False) + + pdk = get_pdk("gf180mcu") + + assert pdk.root == str(env_root.resolve()) + + +def test_get_pdk_gf180mcu_uses_legacy_env_when_namespaced_missing(tmp_path, monkeypatch): + legacy_root = _create_minimal_gf180mcu_pdk(tmp_path / "legacy") + monkeypatch.delenv("CHIPCOMPILER_GF180_PDK_ROOT", raising=False) + monkeypatch.setenv("GF180_PDK_ROOT", str(legacy_root)) + + pdk = get_pdk("gf180mcu") + + assert pdk.root == str(legacy_root.resolve()) + + +def test_get_pdk_gf180mcu_raises_on_missing_pdk_files(tmp_path, monkeypatch): + invalid_root = tmp_path / "broken_gf180mcu" + invalid_root.mkdir(parents=True, exist_ok=True) + monkeypatch.setenv("CHIPCOMPILER_GF180_PDK_ROOT", str(invalid_root)) + + with pytest.raises(ValueError, match="PDK validation failed"): + get_pdk("gf180mcu") + + +def test_get_pdk_gf180mcu_case_insensitive(tmp_path, monkeypatch): + pdk_root = _create_minimal_gf180mcu_pdk(tmp_path / "gf180mcu") + monkeypatch.setenv("CHIPCOMPILER_GF180_PDK_ROOT", str(pdk_root)) + + pdk = get_pdk("GF180MCU") + + assert pdk.name == "gf180mcu" + + +def test_get_pdk_sky130_prefers_explicit_root_over_env(tmp_path, monkeypatch): + explicit_root = _create_minimal_sky130_pdk(tmp_path / "explicit") + env_root = _create_minimal_sky130_pdk(tmp_path / "env") + monkeypatch.setenv("CHIPCOMPILER_SKY130_PDK_ROOT", str(env_root)) + + pdk = get_pdk("sky130", 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_sky130_uses_namespaced_env(tmp_path, monkeypatch): + env_root = _create_minimal_sky130_pdk(tmp_path / "env") + monkeypatch.setenv("CHIPCOMPILER_SKY130_PDK_ROOT", str(env_root)) + monkeypatch.delenv("SKY130_PDK_ROOT", raising=False) + + pdk = get_pdk("sky130") + + assert pdk.root == str(env_root.resolve()) + + +def test_get_pdk_sky130_uses_legacy_env_when_namespaced_missing(tmp_path, monkeypatch): + legacy_root = _create_minimal_sky130_pdk(tmp_path / "legacy") + monkeypatch.delenv("CHIPCOMPILER_SKY130_PDK_ROOT", raising=False) + monkeypatch.setenv("SKY130_PDK_ROOT", str(legacy_root)) + + pdk = get_pdk("sky130") + + assert pdk.root == str(legacy_root.resolve()) + + +def test_get_pdk_sky130_raises_on_missing_pdk_files(tmp_path, monkeypatch): + invalid_root = tmp_path / "broken_sky130" + invalid_root.mkdir(parents=True, exist_ok=True) + monkeypatch.setenv("CHIPCOMPILER_SKY130_PDK_ROOT", str(invalid_root)) + + with pytest.raises(ValueError, match="PDK validation failed"): + get_pdk("sky130") + + +def test_get_pdk_sky130_case_insensitive(tmp_path, monkeypatch): + pdk_root = _create_minimal_sky130_pdk(tmp_path / "sky130") + monkeypatch.setenv("CHIPCOMPILER_SKY130_PDK_ROOT", str(pdk_root)) + + pdk = get_pdk("SKY130") + + assert pdk.name == "sky130" diff --git a/test/test_design_parameters.py b/test/test_design_parameters.py index 950ce826..1642e0bb 100644 --- a/test/test_design_parameters.py +++ b/test/test_design_parameters.py @@ -101,10 +101,105 @@ def test_sg13g2_template_pdn_has_two_power_nets(): 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.""" +def test_get_design_parameters_sg13g2_returns_design_overrides(): parameters = get_design_parameters("sg13g2", "gcd") assert parameters.data["PDK"] == "sg13g2" + assert parameters.data["Design"] == "gcd" + assert parameters.data["Top module"] == "gcd" + assert parameters.data["Clock"] == "clk" + assert parameters.data["Frequency max [MHz]"] == 100 + + +def test_get_parameters_gf180mcu_returns_template(): + parameters = get_parameters("gf180mcu") + + assert parameters.data["PDK"] == "gf180mcu" + assert parameters.data["Design"] == "" + assert parameters.data["Top module"] == "" + assert parameters.data["Bottom layer"] == "Metal1" + assert parameters.data["Top layer"] == "Metal5" + + +def test_get_parameters_gf180mcu_returns_independent_copies(): + first = get_parameters("gf180mcu") + second = get_parameters("gf180mcu") + + first.data["Design"] = "mutated" + first.data["Floorplan"]["Tracks"][0]["x step"] = 999 + + assert second.data["Design"] == "" + assert second.data["Floorplan"]["Tracks"][0]["x step"] == 480 + + +def test_gf180mcu_template_has_correct_core_defaults(): + parameters = get_parameters("gf180mcu") + + assert parameters.data["Core"]["Utilitization"] == 0.50 + assert parameters.data["Core"]["Margin"] == [20, 20] + assert parameters.data["Target density"] == 0.45 + + +def test_gf180mcu_template_pdn_has_two_power_nets(): + parameters = get_parameters("gf180mcu") + + io_nets = parameters.data["PDN"]["IO"] + 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_gf180mcu_gcd_overrides_fields(): + parameters = get_design_parameters("gf180mcu", "gcd") + + assert parameters.data["Design"] == "gcd" + assert parameters.data["Top module"] == "gcd" + assert parameters.data["Clock"] == "clk" + assert parameters.data["Frequency max [MHz]"] == 50 + + +def test_get_parameters_sky130_returns_template(): + parameters = get_parameters("sky130") + + assert parameters.data["PDK"] == "sky130" assert parameters.data["Design"] == "" assert parameters.data["Top module"] == "" + assert parameters.data["Bottom layer"] == "met1" + assert parameters.data["Top layer"] == "met5" + + +def test_get_parameters_sky130_returns_independent_copies(): + first = get_parameters("sky130") + second = get_parameters("sky130") + + first.data["Design"] = "mutated" + first.data["Floorplan"]["Tracks"][0]["x step"] = 999 + + assert second.data["Design"] == "" + assert second.data["Floorplan"]["Tracks"][0]["x step"] == 340 + + +def test_sky130_template_has_correct_core_defaults(): + parameters = get_parameters("sky130") + + assert parameters.data["Core"]["Utilitization"] == 0.40 + assert parameters.data["Core"]["Margin"] == [10, 10] + assert parameters.data["Target density"] == 0.40 + + +def test_sky130_template_pdn_has_two_power_nets(): + parameters = get_parameters("sky130") + + io_nets = parameters.data["PDN"]["IO"] + net_names = [n["net name"] for n in io_nets] + assert "VPWR" in net_names + assert "VGND" in net_names + + +def test_get_design_parameters_sky130_gcd_overrides_fields(): + parameters = get_design_parameters("sky130", "gcd") + + assert parameters.data["Design"] == "gcd" + assert parameters.data["Top module"] == "gcd" + assert parameters.data["Clock"] == "clk" + assert parameters.data["Frequency max [MHz]"] == 100 diff --git a/test/test_tools.py b/test/test_tools.py index 58ec0a99..c27950d9 100644 --- a/test/test_tools.py +++ b/test/test_tools.py @@ -3,6 +3,7 @@ import sys import os +from pathlib import Path current_dir = os.path.split(os.path.abspath(__file__))[0] root = current_dir.rsplit('/', 1)[0] @@ -24,6 +25,37 @@ EngineFlow ) + +def _create_minimal_gf180mcu_pdk(root: Path) -> Path: + tech_path = root / "lef" / "gf180mcu_7t_tech.lef" + tech_path.parent.mkdir(parents=True, exist_ok=True) + tech_path.write_text("VERSION 5.8 ;\n") + + lef_path = root / "lef" / "gf180mcu_fd_sc_mcu7t5v0.lef" + lef_path.write_text("VERSION 5.8 ;\n") + + lib_path = root / "lib" / "gf180mcu_fd_sc_mcu7t5v0__ss_125C_1p65V.lib" + lib_path.parent.mkdir(parents=True, exist_ok=True) + lib_path.write_text("library(test) { }\n") + + return root + + +def _create_minimal_sky130_pdk(root: Path) -> Path: + tech_path = root / "lef" / "sky130_fd_sc_hd.tech.lef" + tech_path.parent.mkdir(parents=True, exist_ok=True) + tech_path.write_text("VERSION 5.8 ;\n") + + lef_path = root / "lef" / "sky130_fd_sc_hd.lef" + lef_path.write_text("VERSION 5.8 ;\n") + + lib_path = root / "lib" / "sky130_fd_sc_hd__tt_025C_1v80.lib" + lib_path.parent.mkdir(parents=True, exist_ok=True) + lib_path.write_text("library(test) { }\n") + + return root + + def test_ics55_gcd(): workspace_dir="{}/test/examples/ics55_gcd_tool".format(root) @@ -87,6 +119,62 @@ def test_sg13g2_gcd(): engine_flow.run_steps() +def test_gf180mcu_gcd(tmp_path): + workspace_dir = tmp_path / "gf180mcu_gcd_tool" + input_def = "" + input_verilog = Path(root) / "test" / "fixtures" / "gcd" / "gcd.v" + parameters = get_design_parameters("gf180mcu", "gcd") + + pdk_root = _create_minimal_gf180mcu_pdk(tmp_path / "gf180mcu") + pdk = get_pdk("gf180mcu", pdk_root=str(pdk_root)) + + workspace = create_workspace( + directory=str(workspace_dir), + origin_def=input_def, + origin_verilog=str(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() + + +def test_sky130_gcd(tmp_path): + workspace_dir = tmp_path / "sky130_gcd_tool" + input_def = "" + input_verilog = Path(root) / "test" / "fixtures" / "gcd" / "gcd.v" + parameters = get_design_parameters("sky130", "gcd") + + pdk_root = _create_minimal_sky130_pdk(tmp_path / "sky130") + pdk = get_pdk("sky130", pdk_root=str(pdk_root)) + + workspace = create_workspace( + directory=str(workspace_dir), + origin_def=input_def, + origin_verilog=str(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() diff --git a/test/test_workspace.py b/test/test_workspace.py index f977c4b3..10793c99 100644 --- a/test/test_workspace.py +++ b/test/test_workspace.py @@ -7,7 +7,7 @@ def _create_minimal_ics55_pdk(root: Path) -> Path: - tech_path = root / "prtech" / "techLEF" / "N551P6M_ecos.lef" + tech_path = root / "prtech" / "techLEF" / "N551P6M.lef" tech_path.parent.mkdir(parents=True, exist_ok=True) tech_path.write_text("VERSION 5.8 ;\n") @@ -156,3 +156,155 @@ def test_load_workspace_sg13g2_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) + + +# GF180MCU workspace tests + +def _create_minimal_gf180mcu_pdk(root: Path) -> Path: + """Create the minimal GF180MCU directory tree required by get_pdk().""" + tech_path = root / "lef" / "gf180mcu_7t_tech.lef" + tech_path.parent.mkdir(parents=True, exist_ok=True) + tech_path.write_text("VERSION 5.8 ;\n") + + lef_path = root / "lef" / "gf180mcu_fd_sc_mcu7t5v0.lef" + lef_path.write_text("VERSION 5.8 ;\n") + + lib_path = root / "lib" / "gf180mcu_fd_sc_mcu7t5v0__ss_125C_1p65V.lib" + lib_path.parent.mkdir(parents=True, exist_ok=True) + lib_path.write_text("library(test) { }\n") + + return root + + +def _gf180mcu_default_parameters() -> dict: + return { + "PDK": "gf180mcu", + "Design": "gcd", + "Top module": "gcd", + "Clock": "clk", + "Frequency max [MHz]": 50, + } + + +def test_create_workspace_gf180mcu_persists_pdk_root_in_parameters(tmp_path): + pdk_root = _create_minimal_gf180mcu_pdk(tmp_path / "gf180mcu") + 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="gf180mcu", + parameters=_gf180mcu_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_gf180mcu_restores_pdk_root_from_parameters(tmp_path): + pdk_root = _create_minimal_gf180mcu_pdk(tmp_path / "gf180mcu") + 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="gf180mcu", + parameters=_gf180mcu_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) + + +# SKY130 workspace tests + +def _create_minimal_sky130_pdk(root: Path) -> Path: + """Create the minimal SKY130 directory tree required by get_pdk().""" + tech_path = root / "lef" / "sky130_fd_sc_hd.tech.lef" + tech_path.parent.mkdir(parents=True, exist_ok=True) + tech_path.write_text("VERSION 5.8 ;\n") + + lef_path = root / "lef" / "sky130_fd_sc_hd.lef" + lef_path.write_text("VERSION 5.8 ;\n") + + lib_path = root / "lib" / "sky130_fd_sc_hd__tt_025C_1v80.lib" + lib_path.parent.mkdir(parents=True, exist_ok=True) + lib_path.write_text("library(test) { }\n") + + return root + + +def _sky130_default_parameters() -> dict: + return { + "PDK": "sky130", + "Design": "gcd", + "Top module": "gcd", + "Clock": "clk", + "Frequency max [MHz]": 100, + } + + +def test_create_workspace_sky130_persists_pdk_root_in_parameters(tmp_path): + pdk_root = _create_minimal_sky130_pdk(tmp_path / "sky130") + 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="sky130", + parameters=_sky130_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_sky130_restores_pdk_root_from_parameters(tmp_path): + pdk_root = _create_minimal_sky130_pdk(tmp_path / "sky130") + 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="sky130", + parameters=_sky130_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)