Replace the SRAM structure with characterization structure.
diff --git a/doitcode/sram.py b/doitcode/sram.py
index cfde4a4..bc0b657 100644
--- a/doitcode/sram.py
+++ b/doitcode/sram.py
@@ -58,36 +58,31 @@
             return None
 
 
+io_spspecs = (
+    _io_spec(sram_signal="in_shiftclk", io_type="io_in", io_number=14),
+    _io_spec(sram_signal="shift_in", io_type="io_in", io_number=15),
+    _io_spec(sram_signal="in_captureclk_l", io_type="io_out", io_number=16),
+    _io_spec(sram_signal="in_captureclk", io_type="io_in", io_number=17),
+    _io_spec(sram_signal="sramclk", io_type="io_in", io_number=18),
+    _io_spec(sram_signal="out_captureclk_l", io_type="io_out", io_number=13),
+    _io_spec(sram_signal="out_captureclk", io_type="io_in", io_number=12),
+    _io_spec(sram_signal="out_docapture", io_type="io_in", io_number=11),
+    _io_spec(sram_signal="out_shiftclk", io_type="io_in", io_number=10),
+    _io_spec(sram_signal="shift_out", io_type="io_out", io_number=9),
+)
+io_spsig2spec = {
+    spec.sram_signal: spec
+    for spec in io_spspecs
+}
+io_sppin2spec = {
+    spec.toppin_name: spec
+    for spec in io_spspecs
+}
+
+
 io_specs = (
-    _io_spec(sram_signal="a[8]", io_type="io_in", io_number=14),
-    _io_spec(sram_signal="a[7]", io_type="io_in", io_number=15),
-    _io_spec(sram_signal="a[6]", io_type="io_in", io_number=16),
-    _io_spec(sram_signal="a[5]", io_type="io_in", io_number=17),
-    _io_spec(sram_signal="a[4]", io_type="io_in", io_number=18),
-    _io_spec(sram_signal="a[3]", io_type="io_in", io_number=19),
-    _io_spec(sram_signal="a[2]", io_type="io_in", io_number=20),
-    _io_spec(sram_signal="a[1]", io_type="io_in", io_number=21),
-    _io_spec(sram_signal="a[0]", io_type="io_in", io_number=22),
-    _io_spec(sram_signal="clk", io_type="io_in", io_number=23),
-    _io_spec(sram_signal="q[7]", io_type="io_out", io_number=13),
-    _io_spec(sram_signal="d[7]", io_type="io_in", io_number=12),
-    _io_spec(sram_signal="d[6]", io_type="io_in", io_number=11),
-    _io_spec(sram_signal="q[6]", io_type="io_out", io_number=10),
-    _io_spec(sram_signal="q[5]", io_type="io_out", io_number=9),
-    _io_spec(sram_signal="d[5]", io_type="io_in", io_number=8),
-    _io_spec(sram_signal="d[4]", io_type="io_in", io_number=7),
-    _io_spec(sram_signal="q[4]", io_type="io_out", io_number=6),
-    _io_spec(sram_signal="q[3]", io_type="io_out", io_number=5),
-    _io_spec(sram_signal="d[3]", io_type="io_in", io_number=4),
-    _io_spec(sram_signal="d[2]", io_type="io_in", io_number=3),
-    _io_spec(sram_signal="q[2]", io_type="io_out", io_number=2),
-    _io_spec(sram_signal="q[1]", io_type="io_out", io_number=1),
-    _io_spec(sram_signal="we[0]", io_type="io_in", io_number=0),
-    _io_spec(sram_signal="d[1]", io_type="io_in", io_number=26),
-    _io_spec(sram_signal="d[0]", io_type="io_in", io_number=25),
-    _io_spec(sram_signal="q[0]", io_type="io_out", io_number=24),
-    _io_spec(sram_signal="vdd", io_type="io_analog", io_number=4),
-    _io_spec(sram_signal="vss", io_type="io_analog", io_number=5),
+    _io_spec(sram_signal="svdd", io_type="io_analog", io_number=4),
+    _io_spec(sram_signal="svss", io_type="io_analog", io_number=5),
 )
 io_sig2spec = {
     spec.sram_signal: spec
@@ -278,6 +273,10 @@
         m2 = cast(_prm.MetalWire, prims.m2)
         assert m2.pin is not None
         m2pin = m2.pin[0]
+        via2 = cast(_prm.Via, prims.via2)
+        m3 = cast(_prm.MetalWire, prims.m3)
+        assert m3.pin is not None
+        m3pin = m3.pin[0]
 
         # Place the SRAM block
         layouter = self.new_circuitlayouter()
@@ -469,6 +468,54 @@
             bottom_enclosure="wide", top_enclosure="tall",
         )
 
+        # Connect svss
+        net = nets.svss
+
+        bbs = tuple(ms.shape.bounds for ms in sram_lay.filter_polygons(
+            net=net, mask=m2pin.mask, depth=1, split=True,
+        ))
+        left = max(bb.left for bb in bbs)
+        top = max(bb.top for bb in bbs)
+        bottom = min(bb.bottom for bb in bbs)
+        w = 10.0
+        x_via2 = left + w/2.0
+        right = left + w
+
+        for bb in bbs:
+            y_via2 = bb.center.y
+            layouter.add_wire(
+                net=net, wire=via2, x=x_via2, y=y_via2,
+                bottom_width=w, bottom_enclosure="wide",
+                top_width=w, top_enclosure="wide",
+            )
+
+        shape = _geo.Rect(left=left, bottom=bottom, right=right, top=top)
+        layouter.add_wire(net=net, wire=m3, pin=m3pin, shape=shape)
+
+        # Connect svdd
+        net = nets.svdd
+
+        bbs = tuple(ms.shape.bounds for ms in sram_lay.filter_polygons(
+            net=net, mask=m2pin.mask, depth=1, split=True,
+        ))
+        right = min(bb.right for bb in bbs)
+        top = max(bb.top for bb in bbs)
+        bottom = min(bb.bottom for bb in bbs)
+        w = 10.0
+        x_via2 = right - w/2.0
+        left = right - w
+
+        for bb in bbs:
+            y_via2 = bb.center.y
+            layouter.add_wire(
+                net=net, wire=via2, x=x_via2, y=y_via2,
+                bottom_width=w, bottom_enclosure="wide",
+                top_width=w, top_enclosure="wide",
+            )
+
+        shape = _geo.Rect(left=left, bottom=bottom, right=right, top=top)
+        layouter.add_wire(net=net, wire=m3, pin=m3pin, shape=shape)
+
         # Connect sramclk
         net = nets.sramclk
 
@@ -1532,6 +1579,8 @@
         via2 = cast(_prm.Via, prims.via2)
         m3 = cast(_prm.MetalWire, prims.m3)
         m3pin = cast(_prm.Marker, prims["m3.pin"])
+        via3 = cast(_prm.Via, prims.via3)
+        m4 = cast(_prm.MetalWire, prims.m4)
 
         zero_cell = stdcells.zero_x1
         zero_nets = zero_cell.circuit.nets
@@ -1563,6 +1612,7 @@
             address_groups=(3, 4, 2), word_size=word_size, we_size=we_size,
             cell_name="512x8",
         )
+        spchar_cell = SPCharacterizationWrapper(lib=lib, spcell=spsram_cell)
         dpsram_cell = dpmem_fab.block(
             address_groups=(3, 4, 2), word_size=word_size, we_size=we_size,
             cell_name="512x8",
@@ -1585,9 +1635,12 @@
         # Place the SRAM
         #
         # instantiate
-        spsram = ckt.instantiate(spsram_cell, name="sram")
+        spsram = ckt.instantiate(spchar_cell, name="sram")
         dpsram = ckt.instantiate(dpsram_cell, name="sram")
 
+        dvss.childports += spsram.ports.dvss
+        dvdd.childports += spsram.ports.dvdd
+
         # place
         _spsram_lay = layouter.inst_layout(inst=spsram)
         _spsram_bb = _spsram_lay.boundary
@@ -1597,23 +1650,16 @@
         assert _dpsram_bb is not None
 
         x = sky130.tech.on_grid(_frm.boundary.center.x - _spsram_bb.center.x)
-        y = _frm.boundary.top - 100.0 - _spsram_bb.top
+        y = _frm.boundary.top - 60.0 - _spsram_bb.top
         spsram_lay = layouter.place(_spsram_lay, x=x, y=y)
         spsram_bb = spsram_lay.boundary
         assert spsram_bb is not None
-        x = spsram_bb.right + 100.0
+        x = sky130.tech.on_grid(_frm.boundary.center.x - _dpsram_bb.center.x)
+        y = 1600.0 - _dpsram_bb.bottom
         dpsram_lay = layouter.place(_dpsram_lay, x=x, y=y)
         dpsram_bb = dpsram_lay.boundary
         assert dpsram_bb is not None
 
-        # Temporary add
-        spchar_cell = SPCharacterizationWrapper(lib=lib, spcell=spsram_cell)
-        spchar = ckt.instantiate(spchar_cell, name="sramchar")
-        x = spsram_bb.right + 700
-        spchar_lay = layouter.place(
-            spchar, x=x, y=(y + 50), rotation=_geo.Rotation.R90,
-        )
-
         # Make three rows of to place standard cells in
         #
         dbound = 4.0
@@ -1755,132 +1801,19 @@
 
         layouter.add_wire(net=dvdd, wire=m3, pin=m3pin, shape=dvdd_bb)
 
-        # below SRAM
-        rot_midrow = _geo.Rotation.R0
-        _tie_rotbb = rot_midrow*_tie_bb
-
-        y_midrow = spsram_bb.bottom - dbound - _tie_rotbb.top
-
-        inst = ckt.instantiate(tie_cell, name="mltie")
-        dvss.childports += inst.ports.vss
-        dvdd.childports += inst.ports.vdd
-
-        x_tie = spsram_bb.left - _tie_rotbb.left
-        lay = layouter.place(inst, x=x_tie, y=y_midrow, rotation=rot_midrow)
-        nwmbb1 = lay.bounds(mask=nwm.mask)
-        lipindvssbb1 = lay.bounds(mask=lipin.mask, net=dvss, depth=1)
-        lipindvddbb1 = lay.bounds(mask=lipin.mask, net=dvdd, depth=1)
-
-        inst = ckt.instantiate(tie_cell, name="mrtie")
-        dvss.childports += inst.ports.vss
-        dvdd.childports += inst.ports.vdd
-
-        x_tie = spsram_bb.right - _tie_rotbb.right
-        lay = layouter.place(inst, x=x_tie, y=y_midrow, rotation=rot_midrow)
-        nwmbb2 = lay.bounds(mask=nwm.mask)
-        lipindvssbb2 = lay.bounds(mask=lipin.mask, net=dvss, depth=1)
-        lipindvddbb2 = lay.bounds(mask=lipin.mask, net=dvdd, depth=1)
-
-        shape = _geo.Rect.from_rect(rect=nwmbb1, right=nwmbb2.right)
-        layouter.add_wire(net=dvdd, wire=nwm, shape=shape)
-
-        shape = _geo.Rect.from_rect(rect=lipindvssbb1, right=lipindvssbb2.right)
-        layouter.add_wire(net=dvss, wire=li, shape=shape)
-        w = tech.on_grid(shape.width, mult=2, rounding="floor")
-        h = tech.on_grid(shape.height, mult=2, rounding="floor")
-        o = tech.on_grid(shape.center)
-        lay = layouter.add_wire(
-            net=dvss, wire=mcon, origin=o, bottom_width=w, bottom_height=h,
-        )
-        dvss_midrowm1bb = lay.bounds(mask=m1.mask)
-
-        shape = _geo.Rect.from_rect(rect=lipindvddbb1, right=lipindvddbb2.right)
-        layouter.add_wire(net=dvdd, wire=li, shape=shape)
-        w = tech.on_grid(shape.width, mult=2, rounding="floor")
-        h = tech.on_grid(shape.height, mult=2, rounding="floor")
-        o = tech.on_grid(shape.center)
-        lay = layouter.add_wire(
-            net=dvdd, wire=mcon, origin=o, bottom_width=w, bottom_height=h,
-        )
-        dvdd_midrowm1bb = lay.bounds(mask=m1.mask)
-
-        assert dvss_leftrowm1bb.center.x > dvdd_leftrowm1bb.center.x, "Internal error"
-        assert dvss_rightrowm1bb.center.x > dvdd_rightrowm1bb.center.x, "Internal error"
-
-        shape = _geo.Rect.from_rect(
-            rect=dvss_midrowm1bb,
-            left=dvss_leftrowm1bb.left, right=dvdd_rightrowm1bb.left - 1.0,
-        )
-        layouter.add_wire(net=dvss, wire=m1, shape=shape)
-
-        w = shape.height
-        _via_lay = layouter.wire_layout(
-            net=dvss, wire=via, bottom_width=w, bottom_height=w,
-        )
-        _via_m1bb = _via_lay.bounds(mask=m1.mask)
-
-        y_via = shape.center.y
-        x_via = shape.right - _via_m1bb.right
-        lay = layouter.place(_via_lay, x=x_via, y=y_via)
-        m2bb1 = lay.bounds(mask=m2.mask)
-        x_via = dvss_rightrowm1bb.center.x
-        lay = layouter.place(_via_lay, x=x_via, y=y_via)
-        m2bb2 = lay.bounds(mask=m2.mask)
-
-        shape = _geo.Rect.from_rect(rect=m2bb1, right=m2bb2.right)
-        layouter.add_wire(net=dvss, wire=m2, shape=shape)
-
-        shape = _geo.Rect.from_rect(
-            rect=dvdd_midrowm1bb,
-            left=(dvss_leftrowm1bb.right + 1.0), right=dvdd_rightrowm1bb.right,
-        )
-        layouter.add_wire(net=dvdd, wire=m1, shape=shape)
-
-        w = shape.height
-        _via_lay = layouter.wire_layout(
-            net=dvdd, wire=via, bottom_width=w, bottom_height=w,
-        )
-        _via_m1bb = _via_lay.bounds(mask=m1.mask)
-
-        y_via = shape.center.y
-        x_via = shape.left - _via_m1bb.left
-        lay = layouter.place(_via_lay, x=x_via, y=y_via)
-        m2bb1 = lay.bounds(mask=m2.mask)
-        x_via = dvdd_leftrowm1bb.center.x
-        lay = layouter.place(_via_lay, x=x_via, y=y_via)
-        m2bb2 = lay.bounds(mask=m2.mask)
-
-        shape = _geo.Rect.from_rect(rect=m2bb1, left=m2bb2.left)
-        layouter.add_wire(net=dvdd, wire=m2, shape=shape)
-
         # Connect the SRAM signals
         #
-        # vss
-        spec = io_sig2spec["vss"]
+        # svss
+        spec = io_sig2spec["svss"]
         sram_port = spsram.ports[spec.sram_signal]
         net = ckt.new_net(name=spec.toppin_name, external=True, childports=sram_port)
         toppin_bb = _frm.toppins[spec.toppin_name]
-        bbs = tuple(ms.shape.bounds for ms in spsram_lay.filter_polygons(
-            net=net, mask=m2pin.mask, depth=1, split=True,
-        ))
-        left = max(bb.left for bb in bbs)
-        top = max(bb.top for bb in bbs)
-        bottom = min(bb.bottom for bb in bbs)
-        w = 10.0
-        x_via2 = left + w/2.0
-        right = left + w
+        sram_m3pinbb = spsram_lay.bounds(mask=m3pin.mask, net=net, depth=1)
+        left = sram_m3pinbb.left
+        right = sram_m3pinbb.right
+        top = sram_m3pinbb.top
 
-        for bb in bbs:
-            y_via2 = bb.center.y
-            layouter.add_wire(
-                net=net, wire=via2, x=x_via2, y=y_via2,
-                bottom_width=w, bottom_enclosure="wide",
-                top_width=w, top_enclosure="wide",
-            )
-
-        shape = _geo.Rect(
-            left=left, bottom=bottom, right=right, top=(top + 20.0),
-        )
+        shape = _geo.Rect.from_rect(rect=sram_m3pinbb, top=(sram_m3pinbb.top + 20.0))
         layouter.add_wire(net=net, wire=m3, shape=shape)
         shape = _geo.Rect(
             left=toppin_bb.left, bottom=(top + 10.0), right=right, top=(top+20.0),
@@ -1895,32 +1828,17 @@
         shape = _geo.Rect.from_rect(rect=clamp_bb, bottom=(top + 10))
         layouter.add_wire(net=net, wire=m3, shape=shape)
 
-        # vdd
-        spec = io_sig2spec["vdd"]
+        # svdd
+        spec = io_sig2spec["svdd"]
         sram_port = spsram.ports[spec.sram_signal]
         net = ckt.new_net(name=spec.toppin_name, external=True, childports=sram_port)
         toppin_bb = _frm.toppins[spec.toppin_name]
-        bbs = tuple(ms.shape.bounds for ms in spsram_lay.filter_polygons(
-            net=net, mask=m2pin.mask, depth=1, split=True,
-        ))
-        right = min(bb.right for bb in bbs)
-        top = max(bb.top for bb in bbs)
-        bottom = min(bb.bottom for bb in bbs)
-        w = 10.0
-        x_via2 = right - w/2.0
-        left = right - w
+        sram_m3pinbb = spsram_lay.bounds(mask=m3pin.mask, net=net, depth=1)
+        left = sram_m3pinbb.left
+        right = sram_m3pinbb.right
+        top = sram_m3pinbb.top
 
-        for bb in bbs:
-            y_via2 = bb.center.y
-            layouter.add_wire(
-                net=net, wire=via2, x=x_via2, y=y_via2,
-                bottom_width=w, bottom_enclosure="wide",
-                top_width=w, top_enclosure="wide",
-            )
-
-        shape = _geo.Rect(
-            left=left, bottom=bottom, right=right, top=(top + 20.0),
-        )
+        shape = _geo.Rect.from_rect(rect=sram_m3pinbb, top=(sram_m3pinbb.top + 20.0))
         layouter.add_wire(net=net, wire=m3, shape=shape)
         shape = _geo.Rect(
             left=left, bottom=(top + 10.0), right=toppin_bb.right, top=(top + 20.0),
@@ -1941,398 +1859,768 @@
         )
         layouter.add_wire(net=net, wire=m3, shape=shape)
 
-        # connect the input signals
-        a_col = 0
-        for sig_name in (
-            *(f"a[{a_bit}]" for a_bit in reversed(range(a_bits))), # Reversed for a_col
-            "clk", "we[0]",
-            *(f"d[{bit}]" for bit in range(word_size)),
-        ):
-            spec = io_sig2spec[sig_name]
+        #
+        # Support functions
+        #
+        def place_ioperiph(*, spec: _io_spec, sram: _ckt._CellInstance):
+            sig_name = spec.sram_signal
             prefix = spec.prefix
             num = spec.io_number
 
             pin_name = spec.toppin_name
             oeb_name = f"io_oeb[{num}]"
             out_name = f"io_out[{num}]"
-            assert spec.io_type == "io_in", "Internal error"
-            assert spec.oeb, "Internal error"
-            sram_port = spsram.ports[sig_name]
+            sram_port = sram.ports[sig_name]
 
-            # instantiate cells
-            buf = ckt.instantiate(buf_cell, name="{prefix}buf")
-            one = ckt.instantiate(one_cell, name=f"{prefix}one")
-            zero = ckt.instantiate(zero_cell, name=f"{prefix}zero")
-            tie = ckt.instantiate(tie_cell, name=f"{prefix}tie")
+            if spec.io_type == "io_in":
+                assert spec.oeb, "Internal error"
 
-            # create nets
-            pin_net = ckt.new_net(name=pin_name, external=True, childports=buf.ports.i)
-            sig_net = ckt.new_net(name=sig_name, external=False, childports=(
-                buf.ports.q, sram_port,
-            ))
-            oeb_net = ckt.new_net(
-                name=oeb_name, external=True, childports=one.ports.one,
-            )
-            out_net = ckt.new_net(
-                name=out_name, external=True, childports=zero.ports.zero,
-            )
+                # instantiate cells
+                buf = ckt.instantiate(buf_cell, name="{prefix}buf")
+                one = ckt.instantiate(one_cell, name=f"{prefix}one")
+                zero = ckt.instantiate(zero_cell, name=f"{prefix}zero")
+                tie = ckt.instantiate(tie_cell, name=f"{prefix}tie")
 
-            # get bbs; SRAM m2 pin to m3 pin
-            toppin_bb = _frm.toppins[pin_name]
-            oeb_bb = _frm.toppins[oeb_name]
-            out_bb = _frm.toppins[out_name]
-            if sig_name.startswith("a["):
-                # Connect signal up to m2
-                sram_m1pinbb = spsram_lay.bounds(mask=m1pin.mask, net=sig_net, depth=1)
+                # create nets
+                pin_net = ckt.new_net(name=pin_name, external=True, childports=buf.ports.i)
+                sig_net = ckt.new_net(name=sig_name, external=False, childports=(
+                    buf.ports.q, sram_port,
+                ))
+                oeb_net = ckt.new_net(
+                    name=oeb_name, external=True, childports=one.ports.one,
+                )
+                out_net = ckt.new_net(
+                    name=out_name, external=True, childports=zero.ports.zero,
+                )
 
-                w = 10.0
-                right = spsram_bb.left - (2*a_col + 1)*w
-                left = right - 1
-                a_col += 1
+                # get bbs
+                toppin_bb = _frm.toppins[pin_name]
+                oeb_bb = _frm.toppins[oeb_name]
+                out_bb = _frm.toppins[out_name]
+                is_leftrow = toppin_bb.center.x < _frm.boundary.center.x
 
-                x_via = right - w/2.0
-                y_via = tech.on_grid(sram_m1pinbb.center.y)
-                via_lay = layouter.add_wire(
-                    net=sig_net, wire=via, x=x_via, y=y_via,
+                rot = rot_leftrow if is_leftrow else rot_rightrow
+                x_row = x_leftrow if is_leftrow else x_rightrow
+
+                # place buf
+                _buf_rotilipinbb = rot*_buf_ilipinbb
+                y_buf = tech.on_grid(
+                    toppin_bb.top - _buf_rotilipinbb.bottom,
+                    mult=2,
+                )
+                buf_lay = layouter.place(buf, x=x_row, y=y_buf, rotation=rot)
+                pinbuf_lipinbb = buf_lay.bounds(mask=lipin.mask, net=pin_net, depth=1)
+                sigbuf_lipinbb = buf_lay.bounds(mask=lipin.mask, net=sig_net, depth=1)
+                buf_bb = buf_lay.boundary
+                assert buf_bb is not None
+
+                # place tie cell
+                _tie_rotbb = rot*_tie_bb
+                if is_leftrow:
+                    y_tie = buf_bb.top - _tie_rotbb.bottom
+                else:
+                    y_tie = buf_bb.bottom - _tie_rotbb.top
+                layouter.place(tie, x=x_row, y=y_tie, rotation=rot)
+
+                # place zero cell
+                _zero_rotbb = rot*_zero_bb
+                if is_leftrow:
+                    y_zero = buf_bb.bottom - _zero_rotbb.top
+                else:
+                    y_zero = buf_bb.top - _zero_rotbb.bottom
+                zero_lay = layouter.place(zero, x=x_row, y=y_zero, rotation=rot)
+                zero_bb = zero_lay.boundary
+                assert zero_bb is not None
+                zeroout_lipinbb = zero_lay.bounds(mask=lipin.mask, net=out_net, depth=1)
+
+                # place one cell
+                _one_rotbb = rot*_one_bb
+                if is_leftrow:
+                    y_one = zero_bb.bottom - _one_rotbb.top
+                else:
+                    y_one = zero_bb.top - _one_rotbb.bottom
+                one_lay = layouter.place(one, x=x_row, y=y_one, rotation=rot)
+                oneoeb_lipinbb = one_lay.bounds(mask=lipin.mask, net=oeb_net, depth=1)
+
+                # connect pin_net
+                w = tech.on_grid(pinbuf_lipinbb.width, mult=2, rounding="floor")
+                o_via = tech.on_grid(pinbuf_lipinbb.center)
+                layouter.add_wire(
+                    net=pin_net, wire=mcon, origin=o_via,
                     bottom_width=w, bottom_enclosure="wide",
                     top_width=w, top_enclosure="wide",
                 )
-                via_m1bb = via_lay.bounds(mask=m1.mask)
-                sram_m2pinbb = via_lay.bounds(mask=m2.mask)
+                layouter.add_wire(
+                    net=pin_net, wire=via, origin=o_via,
+                    bottom_width=w, bottom_enclosure="wide",
+                    top_width=w, top_enclosure="wide",
+                )
+                via2_lay = layouter.add_wire(
+                    net=pin_net, wire=via2, origin=o_via,
+                    bottom_width=w, bottom_enclosure="wide",
+                    top_width=w, top_enclosure="wide",
+                )
+                via2_m3bb = via2_lay.bounds(mask=m3.mask)
 
-                shape = _geo.Rect.from_rect(rect=sram_m1pinbb, left=via_m1bb.left)
-                layouter.add_wire(net=sig_net, wire=m1, shape=shape)
+                if is_leftrow:
+                    shape = _geo.Rect.from_rect(rect=toppin_bb, right=via2_m3bb.right)
+                else:
+                    shape = _geo.Rect.from_rect(rect=toppin_bb, left=via2_m3bb.left)
+                layouter.add_wire(net=pin_net, wire=m3, shape=shape)
+                layouter.add_wire(net=pin_net, wire=m3, pin=m3pin, shape=toppin_bb)
+
+                # connect oeb_net
+                w = tech.on_grid(oneoeb_lipinbb.width, mult=2, rounding="floor")
+                o_via = tech.on_grid(oneoeb_lipinbb.center)
+                layouter.add_wire(
+                    net=oeb_net, wire=mcon, origin=o_via,
+                    bottom_width=w, bottom_enclosure="wide",
+                    top_width=w, top_enclosure="wide",
+                )
+                via_lay = layouter.add_wire(
+                    net=oeb_net, wire=via, origin=o_via,
+                    bottom_width=w, bottom_enclosure="wide",
+                    top_width=w, top_enclosure="wide",
+                )
+                via_m2bb = via_lay.bounds(mask=m2.mask)
+
+                o_via2 = _geo.Point.from_point(point=o_via, y=oeb_bb.center.y)
+                via2_lay = layouter.add_wire(
+                    net=oeb_net, wire=via2, origin=o_via2,
+                    bottom_width=w, bottom_enclosure="wide",
+                    top_width=w, top_enclosure="wide",
+                )
+                via2_m2bb = via2_lay.bounds(mask=m2.mask)
+                via2_m3bb = via2_lay.bounds(mask=m3.mask)
+
+                if is_leftrow:
+                    shape = _geo.Rect.from_rect(rect=via_m2bb, bottom=via2_m2bb.bottom)
+                else:
+                    shape = _geo.Rect.from_rect(rect=via_m2bb, top=via2_m2bb.top)
+                layouter.add_wire(net=oeb_net, wire=m2, shape=shape)
+                if is_leftrow:
+                    shape = _geo.Rect.from_rect(rect=oeb_bb, right=via2_m3bb.right)
+                else:
+                    shape = _geo.Rect.from_rect(rect=oeb_bb, left=via2_m3bb.left)
+                layouter.add_wire(net=oeb_net, wire=m3, shape=shape)
+
+                layouter.add_wire(net=oeb_net, wire=m3, pin=m3pin, shape=oeb_bb)
+
+                # connect out_net
+                w = tech.on_grid(zeroout_lipinbb.width, mult=2, rounding="floor")
+                o_via = tech.on_grid(zeroout_lipinbb.center)
+                layouter.add_wire(
+                    net=out_net, wire=mcon, origin=o_via,
+                    bottom_width=w, bottom_enclosure="wide",
+                    top_width=w, top_enclosure="wide",
+                )
+                layouter.add_wire(
+                    net=out_net, wire=via, origin=o_via,
+                    bottom_width=w, bottom_enclosure="wide",
+                    top_width=w, top_enclosure="wide",
+                )
+                via2_lay = layouter.add_wire(
+                    net=out_net, wire=via2, origin=o_via,
+                    bottom_width=w, bottom_enclosure="wide",
+                    top_width=w, top_enclosure="wide",
+                )
+                via2_m3bb = via2_lay.bounds(mask=m3.mask)
+
+                if is_leftrow:
+                    shape = _geo.Rect.from_rect(rect=via2_m3bb, bottom=out_bb.bottom)
+                else:
+                    shape = _geo.Rect.from_rect(rect=via2_m3bb, top=out_bb.top)
+                layouter.add_wire(net=out_net, wire=m3, shape=shape)
+                if is_leftrow:
+                    shape = _geo.Rect.from_rect(rect=out_bb, right=via2_m3bb.right)
+                else:
+                    shape = _geo.Rect.from_rect(rect=out_bb, left=via2_m3bb.left)
+                layouter.add_wire(net=out_net, wire=m3, shape=shape)
+
+                layouter.add_wire(net=out_net, wire=m3, pin=m3pin, shape=out_bb)
+
+                # connect sig_net
+                w = tech.on_grid(sigbuf_lipinbb.width, mult=2, rounding="floor")
+                o_via = tech.on_grid(sigbuf_lipinbb.center)
+                layouter.add_wire(
+                    net=sig_net, wire=mcon, origin=o_via,
+                    bottom_width=w, bottom_enclosure="wide",
+                    top_width=w, top_enclosure="wide",
+                )
+                layouter.add_wire(
+                    net=sig_net, wire=via, origin=o_via,
+                    bottom_width=w, bottom_enclosure="wide",
+                    top_width=w, top_enclosure="wide",
+                )
+                via2_lay = layouter.add_wire(
+                    net=sig_net, wire=via2, origin=o_via,
+                    bottom_width=w, bottom_enclosure="wide",
+                    top_width=w, top_enclosure="wide",
+                )
+                via2sig_m3bb = via2_lay.bounds(mask=m3.mask)
+            elif spec.io_type == "io_out":
+                assert not spec.oeb, "Internal error"
+
+                # instantiate cells
+                buf = ckt.instantiate(buf_cell, name="{prefix}buf")
+                zero = ckt.instantiate(zero_cell, name=f"{prefix}zero")
+                tie = ckt.instantiate(tie_cell, name=f"{prefix}tie")
+
+                # create nets
+                sig_net = pin_net = ckt.new_net(
+                    name=pin_name, external=True, childports=sram_port,
+                )
+                oeb_net = ckt.new_net(
+                    name=oeb_name, external=True, childports=zero.ports.zero,
+                )
+
+                # get bss
+                toppin_bb = _frm.toppins[pin_name]
+                oeb_bb = _frm.toppins[oeb_name]
+
+                is_leftrow = toppin_bb.center.x < _frm.boundary.center.x
+
+                # place zero
+                rot = rot_leftrow if is_leftrow else rot_rightrow
+                x_row = x_leftrow if is_leftrow else x_rightrow
+                _zero_rotzerolipinbb = rot*_zero_zerolipinbb
+                y_zero = oeb_bb.center.y - _zero_rotzerolipinbb.center.y
+                zero_lay = layouter.place(zero, x=x_row, y=y_zero, rotation=rot)
+                zero_zerolipinbb = zero_lay.bounds(mask=lipin.mask, net=oeb_net, depth=1)
+                zero_bb = zero_lay.boundary
+                assert zero_bb is not None
+
+                # place tie
+                _tie_rotbb = rot*_tie_bb
+                y_tie = zero_bb.top - _tie_rotbb.bottom
+                layouter.place(tie, x=x_row, y=y_tie, rotation=rot)
+
+                # connect sig_net
+                layouter.add_wire(net=sig_net, wire=m3, pin=m3pin, shape=toppin_bb)
+                via2sig_m3bb = toppin_bb
+
+                # connect oeb_net
+                w = tech.on_grid(zero_zerolipinbb.width, mult=2, rounding="floor")
+                o_via = tech.on_grid(zero_zerolipinbb.center)
+                layouter.add_wire(
+                    net=oeb_net, wire=mcon, origin=o_via,
+                    bottom_width=w, bottom_enclosure="wide",
+                    top_width=w, top_enclosure="wide",
+                )
+                layouter.add_wire(
+                    net=oeb_net, wire=via, origin=o_via,
+                    bottom_width=w, bottom_enclosure="wide",
+                    top_width=w, top_enclosure="wide",
+                )
+                via2_lay = layouter.add_wire(
+                    net=oeb_net, wire=via2, origin=o_via,
+                    bottom_width=w, bottom_enclosure="wide",
+                    top_width=w, top_enclosure="wide",
+                )
+                via2_m3bb = via2_lay.bounds(mask=m3.mask)
+
+                if is_leftrow:
+                    shape = _geo.Rect.from_rect(rect=oeb_bb, right=via2_m3bb.right)
+                else:
+                    shape = _geo.Rect.from_rect(rect=oeb_bb, left=via2_m3bb.left)
+                layouter.add_wire(net=oeb_net, wire=m3, shape=shape)
+                layouter.add_wire(net=oeb_net, wire=m3, pin=m3pin, shape=oeb_bb)
             else:
-                sram_m2pinbb = spsram_lay.bounds(mask=m2pin.mask, net=sig_net, depth=1)
-            is_leftrow = toppin_bb.center.x < sram_m2pinbb.center.x
+                assert False, "Internal error"
 
-            rot = rot_leftrow if is_leftrow else rot_rightrow
-            x_row = x_leftrow if is_leftrow else x_rightrow
+            return sig_net, via2sig_m3bb
 
-            # place buf
-            _buf_rotilipinbb = rot*_buf_ilipinbb
-            y_buf = tech.on_grid(
-                toppin_bb.top - _buf_rotilipinbb.bottom,
-                mult=2,
-            )
-            buf_lay = layouter.place(buf, x=x_row, y=y_buf, rotation=rot)
-            pinbuf_lipinbb = buf_lay.bounds(mask=lipin.mask, net=pin_net, depth=1)
-            sigbuf_lipinbb = buf_lay.bounds(mask=lipin.mask, net=sig_net, depth=1)
-            buf_bb = buf_lay.boundary
-            assert buf_bb is not None
+        #
+        # SP connections
+        #
 
-            # place tie cell
-            _tie_rotbb = rot*_tie_bb
-            if is_leftrow:
-                y_tie = buf_bb.top - _tie_rotbb.bottom
-            else:
-                y_tie = buf_bb.bottom - _tie_rotbb.top
-            layouter.place(tie, x=x_row, y=y_tie, rotation=rot)
+        left_col = 1
+        right_col = 1
+        col_width = 10.0
+        col_pitch = 20.0
+        col_left0 = 20.0
+        col_right0 = 2890.0
 
-            # place zero cell
-            _zero_rotbb = rot*_zero_bb
-            if is_leftrow:
-                y_zero = buf_bb.bottom - _zero_rotbb.top
-            else:
-                y_zero = buf_bb.top - _zero_rotbb.bottom
-            zero_lay = layouter.place(zero, x=x_row, y=y_zero, rotation=rot)
-            zero_bb = zero_lay.boundary
-            assert zero_bb is not None
-            zeroout_lipinbb = zero_lay.bounds(mask=lipin.mask, net=out_net, depth=1)
+        # Connect in_shiftclk
+        sig_net, sig_m3bb = place_ioperiph(
+            spec=io_spsig2spec["in_shiftclk"], sram=spsram,
+        )
 
-            # place one cell
-            _one_rotbb = rot*_one_bb
-            if is_leftrow:
-                y_one = zero_bb.bottom - _one_rotbb.top
-            else:
-                y_one = zero_bb.top - _one_rotbb.bottom
-            one_lay = layouter.place(one, x=x_row, y=y_one, rotation=rot)
-            oneoeb_lipinbb = one_lay.bounds(mask=lipin.mask, net=oeb_net, depth=1)
+        sramsig_m1pinbb = spsram_lay.bounds(mask=m1pin.mask, net=sig_net, depth=1)
 
-            # connect pin_net
-            w = tech.on_grid(pinbuf_lipinbb.width, mult=2, rounding="floor")
-            o_via = tech.on_grid(pinbuf_lipinbb.center)
-            layouter.add_wire(
-                net=pin_net, wire=mcon, origin=o_via,
-                bottom_width=w, bottom_enclosure="wide",
-                top_width=w, top_enclosure="wide",
-            )
-            layouter.add_wire(
-                net=pin_net, wire=via, origin=o_via,
-                bottom_width=w, bottom_enclosure="wide",
-                top_width=w, top_enclosure="wide",
-            )
-            via2_lay = layouter.add_wire(
-                net=pin_net, wire=via2, origin=o_via,
-                bottom_width=w, bottom_enclosure="wide",
-                top_width=w, top_enclosure="wide",
-            )
-            via2_m3bb = via2_lay.bounds(mask=m3.mask)
+        _viasig_lay = layouter.wire_layout(
+            net=sig_net, wire=via, rows=10, columns=10,
+        )
+        _viasig_m1bb = _viasig_lay.bounds(mask=m1.mask)
 
-            if is_leftrow:
-                shape = _geo.Rect.from_rect(rect=toppin_bb, right=via2_m3bb.right)
-            else:
-                shape = _geo.Rect.from_rect(rect=toppin_bb, left=via2_m3bb.left)
-            layouter.add_wire(net=pin_net, wire=m3, shape=shape)
-            layouter.add_wire(net=pin_net, wire=m3, pin=m3pin, shape=toppin_bb)
+        x = sramsig_m1pinbb.right - _viasig_m1bb.right
+        y = sramsig_m1pinbb.center.y
+        layouter.place(_viasig_lay, x=x, y=y)
+        via2_lay = layouter.add_wire(
+            net=sig_net, wire=via2, rows=10, columns=10, x=x, y=y,
+        )
+        via2_m3bb = via2_lay.bounds(mask=m3.mask)
 
-            # connect sig_net
-            w = tech.on_grid(sigbuf_lipinbb.width, mult=2, rounding="floor")
-            o_via = tech.on_grid(sigbuf_lipinbb.center)
-            layouter.add_wire(
-                net=sig_net, wire=mcon, origin=o_via,
-                bottom_width=w, bottom_enclosure="wide",
-                top_width=w, top_enclosure="wide",
-            )
-            layouter.add_wire(
-                net=sig_net, wire=via, origin=o_via,
-                bottom_width=w, bottom_enclosure="wide",
-                top_width=w, top_enclosure="wide",
-            )
-            via2_lay = layouter.add_wire(
-                net=sig_net, wire=via2, origin=o_via,
-                bottom_width=w, bottom_enclosure="wide",
-                top_width=w, top_enclosure="wide",
-            )
-            via2_m3bb1 = via2_lay.bounds(mask=m3.mask)
+        col_left = col_left0 + (left_col - 1)*col_pitch
+        col_right = col_left + col_width
+        shape = _geo.Polygon.from_floats(points=(
+            (sig_m3bb.left, sig_m3bb.bottom),
+            (sig_m3bb.left, sig_m3bb.top + 1.0),
+            (col_left, sig_m3bb.top + 1.0),
+            (col_left, via2_m3bb.top),
+            (via2_m3bb.right, via2_m3bb.top),
+            (via2_m3bb.right, via2_m3bb.bottom),
+            (col_right, via2_m3bb.bottom),
+            (col_right, sig_m3bb.bottom),
+            (sig_m3bb.left, sig_m3bb.bottom),
+        ))
+        layouter.add_wire(net=sig_net, wire=m3, shape=shape)
 
-            _via2_lay = layouter.wire_layout(
-                net=sig_net, wire=via2, rows=6, columns=3,
-            )
-            _via2_m2bb = _via2_lay.bounds(mask=m2.mask)
-            if is_leftrow:
-                x_via2 = sram_m2pinbb.right - _via2_m2bb.right
-            else:
-                x_via2 = sram_m2pinbb.left - _via2_m2bb.left
-            y_via2 = via2_m3bb1.center.y
-            via2_lay = layouter.place(_via2_lay, x=x_via2, y=y_via2)
-            via2_m2bb = via2_lay.bounds(mask=m2.mask)
-            via2_m3bb2 = via2_lay.bounds(mask=m3.mask)
+        left_col += 1
 
-            shape = _geo.Rect.from_rect(rect=sram_m2pinbb, bottom=via2_m2bb.bottom)
-            layouter.add_wire(net=sig_net, wire=m2, shape=shape)
-            if is_leftrow:
-                shape = _geo.Rect.from_rect(rect=via2_m3bb1, right=via2_m3bb2.right)
-            else:
-                shape = _geo.Rect.from_rect(rect=via2_m3bb1, left=via2_m3bb2.left)
-            layouter.add_wire(net=sig_net, wire=m3, shape=shape)
+        # Connect shift_in
+        sig_net, sig_m3bb = place_ioperiph(
+            spec=io_spsig2spec["shift_in"], sram=spsram,
+        )
 
-            # connect oeb_net
-            w = tech.on_grid(oneoeb_lipinbb.width, mult=2, rounding="floor")
-            o_via = tech.on_grid(oneoeb_lipinbb.center)
-            layouter.add_wire(
-                net=oeb_net, wire=mcon, origin=o_via,
-                bottom_width=w, bottom_enclosure="wide",
-                top_width=w, top_enclosure="wide",
-            )
-            via_lay = layouter.add_wire(
-                net=oeb_net, wire=via, origin=o_via,
-                bottom_width=w, bottom_enclosure="wide",
-                top_width=w, top_enclosure="wide",
-            )
-            via_m2bb = via_lay.bounds(mask=m2.mask)
+        sramsig_m1pinbb = spsram_lay.bounds(mask=m1pin.mask, net=sig_net, depth=1)
 
-            o_via2 = _geo.Point.from_point(point=o_via, y=oeb_bb.center.y)
-            via2_lay = layouter.add_wire(
-                net=oeb_net, wire=via2, origin=o_via2,
-                bottom_width=w, bottom_enclosure="wide",
-                top_width=w, top_enclosure="wide",
-            )
-            via2_m2bb = via2_lay.bounds(mask=m2.mask)
-            via2_m3bb = via2_lay.bounds(mask=m3.mask)
+        _viasig_lay = layouter.wire_layout(
+            net=sig_net, wire=via, rows=10, columns=10,
+        )
+        _viasig_m1bb = _viasig_lay.bounds(mask=m1.mask)
 
-            if is_leftrow:
-                shape = _geo.Rect.from_rect(rect=via_m2bb, bottom=via2_m2bb.bottom)
-            else:
-                shape = _geo.Rect.from_rect(rect=via_m2bb, top=via2_m2bb.top)
-            layouter.add_wire(net=oeb_net, wire=m2, shape=shape)
-            if is_leftrow:
-                shape = _geo.Rect.from_rect(rect=oeb_bb, right=via2_m3bb.right)
-            else:
-                shape = _geo.Rect.from_rect(rect=oeb_bb, left=via2_m3bb.left)
-            layouter.add_wire(net=oeb_net, wire=m3, shape=shape)
+        x = sramsig_m1pinbb.right - _viasig_m1bb.right
+        y = sramsig_m1pinbb.center.y
+        layouter.place(_viasig_lay, x=x, y=y)
+        via2_lay = layouter.add_wire(
+            net=sig_net, wire=via2, rows=10, columns=10, x=x, y=y,
+        )
+        via2_m3bb = via2_lay.bounds(mask=m3.mask)
 
-            layouter.add_wire(net=oeb_net, wire=m3, pin=m3pin, shape=oeb_bb)
+        col_left = col_left0 + (left_col - 1)*col_pitch
+        col_right = col_left + col_width
+        shape = _geo.Polygon.from_floats(points=(
+            (sig_m3bb.left, sig_m3bb.bottom),
+            (sig_m3bb.left, sig_m3bb.top + 1.0),
+            (col_left, sig_m3bb.top + 1.0),
+            (col_left, via2_m3bb.top),
+            (via2_m3bb.right, via2_m3bb.top),
+            (via2_m3bb.right, via2_m3bb.bottom),
+            (col_right, via2_m3bb.bottom),
+            (col_right, sig_m3bb.bottom),
+            (sig_m3bb.left, sig_m3bb.bottom),
+        ))
+        layouter.add_wire(net=sig_net, wire=m3, shape=shape)
 
-            # connect out_net
-            w = tech.on_grid(zeroout_lipinbb.width, mult=2, rounding="floor")
-            o_via = tech.on_grid(zeroout_lipinbb.center)
-            layouter.add_wire(
-                net=out_net, wire=mcon, origin=o_via,
-                bottom_width=w, bottom_enclosure="wide",
-                top_width=w, top_enclosure="wide",
-            )
-            layouter.add_wire(
-                net=out_net, wire=via, origin=o_via,
-                bottom_width=w, bottom_enclosure="wide",
-                top_width=w, top_enclosure="wide",
-            )
-            via2_lay = layouter.add_wire(
-                net=out_net, wire=via2, origin=o_via,
-                bottom_width=w, bottom_enclosure="wide",
-                top_width=w, top_enclosure="wide",
-            )
-            via2_m3bb = via2_lay.bounds(mask=m3.mask)
+        left_col += 1
 
-            if is_leftrow:
-                shape = _geo.Rect.from_rect(rect=via2_m3bb, bottom=out_bb.bottom)
-            else:
-                shape = _geo.Rect.from_rect(rect=via2_m3bb, top=out_bb.top)
-            layouter.add_wire(net=out_net, wire=m3, shape=shape)
-            if is_leftrow:
-                shape = _geo.Rect.from_rect(rect=out_bb, right=via2_m3bb.right)
-            else:
-                shape = _geo.Rect.from_rect(rect=out_bb, left=via2_m3bb.left)
-            layouter.add_wire(net=out_net, wire=m3, shape=shape)
+        # Connect in_captureclk_l
+        sig_net, sig_m3bb = place_ioperiph(
+            spec=io_spsig2spec["in_captureclk_l"], sram=spsram,
+        )
 
-            layouter.add_wire(net=out_net, wire=m3, pin=m3pin, shape=out_bb)
+        sramsig_m1pinbb = spsram_lay.bounds(mask=m1pin.mask, net=sig_net, depth=1)
 
-        # connect the output signals
-        for sig_name in (
-            *(f"q[{bit}]" for bit in range(word_size)),
-        ):
-            spec = io_sig2spec[sig_name]
-            prefix = spec.prefix
-            num = spec.io_number
+        _viasig_lay = layouter.wire_layout(
+            net=sig_net, wire=via, rows=10, columns=10,
+        )
+        _viasig_m1bb = _viasig_lay.bounds(mask=m1.mask)
 
-            pin_name = spec.toppin_name
-            oeb_name = f"io_oeb[{num}]"
-            assert spec.io_type == "io_out", "Internal error"
-            assert not spec.oeb, "Internal error"
-            sram_port = spsram.ports[spec.sram_signal]
+        x = sramsig_m1pinbb.right - _viasig_m1bb.right
+        y = sramsig_m1pinbb.center.y
+        layouter.place(_viasig_lay, x=x, y=y)
+        via2_lay = layouter.add_wire(
+            net=sig_net, wire=via2, rows=10, columns=10, x=x, y=y,
+        )
+        via2_m3bb = via2_lay.bounds(mask=m3.mask)
 
-            # instantiate cells
-            buf = ckt.instantiate(buf_cell, name="{prefix}buf")
-            zero = ckt.instantiate(zero_cell, name=f"{prefix}zero")
-            tie = ckt.instantiate(tie_cell, name=f"{prefix}tie")
+        col_left = col_left0 + (left_col - 1)*col_pitch
+        col_right = col_left + col_width
+        shape = _geo.Polygon.from_floats(points=(
+            (sig_m3bb.left, sig_m3bb.bottom),
+            (sig_m3bb.left, sig_m3bb.top + 1.0),
+            (col_left, sig_m3bb.top + 1.0),
+            (col_left, via2_m3bb.top),
+            (via2_m3bb.right, via2_m3bb.top),
+            (via2_m3bb.right, via2_m3bb.bottom),
+            (col_right, via2_m3bb.bottom),
+            (col_right, sig_m3bb.bottom),
+            (sig_m3bb.left, sig_m3bb.bottom),
+        ))
+        layouter.add_wire(net=sig_net, wire=m3, shape=shape)
 
-            # create nets
-            pin_net = ckt.new_net(name=pin_name, external=True, childports=buf.ports.q)
-            sig_net = ckt.new_net(name=sig_name, external=False, childports=(
-                buf.ports.i, sram_port,
-            ))
-            oeb_net = ckt.new_net(
-                name=oeb_name, external=True, childports=zero.ports.zero,
-            )
+        left_col += 1
 
-            # get bss
-            toppin_bb = _frm.toppins[pin_name]
-            oeb_bb = _frm.toppins[oeb_name]
-            sram_m2pinbb = spsram_lay.bounds(mask=m2pin.mask, net=sig_net, depth=1)
+        # Connect in_captureclk
+        sig_net, sig_m3bb = place_ioperiph(
+            spec=io_spsig2spec["in_captureclk"], sram=spsram,
+        )
 
-            is_leftrow = toppin_bb.center.x < sram_m2pinbb.center.x
+        sramsig_m2pinbb = spsram_lay.bounds(mask=m2pin.mask, net=sig_net, depth=1)
 
-            # place buf
-            rot = rot_midrow
-            y_row = y_midrow
-            _buf_rotilipinbb = rot*_buf_ilipinbb
-            x_buf = tech.on_grid(
-                sram_m2pinbb.right - _buf_rotilipinbb.left,
-                mult=2, rounding="ceiling",
-            )
-            buf_lay = layouter.place(buf, x=x_buf, y=y_row, rotation=rot)
-            pinbuf_lipinbb = buf_lay.bounds(mask=lipin.mask, net=pin_net, depth=1)
-            sigbuf_lipinbb = buf_lay.bounds(mask=lipin.mask, net=sig_net, depth=1)
-            buf_bb = buf_lay.boundary
-            assert buf_bb is not None
+        _via2sig_lay = layouter.wire_layout(
+            net=sig_net, wire=via2, rows=10, columns=10,
+        )
+        _via2sig_m2bb = _via2sig_lay.bounds(mask=m2.mask)
 
-            # place zero
-            rot = rot_leftrow if is_leftrow else rot_rightrow
-            x_row = x_leftrow if is_leftrow else x_rightrow
-            _zero_rotzerolipinbb = rot*_zero_zerolipinbb
-            y_zero = oeb_bb.center.y - _zero_rotzerolipinbb.center.y
-            zero_lay = layouter.place(zero, x=x_row, y=y_zero, rotation=rot)
-            zero_zerolipinbb = zero_lay.bounds(mask=lipin.mask, net=oeb_net, depth=1)
-            zero_bb = zero_lay.boundary
-            assert zero_bb is not None
+        x = sramsig_m2pinbb.right - _via2sig_m2bb.right
+        y = sramsig_m2pinbb.top - _via2sig_m2bb.top
+        via2_lay = layouter.place(_via2sig_lay, x=x, y=y)
+        via2_m3bb = via2_lay.bounds(mask=m3.mask)
 
-            # place tie
-            _tie_rotbb = rot*_tie_bb
-            y_tie = zero_bb.top - _tie_rotbb.bottom
-            layouter.place(tie, x=x_row, y=y_tie, rotation=rot)
+        col_left = col_left0 + (left_col - 1)*col_pitch
+        col_right = col_left + col_width
+        shape = _geo.Polygon.from_floats(points=(
+            (sig_m3bb.left, sig_m3bb.bottom),
+            (sig_m3bb.left, sig_m3bb.top + 1.0),
+            (col_left, sig_m3bb.top + 1.0),
+            (col_left, via2_m3bb.top),
+            (via2_m3bb.right, via2_m3bb.top),
+            (via2_m3bb.right, via2_m3bb.bottom),
+            (col_right, via2_m3bb.bottom),
+            (col_right, sig_m3bb.bottom),
+            (sig_m3bb.left, sig_m3bb.bottom),
+        ))
+        layouter.add_wire(net=sig_net, wire=m3, shape=shape)
 
-            # connect sig_net
-            h = tech.on_grid(sigbuf_lipinbb.height, mult=2, rounding="floor")
-            o_via = tech.on_grid(sigbuf_lipinbb.center)
-            layouter.add_wire(
-                net=sig_net, wire=mcon, origin=o_via,
-                bottom_height=h, bottom_enclosure="tall",
-                top_height=h, top_enclosure="tall",
-            )
-            via_lay = layouter.add_wire(
-                net=sig_net, wire=via, origin=o_via,
-                bottom_height=h, bottom_enclosure="tall",
-                top_height=h, top_enclosure="tall",
-            )
-            via_m2bb = via_lay.bounds(mask=m2.mask)
+        left_col += 1
 
-            shape = _geo.Rect.from_rect(rect=sram_m2pinbb, bottom=via_m2bb.bottom)
-            layouter.add_wire(net=sig_net, wire=m2, shape=shape)
+        # Connect sramclk
+        sig_net, sig_m3bb = place_ioperiph(
+            spec=io_spsig2spec["sramclk"], sram=spsram,
+        )
 
-            # connect pin_net
-            h = tech.on_grid(pinbuf_lipinbb.height, mult=2, rounding="floor")
-            o_via = tech.on_grid(pinbuf_lipinbb.center)
+        sramsig_m2pinbb = spsram_lay.bounds(mask=m2pin.mask, net=sig_net, depth=1)
 
-            layouter.add_wire(
-                net=sig_net, wire=mcon, origin=o_via,
-                bottom_height=h, bottom_enclosure="tall",
-                top_height=h, top_enclosure="tall",
-            )
-            via_lay = layouter.add_wire(
-                net=sig_net, wire=via, origin=o_via,
-                bottom_height=h, bottom_enclosure="tall",
-                top_height=h, top_enclosure="tall",
-            )
-            via_m2bb = via_lay.bounds(mask=m2.mask)
+        _via2sig_lay = layouter.wire_layout(
+            net=sig_net, wire=via2, rows=10, columns=10,
+        )
+        _via2sig_m2bb = _via2sig_lay.bounds(mask=m2.mask)
 
-            _via2_lay = layouter.wire_layout(
-                net=pin_net, wire=via2, rows=6, columns=3,
-            )
-            _via2_m2bb = _via2_lay.bounds(mask=m2.mask)
-            if is_leftrow:
-                x_via2 = via_m2bb.right - _via2_m2bb.right
-            else:
-                x_via2 = via_m2bb.left - _via2_m2bb.left
-            y_via2 = toppin_bb.center.y
-            via2_lay = layouter.place(_via2_lay, x=x_via2, y=y_via2)
-            via2_m2bb = via2_lay.bounds(mask=m2.mask)
-            via2_m3bb = via2_lay.bounds(mask=m3.mask)
+        x = sramsig_m2pinbb.right - _via2sig_m2bb.right
+        y = sramsig_m2pinbb.bottom - _via2sig_m2bb.bottom
+        via2_lay = layouter.place(_via2sig_lay, x=x, y=y)
+        via2_m3bb = via2_lay.bounds(mask=m3.mask)
 
-            shape = _geo.Rect.from_rect(rect=via_m2bb, bottom=via2_m2bb.bottom)
-            layouter.add_wire(net=pin_net, wire=m2, shape=shape)
-            if is_leftrow:
-                shape = _geo.Rect.from_rect(rect=toppin_bb, right=via2_m3bb.right)
-            else:
-                shape = _geo.Rect.from_rect(rect=toppin_bb, left=via2_m3bb.left)
-            layouter.add_wire(net=pin_net, wire=m3, shape=shape)
-            layouter.add_wire(net=pin_net, wire=m3, pin=m3pin, shape=toppin_bb)
+        col_left = col_left0 + (left_col - 1)*col_pitch
+        col_right = col_left + col_width
+        shape = _geo.Polygon.from_floats(points=(
+            (sig_m3bb.left, sig_m3bb.bottom),
+            (sig_m3bb.left, sig_m3bb.top + 1.0),
+            (col_left, sig_m3bb.top + 1.0),
+            (col_left, via2_m3bb.top),
+            (via2_m3bb.right, via2_m3bb.top),
+            (via2_m3bb.right, via2_m3bb.bottom),
+            (col_right, via2_m3bb.bottom),
+            (col_right, sig_m3bb.bottom),
+            (sig_m3bb.left, sig_m3bb.bottom),
+        ))
+        layouter.add_wire(net=sig_net, wire=m3, shape=shape)
 
-            # connect oeb_net
-            w = tech.on_grid(zero_zerolipinbb.width, mult=2, rounding="floor")
-            o_via = tech.on_grid(zero_zerolipinbb.center)
-            layouter.add_wire(
-                net=oeb_net, wire=mcon, origin=o_via,
-                bottom_width=w, bottom_enclosure="wide",
-                top_width=w, top_enclosure="wide",
-            )
-            layouter.add_wire(
-                net=oeb_net, wire=via, origin=o_via,
-                bottom_width=w, bottom_enclosure="wide",
-                top_width=w, top_enclosure="wide",
-            )
-            via2_lay = layouter.add_wire(
-                net=oeb_net, wire=via2, origin=o_via,
-                bottom_width=w, bottom_enclosure="wide",
-                top_width=w, top_enclosure="wide",
-            )
-            via2_m3bb = via2_lay.bounds(mask=m3.mask)
+        left_col += 1
 
-            if is_leftrow:
-                shape = _geo.Rect.from_rect(rect=oeb_bb, right=via2_m3bb.right)
-            else:
-                shape = _geo.Rect.from_rect(rect=oeb_bb, left=via2_m3bb.left)
-            layouter.add_wire(net=oeb_net, wire=m3, shape=shape)
-            layouter.add_wire(net=oeb_net, wire=m3, pin=m3pin, shape=oeb_bb)
+        # Connect out_captureclk_l
+        sig_net, sig_m3bb = place_ioperiph(
+            spec=io_spsig2spec["out_captureclk_l"], sram=spsram,
+        )
+
+        sramsig_m1pinbb = spsram_lay.bounds(mask=m1pin.mask, net=sig_net, depth=1)
+
+        _viasig_lay = layouter.wire_layout(
+            net=sig_net, wire=via, rows=10, columns=10,
+        )
+        _viasig_m1bb = _viasig_lay.bounds(mask=m1.mask)
+
+        x = sramsig_m1pinbb.right - _viasig_m1bb.right
+        y = sramsig_m1pinbb.center.y
+        layouter.place(_viasig_lay, x=x, y=y)
+        via2_lay = layouter.add_wire(
+            net=sig_net, wire=via2, rows=10, columns=10, x=x, y=y,
+        )
+        via2_m3bb = via2_lay.bounds(mask=m3.mask)
+
+        col_right = col_right0 - (right_col - 1)*col_pitch
+        col_left = col_right - col_width
+        shape = _geo.Polygon.from_floats(points=(
+            (via2_m3bb.left, via2_m3bb.bottom),
+            (via2_m3bb.left, via2_m3bb.top),
+            (col_right, via2_m3bb.top),
+            (col_right, sig_m3bb.top),
+            (sig_m3bb.right, sig_m3bb.top),
+            (sig_m3bb.right, sig_m3bb.bottom),
+            (col_left, sig_m3bb.bottom),
+            (col_left, via2_m3bb.bottom),
+            (via2_m3bb.left, via2_m3bb.bottom),
+        ))
+        layouter.add_wire(net=sig_net, wire=m3, shape=shape)
+
+        right_col += 1
+
+        # Connect out_captureclk
+        sig_net, sig_m3bb = place_ioperiph(
+            spec=io_spsig2spec["out_captureclk"], sram=spsram,
+        )
+
+        sramsig_m1pinbb = spsram_lay.bounds(mask=m1pin.mask, net=sig_net, depth=1)
+
+        _viasig_lay = layouter.wire_layout(
+            net=sig_net, wire=via, rows=10, columns=10,
+        )
+        _viasig_m1bb = _viasig_lay.bounds(mask=m1.mask)
+
+        x = sramsig_m1pinbb.left - _viasig_m1bb.left
+        y = sramsig_m1pinbb.center.y
+        layouter.place(_viasig_lay, x=x, y=y)
+        via2_lay = layouter.add_wire(
+            net=sig_net, wire=via2, rows=10, columns=10, x=x, y=y,
+        )
+        via2_m3bb = via2_lay.bounds(mask=m3.mask)
+
+        col_right = col_right0 - (right_col - 1)*col_pitch
+        col_left = col_right - col_width
+        shape = _geo.Polygon.from_floats(points=(
+            (via2_m3bb.left, via2_m3bb.bottom),
+            (via2_m3bb.left, via2_m3bb.top),
+            (col_right, via2_m3bb.top),
+            (col_right, sig_m3bb.top),
+            (sig_m3bb.right, sig_m3bb.top),
+            (sig_m3bb.right, sig_m3bb.bottom),
+            (col_left, sig_m3bb.bottom),
+            (col_left, via2_m3bb.bottom),
+            (via2_m3bb.left, via2_m3bb.bottom),
+        ))
+        layouter.add_wire(net=sig_net, wire=m3, shape=shape)
+
+        right_col += 1
+
+        # Connect out_docapture
+        sig_net, sig_m3bb = place_ioperiph(
+            spec=io_spsig2spec["out_docapture"], sram=spsram,
+        )
+
+        sramsig_m1pinbb = spsram_lay.bounds(mask=m1pin.mask, net=sig_net, depth=1)
+
+        _viasig_lay = layouter.wire_layout(
+            net=sig_net, wire=via, rows=10, columns=10,
+        )
+        _viasig_m1bb = _viasig_lay.bounds(mask=m1.mask)
+
+        x = sramsig_m1pinbb.right + 5.0 - _viasig_m1bb.left
+        y = sramsig_m1pinbb.center.y - 1.0
+        via_lay = layouter.place(_viasig_lay, x=x, y=y)
+        via_m1bb = via_lay.bounds(mask=m1.mask)
+        via2_lay = layouter.add_wire(
+            net=sig_net, wire=via2, rows=10, columns=10, x=x, y=y,
+        )
+        via2_m3bb = via2_lay.bounds(mask=m3.mask)
+
+        shape =_geo.Rect.from_rect(rect=sramsig_m1pinbb, right=via_m1bb.right)
+        layouter.add_wire(net=sig_net, wire=m1, shape=shape)
+
+        col_right = col_right0 - (right_col - 1)*col_pitch
+        col_left = col_right - col_width
+        shape = _geo.Polygon.from_floats(points=(
+            (via2_m3bb.left, via2_m3bb.bottom),
+            (via2_m3bb.left, via2_m3bb.top),
+            (col_right, via2_m3bb.top),
+            (col_right, sig_m3bb.top),
+            (sig_m3bb.right, sig_m3bb.top),
+            (sig_m3bb.right, sig_m3bb.bottom),
+            (col_left, sig_m3bb.bottom),
+            (col_left, via2_m3bb.bottom),
+            (via2_m3bb.left, via2_m3bb.bottom),
+        ))
+        layouter.add_wire(net=sig_net, wire=m3, shape=shape)
+
+        right_col += 1
+
+        # Connect out_shiftclk
+        sig_net, sig_m3bb = place_ioperiph(
+            spec=io_spsig2spec["out_shiftclk"], sram=spsram,
+        )
+
+        sramsig_m2pinbb = spsram_lay.bounds(mask=m2pin.mask, net=sig_net, depth=1)
+
+        _via2sig_lay = layouter.wire_layout(
+            net=sig_net, wire=via2, rows=10, columns=10,
+        )
+        _via2sig_m2bb = _via2sig_lay.bounds(mask=m2.mask)
+
+        x = sramsig_m2pinbb.left - _via2sig_m2bb.left
+        y = sramsig_m2pinbb.center.y - _via2sig_m2bb.top
+        via2_lay = layouter.place(_via2sig_lay, x=x, y=y)
+        via2_m3bb = via2_lay.bounds(mask=m3.mask)
+
+        col_right = col_right0 - (right_col - 1)*col_pitch
+        col_left = col_right - col_width
+        shape = _geo.Polygon.from_floats(points=(
+            (via2_m3bb.left, via2_m3bb.bottom),
+            (via2_m3bb.left, via2_m3bb.top),
+            (col_right, via2_m3bb.top),
+            (col_right, sig_m3bb.top),
+            (sig_m3bb.right, sig_m3bb.top),
+            (sig_m3bb.right, sig_m3bb.bottom),
+            (col_left, sig_m3bb.bottom),
+            (col_left, via2_m3bb.bottom),
+            (via2_m3bb.left, via2_m3bb.bottom),
+        ))
+        layouter.add_wire(net=sig_net, wire=m3, shape=shape)
+
+        right_col += 1
+
+        # Connect shift_out
+        sig_net, sig_m3bb = place_ioperiph(
+            spec=io_spsig2spec["shift_out"], sram=spsram,
+        )
+
+        sramsig_m1pinbb = spsram_lay.bounds(mask=m1pin.mask, net=sig_net, depth=1)
+
+        _viasig_lay = layouter.wire_layout(
+            net=sig_net, wire=via, rows=10, columns=10,
+        )
+        _viasig_m1bb = _viasig_lay.bounds(mask=m1.mask)
+
+        x = sramsig_m1pinbb.left - _viasig_m1bb.left
+        y = sramsig_m1pinbb.top - _viasig_m1bb.top
+        layouter.place(_viasig_lay, x=x, y=y)
+        via2_lay = layouter.add_wire(
+            net=sig_net, wire=via2, rows=10, columns=10, x=x, y=y,
+        )
+        via2_m3bb = via2_lay.bounds(mask=m3.mask)
+
+        col_right = col_right0 - (right_col - 1)*col_pitch
+        col_left = col_right - col_width
+        shape = _geo.Polygon.from_floats(points=(
+            (via2_m3bb.left, via2_m3bb.bottom),
+            (via2_m3bb.left, via2_m3bb.top),
+            (col_right, via2_m3bb.top),
+            (col_right, sig_m3bb.top),
+            (sig_m3bb.right, sig_m3bb.top),
+            (sig_m3bb.right, sig_m3bb.bottom),
+            (col_left, sig_m3bb.bottom),
+            (col_left, via2_m3bb.bottom),
+            (via2_m3bb.left, via2_m3bb.bottom),
+        ))
+        layouter.add_wire(net=sig_net, wire=m3, shape=shape)
+
+        right_col += 1
+
+        # Connect dvss/dvdd
+        dc_width = 10.0
+
+        sramdvss_m1pinbb = spsram_lay.bounds(mask=m1pin.mask, net=dvss, depth=1)
+        sramdvdd_m2pinbb = spsram_lay.bounds(mask=m2pin.mask, net=dvdd, depth=1)
+
+        o = sramdvss_m1pinbb.center
+        layouter.add_wire(
+            net=dvss, wire=via, origin=o, rows=3,
+            bottom_width=sramdvss_m1pinbb.width
+        )
+        layouter.add_wire(
+            net=dvss, wire=via2, origin=o,
+            bottom_width=sramdvss_m1pinbb.width, bottom_height=dc_width,
+        )
+        via3dvss_lay = layouter.add_wire(
+            net=dvss, wire=via3, origin=o,
+            bottom_width=sramdvss_m1pinbb.width, bottom_height=dc_width,
+        )
+        via3dvss_m4bb = via3dvss_lay.bounds(mask=m4.mask)
+
+        shape = _geo.Rect.from_rect(
+            rect=via3dvss_m4bb, left=dvss_leftrowm1bb.left, right=dvss_rightrowm1bb.right,
+        )
+        layouter.add_wire(net=dvss, wire=m4, shape=shape)
+
+        shape = _geo.Rect(
+            left=dvss_leftrowm1bb.left, bottom=via3dvss_m4bb.bottom,
+            right=dvss_leftrowm1bb.right, top=via3dvss_m4bb.top,
+        )
+        layouter.add_wire(
+            net=dvss, wire=via, bottom_shape=shape, top_shape=shape,
+        )
+        layouter.add_wire(
+            net=dvss, wire=via2, x=shape.center.x, columns=10,
+            bottom_bottom=shape.bottom, bottom_top=shape.top,
+            top_bottom=shape.bottom, top_top=shape.top,
+        )
+        layouter.add_wire(
+            net=dvss, wire=via3, x=shape.center.x, columns=10,
+            bottom_bottom=shape.bottom, bottom_top=shape.top,
+            top_bottom=shape.bottom, top_top=shape.top,
+        )
+
+        shape = _geo.Rect(
+            left=dvss_rightrowm1bb.left, bottom=via3dvss_m4bb.bottom,
+            right=dvss_rightrowm1bb.right, top=via3dvss_m4bb.top,
+        )
+        layouter.add_wire(
+            net=dvss, wire=via, bottom_shape=shape, top_shape=shape,
+        )
+        layouter.add_wire(
+            net=dvss, wire=via2, x=shape.center.x, columns=10,
+            bottom_bottom=shape.bottom, bottom_top=shape.top,
+            top_bottom=shape.bottom, top_top=shape.top,
+        )
+        layouter.add_wire(
+            net=dvss, wire=via3, x=shape.center.x, columns=10,
+            bottom_bottom=shape.bottom, bottom_top=shape.top,
+            top_bottom=shape.bottom, top_top=shape.top,
+        )
+
+        bottom = via3dvss_m4bb.top + 1.0
+        top = bottom + dc_width
+        right = sramdvdd_m2pinbb.right
+        left = right - dc_width
+        shape = _geo.Rect(left=left, bottom=bottom, right=right, top=top)
+        layouter.add_wire(
+            net=dvss, wire=via2, bottom_shape=shape, top_shape=shape,
+        )
+        via3dvdd_lay = layouter.add_wire(
+            net=dvss, wire=via3, bottom_shape=shape, top_shape=shape,
+        )
+        via3dvdd_m4bb = via3dvdd_lay.bounds(mask=m4.mask)
+
+        shape = _geo.Rect.from_rect(
+            rect=via3dvdd_m4bb, left=dvdd_leftrowm1bb.left, right=dvdd_rightrowm1bb.right,
+        )
+        layouter.add_wire(net=dvss, wire=m4, shape=shape)
+
+        shape = _geo.Rect(
+            left=dvdd_leftrowm1bb.left, bottom=via3dvdd_m4bb.bottom,
+            right=dvdd_leftrowm1bb.right, top=via3dvdd_m4bb.top,
+        )
+        layouter.add_wire(
+            net=dvss, wire=via, bottom_shape=shape, top_shape=shape,
+        )
+        layouter.add_wire(
+            net=dvss, wire=via2, x=shape.center.x, columns=10,
+            bottom_bottom=shape.bottom, bottom_top=shape.top,
+            top_bottom=shape.bottom, top_top=shape.top,
+        )
+        layouter.add_wire(
+            net=dvss, wire=via3, x=shape.center.x, columns=10,
+            bottom_bottom=shape.bottom, bottom_top=shape.top,
+            top_bottom=shape.bottom, top_top=shape.top,
+        )
+
+        shape = _geo.Rect(
+            left=dvdd_rightrowm1bb.left, bottom=via3dvdd_m4bb.bottom,
+            right=dvdd_rightrowm1bb.right, top=via3dvdd_m4bb.top,
+        )
+        layouter.add_wire(
+            net=dvss, wire=via, bottom_shape=shape, top_shape=shape,
+        )
+        layouter.add_wire(
+            net=dvss, wire=via2, x=shape.center.x, columns=10,
+            bottom_bottom=shape.bottom, bottom_top=shape.top,
+            top_bottom=shape.bottom, top_top=shape.top,
+        )
+        layouter.add_wire(
+            net=dvss, wire=via3, x=shape.center.x, columns=10,
+            bottom_bottom=shape.bottom, bottom_top=shape.top,
+            top_bottom=shape.bottom, top_top=shape.top,
+        )
 
         # boundary
         #
diff --git a/gds/user_analog_project_wrapper.gds.gz b/gds/user_analog_project_wrapper.gds.gz
index dc51ee5..d54d89f 100644
--- a/gds/user_analog_project_wrapper.gds.gz
+++ b/gds/user_analog_project_wrapper.gds.gz
Binary files differ