mirror of https://github.com/VLSIDA/OpenRAM.git
Merge branch 'dev' into characterizer_bug_fixes
This commit is contained in:
commit
206b02a7ee
|
|
@ -0,0 +1,309 @@
|
|||
# See LICENSE for licensing information.
|
||||
#
|
||||
# Copyright (c) 2016-2019 Regents of the University of California and The Board
|
||||
# of Regents for the Oklahoma Agricultural and Mechanical College
|
||||
# (acting for and on behalf of Oklahoma State University)
|
||||
# All rights reserved.
|
||||
#
|
||||
import collections
|
||||
import debug
|
||||
from tech import drc
|
||||
from vector import vector
|
||||
import design
|
||||
|
||||
|
||||
class channel_route(design.design):
|
||||
|
||||
unique_id = 0
|
||||
|
||||
def __init__(self,
|
||||
netlist,
|
||||
offset,
|
||||
layer_stack,
|
||||
directions=None,
|
||||
vertical=False):
|
||||
"""
|
||||
The net list is a list of the nets with each net being a list of pins
|
||||
to be connected. The offset is the lower-left of where the
|
||||
routing channel will start. This does NOT try to minimize the
|
||||
number of tracks -- instead, it picks an order to avoid the
|
||||
vertical conflicts between pins. The track size must be the number of
|
||||
nets times the *nonpreferred* routing of the non-track layer pitch.
|
||||
|
||||
"""
|
||||
name = "cr_{0}".format(channel_route.unique_id)
|
||||
channel_route.unique_id += 1
|
||||
design.design.__init__(self, name)
|
||||
|
||||
self.netlist = netlist
|
||||
self.offset = offset
|
||||
self.layer_stack = layer_stack
|
||||
self.directions = directions
|
||||
self.vertical = vertical
|
||||
|
||||
if not directions or directions == "pref":
|
||||
# Use the preferred layer directions
|
||||
if self.get_preferred_direction(layer_stack[0]) == "V":
|
||||
self.vertical_layer = layer_stack[0]
|
||||
self.horizontal_layer = layer_stack[2]
|
||||
else:
|
||||
self.vertical_layer = layer_stack[2]
|
||||
self.horizontal_layer = layer_stack[0]
|
||||
elif directions == "nonpref":
|
||||
# Use the preferred layer directions
|
||||
if self.get_preferred_direction(layer_stack[0]) == "V":
|
||||
self.vertical_layer = layer_stack[2]
|
||||
self.horizontal_layer = layer_stack[0]
|
||||
else:
|
||||
self.vertical_layer = layer_stack[0]
|
||||
self.horizontal_layer = layer_stack[2]
|
||||
else:
|
||||
# Use the layer directions specified to the router rather than
|
||||
# the preferred directions
|
||||
debug.check(directions[0] != directions[1], "Must have unique layer directions.")
|
||||
if directions[0] == "V":
|
||||
self.vertical_layer = layer_stack[0]
|
||||
self.horizontal_layer = layer_stack[2]
|
||||
else:
|
||||
self.horizontal_layer = layer_stack[0]
|
||||
self.vertical_layer = layer_stack[2]
|
||||
|
||||
layer_stuff = self.get_layer_pitch(self.vertical_layer)
|
||||
(self.vertical_nonpref_pitch, self.vertical_pitch, self.vertical_width, self.vertical_space) = layer_stuff
|
||||
|
||||
layer_stuff = self.get_layer_pitch(self.horizontal_layer)
|
||||
(self.horizontal_nonpref_pitch, self.horizontal_pitch, self.horizontal_width, self.horizontal_space) = layer_stuff
|
||||
|
||||
self.route()
|
||||
|
||||
def remove_net_from_graph(self, pin, g):
|
||||
"""
|
||||
Remove the pin from the graph and all conflicts
|
||||
"""
|
||||
g.pop(pin, None)
|
||||
|
||||
# Remove the pin from all conflicts
|
||||
# FIXME: This is O(n^2), so maybe optimize it.
|
||||
for other_pin, conflicts in g.items():
|
||||
if pin in conflicts:
|
||||
conflicts.remove(pin)
|
||||
g[other_pin]=conflicts
|
||||
return g
|
||||
|
||||
def vcg_nets_overlap(self, net1, net2):
|
||||
"""
|
||||
Check all the pin pairs on two nets and return a pin
|
||||
overlap if any pin overlaps.
|
||||
"""
|
||||
|
||||
if self.vertical:
|
||||
pitch = self.horizontal_nonpref_pitch
|
||||
else:
|
||||
pitch = self.vertical_nonpref_pitch
|
||||
|
||||
for pin1 in net1:
|
||||
for pin2 in net2:
|
||||
if self.vcg_pin_overlap(pin1, pin2, pitch):
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
def route(self):
|
||||
# FIXME: Must extend this to a horizontal conflict graph
|
||||
# too if we want to minimize the
|
||||
# number of tracks!
|
||||
# hcg = {}
|
||||
|
||||
# Initialize the vertical conflict graph (vcg)
|
||||
# and make a list of all pins
|
||||
vcg = collections.OrderedDict()
|
||||
|
||||
# Create names for the nets for the graphs
|
||||
nets = collections.OrderedDict()
|
||||
index = 0
|
||||
# print(netlist)
|
||||
for pin_list in self.netlist:
|
||||
net_name = "n{}".format(index)
|
||||
index += 1
|
||||
nets[net_name] = pin_list
|
||||
|
||||
# print("Nets:")
|
||||
# for net_name in nets:
|
||||
# print(net_name, [x.name for x in nets[net_name]])
|
||||
|
||||
# Find the vertical pin conflicts
|
||||
# FIXME: O(n^2) but who cares for now
|
||||
for net_name1 in nets:
|
||||
if net_name1 not in vcg.keys():
|
||||
vcg[net_name1] = []
|
||||
for net_name2 in nets:
|
||||
if net_name2 not in vcg.keys():
|
||||
vcg[net_name2] = []
|
||||
# Skip yourself
|
||||
if net_name1 == net_name2:
|
||||
continue
|
||||
if self.vcg_nets_overlap(nets[net_name1],
|
||||
nets[net_name2]):
|
||||
vcg[net_name2].append(net_name1)
|
||||
|
||||
current_offset = self.offset
|
||||
|
||||
# list of routes to do
|
||||
while vcg:
|
||||
# from pprint import pformat
|
||||
# print("VCG:\n", pformat(vcg))
|
||||
# get a route from conflict graph with empty fanout set
|
||||
net_name = None
|
||||
for net_name, conflicts in vcg.items():
|
||||
if len(conflicts) == 0:
|
||||
vcg = self.remove_net_from_graph(net_name, vcg)
|
||||
break
|
||||
else:
|
||||
# FIXME: We don't support cyclic VCGs right now.
|
||||
debug.error("Cyclic VCG in channel router.", -1)
|
||||
|
||||
# These are the pins we'll have to connect
|
||||
pin_list = nets[net_name]
|
||||
# print("Routing:", net_name, [x.name for x in pin_list])
|
||||
|
||||
# Remove the net from other constriants in the VCG
|
||||
vcg = self.remove_net_from_graph(net_name, vcg)
|
||||
|
||||
# Add the trunk routes from the bottom up for
|
||||
# horizontal or the left to right for vertical
|
||||
if self.vertical:
|
||||
self.add_vertical_trunk_route(pin_list,
|
||||
current_offset,
|
||||
self.vertical_nonpref_pitch)
|
||||
# This accounts for the via-to-via spacings
|
||||
current_offset += vector(self.horizontal_nonpref_pitch, 0)
|
||||
else:
|
||||
self.add_horizontal_trunk_route(pin_list,
|
||||
current_offset,
|
||||
self.horizontal_nonpref_pitch)
|
||||
# This accounts for the via-to-via spacings
|
||||
current_offset += vector(0, self.vertical_nonpref_pitch)
|
||||
|
||||
# Return the size of the channel
|
||||
if self.vertical:
|
||||
self.width = 0
|
||||
self.height = current_offset.y
|
||||
return current_offset.y + self.vertical_nonpref_pitch - self.offset.y
|
||||
else:
|
||||
self.width = current_offset.x
|
||||
self.height = 0
|
||||
return current_offset.x + self.horizontal_nonpref_pitch - self.offset.x
|
||||
|
||||
def get_layer_pitch(self, layer):
|
||||
""" Return the track pitch on a given layer """
|
||||
try:
|
||||
# FIXME: Using non-pref pitch here due to overlap bug in VCG constraints.
|
||||
# It should just result in inefficient channel width but will work.
|
||||
pitch = getattr(self, "{}_pitch".format(layer))
|
||||
nonpref_pitch = getattr(self, "{}_nonpref_pitch".format(layer))
|
||||
space = getattr(self, "{}_space".format(layer))
|
||||
except AttributeError:
|
||||
debug.error("Cannot find layer pitch.", -1)
|
||||
return (nonpref_pitch, pitch, pitch - space, space)
|
||||
|
||||
def add_horizontal_trunk_route(self,
|
||||
pins,
|
||||
trunk_offset,
|
||||
pitch):
|
||||
"""
|
||||
Create a trunk route for all pins with
|
||||
the trunk located at the given y offset.
|
||||
"""
|
||||
max_x = max([pin.center().x for pin in pins])
|
||||
min_x = min([pin.center().x for pin in pins])
|
||||
|
||||
# if we are less than a pitch, just create a non-preferred layer jog
|
||||
non_preferred_route = max_x - min_x <= pitch
|
||||
|
||||
if non_preferred_route:
|
||||
half_layer_width = 0.5 * drc["minwidth_{0}".format(self.vertical_layer)]
|
||||
# Add the horizontal trunk on the vertical layer!
|
||||
self.add_path(self.vertical_layer,
|
||||
[vector(min_x - half_layer_width, trunk_offset.y),
|
||||
vector(max_x + half_layer_width, trunk_offset.y)])
|
||||
else:
|
||||
# Add the horizontal trunk
|
||||
self.add_path(self.horizontal_layer,
|
||||
[vector(min_x, trunk_offset.y),
|
||||
vector(max_x, trunk_offset.y)])
|
||||
|
||||
# Route each pin to the trunk
|
||||
for pin in pins:
|
||||
# Find the correct side of the pin
|
||||
if pin.cy() < trunk_offset.y:
|
||||
pin_pos = pin.uc()
|
||||
else:
|
||||
pin_pos = pin.bc()
|
||||
mid = vector(pin_pos.x, trunk_offset.y)
|
||||
self.add_path(self.vertical_layer, [pin_pos, mid])
|
||||
if not non_preferred_route:
|
||||
self.add_via_center(layers=self.layer_stack,
|
||||
offset=mid,
|
||||
directions=self.directions)
|
||||
self.add_via_stack_center(from_layer=pin.layer,
|
||||
to_layer=self.vertical_layer,
|
||||
offset=pin_pos)
|
||||
|
||||
def add_vertical_trunk_route(self,
|
||||
pins,
|
||||
trunk_offset,
|
||||
pitch):
|
||||
"""
|
||||
Create a trunk route for all pins with the
|
||||
trunk located at the given x offset.
|
||||
"""
|
||||
max_y = max([pin.center().y for pin in pins])
|
||||
min_y = min([pin.center().y for pin in pins])
|
||||
|
||||
# if we are less than a pitch, just create a non-preferred layer jog
|
||||
non_preferred_route = max_y - min_y <= pitch
|
||||
|
||||
if non_preferred_route:
|
||||
half_layer_width = 0.5 * drc["minwidth_{0}".format(self.horizontal_layer)]
|
||||
# Add the vertical trunk on the horizontal layer!
|
||||
self.add_path(self.horizontal_layer,
|
||||
[vector(trunk_offset.x, min_y - half_layer_width),
|
||||
vector(trunk_offset.x, max_y + half_layer_width)])
|
||||
else:
|
||||
# Add the vertical trunk
|
||||
self.add_path(self.vertical_layer,
|
||||
[vector(trunk_offset.x, min_y),
|
||||
vector(trunk_offset.x, max_y)])
|
||||
|
||||
# Route each pin to the trunk
|
||||
for pin in pins:
|
||||
# Find the correct side of the pin
|
||||
if pin.cx() < trunk_offset.x:
|
||||
pin_pos = pin.rc()
|
||||
else:
|
||||
pin_pos = pin.lc()
|
||||
mid = vector(trunk_offset.x, pin_pos.y)
|
||||
self.add_path(self.horizontal_layer, [pin_pos, mid])
|
||||
if not non_preferred_route:
|
||||
self.add_via_center(layers=self.layer_stack,
|
||||
offset=mid,
|
||||
directions=self.directions)
|
||||
self.add_via_stack_center(from_layer=pin.layer,
|
||||
to_layer=self.horizontal_layer,
|
||||
offset=pin_pos)
|
||||
|
||||
def vcg_pin_overlap(self, pin1, pin2, pitch):
|
||||
""" Check for vertical or horizontal overlap of the two pins """
|
||||
|
||||
# FIXME: If the pins are not in a row, this may break.
|
||||
# However, a top pin shouldn't overlap another top pin,
|
||||
# for example, so the extra comparison *shouldn't* matter.
|
||||
|
||||
# Pin 1 must be in the "BOTTOM" set
|
||||
x_overlap = pin1.by() < pin2.by() and abs(pin1.center().x - pin2.center().x) < pitch
|
||||
|
||||
# Pin 1 must be in the "LEFT" set
|
||||
y_overlap = pin1.lx() < pin2.lx() and abs(pin1.center().y - pin2.center().y) < pitch
|
||||
overlaps = (not self.vertical and x_overlap) or (self.vertical and y_overlap)
|
||||
return overlaps
|
||||
|
||||
|
|
@ -205,7 +205,8 @@ class design(hierarchy_design):
|
|||
print("poly_to_active", self.poly_to_active)
|
||||
print("poly_extend_active", self.poly_extend_active)
|
||||
print("poly_to_contact", self.poly_to_contact)
|
||||
print("contact_to_gate", self.contact_to_gate)
|
||||
print("active_contact_to_gate", self.active_contact_to_gate)
|
||||
print("poly_contact_to_gate", self.poly_contact_to_gate)
|
||||
print("well_enclose_active", self.well_enclose_active)
|
||||
print("implant_enclose_active", self.implant_enclose_active)
|
||||
print("implant_space", self.implant_space)
|
||||
|
|
|
|||
|
|
@ -7,7 +7,6 @@
|
|||
#
|
||||
import hierarchy_layout
|
||||
import hierarchy_spice
|
||||
import verify
|
||||
import debug
|
||||
import os
|
||||
from globals import OPTS
|
||||
|
|
@ -31,6 +30,7 @@ class hierarchy_design(hierarchy_spice.spice, hierarchy_layout.layout):
|
|||
except AttributeError:
|
||||
lvs_subdir = "lvs_lib"
|
||||
lvs_dir = OPTS.openram_tech + lvs_subdir + "/"
|
||||
|
||||
if os.path.exists(lvs_dir):
|
||||
self.lvs_file = lvs_dir + name + ".sp"
|
||||
else:
|
||||
|
|
@ -54,6 +54,7 @@ class hierarchy_design(hierarchy_spice.spice, hierarchy_layout.layout):
|
|||
|
||||
def DRC_LVS(self, final_verification=False, force_check=False):
|
||||
"""Checks both DRC and LVS for a module"""
|
||||
import verify
|
||||
|
||||
# No layout to check
|
||||
if OPTS.netlist_only:
|
||||
|
|
@ -93,6 +94,8 @@ class hierarchy_design(hierarchy_spice.spice, hierarchy_layout.layout):
|
|||
|
||||
def DRC(self, final_verification=False):
|
||||
"""Checks DRC for a module"""
|
||||
import verify
|
||||
|
||||
# Unit tests will check themselves.
|
||||
# Do not run if disabled in options.
|
||||
|
||||
|
|
@ -116,6 +119,8 @@ class hierarchy_design(hierarchy_spice.spice, hierarchy_layout.layout):
|
|||
|
||||
def LVS(self, final_verification=False):
|
||||
"""Checks LVS for a module"""
|
||||
import verify
|
||||
|
||||
# Unit tests will check themselves.
|
||||
# Do not run if disabled in options.
|
||||
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@
|
|||
# (acting for and on behalf of Oklahoma State University)
|
||||
# All rights reserved.
|
||||
#
|
||||
import collections
|
||||
import geometry
|
||||
import gdsMill
|
||||
import debug
|
||||
|
|
@ -14,6 +13,7 @@ from tech import drc, GDS
|
|||
from tech import layer as techlayer
|
||||
from tech import layer_indices
|
||||
from tech import layer_stacks
|
||||
from tech import preferred_directions
|
||||
import os
|
||||
from globals import OPTS
|
||||
from vector import vector
|
||||
|
|
@ -269,6 +269,23 @@ class layout():
|
|||
width,
|
||||
end.y - start.y)
|
||||
|
||||
def get_tx_insts(self, tx_type=None):
|
||||
"""
|
||||
Return a list of the instances of given tx type.
|
||||
"""
|
||||
tx_list = []
|
||||
for i in self.insts:
|
||||
try:
|
||||
if tx_type and i.mod.tx_type == tx_type:
|
||||
tx_list.append(i)
|
||||
elif not tx_type:
|
||||
if i.mod.tx_type == "nmos" or i.mod.tx_type == "pmos":
|
||||
tx_list.append(i)
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
return tx_list
|
||||
|
||||
def get_pin(self, text):
|
||||
"""
|
||||
Return the pin or list of pins
|
||||
|
|
@ -505,7 +522,6 @@ class layout():
|
|||
|
||||
def get_preferred_direction(self, layer):
|
||||
""" Return the preferred routing directions """
|
||||
from tech import preferred_directions
|
||||
return preferred_directions[layer]
|
||||
|
||||
def add_via(self, layers, offset, size=[1, 1], directions=None, implant_type=None, well_type=None):
|
||||
|
|
@ -551,24 +567,6 @@ class layout():
|
|||
self.connect_inst([])
|
||||
return inst
|
||||
|
||||
def add_via_stack(self, offset, from_layer, to_layer,
|
||||
directions=None,
|
||||
size=[1, 1],
|
||||
implant_type=None,
|
||||
well_type=None):
|
||||
"""
|
||||
Punch a stack of vias from a start layer to a target layer.
|
||||
"""
|
||||
return self.__add_via_stack_internal(offset=offset,
|
||||
directions=directions,
|
||||
from_layer=from_layer,
|
||||
to_layer=to_layer,
|
||||
via_func=self.add_via,
|
||||
last_via=None,
|
||||
size=size,
|
||||
implant_type=implant_type,
|
||||
well_type=well_type)
|
||||
|
||||
def add_via_stack_center(self,
|
||||
offset,
|
||||
from_layer,
|
||||
|
|
@ -578,24 +576,7 @@ class layout():
|
|||
implant_type=None,
|
||||
well_type=None):
|
||||
"""
|
||||
Punch a stack of vias from a start layer to a target layer by the center
|
||||
coordinate accounting for mirroring and rotation.
|
||||
"""
|
||||
return self.__add_via_stack_internal(offset=offset,
|
||||
directions=directions,
|
||||
from_layer=from_layer,
|
||||
to_layer=to_layer,
|
||||
via_func=self.add_via_center,
|
||||
last_via=None,
|
||||
size=size,
|
||||
implant_type=implant_type,
|
||||
well_type=well_type)
|
||||
|
||||
def __add_via_stack_internal(self, offset, directions, from_layer, to_layer,
|
||||
via_func, last_via, size, implant_type=None, well_type=None):
|
||||
"""
|
||||
Punch a stack of vias from a start layer to a target layer. Here we
|
||||
figure out whether to punch it up or down the stack.
|
||||
Punch a stack of vias from a start layer to a target layer by the center.
|
||||
"""
|
||||
|
||||
if from_layer == to_layer:
|
||||
|
|
@ -603,38 +584,65 @@ class layout():
|
|||
# a metal enclosure. This helps with center-line path routing.
|
||||
self.add_rect_center(layer=from_layer,
|
||||
offset=offset)
|
||||
return last_via
|
||||
return None
|
||||
|
||||
from_id = layer_indices[from_layer]
|
||||
to_id = layer_indices[to_layer]
|
||||
via = None
|
||||
cur_layer = from_layer
|
||||
while cur_layer != to_layer:
|
||||
from_id = layer_indices[cur_layer]
|
||||
to_id = layer_indices[to_layer]
|
||||
|
||||
if from_id < to_id: # grow the stack up
|
||||
search_id = 0
|
||||
next_id = 2
|
||||
else: # grow the stack down
|
||||
search_id = 2
|
||||
next_id = 0
|
||||
if from_id < to_id: # grow the stack up
|
||||
search_id = 0
|
||||
next_id = 2
|
||||
else: # grow the stack down
|
||||
search_id = 2
|
||||
next_id = 0
|
||||
|
||||
curr_stack = next(filter(lambda stack: stack[search_id] == from_layer, layer_stacks), None)
|
||||
if curr_stack is None:
|
||||
raise ValueError("Cannot create via from '{0}' to '{1}'."
|
||||
"Layer '{0}' not defined".format(from_layer, to_layer))
|
||||
curr_stack = next(filter(lambda stack: stack[search_id] == cur_layer, layer_stacks), None)
|
||||
|
||||
via = self.add_via_center(layers=curr_stack,
|
||||
size=size,
|
||||
offset=offset,
|
||||
directions=directions,
|
||||
implant_type=implant_type,
|
||||
well_type=well_type)
|
||||
|
||||
if cur_layer != from_layer:
|
||||
self.add_min_area_rect_center(cur_layer,
|
||||
offset,
|
||||
via.mod.first_layer_width,
|
||||
via.mod.first_layer_height)
|
||||
|
||||
cur_layer = curr_stack[next_id]
|
||||
|
||||
via = via_func(layers=curr_stack,
|
||||
size=size,
|
||||
offset=offset,
|
||||
directions=directions,
|
||||
implant_type=implant_type,
|
||||
well_type=well_type)
|
||||
|
||||
via = self.__add_via_stack_internal(offset=offset,
|
||||
directions=directions,
|
||||
from_layer=curr_stack[next_id],
|
||||
to_layer=to_layer,
|
||||
via_func=via_func,
|
||||
last_via=via,
|
||||
size=size)
|
||||
return via
|
||||
|
||||
def add_min_area_rect_center(self,
|
||||
layer,
|
||||
offset,
|
||||
width=None,
|
||||
height=None):
|
||||
"""
|
||||
Add a minimum area retcangle at the given point.
|
||||
Either width or height should be fixed.
|
||||
"""
|
||||
|
||||
min_area = drc("minarea_{}".format(layer))
|
||||
if min_area == 0:
|
||||
return
|
||||
|
||||
min_width = drc("minwidth_{}".format(layer))
|
||||
|
||||
if preferred_directions[layer] == "V":
|
||||
height = max(min_area / width, min_width)
|
||||
else:
|
||||
width = max(min_area / height, min_width)
|
||||
|
||||
self.add_rect_center(layer=layer,
|
||||
offset=offset,
|
||||
width=width,
|
||||
height=height)
|
||||
|
||||
def add_ptx(self, offset, mirror="R0", rotate=0, width=1, mults=1, tx_type="nmos"):
|
||||
"""Adds a ptx module to the design."""
|
||||
|
|
@ -723,7 +731,7 @@ class layout():
|
|||
width=width,
|
||||
height=height,
|
||||
center=False)
|
||||
debug.info(2, "Adding {0} boundary {1}".format(self.name, boundary))
|
||||
debug.info(4, "Adding {0} boundary {1}".format(self.name, boundary))
|
||||
|
||||
self.visited.append(self.name)
|
||||
|
||||
|
|
@ -909,8 +917,10 @@ class layout():
|
|||
(horizontal_layer, via_layer, vertical_layer) = layer_stack
|
||||
if horizontal:
|
||||
route_layer = vertical_layer
|
||||
bys_layer = horizontal_layer
|
||||
else:
|
||||
route_layer = horizontal_layer
|
||||
bus_layer = vertical_layer
|
||||
|
||||
for (pin_name, bus_name) in mapping:
|
||||
pin = inst.get_pin(pin_name)
|
||||
|
|
@ -932,17 +942,18 @@ class layout():
|
|||
# Connect to the pin on the instances with a via if it is
|
||||
# not on the right layer
|
||||
if pin.layer != route_layer:
|
||||
self.add_via_center(layers=layer_stack,
|
||||
offset=pin_pos)
|
||||
self.add_via_stack_center(from_layer=pin.layer,
|
||||
to_layer=route_layer,
|
||||
offset=pin_pos)
|
||||
# FIXME: output pins tend to not be rotate,
|
||||
# but supply pins are. Make consistent?
|
||||
|
||||
# We only need a via if they happened to align perfectly
|
||||
# so the add_wire didn't add a via
|
||||
if (horizontal and bus_pos.y == pin_pos.y) or (not horizontal and bus_pos.x == pin_pos.x):
|
||||
self.add_via_center(layers=layer_stack,
|
||||
offset=bus_pos,
|
||||
rotate=90)
|
||||
self.add_via_stack_center(from_layer=route_layer,
|
||||
to_layer=bus_layer,
|
||||
offset=bus_pos)
|
||||
|
||||
def connect_vbus(self, src_pin, dest_pin, hlayer="m3", vlayer="m2"):
|
||||
"""
|
||||
|
|
@ -1002,282 +1013,24 @@ class layout():
|
|||
to_layer=dest_pin.layer,
|
||||
offset=out_pos)
|
||||
|
||||
def get_layer_pitch(self, layer):
|
||||
""" Return the track pitch on a given layer """
|
||||
try:
|
||||
# FIXME: Using non-pref pitch here due to overlap bug in VCG constraints.
|
||||
# It should just result in inefficient channel width but will work.
|
||||
pitch = getattr(self, "{}_pitch".format(layer))
|
||||
nonpref_pitch = getattr(self, "{}_nonpref_pitch".format(layer))
|
||||
space = getattr(self, "{}_space".format(layer))
|
||||
except AttributeError:
|
||||
debug.error("Cannot find layer pitch.", -1)
|
||||
return (nonpref_pitch, pitch, pitch - space, space)
|
||||
|
||||
def add_horizontal_trunk_route(self,
|
||||
pins,
|
||||
trunk_offset,
|
||||
layer_stack,
|
||||
pitch):
|
||||
"""
|
||||
Create a trunk route for all pins with
|
||||
the trunk located at the given y offset.
|
||||
"""
|
||||
max_x = max([pin.center().x for pin in pins])
|
||||
min_x = min([pin.center().x for pin in pins])
|
||||
|
||||
# if we are less than a pitch, just create a non-preferred layer jog
|
||||
if max_x - min_x <= pitch:
|
||||
half_layer_width = 0.5 * drc["minwidth_{0}".format(self.vertical_layer)]
|
||||
|
||||
# Add the horizontal trunk on the vertical layer!
|
||||
self.add_path(self.vertical_layer,
|
||||
[vector(min_x - half_layer_width, trunk_offset.y),
|
||||
vector(max_x + half_layer_width, trunk_offset.y)])
|
||||
|
||||
# Route each pin to the trunk
|
||||
for pin in pins:
|
||||
# No bend needed here
|
||||
mid = vector(pin.center().x, trunk_offset.y)
|
||||
self.add_path(self.vertical_layer, [pin.center(), mid])
|
||||
else:
|
||||
# Add the horizontal trunk
|
||||
self.add_path(self.horizontal_layer,
|
||||
[vector(min_x, trunk_offset.y),
|
||||
vector(max_x, trunk_offset.y)])
|
||||
|
||||
# Route each pin to the trunk
|
||||
for pin in pins:
|
||||
mid = vector(pin.center().x, trunk_offset.y)
|
||||
self.add_path(self.vertical_layer, [pin.center(), mid])
|
||||
self.add_via_center(layers=layer_stack,
|
||||
offset=mid,
|
||||
directions=self.directions)
|
||||
|
||||
def add_vertical_trunk_route(self,
|
||||
pins,
|
||||
trunk_offset,
|
||||
layer_stack,
|
||||
pitch):
|
||||
"""
|
||||
Create a trunk route for all pins with the
|
||||
trunk located at the given x offset.
|
||||
"""
|
||||
max_y = max([pin.center().y for pin in pins])
|
||||
min_y = min([pin.center().y for pin in pins])
|
||||
|
||||
# if we are less than a pitch, just create a non-preferred layer jog
|
||||
if max_y - min_y <= pitch:
|
||||
|
||||
half_layer_width = 0.5 * drc["minwidth_{0}".format(self.horizontal_layer)]
|
||||
|
||||
# Add the vertical trunk on the horizontal layer!
|
||||
self.add_path(self.horizontal_layer,
|
||||
[vector(trunk_offset.x, min_y - half_layer_width),
|
||||
vector(trunk_offset.x, max_y + half_layer_width)])
|
||||
|
||||
# Route each pin to the trunk
|
||||
for pin in pins:
|
||||
# No bend needed here
|
||||
mid = vector(trunk_offset.x, pin.center().y)
|
||||
self.add_path(self.horizontal_layer, [pin.center(), mid])
|
||||
else:
|
||||
# Add the vertical trunk
|
||||
self.add_path(self.vertical_layer,
|
||||
[vector(trunk_offset.x, min_y),
|
||||
vector(trunk_offset.x, max_y)])
|
||||
|
||||
# Route each pin to the trunk
|
||||
for pin in pins:
|
||||
mid = vector(trunk_offset.x, pin.center().y)
|
||||
self.add_path(self.horizontal_layer, [pin.center(), mid])
|
||||
self.add_via_center(layers=layer_stack,
|
||||
offset=mid,
|
||||
directions=self.directions)
|
||||
|
||||
def create_channel_route(self, netlist,
|
||||
offset,
|
||||
layer_stack,
|
||||
directions=None,
|
||||
vertical=False):
|
||||
"""
|
||||
The net list is a list of the nets with each net being a list of pins
|
||||
to be connected. The offset is the lower-left of where the
|
||||
routing channel will start. This does NOT try to minimize the
|
||||
number of tracks -- instead, it picks an order to avoid the
|
||||
vertical conflicts between pins. The track size must be the number of
|
||||
nets times the *nonpreferred* routing of the non-track layer pitch.
|
||||
|
||||
"""
|
||||
def remove_net_from_graph(pin, g):
|
||||
"""
|
||||
Remove the pin from the graph and all conflicts
|
||||
"""
|
||||
g.pop(pin, None)
|
||||
|
||||
# Remove the pin from all conflicts
|
||||
# FIXME: This is O(n^2), so maybe optimize it.
|
||||
for other_pin, conflicts in g.items():
|
||||
if pin in conflicts:
|
||||
conflicts.remove(pin)
|
||||
g[other_pin]=conflicts
|
||||
return g
|
||||
|
||||
def vcg_nets_overlap(net1, net2, vertical):
|
||||
"""
|
||||
Check all the pin pairs on two nets and return a pin
|
||||
overlap if any pin overlaps.
|
||||
"""
|
||||
|
||||
if vertical:
|
||||
pitch = self.horizontal_nonpref_pitch
|
||||
else:
|
||||
pitch = self.vertical_nonpref_pitch
|
||||
|
||||
for pin1 in net1:
|
||||
for pin2 in net2:
|
||||
if vcg_pin_overlap(pin1, pin2, vertical, pitch):
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
def vcg_pin_overlap(pin1, pin2, vertical, pitch):
|
||||
""" Check for vertical or horizontal overlap of the two pins """
|
||||
|
||||
# FIXME: If the pins are not in a row, this may break.
|
||||
# However, a top pin shouldn't overlap another top pin,
|
||||
# for example, so the extra comparison *shouldn't* matter.
|
||||
|
||||
# Pin 1 must be in the "BOTTOM" set
|
||||
x_overlap = pin1.by() < pin2.by() and abs(pin1.center().x - pin2.center().x) < pitch
|
||||
|
||||
# Pin 1 must be in the "LEFT" set
|
||||
y_overlap = pin1.lx() < pin2.lx() and abs(pin1.center().y - pin2.center().y) < pitch
|
||||
overlaps = (not vertical and x_overlap) or (vertical and y_overlap)
|
||||
return overlaps
|
||||
|
||||
self.directions = directions
|
||||
if not directions or directions == "pref":
|
||||
# Use the preferred layer directions
|
||||
if self.get_preferred_direction(layer_stack[0]) == "V":
|
||||
self.vertical_layer = layer_stack[0]
|
||||
self.horizontal_layer = layer_stack[2]
|
||||
else:
|
||||
self.vertical_layer = layer_stack[2]
|
||||
self.horizontal_layer = layer_stack[0]
|
||||
elif directions == "nonpref":
|
||||
# Use the preferred layer directions
|
||||
if self.get_preferred_direction(layer_stack[0]) == "V":
|
||||
self.vertical_layer = layer_stack[2]
|
||||
self.horizontal_layer = layer_stack[0]
|
||||
else:
|
||||
self.vertical_layer = layer_stack[0]
|
||||
self.horizontal_layer = layer_stack[2]
|
||||
else:
|
||||
# Use the layer directions specified to the router rather than
|
||||
# the preferred directions
|
||||
debug.check(directions[0] != directions[1], "Must have unique layer directions.")
|
||||
if directions[0] == "V":
|
||||
self.vertical_layer = layer_stack[0]
|
||||
self.horizontal_layer = layer_stack[2]
|
||||
else:
|
||||
self.horizontal_layer = layer_stack[0]
|
||||
self.vertical_layer = layer_stack[2]
|
||||
|
||||
layer_stuff = self.get_layer_pitch(self.vertical_layer)
|
||||
(self.vertical_nonpref_pitch, self.vertical_pitch, self.vertical_width, self.vertical_space) = layer_stuff
|
||||
|
||||
layer_stuff = self.get_layer_pitch(self.horizontal_layer)
|
||||
(self.horizontal_nonpref_pitch, self.horizontal_pitch, self.horizontal_width, self.horizontal_space) = layer_stuff
|
||||
|
||||
# FIXME: Must extend this to a horizontal conflict graph
|
||||
# too if we want to minimize the
|
||||
# number of tracks!
|
||||
# hcg = {}
|
||||
|
||||
# Initialize the vertical conflict graph (vcg)
|
||||
# and make a list of all pins
|
||||
vcg = collections.OrderedDict()
|
||||
|
||||
# Create names for the nets for the graphs
|
||||
nets = collections.OrderedDict()
|
||||
index = 0
|
||||
# print(netlist)
|
||||
for pin_list in netlist:
|
||||
net_name = "n{}".format(index)
|
||||
index += 1
|
||||
nets[net_name] = pin_list
|
||||
|
||||
# print("Nets:")
|
||||
# for net_name in nets:
|
||||
# print(net_name, [x.name for x in nets[net_name]])
|
||||
|
||||
# Find the vertical pin conflicts
|
||||
# FIXME: O(n^2) but who cares for now
|
||||
for net_name1 in nets:
|
||||
if net_name1 not in vcg.keys():
|
||||
vcg[net_name1] = []
|
||||
for net_name2 in nets:
|
||||
if net_name2 not in vcg.keys():
|
||||
vcg[net_name2] = []
|
||||
# Skip yourself
|
||||
if net_name1 == net_name2:
|
||||
continue
|
||||
if vcg_nets_overlap(nets[net_name1],
|
||||
nets[net_name2],
|
||||
vertical):
|
||||
vcg[net_name2].append(net_name1)
|
||||
|
||||
# list of routes to do
|
||||
while vcg:
|
||||
# from pprint import pformat
|
||||
# print("VCG:\n", pformat(vcg))
|
||||
# get a route from conflict graph with empty fanout set
|
||||
net_name = None
|
||||
for net_name, conflicts in vcg.items():
|
||||
if len(conflicts) == 0:
|
||||
vcg = remove_net_from_graph(net_name, vcg)
|
||||
break
|
||||
else:
|
||||
# FIXME: We don't support cyclic VCGs right now.
|
||||
debug.error("Cyclic VCG in channel router.", -1)
|
||||
|
||||
# These are the pins we'll have to connect
|
||||
pin_list = nets[net_name]
|
||||
# print("Routing:", net_name, [x.name for x in pin_list])
|
||||
|
||||
# Remove the net from other constriants in the VCG
|
||||
vcg = remove_net_from_graph(net_name, vcg)
|
||||
|
||||
# Add the trunk routes from the bottom up for
|
||||
# horizontal or the left to right for vertical
|
||||
if vertical:
|
||||
self.add_vertical_trunk_route(pin_list,
|
||||
offset,
|
||||
layer_stack,
|
||||
self.vertical_nonpref_pitch)
|
||||
# This accounts for the via-to-via spacings
|
||||
offset += vector(self.horizontal_nonpref_pitch, 0)
|
||||
else:
|
||||
self.add_horizontal_trunk_route(pin_list,
|
||||
offset,
|
||||
layer_stack,
|
||||
self.horizontal_nonpref_pitch)
|
||||
# This accounts for the via-to-via spacings
|
||||
offset += vector(0, self.vertical_nonpref_pitch)
|
||||
|
||||
def create_vertical_channel_route(self, netlist, offset, layer_stack, directions=None):
|
||||
"""
|
||||
Wrapper to create a vertical channel route
|
||||
"""
|
||||
self.create_channel_route(netlist, offset, layer_stack, directions, vertical=True)
|
||||
import channel_route
|
||||
cr = channel_route.channel_route(netlist, offset, layer_stack, directions, vertical=True)
|
||||
self.add_inst("vc", cr)
|
||||
self.connect_inst([])
|
||||
|
||||
def create_horizontal_channel_route(self, netlist, offset, layer_stack, directions=None):
|
||||
"""
|
||||
Wrapper to create a horizontal channel route
|
||||
"""
|
||||
self.create_channel_route(netlist, offset, layer_stack, directions, vertical=False)
|
||||
|
||||
import channel_route
|
||||
cr = channel_route.channel_route(netlist, offset, layer_stack, directions, vertical=False)
|
||||
self.add_inst("hc", cr)
|
||||
self.connect_inst([])
|
||||
|
||||
def add_boundary(self, ll=vector(0, 0), ur=None):
|
||||
""" Add boundary for debugging dimensions """
|
||||
if OPTS.netlist_only:
|
||||
|
|
@ -1301,27 +1054,49 @@ class layout():
|
|||
height=ur.y - ll.y,
|
||||
width=ur.x - ll.x)
|
||||
|
||||
def add_enclosure(self, insts, layer="nwell"):
|
||||
""" Add a layer that surrounds the given instances. Useful
|
||||
def add_enclosure(self, insts, layer="nwell", extend=0, leftx=None, rightx=None, topy=None, boty=None):
|
||||
"""
|
||||
Add a layer that surrounds the given instances. Useful
|
||||
for creating wells, for example. Doesn't check for minimum widths or
|
||||
spacings."""
|
||||
spacings. Extra arg can force a dimension to one side left/right top/bot.
|
||||
"""
|
||||
|
||||
xmin = insts[0].lx()
|
||||
ymin = insts[0].by()
|
||||
xmax = insts[0].rx()
|
||||
ymax = insts[0].uy()
|
||||
for inst in insts:
|
||||
xmin = min(xmin, inst.lx())
|
||||
ymin = min(ymin, inst.by())
|
||||
xmax = max(xmax, inst.rx())
|
||||
ymax = max(ymax, inst.uy())
|
||||
if leftx != None:
|
||||
xmin = leftx
|
||||
else:
|
||||
xmin = insts[0].lx()
|
||||
for inst in insts:
|
||||
xmin = min(xmin, inst.lx())
|
||||
xmin = xmin - extend
|
||||
if boty != None:
|
||||
ymin = boty
|
||||
else:
|
||||
ymin = insts[0].by()
|
||||
for inst in insts:
|
||||
ymin = min(ymin, inst.by())
|
||||
ymin = ymin - extend
|
||||
if rightx != None:
|
||||
xmax = rightx
|
||||
else:
|
||||
xmax = insts[0].rx()
|
||||
for inst in insts:
|
||||
xmax = max(xmax, inst.rx())
|
||||
xmax = xmax + extend
|
||||
if topy != None:
|
||||
ymax = topy
|
||||
else:
|
||||
ymax = insts[0].uy()
|
||||
for inst in insts:
|
||||
ymax = max(ymax, inst.uy())
|
||||
ymax = ymax + extend
|
||||
|
||||
self.add_rect(layer=layer,
|
||||
offset=vector(xmin, ymin),
|
||||
width=xmax - xmin,
|
||||
height=ymax - ymin)
|
||||
|
||||
def copy_power_pins(self, inst, name):
|
||||
rect = self.add_rect(layer=layer,
|
||||
offset=vector(xmin, ymin),
|
||||
width=xmax - xmin,
|
||||
height=ymax - ymin)
|
||||
return rect
|
||||
|
||||
def copy_power_pins(self, inst, name, add_vias=True):
|
||||
"""
|
||||
This will copy a power pin if it is on the lowest power_grid layer.
|
||||
If it is on M1, it will add a power via too.
|
||||
|
|
@ -1335,9 +1110,9 @@ class layout():
|
|||
pin.width(),
|
||||
pin.height())
|
||||
|
||||
else:
|
||||
elif add_vias:
|
||||
self.add_power_pin(name, pin.center(), start_layer=pin.layer)
|
||||
|
||||
|
||||
def add_power_pin(self, name, loc, size=[1, 1], directions=None, start_layer="m1"):
|
||||
"""
|
||||
Add a single power pin from the lowest power_grid layer down to M1 (or li) at
|
||||
|
|
@ -1355,6 +1130,7 @@ class layout():
|
|||
size=size,
|
||||
offset=loc,
|
||||
directions=directions)
|
||||
|
||||
# Hack for min area
|
||||
if OPTS.tech_name == "sky130":
|
||||
width = round_to_grid(sqrt(drc["minarea_m3"]))
|
||||
|
|
@ -1385,7 +1161,7 @@ class layout():
|
|||
layer = "m3"
|
||||
elif side == "right":
|
||||
layer = "m3"
|
||||
peri_pin_loc = vector(right, pin_loc.x)
|
||||
peri_pin_loc = vector(right, pin_loc.y)
|
||||
elif side == "top":
|
||||
layer = "m4"
|
||||
peri_pin_loc = vector(pin_loc.x, top)
|
||||
|
|
@ -1400,13 +1176,9 @@ class layout():
|
|||
self.add_path(layer,
|
||||
[pin_loc, peri_pin_loc])
|
||||
|
||||
self.add_via_stack_center(from_layer=layer,
|
||||
to_layer="m4",
|
||||
offset=peri_pin_loc)
|
||||
|
||||
self.add_layout_pin_rect_center(text=name,
|
||||
layer="m4",
|
||||
offset=peri_pin_loc)
|
||||
return self.add_layout_pin_rect_center(text=name,
|
||||
layer=layer,
|
||||
offset=peri_pin_loc)
|
||||
|
||||
def add_power_ring(self, bbox):
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@
|
|||
import debug
|
||||
from tech import GDS, drc
|
||||
from vector import vector
|
||||
from tech import layer
|
||||
from tech import layer, layer_indices
|
||||
import math
|
||||
|
||||
|
||||
|
|
@ -31,12 +31,15 @@ class pin_layout:
|
|||
debug.check(self.width() > 0, "Zero width pin.")
|
||||
debug.check(self.height() > 0, "Zero height pin.")
|
||||
|
||||
# These are the valid pin layers
|
||||
valid_layers = { x: layer[x] for x in layer_indices.keys()}
|
||||
|
||||
# if it's a string, use the name
|
||||
if type(layer_name_pp) == str:
|
||||
self._layer = layer_name_pp
|
||||
# else it is required to be a lpp
|
||||
else:
|
||||
for (layer_name, lpp) in layer.items():
|
||||
for (layer_name, lpp) in valid_layers.items():
|
||||
if not lpp:
|
||||
continue
|
||||
if self.same_lpp(layer_name_pp, lpp):
|
||||
|
|
|
|||
|
|
@ -657,8 +657,12 @@ class lib:
|
|||
))
|
||||
|
||||
# information of checks
|
||||
(drc_errors, lvs_errors) = self.sram.DRC_LVS(final_verification=True)
|
||||
datasheet.write("{0},{1},".format(drc_errors, lvs_errors))
|
||||
# run it only the first time
|
||||
try:
|
||||
datasheet.write("{0},{1},".format(self.drc_errors, self.lvs_errors))
|
||||
except AttributeError:
|
||||
(self.drc_errors, self.lvs_errors) = self.sram.DRC_LVS(final_verification=True)
|
||||
datasheet.write("{0},{1},".format(self.drc_errors, self.lvs_errors))
|
||||
|
||||
# write area
|
||||
datasheet.write(str(self.sram.width * self.sram.height) + ',')
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@ def error(str, return_value=0):
|
|||
log("ERROR: file {0}: line {1}: {2}\n".format(
|
||||
os.path.basename(filename), line_number, str))
|
||||
|
||||
if globals.OPTS.debug_level > 0:
|
||||
if globals.OPTS.debug_level > 0 and return_value != 0:
|
||||
import pdb
|
||||
pdb.set_trace()
|
||||
assert return_value == 0
|
||||
|
|
|
|||
|
|
@ -132,14 +132,17 @@ class bank(design.design):
|
|||
# Connect the rbl to the port data pin
|
||||
bl_pin = self.port_data_inst[port].get_pin("rbl_bl")
|
||||
if port % 2:
|
||||
pin_offset = bl_pin.uc()
|
||||
pin_pos = bl_pin.uc()
|
||||
pin_offset = pin_pos + vector(0, self.m3_pitch)
|
||||
left_right_offset = vector(self.max_x_offset, pin_offset.y)
|
||||
else:
|
||||
pin_offset = bl_pin.bc()
|
||||
pin_pos = bl_pin.bc()
|
||||
pin_offset = pin_pos - vector(0, self.m3_pitch)
|
||||
left_right_offset = vector(self.min_x_offset, pin_offset.y)
|
||||
self.add_via_stack_center(from_layer=bl_pin.layer,
|
||||
to_layer="m3",
|
||||
offset=pin_offset)
|
||||
self.add_path(bl_pin.layer, [pin_offset, pin_pos])
|
||||
self.add_layout_pin_segment_center(text="rbl_bl{0}".format(port),
|
||||
layer="m3",
|
||||
start=left_right_offset,
|
||||
|
|
@ -217,11 +220,12 @@ class bank(design.design):
|
|||
# Place the col decoder left aligned with wordline driver
|
||||
# This is also placed so that it's supply rails do not align with the SRAM-level
|
||||
# control logic to allow control signals to easily pass over in M3
|
||||
# by placing 1/2 a cell pitch down
|
||||
# by placing 1 1/4 a cell pitch down because both power connections and inputs/outputs
|
||||
# may be routed in M3 or M4
|
||||
x_offset = self.central_bus_width[port] + self.port_address.wordline_driver.width
|
||||
if self.col_addr_size > 0:
|
||||
x_offset += self.column_decoder.width + self.col_addr_bus_width
|
||||
y_offset = 0.5 * self.dff.height + self.column_decoder.height
|
||||
y_offset = 1.25 * self.dff.height + self.column_decoder.height
|
||||
else:
|
||||
y_offset = 0
|
||||
self.column_decoder_offsets[port] = vector(-x_offset, -y_offset)
|
||||
|
|
@ -258,10 +262,14 @@ class bank(design.design):
|
|||
# UPPER RIGHT QUADRANT
|
||||
# Place the col decoder right aligned with wordline driver
|
||||
# Above the bitcell array with a well spacing
|
||||
# This is also placed so that it's supply rails do not align with the SRAM-level
|
||||
# control logic to allow control signals to easily pass over in M3
|
||||
# by placing 1 1/4 a cell pitch down because both power connections and inputs/outputs
|
||||
# may be routed in M3 or M4
|
||||
x_offset = self.bitcell_array_right + self.central_bus_width[port] + self.port_address.wordline_driver.width
|
||||
if self.col_addr_size > 0:
|
||||
x_offset += self.column_decoder.width + self.col_addr_bus_width
|
||||
y_offset = self.bitcell_array_top + 0.5 * self.dff.height + self.column_decoder.height
|
||||
y_offset = self.bitcell_array_top + 1.25 * self.dff.height + self.column_decoder.height
|
||||
else:
|
||||
y_offset = self.bitcell_array_top
|
||||
self.column_decoder_offsets[port] = vector(x_offset, y_offset)
|
||||
|
|
@ -308,7 +316,7 @@ class bank(design.design):
|
|||
self.input_control_signals = []
|
||||
port_num = 0
|
||||
for port in range(OPTS.num_rw_ports):
|
||||
self.input_control_signals.append(["w_en{}".format(port_num), "s_en{}".format(port_num), "p_en_bar{}".format(port_num), "wl_en{}".format(port_num)])
|
||||
self.input_control_signals.append(["s_en{}".format(port_num), "w_en{}".format(port_num), "p_en_bar{}".format(port_num), "wl_en{}".format(port_num)])
|
||||
port_num += 1
|
||||
for port in range(OPTS.num_w_ports):
|
||||
self.input_control_signals.append(["w_en{}".format(port_num), "p_en_bar{}".format(port_num), "wl_en{}".format(port_num)])
|
||||
|
|
@ -321,7 +329,7 @@ class bank(design.design):
|
|||
self.num_control_lines = [len(x) for x in self.input_control_signals]
|
||||
|
||||
# The width of this bus is needed to place other modules (e.g. decoder) for each port
|
||||
self.central_bus_width = [self.m2_pitch * x + self.m2_width for x in self.num_control_lines]
|
||||
self.central_bus_width = [self.m3_pitch * x + self.m3_width for x in self.num_control_lines]
|
||||
|
||||
# These will be outputs of the gaters if this is multibank, if not, normal signals.
|
||||
self.control_signals = []
|
||||
|
|
@ -330,6 +338,7 @@ class bank(design.design):
|
|||
self.control_signals.append(["gated_" + str for str in self.input_control_signals[port]])
|
||||
else:
|
||||
self.control_signals.append(self.input_control_signals[port])
|
||||
|
||||
|
||||
# The central bus is the column address (one hot) and row address (binary)
|
||||
if self.col_addr_size>0:
|
||||
|
|
@ -497,18 +506,20 @@ class bank(design.design):
|
|||
Create a 2:4 or 3:8 column address decoder.
|
||||
"""
|
||||
|
||||
# Height is a multiple of DFF so that it can be staggered
|
||||
# and rows do not align with the control logic module
|
||||
self.dff = factory.create(module_type="dff")
|
||||
self.dff =factory.create(module_type="dff")
|
||||
|
||||
if self.col_addr_size == 0:
|
||||
return
|
||||
elif self.col_addr_size == 1:
|
||||
self.column_decoder = factory.create(module_type="pinvbuf", height=self.dff.height)
|
||||
self.column_decoder = factory.create(module_type="pinvbuf",
|
||||
height=self.dff.height)
|
||||
elif self.col_addr_size == 2:
|
||||
self.column_decoder = factory.create(module_type="hierarchical_predecode2x4", height=self.dff.height)
|
||||
self.column_decoder = factory.create(module_type="hierarchical_predecode2x4",
|
||||
height=self.dff.height)
|
||||
|
||||
elif self.col_addr_size == 3:
|
||||
self.column_decoder = factory.create(module_type="hierarchical_predecode3x8", height=self.dff.height)
|
||||
self.column_decoder = factory.create(module_type="hierarchical_predecode3x8",
|
||||
height=self.dff.height)
|
||||
else:
|
||||
# No error checking before?
|
||||
debug.error("Invalid column decoder?", -1)
|
||||
|
|
@ -576,9 +587,23 @@ class bank(design.design):
|
|||
|
||||
def route_supplies(self):
|
||||
""" Propagate all vdd/gnd pins up to this level for all modules """
|
||||
# Copy only the power pins already on the power layer
|
||||
# (this won't add vias to internal bitcell pins, for example)
|
||||
for inst in self.insts:
|
||||
self.copy_power_pins(inst, "vdd")
|
||||
self.copy_power_pins(inst, "gnd")
|
||||
self.copy_power_pins(inst, "vdd", add_vias=False)
|
||||
self.copy_power_pins(inst, "gnd", add_vias=False)
|
||||
|
||||
# If we use the pinvbuf as the decoder, we need to add power pins.
|
||||
# Other decoders already have them.
|
||||
if self.col_addr_size == 1:
|
||||
for port in self.all_ports:
|
||||
inst = self.column_decoder_inst[port]
|
||||
for pin_name in ["vdd", "gnd"]:
|
||||
pin_list = inst.get_pins(pin_name)
|
||||
for pin in pin_list:
|
||||
self.add_power_pin(pin_name,
|
||||
pin.center(),
|
||||
start_layer=pin.layer)
|
||||
|
||||
def route_bank_select(self, port):
|
||||
""" Route the bank select logic. """
|
||||
|
|
@ -648,7 +673,8 @@ class bank(design.design):
|
|||
names=self.control_signals[0],
|
||||
length=control_bus_length,
|
||||
vertical=True,
|
||||
make_pins=(self.num_banks==1))
|
||||
make_pins=(self.num_banks==1),
|
||||
pitch=self.m3_pitch)
|
||||
|
||||
# Port 1
|
||||
if len(self.all_ports)==2:
|
||||
|
|
@ -662,7 +688,8 @@ class bank(design.design):
|
|||
names=list(reversed(self.control_signals[1])),
|
||||
length=control_bus_length,
|
||||
vertical=True,
|
||||
make_pins=(self.num_banks==1))
|
||||
make_pins=(self.num_banks==1),
|
||||
pitch=self.m3_pitch)
|
||||
|
||||
def route_port_data_to_bitcell_array(self, port):
|
||||
""" Routing of BL and BR between port data and bitcell array """
|
||||
|
|
@ -850,6 +877,13 @@ class bank(design.design):
|
|||
if not self.col_addr_size>0:
|
||||
return
|
||||
|
||||
if OPTS.tech_name == "sky130":
|
||||
stack = self.m2_stack
|
||||
pitch = self.m3_pitch
|
||||
else:
|
||||
stack = self.m1_stack
|
||||
pitch = self.m2_pitch
|
||||
|
||||
if self.col_addr_size == 1:
|
||||
|
||||
# Connect to sel[0] and sel[1]
|
||||
|
|
@ -869,9 +903,9 @@ class bank(design.design):
|
|||
self.copy_layout_pin(self.column_decoder_inst[port], decoder_name, addr_name)
|
||||
|
||||
if port % 2:
|
||||
offset = self.column_decoder_inst[port].ll() - vector(self.num_col_addr_lines * self.m2_nonpref_pitch, 0)
|
||||
offset = self.column_decoder_inst[port].ll() - vector((self.num_col_addr_lines + 1) * pitch, 0)
|
||||
else:
|
||||
offset = self.column_decoder_inst[port].lr() + vector(self.m2_nonpref_pitch, 0)
|
||||
offset = self.column_decoder_inst[port].lr() + vector(pitch, 0)
|
||||
|
||||
decode_pins = [self.column_decoder_inst[port].get_pin(x) for x in decode_names]
|
||||
|
||||
|
|
@ -879,16 +913,9 @@ class bank(design.design):
|
|||
column_mux_pins = [self.port_data_inst[port].get_pin(x) for x in sel_names]
|
||||
|
||||
route_map = list(zip(decode_pins, column_mux_pins))
|
||||
if "li" in layer:
|
||||
stack = self.li_stack
|
||||
directions = "nonpref"
|
||||
else:
|
||||
stack = self.m1_stack
|
||||
directions = "pref"
|
||||
self.create_vertical_channel_route(route_map,
|
||||
offset,
|
||||
stack,
|
||||
directions=directions)
|
||||
stack)
|
||||
|
||||
def add_lvs_correspondence_points(self):
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import debug
|
|||
import design
|
||||
from tech import cell_properties
|
||||
|
||||
|
||||
class bitcell_base_array(design.design):
|
||||
"""
|
||||
Abstract base class for bitcell-arrays -- bitcell, dummy
|
||||
|
|
@ -68,10 +69,10 @@ class bitcell_base_array(design.design):
|
|||
|
||||
pin_names = self.cell.get_all_bitline_names()
|
||||
for pin in pin_names:
|
||||
bitcell_pins.append(pin+"_{0}".format(col))
|
||||
bitcell_pins.append(pin + "_{0}".format(col))
|
||||
pin_names = self.cell.get_all_wl_names()
|
||||
for pin in pin_names:
|
||||
bitcell_pins.append(pin+"_{0}".format(row))
|
||||
bitcell_pins.append(pin + "_{0}".format(row))
|
||||
bitcell_pins.append("vdd")
|
||||
bitcell_pins.append("gnd")
|
||||
|
||||
|
|
@ -85,46 +86,28 @@ class bitcell_base_array(design.design):
|
|||
|
||||
for col in range(self.column_size):
|
||||
for cell_column in column_list:
|
||||
bl_pin = self.cell_inst[0,col].get_pin(cell_column)
|
||||
self.add_layout_pin(text=cell_column+"_{0}".format(col),
|
||||
bl_pin = self.cell_inst[0, col].get_pin(cell_column)
|
||||
self.add_layout_pin(text=cell_column + "_{0}".format(col),
|
||||
layer=bl_pin.layer,
|
||||
offset=bl_pin.ll().scale(1,0),
|
||||
offset=bl_pin.ll().scale(1, 0),
|
||||
width=bl_pin.width(),
|
||||
height=self.height)
|
||||
|
||||
for row in range(self.row_size):
|
||||
for cell_row in row_list:
|
||||
wl_pin = self.cell_inst[row,0].get_pin(cell_row)
|
||||
self.add_layout_pin(text=cell_row+"_{0}".format(row),
|
||||
wl_pin = self.cell_inst[row, 0].get_pin(cell_row)
|
||||
self.add_layout_pin(text=cell_row + "_{0}".format(row),
|
||||
layer=wl_pin.layer,
|
||||
offset=wl_pin.ll().scale(0,1),
|
||||
offset=wl_pin.ll().scale(0, 1),
|
||||
width=self.width,
|
||||
height=wl_pin.height())
|
||||
|
||||
# For non-square via stacks, vertical/horizontal direction refers to the stack orientation in 2d space
|
||||
# Default uses prefered directions for each layer; this cell property is only currently used by sky130 tech (03/20)
|
||||
try:
|
||||
bitcell_power_pin_directions = cell_properties.bitcell_power_pin_directions
|
||||
except AttributeError:
|
||||
bitcell_power_pin_directions = None
|
||||
|
||||
# For specific technologies, there is no vdd via within the bitcell. Instead vdd is connect via end caps.
|
||||
try:
|
||||
bitcell_no_vdd_pin = cell_properties.bitcell.no_vdd_via
|
||||
except AttributeError:
|
||||
bitcell_no_vdd_pin = False
|
||||
|
||||
# Add vdd/gnd via stacks
|
||||
# Copy a vdd/gnd layout pin from every cell
|
||||
for row in range(self.row_size):
|
||||
for col in range(self.column_size):
|
||||
inst = self.cell_inst[row,col]
|
||||
inst = self.cell_inst[row, col]
|
||||
for pin_name in ["vdd", "gnd"]:
|
||||
for pin in inst.get_pins(pin_name):
|
||||
if not (pin_name == "vdd" and bitcell_no_vdd_pin):
|
||||
self.add_power_pin(name=pin_name,
|
||||
loc=pin.center(),
|
||||
directions=bitcell_power_pin_directions,
|
||||
start_layer=pin.layer)
|
||||
self.copy_layout_pin(inst, pin_name)
|
||||
|
||||
def _adjust_x_offset(self, xoffset, col, col_offset):
|
||||
tempx = xoffset
|
||||
|
|
@ -144,11 +127,10 @@ class bitcell_base_array(design.design):
|
|||
dir_x = True
|
||||
return (tempy, dir_x)
|
||||
|
||||
|
||||
def place_array(self, name_template, row_offset=0):
|
||||
# We increase it by a well enclosure so the precharges don't overlap our wells
|
||||
self.height = self.row_size*self.cell.height
|
||||
self.width = self.column_size*self.cell.width
|
||||
self.height = self.row_size * self.cell.height
|
||||
self.width = self.column_size * self.cell.width
|
||||
|
||||
xoffset = 0.0
|
||||
for col in range(self.column_size):
|
||||
|
|
@ -156,7 +138,6 @@ class bitcell_base_array(design.design):
|
|||
tempx, dir_y = self._adjust_x_offset(xoffset, col, self.column_offset)
|
||||
|
||||
for row in range(self.row_size):
|
||||
name = name_template.format(row, col)
|
||||
tempy, dir_x = self._adjust_y_offset(yoffset, row, row_offset)
|
||||
|
||||
if dir_x and dir_y:
|
||||
|
|
@ -168,7 +149,7 @@ class bitcell_base_array(design.design):
|
|||
else:
|
||||
dir_key = ""
|
||||
|
||||
self.cell_inst[row,col].place(offset=[tempx, tempy],
|
||||
mirror=dir_key)
|
||||
self.cell_inst[row, col].place(offset=[tempx, tempy],
|
||||
mirror=dir_key)
|
||||
yoffset += self.cell.height
|
||||
xoffset += self.cell.width
|
||||
|
|
|
|||
|
|
@ -523,12 +523,12 @@ class control_logic(design.design):
|
|||
def route_clk_buf(self):
|
||||
clk_pin = self.clk_buf_inst.get_pin("A")
|
||||
clk_pos = clk_pin.center()
|
||||
self.add_layout_pin_segment_center(text="clk",
|
||||
layer="m2",
|
||||
start=clk_pos,
|
||||
end=clk_pos.scale(1, 0))
|
||||
self.add_via_center(layers=self.m1_stack,
|
||||
offset=clk_pos)
|
||||
self.add_layout_pin_rect_center(text="clk",
|
||||
layer="m2",
|
||||
offset=clk_pos)
|
||||
self.add_via_stack_center(from_layer=clk_pin.layer,
|
||||
to_layer="m2",
|
||||
offset=clk_pos)
|
||||
|
||||
self.route_output_to_bus_jogged(self.clk_buf_inst,
|
||||
"clk_buf")
|
||||
|
|
@ -555,9 +555,15 @@ class control_logic(design.design):
|
|||
clkbuf_map = zip(["A"], ["clk_buf"])
|
||||
self.connect_vertical_bus(clkbuf_map, self.clk_bar_inst, self.input_bus)
|
||||
|
||||
out_pos = self.clk_bar_inst.get_pin("Z").center()
|
||||
in_pos = self.gated_clk_bar_inst.get_pin("A").center()
|
||||
self.add_zjog("m1", out_pos, in_pos)
|
||||
out_pin = self.clk_bar_inst.get_pin("Z")
|
||||
out_pos = out_pin.center()
|
||||
in_pin = self.gated_clk_bar_inst.get_pin("A")
|
||||
in_pos = in_pin.center()
|
||||
self.add_zjog(out_pin.layer, out_pos, in_pos)
|
||||
self.add_via_stack_center(from_layer=out_pin.layer,
|
||||
to_layer=in_pin.layer,
|
||||
offset=in_pos)
|
||||
|
||||
|
||||
# This is the second gate over, so it needs to be on M3
|
||||
clkbuf_map = zip(["B"], ["cs"])
|
||||
|
|
@ -796,32 +802,41 @@ class control_logic(design.design):
|
|||
""" Create an output pin on the right side from the pin of a given instance. """
|
||||
|
||||
out_pin = inst.get_pin(pin_name)
|
||||
right_pos = out_pin.center() + vector(self.width - out_pin.cx(), 0)
|
||||
out_pos = out_pin.center()
|
||||
right_pos = out_pos + vector(self.width - out_pin.cx(), 0)
|
||||
|
||||
self.add_via_stack_center(from_layer=out_pin.layer,
|
||||
to_layer="m2",
|
||||
offset=out_pos)
|
||||
self.add_layout_pin_segment_center(text=out_name,
|
||||
layer="m1",
|
||||
start=out_pin.center(),
|
||||
layer="m2",
|
||||
start=out_pos,
|
||||
end=right_pos)
|
||||
|
||||
def route_supply(self):
|
||||
""" Add vdd and gnd to the instance cells """
|
||||
|
||||
if OPTS.tech_name == "sky130":
|
||||
supply_layer = "li"
|
||||
else:
|
||||
supply_layer = "m1"
|
||||
max_row_x_loc = max([inst.rx() for inst in self.row_end_inst])
|
||||
for inst in self.row_end_inst:
|
||||
pins = inst.get_pins("vdd")
|
||||
for pin in pins:
|
||||
if pin.layer == "m1":
|
||||
if pin.layer == supply_layer:
|
||||
row_loc = pin.rc()
|
||||
pin_loc = vector(max_row_x_loc, pin.rc().y)
|
||||
self.add_power_pin("vdd", pin_loc)
|
||||
self.add_path("m1", [row_loc, pin_loc])
|
||||
self.add_power_pin("vdd", pin_loc, start_layer=pin.layer)
|
||||
self.add_path(supply_layer, [row_loc, pin_loc])
|
||||
|
||||
pins = inst.get_pins("gnd")
|
||||
for pin in pins:
|
||||
if pin.layer == "m1":
|
||||
if pin.layer == supply_layer:
|
||||
row_loc = pin.rc()
|
||||
pin_loc = vector(max_row_x_loc, pin.rc().y)
|
||||
self.add_power_pin("gnd", pin_loc)
|
||||
self.add_path("m1", [row_loc, pin_loc])
|
||||
self.add_power_pin("gnd", pin_loc, start_layer=pin.layer)
|
||||
self.add_path(supply_layer, [row_loc, pin_loc])
|
||||
|
||||
self.copy_layout_pin(self.delay_inst, "gnd")
|
||||
self.copy_layout_pin(self.delay_inst, "vdd")
|
||||
|
|
@ -1004,12 +1019,13 @@ class control_logic(design.design):
|
|||
|
||||
def route_output_to_bus_jogged(self, inst, name):
|
||||
# Connect this at the bottom of the buffer
|
||||
out_pos = inst.get_pin("Z").center()
|
||||
out_pin = inst.get_pin("Z")
|
||||
out_pos = out_pin.center()
|
||||
mid1 = vector(out_pos.x, out_pos.y - 0.4 * inst.mod.height)
|
||||
mid2 = vector(self.input_bus[name].cx(), mid1.y)
|
||||
bus_pos = self.input_bus[name].center()
|
||||
self.add_wire(self.m2_stack[::-1], [out_pos, mid1, mid2, bus_pos])
|
||||
# The pin is on M1, so we need another via as well
|
||||
self.add_via_center(layers=self.m1_stack,
|
||||
offset=out_pos)
|
||||
self.add_via_stack_center(from_layer=out_pin.layer,
|
||||
to_layer="m2",
|
||||
offset=out_pos)
|
||||
|
||||
|
|
|
|||
|
|
@ -140,21 +140,20 @@ class delay_chain(design.design):
|
|||
for load in self.load_inst_map[inv]:
|
||||
# Drop a via on each A pin
|
||||
a_pin = load.get_pin("A")
|
||||
self.add_via_center(layers=self.m1_stack,
|
||||
offset=a_pin.center())
|
||||
self.add_via_center(layers=self.m2_stack,
|
||||
offset=a_pin.center())
|
||||
self.add_via_stack_center(from_layer=a_pin.layer,
|
||||
to_layer="m3",
|
||||
offset=a_pin.center())
|
||||
|
||||
# Route an M3 horizontal wire to the furthest
|
||||
z_pin = inv.get_pin("Z")
|
||||
a_pin = inv.get_pin("A")
|
||||
a_max = self.load_inst_map[inv][-1].get_pin("A")
|
||||
self.add_via_center(layers=self.m1_stack,
|
||||
offset=a_pin.center())
|
||||
self.add_via_center(layers=self.m1_stack,
|
||||
offset=z_pin.center())
|
||||
self.add_via_center(layers=self.m2_stack,
|
||||
offset=z_pin.center())
|
||||
self.add_via_stack_center(from_layer=a_pin.layer,
|
||||
to_layer="m2",
|
||||
offset=a_pin.center())
|
||||
self.add_via_stack_center(from_layer=z_pin.layer,
|
||||
to_layer="m3",
|
||||
offset=z_pin.center())
|
||||
self.add_path("m3", [z_pin.center(), a_max.center()])
|
||||
|
||||
# Route Z to the A of the next stage
|
||||
|
|
@ -178,17 +177,22 @@ class delay_chain(design.design):
|
|||
load_list = self.load_inst_map[inst]
|
||||
for pin_name in ["vdd", "gnd"]:
|
||||
pin = load_list[0].get_pin(pin_name)
|
||||
self.add_power_pin(pin_name, pin.rc() - vector(self.m1_pitch, 0))
|
||||
self.add_power_pin(pin_name,
|
||||
pin.rc() - vector(self.m1_pitch, 0),
|
||||
start_layer=pin.layer)
|
||||
|
||||
pin = load_list[-1].get_pin(pin_name)
|
||||
self.add_power_pin(pin_name, pin.rc() - vector(0.5 * self.m1_pitch, 0))
|
||||
pin = load_list[-2].get_pin(pin_name)
|
||||
self.add_power_pin(pin_name,
|
||||
pin.rc() - vector(self.m1_pitch, 0),
|
||||
start_layer=pin.layer)
|
||||
|
||||
def add_layout_pins(self):
|
||||
|
||||
# input is A pin of first inverter
|
||||
a_pin = self.driver_inst_list[0].get_pin("A")
|
||||
self.add_via_center(layers=self.m1_stack,
|
||||
offset=a_pin.center())
|
||||
self.add_via_stack_center(from_layer=a_pin.layer,
|
||||
to_layer="m2",
|
||||
offset=a_pin.center())
|
||||
self.add_layout_pin(text="in",
|
||||
layer="m2",
|
||||
offset=a_pin.ll().scale(1, 0),
|
||||
|
|
@ -197,8 +201,9 @@ class delay_chain(design.design):
|
|||
# output is A pin of last load inverter
|
||||
last_driver_inst = self.driver_inst_list[-1]
|
||||
a_pin = self.load_inst_map[last_driver_inst][-1].get_pin("A")
|
||||
self.add_via_center(layers=self.m1_stack,
|
||||
offset=a_pin.center())
|
||||
self.add_via_stack_center(from_layer=a_pin.layer,
|
||||
to_layer="m2",
|
||||
offset=a_pin.center())
|
||||
mid_point = vector(a_pin.cx() + 3 * self.m2_width, a_pin.cy())
|
||||
self.add_path("m2", [a_pin.center(), mid_point, mid_point.scale(1, 0)])
|
||||
self.add_layout_pin_segment_center(text="out",
|
||||
|
|
|
|||
|
|
@ -7,12 +7,11 @@
|
|||
#
|
||||
import debug
|
||||
import design
|
||||
from tech import drc
|
||||
from math import log
|
||||
from vector import vector
|
||||
from sram_factory import factory
|
||||
from globals import OPTS
|
||||
|
||||
|
||||
class dff_array(design.design):
|
||||
"""
|
||||
This is a simple row (or multiple rows) of flops.
|
||||
|
|
@ -52,42 +51,41 @@ class dff_array(design.design):
|
|||
self.add_mod(self.dff)
|
||||
|
||||
def add_pins(self):
|
||||
for row in range(self.rows):
|
||||
for row in range(self.rows):
|
||||
for col in range(self.columns):
|
||||
self.add_pin(self.get_din_name(row,col), "INPUT")
|
||||
for row in range(self.rows):
|
||||
self.add_pin(self.get_din_name(row, col), "INPUT")
|
||||
for row in range(self.rows):
|
||||
for col in range(self.columns):
|
||||
self.add_pin(self.get_dout_name(row,col), "OUTPUT")
|
||||
self.add_pin(self.get_dout_name(row, col), "OUTPUT")
|
||||
self.add_pin("clk", "INPUT")
|
||||
self.add_pin("vdd", "POWER")
|
||||
self.add_pin("gnd", "GROUND")
|
||||
|
||||
def create_dff_array(self):
|
||||
self.dff_insts={}
|
||||
for row in range(self.rows):
|
||||
for row in range(self.rows):
|
||||
for col in range(self.columns):
|
||||
name = "dff_r{0}_c{1}".format(row,col)
|
||||
self.dff_insts[row,col]=self.add_inst(name=name,
|
||||
mod=self.dff)
|
||||
instance_ports = [self.get_din_name(row,col),
|
||||
self.get_dout_name(row,col)]
|
||||
name = "dff_r{0}_c{1}".format(row, col)
|
||||
self.dff_insts[row, col]=self.add_inst(name=name,
|
||||
mod=self.dff)
|
||||
instance_ports = [self.get_din_name(row, col),
|
||||
self.get_dout_name(row, col)]
|
||||
for port in self.dff.pin_names:
|
||||
if port != 'D' and port != 'Q':
|
||||
instance_ports.append(port)
|
||||
self.connect_inst(instance_ports)
|
||||
|
||||
def place_dff_array(self):
|
||||
for row in range(self.rows):
|
||||
for row in range(self.rows):
|
||||
for col in range(self.columns):
|
||||
name = "dff_r{0}_c{1}".format(row,col)
|
||||
if (row % 2 == 0):
|
||||
base = vector(col*self.dff.width,row*self.dff.height)
|
||||
base = vector(col * self.dff.width, row * self.dff.height)
|
||||
mirror = "R0"
|
||||
else:
|
||||
base = vector(col*self.dff.width,(row+1)*self.dff.height)
|
||||
base = vector(col * self.dff.width, (row + 1) * self.dff.height)
|
||||
mirror = "MX"
|
||||
self.dff_insts[row,col].place(offset=base,
|
||||
mirror=mirror)
|
||||
self.dff_insts[row, col].place(offset=base,
|
||||
mirror=mirror)
|
||||
|
||||
def get_din_name(self, row, col):
|
||||
if self.columns == 1:
|
||||
|
|
@ -95,7 +93,7 @@ class dff_array(design.design):
|
|||
elif self.rows == 1:
|
||||
din_name = "din_{0}".format(col)
|
||||
else:
|
||||
din_name = "din_{0}_{1}".format(row,col)
|
||||
din_name = "din_{0}_{1}".format(row, col)
|
||||
|
||||
return din_name
|
||||
|
||||
|
|
@ -105,61 +103,58 @@ class dff_array(design.design):
|
|||
elif self.rows == 1:
|
||||
dout_name = "dout_{0}".format(col)
|
||||
else:
|
||||
dout_name = "dout_{0}_{1}".format(row,col)
|
||||
dout_name = "dout_{0}_{1}".format(row, col)
|
||||
|
||||
return dout_name
|
||||
|
||||
|
||||
def add_layout_pins(self):
|
||||
for row in range(self.rows):
|
||||
for col in range(self.columns):
|
||||
for col in range(self.columns):
|
||||
# Continous vdd rail along with label.
|
||||
vdd_pin=self.dff_insts[row,col].get_pin("vdd")
|
||||
self.add_power_pin("vdd", vdd_pin.center())
|
||||
vdd_pin=self.dff_insts[row, col].get_pin("vdd")
|
||||
self.add_power_pin("vdd", vdd_pin.center(), start_layer=vdd_pin.layer)
|
||||
|
||||
# Continous gnd rail along with label.
|
||||
gnd_pin=self.dff_insts[row,col].get_pin("gnd")
|
||||
self.add_power_pin("gnd", gnd_pin.center())
|
||||
gnd_pin=self.dff_insts[row, col].get_pin("gnd")
|
||||
self.add_power_pin("gnd", gnd_pin.center(), start_layer=gnd_pin.layer)
|
||||
|
||||
|
||||
for row in range(self.rows):
|
||||
for col in range(self.columns):
|
||||
din_pin = self.dff_insts[row,col].get_pin("D")
|
||||
debug.check(din_pin.layer=="m2","DFF D pin not on metal2")
|
||||
self.add_layout_pin(text=self.get_din_name(row,col),
|
||||
for row in range(self.rows):
|
||||
for col in range(self.columns):
|
||||
din_pin = self.dff_insts[row, col].get_pin("D")
|
||||
debug.check(din_pin.layer == "m2", "DFF D pin not on metal2")
|
||||
self.add_layout_pin(text=self.get_din_name(row, col),
|
||||
layer=din_pin.layer,
|
||||
offset=din_pin.ll(),
|
||||
width=din_pin.width(),
|
||||
height=din_pin.height())
|
||||
|
||||
dout_pin = self.dff_insts[row,col].get_pin("Q")
|
||||
debug.check(dout_pin.layer=="m2","DFF Q pin not on metal2")
|
||||
self.add_layout_pin(text=self.get_dout_name(row,col),
|
||||
dout_pin = self.dff_insts[row, col].get_pin("Q")
|
||||
debug.check(dout_pin.layer == "m2", "DFF Q pin not on metal2")
|
||||
self.add_layout_pin(text=self.get_dout_name(row, col),
|
||||
layer=dout_pin.layer,
|
||||
offset=dout_pin.ll(),
|
||||
width=dout_pin.width(),
|
||||
height=dout_pin.height())
|
||||
|
||||
|
||||
|
||||
# Create vertical spines to a single horizontal rail
|
||||
clk_pin = self.dff_insts[0,0].get_pin(self.dff.clk_pin)
|
||||
clk_ypos = 2*self.m3_pitch+self.m3_width
|
||||
debug.check(clk_pin.layer=="m2","DFF clk pin not on metal2")
|
||||
clk_pin = self.dff_insts[0, 0].get_pin(self.dff.clk_pin)
|
||||
clk_ypos = 2 * self.m3_pitch + self.m3_width
|
||||
debug.check(clk_pin.layer == "m2", "DFF clk pin not on metal2")
|
||||
self.add_layout_pin_segment_center(text="clk",
|
||||
layer="m3",
|
||||
start=vector(0,clk_ypos),
|
||||
end=vector(self.width,clk_ypos))
|
||||
start=vector(0, clk_ypos),
|
||||
end=vector(self.width, clk_ypos))
|
||||
for col in range(self.columns):
|
||||
clk_pin = self.dff_insts[0,col].get_pin(self.dff.clk_pin)
|
||||
clk_pin = self.dff_insts[0, col].get_pin(self.dff.clk_pin)
|
||||
# Make a vertical strip for each column
|
||||
self.add_rect(layer="m2",
|
||||
offset=clk_pin.ll().scale(1,0),
|
||||
offset=clk_pin.ll().scale(1, 0),
|
||||
width=self.m2_width,
|
||||
height=self.height)
|
||||
# Drop a via to the M3 pin
|
||||
self.add_via_center(layers=self.m2_stack,
|
||||
offset=vector(clk_pin.cx(),clk_ypos))
|
||||
self.add_via_stack_center(from_layer=clk_pin.layer,
|
||||
to_layer="m3",
|
||||
offset=vector(clk_pin.cx(), clk_ypos))
|
||||
|
||||
def get_clk_cin(self):
|
||||
"""Return the total capacitance (in relative units) that the clock is loaded by in the dff array"""
|
||||
|
|
|
|||
|
|
@ -167,11 +167,11 @@ class dff_buf_array(design.design):
|
|||
for col in range(self.columns):
|
||||
# Continous vdd rail along with label.
|
||||
vdd_pin=self.dff_insts[row, col].get_pin("vdd")
|
||||
self.add_power_pin("vdd", vdd_pin.lc())
|
||||
self.add_power_pin("vdd", vdd_pin.lc(), start_layer=vdd_pin.layer)
|
||||
|
||||
# Continous gnd rail along with label.
|
||||
gnd_pin=self.dff_insts[row, col].get_pin("gnd")
|
||||
self.add_power_pin("gnd", gnd_pin.lc())
|
||||
self.add_power_pin("gnd", gnd_pin.lc(), start_layer=gnd_pin.layer)
|
||||
|
||||
def add_layout_pins(self):
|
||||
|
||||
|
|
|
|||
|
|
@ -20,12 +20,16 @@ class hierarchical_predecode(design.design):
|
|||
def __init__(self, name, input_number, height=None):
|
||||
self.number_of_inputs = input_number
|
||||
|
||||
b = factory.create(module_type="bitcell")
|
||||
if not height:
|
||||
b = factory.create(module_type="bitcell")
|
||||
self.cell_height = b.height
|
||||
self.column_decoder = False
|
||||
else:
|
||||
self.cell_height = height
|
||||
|
||||
# If we are pitch matched to the bitcell, it's a predecoder
|
||||
# otherwise it's a column decoder (out of pgates)
|
||||
self.column_decoder = (height != b.height)
|
||||
|
||||
self.number_of_outputs = int(math.pow(2, self.number_of_inputs))
|
||||
design.design.__init__(self, name)
|
||||
|
||||
|
|
@ -40,21 +44,21 @@ class hierarchical_predecode(design.design):
|
|||
def add_modules(self):
|
||||
""" Add the INV and AND gate modules """
|
||||
|
||||
if self.number_of_inputs == 2:
|
||||
self.and_mod = factory.create(module_type="and2_dec",
|
||||
height=self.cell_height)
|
||||
elif self.number_of_inputs == 3:
|
||||
self.and_mod = factory.create(module_type="and3_dec",
|
||||
height=self.cell_height)
|
||||
elif self.number_of_inputs == 4:
|
||||
self.and_mod = factory.create(module_type="and4_dec",
|
||||
height=self.cell_height)
|
||||
debug.check(self.number_of_inputs < 4,
|
||||
"Invalid number of predecode inputs: {}".format(self.number_of_inputs))
|
||||
|
||||
if self.column_decoder:
|
||||
and_type = "pand{}".format(self.number_of_inputs)
|
||||
inv_type = "pinv"
|
||||
else:
|
||||
debug.error("Invalid number of predecode inputs: {}".format(self.number_of_inputs), -1)
|
||||
and_type = "and{}_dec".format(self.number_of_inputs)
|
||||
inv_type = "inv_dec"
|
||||
self.and_mod = factory.create(module_type=and_type,
|
||||
height=self.cell_height)
|
||||
self.add_mod(self.and_mod)
|
||||
|
||||
# This uses the pinv_dec parameterized cell
|
||||
self.inv = factory.create(module_type="inv_dec",
|
||||
self.inv = factory.create(module_type=inv_type,
|
||||
height=self.cell_height,
|
||||
size=1)
|
||||
self.add_mod(self.inv)
|
||||
|
|
@ -80,7 +84,7 @@ class hierarchical_predecode(design.design):
|
|||
# Outputs from cells are on output layer
|
||||
if OPTS.tech_name == "sky130":
|
||||
self.bus_layer = "m1"
|
||||
self.bus_directions = None
|
||||
self.bus_directions = "nonpref"
|
||||
self.bus_pitch = self.m1_pitch
|
||||
self.bus_space = 1.5 * self.m1_space
|
||||
self.input_layer = "m2"
|
||||
|
|
@ -88,7 +92,7 @@ class hierarchical_predecode(design.design):
|
|||
self.output_layer_pitch = self.li_pitch
|
||||
else:
|
||||
self.bus_layer = "m2"
|
||||
self.bus_directions = None
|
||||
self.bus_directions = "pref"
|
||||
self.bus_pitch = self.m2_pitch
|
||||
self.bus_space = self.m2_space
|
||||
# This requires a special jog to ensure to conflicts with the output layers
|
||||
|
|
@ -238,10 +242,7 @@ class hierarchical_predecode(design.design):
|
|||
# add output so that it is just below the vdd or gnd rail
|
||||
# since this is where the p/n devices are and there are no
|
||||
# pins in the and gates.
|
||||
if OPTS.tech_name == "sky130":
|
||||
inv_out_pos = inv_out_pin.lr()
|
||||
else:
|
||||
inv_out_pos = inv_out_pin.rc()
|
||||
inv_out_pos = inv_out_pin.rc()
|
||||
y_offset = (inv_num + 1) * self.inv.height - self.output_layer_pitch
|
||||
right_pos = inv_out_pos + vector(self.inv.width - self.inv.get_pin("Z").rx(), 0)
|
||||
rail_pos = vector(self.decode_rails[out_pin].cx(), y_offset)
|
||||
|
|
@ -309,7 +310,7 @@ class hierarchical_predecode(design.design):
|
|||
""" Add a pin for each row of vdd/gnd which are must-connects next level up. """
|
||||
|
||||
# In sky130, we use hand-made decoder cells with vertical power
|
||||
if OPTS.tech_name == "sky130":
|
||||
if OPTS.tech_name == "sky130" and not self.column_decoder:
|
||||
for n in ["vdd", "gnd"]:
|
||||
# This makes a wire from top to bottom for both inv and and gates
|
||||
for i in [self.inv_inst, self.and_inst]:
|
||||
|
|
|
|||
|
|
@ -377,11 +377,11 @@ class port_data(design.design):
|
|||
temp.append("{0}_{1}".format(br_name, bit))
|
||||
else:
|
||||
temp.append("{0}_out_{1}".format(bl_name, bit))
|
||||
temp.append("{0}_out_{1}".format(br_name, bit))
|
||||
temp.append("{0}_out_{1}".format(br_name, bit))
|
||||
|
||||
for bit in range(self.num_spare_cols):
|
||||
for bit in range(self.num_spare_cols):
|
||||
temp.append("spare{0}_{1}".format(bl_name, bit))
|
||||
temp.append("spare{0}_{1}".format(br_name, bit))
|
||||
temp.append("spare{0}_{1}".format(br_name, bit))
|
||||
|
||||
if self.write_size is not None:
|
||||
for i in range(self.num_wmasks):
|
||||
|
|
@ -522,13 +522,14 @@ class port_data(design.design):
|
|||
wdriver_pos = wdriver_en_pin.rc() - vector(self.m2_pitch, 0)
|
||||
mid_pos = vector(wdriver_pos.x, wmask_pos.y)
|
||||
|
||||
|
||||
# Add driver on mask output
|
||||
self.add_via_center(layers=self.m1_stack,
|
||||
offset=wmask_pos)
|
||||
self.add_via_stack_center(from_layer=wmask_out_pin.layer,
|
||||
to_layer="m1",
|
||||
offset=wmask_pos)
|
||||
# Add via for the write driver array's enable input
|
||||
self.add_via_center(layers=self.m1_stack,
|
||||
offset=wdriver_pos)
|
||||
self.add_via_stack_center(from_layer=wdriver_en_pin.layer,
|
||||
to_layer="m2",
|
||||
offset=wdriver_pos)
|
||||
|
||||
# Route between write mask AND array and write driver array
|
||||
self.add_wire(self.m1_stack, [wmask_pos, mid_pos, wdriver_pos])
|
||||
|
|
|
|||
|
|
@ -30,6 +30,11 @@ class precharge_array(design.design):
|
|||
self.bitcell_br = bitcell_br
|
||||
self.column_offset = column_offset
|
||||
|
||||
if OPTS.tech_name == "sky130":
|
||||
self.en_bar_layer = "m3"
|
||||
else:
|
||||
self.en_bar_layer = "m1"
|
||||
|
||||
self.create_netlist()
|
||||
if not OPTS.netlist_only:
|
||||
self.create_layout()
|
||||
|
|
@ -74,14 +79,18 @@ class precharge_array(design.design):
|
|||
|
||||
def add_layout_pins(self):
|
||||
|
||||
en_bar_pin = self.pc_cell.get_pin("en_bar")
|
||||
self.add_layout_pin(text="en_bar",
|
||||
layer=en_bar_pin.layer,
|
||||
offset=en_bar_pin.ll(),
|
||||
width=self.width,
|
||||
height=en_bar_pin.height())
|
||||
en_pin = self.pc_cell.get_pin("en_bar")
|
||||
start_offset = en_pin.lc().scale(0, 1)
|
||||
end_offset = start_offset + vector(self.width, 0)
|
||||
self.add_layout_pin_segment_center(text="en_bar",
|
||||
layer=self.en_bar_layer,
|
||||
start=start_offset,
|
||||
end=end_offset)
|
||||
|
||||
for inst in self.local_insts:
|
||||
self.add_via_stack_center(from_layer=en_pin.layer,
|
||||
to_layer=self.en_bar_layer,
|
||||
offset=inst.get_pin("en_bar").center())
|
||||
self.copy_layout_pin(inst, "vdd")
|
||||
|
||||
for i in range(len(self.local_insts)):
|
||||
|
|
|
|||
|
|
@ -378,21 +378,22 @@ class replica_bitcell_array(design.design):
|
|||
width=pin.width(),
|
||||
height=self.height)
|
||||
|
||||
# For specific technologies, there is no vdd via within the bitcell. Instead vdd is connect via end caps.
|
||||
try:
|
||||
bitcell_no_vdd_pin = cell_properties.bitcell.no_vdd_via
|
||||
except AttributeError:
|
||||
bitcell_no_vdd_pin = False
|
||||
|
||||
# vdd/gnd are only connected in the perimeter cells
|
||||
# replica column should only have a vdd/gnd in the dummy cell on top/bottom
|
||||
supply_insts = [self.dummy_col_left_inst, self.dummy_col_right_inst,
|
||||
self.dummy_row_top_inst, self.dummy_row_bot_inst]
|
||||
for pin_name in ["vdd", "gnd"]:
|
||||
for inst in self.insts:
|
||||
for inst in supply_insts:
|
||||
pin_list = inst.get_pins(pin_name)
|
||||
for pin in pin_list:
|
||||
if not (pin_name == "vdd" and bitcell_no_vdd_pin):
|
||||
self.add_power_pin(name=pin_name,
|
||||
loc=pin.center(),
|
||||
directions=("V", "V"),
|
||||
start_layer=pin.layer)
|
||||
self.add_power_pin(name=pin_name,
|
||||
loc=pin.center(),
|
||||
directions=("V", "V"),
|
||||
start_layer=pin.layer)
|
||||
|
||||
for inst in list(self.replica_col_inst.values()):
|
||||
self.copy_layout_pin(inst, pin_name)
|
||||
self.copy_layout_pin(inst, pin_name)
|
||||
|
||||
def get_rbl_wl_name(self, port):
|
||||
""" Return the WL for the given RBL port """
|
||||
|
|
|
|||
|
|
@ -183,11 +183,13 @@ class replica_column(design.design):
|
|||
width=self.width,
|
||||
height=wl_pin.height())
|
||||
|
||||
# For every second row and column, add a via for gnd and vdd
|
||||
for row in range(row_range_min, row_range_max):
|
||||
inst = self.cell_inst[row]
|
||||
# Supplies are only connected in the ends
|
||||
for (index, inst) in self.cell_inst.items():
|
||||
for pin_name in ["vdd", "gnd"]:
|
||||
self.copy_layout_pin(inst, pin_name)
|
||||
if inst in [self.cell_inst[0], self.cell_inst[self.total_size - 1]]:
|
||||
self.copy_power_pins(inst, pin_name)
|
||||
else:
|
||||
self.copy_layout_pin(inst, pin_name)
|
||||
|
||||
def get_bitcell_pins(self, col, row):
|
||||
""" Creates a list of connections in the bitcell,
|
||||
|
|
|
|||
|
|
@ -8,11 +8,12 @@
|
|||
import design
|
||||
import debug
|
||||
import utils
|
||||
from tech import GDS,layer, parameter,drc
|
||||
from tech import GDS, layer, parameter, drc
|
||||
from tech import cell_properties as props
|
||||
from globals import OPTS
|
||||
import logical_effort
|
||||
|
||||
|
||||
class sense_amp(design.design):
|
||||
"""
|
||||
This module implements the single sense amp cell used in the design. It
|
||||
|
|
@ -28,10 +29,10 @@ class sense_amp(design.design):
|
|||
props.sense_amp.pin.gnd]
|
||||
type_list = ["INPUT", "INPUT", "OUTPUT", "INPUT", "POWER", "GROUND"]
|
||||
if not OPTS.netlist_only:
|
||||
(width,height) = utils.get_libcell_size("sense_amp", GDS["unit"], layer["boundary"])
|
||||
(width, height) = utils.get_libcell_size("sense_amp", GDS["unit"], layer["boundary"])
|
||||
pin_map = utils.get_libcell_pins(pin_names, "sense_amp", GDS["unit"])
|
||||
else:
|
||||
(width, height) = (0,0)
|
||||
(width, height) = (0, 0)
|
||||
pin_map = []
|
||||
|
||||
def get_bl_names(self):
|
||||
|
|
@ -61,41 +62,41 @@ class sense_amp(design.design):
|
|||
|
||||
# FIXME: This input load will be applied to both the s_en timing and bitline timing.
|
||||
|
||||
#Input load for the bitlines which are connected to the source/drain of a TX. Not the selects.
|
||||
from tech import spice, parameter
|
||||
# Input load for the bitlines which are connected to the source/drain of a TX. Not the selects.
|
||||
from tech import spice
|
||||
# Default is 8x. Per Samira and Hodges-Jackson book:
|
||||
# "Column-mux transistors driven by the decoder must be sized for optimal speed"
|
||||
bitline_pmos_size = 8 #FIXME: This should be set somewhere and referenced. Probably in tech file.
|
||||
return spice["min_tx_drain_c"]*(bitline_pmos_size)#ff
|
||||
bitline_pmos_size = 8 # FIXME: This should be set somewhere and referenced. Probably in tech file.
|
||||
return spice["min_tx_drain_c"] * bitline_pmos_size # ff
|
||||
|
||||
def get_stage_effort(self, load):
|
||||
#Delay of the sense amp will depend on the size of the amp and the output load.
|
||||
# Delay of the sense amp will depend on the size of the amp and the output load.
|
||||
parasitic_delay = 1
|
||||
cin = (parameter["sa_inv_pmos_size"] + parameter["sa_inv_nmos_size"])/drc("minwidth_tx")
|
||||
sa_size = parameter["sa_inv_nmos_size"]/drc("minwidth_tx")
|
||||
cin = (parameter["sa_inv_pmos_size"] + parameter["sa_inv_nmos_size"]) / drc("minwidth_tx")
|
||||
sa_size = parameter["sa_inv_nmos_size"] / drc("minwidth_tx")
|
||||
cc_inv_cin = cin
|
||||
return logical_effort.logical_effort('column_mux', sa_size, cin, load+cc_inv_cin, parasitic_delay, False)
|
||||
return logical_effort.logical_effort('column_mux', sa_size, cin, load + cc_inv_cin, parasitic_delay, False)
|
||||
|
||||
def analytical_power(self, corner, load):
|
||||
"""Returns dynamic and leakage power. Results in nW"""
|
||||
#Power in this module currently not defined. Returns 0 nW (leakage and dynamic).
|
||||
# Power in this module currently not defined. Returns 0 nW (leakage and dynamic).
|
||||
total_power = self.return_power()
|
||||
return total_power
|
||||
|
||||
def get_en_cin(self):
|
||||
"""Get the relative capacitance of sense amp enable gate cin"""
|
||||
pmos_cin = parameter["sa_en_pmos_size"]/drc("minwidth_tx")
|
||||
nmos_cin = parameter["sa_en_nmos_size"]/drc("minwidth_tx")
|
||||
#sen is connected to 2 pmos isolation TX and 1 nmos per sense amp.
|
||||
return 2*pmos_cin + nmos_cin
|
||||
pmos_cin = parameter["sa_en_pmos_size"] / drc("minwidth_tx")
|
||||
nmos_cin = parameter["sa_en_nmos_size"] / drc("minwidth_tx")
|
||||
# sen is connected to 2 pmos isolation TX and 1 nmos per sense amp.
|
||||
return 2 * pmos_cin + nmos_cin
|
||||
|
||||
def get_enable_name(self):
|
||||
"""Returns name used for enable net"""
|
||||
#FIXME: A better programmatic solution to designate pins
|
||||
# FIXME: A better programmatic solution to designate pins
|
||||
enable_name = self.en_name
|
||||
debug.check(enable_name in self.pin_names, "Enable name {} not found in pin list".format(enable_name))
|
||||
return enable_name
|
||||
|
||||
def build_graph(self, graph, inst_name, port_nets):
|
||||
def build_graph(self, graph, inst_name, port_nets):
|
||||
"""Adds edges based on inputs/outputs. Overrides base class function."""
|
||||
self.add_graph_edges(graph, port_nets)
|
||||
self.add_graph_edges(graph, port_nets)
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ class sense_amp_array(design.design):
|
|||
self.add_comment("words_per_row: {0}".format(words_per_row))
|
||||
|
||||
self.word_size = word_size
|
||||
self.words_per_row = words_per_row
|
||||
self.words_per_row = words_per_row
|
||||
if not num_spare_cols:
|
||||
self.num_spare_cols = 0
|
||||
else:
|
||||
|
|
@ -37,6 +37,11 @@ class sense_amp_array(design.design):
|
|||
self.column_offset = column_offset
|
||||
self.row_size = self.word_size * self.words_per_row
|
||||
|
||||
if OPTS.tech_name == "sky130":
|
||||
self.en_layer = "m3"
|
||||
else:
|
||||
self.en_layer = "m1"
|
||||
|
||||
self.create_netlist()
|
||||
if not OPTS.netlist_only:
|
||||
self.create_layout()
|
||||
|
|
@ -77,7 +82,7 @@ class sense_amp_array(design.design):
|
|||
self.DRC_LVS()
|
||||
|
||||
def add_pins(self):
|
||||
for i in range(0,self.word_size + self.num_spare_cols):
|
||||
for i in range(0, self.word_size + self.num_spare_cols):
|
||||
self.add_pin(self.data_name + "_{0}".format(i), "OUTPUT")
|
||||
self.add_pin(self.get_bl_name() + "_{0}".format(i), "INPUT")
|
||||
self.add_pin(self.get_br_name() + "_{0}".format(i), "INPUT")
|
||||
|
|
@ -96,7 +101,7 @@ class sense_amp_array(design.design):
|
|||
|
||||
def create_sense_amp_array(self):
|
||||
self.local_insts = []
|
||||
for i in range(0,self.word_size + self.num_spare_cols):
|
||||
for i in range(0, self.word_size + self.num_spare_cols):
|
||||
name = "sa_d{0}".format(i)
|
||||
self.local_insts.append(self.add_inst(name=name,
|
||||
mod=self.amp))
|
||||
|
|
@ -107,14 +112,10 @@ class sense_amp_array(design.design):
|
|||
|
||||
def place_sense_amp_array(self):
|
||||
from tech import cell_properties
|
||||
if self.bitcell.width > self.amp.width:
|
||||
amp_spacing = self.bitcell.width
|
||||
else:
|
||||
amp_spacing = self.amp.width
|
||||
|
||||
for i in range(0, self.row_size, self.words_per_row):
|
||||
index = int(i / self.words_per_row)
|
||||
xoffset = i * amp_spacing
|
||||
xoffset = i * self.bitcell.width
|
||||
|
||||
if cell_properties.bitcell.mirror.y and (i + self.column_offset) % 2:
|
||||
mirror = "MY"
|
||||
|
|
@ -126,9 +127,9 @@ class sense_amp_array(design.design):
|
|||
self.local_insts[index].place(offset=amp_position, mirror=mirror)
|
||||
|
||||
# place spare sense amps (will share the same enable as regular sense amps)
|
||||
for i in range(0,self.num_spare_cols):
|
||||
for i in range(0, self.num_spare_cols):
|
||||
index = self.word_size + i
|
||||
xoffset = ((self.word_size * self.words_per_row) + i) * amp_spacing
|
||||
xoffset = ((self.word_size * self.words_per_row) + i) * self.bitcell.width
|
||||
|
||||
if cell_properties.bitcell.mirror.y and (i + self.column_offset) % 2:
|
||||
mirror = "MY"
|
||||
|
|
@ -143,17 +144,17 @@ class sense_amp_array(design.design):
|
|||
for i in range(len(self.local_insts)):
|
||||
inst = self.local_insts[i]
|
||||
|
||||
gnd_pin = inst.get_pin("gnd")
|
||||
self.add_power_pin(name="gnd",
|
||||
loc=gnd_pin.center(),
|
||||
start_layer=gnd_pin.layer,
|
||||
directions=("V", "V"))
|
||||
|
||||
vdd_pin = inst.get_pin("vdd")
|
||||
self.add_power_pin(name="vdd",
|
||||
loc=vdd_pin.center(),
|
||||
start_layer=vdd_pin.layer,
|
||||
directions=("V", "V"))
|
||||
for gnd_pin in inst.get_pins("gnd"):
|
||||
self.add_power_pin(name="gnd",
|
||||
loc=gnd_pin.center(),
|
||||
start_layer=gnd_pin.layer,
|
||||
directions=("V", "V"))
|
||||
|
||||
for vdd_pin in inst.get_pins("vdd"):
|
||||
self.add_power_pin(name="vdd",
|
||||
loc=vdd_pin.center(),
|
||||
start_layer=vdd_pin.layer,
|
||||
directions=("V", "V"))
|
||||
|
||||
bl_pin = inst.get_pin(inst.mod.get_bl_names())
|
||||
br_pin = inst.get_pin(inst.mod.get_br_names())
|
||||
|
|
@ -177,14 +178,18 @@ class sense_amp_array(design.design):
|
|||
height=dout_pin.height())
|
||||
|
||||
def route_rails(self):
|
||||
# add sclk rail across entire array
|
||||
sclk = self.amp.get_pin(self.amp.en_name)
|
||||
sclk_offset = self.amp.get_pin(self.amp.en_name).ll().scale(0, 1)
|
||||
self.add_layout_pin(text=self.en_name,
|
||||
layer=sclk.layer,
|
||||
offset=sclk_offset,
|
||||
width=self.width,
|
||||
height=drc("minwidth_" + sclk.layer))
|
||||
# Add enable across the array
|
||||
en_pin = self.amp.get_pin(self.amp.en_name)
|
||||
start_offset = en_pin.lc().scale(0, 1)
|
||||
end_offset = start_offset + vector(self.width, 0)
|
||||
self.add_layout_pin_segment_center(text=self.en_name,
|
||||
layer=self.en_layer,
|
||||
start=start_offset,
|
||||
end=end_offset)
|
||||
for inst in self.local_insts:
|
||||
self.add_via_stack_center(from_layer=en_pin.layer,
|
||||
to_layer=self.en_layer,
|
||||
offset=inst.get_pin(self.amp.en_name).center())
|
||||
|
||||
def input_load(self):
|
||||
return self.amp.input_load()
|
||||
|
|
|
|||
|
|
@ -32,14 +32,16 @@ class single_level_column_mux_array(design.design):
|
|||
self.bitcell_br = bitcell_br
|
||||
self.column_offset = column_offset
|
||||
|
||||
if "li" in layer:
|
||||
self.col_mux_stack = self.li_stack
|
||||
self.col_mux_stack_pitch = self.m1_pitch
|
||||
if OPTS.tech_name == "sky130":
|
||||
self.sel_layer = "m3"
|
||||
self.sel_pitch = self.m3_pitch
|
||||
self.bitline_layer = "m1"
|
||||
else:
|
||||
self.col_mux_stack = self.m1_stack
|
||||
self.col_mux_stack_pitch = self.m1_pitch
|
||||
self.sel_layer = "m1"
|
||||
self.sel_pitch = self.m2_pitch
|
||||
self.bitline_layer = "m2"
|
||||
|
||||
if preferred_directions[self.col_mux_stack[0]] == "V":
|
||||
if preferred_directions[self.sel_layer] == "V":
|
||||
self.via_directions = ("H", "H")
|
||||
else:
|
||||
self.via_directions = "pref"
|
||||
|
|
@ -96,7 +98,7 @@ class single_level_column_mux_array(design.design):
|
|||
self.width = self.columns * self.mux.width
|
||||
# one set of metal1 routes for select signals and a pair to interconnect the mux outputs bl/br
|
||||
# one extra route pitch is to space from the sense amp
|
||||
self.route_height = (self.words_per_row + 3) * self.col_mux_stack_pitch
|
||||
self.route_height = (self.words_per_row + 3) * self.sel_pitch
|
||||
|
||||
def create_array(self):
|
||||
self.mux_inst = []
|
||||
|
|
@ -155,11 +157,11 @@ class single_level_column_mux_array(design.design):
|
|||
self.route_bitlines()
|
||||
|
||||
def add_horizontal_input_rail(self):
|
||||
""" Create address input rails on M1 below the mux transistors """
|
||||
""" Create address input rails below the mux transistors """
|
||||
for j in range(self.words_per_row):
|
||||
offset = vector(0, self.route_height + (j - self.words_per_row) * self.col_mux_stack_pitch)
|
||||
offset = vector(0, self.route_height + (j - self.words_per_row) * self.sel_pitch)
|
||||
self.add_layout_pin(text="sel_{}".format(j),
|
||||
layer=self.col_mux_stack[0],
|
||||
layer=self.sel_layer,
|
||||
offset=offset,
|
||||
width=self.mux.width * self.columns)
|
||||
|
||||
|
|
@ -177,10 +179,10 @@ class single_level_column_mux_array(design.design):
|
|||
# use the y offset from the sel pin and the x offset from the gate
|
||||
offset = vector(gate_offset.x,
|
||||
self.get_pin("sel_{}".format(sel_index)).cy())
|
||||
# Add the poly contact with a shift to account for the rotation
|
||||
self.add_via_center(layers=self.poly_stack,
|
||||
offset=offset,
|
||||
directions=self.via_directions)
|
||||
self.add_via_stack_center(from_layer="poly",
|
||||
to_layer=self.sel_layer,
|
||||
offset=offset,
|
||||
directions=self.via_directions)
|
||||
self.add_path("poly", [offset, gate_offset])
|
||||
|
||||
def route_bitlines(self):
|
||||
|
|
@ -190,42 +192,44 @@ class single_level_column_mux_array(design.design):
|
|||
bl_offset_begin = self.mux_inst[j].get_pin("bl_out").bc()
|
||||
br_offset_begin = self.mux_inst[j].get_pin("br_out").bc()
|
||||
|
||||
bl_out_offset_begin = bl_offset_begin - vector(0, (self.words_per_row + 1) * self.col_mux_stack_pitch)
|
||||
br_out_offset_begin = br_offset_begin - vector(0, (self.words_per_row + 2) * self.col_mux_stack_pitch)
|
||||
bl_out_offset_begin = bl_offset_begin - vector(0, (self.words_per_row + 1) * self.sel_pitch)
|
||||
br_out_offset_begin = br_offset_begin - vector(0, (self.words_per_row + 2) * self.sel_pitch)
|
||||
|
||||
# Add the horizontal wires for the first bit
|
||||
if j % self.words_per_row == 0:
|
||||
bl_offset_end = self.mux_inst[j + self.words_per_row - 1].get_pin("bl_out").bc()
|
||||
br_offset_end = self.mux_inst[j + self.words_per_row - 1].get_pin("br_out").bc()
|
||||
bl_out_offset_end = bl_offset_end - vector(0, (self.words_per_row + 1) * self.col_mux_stack_pitch)
|
||||
br_out_offset_end = br_offset_end - vector(0, (self.words_per_row + 2) * self.col_mux_stack_pitch)
|
||||
bl_out_offset_end = bl_offset_end - vector(0, (self.words_per_row + 1) * self.sel_pitch)
|
||||
br_out_offset_end = br_offset_end - vector(0, (self.words_per_row + 2) * self.sel_pitch)
|
||||
|
||||
self.add_path(self.col_mux_stack[0], [bl_out_offset_begin, bl_out_offset_end])
|
||||
self.add_path(self.col_mux_stack[0], [br_out_offset_begin, br_out_offset_end])
|
||||
self.add_path(self.sel_layer, [bl_out_offset_begin, bl_out_offset_end])
|
||||
self.add_path(self.sel_layer, [br_out_offset_begin, br_out_offset_end])
|
||||
|
||||
# Extend the bitline output rails and gnd downward on the first bit of each n-way mux
|
||||
self.add_layout_pin_segment_center(text="bl_out_{}".format(int(j / self.words_per_row)),
|
||||
layer=self.col_mux_stack[2],
|
||||
layer=self.bitline_layer,
|
||||
start=bl_offset_begin,
|
||||
end=bl_out_offset_begin)
|
||||
self.add_layout_pin_segment_center(text="br_out_{}".format(int(j / self.words_per_row)),
|
||||
layer=self.col_mux_stack[2],
|
||||
layer=self.bitline_layer,
|
||||
start=br_offset_begin,
|
||||
end=br_out_offset_begin)
|
||||
|
||||
else:
|
||||
self.add_path(self.col_mux_stack[2], [bl_out_offset_begin, bl_offset_begin])
|
||||
self.add_path(self.col_mux_stack[2], [br_out_offset_begin, br_offset_begin])
|
||||
self.add_path(self.bitline_layer, [bl_out_offset_begin, bl_offset_begin])
|
||||
self.add_path(self.bitline_layer, [br_out_offset_begin, br_offset_begin])
|
||||
|
||||
# This via is on the right of the wire
|
||||
self.add_via_center(layers=self.col_mux_stack,
|
||||
offset=bl_out_offset_begin,
|
||||
directions=self.via_directions)
|
||||
self.add_via_stack_center(from_layer=self.bitline_layer,
|
||||
to_layer=self.sel_layer,
|
||||
offset=bl_out_offset_begin,
|
||||
directions=self.via_directions)
|
||||
|
||||
# This via is on the left of the wire
|
||||
self.add_via_center(layers=self.col_mux_stack,
|
||||
offset=br_out_offset_begin,
|
||||
directions=self.via_directions)
|
||||
self.add_via_stack_center(from_layer=self.bitline_layer,
|
||||
to_layer=self.sel_layer,
|
||||
offset=br_out_offset_begin,
|
||||
directions=self.via_directions)
|
||||
|
||||
def get_drain_cin(self):
|
||||
"""Get the relative capacitance of the drain of the NMOS pass TX"""
|
||||
|
|
|
|||
|
|
@ -154,7 +154,6 @@ class write_driver_array(design.design):
|
|||
self.get_br_name() + "_{0}".format(index),
|
||||
self.en_name + "_{0}".format(i + offset), "vdd", "gnd"])
|
||||
|
||||
|
||||
def place_write_array(self):
|
||||
from tech import cell_properties
|
||||
if self.bitcell.width > self.driver.width:
|
||||
|
|
|
|||
|
|
@ -108,8 +108,21 @@ class write_mask_and_array(design.design):
|
|||
end=vector(self.width, en_pin.cy()))
|
||||
|
||||
for i in range(self.num_wmasks):
|
||||
# Route the A pin over to the left so that it doesn't conflict with the sense
|
||||
# amp output which is usually in the center
|
||||
a_pin = self.and2_insts[i].get_pin("A")
|
||||
a_pos = a_pin.center()
|
||||
in_pos = vector(self.and2_insts[i].lx(),
|
||||
a_pos.y)
|
||||
self.add_via_stack_center(from_layer=a_pin.layer,
|
||||
to_layer="m2",
|
||||
offset=in_pos)
|
||||
self.add_layout_pin_rect_center(text="wmask_in_{0}".format(i),
|
||||
layer="m2",
|
||||
offset=in_pos)
|
||||
self.add_path(a_pin.layer, [in_pos, a_pos])
|
||||
|
||||
# Copy remaining layout pins
|
||||
self.copy_layout_pin(self.and2_insts[i], "A", "wmask_in_{0}".format(i))
|
||||
self.copy_layout_pin(self.and2_insts[i], "Z", "wmask_out_{0}".format(i))
|
||||
|
||||
# Add via connections to metal3 for AND array's B pin
|
||||
|
|
@ -121,10 +134,7 @@ class write_mask_and_array(design.design):
|
|||
|
||||
for supply in ["gnd", "vdd"]:
|
||||
supply_pin=self.and2_insts[i].get_pin(supply)
|
||||
if "li" in layer:
|
||||
self.add_power_pin(supply, supply_pin.center(), start_layer="li", directions = ("H", "H"))
|
||||
else:
|
||||
self.add_power_pin(supply, supply_pin.center())
|
||||
self.add_power_pin(supply, supply_pin.center(), start_layer=supply_pin.layer)
|
||||
|
||||
for supply in ["gnd", "vdd"]:
|
||||
supply_pin_left = self.and2_insts[0].get_pin(supply)
|
||||
|
|
|
|||
|
|
@ -58,7 +58,7 @@ class options(optparse.Values):
|
|||
delay_chain_stages = 9
|
||||
delay_chain_fanout_per_stage = 4
|
||||
|
||||
|
||||
accuracy_requirement = 0.75
|
||||
|
||||
###################
|
||||
# Debug options.
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ from vector import vector
|
|||
from globals import OPTS
|
||||
|
||||
if(OPTS.tech_name == "sky130"):
|
||||
from tech import nmos_bins, pmos_bins, accuracy_requirement
|
||||
from tech import nmos_bins, pmos_bins
|
||||
|
||||
|
||||
class pgate(design.design):
|
||||
|
|
@ -43,6 +43,9 @@ class pgate(design.design):
|
|||
self.route_layer_space = getattr(self, "{}_space".format(self.route_layer))
|
||||
self.route_layer_pitch = getattr(self, "{}_pitch".format(self.route_layer))
|
||||
|
||||
# hack for enclosing input pin with npc
|
||||
self.input_pin_vias = []
|
||||
|
||||
# This is the space from a S/D contact to the supply rail
|
||||
contact_to_vdd_rail_space = 0.5 * self.route_layer_width + self.route_layer_space
|
||||
# This is a poly-to-poly of a flipped cell
|
||||
|
|
@ -146,20 +149,23 @@ class pgate(design.design):
|
|||
height=contact.poly_contact.first_layer_width,
|
||||
width=left_gate_offset.x - contact_offset.x)
|
||||
|
||||
return via
|
||||
|
||||
def extend_wells(self):
|
||||
""" Extend the n/p wells to cover whole cell """
|
||||
|
||||
# This should match the cells in the cell library
|
||||
self.nwell_y_offset = 0.48 * self.height
|
||||
self.nwell_yoffset = 0.48 * self.height
|
||||
full_height = self.height + 0.5 * self.m1_width
|
||||
|
||||
|
||||
# FIXME: float rounding problem
|
||||
if "nwell" in layer:
|
||||
# Add a rail width to extend the well to the top of the rail
|
||||
nwell_max_offset = max(self.find_highest_layer_coords("nwell").y,
|
||||
full_height)
|
||||
nwell_position = vector(0, self.nwell_y_offset) - vector(self.well_extend_active, 0)
|
||||
nwell_height = nwell_max_offset - self.nwell_y_offset
|
||||
nwell_position = vector(0, self.nwell_yoffset) - vector(self.well_extend_active, 0)
|
||||
nwell_height = nwell_max_offset - self.nwell_yoffset
|
||||
self.add_rect(layer="nwell",
|
||||
offset=nwell_position,
|
||||
width=self.width + 2 * self.well_extend_active,
|
||||
|
|
@ -175,7 +181,7 @@ class pgate(design.design):
|
|||
pwell_min_offset = min(self.find_lowest_layer_coords("pwell").y,
|
||||
-0.5 * self.m1_width)
|
||||
pwell_position = vector(-self.well_extend_active, pwell_min_offset)
|
||||
pwell_height = self.nwell_y_offset - pwell_position.y
|
||||
pwell_height = self.nwell_yoffset - pwell_position.y
|
||||
self.add_rect(layer="pwell",
|
||||
offset=pwell_position,
|
||||
width=self.width + 2 * self.well_extend_active,
|
||||
|
|
@ -186,6 +192,9 @@ class pgate(design.design):
|
|||
width=self.width + 2 * self.well_extend_active,
|
||||
height=pwell_height)
|
||||
|
||||
if OPTS.tech_name == "sky130":
|
||||
self.extend_implants()
|
||||
|
||||
def add_nwell_contact(self, pmos, pmos_pos):
|
||||
""" Add an nwell contact next to the given pmos device. """
|
||||
|
||||
|
|
@ -240,6 +249,52 @@ class pgate(design.design):
|
|||
|
||||
# Return the top of the well
|
||||
|
||||
def extend_implants(self):
|
||||
"""
|
||||
Add top-to-bottom implants for adjacency issues in s8.
|
||||
"""
|
||||
if self.add_wells:
|
||||
rightx = None
|
||||
else:
|
||||
rightx = self.width
|
||||
|
||||
nmos_insts = self.get_tx_insts("nmos")
|
||||
if len(nmos_insts) > 0:
|
||||
self.add_enclosure(nmos_insts,
|
||||
layer="nimplant",
|
||||
extend=self.implant_enclose_active,
|
||||
leftx=0,
|
||||
rightx=rightx,
|
||||
boty=0)
|
||||
|
||||
pmos_insts = self.get_tx_insts("pmos")
|
||||
if len(pmos_insts) > 0:
|
||||
self.add_enclosure(pmos_insts,
|
||||
layer="pimplant",
|
||||
extend=self.implant_enclose_active,
|
||||
leftx=0,
|
||||
rightx=rightx,
|
||||
topy=self.height)
|
||||
|
||||
try:
|
||||
ntap_insts = [self.nwell_contact]
|
||||
self.add_enclosure(ntap_insts,
|
||||
layer="nimplant",
|
||||
extend=self.implant_enclose_active,
|
||||
rightx=self.width,
|
||||
topy=self.height)
|
||||
except AttributeError:
|
||||
pass
|
||||
try:
|
||||
ptap_insts = [self.pwell_contact]
|
||||
self.add_enclosure(ptap_insts,
|
||||
layer="pimplant",
|
||||
extend=self.implant_enclose_active,
|
||||
rightx=self.width,
|
||||
boty=0)
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
def add_pwell_contact(self, nmos, nmos_pos):
|
||||
""" Add an pwell contact next to the given nmos device. """
|
||||
|
||||
|
|
@ -268,7 +323,7 @@ class pgate(design.design):
|
|||
offset=contact_offset.scale(1, 0.5),
|
||||
width=self.pwell_contact.mod.second_layer_width,
|
||||
height=contact_offset.y)
|
||||
|
||||
|
||||
# Now add the full active and implant for the NMOS
|
||||
# active_offset = nmos_pos + vector(nmos.active_width,0)
|
||||
# This might be needed if the spacing between the actives
|
||||
|
|
@ -345,7 +400,7 @@ class pgate(design.design):
|
|||
|
||||
select = -1
|
||||
for i in reversed(range(0, len(scaled_bins))):
|
||||
if abs(target_width - scaled_bins[i])/target_width <= 1-accuracy_requirement:
|
||||
if abs(target_width - scaled_bins[i])/target_width <= 1-OPTS.accuracy_requirement:
|
||||
select = i
|
||||
break
|
||||
if select == -1:
|
||||
|
|
@ -379,4 +434,4 @@ class pgate(design.design):
|
|||
return(scaled_bins)
|
||||
|
||||
def bin_accuracy(self, ideal_width, width):
|
||||
return abs(1-(ideal_width - width)/ideal_width)
|
||||
return 1-abs((ideal_width - width)/ideal_width)
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ from sram_factory import factory
|
|||
from errors import drc_error
|
||||
|
||||
if(OPTS.tech_name == "sky130"):
|
||||
from tech import nmos_bins, pmos_bins, accuracy_requirement
|
||||
from tech import nmos_bins, pmos_bins
|
||||
|
||||
|
||||
class pinv(pgate.pgate):
|
||||
|
|
@ -31,6 +31,9 @@ class pinv(pgate.pgate):
|
|||
height is usually the same as the 6t library cell and is measured
|
||||
from center of rail to rail.
|
||||
"""
|
||||
# binning %error tracker
|
||||
bin_count = 0
|
||||
bin_error = 0
|
||||
|
||||
def __init__(self, name, size=1, beta=parameter["beta"], height=None, add_wells=True):
|
||||
|
||||
|
|
@ -44,7 +47,7 @@ class pinv(pgate.pgate):
|
|||
self.nmos_size = size
|
||||
self.pmos_size = beta * size
|
||||
self.beta = beta
|
||||
|
||||
|
||||
pgate.pgate.__init__(self, name, height, add_wells)
|
||||
|
||||
def create_netlist(self):
|
||||
|
|
@ -166,30 +169,37 @@ class pinv(pgate.pgate):
|
|||
|
||||
valid_pmos = []
|
||||
for bin in pmos_bins:
|
||||
if self.bin_accuracy(self.pmos_width, bin[0]) > accuracy_requirement:
|
||||
if abs(self.bin_accuracy(self.pmos_width, bin[0])) > OPTS.accuracy_requirement and abs(self.bin_accuracy(self.pmos_width, bin[0])) <= 1:
|
||||
valid_pmos.append(bin)
|
||||
valid_pmos.sort(key = operator.itemgetter(1))
|
||||
|
||||
valid_nmos = []
|
||||
for bin in nmos_bins:
|
||||
if self.bin_accuracy(self.nmos_width, bin[0]) > accuracy_requirement:
|
||||
if abs(self.bin_accuracy(self.nmos_width, bin[0])) > OPTS.accuracy_requirement and abs(self.bin_accuracy(self.nmos_width, bin[0])) <= 1:
|
||||
valid_nmos.append(bin)
|
||||
valid_nmos.sort(key = operator.itemgetter(1))
|
||||
|
||||
for bin in valid_pmos:
|
||||
if bin[0]/bin[1] < pmos_height_available:
|
||||
self.pmos_width = bin[0]/bin[1]
|
||||
pmos_mults = valid_pmos[0][1]
|
||||
pmos_mults = bin[1]
|
||||
break
|
||||
|
||||
for bin in valid_nmos:
|
||||
if bin[0]/bin[1] < nmos_height_available:
|
||||
self.nmos_width = bin[0]/bin[1]
|
||||
nmos_mults = valid_pmos[0][1]
|
||||
nmos_mults = bin[1]
|
||||
break
|
||||
|
||||
self.tx_mults = max(pmos_mults, nmos_mults)
|
||||
|
||||
debug.info(2, "prebinning {0} tx, target: {4}, found {1} x {2} = {3}".format("pmos", self.pmos_width, pmos_mults, self.pmos_width * pmos_mults, self.pmos_size * drc("minwidth_tx")))
|
||||
debug.info(2, "prebinning {0} tx, target: {4}, found {1} x {2} = {3}".format("nmos", self.nmos_width, nmos_mults, self.nmos_width * nmos_mults, self.nmos_size * drc("minwidth_tx")))
|
||||
pinv.bin_count += 1
|
||||
pinv.bin_error += abs(((self.pmos_width * pmos_mults) - (self.pmos_size * drc("minwidth_tx")))/(self.pmos_size * drc("minwidth_tx")))
|
||||
pinv.bin_count += 1
|
||||
pinv.bin_error += abs(((self.nmos_width * nmos_mults) - (self.nmos_size * drc("minwidth_tx")))/(self.nmos_size * drc("minwidth_tx")))
|
||||
debug.info(2, "pinv bin count: {0} pinv bin error: {1} percent error {2}".format(pinv.bin_count, pinv.bin_error, pinv.bin_error/pinv.bin_count))
|
||||
|
||||
def add_ptx(self):
|
||||
""" Create the PMOS and NMOS transistors. """
|
||||
self.nmos = factory.create(module_type="ptx",
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ from globals import OPTS
|
|||
from sram_factory import factory
|
||||
|
||||
if(OPTS.tech_name == "sky130"):
|
||||
from tech import nmos_bins, pmos_bins, accuracy_requirement
|
||||
from tech import nmos_bins, pmos_bins
|
||||
|
||||
|
||||
class pinv_dec(pinv.pinv):
|
||||
|
|
|
|||
|
|
@ -184,33 +184,37 @@ class pnand2(pgate.pgate):
|
|||
# doesn't use nmos uy because that is calculated using offset + poly height
|
||||
active_top = self.nmos1_inst.by() + self.nmos1_inst.mod.active_height
|
||||
active_to_poly_contact = active_top + self.poly_to_active + 0.5 * contact.poly_contact.first_layer_height
|
||||
active_to_poly_contact2 = active_top + drc("contact_to_gate") + 0.5 * self.route_layer_width
|
||||
active_to_poly_contact2 = active_top + self.poly_contact_to_gate + 0.5 * self.route_layer_width
|
||||
self.inputA_yoffset = max(active_contact_to_poly_contact,
|
||||
active_to_poly_contact,
|
||||
active_to_poly_contact2)
|
||||
|
||||
self.route_input_gate(self.pmos1_inst,
|
||||
self.nmos1_inst,
|
||||
self.inputA_yoffset,
|
||||
"A",
|
||||
position="center")
|
||||
apin = self.route_input_gate(self.pmos1_inst,
|
||||
self.nmos1_inst,
|
||||
self.inputA_yoffset,
|
||||
"A",
|
||||
position="center")
|
||||
|
||||
self.inputB_yoffset = self.inputA_yoffset + 2 * self.m3_pitch
|
||||
# # active contact metal to poly contact metal spacing
|
||||
# active_contact_to_poly_contact = self.output_yoffset - self.route_layer_space - 0.5 * contact.poly_contact.second_layer_height
|
||||
# active_bottom = self.pmos1_inst.by()
|
||||
# active_to_poly_contact = active_bottom - self.poly_to_active - 0.5 * contact.poly_contact.first_layer_height
|
||||
# active_to_poly_contact2 = active_bottom - drc("contact_to_gate") - 0.5 * self.route_layer_width
|
||||
# active_to_poly_contact2 = active_bottom - self.poly_contact_to_gate - 0.5 * self.route_layer_width
|
||||
# self.inputB_yoffset = min(active_contact_to_poly_contact,
|
||||
# active_to_poly_contact,
|
||||
# active_to_poly_contact2)
|
||||
|
||||
# This will help with the wells and the input/output placement
|
||||
self.route_input_gate(self.pmos2_inst,
|
||||
self.nmos2_inst,
|
||||
self.inputB_yoffset,
|
||||
"B",
|
||||
position="center")
|
||||
bpin = self.route_input_gate(self.pmos2_inst,
|
||||
self.nmos2_inst,
|
||||
self.inputB_yoffset,
|
||||
"B",
|
||||
position="center")
|
||||
|
||||
if OPTS.tech_name == "sky130":
|
||||
self.add_enclosure([apin, bpin], "npc", drc("npc_enclose_poly"))
|
||||
|
||||
|
||||
def route_output(self):
|
||||
""" Route the Z output """
|
||||
|
|
|
|||
|
|
@ -222,31 +222,33 @@ class pnand3(pgate.pgate):
|
|||
# doesn't use nmos uy because that is calculated using offset + poly height
|
||||
active_top = self.nmos1_inst.by() + self.nmos1_inst.mod.active_height
|
||||
active_to_poly_contact = active_top + self.poly_to_active + 0.5 * contact.poly_contact.first_layer_height
|
||||
active_to_poly_contact2 = active_top + drc("contact_to_gate") + 0.5 * self.route_layer_width
|
||||
active_to_poly_contact2 = active_top + self.poly_contact_to_gate + 0.5 * self.route_layer_width
|
||||
self.inputA_yoffset = max(active_contact_to_poly_contact,
|
||||
active_to_poly_contact,
|
||||
active_to_poly_contact2)
|
||||
|
||||
self.route_input_gate(self.pmos1_inst,
|
||||
self.nmos1_inst,
|
||||
self.inputA_yoffset,
|
||||
"A",
|
||||
position="left")
|
||||
apin = self.route_input_gate(self.pmos1_inst,
|
||||
self.nmos1_inst,
|
||||
self.inputA_yoffset,
|
||||
"A",
|
||||
position="left")
|
||||
|
||||
# Put B right on the well line
|
||||
self.inputB_yoffset = self.inputA_yoffset + non_contact_pitch
|
||||
self.route_input_gate(self.pmos2_inst,
|
||||
self.nmos2_inst,
|
||||
self.inputB_yoffset,
|
||||
"B",
|
||||
position="center")
|
||||
self.inputB_yoffset = self.inputA_yoffset + self.m3_pitch
|
||||
bpin = self.route_input_gate(self.pmos2_inst,
|
||||
self.nmos2_inst,
|
||||
self.inputB_yoffset,
|
||||
"B",
|
||||
position="center")
|
||||
|
||||
self.inputC_yoffset = self.inputB_yoffset + non_contact_pitch
|
||||
self.route_input_gate(self.pmos3_inst,
|
||||
self.nmos3_inst,
|
||||
self.inputC_yoffset,
|
||||
"C",
|
||||
position="right")
|
||||
self.inputC_yoffset = self.inputB_yoffset + self.m3_pitch
|
||||
cpin = self.route_input_gate(self.pmos3_inst,
|
||||
self.nmos3_inst,
|
||||
self.inputC_yoffset,
|
||||
"C",
|
||||
position="right")
|
||||
|
||||
if OPTS.tech_name == "sky130":
|
||||
self.add_enclosure([apin, bpin, cpin], "npc", drc("npc_enclose_poly"))
|
||||
|
||||
def route_output(self):
|
||||
""" Route the Z output """
|
||||
|
|
|
|||
|
|
@ -195,22 +195,25 @@ class pnor2(pgate.pgate):
|
|||
self.inputB_yoffset = bottom_pin_offset + self.m1_nonpref_pitch
|
||||
self.inputA_yoffset = self.inputB_yoffset + self.m1_nonpref_pitch
|
||||
|
||||
self.route_input_gate(self.pmos2_inst,
|
||||
self.nmos2_inst,
|
||||
self.inputB_yoffset,
|
||||
"B",
|
||||
position="right",
|
||||
directions=("V", "V"))
|
||||
bpin = self.route_input_gate(self.pmos2_inst,
|
||||
self.nmos2_inst,
|
||||
self.inputB_yoffset,
|
||||
"B",
|
||||
position="right",
|
||||
directions=("V", "V"))
|
||||
|
||||
# This will help with the wells and the input/output placement
|
||||
self.route_input_gate(self.pmos1_inst,
|
||||
self.nmos1_inst,
|
||||
self.inputA_yoffset,
|
||||
"A",
|
||||
directions=("V", "V"))
|
||||
apin = self.route_input_gate(self.pmos1_inst,
|
||||
self.nmos1_inst,
|
||||
self.inputA_yoffset,
|
||||
"A",
|
||||
directions=("V", "V"))
|
||||
|
||||
self.output_yoffset = self.inputA_yoffset + self.m1_nonpref_pitch
|
||||
|
||||
if OPTS.tech_name == "sky130":
|
||||
self.add_enclosure([apin, bpin], "npc", drc("npc_enclose_poly"))
|
||||
|
||||
def route_output(self):
|
||||
""" Route the Z output """
|
||||
# PMOS2 (right) drain
|
||||
|
|
|
|||
|
|
@ -38,16 +38,10 @@ class precharge(design.design):
|
|||
|
||||
if self.bitcell_bl_pin.layer == "m1":
|
||||
self.bitline_layer = "m1"
|
||||
if "li" in layer:
|
||||
self.en_layer = "li"
|
||||
else:
|
||||
self.en_layer = "m2"
|
||||
self.en_layer = "m2"
|
||||
else:
|
||||
self.bitline_layer = "m2"
|
||||
if "li" in layer:
|
||||
self.en_layer = "li"
|
||||
else:
|
||||
self.en_layer = "m1"
|
||||
self.en_layer = "m1"
|
||||
|
||||
# Creates the netlist and layout
|
||||
# Since it has variable height, it is not a pgate.
|
||||
|
|
@ -196,7 +190,7 @@ class precharge(design.design):
|
|||
pin_offset = self.lower_pmos_inst.get_pin("G").lr()
|
||||
# This is an extra space down for some techs with contact to active spacing
|
||||
contact_space = max(self.poly_space,
|
||||
self.contact_to_gate) + 0.5 * contact.poly_contact.first_layer_height
|
||||
self.poly_contact_to_gate) + 0.5 * contact.poly_contact.first_layer_height
|
||||
offset = pin_offset - vector(0, contact_space)
|
||||
self.add_via_stack_center(from_layer="poly",
|
||||
to_layer=self.en_layer,
|
||||
|
|
|
|||
|
|
@ -196,7 +196,7 @@ class ptx(design.design):
|
|||
# This is the spacing between the poly gates
|
||||
self.min_poly_pitch = self.poly_space + self.poly_width
|
||||
self.contacted_poly_pitch = self.poly_space + contact.poly_contact.width
|
||||
self.contact_pitch = 2 * self.contact_to_gate + self.poly_width + self.contact_width
|
||||
self.contact_pitch = 2 * self.active_contact_to_gate + self.poly_width + self.contact_width
|
||||
self.poly_pitch = max(self.min_poly_pitch,
|
||||
self.contacted_poly_pitch,
|
||||
self.contact_pitch)
|
||||
|
|
@ -206,7 +206,7 @@ class ptx(design.design):
|
|||
# Active width is determined by enclosure on both ends and contacted pitch,
|
||||
# at least one poly and n-1 poly pitches
|
||||
self.active_width = 2 * self.end_to_contact + self.active_contact.width \
|
||||
+ 2 * self.contact_to_gate + self.poly_width + (self.mults - 1) * self.poly_pitch
|
||||
+ 2 * self.active_contact_to_gate + self.poly_width + (self.mults - 1) * self.poly_pitch
|
||||
|
||||
# Active height is just the transistor width
|
||||
self.active_height = self.tx_width
|
||||
|
|
@ -321,7 +321,7 @@ class ptx(design.design):
|
|||
"""
|
||||
# poly is one contacted spacing from the end and down an extension
|
||||
poly_offset = self.contact_offset \
|
||||
+ vector(0.5 * self.active_contact.width + 0.5 * self.poly_width + self.contact_to_gate, 0)
|
||||
+ vector(0.5 * self.active_contact.width + 0.5 * self.poly_width + self.active_contact_to_gate, 0)
|
||||
|
||||
# poly_positions are the bottom center of the poly gates
|
||||
self.poly_positions = []
|
||||
|
|
|
|||
|
|
@ -160,11 +160,11 @@ class pwrite_driver(design.design):
|
|||
track_xoff = self.get_m2_track(1)
|
||||
|
||||
din_loc = self.din_inst.get_pin("A").center()
|
||||
self.add_via_stack("m1", "m2", din_loc)
|
||||
self.add_via_stack_center("m1", "m2", din_loc)
|
||||
din_track = vector(track_xoff,din_loc.y)
|
||||
|
||||
br_in = self.br_inst.get_pin("in").center()
|
||||
self.add_via_stack("m1", "m2", br_in)
|
||||
self.add_via_stack_center("m1", "m2", br_in)
|
||||
br_track = vector(track_xoff,br_in.y)
|
||||
|
||||
din_in = vector(track_xoff,0)
|
||||
|
|
@ -181,11 +181,11 @@ class pwrite_driver(design.design):
|
|||
track_xoff = self.get_m4_track(self.din_bar_track)
|
||||
|
||||
din_bar_in = self.din_inst.get_pin("Z").center()
|
||||
self.add_via_stack("m1", "m3", din_bar_in)
|
||||
self.add_via_stack_center("m1", "m3", din_bar_in)
|
||||
din_bar_track = vector(track_xoff,din_bar_in.y)
|
||||
|
||||
bl_in = self.bl_inst.get_pin("in").center()
|
||||
self.add_via_stack("m1", "m3", bl_in)
|
||||
self.add_via_stack_center("m1", "m3", bl_in)
|
||||
bl_track = vector(track_xoff,bl_in.y)
|
||||
|
||||
din_in = vector(track_xoff,0)
|
||||
|
|
@ -204,15 +204,15 @@ class pwrite_driver(design.design):
|
|||
# This M2 pitch is a hack since the A and Z pins align horizontally
|
||||
en_bar_loc = self.en_inst.get_pin("Z").uc()
|
||||
en_bar_track = vector(track_xoff, en_bar_loc.y)
|
||||
self.add_via_stack("m1", "m3", en_bar_loc)
|
||||
self.add_via_stack_center("m1", "m3", en_bar_loc)
|
||||
|
||||
# This is a U route to the right down then left
|
||||
bl_en_loc = self.bl_inst.get_pin("en_bar").center()
|
||||
bl_en_track = vector(track_xoff, bl_en_loc.y)
|
||||
self.add_via_stack("m1", "m3", bl_en_loc)
|
||||
self.add_via_stack_center("m1", "m3", bl_en_loc)
|
||||
br_en_loc = self.br_inst.get_pin("en_bar").center()
|
||||
br_en_track = vector(track_xoff, bl_en_loc.y)
|
||||
self.add_via_stack("m1", "m3", br_en_loc)
|
||||
self.add_via_stack_center("m1", "m3", br_en_loc)
|
||||
|
||||
|
||||
# L shape
|
||||
|
|
@ -237,21 +237,21 @@ class pwrite_driver(design.design):
|
|||
|
||||
en_loc = self.en_inst.get_pin("A").center()
|
||||
en_rail = vector(en_loc.x, vdd_yloc)
|
||||
self.add_via_stack("m1", "m2", en_loc)
|
||||
self.add_via_stack_center("m1", "m2", en_loc)
|
||||
self.add_path("m2", [en_loc, en_rail])
|
||||
self.add_via_stack("m2", "m3", en_rail)
|
||||
self.add_via_stack_center("m2", "m3", en_rail)
|
||||
|
||||
# Start point in the track on the pin rail
|
||||
en_track = vector(track_xoff, vdd_yloc)
|
||||
self.add_via_stack("m3", "m4", en_track)
|
||||
self.add_via_stack_center("m3", "m4", en_track)
|
||||
|
||||
# This is a U route to the right down then left
|
||||
bl_en_loc = self.bl_inst.get_pin("en").center()
|
||||
bl_en_track = vector(track_xoff, bl_en_loc.y)
|
||||
self.add_via_stack("m1", "m3", bl_en_loc)
|
||||
self.add_via_stack_center("m1", "m3", bl_en_loc)
|
||||
br_en_loc = self.br_inst.get_pin("en").center()
|
||||
br_en_track = vector(track_xoff, bl_en_loc.y)
|
||||
self.add_via_stack("m1", "m3", br_en_loc)
|
||||
self.add_via_stack_center("m1", "m3", br_en_loc)
|
||||
|
||||
# U shape
|
||||
self.add_wire(self.m3_stack,
|
||||
|
|
|
|||
|
|
@ -70,96 +70,34 @@ class sram_1bank(sram_base):
|
|||
# If a horizontal channel, they rely on the vertical channel non-preferred (contacted) pitch.
|
||||
# If a vertical channel, they rely on the horizontal channel non-preferred (contacted) pitch.
|
||||
# So, m3 non-pref pitch means that this is routed on the m2 layer.
|
||||
if self.write_size:
|
||||
self.data_bus_gap = self.m4_nonpref_pitch * 2
|
||||
self.data_bus_size = self.m4_nonpref_pitch * (self.word_size + self.num_spare_cols) + self.data_bus_gap
|
||||
self.wmask_bus_gap = self.m2_nonpref_pitch * 2
|
||||
self.wmask_bus_size = self.m2_nonpref_pitch * (max(self.num_wmasks + 1, self.col_addr_size + 1)) + self.wmask_bus_gap
|
||||
if self.num_spare_cols:
|
||||
self.spare_wen_bus_gap = self.m2_nonpref_pitch * 2
|
||||
self.spare_wen_bus_size = self.m2_nonpref_pitch * (max(self.num_spare_cols + 1, self.col_addr_size + 1)) + self.spare_wen_bus_gap
|
||||
else:
|
||||
self.spare_wen_bus_size = 0
|
||||
|
||||
elif self.num_spare_cols and not self.write_size:
|
||||
self.data_bus_gap = self.m4_nonpref_pitch * 2
|
||||
self.data_bus_size = self.m4_nonpref_pitch * (self.word_size + self.num_spare_cols) + self.data_bus_gap
|
||||
self.spare_wen_bus_gap = self.m2_nonpref_pitch * 2
|
||||
self.spare_wen_bus_size = self.m2_nonpref_pitch * (max(self.num_spare_cols + 1, self.col_addr_size + 1)) + self.spare_wen_bus_gap
|
||||
self.data_bus_gap = self.m4_nonpref_pitch * 2
|
||||
|
||||
else:
|
||||
self.data_bus_gap = self.m3_nonpref_pitch * 2
|
||||
self.data_bus_size = self.m3_nonpref_pitch * (max(self.word_size + 1, self.col_addr_size + 1)) + self.data_bus_gap
|
||||
|
||||
self.col_addr_bus_gap = self.m2_nonpref_pitch * 2
|
||||
self.col_addr_bus_size = self.m2_nonpref_pitch * (self.col_addr_size) + self.col_addr_bus_gap
|
||||
# Spare wen are on a separate layer so not included
|
||||
# Start with 1 track minimum
|
||||
self.data_bus_size = [1] * len(self.all_ports)
|
||||
for port in self.all_ports:
|
||||
# All ports need the col addr flops
|
||||
self.data_bus_size[port] += self.col_addr_size
|
||||
# Write ports need the data input flops and write mask flops
|
||||
if port in self.write_ports:
|
||||
self.data_bus_size[port] += self.num_wmasks + self.word_size
|
||||
# This is for the din pins that get routed in the same channel
|
||||
# when we have dout and din together
|
||||
if port in self.readwrite_ports:
|
||||
self.data_bus_size[port] += self.word_size
|
||||
# Convert to length
|
||||
self.data_bus_size[port] *= self.m4_nonpref_pitch
|
||||
# Add the gap in unit length
|
||||
self.data_bus_size[port] += self.data_bus_gap
|
||||
|
||||
# Port 0
|
||||
port = 0
|
||||
|
||||
if port in self.write_ports:
|
||||
if self.write_size:
|
||||
bus_size = max(self.wmask_bus_size, self.spare_wen_bus_size)
|
||||
# Add the write mask flops below the write mask AND array.
|
||||
wmask_pos[port] = vector(self.bank.bank_array_ll.x,
|
||||
- bus_size - self.dff.height)
|
||||
self.wmask_dff_insts[port].place(wmask_pos[port])
|
||||
|
||||
# Add the data flops below the write mask flops.
|
||||
data_pos[port] = vector(self.bank.bank_array_ll.x,
|
||||
- self.data_bus_size - bus_size - 2 * self.dff.height)
|
||||
self.data_dff_insts[port].place(data_pos[port])
|
||||
|
||||
#Add spare write enable flops to the right of write mask flops
|
||||
if self.num_spare_cols:
|
||||
spare_wen_pos[port] = vector(self.bank.bank_array_ll.x + self.wmask_dff_insts[port].width + self.bank.m2_gap,
|
||||
- bus_size - self.dff.height)
|
||||
self.spare_wen_dff_insts[port].place(spare_wen_pos[port])
|
||||
|
||||
elif self.num_spare_cols and not self.write_size:
|
||||
# Add spare write enable flops below bank (lower right)
|
||||
spare_wen_pos[port] = vector(self.bank.bank_array_ll.x,
|
||||
- self.spare_wen_bus_size - self.dff.height)
|
||||
self.spare_wen_dff_insts[port].place(spare_wen_pos[port])
|
||||
|
||||
# Add the data flops below the spare write enable flops.
|
||||
data_pos[port] = vector(self.bank.bank_array_ll.x,
|
||||
- self.data_bus_size - self.spare_wen_bus_size - 2 * self.dff.height)
|
||||
self.data_dff_insts[port].place(data_pos[port])
|
||||
|
||||
else:
|
||||
# Add the data flops below the bank to the right of the lower-left of bank array
|
||||
# This relies on the lower-left of the array of the bank
|
||||
# decoder in upper left, bank in upper right, sensing in lower right.
|
||||
# These flops go below the sensing and leave a gap to channel route to the
|
||||
# sense amps.
|
||||
data_pos[port] = vector(self.bank.bank_array_ll.x,
|
||||
-self.data_bus_size - self.dff.height)
|
||||
self.data_dff_insts[port].place(data_pos[port])
|
||||
|
||||
else:
|
||||
wmask_pos[port] = vector(self.bank.bank_array_ll.x, 0)
|
||||
data_pos[port] = vector(self.bank.bank_array_ll.x, 0)
|
||||
spare_wen_pos[port] = vector(self.bank.bank_array_ll.x, 0)
|
||||
|
||||
# Add the col address flops below the bank to the left of the lower-left of bank array
|
||||
if self.col_addr_dff:
|
||||
if self.write_size:
|
||||
col_addr_pos[port] = vector(self.bank.bank_array_ll.x - self.col_addr_dff_insts[port].width - self.bank.m2_gap,
|
||||
-bus_size - self.col_addr_dff_insts[port].height)
|
||||
elif self.num_spare_cols and not self.write_size:
|
||||
col_addr_pos[port] = vector(self.bank.bank_array_ll.x - self.col_addr_dff_insts[port].width - self.bank.m2_gap,
|
||||
-self.spare_wen_bus_size - self.col_addr_dff_insts[port].height)
|
||||
else:
|
||||
col_addr_pos[port] = vector(self.bank.bank_array_ll.x - self.col_addr_dff_insts[port].width - self.bank.m2_gap,
|
||||
-self.data_bus_size - self.col_addr_dff_insts[port].height)
|
||||
self.col_addr_dff_insts[port].place(col_addr_pos[port])
|
||||
else:
|
||||
col_addr_pos[port] = vector(self.bank.bank_array_ll.x, 0)
|
||||
|
||||
# This includes 2 M2 pitches for the row addr clock line.
|
||||
# The delay line is aligned with the bitcell array while the control logic is aligned with the port_data
|
||||
# using the control_logic_center value.
|
||||
control_pos[port] = vector(-self.control_logic_insts[port].width - 2 * self.m2_pitch,
|
||||
self.bank.bank_array_ll.y - self.control_logic_insts[port].mod.control_logic_center.y - 2 * self.bank.m2_gap)
|
||||
self.bank.bank_array_ll.y - self.control_logic_insts[port].mod.control_logic_center.y)
|
||||
self.control_logic_insts[port].place(control_pos[port])
|
||||
|
||||
# The row address bits are placed above the control logic aligned on the right.
|
||||
|
|
@ -169,70 +107,56 @@ class sram_1bank(sram_base):
|
|||
row_addr_pos[port] = vector(x_offset, y_offset)
|
||||
self.row_addr_dff_insts[port].place(row_addr_pos[port])
|
||||
|
||||
# Add the col address flops below the bank to the right of the control logic
|
||||
x_offset = self.control_logic_insts[port].rx() + self.dff.width
|
||||
y_offset = - self.data_bus_size[port] - self.dff.height
|
||||
if self.col_addr_dff:
|
||||
col_addr_pos[port] = vector(x_offset,
|
||||
y_offset)
|
||||
self.col_addr_dff_insts[port].place(col_addr_pos[port])
|
||||
x_offset = self.col_addr_dff_insts[port].rx()
|
||||
else:
|
||||
col_addr_pos[port] = vector(x_offset, 0)
|
||||
|
||||
if port in self.write_ports:
|
||||
if self.write_size:
|
||||
# Add the write mask flops below the write mask AND array.
|
||||
wmask_pos[port] = vector(x_offset,
|
||||
y_offset)
|
||||
self.wmask_dff_insts[port].place(wmask_pos[port])
|
||||
x_offset = self.wmask_dff_insts[port].rx()
|
||||
|
||||
# Add the data flops below the write mask flops.
|
||||
data_pos[port] = vector(x_offset,
|
||||
y_offset)
|
||||
self.data_dff_insts[port].place(data_pos[port])
|
||||
x_offset = self.data_dff_insts[port].rx()
|
||||
|
||||
# Add spare write enable flops to the right of data flops since the spare columns
|
||||
# will be on the right
|
||||
if self.num_spare_cols:
|
||||
spare_wen_pos[port] = vector(x_offset,
|
||||
y_offset)
|
||||
self.spare_wen_dff_insts[port].place(spare_wen_pos[port])
|
||||
x_offset = self.spare_wen_dff_insts[port].rx()
|
||||
|
||||
else:
|
||||
wmask_pos[port] = vector(x_offset, y_offset)
|
||||
data_pos[port] = vector(x_offset, y_offset)
|
||||
spare_wen_pos[port] = vector(x_offset, y_offset)
|
||||
|
||||
if len(self.all_ports)>1:
|
||||
# Port 1
|
||||
port = 1
|
||||
|
||||
if port in self.write_ports:
|
||||
if self.write_size:
|
||||
bus_size = max(self.wmask_bus_size, self.spare_wen_bus_size)
|
||||
# Add the write mask flops above the write mask AND array.
|
||||
wmask_pos[port] = vector(self.bank.bank_array_ur.x - self.wmask_dff_insts[port].width,
|
||||
self.bank.height + bus_size + self.dff.height)
|
||||
self.wmask_dff_insts[port].place(wmask_pos[port], mirror="MX")
|
||||
|
||||
# Add the data flops above the write mask flops
|
||||
data_pos[port] = vector(self.bank.bank_array_ur.x - self.data_dff_insts[port].width,
|
||||
self.bank.height + bus_size + self.data_bus_size + 2 * self.dff.height)
|
||||
self.data_dff_insts[port].place(data_pos[port], mirror="MX")
|
||||
|
||||
if self.num_spare_cols:
|
||||
spare_wen_pos[port] = vector(self.bank.bank_array_ur.x - self.wmask_dff_insts[port].width
|
||||
- self.spare_wen_dff_insts[port].width - self.bank.m2_gap,
|
||||
self.bank.height + bus_size + self.dff.height)
|
||||
self.spare_wen_dff_insts[port].place(spare_wen_pos[port], mirror="MX")
|
||||
|
||||
# Place dffs when spare cols is enabled
|
||||
elif self.num_spare_cols and not self.write_size:
|
||||
# Spare wen flops on the upper right, below data flops
|
||||
spare_wen_pos[port] = vector(self.bank.bank_array_ur.x - self.spare_wen_dff_insts[port].width,
|
||||
self.bank.height + self.spare_wen_bus_size + self.dff.height)
|
||||
self.spare_wen_dff_insts[port].place(spare_wen_pos[port], mirror="MX")
|
||||
# Add the data flops above the spare write enable flops
|
||||
data_pos[port] = vector(self.bank.bank_array_ur.x - self.data_dff_insts[port].width,
|
||||
self.bank.height + self.spare_wen_bus_size + self.data_bus_size + 2 * self.dff.height)
|
||||
self.data_dff_insts[port].place(data_pos[port], mirror="MX")
|
||||
|
||||
else:
|
||||
# Add the data flops above the bank to the left of the upper-right of bank array
|
||||
# This relies on the upper-right of the array of the bank
|
||||
# decoder in upper left, bank in upper right, sensing in lower right.
|
||||
# These flops go below the sensing and leave a gap to channel route to the
|
||||
# sense amps.
|
||||
data_pos[port] = vector(self.bank.bank_array_ur.x - self.data_dff_insts[port].width,
|
||||
self.bank.height + self.data_bus_size + self.dff.height)
|
||||
self.data_dff_insts[port].place(data_pos[port], mirror="MX")
|
||||
|
||||
# Add the col address flops above the bank to the right of the upper-right of bank array
|
||||
if self.col_addr_dff:
|
||||
if self.write_size:
|
||||
col_addr_pos[port] = vector(self.bank.bank_array_ur.x + self.bank.m2_gap,
|
||||
self.bank.height + bus_size + self.dff.height)
|
||||
elif self.num_spare_cols and not self.write_size:
|
||||
col_addr_pos[port] = vector(self.bank.bank_array_ur.x + self.bank.m2_gap,
|
||||
self.bank.height + self.spare_wen_bus_size + self.dff.height)
|
||||
else:
|
||||
col_addr_pos[port] = vector(self.bank.bank_array_ur.x + self.bank.m2_gap,
|
||||
self.bank.height + self.data_bus_size + self.dff.height)
|
||||
self.col_addr_dff_insts[port].place(col_addr_pos[port], mirror="MX")
|
||||
else:
|
||||
col_addr_pos[port] = self.bank_inst.ur()
|
||||
|
||||
# This includes 2 M2 pitches for the row addr clock line
|
||||
# The delay line is aligned with the bitcell array while the control logic is aligned with the port_data
|
||||
# using the control_logic_center value.
|
||||
control_pos[port] = vector(self.bank_inst.rx() + self.control_logic_insts[port].width + 2 * self.m2_pitch,
|
||||
self.bank.bank_array_ur.y + self.control_logic_insts[port].height - \
|
||||
(self.control_logic_insts[port].height - self.control_logic_insts[port].mod.control_logic_center.y)
|
||||
+ 2 * self.bank.m2_gap)
|
||||
self.bank.bank_array_ur.y
|
||||
+ self.control_logic_insts[port].height
|
||||
- self.control_logic_insts[port].height
|
||||
+ self.control_logic_insts[port].mod.control_logic_center.y)
|
||||
self.control_logic_insts[port].place(control_pos[port], mirror="XY")
|
||||
|
||||
# The row address bits are placed above the control logic aligned on the left.
|
||||
|
|
@ -242,6 +166,42 @@ class sram_1bank(sram_base):
|
|||
row_addr_pos[port] = vector(x_offset, y_offset)
|
||||
self.row_addr_dff_insts[port].place(row_addr_pos[port], mirror="XY")
|
||||
|
||||
# Add the col address flops below the bank to the right of the control logic
|
||||
x_offset = self.control_logic_insts[port].lx() - 2 * self.dff.width
|
||||
y_offset = self.bank.height + self.data_bus_size[port] + self.dff.height
|
||||
if self.col_addr_dff:
|
||||
col_addr_pos[port] = vector(x_offset - self.col_addr_dff_insts[port].width,
|
||||
y_offset)
|
||||
self.col_addr_dff_insts[port].place(col_addr_pos[port], mirror="MX")
|
||||
x_offset = self.col_addr_dff_insts[port].lx()
|
||||
else:
|
||||
col_addr_pos[port] = vector(x_offset, y_offset)
|
||||
|
||||
if port in self.write_ports:
|
||||
# Add spare write enable flops to the right of the data flops since the spare
|
||||
# columns will be on the left
|
||||
if self.num_spare_cols:
|
||||
spare_wen_pos[port] = vector(x_offset - self.spare_wen_dff_insts[port].width,
|
||||
y_offset)
|
||||
self.spare_wen_dff_insts[port].place(spare_wen_pos[port], mirror="MX")
|
||||
x_offset = self.spare_wen_dff_insts[port].lx()
|
||||
|
||||
if self.write_size:
|
||||
# Add the write mask flops below the write mask AND array.
|
||||
wmask_pos[port] = vector(x_offset - self.wmask_dff_insts[port].width,
|
||||
y_offset)
|
||||
self.wmask_dff_insts[port].place(wmask_pos[port], mirror="MX")
|
||||
x_offset = self.wmask_dff_insts[port].lx()
|
||||
|
||||
# Add the data flops below the write mask flops.
|
||||
data_pos[port] = vector(x_offset - self.data_dff_insts[port].width,
|
||||
y_offset)
|
||||
self.data_dff_insts[port].place(data_pos[port], mirror="MX")
|
||||
else:
|
||||
wmask_pos[port] = vector(x_offset, y_offset)
|
||||
data_pos[port] = vector(x_offset, y_offset)
|
||||
spare_wen_pos[port] = vector(x_offset, y_offset)
|
||||
|
||||
def add_layout_pins(self):
|
||||
"""
|
||||
Add the top-level pins for a single bank SRAM with control.
|
||||
|
|
@ -250,7 +210,6 @@ class sram_1bank(sram_base):
|
|||
lowest_coord = self.find_lowest_coords()
|
||||
bbox = [lowest_coord, highest_coord]
|
||||
|
||||
|
||||
for port in self.all_ports:
|
||||
# Depending on the port, use the bottom/top or left/right sides
|
||||
# Port 0 is left/bottom
|
||||
|
|
@ -282,10 +241,35 @@ class sram_1bank(sram_base):
|
|||
"clk",
|
||||
"clk{}".format(port))
|
||||
|
||||
# Data output pins go to BOTTOM/TOP
|
||||
if port in self.read_ports:
|
||||
# Data input pins go to BOTTOM/TOP
|
||||
din_ports = []
|
||||
if port in self.write_ports:
|
||||
for bit in range(self.word_size + self.num_spare_cols):
|
||||
if OPTS.perimeter_pins:
|
||||
p = self.add_perimeter_pin(name="din{0}[{1}]".format(port, bit),
|
||||
pin=self.data_dff_insts[port].get_pin("din_{0}".format(bit)),
|
||||
side=bottom_or_top,
|
||||
bbox=bbox)
|
||||
din_ports.append(p)
|
||||
else:
|
||||
self.copy_layout_pin(self.data_dff_insts[port],
|
||||
"din_{}".format(bit),
|
||||
"din{0}[{1}]".format(port, bit))
|
||||
|
||||
# Data output pins go to BOTTOM/TOP
|
||||
if port in self.readwrite_ports and OPTS.perimeter_pins:
|
||||
for bit in range(self.word_size + self.num_spare_cols):
|
||||
# This should be routed next to the din pin
|
||||
p = din_ports[bit]
|
||||
self.add_layout_pin_rect_center(text="dout{0}[{1}]".format(port, bit),
|
||||
layer=p.layer,
|
||||
offset=p.center() + vector(self.m3_pitch, 0),
|
||||
width=p.width(),
|
||||
height=p.height())
|
||||
elif port in self.read_ports:
|
||||
for bit in range(self.word_size + self.num_spare_cols):
|
||||
if OPTS.perimeter_pins:
|
||||
# This should have a clear route to the perimeter if there are no din routes
|
||||
self.add_perimeter_pin(name="dout{0}[{1}]".format(port, bit),
|
||||
pin=self.bank_inst.get_pin("dout{0}_{1}".format(port, bit)),
|
||||
side=bottom_or_top,
|
||||
|
|
@ -294,6 +278,8 @@ class sram_1bank(sram_base):
|
|||
self.copy_layout_pin(self.bank_inst,
|
||||
"dout{0}_{1}".format(port, bit),
|
||||
"dout{0}[{1}]".format(port, bit))
|
||||
|
||||
|
||||
|
||||
# Lower address bits go to BOTTOM/TOP
|
||||
for bit in range(self.col_addr_size):
|
||||
|
|
@ -319,19 +305,6 @@ class sram_1bank(sram_base):
|
|||
"din_{}".format(bit),
|
||||
"addr{0}[{1}]".format(port, bit + self.col_addr_size))
|
||||
|
||||
# Data input pins go to BOTTOM/TOP
|
||||
if port in self.write_ports:
|
||||
for bit in range(self.word_size + self.num_spare_cols):
|
||||
if OPTS.perimeter_pins:
|
||||
self.add_perimeter_pin(name="din{0}[{1}]".format(port, bit),
|
||||
pin=self.data_dff_insts[port].get_pin("din_{}".format(bit)),
|
||||
side=bottom_or_top,
|
||||
bbox=bbox)
|
||||
else:
|
||||
self.copy_layout_pin(self.data_dff_insts[port],
|
||||
"din_{}".format(bit),
|
||||
"din{0}[{1}]".format(port, bit))
|
||||
|
||||
# Write mask pins go to BOTTOM/TOP
|
||||
if port in self.write_ports:
|
||||
if self.write_size:
|
||||
|
|
@ -370,17 +343,76 @@ class sram_1bank(sram_base):
|
|||
|
||||
self.route_row_addr_dff()
|
||||
|
||||
if self.col_addr_dff:
|
||||
self.route_col_addr_dff()
|
||||
|
||||
self.route_data_dff()
|
||||
|
||||
if self.write_size:
|
||||
self.route_wmask_dff()
|
||||
for port in self.all_ports:
|
||||
self.route_dff(port)
|
||||
|
||||
if self.num_spare_cols:
|
||||
self.route_spare_wen_dff()
|
||||
def route_dff(self, port):
|
||||
|
||||
route_map = []
|
||||
|
||||
# column mux dff
|
||||
if self.col_addr_size > 0:
|
||||
dff_names = ["dout_{}".format(x) for x in range(self.col_addr_size)]
|
||||
dff_pins = [self.col_addr_dff_insts[port].get_pin(x) for x in dff_names]
|
||||
bank_names = ["addr{0}_{1}".format(port, x) for x in range(self.col_addr_size)]
|
||||
bank_pins = [self.bank_inst.get_pin(x) for x in bank_names]
|
||||
route_map.extend(list(zip(bank_pins, dff_pins)))
|
||||
|
||||
# wmask dff
|
||||
if self.num_wmasks > 0 and port in self.write_ports:
|
||||
dff_names = ["dout_{}".format(x) for x in range(self.num_wmasks)]
|
||||
dff_pins = [self.wmask_dff_insts[port].get_pin(x) for x in dff_names]
|
||||
bank_names = ["bank_wmask{0}_{1}".format(port, x) for x in range(self.num_wmasks)]
|
||||
bank_pins = [self.bank_inst.get_pin(x) for x in bank_names]
|
||||
route_map.extend(list(zip(bank_pins, dff_pins)))
|
||||
|
||||
if port in self.write_ports:
|
||||
# synchronized inputs from data dff
|
||||
dff_names = ["dout_{}".format(x) for x in range(self.word_size + self.num_spare_cols)]
|
||||
dff_pins = [self.data_dff_insts[port].get_pin(x) for x in dff_names]
|
||||
bank_names = ["din{0}_{1}".format(port, x) for x in range(self.word_size + self.num_spare_cols)]
|
||||
bank_pins = [self.bank_inst.get_pin(x) for x in bank_names]
|
||||
route_map.extend(list(zip(bank_pins, dff_pins)))
|
||||
|
||||
if port in self.readwrite_ports and OPTS.perimeter_pins:
|
||||
# outputs from sense amp
|
||||
# These are the output pins which had their pin placed on the perimeter, so route from the
|
||||
# sense amp which should not align with write driver input
|
||||
sram_names = ["dout{0}[{1}]".format(port, x) for x in range(self.word_size + self.num_spare_cols)]
|
||||
sram_pins = [self.get_pin(x) for x in sram_names]
|
||||
bank_names = ["dout{0}_{1}".format(port, x) for x in range(self.word_size + self.num_spare_cols)]
|
||||
bank_pins = [self.bank_inst.get_pin(x) for x in bank_names]
|
||||
route_map.extend(list(zip(bank_pins, sram_pins)))
|
||||
|
||||
if self.num_wmasks > 0 and port in self.write_ports:
|
||||
layer_stack = self.m3_stack
|
||||
else:
|
||||
layer_stack = self.m1_stack
|
||||
|
||||
if port == 0:
|
||||
offset = vector(self.control_logic_insts[port].rx() + self.dff.width,
|
||||
- self.data_bus_size[port] + 2 * self.m1_pitch)
|
||||
else:
|
||||
offset = vector(0,
|
||||
self.bank.height + 2 * self.m1_space)
|
||||
|
||||
if len(route_map) > 0:
|
||||
self.create_horizontal_channel_route(netlist=route_map,
|
||||
offset=offset,
|
||||
layer_stack=layer_stack)
|
||||
|
||||
# Route these separately because sometimes the pin pitch on the write driver is too narrow for M3 (FreePDK45)
|
||||
# spare wen dff
|
||||
if self.num_spare_cols > 0 and port in self.write_ports:
|
||||
dff_names = ["dout_{}".format(x) for x in range(self.num_spare_cols)]
|
||||
dff_pins = [self.spare_wen_dff_insts[port].get_pin(x) for x in dff_names]
|
||||
bank_names = ["bank_spare_wen{0}_{1}".format(port, x) for x in range(self.num_spare_cols)]
|
||||
bank_pins = [self.bank_inst.get_pin(x) for x in bank_names]
|
||||
route_map = zip(bank_pins, dff_pins)
|
||||
self.create_horizontal_channel_route(netlist=route_map,
|
||||
offset=offset,
|
||||
layer_stack=self.m1_stack)
|
||||
|
||||
def route_clk(self):
|
||||
""" Route the clock network """
|
||||
|
||||
|
|
@ -423,8 +455,7 @@ class sram_1bank(sram_base):
|
|||
mid_pos = vector(clk_steiner_pos.x, dff_clk_pos.y)
|
||||
self.add_wire(self.m2_stack[::-1],
|
||||
[dff_clk_pos, mid_pos, clk_steiner_pos])
|
||||
|
||||
if port in self.write_ports:
|
||||
elif port in self.write_ports:
|
||||
data_dff_clk_pin = self.data_dff_insts[port].get_pin("clk")
|
||||
data_dff_clk_pos = data_dff_clk_pin.center()
|
||||
mid_pos = vector(clk_steiner_pos.x, data_dff_clk_pos.y)
|
||||
|
|
@ -436,24 +467,6 @@ class sram_1bank(sram_base):
|
|||
self.add_wire(self.m2_stack[::-1],
|
||||
[data_dff_clk_pos, mid_pos, clk_steiner_pos])
|
||||
|
||||
if self.write_size:
|
||||
wmask_dff_clk_pin = self.wmask_dff_insts[port].get_pin("clk")
|
||||
wmask_dff_clk_pos = wmask_dff_clk_pin.center()
|
||||
mid_pos = vector(clk_steiner_pos.x, wmask_dff_clk_pos.y)
|
||||
# In some designs, the steiner via will be too close to the mid_pos via
|
||||
# so make the wire as wide as the contacts
|
||||
self.add_path("m2", [mid_pos, clk_steiner_pos], width=max(m2_via.width, m2_via.height))
|
||||
self.add_wire(self.m2_stack[::-1], [wmask_dff_clk_pos, mid_pos, clk_steiner_pos])
|
||||
|
||||
if self.num_spare_cols:
|
||||
spare_wen_dff_clk_pin = self.spare_wen_dff_insts[port].get_pin("clk")
|
||||
spare_wen_dff_clk_pos = spare_wen_dff_clk_pin.center()
|
||||
mid_pos = vector(clk_steiner_pos.x, spare_wen_dff_clk_pos.y)
|
||||
# In some designs, the steiner via will be too close to the mid_pos via
|
||||
# so make the wire as wide as the contacts
|
||||
self.add_path("m2", [mid_pos, clk_steiner_pos], width=max(m2_via.width, m2_via.height))
|
||||
self.add_wire(self.m2_stack[::-1], [spare_wen_dff_clk_pos, mid_pos, clk_steiner_pos])
|
||||
|
||||
def route_control_logic(self):
|
||||
""" Route the control logic pins that are not inputs """
|
||||
|
||||
|
|
@ -472,7 +485,12 @@ class sram_1bank(sram_base):
|
|||
dest_pin = self.bank_inst.get_pin("rbl_bl{}".format(port))
|
||||
self.add_wire(self.m2_stack[::-1],
|
||||
[src_pin.center(), vector(src_pin.cx(), dest_pin.cy()), dest_pin.rc()])
|
||||
# self.connect_hbus(src_pin, dest_pin)
|
||||
self.add_via_stack_center(from_layer=src_pin.layer,
|
||||
to_layer="m2",
|
||||
offset=src_pin.center())
|
||||
self.add_via_stack_center(from_layer=dest_pin.layer,
|
||||
to_layer="m2",
|
||||
offset=dest_pin.center())
|
||||
|
||||
def route_row_addr_dff(self):
|
||||
""" Connect the output of the row flops to the bank pins """
|
||||
|
|
@ -485,141 +503,14 @@ class sram_1bank(sram_base):
|
|||
flop_pos = flop_pin.center()
|
||||
bank_pos = bank_pin.center()
|
||||
mid_pos = vector(bank_pos.x, flop_pos.y)
|
||||
self.add_wire(self.m2_stack[::-1],
|
||||
[flop_pos, mid_pos, bank_pos])
|
||||
self.add_via_stack_center(from_layer=flop_pin.layer,
|
||||
to_layer="m3",
|
||||
offset=flop_pos)
|
||||
|
||||
def route_col_addr_dff(self):
|
||||
""" Connect the output of the col flops to the bank pins """
|
||||
for port in self.all_ports:
|
||||
if port % 2:
|
||||
offset = self.col_addr_dff_insts[port].ll() - vector(0, self.col_addr_bus_size)
|
||||
else:
|
||||
offset = self.col_addr_dff_insts[port].ul() + vector(0, self.col_addr_bus_gap)
|
||||
|
||||
bus_names = ["addr_{}".format(x) for x in range(self.col_addr_size)]
|
||||
col_addr_bus_offsets = self.create_horizontal_bus(layer="m1",
|
||||
offset=offset,
|
||||
names=bus_names,
|
||||
length=self.col_addr_dff_insts[port].width)
|
||||
|
||||
dff_names = ["dout_{}".format(x) for x in range(self.col_addr_size)]
|
||||
data_dff_map = zip(dff_names, bus_names)
|
||||
self.connect_horizontal_bus(data_dff_map,
|
||||
self.col_addr_dff_insts[port],
|
||||
col_addr_bus_offsets)
|
||||
|
||||
bank_names = ["addr{0}_{1}".format(port, x) for x in range(self.col_addr_size)]
|
||||
data_bank_map = zip(bank_names, bus_names)
|
||||
self.connect_horizontal_bus(data_bank_map,
|
||||
self.bank_inst,
|
||||
col_addr_bus_offsets)
|
||||
|
||||
def route_data_dff(self):
|
||||
""" Connect the output of the data flops to the write driver """
|
||||
# This is where the channel will start (y-dimension at least)
|
||||
for port in self.write_ports:
|
||||
if port % 2:
|
||||
offset = self.data_dff_insts[port].ll() - vector(0, self.data_bus_size)
|
||||
else:
|
||||
offset = self.data_dff_insts[port].ul() + vector(0, self.data_bus_gap)
|
||||
|
||||
dff_names = ["dout_{}".format(x) for x in range(self.word_size + self.num_spare_cols)]
|
||||
dff_pins = [self.data_dff_insts[port].get_pin(x) for x in dff_names]
|
||||
if self.write_size or self.num_spare_cols:
|
||||
for x in dff_names:
|
||||
pin = self.data_dff_insts[port].get_pin(x)
|
||||
pin_offset = pin.center()
|
||||
self.add_via_center(layers=self.m1_stack,
|
||||
offset=pin_offset,
|
||||
directions=("V", "V"))
|
||||
self.add_via_stack_center(from_layer="m2",
|
||||
to_layer="m4",
|
||||
offset=pin_offset)
|
||||
|
||||
bank_names = ["din{0}_{1}".format(port, x) for x in range(self.word_size + self.num_spare_cols)]
|
||||
bank_pins = [self.bank_inst.get_pin(x) for x in bank_names]
|
||||
if self.write_size or self.num_spare_cols:
|
||||
for x in bank_names:
|
||||
pin = self.bank_inst.get_pin(x)
|
||||
if port % 2:
|
||||
pin_offset = pin.uc()
|
||||
else:
|
||||
pin_offset = pin.bc()
|
||||
self.add_via_stack_center(from_layer=pin.layer,
|
||||
to_layer="m4",
|
||||
offset=pin_offset)
|
||||
|
||||
route_map = list(zip(bank_pins, dff_pins))
|
||||
if self.write_size or self.num_spare_cols:
|
||||
layer_stack = self.m3_stack
|
||||
else:
|
||||
layer_stack = self.m1_stack
|
||||
|
||||
self.create_horizontal_channel_route(netlist=route_map,
|
||||
offset=offset,
|
||||
layer_stack=layer_stack)
|
||||
|
||||
def route_wmask_dff(self):
|
||||
""" Connect the output of the wmask flops to the write mask AND array """
|
||||
# This is where the channel will start (y-dimension at least)
|
||||
for port in self.write_ports:
|
||||
if port % 2:
|
||||
offset = self.wmask_dff_insts[port].ll() - vector(0, self.wmask_bus_size)
|
||||
else:
|
||||
offset = self.wmask_dff_insts[port].ul() + vector(0, self.wmask_bus_gap)
|
||||
|
||||
dff_names = ["dout_{}".format(x) for x in range(self.num_wmasks)]
|
||||
dff_pins = [self.wmask_dff_insts[port].get_pin(x) for x in dff_names]
|
||||
for x in dff_names:
|
||||
offset_pin = self.wmask_dff_insts[port].get_pin(x).center()
|
||||
self.add_via_center(layers=self.m1_stack,
|
||||
offset=offset_pin,
|
||||
directions=("V", "V"))
|
||||
|
||||
bank_names = ["bank_wmask{0}_{1}".format(port, x) for x in range(self.num_wmasks)]
|
||||
bank_pins = [self.bank_inst.get_pin(x) for x in bank_names]
|
||||
for x in bank_names:
|
||||
offset_pin = self.bank_inst.get_pin(x).center()
|
||||
self.add_via_center(layers=self.m1_stack,
|
||||
offset=offset_pin)
|
||||
|
||||
route_map = list(zip(bank_pins, dff_pins))
|
||||
self.create_horizontal_channel_route(netlist=route_map,
|
||||
offset=offset,
|
||||
layer_stack=self.m1_stack)
|
||||
def route_spare_wen_dff(self):
|
||||
""" Connect the output of the spare write enable flops to the spare write drivers """
|
||||
# This is where the channel will start (y-dimension at least)
|
||||
for port in self.write_ports:
|
||||
if port % 2:
|
||||
# for port 0
|
||||
offset = self.spare_wen_dff_insts[port].ll() - vector(0, self.spare_wen_bus_size)
|
||||
else:
|
||||
offset = self.spare_wen_dff_insts[port].ul() + vector(0, self.spare_wen_bus_gap)
|
||||
|
||||
dff_names = ["dout_{}".format(x) for x in range(self.num_spare_cols)]
|
||||
dff_pins = [self.spare_wen_dff_insts[port].get_pin(x) for x in dff_names]
|
||||
for x in dff_names:
|
||||
offset_pin = self.spare_wen_dff_insts[port].get_pin(x).center()
|
||||
self.add_via_center(layers=self.m1_stack,
|
||||
offset=offset_pin,
|
||||
directions=("V", "V"))
|
||||
|
||||
bank_names = ["bank_spare_wen{0}_{1}".format(port, x) for x in range(self.num_spare_cols)]
|
||||
bank_pins = [self.bank_inst.get_pin(x) for x in bank_names]
|
||||
for x in bank_names:
|
||||
offset_pin = self.bank_inst.get_pin(x).center()
|
||||
self.add_via_center(layers=self.m1_stack,
|
||||
offset=offset_pin)
|
||||
|
||||
route_map = list(zip(bank_pins, dff_pins))
|
||||
self.create_horizontal_channel_route(netlist=route_map,
|
||||
offset=offset,
|
||||
layer_stack=self.m1_stack)
|
||||
|
||||
self.add_path("m3", [flop_pos, mid_pos])
|
||||
self.add_via_stack_center(from_layer=bank_pin.layer,
|
||||
to_layer="m3",
|
||||
offset=mid_pos)
|
||||
self.add_path(bank_pin.layer, [mid_pos, bank_pos])
|
||||
|
||||
def add_lvs_correspondence_points(self):
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -28,9 +28,11 @@ class replica_bitcell_array_1rw_1r_test(openram_test):
|
|||
a = factory.create(module_type="replica_bitcell_array", cols=4, rows=4, left_rbl=1, right_rbl=1, bitcell_ports=[0, 1])
|
||||
self.local_check(a)
|
||||
|
||||
debug.info(2, "Testing 4x4 array for cell_1rw_1r")
|
||||
a = factory.create(module_type="replica_bitcell_array", cols=4, rows=4, left_rbl=2, right_rbl=0, bitcell_ports=[0, 1])
|
||||
self.local_check(a)
|
||||
# Sky 130 has restrictions on the symmetries
|
||||
if OPTS.tech_name != "sky130":
|
||||
debug.info(2, "Testing 4x4 array for cell_1rw_1r")
|
||||
a = factory.create(module_type="replica_bitcell_array", cols=4, rows=4, left_rbl=2, right_rbl=0, bitcell_ports=[0, 1])
|
||||
self.local_check(a)
|
||||
|
||||
globals.end_openram()
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,65 @@
|
|||
#!/usr/bin/env python3
|
||||
# See LICENSE for licensing information.
|
||||
#
|
||||
# Copyright (c) 2016-2019 Regents of the University of California and The Board
|
||||
# of Regents for the Oklahoma Agricultural and Mechanical College
|
||||
# (acting for and on behalf of Oklahoma State University)
|
||||
# All rights reserved.
|
||||
#
|
||||
import unittest
|
||||
from testutils import *
|
||||
import sys,os
|
||||
sys.path.append(os.getenv("OPENRAM_HOME"))
|
||||
import globals
|
||||
from globals import OPTS
|
||||
from sram_factory import factory
|
||||
import debug
|
||||
|
||||
@unittest.skip("SKIPPING 50_riscv_func_test")
|
||||
class riscv_func_test(openram_test):
|
||||
|
||||
def runTest(self):
|
||||
config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME"))
|
||||
globals.init_openram(config_file)
|
||||
OPTS.analytical_delay = False
|
||||
OPTS.netlist_only = True
|
||||
OPTS.trim_netlist = False
|
||||
OPTS.num_rw_ports = 1
|
||||
OPTS.num_w_ports = 0
|
||||
OPTS.num_r_ports = 1
|
||||
globals.setup_bitcell()
|
||||
|
||||
# This is a hack to reload the characterizer __init__ with the spice version
|
||||
from importlib import reload
|
||||
import characterizer
|
||||
reload(characterizer)
|
||||
from characterizer import functional, delay
|
||||
from sram_config import sram_config
|
||||
c = sram_config(word_size=32,
|
||||
write_size=8,
|
||||
num_words=256,
|
||||
num_banks=1)
|
||||
c.words_per_row=1
|
||||
c.recompute_sizes()
|
||||
debug.info(1, "Functional test RISC-V memory"
|
||||
"{} bit words, {} words, {} words per row, {} banks".format(c.word_size,
|
||||
c.num_words,
|
||||
c.words_per_row,
|
||||
c.num_banks))
|
||||
s = factory.create(module_type="sram", sram_config=c)
|
||||
tempspice = OPTS.openram_temp + "sram.sp"
|
||||
s.sp_write(tempspice)
|
||||
|
||||
corner = (OPTS.process_corners[0], OPTS.supply_voltages[0], OPTS.temperatures[0])
|
||||
f = functional(s.s, tempspice, corner)
|
||||
(fail, error) = f.run()
|
||||
self.assertTrue(fail,error)
|
||||
|
||||
globals.end_openram()
|
||||
|
||||
# instantiate a copy of the class to actually run the test
|
||||
if __name__ == "__main__":
|
||||
(OPTS, args) = globals.parse_args()
|
||||
del sys.argv[1:]
|
||||
header(__file__, OPTS.tech_name)
|
||||
unittest.main(testRunner=debugTestRunner())
|
||||
|
|
@ -0,0 +1,59 @@
|
|||
#!/usr/bin/env python3
|
||||
# See LICENSE for licensing information.
|
||||
#
|
||||
# Copyright (c) 2016-2019 Regents of the University of California and The Board
|
||||
# of Regents for the Oklahoma Agricultural and Mechanical College
|
||||
# (acting for and on behalf of Oklahoma State University)
|
||||
# All rights reserved.
|
||||
#
|
||||
import unittest
|
||||
from testutils import *
|
||||
import sys,os
|
||||
sys.path.append(os.getenv("OPENRAM_HOME"))
|
||||
import globals
|
||||
from globals import OPTS
|
||||
from sram_factory import factory
|
||||
import debug
|
||||
|
||||
@unittest.skip("SKIPPING 50_riscv_phys_test")
|
||||
class riscv_phys_test(openram_test):
|
||||
|
||||
def runTest(self):
|
||||
config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME"))
|
||||
globals.init_openram(config_file)
|
||||
from sram_config import sram_config
|
||||
|
||||
OPTS.num_rw_ports = 1
|
||||
OPTS.num_r_ports = 1
|
||||
OPTS.num_w_ports = 0
|
||||
globals.setup_bitcell()
|
||||
OPTS.route_supplies=False
|
||||
OPTS.perimeter_pins=False
|
||||
|
||||
c = sram_config(word_size=32,
|
||||
write_size=8,
|
||||
num_words=256,
|
||||
num_banks=1)
|
||||
|
||||
c.words_per_row=2
|
||||
c.recompute_sizes()
|
||||
debug.info(1, "Layout test for {}rw,{}r,{}w sram "
|
||||
"with {} bit words, {} words, {} words per "
|
||||
"row, {} banks".format(OPTS.num_rw_ports,
|
||||
OPTS.num_r_ports,
|
||||
OPTS.num_w_ports,
|
||||
c.word_size,
|
||||
c.num_words,
|
||||
c.words_per_row,
|
||||
c.num_banks))
|
||||
a = factory.create(module_type="sram", sram_config=c)
|
||||
self.local_check(a, final_verification=True)
|
||||
|
||||
globals.end_openram()
|
||||
|
||||
# instantiate a copy of the class to actually run the test
|
||||
if __name__ == "__main__":
|
||||
(OPTS, args) = globals.parse_args()
|
||||
del sys.argv[1:]
|
||||
header(__file__, OPTS.tech_name)
|
||||
unittest.main(testRunner=debugTestRunner())
|
||||
|
|
@ -48,18 +48,16 @@ class openram_test(unittest.TestCase):
|
|||
# if we ignore things like minimum metal area of pins
|
||||
drc_result=verify.run_drc(a.name, tempgds, extract=True, final_verification=final_verification)
|
||||
|
||||
# Always run LVS if we are using magic
|
||||
if "magic" in OPTS.drc_exe or drc_result == 0:
|
||||
lvs_result=verify.run_lvs(a.name, tempgds, tempspice, final_verification=final_verification)
|
||||
# We can still run LVS even if DRC fails in Magic OR Calibre
|
||||
lvs_result=verify.run_lvs(a.name, tempgds, tempspice, final_verification=final_verification)
|
||||
|
||||
# Only allow DRC to fail and LVS to pass if we are using magic
|
||||
if "magic" in OPTS.drc_exe and lvs_result == 0 and drc_result != 0:
|
||||
if lvs_result == 0 and drc_result != 0:
|
||||
# import shutil
|
||||
# zip_file = "/tmp/{0}_{1}".format(a.name, os.getpid())
|
||||
# debug.info(0, "Archiving failed files to {}.zip".format(zip_file))
|
||||
# shutil.make_archive(zip_file, 'zip', OPTS.openram_temp)
|
||||
debug.warning("DRC failed but LVS passed: {}".format(a.name))
|
||||
# self.fail("DRC failed but LVS passed: {}".format(a.name))
|
||||
self.fail("DRC failed but LVS passed: {}".format(a.name))
|
||||
elif drc_result != 0:
|
||||
# import shutil
|
||||
# zip_file = "/tmp/{0}_{1}".format(a.name, os.getpid())
|
||||
|
|
|
|||
|
|
@ -219,16 +219,14 @@ def run_drc(cell_name, gds_name, extract=False, final_verification=False):
|
|||
errors = int(re.split(r'\W+', results[2])[5])
|
||||
|
||||
# always display this summary
|
||||
if errors > 0:
|
||||
debug.error("{0}\tGeometries: {1}\tChecks: {2}\tErrors: {3}".format(cell_name,
|
||||
result_str = "{0}\tGeometries: {1}\tChecks: {2}\tErrors: {3}".format(cell_name,
|
||||
geometries,
|
||||
rulechecks,
|
||||
errors))
|
||||
errors)
|
||||
if errors > 0:
|
||||
debug.warning(result_str)
|
||||
else:
|
||||
debug.info(1, "{0}\tGeometries: {1}\tChecks: {2}\tErrors: {3}".format(cell_name,
|
||||
geometries,
|
||||
rulechecks,
|
||||
errors))
|
||||
debug.info(1, result_str)
|
||||
return errors
|
||||
|
||||
|
||||
|
|
@ -307,16 +305,15 @@ def run_lvs(cell_name, gds_name, sp_name, final_verification=False):
|
|||
out_errors = len(stdouterrors)
|
||||
total_errors = summary_errors + out_errors + ext_errors
|
||||
|
||||
if total_errors > 0:
|
||||
debug.error("{0}\tSummary: {1}\tOutput: {2}\tExtraction: {3}".format(cell_name,
|
||||
# always display this summary
|
||||
result_str = "{0}\tSummary: {1}\tOutput: {2}\tExtraction: {3}".format(cell_name,
|
||||
summary_errors,
|
||||
out_errors,
|
||||
ext_errors))
|
||||
ext_errors)
|
||||
if total_errors > 0:
|
||||
debug.warning(result_str)
|
||||
else:
|
||||
debug.info(1, "{0}\tSummary: {1}\tOutput: {2}\tExtraction: {3}".format(cell_name,
|
||||
summary_errors,
|
||||
out_errors,
|
||||
ext_errors))
|
||||
debug.info(1, result_str)
|
||||
|
||||
return total_errors
|
||||
|
||||
|
|
|
|||
|
|
@ -200,13 +200,14 @@ def run_drc(cell_name, gds_name, extract=True, final_verification=False):
|
|||
|
||||
|
||||
# always display this summary
|
||||
result_str = "DRC Errors {0}\t{1}".format(cell_name, errors)
|
||||
if errors > 0:
|
||||
for line in results:
|
||||
if "error tiles" in line:
|
||||
debug.info(1,line.rstrip("\n"))
|
||||
debug.error("DRC Errors {0}\t{1}".format(cell_name, errors))
|
||||
debug.warning(result_str)
|
||||
else:
|
||||
debug.info(1, "DRC Errors {0}\t{1}".format(cell_name, errors))
|
||||
debug.info(1, result_str)
|
||||
|
||||
return errors
|
||||
|
||||
|
|
|
|||
Binary file not shown.
|
|
@ -234,9 +234,9 @@ drc.add_enclosure("active",
|
|||
enclosure = 0.005)
|
||||
|
||||
# CONTACT.6 Minimum spacing of contact and gate
|
||||
drc["contact_to_gate"] = 0.0375 #changed from 0.035
|
||||
drc["active_contact_to_gate"] = 0.0375 #changed from 0.035
|
||||
# CONTACT.7 Minimum spacing of contact and poly
|
||||
drc["contact_to_poly"] = 0.090
|
||||
drc["poly_contact_to_gate"] = 0.090
|
||||
|
||||
# CONTACT.1 Minimum width of contact
|
||||
# CONTACT.2 Minimum spacing of contact
|
||||
|
|
|
|||
|
|
@ -217,9 +217,9 @@ drc.add_enclosure("active",
|
|||
layer = "contact",
|
||||
enclosure = _lambda_)
|
||||
# Reserved for other technologies
|
||||
drc["contact_to_gate"] = 2*_lambda_
|
||||
drc["active_contact_to_gate"] = 2*_lambda_
|
||||
# 5.4 Minimum spacing to gate of transistor
|
||||
drc["contact_to_poly"] = 2*_lambda_
|
||||
drc["poly_contact_to_gate"] = 2*_lambda_
|
||||
|
||||
# 6.1 Exact contact size
|
||||
# 5.3 Minimum contact spacing
|
||||
|
|
|
|||
Loading…
Reference in New Issue