First connected out version

* Add logic inside IORO signal to enable and drive output of RO.
* Connected input/outputs of instantiated subcells to pins on the top cell.
diff --git a/doitcode/frame.py b/doitcode/frame.py
index fa2256e..885c797 100644
--- a/doitcode/frame.py
+++ b/doitcode/frame.py
@@ -2,21 +2,45 @@
 from pdkmaster.technology import geometry as _geo
 
 
-__all__ = ["boundary", "ioanalog"]
+__all__ = ["boundary", "toppins"]
 
 
 # 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
+toppins = {
+    "io_analog[0]": _geo.Rect(left=2911.50, bottom=3389.92, right=2924.00, top=3414.92),
+    "io_analog[1]": _geo.Rect(left=2832.97, bottom=3511.50, right=2857.97, top=3524.00),
+    "io_analog[2]": _geo.Rect(left=2326.97, bottom=3511.50, right=2351.97, top=3524.00),
+    "io_analog[3]": _geo.Rect(left=2066.97, bottom=3511.50, right=2091.97, top=3524.00),
+    "io_analog[4]": _geo.Rect(left=1594.97, bottom=3511.50, right=1619.97, top=3524.00),
+    "io_analog[5]": _geo.Rect(left=1086.47, bottom=3511.50, right=1111.47, top=3524.00),
+    "io_analog[6]": _geo.Rect(left=827.97, bottom=3511.50, right=852.97, top=3524.00),
+    "io_analog[7]": _geo.Rect(left=600.97, bottom=3511.50, right=625.97, top=3524.00),
+    "io_analog[8]": _geo.Rect(left=340.97, bottom=3511.50, right=365.97, top=3524.00),
+    "io_analog[9]": _geo.Rect(left=80.97, bottom=3511.50, right=105.97, top=3524.00),
+    "io_analog[10]": _geo.Rect(left=-4.00, bottom=3401.21, right=8.50, top=3426.21),
+    "io_oeb[13]": _geo.Rect(left=2917.60, bottom=2947.36, right=2924.00, top=2947.92),
+    "io_out[13]": _geo.Rect(left=2917.60, bottom=2941.45, right=2924.00, top=2942.01),
+    "io_in[13]": _geo.Rect(left=2917.60, bottom=2935.54, right=2924.00, top=2936.10),
+    "io_in_3v3[13]": _geo.Rect(left=2917.60, bottom=2929.63, right=2924.00, top=2930.19),
+    "gpio_noesd[6]": _geo.Rect(left=2917.60, bottom=2923.72, right=2924.00, top=2924.28),
+    "gpio_analog[6]": _geo.Rect(left=2917.60, bottom=2917.81, right=2924.00, top=2918.37),
+    "io_oeb[12]": _geo.Rect(left=2917.60, bottom=2500.25, right=2924.00, top=2500.81),
+    "io_out[12]": _geo.Rect(left=2917.60, bottom=2494.34, right=2924.00, top=2494.90),
+    "io_in[12]": _geo.Rect(left=2917.60, bottom=2488.43, right=2924.00, top=2488.99),
+    "io_in_3v3[12]": _geo.Rect(left=2917.60, bottom=2482.52, right=2924.00, top=2483.08),
+    "gpio_noesd[5]": _geo.Rect(left=2917.60, bottom=2476.61, right=2924.00, top=2477.17),
+    "gpio_analog[5]": _geo.Rect(left=2917.60, bottom=2470.70, right=2924.00, top=2471.26),
+    "io_oeb[11]": _geo.Rect(left=2917.60, bottom=2278.14, right=2924.00, top=2278.70),
+    "io_out[11]": _geo.Rect(left=2917.60, bottom=2272.23, right=2924.00, top=2272.79),
+    "io_in[11]": _geo.Rect(left=2917.60, bottom=2266.32, right=2924.00, top=2266.88),
+    "io_in_3v3[11]": _geo.Rect(left=2917.60, bottom=2260.41, right=2924.00, top=2260.97),
+    "gpio_noesd[4]": _geo.Rect(left=2917.60, bottom=2254.50, right=2924.00, top=2255.06),
+    "gpio_analog[4]": _geo.Rect(left=2917.60, bottom=2248.59, right=2924.00, top=2249.15),
+    "io_oeb[14]": _geo.Rect(left=-4.00, bottom=2528.10, right=2.40, top=2528.66),
+    "io_out[14]": _geo.Rect(left=-4.00, bottom=2534.01, right=2.40, top=2534.57),
+    "io_in[14]": _geo.Rect(left=-4.00, bottom=2539.92, right=2.40, top=2540.48),
+    "io_in_3v3[14]": _geo.Rect(left=-4.00, bottom=2545.83, right=2.40, top=2546.39),
+    "gpio_noesd[7]": _geo.Rect(left=-4.00, bottom=2551.74, right=2.40, top=2552.30),
+    "gpio_analog[7]": _geo.Rect(left=-4.00, bottom=2557.65, right=2.40, top=2558.21),
+}
diff --git a/doitcode/generate.py b/doitcode/generate.py
index a5b823c..04aec13 100644
--- a/doitcode/generate.py
+++ b/doitcode/generate.py
@@ -2,50 +2,184 @@
 # 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
+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 library as _lib
+from pdkmaster.technology import geometry as _geo, primitive as _prm
+from pdkmaster.design import layout as _lay, 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
 
 __all__ = ["gen_gds"]
 
-    lib = _lib.Library(
-        name="MPW4IOTest", tech=sky130.tech, cktfab=sky130.cktfab, layoutfab=sky130.layoutfab,
-    )
 
+# 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
-    for cell_name in (
-        "IOPadIOVss", "IOPadIOVdd", "IOPadIn", "IOPadInOut", "IOPadOut",
-        "IOPadVdd", "IOPadVss",
+    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) + m1.min_space
+    width = d_m1pin_bounds2.right - d_m1pin_bounds1.left
+    _l_via = layouter.wire_layout(
+        net=net, wire=via,
+        bottom_width=width, bottom_enclosure="wide",
+        top_width=width, top_height=m2_width, top_enclosure="wide",
+    )
+    _m1_bounds = _l_via.bounds(mask=m1.mask)
+    x = 0.5*(d_m1pin_bounds2.right + d_m1pin_bounds1.left)
+    y = bottom - _m1_bounds.bottom
+    l_via = layouter.place(_l_via, x=x, y=y)
+    m1_bounds = l_via.bounds(mask=m1.mask)
+    m2_bounds = l_via.bounds(mask=m2.mask)
+    layouter.add_wire(
+        net=net, wire=m1, shape=_geo.Rect.from_rect(rect=d_m1pin_bounds1, top=m1_bounds.top),
+    )
+    layouter.add_wire(
+        net=net, wire=m1, shape=_geo.Rect.from_rect(rect=d_m1pin_bounds2, top=m1_bounds.top),
+    )
+    _l_m2 = layouter.wire_layout(
+        net=net, wire=m2, pin=m2pin, width=m2_bounds.width, height=m2_width,
+    )
+    _m2_bounds = _l_m2.bounds()
+    x = m2_bounds.left - _m2_bounds.left
+    y = m2_bounds.bottom - _m2_bounds.bottom
+    l_m2 = layouter.place(_l_m2, x=x, y=y)
+    dm2_bounds = l_m2.bounds()
+
+    net = nets.de_core
+    bottom = dm2_bounds.top + m2.min_space
+    width = de_m1pin_bounds2.right - de_m1pin_bounds1.left
+    _l_via = layouter.wire_layout(
+        net=net, wire=via,
+        bottom_width=width, bottom_enclosure="wide",
+        top_width=width, top_height=m2_width, top_enclosure="wide",
+    )
+    _m1_bounds = _l_via.bounds(mask=m1.mask)
+    x = 0.5*(de_m1pin_bounds2.right + de_m1pin_bounds1.left)
+    y = bottom - _m1_bounds.bottom
+    l_via = layouter.place(_l_via, x=x, y=y)
+    m1_bounds = l_via.bounds(mask=m1.mask)
+    m2_bounds = l_via.bounds(mask=m2.mask)
+    layouter.add_wire(
+        net=net, wire=m1, shape=_geo.Rect.from_rect(rect=de_m1pin_bounds1, top=m1_bounds.top),
+    )
+    layouter.add_wire(
+        net=net, wire=m1, shape=_geo.Rect.from_rect(rect=de_m1pin_bounds2, top=m1_bounds.top),
+    )
+    _l_m2 = layouter.wire_layout(
+        net=net, wire=m2, pin=m2pin, width=m2_bounds.width, height=m2_width,
+    )
+    _m2_bounds = _l_m2.bounds()
+    x = m2_bounds.left - _m2_bounds.left
+    y = m2_bounds.bottom - _m2_bounds.bottom
+    layouter.place(_l_m2, x=x, y=y)
 
     assert isinstance(lay.boundary, _geo.Rect)
     layouter.layout.boundary = _geo.Rect.from_rect(rect=lay.boundary, left=0.0)
@@ -54,44 +188,409 @@
 
 
 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 in ("IOPadVdd", "IOPadVss", "IOPadIOVdd", "IOPadIOVss"):
+    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=f"{cell_name}0", object_=cell)
-        lay = layouter.place(object_=inst, x=x, y=0.0)
+        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(5):
-        inst = ckt.new_instance(name=f"in{i}", object_=cell_in)
-        lay = layouter.place(object_=inst, x=x, y=0.0)
+    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 = ckt.new_instance(name=f"out{i}", object_=cell_out)
-        lay = layouter.place(object_=inst, x=x, y=0.0)
+        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 in ("IOPadVdd", "IOPadVss", "IOPadIOVdd", "IOPadIOVss"):
+    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=f"{cell_name}1", object_=cell)
-        lay = layouter.place(object_=inst, x=x, y=0.0)
+        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
+    assert (lay is not None) and (lay.boundary is not None)
+    block_top = lay.boundary.top
+    cell_width = x
 
-    assert isinstance(lay.boundary, _geo.Rect)
-    layouter.layout.boundary = _geo.Rect.from_rect(rect=lay.boundary, left=0.0)
+    # 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
+    y = nandlipin_bounds.top - _li_bounds.top
+    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
+    l_via4 = layouter.add_wire(net=net, wire=via4, rows=2, x=x, y=y)
+    m5_bounds1 = l_via4.bounds(mask=m5.mask)
+    _l_via4 = layouter.wire_layout(net=net, wire=via4, rows=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
 
@@ -99,25 +598,472 @@
 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 = _frm.boundary.top - 500.0
-    layouter.place(object_=inst, x=x, y=y)
+    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
-    y = _frm.boundary.top - 1500.0
-    layouter.place(object_=inst, x=x, y=y)
+    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
diff --git a/gds/user_analog_project_wrapper.gds.gz b/gds/user_analog_project_wrapper.gds.gz
index e111994..d9732d8 100644
--- a/gds/user_analog_project_wrapper.gds.gz
+++ b/gds/user_analog_project_wrapper.gds.gz
Binary files differ