| # 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 Dict, 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 circuit, layout as _lay, library as _lib |
| from pdkmaster.io.klayout import export as _klexp |
| |
| from c4m.pdk import sky130 |
| |
| from . import frame as _frm |
| |
| |
| __all__ = ["gen_gds"] |
| |
| |
| # Match nets in IOConnected to pins |
| _netlookup = { |
| "vss": "io_analog[0]", |
| "vdd": "io_analog[1]", |
| "ioout_pad": "io_analog[2]", |
| "ioinout_pad": "io_analog[3]", |
| "d_core": "io_analog[4]", |
| "de_core": "io_analog[5]", |
| "ioinout_core": "io_analog[6]", |
| "ioin_core": "io_analog[7]", |
| "ioin_pad": "io_analog[8]", |
| "iovdd": "io_analog[9]", |
| "iovss": "io_analog[10]", |
| "ro_de": "gpio_analog[4]", |
| "ro_en": "gpio_analog[5]", |
| "ro_out": "gpio_noesd[7]", |
| } |
| |
| # Dimensions for top level interconnects |
| _conn_width = 25.0 |
| _conn_space = 2.0 |
| _conn_pitch = _conn_width + _conn_space |
| |
| |
| def _gen_ioblock(*, lib: _lib.Library): |
| tech = lib.tech |
| prims = tech.primitives |
| |
| m1 = cast(_prm.MetalWire, prims.m1) |
| assert m1.pin is not None |
| m1pin = m1.pin[0] |
| via = cast(_prm.Via, prims.via) |
| m2 = cast(_prm.MetalWire, prims.m2) |
| assert m2.pin is not None |
| m2pin = m2.pin[0] |
| m5 = cast(_prm.MetalWire, prims.m5) |
| assert m5.pin is not None |
| m5pin = m5.pin[0] |
| |
| # Create the IOBlock |
| ioblock = lib.new_cell(name="IOBlock") |
| ckt = ioblock.new_circuit() |
| nets = ckt.nets |
| layouter = ioblock.new_circuitlayouter() |
| |
| for net_name in _netlookup.keys(): |
| if net_name.startswith("ro_"): |
| continue |
| ckt.new_net(name=net_name, external=True) |
| |
| x = 0.0 |
| lay = None |
| d_m1pin_bounds1 = d_m1pin_bounds2 = None |
| de_m1pin_bounds1 = de_m1pin_bounds2 = None |
| for (cell_name, top_pad_netname, cell_pad_netname) in ( |
| ("IOPadIOVss", "iovss", "iovss"), |
| ("IOPadIOVdd", "iovdd", "iovdd"), |
| ("IOPadIn", "ioin_pad", "pad"), |
| ("IOPadInOut", "ioinout_pad", "pad"), |
| ("IOPadOut", "ioout_pad", "pad"), |
| ("IOPadVdd", "vdd", "vdd"), |
| ("IOPadVss", "vss", "vss"), |
| ): |
| cell = sky130.iolib.cells[cell_name] |
| inst = ckt.new_instance(name=cell_name, object_=cell) |
| lay = layouter.place(inst, x=x, y=0.0) |
| |
| nets[top_pad_netname].childports += inst.ports[cell_pad_netname] |
| if cell_name == "IOPadIn": |
| nets.ioin_core.childports += inst.ports.s |
| elif cell_name == "IOPadInOut": |
| nets.ioinout_core.childports += inst.ports.s |
| if cell_name in ("IOPadInOut", "IOPadOut"): |
| nets.d_core.childports += inst.ports.d |
| nets.de_core.childports += inst.ports.de |
| m5pin_bounds = lay.bounds(mask=m5pin.mask) |
| layouter.add_wire(net=nets[top_pad_netname], wire=m5, pin=m5pin, shape=m5pin_bounds) |
| |
| if cell_name == "IOPadIn": |
| net = nets.ioin_core |
| m1pin_bounds = lay.bounds(mask=m1pin.mask, net=net, depth=1) |
| layouter.add_wire(net=net, wire=m1, pin=m1pin, shape=m1pin_bounds) |
| elif cell_name == "IOPadInOut": |
| net = nets.ioinout_core |
| m1pin_bounds = lay.bounds(mask=m1pin.mask, net=net, depth=1) |
| layouter.add_wire(net=net, wire=m1, pin=m1pin, shape=m1pin_bounds) |
| |
| d_m1pin_bounds1 = lay.bounds(mask=m1pin.mask, net=nets.d_core, depth=1) |
| de_m1pin_bounds1 = lay.bounds(mask=m1pin.mask, net=nets.de_core, depth=1) |
| elif cell_name == "IOPadOut": |
| d_m1pin_bounds2 = lay.bounds(mask=m1pin.mask, net=nets.d_core, depth=1) |
| de_m1pin_bounds2 = lay.bounds(mask=m1pin.mask, net=nets.de_core, depth=1) |
| |
| assert lay.boundary is not None |
| x = lay.boundary.right |
| assert lay is not None |
| assert d_m1pin_bounds1 is not None |
| assert d_m1pin_bounds2 is not None |
| assert de_m1pin_bounds1 is not None |
| assert de_m1pin_bounds2 is not None |
| |
| # Connect d and de |
| m2_width = tech.computed.min_width(m2, down=True, up=True, min_enclosure=True) |
| |
| net = nets.d_core |
| # bottom = max(d_m1pin_bounds1.top, d_m1pin_bounds2.top) + 2*m2.min_space |
| bottom = max(d_m1pin_bounds1.top, d_m1pin_bounds2.top) + 0.5 # big m3 space rule |
| _l_via = layouter.wire_layout(net=net, wire=via, rows=2) |
| _m1_bounds = _l_via.bounds(mask=m1.mask) |
| x = d_m1pin_bounds1.center.x |
| y = bottom - _m1_bounds.bottom |
| l1_via = layouter.place(_l_via, x=x, y=y) |
| m1_bounds1 = l1_via.bounds(mask=m1.mask) |
| m2_bounds1 = l1_via.bounds(mask=m2.mask) |
| x = d_m1pin_bounds2.center.x |
| l2_via = layouter.place(_l_via, x=x, y=y) |
| m1_bounds2 = l2_via.bounds(mask=m1.mask) |
| m2_bounds2 = l2_via.bounds(mask=m2.mask) |
| |
| rect = _geo.Rect.from_rect(rect=d_m1pin_bounds1, top=m1_bounds1.top) |
| layouter.add_wire(net=net, wire=m1, shape=rect) |
| rect = _geo.Rect.from_rect(rect=d_m1pin_bounds2, top=m1_bounds2.top) |
| layouter.add_wire(net=net, wire=m1, shape=rect) |
| rect = _geo.Rect.from_rect(rect=m2_bounds1, right=m2_bounds2.right) |
| l_m2 = layouter.add_wire(net=net, wire=m2, pin=m2pin, shape=rect) |
| dm2_bounds = l_m2.bounds() |
| |
| net = nets.de_core |
| bottom = dm2_bounds.top + m2.min_space |
| _l_via = layouter.wire_layout(net=net, wire=via, rows=2) |
| _m1_bounds = _l_via.bounds(mask=m1.mask) |
| x = de_m1pin_bounds1.center.x |
| y = bottom - _m1_bounds.bottom |
| l1_via = layouter.place(_l_via, x=x, y=y) |
| m1_bounds1 = l1_via.bounds(mask=m1.mask) |
| m2_bounds1 = l1_via.bounds(mask=m2.mask) |
| x = de_m1pin_bounds2.center.x |
| l2_via = layouter.place(_l_via, x=x, y=y) |
| m1_bounds2 = l2_via.bounds(mask=m1.mask) |
| m2_bounds2 = l2_via.bounds(mask=m2.mask) |
| |
| rect = _geo.Rect.from_rect(rect=de_m1pin_bounds1, top=m1_bounds1.top) |
| layouter.add_wire(net=net, wire=m1, shape=rect) |
| rect = _geo.Rect.from_rect(rect=de_m1pin_bounds2, top=m1_bounds2.top) |
| layouter.add_wire(net=net, wire=m1, shape=rect) |
| rect = _geo.Rect.from_rect(rect=m2_bounds1, right=m2_bounds2.right) |
| layouter.add_wire(net=net, wire=m2, pin=m2pin, shape=rect) |
| |
| assert isinstance(lay.boundary, _geo.Rect) |
| layouter.layout.boundary = _geo.Rect.from_rect(rect=lay.boundary, left=0.0) |
| |
| return ioblock |
| |
| |
| def _gen_ioro(*, lib: _lib.Library): |
| tech = lib.tech |
| prims = tech.primitives |
| |
| li = cast(_prm.MetalWire, prims.li) |
| assert (li.pin is not None) and (len(li.pin) == 1) |
| lipin = li.pin[0] |
| mcon = cast(_prm.Via, prims.mcon) |
| m1 = cast(_prm.MetalWire, prims.m1) |
| assert (m1.pin is not None) and (len(m1.pin) == 1) |
| m1pin = m1.pin[0] |
| via = cast(_prm.Via, prims.via) |
| m2 = cast(_prm.MetalWire, prims.m2) |
| assert (m2.pin is not None) and (len(m2.pin) == 1) |
| m2pin = m2.pin[0] |
| via2 = cast(_prm.Via, prims.via2) |
| via3 = cast(_prm.Via, prims.via3) |
| m4 = cast(_prm.MetalWire, prims.m4) |
| assert (m4.pin is not None) and (len(m4.pin) == 1) |
| m4pin = m4.pin[0] |
| via4 = cast(_prm.Via, prims.via4) |
| m5 = cast(_prm.MetalWire, prims.m5) |
| assert (m5.pin is not None) and (len(m5.pin) == 1) |
| m5pin = m5.pin[0] |
| |
| # Create the IORO |
| ioro = lib.new_cell(name="IORO") |
| ckt = ioro.new_circuit() |
| nets = ckt.nets |
| layouter = ioro.new_circuitlayouter() |
| |
| stages = 5 |
| |
| # External nets |
| for net_name in ("iovdd", "iovss", "vdd", "vss", "ro_de", "ro_en", "ro_out"): |
| ckt.new_net(name=net_name, external=True) |
| # Internal nets |
| for i in range(stages): |
| ckt.new_net(f"stage{i}_3v3", external=False) |
| ckt.new_net(f"stage{i}_1v8", external=False) |
| # Extra net for last d that will come from a NAND gate |
| ckt.new_net(f"stage{stages}_1v8", external=False) |
| |
| # Instantiate all cells and compute cell width |
| x = 0.0 |
| lay = None |
| layouts: Dict[str, _lay._Layout] = {} |
| # First DC cells |
| for cell_name, pad_net_name in ( |
| ("IOPadVdd", "vdd"), |
| ("IOPadVss", "vss"), |
| ("IOPadIOVdd", "iovdd"), |
| ("IOPadIOVss", "iovss"), |
| ): |
| inst_name = f"{cell_name}0" |
| cell = sky130.iolib.cells[cell_name] |
| inst = ckt.new_instance(name=inst_name, object_=cell) |
| nets[pad_net_name].childports += inst.ports[pad_net_name] |
| layouts[inst_name] = 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(stages): |
| conn_net_3v3 = nets[f"stage{i}_3v3"] |
| conn_net_1v8_in = nets[f"stage{i}_1v8"] |
| conn_net_1v8_out = nets[f"stage{i + 1}_1v8"] |
| |
| inst_name = f"in{i}" |
| inst = ckt.new_instance(name=inst_name, object_=cell_in) |
| nets.vss.childports += inst.ports.vss |
| nets.vdd.childports += inst.ports.vdd |
| nets.iovss.childports += inst.ports.iovss |
| nets.iovdd.childports += inst.ports.iovdd |
| conn_net_3v3.childports += inst.ports.pad |
| conn_net_1v8_in.childports += inst.ports.s |
| layouts[inst_name] = lay = layouter.place(object_=inst, x=x, y=0.0) |
| assert lay.boundary is not None |
| |
| if i == 0: |
| # Insert standard cells |
| bottom = lay.boundary.top + 2*m4.min_space |
| |
| buf = sky130.stdcelllib.cells["buf_x4"] |
| assert buf.layout.boundary is not None |
| x2 = lay.boundary.center.x - buf.layout.boundary.right |
| inst_name = "roout_buf" |
| inst2 = ckt.new_instance(name=inst_name, object_=buf) |
| nets.vss.childports += inst2.ports.vss |
| nets.vdd.childports += inst2.ports.vdd |
| conn_net_1v8_in.childports += inst2.ports.i |
| nets.ro_out.childports += inst2.ports.q |
| layouts[inst_name] = lay2 = layouter.place(inst2, x=x2, y=bottom) |
| assert lay2.boundary is not None |
| x2 = lay2.boundary.right |
| |
| nand = sky130.stdcelllib.cells["nand2_x0"] |
| inst_name = "en_nand" |
| inst2 = ckt.new_instance(name=inst_name, object_=nand) |
| nets.vss.childports += inst2.ports.vss |
| nets.vdd.childports += inst2.ports.vdd |
| conn_net_1v8_in.childports += inst2.ports.i0 |
| nets.ro_en.childports += inst2.ports.i1 |
| nets[f"stage{stages}_1v8"].childports += inst2.ports.nq |
| layouts[inst_name] = lay2 = layouter.place(inst2, x=x2, y=bottom) |
| assert lay2.boundary is not None |
| x2 = lay2.boundary.right |
| |
| tie = sky130.stdcelllib.cells["tie"] |
| inst_name = "tie0" |
| inst2 = ckt.new_instance(name=inst_name, object_=tie) |
| nets.vss.childports += inst2.ports.vss |
| nets.vdd.childports += inst2.ports.vdd |
| layouts[inst_name] = lay2 = layouter.place(inst2, x=x2, y=bottom) |
| assert lay2.boundary is not None |
| x2 = lay2.boundary.right |
| inst_name = "tie1" |
| inst2 = ckt.new_instance(name=inst_name, object_=tie) |
| nets.vss.childports += inst2.ports.vss |
| nets.vdd.childports += inst2.ports.vdd |
| layouts[inst_name] = lay2 = layouter.place(inst2, x=x2, y=bottom) |
| |
| x = lay.boundary.right |
| |
| inst_name = f"out{i}" |
| inst = ckt.new_instance(name=inst_name, object_=cell_out) |
| nets.vss.childports += inst.ports.vss |
| nets.vdd.childports += inst.ports.vdd |
| nets.iovss.childports += inst.ports.iovss |
| nets.iovdd.childports += inst.ports.iovdd |
| conn_net_3v3.childports += inst.ports.pad |
| conn_net_1v8_out.childports += inst.ports.d |
| nets.ro_de.childports += inst.ports.de |
| layouts[inst_name] = 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, pad_net_name in ( |
| ("IOPadVdd", "vdd"), |
| ("IOPadVss", "vss"), |
| ("IOPadIOVdd", "iovdd"), |
| ("IOPadIOVss", "iovss"), |
| ): |
| inst_name = f"{cell_name}1" |
| cell = sky130.iolib.cells[cell_name] |
| inst = ckt.new_instance(name=inst_name, object_=cell) |
| nets[pad_net_name].childports += inst.ports[pad_net_name] |
| layouts[inst_name] = 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) and (lay.boundary is not None) |
| block_top = lay.boundary.top |
| cell_width = x |
| |
| # Add DC pins and connect to pads |
| for i, (cell_name, pad_net) in enumerate(( |
| ("IOPadVdd", nets.vdd), |
| ("IOPadVss", nets.vss), |
| ("IOPadIOVdd", nets.iovdd), |
| ("IOPadIOVss", nets.iovss), |
| )): |
| m4_top = -_conn_space - i*_conn_pitch |
| m4_bottom = m4_top - _conn_width |
| rect = _geo.Rect(left=0.0, bottom=m4_bottom, right=cell_width, top=m4_top) |
| layouter.add_wire(net=pad_net, wire=m4, pin=m4pin, shape=rect) |
| |
| for j in range(2): |
| inst_name = f"{cell_name}{j}" |
| lay = layouts[inst_name] |
| m5pin_bounds = lay.bounds(net=pad_net, mask=m5pin.mask) |
| |
| _l_via4 = layouter.wire_layout( |
| net=pad_net, wire=via4, |
| bottom_width=_conn_width, bottom_height=_conn_width, |
| top_width=_conn_width, top_height=_conn_width, |
| ) |
| _m4_bounds = _l_via4.bounds(mask=m4.mask) |
| x = m5pin_bounds.center.x |
| y = m4_top - _m4_bounds.top |
| l_via4 = layouter.place(_l_via4, x=x, y=y) |
| via4m5_bounds = l_via4.bounds(mask=m5.mask) |
| layouter.add_wire(net=pad_net, wire=m5, shape=_geo.Rect.from_rect( |
| rect=via4m5_bounds, top=m5pin_bounds.top, |
| )) |
| cell_bottom = -_conn_space - (4 - 1)*_conn_pitch |
| |
| tie0_bndry = layouts["tie0"].boundary |
| assert tie0_bndry is not None |
| |
| # Draw output pins |
| dem2_bottom = tie0_bndry.top + _conn_space |
| dem2_top = dem2_bottom + _conn_width |
| dem2_rect = _geo.Rect(left=0.0, bottom=dem2_bottom, right=cell_width, top=dem2_top) |
| layouter.add_wire(net=nets.ro_de, wire=m2, pin=m2pin, shape=dem2_rect) |
| |
| roen_bottom = dem2_top + _conn_space |
| roen_top = roen_bottom + _conn_width |
| roen_rect = _geo.Rect(left=0.0, bottom=roen_bottom, right=cell_width, top=roen_top) |
| layouter.add_wire(net=nets.ro_en, wire=m2, pin=m2pin, shape=roen_rect) |
| |
| roout_bottom = roen_top + _conn_space |
| cell_top = roout_top = roout_bottom + _conn_width |
| roout_rect = _geo.Rect(left=0.0, bottom=roout_bottom, right=cell_width, top=roout_top) |
| layouter.add_wire(net=nets.ro_out, wire=m2, pin=m2pin, shape=roout_rect) |
| |
| # Connect de to pin |
| _l_de_via = layouter.wire_layout( |
| net=nets.ro_de, wire=via, bottom_height=_conn_width, top_height=_conn_width, |
| ) |
| _m1_bounds = _l_de_via.bounds(mask=m1.mask) |
| s = dem2_bottom - block_top |
| rect = _geo.Rect.from_rect(rect=_m1_bounds, bottom=(_m1_bounds.bottom - s)) |
| _l_de_via.add_shape(prim=m1, net=nets.ro_de, shape=rect) |
| y_de_via = block_top - rect.bottom |
| for i in range(stages): |
| conn_net_3v3 = nets[f"stage{i}_3v3"] |
| conn_net_1v8_in = nets[f"stage{i}_1v8"] |
| conn_net_1v8_out = nets[f"stage{i + 1}_1v8"] |
| |
| inst_name = f"in{i}" |
| lay = layouts[inst_name] |
| inm5pin_bounds = lay.bounds(mask=m5pin.mask, net=conn_net_3v3) |
| |
| inst_name = f"out{i}" |
| lay = layouts[inst_name] |
| outm5pin_bounds = lay.bounds(mask=m5pin.mask, net=conn_net_3v3) |
| dem1pin_bounds = lay.bounds(mask=m1pin.mask, net=nets.ro_de) |
| assert abs(dem1pin_bounds.top - block_top) < tech.grid |
| x = dem1pin_bounds.center.x |
| y = y_de_via |
| layouter.place(_l_de_via, x=x, y=y) |
| |
| rect = _geo.Rect( |
| left=inm5pin_bounds.right, bottom=inm5pin_bounds.bottom, |
| right=outm5pin_bounds.left, top=inm5pin_bounds.bottom + _conn_width, |
| ) |
| layouter.add_wire(net=conn_net_3v3, wire=m5, shape=rect) |
| rect = _geo.Rect( |
| left=inm5pin_bounds.right, bottom=inm5pin_bounds.top - _conn_width, |
| right=outm5pin_bounds.left, top=inm5pin_bounds.top, |
| ) |
| layouter.add_wire(net=conn_net_3v3, wire=m5, shape=rect) |
| |
| # Connect ro_out |
| net = nets.ro_out |
| rooutlipin_bounds = layouts["roout_buf"].bounds(mask=lipin.mask, net=net, depth=1) |
| _l_mcon = layouter.wire_layout(net=net, wire=mcon, rows=3) |
| _li_bounds = _l_mcon.bounds(mask=li.mask) |
| x = rooutlipin_bounds.center.x |
| y = rooutlipin_bounds.top - _li_bounds.top |
| l_mcon = layouter.place(_l_mcon, x=x, y=y) |
| rooutmconm1_bounds = l_mcon.bounds(mask=m1.mask) |
| _l_via = layouter.wire_layout(net=net, wire=via, top_height=_conn_width) |
| _m2_bounds = _l_via.bounds(mask=m2.mask) |
| x = rooutlipin_bounds.center.x |
| y = roout_bottom - _m2_bounds.bottom |
| l_via = layouter.place(_l_via, x=x, y=y) |
| rooutviam1_bounds = l_via.bounds(mask=m1.mask) |
| rect = _geo.Rect.from_rect(rect=rooutviam1_bounds, bottom=rooutmconm1_bounds.bottom) |
| layouter.add_wire(net=net, wire=m1, shape=rect) |
| |
| # Connect ro_en |
| net = nets.ro_en |
| roenlipin_bounds = layouts["en_nand"].bounds(mask=lipin.mask, net=net, depth=1) |
| _l_mcon = layouter.wire_layout(net=net, wire=mcon, rows=3) |
| _li_bounds = _l_mcon.bounds(mask=li.mask) |
| x = roenlipin_bounds.center.x |
| y = roenlipin_bounds.top - _li_bounds.top |
| l_mcon = layouter.place(_l_mcon, x=x, y=y) |
| roenmconm1_bounds = l_mcon.bounds(mask=m1.mask) |
| _l_via = layouter.wire_layout(net=net, wire=via, top_height=_conn_width) |
| _m2_bounds = _l_via.bounds(mask=m2.mask) |
| x = roenmconm1_bounds.left - _m2_bounds.right |
| y = roen_bottom - _m2_bounds.bottom |
| l_via = layouter.place(_l_via, x=x, y=y) |
| roenviam1_bounds = l_via.bounds(mask=m1.mask) |
| rect = _geo.Rect.from_rect(rect=roenviam1_bounds, bottom=roenmconm1_bounds.bottom) |
| layouter.add_wire(net=net, wire=m1, shape=rect) |
| |
| # Connect stage0_1v8 |
| net = nets.stage0_1v8 |
| |
| padm1pin_bounds = layouts["in0"].bounds(net=net, mask=m1pin.mask, depth=1) |
| buflipin_bounds = layouts["roout_buf"].bounds(net=net, mask=lipin.mask, depth=1) |
| nandlipin_bounds = layouts["en_nand"].bounds(net=net, mask=lipin.mask, depth=1) |
| # Check assumption of placement of cells with regard to pad pin |
| assert buflipin_bounds.center.x < padm1pin_bounds.center.x < nandlipin_bounds.center.x |
| |
| _l_mcon = layouter.wire_layout(net=net, wire=mcon, rows=2) |
| _li_bounds = _l_mcon.bounds(mask=li.mask) |
| bottom = max(buflipin_bounds.bottom, nandlipin_bounds.bottom) |
| y = bottom - _li_bounds.bottom |
| l_mcon1 = layouter.place(_l_mcon, x=buflipin_bounds.center.x, y=y) |
| bufm1_bounds = l_mcon1.bounds(mask=m1.mask) |
| l_mcon2 = layouter.place(_l_mcon, x=nandlipin_bounds.center.x, y=y) |
| nandm1_bounds = l_mcon2.bounds(mask=m1.mask) |
| shape = _geo.Polygon.from_floats(points=( |
| (padm1pin_bounds.left, padm1pin_bounds.top), |
| (padm1pin_bounds.left, bottom), |
| (bufm1_bounds.left, bottom), |
| (bufm1_bounds.left, bufm1_bounds.top), |
| (nandm1_bounds.right, bufm1_bounds.top), |
| (nandm1_bounds.right, bottom), |
| (padm1pin_bounds.right, bottom), |
| (padm1pin_bounds.right, padm1pin_bounds.top), |
| (padm1pin_bounds.left, padm1pin_bounds.top), |
| )) |
| layouter.add_wire(net=net, wire=m1, shape=shape) |
| |
| # Connect stage{stages}_1v8 |
| net = nets[f"stage{stages}_1v8"] |
| |
| padm1pin_bounds = layouts[f"out{stages - 1}"].bounds(net=net, mask=m1pin.mask, depth=1) |
| nandlipin_bounds = layouts["en_nand"].bounds(net=net, mask=lipin.mask) |
| # Check assumptions of placement of pin |
| assert nandlipin_bounds.right < padm1pin_bounds.left |
| |
| _l_mcon = layouter.wire_layout(net=net, wire=mcon, rows=2) |
| _li_bounds = _l_mcon.bounds(mask=li.mask) |
| x = nandlipin_bounds.center.x |
| # Shift a little below highest possible top to avoid being too close to vdd |
| # connection via |
| y = nandlipin_bounds.top - _li_bounds.top - m2.min_space |
| l_mcon = layouter.place(_l_mcon, x=x, y=y) |
| nandm1_bounds = l_mcon.bounds(mask=m1.mask) |
| x = nandm1_bounds.center.x |
| y = nandm1_bounds.center.y |
| l1_via = layouter.add_wire(net=net, wire=via, rows=2, x=x, y=y) |
| m2_bounds1 = l1_via.bounds(mask=m2.mask) |
| x = padm1pin_bounds.center.x |
| l2_via = layouter.add_wire(net=net, wire=via, rows=2, x=x, y=y) |
| m1_bounds2 = l2_via.bounds(mask=m1.mask) |
| m2_bounds2 = l2_via.bounds(mask=m2.mask) |
| rect = _geo.Rect.from_rect(rect=m2_bounds1, right=m2_bounds2.right) |
| layouter.add_wire(net=net, wire=m2, shape=rect) |
| rect = _geo.Rect.from_rect(rect=padm1pin_bounds, top=m1_bounds2.top) |
| layouter.add_wire(net=net, wire=m1, shape=rect) |
| |
| layouter.add_wire(net=net, wire=m1, shape=shape) |
| |
| # Connect stage{n}_1v8 n=1..(stages - 1) |
| for i in range(1, stages): |
| net = nets[f"stage{i}_1v8"] |
| |
| outpadm1pin_bounds = layouts[f"out{i - 1}"].bounds(net=net, mask=m1pin.mask, depth=1) |
| inpadm1pin_bounds = layouts[f"in{i}"].bounds(net=net, mask=m1pin.mask, depth=1) |
| # Check assumptions |
| assert outpadm1pin_bounds.right < inpadm1pin_bounds.left |
| |
| _l_via = layouter.wire_layout(net=net, wire=via, rows=2) |
| _m2_bounds = _l_via.bounds(mask=m2.mask) |
| y = block_top + 2*m2.min_space - _m2_bounds.bottom |
| l1_via = layouter.place(_l_via, x=outpadm1pin_bounds.center.x, y=y) |
| m1_bounds1 = l1_via.bounds(mask=m1.mask) |
| m2_bounds1 = l1_via.bounds(mask=m2.mask) |
| l2_via = layouter.place(_l_via, x=inpadm1pin_bounds.center.x, y=y) |
| m1_bounds2 = l2_via.bounds(mask=m1.mask) |
| m2_bounds2 = l2_via.bounds(mask=m2.mask) |
| |
| rect = _geo.Rect.from_rect(rect=outpadm1pin_bounds, top=m1_bounds1.top) |
| layouter.add_wire(net=net, wire=m1, shape=rect) |
| rect = _geo.Rect.from_rect(rect=inpadm1pin_bounds, top=m1_bounds2.top) |
| layouter.add_wire(net=net, wire=m1, shape=rect) |
| rect = _geo.Rect.from_rect(rect=m2_bounds1, right=m2_bounds2.right) |
| layouter.add_wire(net=net, wire=m2, shape=rect) |
| |
| # Connect vss/vdd of standard cells |
| net = nets.vss |
| vsstielipin_bounds = layouts["tie0"].bounds(net=net, mask=lipin.mask) |
| vsspadm4pin_bounds = layouts["in0"].bounds(net=net, mask=m4pin.mask) |
| x = vsstielipin_bounds.center.x |
| y = vsstielipin_bounds.center.y |
| h = vsstielipin_bounds.height |
| for via_layer in (mcon, via, via2, via3): |
| l_via = layouter.add_wire( |
| net=net, wire=via_layer, bottom_height=h, top_height=h, x=x, y=y, |
| ) |
| m4_bounds = l_via.bounds(mask=m4.mask) |
| rect = _geo.Rect.from_rect(rect=m4_bounds, bottom=vsspadm4pin_bounds.bottom) |
| layouter.add_wire(net=net, wire=m4, shape=rect) |
| |
| net = nets.vdd |
| vddtielipin_bounds = layouts["tie1"].bounds(net=net, mask=lipin.mask) |
| vddpadm4pin_bounds = layouts["in0"].bounds(net=net, mask=m4pin.mask) |
| x = vddtielipin_bounds.center.x |
| y = vddtielipin_bounds.center.y |
| h = vddtielipin_bounds.height |
| for via_layer in (mcon, via, via2, via3): |
| l_via = layouter.add_wire( |
| net=net, wire=via_layer, bottom_height=h, top_height=h, x=x, y=y, |
| ) |
| # via4 needs bigger line |
| # Use 2 columns so width is > min_width |
| l_via4 = layouter.add_wire(net=net, wire=via4, columns=2, x=x, y=y) |
| m5_bounds1 = l_via4.bounds(mask=m5.mask) |
| _l_via4 = layouter.wire_layout(net=net, wire=via4, columns=2) |
| _m4_bounds = _l_via4.bounds(mask=m4.mask) |
| y = vddpadm4pin_bounds.top - _m4_bounds.top |
| l_via4 = layouter.place(_l_via4, x=x, y=y) |
| m5_bounds2 = l_via4.bounds(mask=m5.mask) |
| rect = _geo.Rect.from_rect(rect=m5_bounds1, bottom=m5_bounds2.bottom) |
| layouter.add_wire(net=net, wire=m5, shape=rect) |
| |
| # Set set boundary |
| bndry = _geo.Rect(left=0.0, bottom=cell_bottom, right=cell_width, top=cell_top) |
| layouter.layout.boundary = bndry |
| |
| return ioro |
| |
| |
| def _gen_ioconn(*, |
| lib: _lib.Library, ioblock: _lib._Cell[_lib.Library], ioro: _lib._Cell[_lib.Library], |
| ): |
| tech = lib.tech |
| prims = tech.primitives |
| |
| m1 = cast(_prm.MetalWire, prims.m1) |
| assert (m1.pin is not None) and (len(m1.pin) == 1) |
| m1pin = m1.pin[0] |
| via = cast(_prm.Via, prims.via) |
| m2 = cast(_prm.MetalWire, prims.m2) |
| assert (m2.pin is not None) and (len(m2.pin) == 1) |
| m2pin = m2.pin[0] |
| via2 = cast(_prm.Via, prims.via2) |
| m3 = cast(_prm.MetalWire, prims.m3) |
| assert (m3.pin is not None) and (len(m3.pin) == 1) |
| m3pin = m3.pin[0] |
| via3 = cast(_prm.Via, prims.via3) |
| m4 = cast(_prm.MetalWire, prims.m4) |
| assert (m4.pin is not None) and (len(m4.pin) == 1) |
| m4pin = m4.pin[0] |
| via4 = cast(_prm.Via, prims.via4) |
| m5 = cast(_prm.MetalWire, prims.m5) |
| assert (m5.pin is not None) and (len(m5.pin) == 1) |
| m5pin = m5.pin[0] |
| |
| conn_top = 3500.0 |
| |
| # Create connected out cell |
| ioconn = lib.new_cell(name="IOConnected") |
| ckt = ioconn.new_circuit() |
| nets = ckt.nets |
| layouter = ioconn.new_circuitlayouter() |
| |
| # Create the top nets |
| for net_name in _netlookup.keys(): |
| ckt.new_net(name=net_name, external=True) |
| |
| # assign nets to different horizontal m4 lanes, some at same height |
| n_conn = { |
| "iovdd": 0, |
| "vdd": 0, |
| "ioin_pad": 1, |
| "ioout_pad": 1, |
| "ioin_core": 2, |
| "ioinout_pad": 2, |
| "ioinout_core": 3, |
| "d_core": 4, |
| "de_core": 4, |
| } |
| conns = max(n_conn.values()) + 1 |
| blk_top = conn_top - conns*_conn_pitch |
| |
| inst = ckt.new_instance(name="block", object_=ioblock) |
| for net_name in _netlookup.keys(): |
| if net_name.startswith("ro_"): |
| continue |
| nets[net_name].childports += inst.ports[net_name] |
| bound = ioblock.layout.boundary |
| assert isinstance(bound, _geo.Rect) |
| x = _frm.boundary.center.x - bound.center.x |
| y = blk_top - bound.top |
| l_block = layouter.place(inst, x=x, y=y) |
| |
| # Connect other bond pads |
| for net_name in ("iovdd", "ioin_pad", "ioinout_pad", "ioout_pad", "vdd"): |
| net = nets[net_name] |
| pinrect = _frm.toppins[_netlookup[net_name]] |
| blkpinm5_bounds = l_block.bounds(net=net, mask=m5pin.mask) |
| lane = n_conn[net_name] |
| m4_top = conn_top - lane*_conn_pitch |
| conn_right = pinrect.right < blkpinm5_bounds.left |
| if not conn_right: |
| assert pinrect.left > blkpinm5_bounds.right |
| |
| layouter.add_wire(net=net, wire=m3, pin=m3pin, shape=pinrect) |
| _l_via3 = layouter.wire_layout( |
| net=net, wire=via3, |
| bottom_width=_conn_width, bottom_height=_conn_width, |
| top_width=_conn_width, top_height=_conn_width, |
| ) |
| _m4_bounds = _l_via3.bounds(mask=m4.mask) |
| x = pinrect.center.x |
| y = m4_top - _m4_bounds.top |
| l_via3 = layouter.place(_l_via3, x=x, y=y) |
| via3m3_bounds = l_via3.bounds(mask=m3.mask) |
| via3m4_bounds = l_via3.bounds(mask=m4.mask) |
| layouter.add_wire(net=net, wire=m3, shape=_geo.Rect.from_rect( |
| rect=via3m3_bounds, top=pinrect.bottom, |
| )) |
| _l_via4 = layouter.wire_layout( |
| net=net, wire=via4, |
| bottom_width=_conn_width, bottom_height=_conn_width, |
| top_width=_conn_width, top_height=_conn_width, |
| ) |
| _m4_bounds = _l_via4.bounds(mask=m4.mask) |
| _m5_bounds = _l_via4.bounds(mask=m5.mask) |
| x = blkpinm5_bounds.center.x |
| y = m4_top - _m4_bounds.top |
| l_via4 = layouter.place(_l_via4, x=x, y=y) |
| via4m4_bounds = l_via4.bounds(mask=m4.mask) |
| via4m5_bounds = l_via4.bounds(mask=m5.mask) |
| if conn_right: |
| layouter.add_wire(net=net, wire=m4, shape=_geo.Rect.from_rect( |
| rect=via3m4_bounds, right=via4m4_bounds.right, |
| )) |
| else: |
| layouter.add_wire(net=net, wire=m4, shape=_geo.Rect.from_rect( |
| rect=via3m4_bounds, left=via4m4_bounds.left, |
| )) |
| layouter.add_wire(net=net, wire=m5, shape=_geo.Rect.from_rect( |
| rect=via4m5_bounds, bottom=blkpinm5_bounds.bottom, |
| )) |
| |
| # Connect core pins |
| for net_name in ("ioin_core", "ioinout_core"): |
| net = nets[net_name] |
| pinrect = _frm.toppins[_netlookup[net_name]] |
| blkpinm1_bounds = l_block.bounds(net=net, mask=m1pin.mask) |
| blkm2_bounds = l_block.bounds(mask=m2.mask) |
| lane = n_conn[net_name] |
| m4_top = conn_top - lane*_conn_pitch |
| conn_right = pinrect.right < blkpinm1_bounds.left |
| if not conn_right: |
| assert pinrect.left > blkpinm1_bounds.right |
| |
| layouter.add_wire(net=net, wire=m3, pin=m3pin, shape=pinrect) |
| _l_via3 = layouter.wire_layout( |
| net=net, wire=via3, |
| bottom_width=_conn_width, bottom_height=_conn_width, |
| top_width=_conn_width, top_height=_conn_width, |
| ) |
| _m4_bounds = _l_via3.bounds(mask=m4.mask) |
| x = pinrect.center.x |
| y = m4_top - _m4_bounds.top |
| l_via3 = layouter.place(_l_via3, x=x, y=y) |
| via3m3_bounds = l_via3.bounds(mask=m3.mask) |
| via3m4_bounds = l_via3.bounds(mask=m4.mask) |
| layouter.add_wire(net=net, wire=m3, shape=_geo.Rect.from_rect( |
| rect=via3m3_bounds, top=pinrect.bottom, |
| )) |
| _l_blkvia = layouter.wire_layout( |
| net=net, wire=via, columns=10, rows=10, |
| ) |
| _m2_bounds = _l_blkvia.bounds(mask=m2.mask) |
| x = blkpinm1_bounds.center.x |
| y = blkm2_bounds.top + 2*m2.min_space - _m2_bounds.bottom |
| l_blkvia = layouter.place(_l_blkvia, x=x, y=y) |
| blkviam1_bounds = l_blkvia.bounds(mask=m2.mask) |
| blkviam2_bounds = l_blkvia.bounds(mask=m2.mask) |
| layouter.add_wire(net=net, wire=m1, shape=_geo.Rect.from_rect( |
| rect=blkpinm1_bounds, top=blkviam1_bounds.top, |
| )) |
| _l_blkvia2 = layouter.wire_layout( |
| net=net, wire=via2, columns=20, rows=20, |
| ) |
| _m2_bounds = _l_blkvia2.bounds(mask=m2.mask) |
| x = blkpinm1_bounds.center.x |
| y = blkviam2_bounds.top - _m2_bounds.bottom |
| l_blkvia2 = layouter.place(_l_blkvia2, x=x, y=y) |
| blkvia2m3_bounds = l_blkvia2.bounds(mask=m3.mask) |
| layouter.add_wire(net=net, wire=m3, shape=_geo.Rect.from_rect( |
| rect=blkvia2m3_bounds, top=m4_top, |
| )) |
| _l_blkvia3 = layouter.wire_layout( |
| net=net, wire=via3, |
| bottom_width=blkvia2m3_bounds.width, bottom_height=_conn_width, |
| top_width=blkvia2m3_bounds.width, top_height=_conn_width, |
| ) |
| _m4_bounds = _l_blkvia3.bounds(mask=m4.mask) |
| x = blkpinm1_bounds.center.x |
| y = m4_top - _m4_bounds.top |
| l_blkvia3 = layouter.place(_l_blkvia3, x=x, y=y) |
| blkvia3m4_bounds = l_blkvia3.bounds(mask=m4.mask) |
| if conn_right: |
| layouter.add_wire(net=net, wire=m4, shape=_geo.Rect.from_rect( |
| rect=via3m4_bounds, right=blkvia3m4_bounds.right, |
| )) |
| else: |
| layouter.add_wire(net=net, wire=m4, shape=_geo.Rect.from_rect( |
| rect=via3m4_bounds, left=blkvia3m4_bounds.left, |
| )) |
| |
| # Connect block m2 pins |
| for net_name in ("d_core", "de_core"): |
| net = nets[net_name] |
| pinrect = _frm.toppins[_netlookup[net_name]] |
| blkpinm2_bounds = l_block.bounds(net=net, mask=m2pin.mask) |
| lane = n_conn[net_name] |
| m4_top = conn_top - lane*_conn_pitch |
| conn_right = pinrect.right < blkpinm2_bounds.left |
| if not conn_right: |
| assert pinrect.left > blkpinm2_bounds.right |
| |
| layouter.add_wire(net=net, wire=m3, pin=m3pin, shape=pinrect) |
| _l_via3 = layouter.wire_layout( |
| net=net, wire=via3, |
| bottom_width=_conn_width, bottom_height=_conn_width, |
| top_width=_conn_width, top_height=_conn_width, |
| ) |
| _m4_bounds = _l_via3.bounds(mask=m4.mask) |
| x = pinrect.center.x |
| y = m4_top - _m4_bounds.top |
| l_via3 = layouter.place(_l_via3, x=x, y=y) |
| via3m3_bounds = l_via3.bounds(mask=m3.mask) |
| via3m4_bounds = l_via3.bounds(mask=m4.mask) |
| layouter.add_wire(net=net, wire=m3, shape=_geo.Rect.from_rect( |
| rect=via3m3_bounds, top=pinrect.bottom, |
| )) |
| _l_blkvia2 = layouter.wire_layout(net=net, wire=via2, columns=20, bottom_enclosure="wide") |
| _m2_bounds = _l_blkvia2.bounds(mask=m2.mask) |
| if conn_right: |
| x = blkpinm2_bounds.left - _m2_bounds.left |
| else: |
| x = blkpinm2_bounds.right - _m2_bounds.right |
| y = blkpinm2_bounds.center.y |
| l_blkvia2 = layouter.place(_l_blkvia2, x=x, y=y) |
| blkvia2m3_bounds = l_blkvia2.bounds(mask=m3.mask) |
| layouter.add_wire(net=net, wire=m3, shape=_geo.Rect.from_rect( |
| rect=blkvia2m3_bounds, top=m4_top, |
| )) |
| _l_blkvia3 = layouter.wire_layout( |
| net=net, wire=via3, |
| bottom_width=blkvia2m3_bounds.width, bottom_height=_conn_width, |
| top_width=blkvia2m3_bounds.width, top_height=_conn_width, |
| ) |
| _m4_bounds = _l_blkvia3.bounds(mask=m4.mask) |
| x = blkvia2m3_bounds.center.x |
| y = m4_top - _m4_bounds.top |
| l_blkvia3 = layouter.place(_l_blkvia3, x=x, y=y) |
| blkvia3m4_bounds = l_blkvia3.bounds(mask=m4.mask) |
| if conn_right: |
| layouter.add_wire(net=net, wire=m4, shape=_geo.Rect.from_rect( |
| rect=via3m4_bounds, right=blkvia3m4_bounds.right, |
| )) |
| else: |
| layouter.add_wire(net=net, wire=m4, shape=_geo.Rect.from_rect( |
| rect=via3m4_bounds, left=blkvia3m4_bounds.left, |
| )) |
| |
| inst = ckt.new_instance(name="ro", object_=ioro) |
| for net_name in _netlookup.keys(): |
| if not ( |
| net_name.startswith("ro_") |
| or net_name.endswith("vss") |
| or net_name.endswith("vdd") |
| ): |
| continue |
| nets[net_name].childports += inst.ports[net_name] |
| bound = ioro.layout.boundary |
| assert isinstance(bound, _geo.Rect) |
| x = _frm.boundary.center.x - bound.center.x |
| assert l_block.boundary is not None |
| y = l_block.boundary.bottom - _conn_space - bound.top |
| l_ro = layouter.place(object_=inst, x=x, y=y) |
| |
| # Connect iovss |
| net_name = "iovss" |
| net = nets[net_name] |
| pinrect = _frm.toppins[_netlookup[net_name]] |
| blkpinm5_bounds = l_block.bounds(net=net, mask=m5pin.mask, depth=1) |
| ropinm4_bounds = l_ro.bounds(net=net, mask=m4pin.mask, depth=1) |
| |
| layouter.add_wire(net=net, wire=m3, pin=m3pin, shape=pinrect) |
| |
| _l_via3 = layouter.wire_layout( |
| net=net, wire=via3, |
| bottom_width=_conn_width, bottom_height=_conn_width, |
| top_width=_conn_width, top_height=_conn_width, |
| ) |
| _m3_bounds = _l_via3.bounds(mask=m3.mask) |
| _m4_bounds = _l_via3.bounds(mask=m4.mask) |
| x = pinrect.right + _conn_width - _m3_bounds.left |
| y = blkpinm5_bounds.top - _m4_bounds.top |
| l_via3 = layouter.place(_l_via3, x=x, y=y) |
| via3m4_bounds = l_via3.bounds(mask=m4.mask) |
| rect = _geo.Rect.from_rect(rect=via3m4_bounds, right=blkpinm5_bounds.left) |
| layouter.add_wire(net=net, wire=m4, shape=rect) |
| y = ropinm4_bounds.center.y |
| l_via3 = layouter.place(_l_via3, x=x, y=y) |
| via3m3_bounds = l_via3.bounds(mask=m3.mask) |
| via3m4_bounds = l_via3.bounds(mask=m4.mask) |
| rect = _geo.Rect.from_rect(rect=ropinm4_bounds, left=via3m4_bounds.left) |
| layouter.add_wire(net=net, wire=m4, shape=rect) |
| |
| shape = _geo.Polygon.from_floats(points=( |
| (via3m3_bounds.left, via3m3_bounds.bottom), |
| (via3m3_bounds.left, pinrect.bottom), |
| (pinrect.left, pinrect.bottom), |
| (pinrect.left, pinrect.top), |
| (via3m3_bounds.right, pinrect.top), |
| (via3m3_bounds.right, via3m3_bounds.bottom), |
| (via3m3_bounds.left, via3m3_bounds.bottom), |
| )) |
| layouter.add_wire(net=net, wire=m3, shape=shape) |
| |
| # Connect vss |
| net_name = "vss" |
| net = nets[net_name] |
| pinrect = _frm.toppins[_netlookup[net_name]] |
| blkpinm5_bounds = l_block.bounds(net=net, mask=m5pin.mask, depth=1) |
| ropinm4_bounds = l_ro.bounds(net=net, mask=m4pin.mask, depth=1) |
| |
| layouter.add_wire(net=net, wire=m3, pin=m3pin, shape=pinrect) |
| |
| _l_via3 = layouter.wire_layout( |
| net=net, wire=via3, |
| bottom_width=_conn_width, bottom_height=_conn_width, |
| top_width=_conn_width, top_height=_conn_width, |
| ) |
| _m3_bounds = _l_via3.bounds(mask=m3.mask) |
| _m4_bounds = _l_via3.bounds(mask=m4.mask) |
| x = pinrect.left - _conn_width - _m3_bounds.right |
| y = blkpinm5_bounds.top - _m4_bounds.top |
| l_via3 = layouter.place(_l_via3, x=x, y=y) |
| via3m4_bounds = l_via3.bounds(mask=m4.mask) |
| rect = _geo.Rect.from_rect(rect=via3m4_bounds, left=blkpinm5_bounds.right) |
| layouter.add_wire(net=net, wire=m4, shape=rect) |
| y = ropinm4_bounds.center.y |
| l_via3 = layouter.place(_l_via3, x=x, y=y) |
| via3m3_bounds = l_via3.bounds(mask=m3.mask) |
| via3m4_bounds = l_via3.bounds(mask=m4.mask) |
| rect = _geo.Rect.from_rect(rect=ropinm4_bounds, right=via3m4_bounds.right) |
| layouter.add_wire(net=net, wire=m4, shape=rect) |
| |
| shape = _geo.Polygon.from_floats(points=( |
| (via3m3_bounds.left, via3m3_bounds.bottom), |
| (via3m3_bounds.left, pinrect.top), |
| (pinrect.left, pinrect.top), |
| (pinrect.left, pinrect.bottom), |
| (via3m3_bounds.right, pinrect.bottom), |
| (via3m3_bounds.right, via3m3_bounds.bottom), |
| (via3m3_bounds.left, via3m3_bounds.bottom), |
| )) |
| layouter.add_wire(net=net, wire=m3, shape=shape) |
| |
| # Connect second iovdd |
| net_name = "iovdd" |
| net = nets[net_name] |
| pinrect = _frm.toppins[_netlookup[net_name]] |
| ropinm4_bounds = l_ro.bounds(net=net, mask=m4pin.mask, depth=1) |
| |
| l_via3 = layouter.add_wire( |
| net=net, wire=via3, x=pinrect.center.x, y=ropinm4_bounds.center.y, |
| bottom_width=_conn_width, bottom_height=_conn_width, |
| top_width=_conn_width, top_height=_conn_width, |
| ) |
| via3m3_bounds = l_via3.bounds(mask=m3.mask) |
| via3m4_bounds = l_via3.bounds(mask=m4.mask) |
| rect = _geo.Rect.from_rect(rect=pinrect, bottom=via3m3_bounds.bottom) |
| layouter.add_wire(net=net, wire=m3, shape=rect) |
| rect = _geo.Rect.from_rect(rect=ropinm4_bounds, left=via3m4_bounds.left) |
| layouter.add_wire(net=net, wire=m4, shape=rect) |
| |
| # Connect second vdd |
| net_name = "vdd" |
| net = nets[net_name] |
| pinrect = _frm.toppins[_netlookup[net_name]] |
| ropinm4_bounds = l_ro.bounds(net=net, mask=m4pin.mask, depth=1) |
| |
| l_via3 = layouter.add_wire( |
| net=net, wire=via3, x=pinrect.center.x, y=ropinm4_bounds.center.y, |
| bottom_width=_conn_width, bottom_height=_conn_width, |
| top_width=_conn_width, top_height=_conn_width, |
| ) |
| via3m3_bounds = l_via3.bounds(mask=m3.mask) |
| via3m4_bounds = l_via3.bounds(mask=m4.mask) |
| rect = _geo.Rect.from_rect(rect=pinrect, bottom=via3m3_bounds.bottom) |
| layouter.add_wire(net=net, wire=m3, shape=rect) |
| rect = _geo.Rect.from_rect(rect=ropinm4_bounds, right=via3m4_bounds.right) |
| layouter.add_wire(net=net, wire=m4, shape=rect) |
| |
| # Connect ro_en |
| net_name = "ro_en" |
| net = nets[net_name] |
| pinrect = _frm.toppins[_netlookup[net_name]] |
| ropinm2_bounds = l_ro.bounds(net=net, mask=m2pin.mask, depth=1) |
| # Assumptions |
| assert pinrect.top < ropinm2_bounds.bottom |
| assert pinrect.left > ropinm2_bounds.right |
| |
| layouter.add_wire(net=net, wire=m3, pin=m3pin, shape=pinrect) |
| |
| x = pinrect.left - _conn_width |
| l_via2 = layouter.add_wire( |
| net=net, wire=via2, x=x, y=pinrect.center.y, |
| bottom_width=_conn_width, bottom_height=_conn_width, |
| top_width=_conn_width, top_height=_conn_width, |
| ) |
| m2_bounds = l_via2.bounds(mask=m2.mask) |
| m3_bounds = l_via2.bounds(mask=m3.mask) |
| rect = _geo.Rect.from_rect(rect=pinrect, left=m3_bounds.left) |
| layouter.add_wire(net=net, wire=m3, shape=rect) |
| shape = _geo.Polygon.from_floats(points=( |
| (m2_bounds.left, m2_bounds.bottom), |
| (m2_bounds.left, ropinm2_bounds.bottom), |
| (ropinm2_bounds.left, ropinm2_bounds.bottom), |
| (ropinm2_bounds.left, ropinm2_bounds.top), |
| (m2_bounds.right, ropinm2_bounds.top), |
| (m2_bounds.right, m2_bounds.bottom), |
| (m2_bounds.left, m2_bounds.bottom), |
| )) |
| layouter.add_wire(net=net, wire=m2, shape=shape) |
| |
| # Connect ro_de |
| net_name = "ro_de" |
| net = nets[net_name] |
| pinrect = _frm.toppins[_netlookup[net_name]] |
| ropinm2_bounds = l_ro.bounds(net=net, mask=m2pin.mask, depth=1) |
| # Assumptions |
| assert pinrect.top < ropinm2_bounds.bottom |
| assert pinrect.left > ropinm2_bounds.right |
| |
| layouter.add_wire(net=net, wire=m3, pin=m3pin, shape=pinrect) |
| |
| x = pinrect.left - _conn_pitch - _conn_width |
| l_via2 = layouter.add_wire( |
| net=net, wire=via2, x=x, y=pinrect.center.y, |
| bottom_width=_conn_width, bottom_height=_conn_width, |
| top_width=_conn_width, top_height=_conn_width, |
| ) |
| m2_bounds = l_via2.bounds(mask=m2.mask) |
| m3_bounds = l_via2.bounds(mask=m3.mask) |
| rect = _geo.Rect.from_rect(rect=pinrect, left=m3_bounds.left) |
| layouter.add_wire(net=net, wire=m3, shape=rect) |
| shape = _geo.Polygon.from_floats(points=( |
| (m2_bounds.left, m2_bounds.bottom), |
| (m2_bounds.left, ropinm2_bounds.bottom), |
| (ropinm2_bounds.left, ropinm2_bounds.bottom), |
| (ropinm2_bounds.left, ropinm2_bounds.top), |
| (m2_bounds.right, ropinm2_bounds.top), |
| (m2_bounds.right, m2_bounds.bottom), |
| (m2_bounds.left, m2_bounds.bottom), |
| )) |
| layouter.add_wire(net=net, wire=m2, shape=shape) |
| |
| # Connect ro_out |
| net_name = "ro_out" |
| net = nets[net_name] |
| pinrect = _frm.toppins[_netlookup[net_name]] |
| ropinm2_bounds = l_ro.bounds(net=net, mask=m2pin.mask, depth=1) |
| # Assumptions |
| assert pinrect.top < ropinm2_bounds.bottom |
| assert pinrect.right < ropinm2_bounds.left |
| |
| layouter.add_wire(net=net, wire=m3, pin=m3pin, shape=pinrect) |
| |
| x = pinrect.right + _conn_width |
| l_via2 = layouter.add_wire( |
| net=net, wire=via2, x=x, y=pinrect.center.y, |
| bottom_width=_conn_width, bottom_height=_conn_width, |
| top_width=_conn_width, top_height=_conn_width, |
| ) |
| m2_bounds = l_via2.bounds(mask=m2.mask) |
| m3_bounds = l_via2.bounds(mask=m3.mask) |
| rect = _geo.Rect.from_rect(rect=pinrect, right=m3_bounds.right) |
| layouter.add_wire(net=net, wire=m3, shape=rect) |
| shape = _geo.Polygon.from_floats(points=( |
| (m2_bounds.left, m2_bounds.bottom), |
| (m2_bounds.left, ropinm2_bounds.top), |
| (ropinm2_bounds.left, ropinm2_bounds.top), |
| (ropinm2_bounds.left, ropinm2_bounds.bottom), |
| (m2_bounds.right, ropinm2_bounds.bottom), |
| (m2_bounds.right, m2_bounds.bottom), |
| (m2_bounds.left, m2_bounds.bottom), |
| )) |
| layouter.add_wire(net=net, wire=m2, shape=shape) |
| |
| # Set boundary |
| layouter.layout.boundary = _frm.boundary |
| |
| return ioconn |
| |
| |
| def _postprocess(*, klay: pya.Layout): |
| # 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() |
| |
| |
| def gen_gds(*, gds_out: Path, gds_empty: Path): |
| top_name = "user_analog_project_wrapper" |
| |
| lib = _lib.Library( |
| name="MPW4IOTest", tech=sky130.tech, cktfab=sky130.cktfab, layoutfab=sky130.layoutfab, |
| ) |
| |
| ioblock = _gen_ioblock(lib=lib) |
| ioro = _gen_ioro(lib=lib) |
| ioconn = _gen_ioconn(lib=lib, ioblock=ioblock, ioro=ioro) |
| |
| # 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, |
| ) |
| |
| _postprocess(klay=klay) |
| |
| # 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)) |
| |
| ktech = pya.Technology.technology_by_name("Skywater_S8") |
| ksaveopts = ktech.save_layout_options.dup() |
| ksaveopts.write_context_info = False |
| klay.write(str(gds_out), ksaveopts) |