diff --git a/.github/workflows/mypy-check.yaml b/.github/workflows/mypy-check.yaml index 03d3426..36cee79 100644 --- a/.github/workflows/mypy-check.yaml +++ b/.github/workflows/mypy-check.yaml @@ -34,4 +34,4 @@ jobs: - name: Run mypy run: | - mypy cells.py config.py configure.py documentation.py git_utils.py markdown_utils.py project.py project_info.py rom.py shuttle.py --check-untyped-defs + mypy *.py --check-untyped-defs diff --git a/.github/workflows/test-precheck.yaml b/.github/workflows/test-precheck.yaml index 044ffcd..8007452 100644 --- a/.github/workflows/test-precheck.yaml +++ b/.github/workflows/test-precheck.yaml @@ -27,7 +27,7 @@ jobs: run: pip install -r requirements.txt - name: Install Sky130 PDK - uses: TinyTapeout/volare-action@v2 + uses: TinyTapeout/ciel-action@v1 with: pdk_name: sky130 pdk_version: ${{ env.PDK_VERSION }} diff --git a/cells.py b/cells.py deleted file mode 100644 index 724c1e6..0000000 --- a/cells.py +++ /dev/null @@ -1,44 +0,0 @@ -import json -import os -from typing import Dict, List, Optional, TypedDict - - -class Port(TypedDict): - kind: str - name: str - direction: str - description: str - - -class Sky130Cell(TypedDict): - description: str - file_prefix: str - library: str - name: str - parameters: List[str] - ports: List[Port] - type: str - verilog_name: str - equation: Optional[str] - - -class IHPCell(TypedDict): - description: str - doc_name: str - doc_ref: int - - -Sky130Cells = Dict[str, Sky130Cell] -IHPCells = Dict[str, IHPCell] - - -def load_sky130_cells() -> Sky130Cells: - script_dir = os.path.dirname(os.path.realpath(__file__)) - with open(os.path.join(script_dir, "cells.json")) as fh: - return json.load(fh) - - -def load_ihp_cells() -> IHPCells: - script_dir = os.path.dirname(os.path.realpath(__file__)) - with open(os.path.join(script_dir, "ihp/cells.json")) as fh: - return json.load(fh) diff --git a/config.py b/config.py index 566c094..69f4fa2 100644 --- a/config.py +++ b/config.py @@ -1,5 +1,7 @@ from typing import TypedDict +from tech import TechName + class Config(TypedDict): """TypedDict for Tiny Tapeout's config.yaml file.""" @@ -11,4 +13,4 @@ class Config(TypedDict): top_level_macro: str powered_netlists: bool no_power_gating: bool - pdk: str + pdk: TechName diff --git a/logo.py b/logo.py index 82f3243..82684c1 100755 --- a/logo.py +++ b/logo.py @@ -21,7 +21,7 @@ class TechDef(TypedDict): tech: Dict[str, TechDef] = { - "sky130": { + "sky130A": { "PRBOUNDARY_LAYER": 235, # prBoundary "PRBOUNDARY_DATATYPE": 4, # .boundary "LOGO_LAYER": 71, # Metal4 @@ -149,7 +149,7 @@ if __name__ == "__main__": parser = argparse.ArgumentParser(description="TT logo generator") parser.add_argument( - "--pdk", type=str, default="sky130", choices=["sky130", "ihp-sg13g2"] + "--pdk", type=str, default="sky130A", choices=["sky130A", "ihp-sg13g2"] ) parser.add_argument("--top", action="store_true") parser.add_argument("--bottom", action="store_true") diff --git a/precheck/precheck.py b/precheck/precheck.py index f7a1351..394f650 100644 --- a/precheck/precheck.py +++ b/precheck/precheck.py @@ -293,9 +293,9 @@ def main(): else: raise PrecheckFailure("Layout file extension is neither .gds nor .gds.br") - if args.tech not in tech_names: - raise PrecheckFailure(f"Invalid tech: {args.tech}") tech = args.tech + if tech not in tech_names: + raise PrecheckFailure(f"Invalid tech: {tech}") yaml_dir = os.path.dirname(args.gds) while not os.path.exists(f"{yaml_dir}/info.yaml"): @@ -317,15 +317,16 @@ def main(): pinout = yaml_data.get("pinout", {}) if uses_3v3 and not is_analog: raise PrecheckFailure("Projects with 3v3 power need at least one analog pin") + def_root = f"../tech/{tech}/def" if is_analog: if uses_3v3: - template_def = f"../def/analog/tt_analog_{tiles}_3v3.def" + template_def = f"{def_root}/analog/tt_analog_{tiles}_3v3.def" else: - template_def = f"../def/analog/tt_analog_{tiles}.def" + template_def = f"{def_root}/analog/tt_analog_{tiles}.def" elif tech == "ihp-sg13g2": - template_def = f"../ihp/def/tt_block_{tiles}_pgvdd.def" + template_def = f"{def_root}/tt_block_{tiles}_pgvdd.def" else: - template_def = f"../def/tt_block_{tiles}_pg.def" + template_def = f"{def_root}/tt_block_{tiles}_pg.def" logging.info(f"using def template {template_def}") lef_file = gds_stem + ".lef" @@ -335,22 +336,22 @@ def main(): { "name": "Magic DRC", "check": lambda: magic_drc(gds_file, top_module), - "tech": "sky130", + "tech": "sky130A", }, { "name": "KLayout FEOL", "check": lambda: klayout_drc(gds_file, "feol"), - "tech": "sky130", + "tech": "sky130A", }, { "name": "KLayout BEOL", "check": lambda: klayout_drc(gds_file, "beol"), - "tech": "sky130", + "tech": "sky130A", }, { "name": "KLayout offgrid", "check": lambda: klayout_drc(gds_file, "offgrid"), - "tech": "sky130", + "tech": "sky130A", }, { "name": "KLayout pin label overlapping drawing", @@ -380,7 +381,7 @@ def main(): { "name": "Power pin check", "check": lambda: power_pin_check(verilog_file, lef_file, uses_3v3), - "tech": "sky130", + "tech": "sky130A", }, {"name": "Layer check", "check": lambda: layer_check(gds_file, tech)}, {"name": "Cell name check", "check": lambda: cell_name_check(gds_file)}, diff --git a/precheck/tech_data.py b/precheck/tech_data.py index 2366343..031c0eb 100644 --- a/precheck/tech_data.py +++ b/precheck/tech_data.py @@ -1,5 +1,5 @@ # fmt: off -valid_layers_sky130 = { +valid_layers_sky130A = { (10, 0), (11, 0), (11, 20), (11, 44), (17, 0), (18, 20), (20, 0), (21, 0), (22, 0), (22, 20), (22, 21), (22, 22), (22, 23), (22, 24), (23, 0), (23, 28), (25, 0), (25, 42), (25, 43), (25, 44), (26, 20), (26, 21), (26, 22), (27, 0), @@ -176,24 +176,24 @@ valid_layers_ihp_sg13g2 = [ ] # layers used for the analog pin check -layer_map_sky130 = { +layer_map_sky130A = { "met4": (71, 20), "via3": (70, 44), } -def analog_pin_pos_sky130(pin_number: int, uses_3v3: bool): +def analog_pin_pos_sky130A(pin_number: int, uses_3v3: bool): return 151.81 - 19.32 * pin_number - (15.64 if uses_3v3 else 0) -valid_layers = {"sky130": valid_layers_sky130, "ihp-sg13g2": valid_layers_ihp_sg13g2} -layer_map = {"sky130": layer_map_sky130} -analog_pin_pos = {"sky130": analog_pin_pos_sky130} -lyp_filename = {"sky130": "sky130A.lyp", "ihp-sg13g2": "sg13g2.lyp"} +valid_layers = {"sky130A": valid_layers_sky130A, "ihp-sg13g2": valid_layers_ihp_sg13g2} +layer_map = {"sky130A": layer_map_sky130A} +analog_pin_pos = {"sky130A": analog_pin_pos_sky130A} +lyp_filename = {"sky130A": "sky130A.lyp", "ihp-sg13g2": "sg13g2.lyp"} # TODO: read layer numbers from lyp file valid_lef_port_layers = { - "sky130": { + "sky130A": { "met1.pin": (68, 16), "met2.pin": (69, 16), "met3.pin": (70, 16), @@ -208,7 +208,7 @@ valid_lef_port_layers = { }, } forbidden_layers = { - "sky130": ["met5.drawing", "met5.pin", "met5.label"], + "sky130A": ["met5.drawing", "met5.pin", "met5.label"], "ihp-sg13g2": [ "TopMetal1.drawing", "TopMetal1.pin", @@ -219,8 +219,8 @@ forbidden_layers = { ], } power_pins_layer = { - "sky130": "met4", + "sky130A": "met4", "ihp-sg13g2": "Metal5", } -tech_names = ["sky130", "ihp-sg13g2"] +tech_names = ["sky130A", "ihp-sg13g2"] diff --git a/precheck/test_precheck.py b/precheck/test_precheck.py index e745bd7..6e043b8 100644 --- a/precheck/test_precheck.py +++ b/precheck/test_precheck.py @@ -251,7 +251,7 @@ def generate_analog_example( tcl_append( f""" - def read ../def/analog/tt_analog_1x2.def + def read ../tech/sky130A/def/analog/tt_analog_1x2.def cellname rename tt_um_template {toplevel} """ ) @@ -477,14 +477,14 @@ def test_klayout_beol_fail(gds_fail_met1_poly: str): def test_klayout_checks_pass(gds_valid: str): - precheck.klayout_checks(gds_valid, "TEST_valid", "sky130") + precheck.klayout_checks(gds_valid, "TEST_valid", "sky130A") def test_klayout_checks_fail_metal5(gds_fail_metal5_poly: str): with pytest.raises( precheck.PrecheckFailure, match=r"Forbidden layer met5\.drawing found in .+" ): - precheck.klayout_checks(gds_fail_metal5_poly, "TEST_met5_error", "sky130") + precheck.klayout_checks(gds_fail_metal5_poly, "TEST_met5_error", "sky130A") def test_klayout_checks_fail_pr_boundary(gds_no_pr_boundary: str): @@ -492,7 +492,7 @@ def test_klayout_checks_fail_pr_boundary(gds_no_pr_boundary: str): precheck.PrecheckFailure, match=r"prBoundary.boundary \(235/4\) layer not found in .+", ): - precheck.klayout_checks(gds_no_pr_boundary, "TEST_no_prboundary", "sky130") + precheck.klayout_checks(gds_no_pr_boundary, "TEST_no_prboundary", "sky130A") def test_klayout_top_module_name(gds_invalid_macro_name: str): @@ -501,7 +501,7 @@ def test_klayout_top_module_name(gds_invalid_macro_name: str): match="Top macro name mismatch: expected TEST_invalid_macro_name, got wrong_name", ): precheck.klayout_checks( - gds_invalid_macro_name, "TEST_invalid_macro_name", "sky130" + gds_invalid_macro_name, "TEST_invalid_macro_name", "sky130A" ) @@ -516,7 +516,7 @@ def test_klayout_zero_area_drc_fail(gds_zero_area: str): def test_shapes_outside_area(gds_shapes_outside_area: str): with pytest.raises(precheck.PrecheckFailure, match="Shapes outside project area"): - precheck.boundary_check(gds_shapes_outside_area, "sky130") + precheck.boundary_check(gds_shapes_outside_area, "sky130A") def test_wrong_power_pins_1(verilog_lef_wrong_power_pins: tuple[str, str]): @@ -533,7 +533,7 @@ def test_wrong_power_pins_2(verilog_lef_wrong_power_pins: tuple[str, str]): def test_invalid_layer(gds_invalid_layer: str): with pytest.raises(precheck.PrecheckFailure, match="Invalid layers in GDS"): - precheck.layer_check(gds_invalid_layer, "sky130") + precheck.layer_check(gds_invalid_layer, "sky130A") def test_invalid_cell_name(gds_invalid_cell_name: str): @@ -557,10 +557,10 @@ def test_pin_analog_example(gds_lef_analog_example: tuple[str, str]): precheck.pin_check( gds_file, lef_file, - "../def/analog/tt_analog_1x2.def", + "../tech/sky130A/def/analog/tt_analog_1x2.def", "TEST_analog_example", False, - "sky130", + "sky130A", ) @@ -569,10 +569,10 @@ def test_pin_analog_power_compat(gds_lef_analog_power_compat: tuple[str, str]): precheck.pin_check( gds_file, lef_file, - "../def/analog/tt_analog_1x2.def", + "../tech/sky130A/def/analog/tt_analog_1x2.def", "TEST_analog_power_compat", False, - "sky130", + "sky130A", ) @@ -585,10 +585,10 @@ def test_pin_analog_wrong_vgnd(gds_lef_analog_wrong_vgnd: tuple[str, str]): precheck.pin_check( gds_file, lef_file, - "../def/analog/tt_analog_1x2.def", + "../tech/sky130A/def/analog/tt_analog_1x2.def", "TEST_analog_wrong_vgnd", False, - "sky130", + "sky130A", ) @@ -601,10 +601,10 @@ def test_pin_analog_overlapping_vgnd(gds_lef_analog_overlapping_vgnd: tuple[str, precheck.pin_check( gds_file, lef_file, - "../def/analog/tt_analog_1x2.def", + "../tech/sky130A/def/analog/tt_analog_1x2.def", "TEST_analog_overlapping_vgnd", False, - "sky130", + "sky130A", ) @@ -613,10 +613,10 @@ def test_pin_analog_compound_vgnd(gds_lef_analog_compound_vgnd: tuple[str, str]) precheck.pin_check( gds_file, lef_file, - "../def/analog/tt_analog_1x2.def", + "../tech/sky130A/def/analog/tt_analog_1x2.def", "TEST_analog_compound_vgnd", False, - "sky130", + "sky130A", ) @@ -625,10 +625,10 @@ def test_pin_analog_example_3v3(gds_lef_analog_example_3v3: tuple[str, str]): precheck.pin_check( gds_file, lef_file, - "../def/analog/tt_analog_1x2.def", + "../tech/sky130A/def/analog/tt_analog_1x2.def", "TEST_analog_example_3v3", True, - "sky130", + "sky130A", ) @@ -641,10 +641,10 @@ def test_pin_analog_3v3_mismatch1(gds_lef_analog_example: tuple[str, str]): precheck.pin_check( gds_file, lef_file, - "../def/analog/tt_analog_1x2.def", + "../tech/sky130A/def/analog/tt_analog_1x2.def", "TEST_analog_example", True, - "sky130", + "sky130A", ) @@ -657,17 +657,17 @@ def test_pin_analog_3v3_mismatch2(gds_lef_analog_example_3v3: tuple[str, str]): precheck.pin_check( gds_file, lef_file, - "../def/analog/tt_analog_1x2.def", + "../tech/sky130A/def/analog/tt_analog_1x2.def", "TEST_analog_example_3v3", False, - "sky130", + "sky130A", ) def test_analog_exact_pins(gds_lef_analog_pin_example: tuple[str, str]): gds_file, lef_file = gds_lef_analog_pin_example precheck.analog_pin_check( - gds_file, "sky130", True, False, 2, {"ua[0]": "x", "ua[1]": "x"} + gds_file, "sky130A", True, False, 2, {"ua[0]": "x", "ua[1]": "x"} ) @@ -678,7 +678,7 @@ def test_analog_less_pins(gds_lef_analog_pin_example: tuple[str, str]): match="Analog pin `ua\\[1\\]` is connected to some metal but `analog_pins` is set to 1 .*", ): precheck.analog_pin_check( - gds_file, "sky130", True, False, 1, {"ua[0]": "x", "ua[1]": "x"} + gds_file, "sky130A", True, False, 1, {"ua[0]": "x", "ua[1]": "x"} ) @@ -689,7 +689,7 @@ def test_analog_more_pins(gds_lef_analog_pin_example: tuple[str, str]): match="Analog pin `ua\\[2\\]` is not connected to any adjacent metal but `analog_pins` is set to 3 .*", ): precheck.analog_pin_check( - gds_file, "sky130", True, False, 3, {"ua[0]": "x", "ua[1]": "x"} + gds_file, "sky130A", True, False, 3, {"ua[0]": "x", "ua[1]": "x"} ) @@ -699,7 +699,7 @@ def test_analog_less_ua_entries(gds_lef_analog_pin_example: tuple[str, str]): precheck.PrecheckFailure, match="Analog pin `ua\\[1\\]` is connected to some metal but the description of `ua\\[1\\]` .*", ): - precheck.analog_pin_check(gds_file, "sky130", True, False, 2, {"ua[0]": "x"}) + precheck.analog_pin_check(gds_file, "sky130A", True, False, 2, {"ua[0]": "x"}) def test_analog_more_ua_entries(gds_lef_analog_pin_example: tuple[str, str]): @@ -710,7 +710,7 @@ def test_analog_more_ua_entries(gds_lef_analog_pin_example: tuple[str, str]): ): precheck.analog_pin_check( gds_file, - "sky130", + "sky130A", True, False, 2, diff --git a/project.py b/project.py index 3e2d79f..52fcaca 100644 --- a/project.py +++ b/project.py @@ -12,27 +12,14 @@ import typing import cairosvg # type: ignore import chevron import gdstk # type: ignore -import volare import yaml from git.repo import Repo import git_utils -from cells import Sky130Cell, load_ihp_cells, load_sky130_cells from config_utils import read_config, write_config from markdown_utils import limit_markdown_headings from project_info import ProjectInfo, ProjectYamlError - -_SKY130_CELL_URL_FMT = "https://skywater-pdk.readthedocs.io/en/main/contents/libraries/sky130_fd_sc_hd/cells/{name}/README.html" -_IHP_CELL_URL_FMT = "https://raw.githubusercontent.com/IHP-GmbH/IHP-Open-PDK/refs/heads/main/ihp-sg13g2/libs.ref/sg13g2_stdcell/doc/sg13g2_stdcell_typ_1p20V_25C.pdf#{ref}" - - -def _sky130_cell_url(cell_name): - return _SKY130_CELL_URL_FMT.format(name=cell_name) - - -def _ihp_cell_url(doc_ref): - return _IHP_CELL_URL_FMT.format(ref=doc_ref) - +from tech import TechName, tech_map PINOUT_KEYS = [ "ui[0]", @@ -76,11 +63,12 @@ class Project: index: int, git_url: str, local_dir: str, - pdk: str, + pdk: TechName, is_user_project: bool, ): self.git_url = git_url - self.ihp = pdk == "ihp-sg13g2" + self.pdk = pdk + self.tech = tech_map[pdk] self.index = index self.local_dir = os.path.realpath(local_dir) self.is_user_project = is_user_project @@ -357,10 +345,7 @@ class Project: return os.path.join(self.local_dir, f"{self.info.top_module}.v") def get_tile_sizes(self): - if self.ihp: - tile_sizes_yaml = "ihp/tile_sizes.yaml" - else: - tile_sizes_yaml = "tile_sizes.yaml" + tile_sizes_yaml = f"tech/{self.pdk}/tile_sizes.yaml" with open( os.path.join(os.path.dirname(__file__), tile_sizes_yaml), "r" ) as stream: @@ -487,10 +472,10 @@ class Project: tiles = self.info.tiles tile_sizes = self.get_tile_sizes() die_area = tile_sizes[tiles] - if self.ihp: - def_template = f"dir::../tt/ihp/def/tt_block_{tiles}_pgvdd.def" - else: - def_template = f"dir::../tt/def/tt_block_{tiles}_pg.def" + def_suffix = self.tech.def_suffix + def_template = ( + f"dir::../tt/tech/{self.pdk}/def/tt_block_{tiles}_{def_suffix}.def" + ) config = { "DESIGN_NAME": self.info.top_module, "VERILOG_FILES": [f"dir::{src}" for src in self.sources], @@ -522,7 +507,7 @@ class Project: os.makedirs("runs/wokwi", exist_ok=True) arg_progress = "--hide-progress-bar" if "CI" in os.environ else "" arg_pdk_root = '--pdk-root "$PDK_ROOT"' if "PDK_ROOT" in os.environ else "" - arg_pdk = "--manual-pdk --pdk ihp-sg13g2" if self.ihp else "" + arg_pdk = self.tech.librelane_pdk_args harden_cmd = f"python -m librelane {arg_pdk_root} --docker-no-tty --dockerized {arg_pdk_root} {arg_pdk} --run-tag wokwi --force-run-dir runs/wokwi {arg_progress} src/config_merged.json" env = os.environ.copy() logging.debug(harden_cmd) @@ -546,27 +531,13 @@ class Project: ) f.write("\n") - resolved_json_path = "runs/wokwi/resolved.json" - config = json.load(open(os.path.join(self.local_dir, resolved_json_path))) - librelane_version = config["meta"]["librelane_version"] - if self.ihp: - pdk_repo = Repo(env["PDK_ROOT"]) - pdk_repo_name = list(pdk_repo.remotes[0].urls)[0].split("/")[-1] - pdk_commit = pdk_repo.commit().hexsha - pdk_sources = f"{pdk_repo_name} {pdk_commit}" - else: - pdk_sources_file = os.path.join( - config["PDK_ROOT"], config["PDK"], "SOURCES" - ) - pdk_sources = open(pdk_sources_file).read() - open("runs/wokwi/FLOW_VERSION", "w").write(f"LibreLane {librelane_version}\n") - open("runs/wokwi/PDK_SOURCES", "w").write(pdk_sources) - if not self.ihp: - volare.enable( - volare.get_volare_home(), # uses PDK_ROOT if set, ~/.volare otherwise - {"sky130A": "sky130"}[config["PDK"]], - pdk_sources.split()[1], - ) + with open("runs/wokwi/FLOW_VERSION", "w") as f: + resolved_json_path = "runs/wokwi/resolved.json" + config = json.load(open(os.path.join(self.local_dir, resolved_json_path))) + librelane_version = config["meta"]["librelane_version"] + f.write(f"LibreLane {librelane_version}\n") + with open("runs/wokwi/PDK_SOURCES", "w") as f: + f.write(self.tech.read_pdk_version(config["PDK_ROOT"])) os.chdir(cwd) @@ -727,26 +698,7 @@ class Project: top_cells = self.get_final_gds_top_cells() # Remove label layers (i.e. delete text), then generate SVG: - sky130_label_layers = [ - (64, 59), # 64/59 - pwell.label - (64, 5), # 64/5 - nwell.label - (67, 5), # 67/5 - li1.label - (68, 5), # 68/5 - met1.label - (69, 5), # 69/5 - met2.label - (70, 5), # 70/5 - met3.label - (71, 5), # 71/5 - met4.label - ] - ihp_label_layers = [ - (8, 1), # 8/1 - Metal1.label - (8, 25), # 8/25 - Metal1.text - (67, 25), # 67/25 - Metal5.text - (126, 25), # 126/25 - TopMetal1.text - ] - if self.ihp: - label_layers = ihp_label_layers - else: - label_layers = sky130_label_layers - + label_layers = self.tech.label_layers top_cells.filter(label_layers) for subcell in top_cells.dependencies(True): subcell.filter(label_layers) @@ -777,31 +729,9 @@ class Project: p.stderr.decode().strip() ) ) - # Remove more layers that are hardly visible anyway: - sky130_buried_layers = [ - (64, 16), # 64/16 - nwell.pin - (65, 44), # 65/44 - tap.drawing - (68, 16), # 68/16 - met1.pin - (68, 44), # 68/44 - via.drawing - (81, 4), # 81/4 - areaid.standardc - (70, 20), # 70/20 - met3.drawing - # Important: - # (68,20), # 68/20 - met1.drawing - # Less important, but keep for now: - # (69,20), # 69/20 - met2.drawing - ] - ihp_buried_layers = [ - (6, 0), # 6/0 - Cont.drawing - (8, 2), # 8/2 - Metal1.pin - (19, 0), # 19/0 - Via1.drawing - (51, 0), # 51/0 - HeatTrans.drawing - (189, 4), # 189/4 - prBoundary.boundary - ] - if self.ihp: - buried_layers = ihp_buried_layers - else: - buried_layers = sky130_buried_layers + # Remove more layers that are hardly visible anyway: + buried_layers = self.tech.buried_layers top_cells.filter(buried_layers) for subcell in top_cells.dependencies(True): subcell.filter(buried_layers) @@ -822,6 +752,7 @@ class Project: p.stderr.decode().strip() ) ) + # Fall back to cairosvg, and since we're doing that, might as well use the original full-detail SVG anyway: cairosvg.svg2png(url=svg, write_to=png) @@ -869,10 +800,7 @@ class Project: if "WIDTHLABEL" not in line: warnings.append(line.strip()) - if self.ihp: - tt_corner = "nom_typ_1p20V_25C" - else: - tt_corner = "nom_tt_025C_1v80" + tt_corner = self.tech.tt_corner sta_report_glob = f"runs/wokwi/*-openroad-stapostpnr/{tt_corner}/checks.rpt" sta_report = glob.glob(os.path.join(self.local_dir, sta_report_glob))[0] with open(os.path.join(self.local_dir, sta_report)) as f: @@ -911,23 +839,11 @@ class Project: Categories = typing.TypedDict( "Categories", {"categories": typing.List[str], "map": typing.Dict[str, int]} ) - if self.ihp: - categories_file = "ihp/categories.json" - else: - categories_file = "categories.json" + categories_file = f"tech/{self.pdk}/categories.json" with open(os.path.join(SCRIPT_DIR, categories_file)) as fh: categories: Categories = json.load(fh) - if self.ihp: - ihp_defs = load_ihp_cells() - else: - sky130_defs = load_sky130_cells() - - def _cell_url(name): - if self.ihp: - return _ihp_cell_url(ihp_defs[name]["doc_ref"]) - else: - return _sky130_cell_url(name) + cell_defs = self.tech.load_cell_definitions() # print all used cells, sorted by frequency total = 0 @@ -941,12 +857,13 @@ class Project: ): if count > 0: total += count - cell_link = _cell_url(name) - if self.ihp: - description = ihp_defs[name]["description"] + cell_def = cell_defs.get(name) + if cell_def is not None: + cell_link = cell_def["url"] + description = cell_def["description"] + print(f"| [{name}]({cell_link}) | {description} |{count} |") else: - description = sky130_defs[name]["description"] - print(f"| [{name}]({cell_link}) | {description} |{count} |") + print(f"| {name} | (unknown) | {count} |") print(f"| | Total | {total} |") @@ -978,9 +895,14 @@ class Project: for cat_name, cat_dict in sorted( by_category.items(), key=lambda x: x[1]["count"], reverse=True ): - cell_links = [ - f"[{name}]({_cell_url(name)})" for name in cat_dict["examples"] - ] + cell_links = [] + for name in cat_dict["examples"]: + cell_def = cell_defs.get(name) + if cell_def is not None: + cell_links.append(f"[{name}]({cell_def['url']})") + else: + cell_links.append(name) + print(f'|{cat_name} | {" ".join(cell_links)} | {cat_dict["count"]}|') print(f"## {total} total cells (excluding fill and tap cells)") @@ -1005,15 +927,9 @@ class Project: def get_cell_counts_from_gl(self): cell_count: typing.Dict[str, int] = {} total = 0 - if self.ihp: - cell_re = r"sg13g2_(?P\S+)_(?P\d+)" - else: - cell_re = ( - r"sky130_(?P\S+)__(?P\S+)_(?P\d+)" - ) with open(self.get_gl_path()) as fh: for line in fh.readlines(): - m = re.search(cell_re, line) + m = re.search(self.tech.cell_regexp, line) if m is not None: total += 1 gd = m.groupdict() @@ -1025,17 +941,3 @@ class Project: except KeyError: cell_count[cell_name] = 1 return cell_count - - # Load all the json defs and combine into one big dict, keyed by cellname - # Needs access to the PDK - def create_defs(self): - # replace this with a find - json_files = glob.glob("sky130_fd_sc_hd/latest/cells/*/definition.json") - definitions: typing.Dict[str, Sky130Cell] = {} - for json_file in json_files: - with open(json_file) as fh: - definition: Sky130Cell = json.load(fh) - definitions[definition["name"]] = definition - - with open("cells.json", "w") as fh: - json.dump(definitions, fh) diff --git a/pyproject.toml b/pyproject.toml index 894a23d..a2e7a17 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -17,7 +17,6 @@ dependencies = [ "python-frontmatter", "PyYAML", "requests", - "volare", "yowasp-yosys", ] diff --git a/requirements.txt b/requirements.txt index 5fab7f6..ed99f53 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,11 +1,9 @@ # -# This file is autogenerated by pip-compile with Python 3.13 +# This file is autogenerated by pip-compile with Python 3.12 # by the following command: # # pip-compile # -anyio==4.10.0 - # via httpx brotli==1.1.0 # via tt-support-tools (pyproject.toml) cairocffi==1.7.1 @@ -13,10 +11,7 @@ cairocffi==1.7.1 cairosvg==2.8.2 # via tt-support-tools (pyproject.toml) certifi==2025.8.3 - # via - # httpcore - # httpx - # requests + # via requests cffi==1.17.1 # via cairocffi cfgv==3.4.0 @@ -26,9 +21,7 @@ charset-normalizer==3.4.2 chevron==0.14.0 # via tt-support-tools (pyproject.toml) click==8.2.1 - # via - # volare - # yowasp-yosys + # via yowasp-yosys cocotb==1.9.2 # via tt-support-tools (pyproject.toml) cssselect2==0.8.0 @@ -47,29 +40,16 @@ gitdb==4.0.12 # via gitpython gitpython==3.1.45 # via tt-support-tools (pyproject.toml) -h11==0.16.0 - # via httpcore -httpcore==1.0.9 - # via httpx -httpx==0.28.1 - # via volare identify==2.6.12 # via pre-commit idna==3.10 - # via - # anyio - # httpx - # requests + # via requests importlib-resources==6.5.2 # via wasmtime iniconfig==2.1.0 # via pytest klayout==0.29.12 # via tt-support-tools (pyproject.toml) -markdown-it-py==3.0.0 - # via rich -mdurl==0.1.2 - # via markdown-it-py mistune==3.1.3 # via tt-support-tools (pyproject.toml) nodeenv==1.9.1 @@ -80,8 +60,6 @@ numpy==1.26.4 # tt-support-tools (pyproject.toml) packaging==25.0 # via pytest -pcpp==1.30 - # via volare pillow==11.3.0 # via cairosvg platformdirs==4.3.8 @@ -95,9 +73,7 @@ pre-commit==4.2.0 pycparser==2.22 # via cffi pygments==2.19.2 - # via - # pytest - # rich + # via pytest pytest==8.4.1 # via tt-support-tools (pyproject.toml) python-frontmatter==1.1.0 @@ -107,15 +83,10 @@ pyyaml==6.0.2 # pre-commit # python-frontmatter # tt-support-tools (pyproject.toml) - # volare requests==2.32.4 # via tt-support-tools (pyproject.toml) -rich==13.9.4 - # via volare smmap==5.0.2 # via gitdb -sniffio==1.3.1 - # via anyio tinycss2==1.4.0 # via # cairosvg @@ -124,8 +95,6 @@ urllib3==2.5.0 # via requests virtualenv==20.33.1 # via pre-commit -volare==0.20.6 - # via tt-support-tools (pyproject.toml) wasmtime==35.0.0 # via yowasp-runtime webencodings==0.5.1 @@ -136,5 +105,3 @@ yowasp-runtime==1.78 # via yowasp-yosys yowasp-yosys==0.55.0.0.post944 # via tt-support-tools (pyproject.toml) -zstandard==0.23.0 - # via volare diff --git a/rom/config.json b/rom/config.json index b20e51d..608c4c5 100644 --- a/rom/config.json +++ b/rom/config.json @@ -3,8 +3,6 @@ "VERILOG_FILES": "dir::tt_um_chip_rom.v", "FP_SIZING": "absolute", - "DIE_AREA": [0, 0, 161.00, 111.52], - "FP_DEF_TEMPLATE": "ref::$DESIGN_DIR/../def/tt_block_1x1_pg.def", "FP_IO_HLENGTH": 2, "FP_IO_VLENGTH": 2, @@ -13,9 +11,8 @@ "CLOCK_PERIOD": 20, "CLOCK_PORT": "clk", - "//": "don't use power rings or met5", + "//": "don't use power rings", "DESIGN_IS_CORE": false, - "RT_MAX_LAYER": "met4", "//": "save some time", "RUN_KLAYOUT_XOR": false, @@ -24,5 +21,17 @@ "TOP_MARGIN_MULT": 1, "BOTTOM_MARGIN_MULT": 1, "LEFT_MARGIN_MULT": 6, - "RIGHT_MARGIN_MULT": 6 + "RIGHT_MARGIN_MULT": 6, + + "pdk::sky130A": { + "DIE_AREA": [0, 0, 161.0, 111.52], + "FP_DEF_TEMPLATE": "ref::$DESIGN_DIR/../tech/sky130A/def/tt_block_1x1_pg.def", + "RT_MAX_LAYER": "met4" + }, + + "pdk::ihp-sg13g2": { + "DIE_AREA": [0, 0, 202.08, 154.98], + "FP_DEF_TEMPLATE": "ref::$DESIGN_DIR/../tech/ihp-sg13g2/def/tt_block_1x1_pgvdd.def", + "RT_MAX_LAYER": "Metal5" + } } diff --git a/rom/config_ihp.json b/rom/config_ihp.json deleted file mode 100644 index e1ce42a..0000000 --- a/rom/config_ihp.json +++ /dev/null @@ -1,28 +0,0 @@ -{ - "DESIGN_NAME": "tt_um_chip_rom", - "VERILOG_FILES": "dir::tt_um_chip_rom.v", - - "FP_SIZING": "absolute", - "DIE_AREA": [0, 0, 202.08, 154.98], - "FP_DEF_TEMPLATE": "ref::$DESIGN_DIR/../ihp/def/tt_block_1x1_pgvdd.def", - - "FP_IO_HLENGTH": 2, - "FP_IO_VLENGTH": 2, - - "//": "period is in ns, so 20ns == 50mHz", - "CLOCK_PERIOD": 20, - "CLOCK_PORT": "clk", - - "//": "don't use power rings or TopMetal", - "DESIGN_IS_CORE": false, - "RT_MAX_LAYER": "Metal5", - - "//": "save some time", - "RUN_KLAYOUT_XOR": false, - - "//": "reduce wasted space", - "TOP_MARGIN_MULT": 1, - "BOTTOM_MARGIN_MULT": 1, - "LEFT_MARGIN_MULT": 6, - "RIGHT_MARGIN_MULT": 6 -} diff --git a/shuttle.py b/shuttle.py index cfe78be..ab94be0 100644 --- a/shuttle.py +++ b/shuttle.py @@ -13,27 +13,7 @@ import yaml from config import Config from project import Project from shuttle_index import ShuttleIndex, ShuttleIndexLayout, ShuttleIndexProject - -# Power gate macros for each PDK. We need to copy them to the multiplexer directory: -SHUTTLE_PG_MACROS = { - "sky130": [ - "tt_pg_1v8_hp_1", - "tt_pg_1v8_hp_2", - "tt_pg_1v8_hp_4", - "tt_pg_1v8_ll_1", - "tt_pg_1v8_ll_2", - "tt_pg_1v8_ll_4", - "tt_pg_3v3_2", - ], - "ihp-sg13g2": [ - "tt_pg_1v5_hp_1", - "tt_pg_1v5_hp_2", - "tt_pg_1v5_hp_4", - "tt_pg_1v5_ll_1", - "tt_pg_1v5_ll_2", - "tt_pg_1v5_ll_4", - ], -} +from tech import tech_map def copy_print(src: str, dest: str): @@ -76,7 +56,13 @@ class ShuttleConfig: self.projects = projects self.script_dir = os.path.dirname(os.path.realpath(__file__)) self.modules_yaml_name = modules_yaml_name - self.mux_config_yaml_name = os.environ.get("TT_CONFIG", "sky130.yaml") + pdk = config.get("pdk") + if pdk is None: + raise ValueError("PDK is not specified in the configuration.") + self.tech = tech_map[pdk] + self.mux_config_yaml_name = os.environ.get( + "TT_CONFIG", self.tech.mux_config_yaml_name + ) self.tt_top_macro = config.get("top_level_macro", "openframe_project_wrapper") self.read_mux_config_file() @@ -283,17 +269,17 @@ class ShuttleConfig: ) # Copy power gate / analog switch macros: - for pdk, pg_macros in SHUTTLE_PG_MACROS.items(): - for pg_macro in pg_macros: - self.copy_mux_macro(f"pg/{pdk}/{pg_macro}", pg_macro) - - self.copy_mux_macro("asw/sky130/tt_asw_3v3", "tt_asw_3v3") + for mux_macro in self.tech.mux_macros: + self.copy_mux_macro(mux_macro, os.path.basename(mux_macro)) # Copy logo & shuttle ID self.copy_logo_macro("tt_logo_top") self.copy_logo_macro("tt_logo_bottom") - if self.config.get("pdk") == "ihp-sg13g2": - self.copy_logo_macro("tt_logo_corner", source_dir="tt/ihp") + for logo_macro in self.tech.extra_logo_macros: + self.copy_logo_macro( + os.path.basename(logo_macro), + source_dir=os.path.join("tt", os.path.dirname(logo_macro)), + ) def copy_final_results(self): macros = ["tt_um_chip_rom", "tt_ctrl", "tt_mux", "tt_top"] diff --git a/tech.py b/tech.py new file mode 100644 index 0000000..964e83d --- /dev/null +++ b/tech.py @@ -0,0 +1,139 @@ +import json +import os +from typing import Dict, List, Literal, Protocol, Tuple, TypedDict + +from git.repo import Repo + +TechName = Literal["sky130A", "ihp-sg13g2"] + + +class CellDefinition(TypedDict): + url: str + description: str + + +class Tech(Protocol): + def_suffix: str + librelane_pdk_args: str + tt_corner: str + cell_regexp: str + + """ These layers will be removed from the SVG render of the layout """ + label_layers: List[Tuple[int, int]] + """ These layers are hardly visible and will also be removed from the SVG render of the layout """ + buried_layers: List[Tuple[int, int]] + """ Default name of the mux config YAML file. """ + mux_config_yaml_name: str + """ Lists all of the analog switch / power gate macros that need to be copied to the tt_top directory. """ + mux_macros: List[str] + """ Lists all of the extra logo macros that need to be copied to the tt_top directory. """ + extra_logo_macros: List[str] + + def read_pdk_version(self, pdk_root: str) -> str: + raise NotImplementedError() + + def load_cell_definitions(self) -> Dict[str, CellDefinition]: + raise NotImplementedError() + + +class Sky130Tech(Tech): + def_suffix = "pg" + librelane_pdk_args = "" + tt_corner = "nom_tt_025C_1v80" + cell_regexp = r"sky130_(?P\S+)__(?P\S+)_(?P\d+)" + label_layers = [ + (64, 59), # pwell.label + (64, 5), # nwell.label + (67, 5), # li1.label + (68, 5), # met1.label + (69, 5), # met2.label + (70, 5), # met3.label + (71, 5), # met4.label + ] + buried_layers = [ + (64, 16), # nwell.pin + (65, 44), # tap.drawing + (68, 16), # met1.pin + (68, 44), # via.drawing + (81, 4), # areaid.standardc + (70, 20), # met3.drawing + ] + mux_config_yaml_name = "sky130.yaml" + mux_macros = [ + "pg/sky130/tt_pg_1v8_hp_1", + "pg/sky130/tt_pg_1v8_hp_2", + "pg/sky130/tt_pg_1v8_hp_4", + "pg/sky130/tt_pg_1v8_ll_1", + "pg/sky130/tt_pg_1v8_ll_2", + "pg/sky130/tt_pg_1v8_ll_4", + "pg/sky130/tt_pg_3v3_2", + "asw/sky130/tt_asw_3v3", + ] + extra_logo_macros = [] + + def read_pdk_version(self, pdk_root: str) -> str: + pdk_sources_file = os.path.join(pdk_root, "sky130A", "SOURCES") + with open(pdk_sources_file) as f: + return f.read() + + def load_cell_definitions(self) -> Dict[str, CellDefinition]: + URL_FORMAT = "https://skywater-pdk.readthedocs.io/en/main/contents/libraries/sky130_fd_sc_hd/cells/{name}/README.html" + script_dir = os.path.dirname(os.path.realpath(__file__)) + with open(os.path.join(script_dir, "tech/sky130A/cells.json")) as fh: + cells = json.load(fh) + for name, cell in cells.items(): + cell["url"] = URL_FORMAT.format(name=name) + return cells + + +class IHPTech(Tech): + def_suffix = "pgvdd" + librelane_pdk_args = "--manual-pdk --pdk ihp-sg13g2" + tt_corner = "nom_typ_1p20V_25C" + cell_regexp = r"sg13g2_(?P\S+)_(?P\d+)" + label_layers = [ + (8, 1), # Metal1.label + (8, 25), # Metal1.text + (67, 25), # Metal5.text + (126, 25), # TopMetal1.text + ] + buried_layers = [ + (6, 0), # Cont.drawing + (8, 2), # Metal1.pin + (19, 0), # Via1.drawing + (51, 0), # HeatTrans.drawing + (189, 4), # prBoundary.boundary + ] + mux_config_yaml_name = "ihp-sg13g2.yaml" + mux_macros = [ + "pg/ihp-sg13g2/tt_pg_1v5_hp_1", + "pg/ihp-sg13g2/tt_pg_1v5_hp_2", + "pg/ihp-sg13g2/tt_pg_1v5_hp_4", + "pg/ihp-sg13g2/tt_pg_1v5_ll_1", + "pg/ihp-sg13g2/tt_pg_1v5_ll_2", + "pg/ihp-sg13g2/tt_pg_1v5_ll_4", + ] + extra_logo_macros = [ + "tech/ihp-sg13g2/tt_logo_corner", + ] + + def read_pdk_version(self, pdk_root: str) -> str: + pdk_repo = Repo(pdk_root) + pdk_repo_name = list(pdk_repo.remotes[0].urls)[0].split("/")[-1] + pdk_commit = pdk_repo.commit().hexsha + return f"{pdk_repo_name} {pdk_commit}" + + def load_cell_definitions(self) -> Dict[str, CellDefinition]: + URL_FORMAT = "https://raw.githubusercontent.com/IHP-GmbH/IHP-Open-PDK/refs/heads/main/ihp-sg13g2/libs.ref/sg13g2_stdcell/doc/sg13g2_stdcell_typ_1p20V_25C.pdf#{ref}" + script_dir = os.path.dirname(os.path.realpath(__file__)) + with open(os.path.join(script_dir, "tech/ihp-sg13g2/cells.json")) as fh: + cells = json.load(fh) + for cell in cells.values(): + cell["url"] = URL_FORMAT.format(ref=cell["doc_ref"]) + return cells + + +tech_map: dict[TechName, Tech] = { + "sky130A": Sky130Tech(), + "ihp-sg13g2": IHPTech(), +} diff --git a/ihp/categories.json b/tech/ihp-sg13g2/categories.json similarity index 100% rename from ihp/categories.json rename to tech/ihp-sg13g2/categories.json diff --git a/ihp/cells.json b/tech/ihp-sg13g2/cells.json similarity index 100% rename from ihp/cells.json rename to tech/ihp-sg13g2/cells.json diff --git a/ihp/def/tt_block_1x1_pgvdd.def b/tech/ihp-sg13g2/def/tt_block_1x1_pgvdd.def similarity index 100% rename from ihp/def/tt_block_1x1_pgvdd.def rename to tech/ihp-sg13g2/def/tt_block_1x1_pgvdd.def diff --git a/ihp/def/tt_block_1x2_pgvdd.def b/tech/ihp-sg13g2/def/tt_block_1x2_pgvdd.def similarity index 100% rename from ihp/def/tt_block_1x2_pgvdd.def rename to tech/ihp-sg13g2/def/tt_block_1x2_pgvdd.def diff --git a/ihp/def/tt_block_2x1_pgvdd.def b/tech/ihp-sg13g2/def/tt_block_2x1_pgvdd.def similarity index 100% rename from ihp/def/tt_block_2x1_pgvdd.def rename to tech/ihp-sg13g2/def/tt_block_2x1_pgvdd.def diff --git a/ihp/def/tt_block_2x2_pgvdd.def b/tech/ihp-sg13g2/def/tt_block_2x2_pgvdd.def similarity index 100% rename from ihp/def/tt_block_2x2_pgvdd.def rename to tech/ihp-sg13g2/def/tt_block_2x2_pgvdd.def diff --git a/ihp/def/tt_block_3x1_pgvdd.def b/tech/ihp-sg13g2/def/tt_block_3x1_pgvdd.def similarity index 100% rename from ihp/def/tt_block_3x1_pgvdd.def rename to tech/ihp-sg13g2/def/tt_block_3x1_pgvdd.def diff --git a/ihp/def/tt_block_3x2_pgvdd.def b/tech/ihp-sg13g2/def/tt_block_3x2_pgvdd.def similarity index 100% rename from ihp/def/tt_block_3x2_pgvdd.def rename to tech/ihp-sg13g2/def/tt_block_3x2_pgvdd.def diff --git a/ihp/def/tt_block_3x4_pgvdd.def b/tech/ihp-sg13g2/def/tt_block_3x4_pgvdd.def similarity index 100% rename from ihp/def/tt_block_3x4_pgvdd.def rename to tech/ihp-sg13g2/def/tt_block_3x4_pgvdd.def diff --git a/ihp/def/tt_block_4x1_pgvdd.def b/tech/ihp-sg13g2/def/tt_block_4x1_pgvdd.def similarity index 100% rename from ihp/def/tt_block_4x1_pgvdd.def rename to tech/ihp-sg13g2/def/tt_block_4x1_pgvdd.def diff --git a/ihp/def/tt_block_4x2_pgvdd.def b/tech/ihp-sg13g2/def/tt_block_4x2_pgvdd.def similarity index 100% rename from ihp/def/tt_block_4x2_pgvdd.def rename to tech/ihp-sg13g2/def/tt_block_4x2_pgvdd.def diff --git a/ihp/def/tt_block_4x4_pgvdd.def b/tech/ihp-sg13g2/def/tt_block_4x4_pgvdd.def similarity index 100% rename from ihp/def/tt_block_4x4_pgvdd.def rename to tech/ihp-sg13g2/def/tt_block_4x4_pgvdd.def diff --git a/ihp/def/tt_block_5x4_pgvdd.def b/tech/ihp-sg13g2/def/tt_block_5x4_pgvdd.def similarity index 100% rename from ihp/def/tt_block_5x4_pgvdd.def rename to tech/ihp-sg13g2/def/tt_block_5x4_pgvdd.def diff --git a/ihp/def/tt_block_6x1_pgvdd.def b/tech/ihp-sg13g2/def/tt_block_6x1_pgvdd.def similarity index 100% rename from ihp/def/tt_block_6x1_pgvdd.def rename to tech/ihp-sg13g2/def/tt_block_6x1_pgvdd.def diff --git a/ihp/def/tt_block_6x2_pgvdd.def b/tech/ihp-sg13g2/def/tt_block_6x2_pgvdd.def similarity index 100% rename from ihp/def/tt_block_6x2_pgvdd.def rename to tech/ihp-sg13g2/def/tt_block_6x2_pgvdd.def diff --git a/ihp/def/tt_block_6x4_pgvdd.def b/tech/ihp-sg13g2/def/tt_block_6x4_pgvdd.def similarity index 100% rename from ihp/def/tt_block_6x4_pgvdd.def rename to tech/ihp-sg13g2/def/tt_block_6x4_pgvdd.def diff --git a/ihp/def/tt_block_8x1_pgvdd.def b/tech/ihp-sg13g2/def/tt_block_8x1_pgvdd.def similarity index 100% rename from ihp/def/tt_block_8x1_pgvdd.def rename to tech/ihp-sg13g2/def/tt_block_8x1_pgvdd.def diff --git a/ihp/def/tt_block_8x2_pgvdd.def b/tech/ihp-sg13g2/def/tt_block_8x2_pgvdd.def similarity index 100% rename from ihp/def/tt_block_8x2_pgvdd.def rename to tech/ihp-sg13g2/def/tt_block_8x2_pgvdd.def diff --git a/ihp/tile_sizes.yaml b/tech/ihp-sg13g2/tile_sizes.yaml similarity index 100% rename from ihp/tile_sizes.yaml rename to tech/ihp-sg13g2/tile_sizes.yaml diff --git a/ihp/tt_logo_corner.gds b/tech/ihp-sg13g2/tt_logo_corner.gds similarity index 100% rename from ihp/tt_logo_corner.gds rename to tech/ihp-sg13g2/tt_logo_corner.gds diff --git a/ihp/tt_logo_corner.lef b/tech/ihp-sg13g2/tt_logo_corner.lef similarity index 100% rename from ihp/tt_logo_corner.lef rename to tech/ihp-sg13g2/tt_logo_corner.lef diff --git a/ihp/tt_logo_corner.v b/tech/ihp-sg13g2/tt_logo_corner.v similarity index 100% rename from ihp/tt_logo_corner.v rename to tech/ihp-sg13g2/tt_logo_corner.v diff --git a/categories.json b/tech/sky130A/categories.json similarity index 100% rename from categories.json rename to tech/sky130A/categories.json diff --git a/cells.json b/tech/sky130A/cells.json similarity index 100% rename from cells.json rename to tech/sky130A/cells.json diff --git a/tech/sky130A/create_cell_defs.py b/tech/sky130A/create_cell_defs.py new file mode 100644 index 0000000..945897a --- /dev/null +++ b/tech/sky130A/create_cell_defs.py @@ -0,0 +1,40 @@ +import glob +import json +import os +from typing import Dict, List, Optional, TypedDict + + +class Port(TypedDict): + kind: str + name: str + direction: str + description: str + + +class Sky130Cell(TypedDict): + description: str + file_prefix: str + library: str + name: str + parameters: List[str] + ports: List[Port] + type: str + verilog_name: str + equation: Optional[str] + + +def create_cell_defs(): + json_files = glob.glob("sky130_fd_sc_hd/latest/cells/*/definition.json") + definitions: Dict[str, Sky130Cell] = {} + for json_file in json_files: + with open(json_file) as fh: + definition: Sky130Cell = json.load(fh) + definitions[definition["name"]] = definition + + script_dir = os.path.dirname(os.path.realpath(__file__)) + with open(f"{script_dir}/cells.json", "w") as fh: + json.dump(definitions, fh) + + +if __name__ == "__main__": + create_cell_defs() diff --git a/def/analog/magic_init_project.tcl b/tech/sky130A/def/analog/magic_init_project.tcl similarity index 94% rename from def/analog/magic_init_project.tcl rename to tech/sky130A/def/analog/magic_init_project.tcl index a04be87..8b29fd0 100644 --- a/def/analog/magic_init_project.tcl +++ b/tech/sky130A/def/analog/magic_init_project.tcl @@ -1,10 +1,10 @@ # SPDX-License-Identifier: Apache-2.0 -# Copyright (c) 2024 Tiny Tapeout LTD +# Copyright (c) 2024-2025 Tiny Tapeout LTD # Author: Uri Shaked # Description: This script initializes a new Magic project for an analog design on Tiny Tapeout. # Important: before running this script, download the the .def file from -# https://raw.githubusercontent.com/TinyTapeout/tt-support-tools/tt08/def/analog/tt_analog_1x2.def +# https://raw.githubusercontent.com/TinyTapeout/tt-support-tools/main/tech/sky130A/def/analog/tt_analog_1x2.def # Change the settings below to match your design: # ------------------------------------------------ diff --git a/def/analog/tt_analog_1x2.def b/tech/sky130A/def/analog/tt_analog_1x2.def similarity index 100% rename from def/analog/tt_analog_1x2.def rename to tech/sky130A/def/analog/tt_analog_1x2.def diff --git a/def/analog/tt_analog_1x2_3v3.def b/tech/sky130A/def/analog/tt_analog_1x2_3v3.def similarity index 100% rename from def/analog/tt_analog_1x2_3v3.def rename to tech/sky130A/def/analog/tt_analog_1x2_3v3.def diff --git a/def/analog/tt_analog_2x2.def b/tech/sky130A/def/analog/tt_analog_2x2.def similarity index 100% rename from def/analog/tt_analog_2x2.def rename to tech/sky130A/def/analog/tt_analog_2x2.def diff --git a/def/analog/tt_analog_2x2_3v3.def b/tech/sky130A/def/analog/tt_analog_2x2_3v3.def similarity index 100% rename from def/analog/tt_analog_2x2_3v3.def rename to tech/sky130A/def/analog/tt_analog_2x2_3v3.def diff --git a/def/tt_block_1x1_pg.def b/tech/sky130A/def/tt_block_1x1_pg.def similarity index 100% rename from def/tt_block_1x1_pg.def rename to tech/sky130A/def/tt_block_1x1_pg.def diff --git a/def/tt_block_1x2_pg.def b/tech/sky130A/def/tt_block_1x2_pg.def similarity index 100% rename from def/tt_block_1x2_pg.def rename to tech/sky130A/def/tt_block_1x2_pg.def diff --git a/def/tt_block_2x2_pg.def b/tech/sky130A/def/tt_block_2x2_pg.def similarity index 100% rename from def/tt_block_2x2_pg.def rename to tech/sky130A/def/tt_block_2x2_pg.def diff --git a/def/tt_block_3x2_pg.def b/tech/sky130A/def/tt_block_3x2_pg.def similarity index 100% rename from def/tt_block_3x2_pg.def rename to tech/sky130A/def/tt_block_3x2_pg.def diff --git a/def/tt_block_3x4_pg.def b/tech/sky130A/def/tt_block_3x4_pg.def similarity index 100% rename from def/tt_block_3x4_pg.def rename to tech/sky130A/def/tt_block_3x4_pg.def diff --git a/def/tt_block_4x2_pg.def b/tech/sky130A/def/tt_block_4x2_pg.def similarity index 100% rename from def/tt_block_4x2_pg.def rename to tech/sky130A/def/tt_block_4x2_pg.def diff --git a/def/tt_block_4x4_pg.def b/tech/sky130A/def/tt_block_4x4_pg.def similarity index 100% rename from def/tt_block_4x4_pg.def rename to tech/sky130A/def/tt_block_4x4_pg.def diff --git a/def/tt_block_5x4_pg.def b/tech/sky130A/def/tt_block_5x4_pg.def similarity index 100% rename from def/tt_block_5x4_pg.def rename to tech/sky130A/def/tt_block_5x4_pg.def diff --git a/def/tt_block_6x2_pg.def b/tech/sky130A/def/tt_block_6x2_pg.def similarity index 100% rename from def/tt_block_6x2_pg.def rename to tech/sky130A/def/tt_block_6x2_pg.def diff --git a/def/tt_block_6x4_pg.def b/tech/sky130A/def/tt_block_6x4_pg.def similarity index 100% rename from def/tt_block_6x4_pg.def rename to tech/sky130A/def/tt_block_6x4_pg.def diff --git a/def/tt_block_8x2_pg.def b/tech/sky130A/def/tt_block_8x2_pg.def similarity index 100% rename from def/tt_block_8x2_pg.def rename to tech/sky130A/def/tt_block_8x2_pg.def diff --git a/def/tt_block_8x4_pg.def b/tech/sky130A/def/tt_block_8x4_pg.def similarity index 100% rename from def/tt_block_8x4_pg.def rename to tech/sky130A/def/tt_block_8x4_pg.def diff --git a/tile_sizes.yaml b/tech/sky130A/tile_sizes.yaml similarity index 100% rename from tile_sizes.yaml rename to tech/sky130A/tile_sizes.yaml diff --git a/tt_tool.py b/tt_tool.py index 36bb2b3..c479b3c 100755 --- a/tt_tool.py +++ b/tt_tool.py @@ -4,6 +4,7 @@ import logging import sys from project import Project +from tech import TechName if __name__ == "__main__": parser = argparse.ArgumentParser(description="TT setup") @@ -140,7 +141,7 @@ if __name__ == "__main__": ch.setFormatter(log_format) log.addHandler(ch) - pdk = "ihp-sg13g2" if args.ihp else "sky130A" + pdk: TechName = "ihp-sg13g2" if args.ihp else "sky130A" project = Project(0, "unknown", args.project_dir, pdk, is_user_project=True) project.post_clone_setup()