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