Add first version of IO test vehicle.
diff --git a/dodo.py b/dodo.py new file mode 100644 index 0000000..ae069a8 --- /dev/null +++ b/dodo.py
@@ -0,0 +1,30 @@ +# SPDX-License-Identifier: CERN-OHL-S-2.0+ +from os.path import dirname +from pathlib import Path + +DOIT_CONFIG = { + "default_tasks": ["gds"], +} + + +top_dir = Path(dirname(__file__)) + +code_dir = top_dir.joinpath("code") +code_py_files = tuple(code_dir.rglob("*.py")) + +gds_dir = top_dir.joinpath("gds") +gds_empty_file = gds_dir.joinpath("user_analog_project_wrapper_empty.gds") +gds_out_file = gds_dir.joinpath("user_analog_project_wrapper.gds.gz") + +def task_gds(): + """Generating GDS file with joined difftap""" + def run(): + from doitcode.generate import gen_gds + + gen_gds(gds_out=gds_out_file, gds_empty=gds_empty_file) + + return { + "file_dep": code_py_files, + "targets": (gds_out_file,), + "actions": (run,), + } \ No newline at end of file
diff --git a/doitcode/__init__.py b/doitcode/__init__.py new file mode 100644 index 0000000..99aa209 --- /dev/null +++ b/doitcode/__init__.py
@@ -0,0 +1 @@ +# SPDX-License-Identifier: CERN-OHL-S-2.0+
diff --git a/doitcode/frame.py b/doitcode/frame.py new file mode 100644 index 0000000..a3a11fe --- /dev/null +++ b/doitcode/frame.py
@@ -0,0 +1,22 @@ +# SPDX-License-Identifier: CERN-OHL-S-2.0+ +from pdkmaster.technology import geometry as _geo + + +__all__ = ["boundary", "ioanalog"] + + +# The metal3 pins for the IOs +boundary = _geo.Rect(left=0.0, bottom=0.0, right=2920.0, top=3520.0) +ioanalog = ( + _geo.Rect(left=2911.50, bottom=3389.92, right=2924.00, top=3414.92), # 0 + _geo.Rect(left=2832.97, bottom=3511.50, right=2857.97, top=3524.00), # 1 + _geo.Rect(left=2326.97, bottom=3511.50, right=2351.97, top=3524.00), # 2 + _geo.Rect(left=2066.97, bottom=3511.50, right=2091.97, top=3524.00), # 3 + _geo.Rect(left=1594.97, bottom=3511.50, right=1619.97, top=3524.00), # 4 + _geo.Rect(left=1086.47, bottom=3511.50, right=1111.47, top=3524.00), # 5 + _geo.Rect(left=827.97, bottom=3511.50, right=852.97, top=3524.00), # 6 + _geo.Rect(left=600.97, bottom=3511.50, right=625.97, top=3524.00), # 7 + _geo.Rect(left=340.97, bottom=3511.50, right=365.97, top=3524.00), # 8 + _geo.Rect(left=80.97, bottom=3511.50, right=105.97, top=3524.00), # 9 + _geo.Rect(left=-4.00, bottom=3401.21, right=8.50, top=3426.21), # 10 +) \ No newline at end of file
diff --git a/doitcode/generate.py b/doitcode/generate.py new file mode 100644 index 0000000..caf2575 --- /dev/null +++ b/doitcode/generate.py
@@ -0,0 +1,158 @@ +# SPDX-License-Identifier: CERN-OHL-S-2.0+ +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, + ) + + # Split tapdiff into tap and diff layers + 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) + + 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) + + # 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))
diff --git a/gds/user_analog_project_wrapper.gds.gz b/gds/user_analog_project_wrapper.gds.gz new file mode 100644 index 0000000..d1001b8 --- /dev/null +++ b/gds/user_analog_project_wrapper.gds.gz Binary files differ