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