blob: a6816985f676778870a734e2c3de298d445c7730 [file] [log] [blame]
# SPDX-License-Identifier: GPL-2.0-or-later
# PDKMaster & co. don't put a license requirement on the generated files.
# The generated gds files in this project are released under the LGPL 2.0 or later license.
from pathlib import Path
from typing import Any, cast
import pya as _pya
# Disable type checking
pya = cast(Any, _pya)
from pdkmaster.technology import (geometry as _geo, primitive as _prm)
from pdkmaster.design import library as _lib
from pdkmaster.io.klayout import export as _klexp
from c4m.pdk import sky130
from . import frame as _frm
def gen_gds(*, gds_out: Path, gds_empty: Path):
top_name = "user_analog_project_wrapper"
prims = sky130.tech.primitives
lib = _lib.Library(
name="MPW4IOTest", tech=sky130.tech, cktfab=sky130.cktfab, layoutfab=sky130.layoutfab,
)
# Create the IOBlock
ioblock = lib.new_cell(name="IOBlock")
ckt = ioblock.new_circuit()
layouter = ioblock.new_circuitlayouter()
x = 0.0
lay = None
for cell_name in (
"IOPadIOVss", "IOPadIOVdd", "IOPadIn", "IOPadInOut", "IOPadOut",
"IOPadVdd", "IOPadVss",
):
cell = sky130.iolib.cells[cell_name]
inst = ckt.new_instance(name=cell_name, object_=cell)
lay = layouter.place(inst, x=x, y=0.0)
assert lay.boundary is not None
x = lay.boundary.right
assert lay is not None
assert isinstance(lay.boundary, _geo.Rect)
layouter.layout.boundary = _geo.Rect.from_rect(rect=lay.boundary, left=0.0)
# Create the IORO
ioro = lib.new_cell(name="IORO")
ckt = ioro.new_circuit()
layouter = ioro.new_circuitlayouter()
x = 0.0
lay = None
# First DC cells
for cell_name in ("IOPadVdd", "IOPadVss", "IOPadIOVdd", "IOPadIOVss"):
cell = sky130.iolib.cells[cell_name]
inst = ckt.new_instance(name=f"{cell_name}0", object_=cell)
lay = layouter.place(object_=inst, x=x, y=0.0)
assert lay.boundary is not None
x = lay.boundary.right
# 5 In/Out pairs
cell_in = sky130.iolib.cells["IOPadIn"]
cell_out = sky130.iolib.cells["IOPadOut"]
for i in range(5):
inst = ckt.new_instance(name=f"in{i}", object_=cell_in)
lay = layouter.place(object_=inst, x=x, y=0.0)
assert lay.boundary is not None
x = lay.boundary.right
inst = ckt.new_instance(name=f"out{i}", object_=cell_out)
lay = layouter.place(object_=inst, x=x, y=0.0)
assert lay.boundary is not None
x = lay.boundary.right
# Last DC cells
for cell_name in ("IOPadVdd", "IOPadVss", "IOPadIOVdd", "IOPadIOVss"):
cell = sky130.iolib.cells[cell_name]
inst = ckt.new_instance(name=f"{cell_name}1", object_=cell)
lay = layouter.place(object_=inst, x=x, y=0.0)
assert lay.boundary is not None
x = lay.boundary.right
assert lay is not None
assert isinstance(lay.boundary, _geo.Rect)
layouter.layout.boundary = _geo.Rect.from_rect(rect=lay.boundary, left=0.0)
# Create connected out cell
ioconn = lib.new_cell(name="IOConnected")
ckt = ioconn.new_circuit()
layouter = ioconn.new_circuitlayouter()
inst = ckt.new_instance(name="block", object_=ioblock)
bound = ioblock.layout.boundary
assert isinstance(bound, _geo.Rect)
x = _frm.boundary.center.x - bound.center.x
y = _frm.boundary.top - 500.0
layouter.place(object_=inst, x=x, y=y)
inst = ckt.new_instance(name="ro", object_=ioro)
bound = ioro.layout.boundary
assert isinstance(bound, _geo.Rect)
x = _frm.boundary.center.x - bound.center.x
y = _frm.boundary.top - 1500.0
layouter.place(object_=inst, x=x, y=y)
layouter.layout.boundary = _frm.boundary
# Create user_analog_project_wrapper top cell
top = lib.new_cell(name=top_name)
ckt = top.new_circuit()
layouter = top.new_circuitlayouter()
inst = ckt.new_instance(name="ioconn", object_=ioconn)
layouter.place(inst, x=0.0, y=0.0)
layouter.layout.boundary = _frm.boundary
# Convert to klayout
klay = _klexp.export2db(
obj=lib, add_pin_label=True, gds_layers=sky130.gds_layers, cell_name=None, merge=True,
)
# Post process
# - split tapdiff into tap and diff layers
# - remove pad drawing layer to avoid interacting with RDL
nwell_idx = klay.layer(64, 20)
diff_idx = klay.layer(65, 20)
tap_idx = klay.layer(65, 44)
nsdm_idx = klay.layer(93, 44)
psdm_idx = klay.layer(94, 20)
pad_idx = klay.layer(76, 20)
for cell in klay.each_cell():
nwell = pya.Region(cell.shapes(nwell_idx))
nsdm = pya.Region(cell.shapes(nsdm_idx))
psdm = pya.Region(cell.shapes(psdm_idx))
tap_cover = (nsdm & nwell) + (psdm - nwell)
diff_shapes = cell.shapes(diff_idx)
tap_shapes = cell.shapes(tap_idx)
difftap = pya.Region(cell.shapes(diff_idx)) # Original difftap layer to be split
tap = difftap & tap_cover
diff = difftap - tap
diff_shapes.clear()
diff_shapes.insert(diff)
tap_shapes.insert(tap)
cell.shapes(pad_idx).clear()
# Instantiate the empty cell in top cell
emptylib = pya.Library()
emptylib.register("empty")
emptylayout = emptylib.layout()
emptylayout.read(str(gds_empty))
emptylibcell_idx = emptylayout.cell("user_analog_project_wrapper_empty").cell_index()
emptycell_idx = klay.add_lib_cell(emptylib, emptylibcell_idx)
ktop = klay.cell(top_name)
ktop.insert(pya.CellInstArray(emptycell_idx, pya.Trans.R0))
klay.write(str(gds_out))