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