mirror of https://github.com/VLSIDA/OpenRAM.git
sky130 dp bank passing
This commit is contained in:
parent
5222224936
commit
c3da65c33c
|
|
@ -1368,6 +1368,142 @@ class layout():
|
||||||
|
|
||||||
return via
|
return via
|
||||||
|
|
||||||
|
def compute_min_area_rect_dims(self, layer, width, height):
|
||||||
|
"""
|
||||||
|
Return ``(width, height)`` after the same min-area expansion as
|
||||||
|
``add_min_area_rect_center`` (no geometry added). If ``minarea`` for
|
||||||
|
``layer`` is zero, returns ``(width, height)`` unchanged.
|
||||||
|
"""
|
||||||
|
min_area = drc("minarea_{}".format(layer))
|
||||||
|
if min_area == 0:
|
||||||
|
return width, height
|
||||||
|
|
||||||
|
min_width = drc("minwidth_{}".format(layer))
|
||||||
|
|
||||||
|
if preferred_directions[layer] == "V":
|
||||||
|
new_height = ceil(max(min_area / width, min_width))
|
||||||
|
new_width = width
|
||||||
|
else:
|
||||||
|
new_width = ceil(max(min_area / height, min_width))
|
||||||
|
new_height = height
|
||||||
|
debug.check(min_area <= round_to_grid(new_height * new_width), "Min area violated.")
|
||||||
|
return new_width, new_height
|
||||||
|
|
||||||
|
def via_stack_metal_extent_after_min_area(self,
|
||||||
|
from_layer,
|
||||||
|
to_layer,
|
||||||
|
directions,
|
||||||
|
metal_layer,
|
||||||
|
horizontal_extent,
|
||||||
|
size=(1, 1)):
|
||||||
|
"""
|
||||||
|
Horizontal span (if ``horizontal_extent``) or vertical span of
|
||||||
|
``metal_layer`` patches that ``add_via_stack_center`` would produce on
|
||||||
|
that layer: the hop's contact ``first_layer`` size, plus
|
||||||
|
``compute_min_area_rect_dims`` when ``add_via_stack_center`` would call
|
||||||
|
``add_min_area_rect_center`` for that hop (intermediate routing metal).
|
||||||
|
|
||||||
|
``directions`` and ``size`` match ``add_via_stack_center`` / ``add_via_center``.
|
||||||
|
Returns ``0.0`` if ``metal_layer`` is never the starting layer of a hop
|
||||||
|
on the path from ``from_layer`` to ``to_layer``.
|
||||||
|
"""
|
||||||
|
if from_layer == to_layer:
|
||||||
|
return 0.0
|
||||||
|
|
||||||
|
intermediate_layers = self.get_metal_layers(from_layer, to_layer)
|
||||||
|
best = 0.0
|
||||||
|
cur_layer = from_layer
|
||||||
|
while cur_layer != to_layer:
|
||||||
|
from_id = tech_layer_indices[cur_layer]
|
||||||
|
to_id = tech_layer_indices[to_layer]
|
||||||
|
|
||||||
|
if from_id < to_id:
|
||||||
|
search_id = 0
|
||||||
|
next_id = 2
|
||||||
|
else:
|
||||||
|
search_id = 2
|
||||||
|
next_id = 0
|
||||||
|
|
||||||
|
curr_stack = next(filter(lambda stack: stack[search_id] == cur_layer, tech_layer_stacks), None)
|
||||||
|
if curr_stack is None:
|
||||||
|
debug.error("via_stack_metal_extent_after_min_area: no stack for {} toward {}".format(cur_layer, to_layer), -1)
|
||||||
|
|
||||||
|
via_mod = factory.create(module_type="contact",
|
||||||
|
layer_stack=curr_stack,
|
||||||
|
dimensions=size,
|
||||||
|
directions=directions,
|
||||||
|
implant_type=None,
|
||||||
|
well_type=None)
|
||||||
|
|
||||||
|
if cur_layer == metal_layer:
|
||||||
|
fw = via_mod.first_layer_width
|
||||||
|
fh = via_mod.first_layer_height
|
||||||
|
if cur_layer in intermediate_layers:
|
||||||
|
nw, nh = self.compute_min_area_rect_dims(cur_layer, fw, fh)
|
||||||
|
else:
|
||||||
|
nw, nh = fw, fh
|
||||||
|
cand = nw if horizontal_extent else nh
|
||||||
|
best = max(best, cand)
|
||||||
|
|
||||||
|
cur_layer = curr_stack[next_id]
|
||||||
|
|
||||||
|
return best
|
||||||
|
|
||||||
|
def via_stack_metal_layer_extent_parallel_to_rail(self,
|
||||||
|
from_layer,
|
||||||
|
to_layer,
|
||||||
|
directions,
|
||||||
|
metal_layer,
|
||||||
|
parallel_along_y,
|
||||||
|
size=(1, 1)):
|
||||||
|
"""
|
||||||
|
Span along the rail axis of ``metal_layer`` metal only (the hop where
|
||||||
|
that layer is the contact first layer), including ``compute_min_area_rect_dims``
|
||||||
|
when ``add_via_stack_center`` would add a min-area patch on that layer.
|
||||||
|
Does **not** use the full contact cell bbox (which includes via cut layers).
|
||||||
|
"""
|
||||||
|
if from_layer == to_layer:
|
||||||
|
return 0.0
|
||||||
|
|
||||||
|
intermediate_layers = self.get_metal_layers(from_layer, to_layer)
|
||||||
|
best = 0.0
|
||||||
|
cur_layer = from_layer
|
||||||
|
while cur_layer != to_layer:
|
||||||
|
from_id = tech_layer_indices[cur_layer]
|
||||||
|
to_id = tech_layer_indices[to_layer]
|
||||||
|
|
||||||
|
if from_id < to_id:
|
||||||
|
search_id = 0
|
||||||
|
next_id = 2
|
||||||
|
else:
|
||||||
|
search_id = 2
|
||||||
|
next_id = 0
|
||||||
|
|
||||||
|
curr_stack = next(filter(lambda stack: stack[search_id] == cur_layer, tech_layer_stacks), None)
|
||||||
|
if curr_stack is None:
|
||||||
|
debug.error("via_stack_metal_layer_extent_parallel_to_rail: no stack for {} toward {}".format(cur_layer, to_layer), -1)
|
||||||
|
|
||||||
|
via_mod = factory.create(module_type="contact",
|
||||||
|
layer_stack=curr_stack,
|
||||||
|
dimensions=size,
|
||||||
|
directions=directions,
|
||||||
|
implant_type=None,
|
||||||
|
well_type=None)
|
||||||
|
|
||||||
|
if cur_layer == metal_layer:
|
||||||
|
fw = via_mod.first_layer_width
|
||||||
|
fh = via_mod.first_layer_height
|
||||||
|
if cur_layer in intermediate_layers:
|
||||||
|
nw, nh = self.compute_min_area_rect_dims(cur_layer, fw, fh)
|
||||||
|
else:
|
||||||
|
nw, nh = fw, fh
|
||||||
|
span = nh if parallel_along_y else nw
|
||||||
|
best = max(best, span)
|
||||||
|
|
||||||
|
cur_layer = curr_stack[next_id]
|
||||||
|
|
||||||
|
return best
|
||||||
|
|
||||||
def add_min_area_rect_center(self,
|
def add_min_area_rect_center(self,
|
||||||
layer,
|
layer,
|
||||||
offset,
|
offset,
|
||||||
|
|
@ -1381,15 +1517,7 @@ class layout():
|
||||||
if min_area == 0:
|
if min_area == 0:
|
||||||
return
|
return
|
||||||
|
|
||||||
min_width = drc("minwidth_{}".format(layer))
|
new_width, new_height = self.compute_min_area_rect_dims(layer, width, height)
|
||||||
|
|
||||||
if preferred_directions[layer] == "V":
|
|
||||||
new_height = ceil(max(min_area / width, min_width))
|
|
||||||
new_width = width
|
|
||||||
else:
|
|
||||||
new_width = ceil(max(min_area / height, min_width))
|
|
||||||
new_height = height
|
|
||||||
debug.check(min_area <= round_to_grid(new_height*new_width), "Min area violated.")
|
|
||||||
|
|
||||||
self.add_rect_center(layer=layer,
|
self.add_rect_center(layer=layer,
|
||||||
offset=offset,
|
offset=offset,
|
||||||
|
|
|
||||||
|
|
@ -243,9 +243,12 @@ class capped_replica_bitcell_array(bitcell_base_array):
|
||||||
|
|
||||||
|
|
||||||
self.route_power_ring(self.supply_stack[2], self.supply_stack[0])
|
self.route_power_ring(self.supply_stack[2], self.supply_stack[0])
|
||||||
|
self._strap_routing_endpoints = []
|
||||||
self.route_supplies()
|
self.route_supplies()
|
||||||
|
|
||||||
self.route_unused_wordlines()
|
self.route_unused_wordlines()
|
||||||
|
self.debug_print_strap_routing_endpoints("right")
|
||||||
|
self._bridge_close_strap_taps()
|
||||||
|
|
||||||
self.reset_coordinates()
|
self.reset_coordinates()
|
||||||
self.add_layout_pins()
|
self.add_layout_pins()
|
||||||
|
|
@ -270,6 +273,11 @@ class capped_replica_bitcell_array(bitcell_base_array):
|
||||||
getattr(self, "{}_pitch".format(h_layer)))
|
getattr(self, "{}_pitch".format(h_layer)))
|
||||||
self.supply_rail_pitch = max(drc_pitch, tech_pitch)
|
self.supply_rail_pitch = max(drc_pitch, tech_pitch)
|
||||||
self.add_power_ring(v_layer=v_layer, h_layer=h_layer, top=power_ring_top, bottom=power_ring_bottom, left=power_ring_left, right=power_ring_right)
|
self.add_power_ring(v_layer=v_layer, h_layer=h_layer, top=power_ring_top, bottom=power_ring_bottom, left=power_ring_left, right=power_ring_right)
|
||||||
|
# Match metal widths used by route_vertical_side_pin / route_horizontal_side_pin for strap bridges.
|
||||||
|
_vring = contact(layer_stack=self.supply_stack, directions=("H", "H"))
|
||||||
|
self._strap_width_vertical_rail = _vring.second_layer_width
|
||||||
|
_hring = contact(layer_stack=self.supply_stack, directions=("V", "V"))
|
||||||
|
self._strap_width_horizontal_rail = _hring.first_layer_height
|
||||||
|
|
||||||
def get_main_array_top(self):
|
def get_main_array_top(self):
|
||||||
return self.replica_bitcell_array_inst.by() + self.replica_bitcell_array.get_main_array_top()
|
return self.replica_bitcell_array_inst.by() + self.replica_bitcell_array.get_main_array_top()
|
||||||
|
|
@ -360,96 +368,55 @@ class capped_replica_bitcell_array(bitcell_base_array):
|
||||||
bottom = connect_ring_bottom
|
bottom = connect_ring_bottom
|
||||||
left = connect_ring_left
|
left = connect_ring_left
|
||||||
right = connect_ring_right
|
right = connect_ring_right
|
||||||
|
|
||||||
if 'vdd' in top:
|
if 'vdd' in top:
|
||||||
inst = self.dummy_row_insts[1]
|
inst = self.dummy_row_insts[1]
|
||||||
if 'vdd' in inst.mod.pins:
|
if 'vdd' in inst.mod.pins:
|
||||||
array_pins = inst.get_pins('vdd')
|
for array_pin in inst.get_pins('vdd'):
|
||||||
print("found pin", inst, inst.mod, array_pins)
|
self.connect_side_pin(array_pin, "top", self.top_vdd_pin.cy(),
|
||||||
for array_pin in array_pins:
|
strap_pin=self.top_vdd_pin)
|
||||||
supply_pin = self.top_vdd_pin
|
|
||||||
self.add_path(array_pin.layer, [array_pin.center(), vector(array_pin.center()[0], supply_pin.center()[1])])
|
|
||||||
self.add_via_stack_center(from_layer = array_pin.layer,
|
|
||||||
to_layer = supply_pin.layer,
|
|
||||||
offset = vector(array_pin.center()[0], supply_pin.center()[1]),
|
|
||||||
directions=("V", "V"))
|
|
||||||
if 'gnd' in top:
|
if 'gnd' in top:
|
||||||
inst = self.dummy_row_insts[1]
|
inst = self.dummy_row_insts[1]
|
||||||
if 'gnd' in inst.mod.pins:
|
if 'gnd' in inst.mod.pins:
|
||||||
array_pins = inst.get_pins('gnd')
|
for array_pin in inst.get_pins('gnd'):
|
||||||
for array_pin in array_pins:
|
self.connect_side_pin(array_pin, "top", self.top_gnd_pin.cy(),
|
||||||
supply_pin = self.top_gnd_pin
|
strap_pin=self.top_gnd_pin)
|
||||||
self.add_path(array_pin.layer, [array_pin.center(), vector(array_pin.center()[0], supply_pin.center()[1])])
|
|
||||||
self.add_via_stack_center(from_layer = array_pin.layer,
|
|
||||||
to_layer = supply_pin.layer,
|
|
||||||
offset = vector(array_pin.center()[0], supply_pin.center()[1]),
|
|
||||||
directions=("V", "V"))
|
|
||||||
if 'vdd' in bottom:
|
if 'vdd' in bottom:
|
||||||
inst = self.dummy_row_insts[0]
|
inst = self.dummy_row_insts[0]
|
||||||
if 'vdd' in inst.mod.pins:
|
if 'vdd' in inst.mod.pins:
|
||||||
array_pins = inst.get_pins('vdd')
|
for array_pin in inst.get_pins('vdd'):
|
||||||
for array_pin in array_pins:
|
self.connect_side_pin(array_pin, "bottom", self.bottom_vdd_pin.cy(),
|
||||||
supply_pin = self.bottom_vdd_pin
|
strap_pin=self.bottom_vdd_pin)
|
||||||
self.add_path(array_pin.layer, [array_pin.center(), vector(array_pin.center()[0], supply_pin.center()[1])])
|
|
||||||
self.add_via_stack_center(from_layer = array_pin.layer,
|
|
||||||
to_layer = supply_pin.layer,
|
|
||||||
offset = vector(array_pin.center()[0], supply_pin.center()[1]),
|
|
||||||
directions=("V", "V"))
|
|
||||||
if 'gnd' in bottom:
|
if 'gnd' in bottom:
|
||||||
inst = self.dummy_row_insts[0]
|
inst = self.dummy_row_insts[0]
|
||||||
if 'gnd' in inst.mod.pins:
|
if 'gnd' in inst.mod.pins:
|
||||||
array_pins = inst.get_pins('gnd')
|
for array_pin in inst.get_pins('gnd'):
|
||||||
for array_pin in array_pins:
|
self.connect_side_pin(array_pin, "bottom", self.bottom_gnd_pin.cy(),
|
||||||
supply_pin = self.bottom_gnd_pin
|
strap_pin=self.bottom_gnd_pin)
|
||||||
self.add_path(array_pin.layer, [array_pin.center(), vector(array_pin.center()[0], supply_pin.center()[1])])
|
|
||||||
self.add_via_stack_center(from_layer = array_pin.layer,
|
|
||||||
to_layer = supply_pin.layer,
|
|
||||||
offset = vector(array_pin.center()[0], supply_pin.center()[1]),
|
|
||||||
directions=("V", "V"))
|
|
||||||
if 'vdd' in left:
|
if 'vdd' in left:
|
||||||
inst = self.dummy_col_insts[0]
|
inst = self.dummy_col_insts[0]
|
||||||
if 'vdd' in inst.mod.pins:
|
if 'vdd' in inst.mod.pins:
|
||||||
array_pins = inst.get_pins('vdd')
|
for array_pin in inst.get_pins('vdd'):
|
||||||
for array_pin in array_pins:
|
self.connect_side_pin(array_pin, "left", self.left_vdd_pin.cx(),
|
||||||
supply_pin = self.left_vdd_pin
|
strap_pin=self.left_vdd_pin)
|
||||||
self.add_path(array_pin.layer, [array_pin.center(), vector(supply_pin.center()[0], array_pin.center()[1])])
|
|
||||||
self.add_via_stack_center(from_layer = array_pin.layer,
|
|
||||||
to_layer = supply_pin.layer,
|
|
||||||
offset = vector(supply_pin.center()[0], array_pin.center()[1]),
|
|
||||||
directions=("H", "H"))
|
|
||||||
if 'gnd' in left:
|
if 'gnd' in left:
|
||||||
inst = self.dummy_col_insts[0]
|
inst = self.dummy_col_insts[0]
|
||||||
if 'gnd' in inst.mod.pins:
|
if 'gnd' in inst.mod.pins:
|
||||||
array_pins = inst.get_pins('gnd')
|
for array_pin in inst.get_pins('gnd'):
|
||||||
for array_pin in array_pins:
|
self.connect_side_pin(array_pin, "left", self.left_gnd_pin.cx(),
|
||||||
supply_pin = self.left_gnd_pin
|
strap_pin=self.left_gnd_pin)
|
||||||
self.add_path(array_pin.layer, [array_pin.center(), vector(supply_pin.center()[0], array_pin.center()[1])])
|
|
||||||
self.add_via_stack_center(from_layer = array_pin.layer,
|
|
||||||
to_layer = supply_pin.layer,
|
|
||||||
offset = vector(supply_pin.center()[0], array_pin.center()[1]),
|
|
||||||
directions=("H", "H"))
|
|
||||||
if 'vdd' in right:
|
if 'vdd' in right:
|
||||||
inst = self.dummy_col_insts[1]
|
inst = self.dummy_col_insts[1]
|
||||||
if 'vdd' in inst.mod.pins:
|
if 'vdd' in inst.mod.pins:
|
||||||
array_pins = inst.get_pins('vdd')
|
for array_pin in inst.get_pins('vdd'):
|
||||||
for array_pin in array_pins:
|
self.connect_side_pin(array_pin, "right", self.right_vdd_pin.cx(),
|
||||||
supply_pin = self.right_vdd_pin
|
strap_pin=self.right_vdd_pin)
|
||||||
self.add_path(array_pin.layer, [array_pin.center(), vector(supply_pin.center()[0], array_pin.center()[1])])
|
|
||||||
self.add_via_stack_center(from_layer = array_pin.layer,
|
|
||||||
to_layer = supply_pin.layer,
|
|
||||||
offset = vector(supply_pin.center()[0], array_pin.center()[1]),
|
|
||||||
directions=("H", "H"))
|
|
||||||
if 'gnd' in right:
|
if 'gnd' in right:
|
||||||
inst = self.dummy_col_insts[1]
|
inst = self.dummy_col_insts[1]
|
||||||
if 'gnd' in inst.mod.pins:
|
if 'gnd' in inst.mod.pins:
|
||||||
array_pins = inst.get_pins('gnd')
|
for array_pin in inst.get_pins('gnd'):
|
||||||
for array_pin in array_pins:
|
self.connect_side_pin(array_pin, "right", self.right_gnd_pin.cx(),
|
||||||
supply_pin = self.right_gnd_pin
|
strap_pin=self.right_gnd_pin)
|
||||||
self.add_path(array_pin.layer, [array_pin.center(), vector(supply_pin.center()[0], array_pin.center()[1])])
|
|
||||||
self.add_via_stack_center(from_layer = array_pin.layer,
|
|
||||||
to_layer = supply_pin.layer,
|
|
||||||
offset = vector(supply_pin.center()[0], array_pin.center()[1]),
|
|
||||||
directions=("H", "H"))
|
|
||||||
|
|
||||||
def route_unused_wordlines(self):
|
def route_unused_wordlines(self):
|
||||||
"""
|
"""
|
||||||
|
|
@ -459,14 +426,18 @@ class capped_replica_bitcell_array(bitcell_base_array):
|
||||||
for inst in self.dummy_row_insts:
|
for inst in self.dummy_row_insts:
|
||||||
for wl_name in inst.mod.get_wordline_names():
|
for wl_name in inst.mod.get_wordline_names():
|
||||||
pin = inst.get_pin(wl_name)
|
pin = inst.get_pin(wl_name)
|
||||||
self.connect_side_pin(pin, "left", self.left_gnd_pin.cx())
|
self.connect_side_pin(pin, "left", self.left_gnd_pin.cx(),
|
||||||
self.connect_side_pin(pin, "right", self.right_gnd_pin.cx())
|
strap_pin=self.left_gnd_pin)
|
||||||
|
self.connect_side_pin(pin, "right", self.right_gnd_pin.cx(),
|
||||||
|
strap_pin=self.right_gnd_pin)
|
||||||
|
|
||||||
# Ground the unused replica wordlines
|
# Ground the unused replica wordlines
|
||||||
for wl_name in self.unused_wordline_names:
|
for wl_name in self.unused_wordline_names:
|
||||||
pin = self.replica_bitcell_array_inst.get_pin(wl_name)
|
pin = self.replica_bitcell_array_inst.get_pin(wl_name)
|
||||||
self.connect_side_pin(pin, "left", self.left_gnd_pin.cx())
|
self.connect_side_pin(pin, "left", self.left_gnd_pin.cx(),
|
||||||
self.connect_side_pin(pin, "right", self.right_gnd_pin.cx())
|
strap_pin=self.left_gnd_pin)
|
||||||
|
self.connect_side_pin(pin, "right", self.right_gnd_pin.cx(),
|
||||||
|
strap_pin=self.right_gnd_pin)
|
||||||
|
|
||||||
def route_side_pin(self, name, side, offset_multiple=1):
|
def route_side_pin(self, name, side, offset_multiple=1):
|
||||||
"""
|
"""
|
||||||
|
|
@ -527,49 +498,333 @@ class capped_replica_bitcell_array(bitcell_base_array):
|
||||||
|
|
||||||
return (left_loc, right_loc)
|
return (left_loc, right_loc)
|
||||||
|
|
||||||
def connect_side_pin(self, pin, side, offset):
|
def connect_side_pin(self, pin, side, offset, strap_pin=None):
|
||||||
"""
|
"""
|
||||||
Used to connect a pin to the a horizontal or vertical strap
|
Connect a pin to the horizontal or vertical supply strap.
|
||||||
offset gives the location of the strap
|
offset is the strap coordinate (y for top/bottom, x for left/right).
|
||||||
|
strap_pin is the ring segment pin; its layer is used as the via top target
|
||||||
|
(same as legacy route_supplies to_layer=supply_pin.layer).
|
||||||
|
|
||||||
|
Each tap is recorded in ``_strap_routing_endpoints``; after all supplies and
|
||||||
|
wordline grounds are routed, ``_bridge_close_strap_taps`` widens strap metal
|
||||||
|
along each rail group (min–max tap extent) to merge same-net strap shapes.
|
||||||
"""
|
"""
|
||||||
if side in ["left", "right"]:
|
if side in ["left", "right"]:
|
||||||
self.connect_vertical_side_pin(pin, offset)
|
self.connect_vertical_side_pin(pin, offset, strap_pin=strap_pin, side=side)
|
||||||
elif side in ["top", "bottom", "bot"]:
|
elif side in ["top", "bottom", "bot"]:
|
||||||
self.connect_horizontal_side_pin(pin, offset)
|
self.connect_horizontal_side_pin(pin, offset, strap_pin=strap_pin, side=side)
|
||||||
else:
|
else:
|
||||||
debug.error("Invalid side {}".format(side), -1)
|
debug.error("Invalid side {}".format(side), -1)
|
||||||
|
|
||||||
def connect_horizontal_side_pin(self, pin, yoffset):
|
def connect_horizontal_side_pin(self, pin, yoffset, strap_pin=None, side=None):
|
||||||
"""
|
"""
|
||||||
Used to connect a pin to the top/bottom horizontal straps
|
Used to connect a pin to the top/bottom horizontal straps.
|
||||||
"""
|
"""
|
||||||
cell_loc = pin.center()
|
cell_loc = pin.center()
|
||||||
pin_loc = vector(cell_loc.x, yoffset)
|
pin_loc = vector(cell_loc.x, yoffset)
|
||||||
|
to_layer = strap_pin.layer if strap_pin is not None else self.supply_stack[0]
|
||||||
|
|
||||||
# Place the pins a track outside of the array
|
|
||||||
self.add_via_stack_center(offset=pin_loc,
|
self.add_via_stack_center(offset=pin_loc,
|
||||||
from_layer=pin.layer,
|
from_layer=pin.layer,
|
||||||
to_layer=self.supply_stack[0],
|
to_layer=to_layer,
|
||||||
directions=("V", "V"))
|
directions=("V", "V"))
|
||||||
|
|
||||||
# Add a path to connect to the array
|
|
||||||
self.add_path(pin.layer, [cell_loc, pin_loc])
|
self.add_path(pin.layer, [cell_loc, pin_loc])
|
||||||
|
if strap_pin is not None and side is not None:
|
||||||
|
self._strap_routing_endpoints.append({"kind": "horizontal",
|
||||||
|
"side": side,
|
||||||
|
"strap": strap_pin,
|
||||||
|
"from_layer": pin.layer,
|
||||||
|
"pin_name": getattr(pin, "name", ""),
|
||||||
|
"center": pin_loc})
|
||||||
|
|
||||||
def connect_vertical_side_pin(self, pin, xoffset):
|
def connect_vertical_side_pin(self, pin, xoffset, strap_pin=None, side=None):
|
||||||
"""
|
"""
|
||||||
Used to connect a pin to the left/right vertical straps
|
Used to connect a pin to the left/right vertical straps.
|
||||||
"""
|
"""
|
||||||
cell_loc = pin.center()
|
cell_loc = pin.center()
|
||||||
pin_loc = vector(xoffset, cell_loc.y)
|
pin_loc = vector(xoffset, cell_loc.y)
|
||||||
|
to_layer = strap_pin.layer if strap_pin is not None else self.supply_stack[2]
|
||||||
|
|
||||||
# Place the pins a track outside of the array
|
|
||||||
self.add_via_stack_center(offset=pin_loc,
|
self.add_via_stack_center(offset=pin_loc,
|
||||||
from_layer=pin.layer,
|
from_layer=pin.layer,
|
||||||
to_layer=self.supply_stack[2],
|
to_layer=to_layer,
|
||||||
directions=("H", "H"))
|
directions=("H", "H"))
|
||||||
|
|
||||||
# Add a path to connect to the array
|
|
||||||
self.add_path(pin.layer, [cell_loc, pin_loc])
|
self.add_path(pin.layer, [cell_loc, pin_loc])
|
||||||
|
if strap_pin is not None and side is not None:
|
||||||
|
self._strap_routing_endpoints.append({"kind": "vertical",
|
||||||
|
"side": side,
|
||||||
|
"strap": strap_pin,
|
||||||
|
"from_layer": pin.layer,
|
||||||
|
"pin_name": getattr(pin, "name", ""),
|
||||||
|
"center": pin_loc})
|
||||||
|
|
||||||
|
def _strap_side_key(self, side):
|
||||||
|
return "bottom" if side == "bot" else side
|
||||||
|
|
||||||
|
def debug_print_strap_routing_endpoints(self, direction):
|
||||||
|
"""
|
||||||
|
Debug: print all entries in ``_strap_routing_endpoints`` for one strap
|
||||||
|
direction, sorted along the rail (y for left/right, x for top/bottom).
|
||||||
|
|
||||||
|
direction: ``'left'``, ``'right'``, ``'top'``, ``'bottom'``, or ``'bot'``.
|
||||||
|
|
||||||
|
Call after ``route_supplies`` / ``route_unused_wordlines`` and before
|
||||||
|
``_bridge_close_strap_taps`` (the bridge step clears the list).
|
||||||
|
"""
|
||||||
|
want = self._strap_side_key(direction)
|
||||||
|
if want not in ("left", "right", "top", "bottom"):
|
||||||
|
print("debug_print_strap_routing_endpoints: invalid direction {!r} (use left, right, top, bottom, bot)".format(direction))
|
||||||
|
return
|
||||||
|
|
||||||
|
recs = getattr(self, "_strap_routing_endpoints", None) or []
|
||||||
|
filtered = []
|
||||||
|
for r in recs:
|
||||||
|
sk = self._strap_side_key(r["side"])
|
||||||
|
if sk != want:
|
||||||
|
continue
|
||||||
|
if want in ("left", "right") and r["kind"] != "vertical":
|
||||||
|
continue
|
||||||
|
if want in ("top", "bottom") and r["kind"] != "horizontal":
|
||||||
|
continue
|
||||||
|
filtered.append(r)
|
||||||
|
|
||||||
|
if want in ("left", "right"):
|
||||||
|
filtered.sort(key=lambda r: (r["center"].y, r["center"].x, r.get("pin_name", "")))
|
||||||
|
sort_axis = "y"
|
||||||
|
else:
|
||||||
|
filtered.sort(key=lambda r: (r["center"].x, r["center"].y, r.get("pin_name", "")))
|
||||||
|
sort_axis = "x"
|
||||||
|
|
||||||
|
sep = "-" * 88
|
||||||
|
print(sep)
|
||||||
|
print("{} strap_routing_endpoints side={!r} ({} taps, sort by {})".format(
|
||||||
|
self.name, want, len(filtered), sort_axis))
|
||||||
|
print(sep)
|
||||||
|
hdr = "{:>4} {:>12} {:>12} {:>6} {:>6} {:<20} {}".format(
|
||||||
|
"idx", "cx", "cy", "strap", "from", "pin", "strap_c")
|
||||||
|
print(hdr)
|
||||||
|
print(sep)
|
||||||
|
for i, r in enumerate(filtered):
|
||||||
|
c = r["center"]
|
||||||
|
sp = r["strap"]
|
||||||
|
print("{:>4} {:12.4f} {:12.4f} {:>6} {:>6} {:<20} ({:.4f},{:.4f})".format(
|
||||||
|
i, c.x, c.y, sp.layer, r["from_layer"],
|
||||||
|
(r.get("pin_name") or "-")[:20],
|
||||||
|
sp.cx(), sp.cy()))
|
||||||
|
print(sep)
|
||||||
|
|
||||||
|
def _strap_bridge_supply_stack_contact_extent(self, vertical_rail_taps):
|
||||||
|
"""
|
||||||
|
Fallback: m3 (``supply_stack[0]``) first-layer span from the same
|
||||||
|
``directions`` as ``connect_side_pin``, without walking a pin-specific
|
||||||
|
via stack.
|
||||||
|
"""
|
||||||
|
st = self.supply_stack
|
||||||
|
if isinstance(st, (list, tuple)) and len(st) >= 3:
|
||||||
|
if vertical_rail_taps:
|
||||||
|
return contact(layer_stack=st, directions=("H", "H")).first_layer_width
|
||||||
|
return contact(layer_stack=st, directions=("V", "V")).first_layer_height
|
||||||
|
return self.supply_rail_width
|
||||||
|
|
||||||
|
def _strap_bridge_merge_draw_width(self, vertical_rail_taps, from_layers, strap_layer):
|
||||||
|
"""
|
||||||
|
Perpendicular merge-segment width matching ``add_via_stack_center`` plus
|
||||||
|
``add_min_area_rect_center`` on ``supply_stack[0]`` for each tap
|
||||||
|
``from_layer`` up to ``strap_layer``.
|
||||||
|
"""
|
||||||
|
st = self.supply_stack
|
||||||
|
if not isinstance(st, (list, tuple)) or len(st) < 3:
|
||||||
|
return self.supply_rail_width
|
||||||
|
metal_layer = st[0]
|
||||||
|
dirs = ("H", "H") if vertical_rail_taps else ("V", "V")
|
||||||
|
horizontal_extent = vertical_rail_taps
|
||||||
|
best = 0.0
|
||||||
|
for fl in from_layers:
|
||||||
|
ext = self.via_stack_metal_extent_after_min_area(fl, strap_layer, dirs,
|
||||||
|
metal_layer, horizontal_extent)
|
||||||
|
best = max(best, ext)
|
||||||
|
if best > 0:
|
||||||
|
return best
|
||||||
|
return self._strap_bridge_supply_stack_contact_extent(vertical_rail_taps)
|
||||||
|
|
||||||
|
def _strap_supply_bottom_metal(self):
|
||||||
|
"""Bottom routing metal in ``supply_stack`` (e.g. ``m3`` for ``m3_stack``)."""
|
||||||
|
st = self.supply_stack
|
||||||
|
if isinstance(st, (list, tuple)) and len(st) >= 3:
|
||||||
|
return st[0]
|
||||||
|
return None
|
||||||
|
|
||||||
|
def _strap_supply_top_metal(self):
|
||||||
|
"""Top routing metal in ``supply_stack`` (e.g. ``m4`` for ``m3_stack``)."""
|
||||||
|
st = self.supply_stack
|
||||||
|
if isinstance(st, (list, tuple)) and len(st) >= 3:
|
||||||
|
return st[2]
|
||||||
|
return None
|
||||||
|
|
||||||
|
def _strap_bridge_top_metal_via_width(self, vertical_rail_taps):
|
||||||
|
"""
|
||||||
|
Perpendicular span of ``supply_stack[2]`` (second metal) from the same
|
||||||
|
``contact(layer_stack=supply_stack, directions=...)`` as the side routes:
|
||||||
|
``second_layer_width`` with ``("H","H")`` (``route_vertical_side_pin``),
|
||||||
|
``second_layer_height`` with ``("V","V")`` (horizontal ring uses m3 width
|
||||||
|
from first layer; top metal thickness here matches the via's m4 enclosure).
|
||||||
|
"""
|
||||||
|
st = self.supply_stack
|
||||||
|
if not isinstance(st, (list, tuple)) or len(st) < 3:
|
||||||
|
return 0.0
|
||||||
|
if vertical_rail_taps:
|
||||||
|
return contact(layer_stack=st, directions=("H", "H")).second_layer_width
|
||||||
|
return contact(layer_stack=st, directions=("V", "V")).second_layer_height
|
||||||
|
|
||||||
|
def _strap_bridge_layer_spacing_thickness(self, rail_layer, strap_width_along_rail):
|
||||||
|
"""
|
||||||
|
Return ``(segment_layer, min_center_spacing)`` for merge bars on the
|
||||||
|
supply-stack **bottom metal** (e.g. ``m3``), not the via cut layer.
|
||||||
|
|
||||||
|
Tap-center spacing uses that metal's minwidth + same-layer spacing.
|
||||||
|
Drawn merge width is computed separately by ``_strap_bridge_merge_draw_width``.
|
||||||
|
"""
|
||||||
|
metal = self._strap_supply_bottom_metal()
|
||||||
|
if metal is not None:
|
||||||
|
thick = drc("minwidth_{}".format(metal))
|
||||||
|
same = "{}_to_{}".format(metal, metal)
|
||||||
|
sp = drc(same) if same in drc else 0.0
|
||||||
|
min_sep = max(self.supply_rail_pitch, thick + sp)
|
||||||
|
return metal, min_sep
|
||||||
|
same = "{}_to_{}".format(rail_layer, rail_layer)
|
||||||
|
sp = drc(same) if same in drc else 0.0
|
||||||
|
min_sep = max(self.supply_rail_pitch, strap_width_along_rail + sp)
|
||||||
|
return rail_layer, min_sep
|
||||||
|
|
||||||
|
def _bridge_close_strap_taps(self):
|
||||||
|
"""
|
||||||
|
After routing strap taps, add merge segments on the supply-stack **bottom
|
||||||
|
metal** (e.g. ``m3``), not the via cut layer, between consecutive taps that
|
||||||
|
are closer than DRC-safe spacing on that metal. When taps are on the
|
||||||
|
supply-stack **top** metal (e.g. ``m4``), the same merge also draws on that
|
||||||
|
layer using the via's second-metal width (``route_vertical_side_pin`` /
|
||||||
|
``contact`` ``second_layer_*``). Each segment runs from the outer **metal**
|
||||||
|
extent at one tap to the next along the rail (same stack as
|
||||||
|
``add_via_stack_center``), not only between tap centers. Falls back to strap
|
||||||
|
routing metal if no 3-tuple ``supply_stack`` is defined.
|
||||||
|
"""
|
||||||
|
if not getattr(self, "_strap_routing_endpoints", None):
|
||||||
|
return
|
||||||
|
|
||||||
|
w_vert = getattr(self, "_strap_width_vertical_rail", self.supply_rail_width)
|
||||||
|
w_horiz = getattr(self, "_strap_width_horizontal_rail", self.supply_rail_width)
|
||||||
|
eps = 1e-9
|
||||||
|
|
||||||
|
def bridge_vertical_cluster(cx, rail_layer, recs_sorted):
|
||||||
|
if len(recs_sorted) < 2:
|
||||||
|
return
|
||||||
|
from_layers = {r["from_layer"] for r in recs_sorted}
|
||||||
|
seg_layer, min_sep = self._strap_bridge_layer_spacing_thickness(
|
||||||
|
rail_layer, w_vert)
|
||||||
|
draw_w = self._strap_bridge_merge_draw_width(True, from_layers, rail_layer)
|
||||||
|
dirs = ("H", "H")
|
||||||
|
metal_lm = self._strap_supply_bottom_metal()
|
||||||
|
for i in range(len(recs_sorted) - 1):
|
||||||
|
r0, r1 = recs_sorted[i], recs_sorted[i + 1]
|
||||||
|
y0, y1 = r0["center"].y, r1["center"].y
|
||||||
|
gap = y1 - y0
|
||||||
|
if gap <= eps:
|
||||||
|
continue
|
||||||
|
if gap < min_sep:
|
||||||
|
if metal_lm is not None:
|
||||||
|
ext0 = self.via_stack_metal_layer_extent_parallel_to_rail(
|
||||||
|
r0["from_layer"], rail_layer, dirs, metal_lm, True)
|
||||||
|
ext1 = self.via_stack_metal_layer_extent_parallel_to_rail(
|
||||||
|
r1["from_layer"], rail_layer, dirs, metal_lm, True)
|
||||||
|
else:
|
||||||
|
ext0 = ext1 = 0.0
|
||||||
|
ya = y0 - 0.5 * ext0
|
||||||
|
yb = y1 + 0.5 * ext1
|
||||||
|
if yb <= ya + eps:
|
||||||
|
ya, yb = y0, y1
|
||||||
|
self.add_segment_center(layer=seg_layer,
|
||||||
|
start=vector(cx, ya),
|
||||||
|
end=vector(cx, yb),
|
||||||
|
width=draw_w)
|
||||||
|
top_m = self._strap_supply_top_metal()
|
||||||
|
if top_m is not None and rail_layer == top_m:
|
||||||
|
w_top = self._strap_bridge_top_metal_via_width(True)
|
||||||
|
if w_top > 0:
|
||||||
|
self.add_segment_center(layer=top_m,
|
||||||
|
start=vector(cx, ya),
|
||||||
|
end=vector(cx, yb),
|
||||||
|
width=w_top)
|
||||||
|
|
||||||
|
def bridge_horizontal_cluster(cy, rail_layer, recs_sorted):
|
||||||
|
if len(recs_sorted) < 2:
|
||||||
|
return
|
||||||
|
from_layers = {r["from_layer"] for r in recs_sorted}
|
||||||
|
seg_layer, min_sep = self._strap_bridge_layer_spacing_thickness(
|
||||||
|
rail_layer, w_horiz)
|
||||||
|
draw_w = self._strap_bridge_merge_draw_width(False, from_layers, rail_layer)
|
||||||
|
dirs = ("V", "V")
|
||||||
|
metal_lm = self._strap_supply_bottom_metal()
|
||||||
|
for i in range(len(recs_sorted) - 1):
|
||||||
|
r0, r1 = recs_sorted[i], recs_sorted[i + 1]
|
||||||
|
x0, x1 = r0["center"].x, r1["center"].x
|
||||||
|
gap = x1 - x0
|
||||||
|
if gap <= eps:
|
||||||
|
continue
|
||||||
|
if gap < min_sep:
|
||||||
|
if metal_lm is not None:
|
||||||
|
ext0 = self.via_stack_metal_layer_extent_parallel_to_rail(
|
||||||
|
r0["from_layer"], rail_layer, dirs, metal_lm, False)
|
||||||
|
ext1 = self.via_stack_metal_layer_extent_parallel_to_rail(
|
||||||
|
r1["from_layer"], rail_layer, dirs, metal_lm, False)
|
||||||
|
else:
|
||||||
|
ext0 = ext1 = 0.0
|
||||||
|
xa = x0 - 0.5 * ext0
|
||||||
|
xb = x1 + 0.5 * ext1
|
||||||
|
if xb <= xa + eps:
|
||||||
|
xa, xb = x0, x1
|
||||||
|
self.add_segment_center(layer=seg_layer,
|
||||||
|
start=vector(xa, cy),
|
||||||
|
end=vector(xb, cy),
|
||||||
|
width=draw_w)
|
||||||
|
top_m = self._strap_supply_top_metal()
|
||||||
|
if top_m is not None and rail_layer == top_m:
|
||||||
|
w_top = self._strap_bridge_top_metal_via_width(False)
|
||||||
|
if w_top > 0:
|
||||||
|
self.add_segment_center(layer=top_m,
|
||||||
|
start=vector(xa, cy),
|
||||||
|
end=vector(xb, cy),
|
||||||
|
width=w_top)
|
||||||
|
|
||||||
|
vertical_groups = {}
|
||||||
|
horizontal_groups = {}
|
||||||
|
for rec in self._strap_routing_endpoints:
|
||||||
|
strap = rec["strap"]
|
||||||
|
c = rec["center"]
|
||||||
|
sk = self._strap_side_key(rec["side"])
|
||||||
|
if rec["kind"] == "vertical":
|
||||||
|
key = (sk, round(strap.cx(), 9), strap.layer)
|
||||||
|
vertical_groups.setdefault(key, []).append(rec)
|
||||||
|
elif rec["kind"] == "horizontal":
|
||||||
|
key = (sk, round(strap.cy(), 9), strap.layer)
|
||||||
|
horizontal_groups.setdefault(key, []).append(rec)
|
||||||
|
|
||||||
|
for key, recs in vertical_groups.items():
|
||||||
|
if len(recs) < 2:
|
||||||
|
continue
|
||||||
|
recs.sort(key=lambda r: (r["center"].y, r["center"].x))
|
||||||
|
cx = recs[0]["center"].x
|
||||||
|
rail_layer = key[2]
|
||||||
|
bridge_vertical_cluster(cx, rail_layer, recs)
|
||||||
|
|
||||||
|
for key, recs in horizontal_groups.items():
|
||||||
|
if len(recs) < 2:
|
||||||
|
continue
|
||||||
|
recs.sort(key=lambda r: (r["center"].x, r["center"].y))
|
||||||
|
cy = recs[0]["center"].y
|
||||||
|
rail_layer = key[2]
|
||||||
|
bridge_horizontal_cluster(cy, rail_layer, recs)
|
||||||
|
|
||||||
|
self._strap_routing_endpoints = []
|
||||||
|
|
||||||
def analytical_power(self, corner, load):
|
def analytical_power(self, corner, load):
|
||||||
"""Power of Bitcell array and bitline in nW."""
|
"""Power of Bitcell array and bitline in nW."""
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue