Merge branch 'dev' into characterizer_bug_fixes

This commit is contained in:
Hunter Nichols 2020-07-02 18:00:41 -07:00
commit 206b02a7ee
45 changed files with 1319 additions and 1087 deletions

View File

@ -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

View File

@ -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)

View File

@ -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.

View File

@ -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):
"""

View File

@ -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):

View File

@ -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) + ',')

View File

@ -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

View File

@ -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):
"""

View File

@ -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

View File

@ -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)

View File

@ -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",

View File

@ -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"""

View File

@ -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):

View File

@ -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]:

View File

@ -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])

View File

@ -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)):

View File

@ -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 """

View File

@ -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,

View File

@ -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)

View File

@ -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()

View File

@ -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"""

View File

@ -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:

View File

@ -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)

View File

@ -58,7 +58,7 @@ class options(optparse.Values):
delay_chain_stages = 9
delay_chain_fanout_per_stage = 4
accuracy_requirement = 0.75
###################
# Debug options.

View File

@ -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)

View File

@ -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",

View File

@ -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):

View File

@ -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 """

View File

@ -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 """

View File

@ -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

View File

@ -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,

View File

@ -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 = []

View File

@ -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,

View File

@ -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):
"""

0
compiler/tests/10_write_driver_array_1rw_1r_test.py Normal file → Executable file
View File

0
compiler/tests/10_write_mask_and_array_1rw_1r_test.py Normal file → Executable file
View File

View File

@ -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()

View File

@ -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())

View File

@ -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())

View File

@ -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())

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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