| # 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)) |