mirror of
https://github.com/TinyTapeout/tt-support-tools.git
synced 2026-11-03 04:36:09 +01:00
refactor: separate tech specific code
Make the codebase more PDK-agnostic by moving tech-specific code to `tech.py` and tech specific files (e.g. def files, tile_sizes.yaml) to `tech/<pdkname>` subdirectory.
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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)
|
||||
@@ -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
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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}
|
||||
"""
|
||||
)
|
||||
@@ -557,7 +557,7 @@ 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",
|
||||
@@ -569,7 +569,7 @@ 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",
|
||||
@@ -585,7 +585,7 @@ 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",
|
||||
@@ -601,7 +601,7 @@ 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",
|
||||
@@ -613,7 +613,7 @@ 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",
|
||||
@@ -625,7 +625,7 @@ 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",
|
||||
@@ -641,7 +641,7 @@ 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",
|
||||
@@ -657,7 +657,7 @@ 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",
|
||||
|
||||
+36
-134
@@ -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")
|
||||
|
||||
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"]
|
||||
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],
|
||||
)
|
||||
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"]
|
||||
else:
|
||||
description = sky130_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:
|
||||
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<cell_name>\S+)_(?P<cell_drive>\d+)"
|
||||
else:
|
||||
cell_re = (
|
||||
r"sky130_(?P<cell_lib>\S+)__(?P<cell_name>\S+)_(?P<cell_drive>\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)
|
||||
|
||||
@@ -17,7 +17,6 @@ dependencies = [
|
||||
"python-frontmatter",
|
||||
"PyYAML",
|
||||
"requests",
|
||||
"volare",
|
||||
"yowasp-yosys",
|
||||
]
|
||||
|
||||
|
||||
+5
-38
@@ -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
|
||||
|
||||
+1
-1
@@ -4,7 +4,7 @@
|
||||
|
||||
"FP_SIZING": "absolute",
|
||||
"DIE_AREA": [0, 0, 161.00, 111.52],
|
||||
"FP_DEF_TEMPLATE": "ref::$DESIGN_DIR/../def/tt_block_1x1_pg.def",
|
||||
"FP_DEF_TEMPLATE": "ref::$DESIGN_DIR/../tech/sky130A/def/tt_block_1x1_pg.def",
|
||||
|
||||
"FP_IO_HLENGTH": 2,
|
||||
"FP_IO_VLENGTH": 2,
|
||||
|
||||
+1
-1
@@ -4,7 +4,7 @@
|
||||
|
||||
"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_DEF_TEMPLATE": "ref::$DESIGN_DIR/../tech/ihp-sg13g2/def/tt_block_1x1_pgvdd.def",
|
||||
|
||||
"FP_IO_HLENGTH": 2,
|
||||
"FP_IO_VLENGTH": 2,
|
||||
|
||||
+15
-29
@@ -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"]
|
||||
|
||||
@@ -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<cell_lib>\S+)__(?P<cell_name>\S+)_(?P<cell_drive>\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<cell_name>\S+)_(?P<cell_drive>\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(),
|
||||
}
|
||||
@@ -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()
|
||||
@@ -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:
|
||||
# ------------------------------------------------
|
||||
+2
-1
@@ -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()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user