SRAM signals connected to the IO.
diff --git a/doitcode/frame.py b/doitcode/frame.py
index 6772e0b..9cb8659 100644
--- a/doitcode/frame.py
+++ b/doitcode/frame.py
@@ -163,7 +163,7 @@
     "io_out[26]": _geo.Rect(left=-4.00, bottom=13.63, right=2.40, top=14.19),
     "io_in[26]": _geo.Rect(left=-4.00, bottom=19.54, right=2.40, top=20.10),
     "io_in_3v3[26]": _geo.Rect(left=-4.00, bottom=25.45, right=2.40, top=26.01),
-    # TODO: vss*, vdd*, wb_*, la_data*, user_clock2, user_irq
+    # TODO: vss*, vdd*, ioclamp*, wb(s)_*, la_data*, user_clock2, user_irq
     # "vdda2": _geo.Rect(left=-4.00, bottom=1074.44, right=8.30, top=1098.44),
     # "vdda2": _geo.Rect(left=-4.00, bottom=1024.44, right=8.30, top=1048.44),
     # "vssd2": _geo.Rect(left=-4.00, bottom=864.44, right=8.30, top=888.44),
diff --git a/doitcode/sram.py b/doitcode/sram.py
index d0d2519..e3a590d 100644
--- a/doitcode/sram.py
+++ b/doitcode/sram.py
@@ -1,4 +1,7 @@
 # SPDX-License-Identifier: GPL-2.0-or-later
+from typing import Optional, Tuple, Union, cast
+
+from pdkmaster.technology import geometry as _geo, primitive as _prm
 from pdkmaster.design import circuit as _ckt, library as _lbry
 
 from c4m.pdk import sky130
@@ -10,26 +13,89 @@
 
 
 class _io_spec:
-    def __init__(self, *, io_number: int, sram_signal: str):
-        self.io_number = io_number
+    def __init__(self, *,
+        sram_signal: Union[str, Tuple[str, str, str]],
+        io_type: str, io_number: int,
+    ):
+        assert io_type in ("io_in", "io_out", "io_analog")
+        assert (io_type != "io_inout") or isinstance(sram_signal, tuple)
         self.sram_signal = sram_signal
+        self.io_type = io_type
+        self.io_number = io_number
+
+    @property
+    def prefix(self) -> str:
+        if self.io_type in ("io_in", "io_out", "io_inout"):
+            return f"io{self.io_number}"
+        elif self.io_type == "io_analog":
+            return f"ioa{self.io_number}"
+        elif self.io_type == "gpio":
+            return f"gpio{self.io_number}"
+        else:
+            raise NotImplementedError(f"io_type == '{self.io_type}'")
+
+    @property
+    def toppin_name(self) -> str:
+        num = self.io_number
+        if self.io_type == "io_in":
+            return f"io_in[{num}]"
+        elif self.io_type == "io_out":
+            return f"io_out[{num}]"
+        elif self.io_type == "io_analog":
+            return f"io_analog[{num}]"
+        else:
+            raise NotImplementedError(f"io_type == {self.io_type}")
+
+    @property
+    def oeb(self) -> Optional[bool]:
+        if self.io_type == "io_in":
+            return True
+        elif self.io_type == "io_out":
+            return False
+        else:
+            return None
 
 
 io_specs = (
-    _io_spec(sram_signal="a[8]", io_number=14),
-    _io_spec(sram_signal="a[7]", io_number=15),
-    _io_spec(sram_signal="a[6]", io_number=16),
-    _io_spec(sram_signal="a[5]", io_number=17),
-    _io_spec(sram_signal="a[4]", io_number=18),
-    _io_spec(sram_signal="a[3]", io_number=19),
-    _io_spec(sram_signal="a[2]", io_number=20),
-    _io_spec(sram_signal="a[1]", io_number=21),
-    _io_spec(sram_signal="a[0]", io_number=22),
+    _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_in", io_number=13),
+    _io_spec(sram_signal="d[7]", io_type="io_out", 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_in", io_number=9),
+    _io_spec(sram_signal="d[5]", io_type="io_out", 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_in", io_number=5),
+    _io_spec(sram_signal="d[3]", io_type="io_out", 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_in", 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_out", 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="vss", io_type="io_analog", io_number=4),
+    _io_spec(sram_signal="vdd", io_type="io_analog", io_number=5),
 )
 io_sig2spec = {
     spec.sram_signal: spec
     for spec in io_specs
 }
+io_pin2spec = {
+    spec.toppin_name: spec
+    for spec in io_specs
+}
+
 
 class ConnectedSRAM(_lbry._OnDemandCell[_lbry.Library]):
     def __init__(self, *, lib: _lbry.Library):
@@ -38,19 +104,61 @@
     def _create_circuit(self):
         stdcells = sky130.stdcelllib.cells
 
-        mem_factory = sky130.Sky130SP6TFactory(lib=self.lib)
+        mem_fab = sky130.Sky130SP6TFactory(lib=self.lib)
 
-        sram_cell = mem_factory.block(words=512, word_size=8, we_size=1)
-        
+        zero_cell = stdcells.zero_x1
+        one_cell = stdcells.one_x1
+        tie_cell = stdcells.tie_diff_w4
+
+        sram_cell = mem_fab.block(words=512, word_size=8, we_size=1)
+        self.a_bits = sram_cell.a_bits
+        self.word_size = sram_cell.word_size
+
         ckt = self.new_circuit()
 
-        ckt.instantiate(sram_cell, name="sram")
+        sram = ckt.instantiate(sram_cell, name="sram")
+    
+        for spec in io_specs:
+            prefix = spec.prefix
+
+            sram_port = sram.ports[spec.sram_signal]
+            ckt.new_net(name=spec.toppin_name, external=True, childports=sram_port)
+
+            oeb = spec.oeb
+            if oeb is not None:
+                num = spec.io_number
+                if oeb:
+                    one = ckt.instantiate(one_cell, name=f"{prefix}one")
+                    ckt.new_net(
+                        name=f"io_oeb[{num}]", external=True, childports=one.ports.one,
+                    )
+                else:
+                    zero = ckt.instantiate(zero_cell, name=f"{prefix}zero")
+                    ckt.new_net(
+                        name=f"io_oeb[{num}]", external=True, childports=zero.ports.zero,
+                    )
+
+            if spec.io_type != "io_analog":
+                ckt.instantiate(tie_cell, name=f"{prefix}tie")
 
     def _create_layout(self):
+        tech = sky130.tech
+        prims = tech.primitives
+
         ckt = self.circuit
         nets = ckt.nets
         insts = ckt.instances
 
+        li = cast(_prm.MetalWire, prims.li)
+        lipin = cast(_prm.Marker, prims["li.pin"])
+        m1 = cast(_prm.MetalWire, prims.m1)
+        m1pin = cast(_prm.Marker, prims["m1.pin"])
+        via = cast(_prm.Via, prims.via)
+        m2 = cast(_prm.MetalWire, prims.m2)
+        m2pin = cast(_prm.Marker, prims["m2.pin"])
+        via2 = cast(_prm.Via, prims.via2)
+        m3 = cast(_prm.MetalWire, prims.m3)
+
         layouter = self.new_circuitlayouter()
         layout = layouter.layout
 
@@ -58,7 +166,153 @@
         _sram_bb = _sram_lay.boundary
         assert _sram_bb is not None
 
-        o = sky130.tech.on_grid(_frm.boundary.center - _sram_bb.center)
-        layouter.place(_sram_lay, origin=o)
+        x = sky130.tech.on_grid(_frm.boundary.center.x - _sram_bb.center.x)
+        y = _frm.boundary.top - 100.0 - _sram_bb.top
+        sram_lay = layouter.place(_sram_lay, x=x, y=y)
+        sram_bb = sram_lay.boundary
+        assert sram_bb is not None
 
+        # vss
+        spec = io_sig2spec["vss"]
+        net = nets[spec.toppin_name]
+        toppin_bb = _frm.toppins[spec.toppin_name]
+        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 + 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),
+        )
+        layouter.add_wire(net=net, wire=m3, shape=shape)
+        shape = _geo.Rect.from_rect(rect=toppin_bb, bottom=(top + 10.0))
+        layouter.add_wire(net=net, wire=m3, shape=shape)
+
+        # vdd
+        spec = io_sig2spec["vdd"]
+        net = nets[spec.toppin_name]
+        toppin_bb = _frm.toppins[spec.toppin_name]
+        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 + 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),
+        )
+        layouter.add_wire(net=net, wire=m3, shape=shape)
+        shape = _geo.Rect.from_rect(rect=toppin_bb, bottom=(top + 10.0))
+        layouter.add_wire(net=net, wire=m3, shape=shape)
+
+        # a
+        col = 0
+        w = 10.0
+        for a_bit in reversed(range(self.a_bits)):
+            spec = io_sig2spec[f"a[{a_bit}]"]
+            assert spec.io_type == "io_in", "Internal error"
+            net = nets[spec.toppin_name]
+
+            sram_m1pinbb = sram_lay.bounds(mask=m1pin.mask, net=net, depth=1)
+            toppin_bb = _frm.toppins[spec.toppin_name]
+
+            right = sram_bb.left - (2*col + 1)*w
+            left = right - 1
+            col += 1
+
+            x_via = right - w/2.0
+            y_via = tech.on_grid(sram_m1pinbb.center.y)
+            via_lay = layouter.add_wire(
+                net=net, wire=via, x=x_via, y=y_via,
+                bottom_width=w, bottom_enclosure="wide",
+                top_width=w, top_enclosure="wide",
+            )
+            via_m1bb = via_lay.bounds(mask=m1.mask)
+            via_m2bb = via_lay.bounds(mask=m2.mask)
+
+            shape = _geo.Rect.from_rect(rect=sram_m1pinbb, left=via_m1bb.left)
+            layouter.add_wire(net=net, wire=m1, shape=shape)
+
+            x_via2 = x_via
+            y_via2 = toppin_bb.center.y
+            via2_lay = 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",
+            )
+            via2_m2bb = via2_lay.bounds(mask=m2.mask)
+            via2_m3bb = via2_lay.bounds(mask=m3.mask)
+
+            shape = _geo.Rect.from_rect(rect=via_m2bb, bottom=via2_m2bb.bottom)
+            layouter.add_wire(net=net, wire=m2, shape=shape)
+            shape = _geo.Rect.from_rect(rect=toppin_bb, right=via2_m3bb.right)
+            layouter.add_wire(net=net, wire=m3, shape=shape)
+
+        # The rest of the pin on m2
+        for sram_signame in (
+            "clk", "we[0]",
+            *(f"d[{bit}]" for bit in range(self.word_size)),
+            *(f"q[{bit}]" for bit in range(self.word_size)),
+        ):
+            spec = io_sig2spec[sram_signame]
+            net = nets[spec.toppin_name]
+
+            sram_m2pinbb = sram_lay.bounds(mask=m2pin.mask, net=net, depth=1)
+            toppin_bb = _frm.toppins[spec.toppin_name]
+
+            _via2_lay = layouter.add_wire(
+                net=net, wire=via2, rows=6, columns=6,
+            )
+            _via2_m2bb = _via2_lay.bounds(mask=m2.mask)
+            if toppin_bb.center.x > sram_m2pinbb.center.x:
+                x_via2 = sram_m2pinbb.left - _via2_m2bb.left
+            else:
+                x_via2 = sram_m2pinbb.right - _via2_m2bb.right
+            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)
+
+            shape = _geo.Rect.from_rect(rect=sram_m2pinbb, bottom=via2_m2bb.bottom)
+            layouter.add_wire(net=net, wire=m2, shape=shape)
+            if toppin_bb.center.x > sram_m2pinbb.center.x:
+                shape = _geo.Rect.from_rect(rect=toppin_bb, left=via2_m3bb.left)
+            else:
+                shape = _geo.Rect.from_rect(rect=toppin_bb, right=via2_m3bb.right)
+            layouter.add_wire(net=net, wire=m3, shape=shape)
+
+        # boundary
         layout.boundary = _frm.boundary
diff --git a/gds/user_analog_project_wrapper.gds.gz b/gds/user_analog_project_wrapper.gds.gz
index 51288eb..12287f3 100644
--- a/gds/user_analog_project_wrapper.gds.gz
+++ b/gds/user_analog_project_wrapper.gds.gz
Binary files differ