mirror of https://github.com/VLSIDA/OpenRAM.git
Merge branch 'dev' into tech_migration
This commit is contained in:
commit
8ece411954
|
|
@ -74,7 +74,7 @@ class design(hierarchy_design):
|
|||
return pitch
|
||||
|
||||
def setup_drc_constants(self):
|
||||
"""
|
||||
"""
|
||||
These are some DRC constants used in many places
|
||||
in the compiler.
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -0,0 +1,15 @@
|
|||
|
||||
|
||||
class drc_error(Exception):
|
||||
"""Exception raised for DRC errors.
|
||||
|
||||
Attributes:
|
||||
expression -- input expression in which the error occurred
|
||||
message -- explanation of the error
|
||||
"""
|
||||
|
||||
# def __init__(self, expression, message):
|
||||
# self.expression = expression
|
||||
# self.message = message
|
||||
def __init__(self, message):
|
||||
self.message = message
|
||||
|
|
@ -46,7 +46,6 @@ class layout():
|
|||
except ImportError:
|
||||
self.pwr_grid_layer = "m3"
|
||||
|
||||
|
||||
############################################################
|
||||
# GDS layout
|
||||
############################################################
|
||||
|
|
@ -196,7 +195,7 @@ class layout():
|
|||
self.insts.append(geometry.instance(name, mod, offset, mirror, rotate))
|
||||
debug.info(3, "adding instance {}".format(self.insts[-1]))
|
||||
# This is commented out for runtime reasons
|
||||
#debug.info(4, "instance list: " + ",".join(x.name for x in self.insts))
|
||||
# debug.info(4, "instance list: " + ",".join(x.name for x in self.insts))
|
||||
return self.insts[-1]
|
||||
|
||||
def get_inst(self, name):
|
||||
|
|
@ -214,12 +213,14 @@ class layout():
|
|||
width = drc["minwidth_{}".format(layer)]
|
||||
if not height:
|
||||
height = drc["minwidth_{}".format(layer)]
|
||||
# negative layers indicate "unused" layers in a given technology
|
||||
lpp = techlayer[layer]
|
||||
if lpp[0] >= 0:
|
||||
self.objs.append(geometry.rectangle(lpp, offset, width, height))
|
||||
return self.objs[-1]
|
||||
return None
|
||||
if abs(offset[0]-5.16250)<0.01 and abs(offset[1]-8.70750)<0.01:
|
||||
import pdb; pdb.set_trace()
|
||||
self.objs.append(geometry.rectangle(lpp,
|
||||
offset,
|
||||
width,
|
||||
height))
|
||||
return self.objs[-1]
|
||||
|
||||
def add_rect_center(self, layer, offset, width=None, height=None):
|
||||
"""
|
||||
|
|
@ -230,16 +231,13 @@ class layout():
|
|||
width = drc["minwidth_{}".format(layer)]
|
||||
if not height:
|
||||
height = drc["minwidth_{}".format(layer)]
|
||||
# negative layers indicate "unused" layers in a given technology
|
||||
lpp = techlayer[layer]
|
||||
corrected_offset = offset - vector(0.5 * width, 0.5 * height)
|
||||
if lpp[0] >= 0:
|
||||
self.objs.append(geometry.rectangle(lpp,
|
||||
corrected_offset,
|
||||
width,
|
||||
height))
|
||||
return self.objs[-1]
|
||||
return None
|
||||
self.objs.append(geometry.rectangle(lpp,
|
||||
corrected_offset,
|
||||
width,
|
||||
height))
|
||||
return self.objs[-1]
|
||||
|
||||
def add_segment_center(self, layer, start, end):
|
||||
"""
|
||||
|
|
@ -252,15 +250,15 @@ class layout():
|
|||
elif start.x != end.x:
|
||||
offset = vector(0, 0.5 * minwidth_layer)
|
||||
return self.add_rect(layer,
|
||||
start-offset,
|
||||
end.x-start.x,
|
||||
start - offset,
|
||||
end.x - start.x,
|
||||
minwidth_layer)
|
||||
else:
|
||||
offset = vector(0.5 * minwidth_layer, 0)
|
||||
return self.add_rect(layer,
|
||||
start-offset,
|
||||
start - offset,
|
||||
minwidth_layer,
|
||||
end.y-start.y)
|
||||
end.y - start.y)
|
||||
|
||||
def get_pin(self, text):
|
||||
"""
|
||||
|
|
@ -268,14 +266,14 @@ class layout():
|
|||
"""
|
||||
try:
|
||||
if len(self.pin_map[text]) > 1:
|
||||
debug.error("Should use a pin iterator since more than one pin {}".format(text),-1)
|
||||
debug.error("Should use a pin iterator since more than one pin {}".format(text), -1)
|
||||
# If we have one pin, return it and not the list.
|
||||
# Otherwise, should use get_pins()
|
||||
any_pin = next(iter(self.pin_map[text]))
|
||||
return any_pin
|
||||
except Exception:
|
||||
self.gds_write("missing_pin.gds")
|
||||
debug.error("No pin found with name {0} on {1}. Saved as missing_pin.gds.".format(text,self.name),-1)
|
||||
debug.error("No pin found with name {0} on {1}. Saved as missing_pin.gds.".format(text, self.name), -1)
|
||||
|
||||
def get_pins(self, text):
|
||||
"""
|
||||
|
|
@ -377,7 +375,7 @@ class layout():
|
|||
height = drc["minwidth_{0}".format(layer)]
|
||||
|
||||
new_pin = pin_layout(text,
|
||||
[offset, offset+vector(width, height)],
|
||||
[offset, offset + vector(width, height)],
|
||||
layer)
|
||||
|
||||
try:
|
||||
|
|
@ -413,23 +411,18 @@ class layout():
|
|||
|
||||
def add_label(self, text, layer, offset=[0, 0], zoom=-1):
|
||||
"""Adds a text label on the given layer,offset, and zoom level"""
|
||||
# negative layers indicate "unused" layers in a given technology
|
||||
debug.info(5, "add label " + str(text) + " " + layer + " " + str(offset))
|
||||
lpp = techlayer[layer]
|
||||
if lpp[0] >= 0:
|
||||
self.objs.append(geometry.label(text, lpp, offset, zoom))
|
||||
return self.objs[-1]
|
||||
return None
|
||||
self.objs.append(geometry.label(text, lpp, offset, zoom))
|
||||
return self.objs[-1]
|
||||
|
||||
def add_path(self, layer, coordinates, width=None):
|
||||
"""Connects a routing path on given layer,coordinates,width."""
|
||||
debug.info(4, "add path " + str(layer) + " " + str(coordinates))
|
||||
import wire_path
|
||||
# NOTE: (UNTESTED) add_path(...) is currently not used
|
||||
# negative layers indicate "unused" layers in a given technology
|
||||
# lpp = techlayer[layer]
|
||||
# if lpp[0] >= 0:
|
||||
# self.objs.append(geometry.path(lpp, coordinates, width))
|
||||
# self.objs.append(geometry.path(lpp, coordinates, width))
|
||||
|
||||
wire_path.wire_path(obj=self,
|
||||
layer=layer,
|
||||
|
|
@ -465,7 +458,7 @@ class layout():
|
|||
from tech import preferred_directions
|
||||
return preferred_directions[layer]
|
||||
|
||||
def add_via(self, layers, offset, size=[1,1], directions=None, implant_type=None, well_type=None):
|
||||
def add_via(self, layers, offset, size=[1, 1], directions=None, implant_type=None, well_type=None):
|
||||
""" Add a three layer via structure. """
|
||||
|
||||
if not directions:
|
||||
|
|
@ -487,7 +480,7 @@ class layout():
|
|||
self.connect_inst([])
|
||||
return inst
|
||||
|
||||
def add_via_center(self, layers, offset, directions=None, size=[1,1], implant_type=None, well_type=None):
|
||||
def add_via_center(self, layers, offset, directions=None, size=[1, 1], implant_type=None, well_type=None):
|
||||
"""
|
||||
Add a three layer via structure by the center coordinate
|
||||
accounting for mirroring and rotation.
|
||||
|
|
@ -799,9 +792,7 @@ class layout():
|
|||
self.add_rect(layer=layer,
|
||||
offset=line_offset,
|
||||
height=length)
|
||||
# Make this the center of the rail
|
||||
line_positions[names[i]] = line_offset + vector(half_minwidth,
|
||||
0.5 * length)
|
||||
line_positions[names[i]] = line_offset + vector(half_minwidth, 0)
|
||||
else:
|
||||
for i in range(len(names)):
|
||||
line_offset = offset + vector(0,
|
||||
|
|
@ -953,14 +944,14 @@ class layout():
|
|||
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:
|
||||
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)])
|
||||
vector(trunk_offset.x, max_y + half_layer_width)])
|
||||
|
||||
# Route each pin to the trunk
|
||||
for pin in pins:
|
||||
|
|
@ -1000,7 +991,7 @@ class layout():
|
|||
|
||||
# Remove the pin from all conflicts
|
||||
# FIXME: This is O(n^2), so maybe optimize it.
|
||||
for other_pin,conflicts in g.items():
|
||||
for other_pin, conflicts in g.items():
|
||||
if pin in conflicts:
|
||||
conflicts.remove(pin)
|
||||
g[other_pin]=conflicts
|
||||
|
|
@ -1151,8 +1142,8 @@ class layout():
|
|||
else:
|
||||
self.bounding_box = self.add_rect(layer=boundary_layer,
|
||||
offset=ll,
|
||||
height=ur.y-ll.y,
|
||||
width=ur.x-ll.x)
|
||||
height=ur.y - ll.y,
|
||||
width=ur.x - ll.x)
|
||||
|
||||
def add_enclosure(self, insts, layer="nwell"):
|
||||
""" Add a layer that surrounds the given instances. Useful
|
||||
|
|
@ -1171,8 +1162,8 @@ class layout():
|
|||
|
||||
self.add_rect(layer=layer,
|
||||
offset=vector(xmin, ymin),
|
||||
width=xmax-xmin,
|
||||
height=ymax-ymin)
|
||||
width=xmax - xmin,
|
||||
height=ymax - ymin)
|
||||
|
||||
def copy_power_pins(self, inst, name):
|
||||
"""
|
||||
|
|
@ -1192,7 +1183,7 @@ class layout():
|
|||
else:
|
||||
debug.warning("{0} pins of {1} should be on {2} or metal1 for "\
|
||||
"supply router."
|
||||
.format(name,inst.name,self.pwr_grid_layer))
|
||||
.format(name, inst.name, self.pwr_grid_layer))
|
||||
|
||||
def add_power_pin(self, name, loc, size=[1, 1], vertical=False, start_layer="m1"):
|
||||
"""
|
||||
|
|
@ -1244,8 +1235,8 @@ class layout():
|
|||
[ll, ur] = bbox
|
||||
|
||||
supply_rail_spacing = self.supply_rail_pitch - self.supply_rail_width
|
||||
height = (ur.y-ll.y) + 3 * self.supply_rail_pitch - supply_rail_spacing
|
||||
width = (ur.x-ll.x) + 3 * self.supply_rail_pitch - supply_rail_spacing
|
||||
height = (ur.y - ll.y) + 3 * self.supply_rail_pitch - supply_rail_spacing
|
||||
width = (ur.x - ll.x) + 3 * self.supply_rail_pitch - supply_rail_spacing
|
||||
|
||||
# LEFT vertical rails
|
||||
offset = ll + vector(-2 * self.supply_rail_pitch,
|
||||
|
|
|
|||
|
|
@ -6,18 +6,19 @@
|
|||
# All rights reserved.
|
||||
#
|
||||
from tech import drc
|
||||
import debug
|
||||
import contact
|
||||
from wire_path import wire_path
|
||||
from sram_factory import factory
|
||||
|
||||
|
||||
class wire(wire_path):
|
||||
"""
|
||||
"""
|
||||
Object metal wire; given the layer type
|
||||
Add a wire of minimium metal width between a set of points.
|
||||
Add a wire of minimium metal width between a set of points.
|
||||
The points should be rectilinear to control the bend points. If
|
||||
not, it will always go down first.
|
||||
The points are the center of the wire.
|
||||
The layer stack is the vertical, contact/via, and horizontal layers, respectively.
|
||||
The layer stack is the vertical, contact/via, and horizontal layers, respectively.
|
||||
"""
|
||||
def __init__(self, obj, layer_stack, position_list):
|
||||
self.obj = obj
|
||||
|
|
@ -36,6 +37,7 @@ class wire(wire_path):
|
|||
# wires and wire_paths should not be offset to (0,0)
|
||||
|
||||
def setup_layers(self):
|
||||
|
||||
(horiz_layer, via_layer, vert_layer) = self.layer_stack
|
||||
self.via_layer_name = via_layer
|
||||
|
||||
|
|
@ -47,21 +49,49 @@ class wire(wire_path):
|
|||
via_connect = factory.create(module_type="contact",
|
||||
layer_stack=self.layer_stack,
|
||||
dimensions=(1, 1))
|
||||
|
||||
# This is used for short connections to avoid via-to-via spacing errors
|
||||
self.vert_layer_contact_width = max(via_connect.second_layer_width,
|
||||
via_connect.first_layer_width)
|
||||
self.horiz_layer_contact_width = max(via_connect.second_layer_height,
|
||||
via_connect.first_layer_height)
|
||||
|
||||
self.node_to_node = [drc("minwidth_" + str(self.horiz_layer_name)) + via_connect.width,
|
||||
drc("minwidth_" + str(self.horiz_layer_name)) + via_connect.height]
|
||||
self.pitch = self.compute_pitch(self.layer_stack)
|
||||
|
||||
def compute_pitch(self, layer_stack):
|
||||
|
||||
"""
|
||||
This is contact direction independent pitch,
|
||||
i.e. we take the maximum contact dimension
|
||||
"""
|
||||
(layer1, via, layer2) = layer_stack
|
||||
|
||||
if layer1 == "poly" or layer1 == "active":
|
||||
contact1 = getattr(contact, layer1 + "_contact")
|
||||
else:
|
||||
try:
|
||||
contact1 = getattr(contact, layer1 + "_via")
|
||||
except AttributeError:
|
||||
contact1 = getattr(contact, layer2 + "_via")
|
||||
max_contact = max(contact1.width, contact1.height)
|
||||
|
||||
layer1_space = drc("{0}_to_{0}".format(layer1))
|
||||
layer2_space = drc("{0}_to_{0}".format(layer2))
|
||||
pitch = max_contact + max(layer1_space, layer2_space)
|
||||
|
||||
return pitch
|
||||
|
||||
# create a 1x1 contact
|
||||
def create_vias(self):
|
||||
""" Add a via and corner square at every corner of the path."""
|
||||
self.c=factory.create(module_type="contact",
|
||||
layer_stack=self.layer_stack,
|
||||
dimensions=(1, 1))
|
||||
c_width = self.c.width
|
||||
c_height = self.c.height
|
||||
|
||||
from itertools import tee,islice
|
||||
nwise = lambda g,n=2: zip(*(islice(g,i,None) for i,g in enumerate(tee(g,n))))
|
||||
threewise=nwise(self.position_list,3)
|
||||
from itertools import tee, islice
|
||||
nwise = lambda g, n=2: zip(*(islice(g, i, None) for i, g in enumerate(tee(g, n))))
|
||||
threewise = nwise(self.position_list, 3)
|
||||
|
||||
for (a, offset, c) in list(threewise):
|
||||
# add a exceptions to prevent a via when we don't change directions
|
||||
|
|
@ -72,18 +102,25 @@ class wire(wire_path):
|
|||
self.obj.add_via_center(layers=self.layer_stack,
|
||||
offset=offset)
|
||||
|
||||
|
||||
def create_rectangles(self):
|
||||
"""
|
||||
"""
|
||||
Create the actual rectangles on the appropriate layers
|
||||
using the position list of the corners.
|
||||
using the position list of the corners.
|
||||
"""
|
||||
pl = self.position_list # position list
|
||||
for index in range(len(pl) - 1):
|
||||
# Horizontal wire segment
|
||||
if pl[index][0] != pl[index + 1][0]:
|
||||
line_length = pl[index + 1][0] - pl[index][0]
|
||||
# Make the wire wider to avoid via-to-via spacing problems
|
||||
# But don't make it wider if it is shorter than one via
|
||||
if abs(line_length) < self.pitch and abs(line_length) > self.horiz_layer_contact_width:
|
||||
width = self.horiz_layer_contact_width
|
||||
else:
|
||||
width = self.horiz_layer_width
|
||||
temp_offset = [pl[index][0],
|
||||
pl[index][1] - 0.5*self.horiz_layer_width]
|
||||
pl[index][1] - 0.5 * width]
|
||||
# If we go in the negative direction, move the offset
|
||||
if line_length < 0:
|
||||
temp_offset = [temp_offset[0] + line_length,
|
||||
temp_offset[1]]
|
||||
|
|
@ -91,10 +128,17 @@ class wire(wire_path):
|
|||
length=abs(line_length),
|
||||
offset=temp_offset,
|
||||
orientation="horizontal",
|
||||
layer_width=self.horiz_layer_width)
|
||||
layer_width=width)
|
||||
# Vertical wire segment
|
||||
elif pl[index][1] != pl[index + 1][1]:
|
||||
line_length = pl[index + 1][1] - pl[index][1]
|
||||
temp_offset = [pl[index][0] - 0.5 * self.vert_layer_width,
|
||||
# Make the wire wider to avoid via-to-via spacing problems
|
||||
# But don't make it wider if it is shorter than one via
|
||||
if abs(line_length) < self.pitch and abs(line_length) > self.vert_layer_contact_width:
|
||||
width = self.vert_layer_contact_width
|
||||
else:
|
||||
width = self.vert_layer_width
|
||||
temp_offset = [pl[index][0] - 0.5 * width,
|
||||
pl[index][1]]
|
||||
if line_length < 0:
|
||||
temp_offset = [temp_offset[0],
|
||||
|
|
@ -103,11 +147,13 @@ class wire(wire_path):
|
|||
length=abs(line_length),
|
||||
offset=temp_offset,
|
||||
orientation="vertical",
|
||||
layer_width=self.vert_layer_width)
|
||||
layer_width=width)
|
||||
|
||||
def assert_node(self, A, B):
|
||||
""" Check if the node movements are not big enough for the
|
||||
technology sizes."""
|
||||
"""
|
||||
Check if the node movements are not big enough for the
|
||||
technology sizes.
|
||||
"""
|
||||
X_diff = abs(A[0] - B[0])
|
||||
Y_diff = abs(A[1] - B[1])
|
||||
[minX, minY] = self.node_to_node
|
||||
|
|
|
|||
|
|
@ -9,9 +9,10 @@ import debug
|
|||
from drc_value import *
|
||||
from drc_lut import *
|
||||
|
||||
|
||||
class design_rules(dict):
|
||||
"""
|
||||
This is a class that implements the design rules structures.
|
||||
"""
|
||||
This is a class that implements the design rules structures.
|
||||
"""
|
||||
def __init__(self, name):
|
||||
self.tech_name = name
|
||||
|
|
|
|||
|
|
@ -7,9 +7,10 @@
|
|||
#
|
||||
import debug
|
||||
|
||||
|
||||
class drc_lut():
|
||||
"""
|
||||
Implement a lookup table of rules.
|
||||
"""
|
||||
Implement a lookup table of rules.
|
||||
Each element is a tuple with the last value being the rule.
|
||||
It searches through backwards until all of the key values are
|
||||
met and returns the rule value.
|
||||
|
|
@ -31,7 +32,6 @@ class drc_lut():
|
|||
for table_key in sorted(self.table.keys(), reverse=True):
|
||||
if self.match(key, table_key):
|
||||
return self.table[table_key]
|
||||
|
||||
|
||||
def match(self, key1, key2):
|
||||
"""
|
||||
|
|
@ -39,8 +39,8 @@ class drc_lut():
|
|||
(i.e. return false if key1<key2 for any pair.)
|
||||
"""
|
||||
# If any one pair is less than, return False
|
||||
debug.check(len(key1)==len(key2),"Comparing invalid key lengths.")
|
||||
for k1,k2 in zip(key1,key2):
|
||||
debug.check(len(key1) == len(key2), "Comparing invalid key lengths.")
|
||||
for k1, k2 in zip(key1, key2):
|
||||
if k1 < k2:
|
||||
return False
|
||||
return True
|
||||
|
|
|
|||
|
|
@ -6,8 +6,9 @@
|
|||
# All rights reserved.
|
||||
#
|
||||
|
||||
|
||||
class drc_value():
|
||||
"""
|
||||
"""
|
||||
A single DRC value.
|
||||
"""
|
||||
def __init__(self, value):
|
||||
|
|
|
|||
|
|
@ -589,4 +589,4 @@ def report_status():
|
|||
if OPTS.trim_netlist:
|
||||
debug.print_raw("Trimming netlist to speed up characterization (trim_netlist=False to disable).")
|
||||
if OPTS.nominal_corner_only:
|
||||
debug.print_raw("Only characterizing nominal corner.")
|
||||
debug.print_raw("Only characterizing nominal corner.")
|
||||
|
|
|
|||
|
|
@ -45,11 +45,9 @@ class bank_select(design.design):
|
|||
self.height = max([x.uy() for x in self.inv_inst]) + self.m1_width
|
||||
self.width = max([x.rx() for x in self.inv_inst])
|
||||
|
||||
|
||||
self.add_boundary()
|
||||
self.DRC_LVS()
|
||||
|
||||
|
||||
def add_pins(self):
|
||||
|
||||
# Number of control lines in the bus
|
||||
|
|
@ -65,19 +63,18 @@ class bank_select(design.design):
|
|||
if (self.port == "rw") or (self.port == "r"):
|
||||
self.input_control_signals.append("s_en")
|
||||
# These will be outputs of the gaters if this is multibank
|
||||
self.control_signals = ["gated_"+str for str in self.input_control_signals]
|
||||
self.control_signals = ["gated_" + str for str in self.input_control_signals]
|
||||
|
||||
self.add_pin_list(self.input_control_signals, "INPUT")
|
||||
self.add_pin("bank_sel")
|
||||
self.add_pin_list(self.control_signals, "OUTPUT")
|
||||
self.add_pin("vdd","POWER")
|
||||
self.add_pin("gnd","GROUND")
|
||||
self.add_pin("vdd", "POWER")
|
||||
self.add_pin("gnd", "GROUND")
|
||||
|
||||
def add_modules(self):
|
||||
""" Create modules for later instantiation """
|
||||
self.bitcell = factory.create(module_type="bitcell")
|
||||
|
||||
height = self.bitcell.height + drc("poly_to_active")
|
||||
self.dff = factory.create(module_type="dff")
|
||||
height = self.dff.height + drc("poly_to_active")
|
||||
|
||||
# 1x Inverter
|
||||
self.inv_sel = factory.create(module_type="pinv", height=height)
|
||||
|
|
@ -98,17 +95,15 @@ class bank_select(design.design):
|
|||
|
||||
def calculate_module_offsets(self):
|
||||
|
||||
self.xoffset_nand = self.inv4x.width + 2*self.m2_pitch + drc("pwell_to_nwell")
|
||||
self.xoffset_nor = self.inv4x.width + 2*self.m2_pitch + drc("pwell_to_nwell")
|
||||
self.xoffset_bank_sel_inv = 0
|
||||
self.xoffset_nand = self.inv4x.width + 3 * self.m2_pitch + drc("pwell_to_nwell")
|
||||
self.xoffset_nor = self.inv4x.width + 3 * self.m2_pitch + drc("pwell_to_nwell")
|
||||
self.xoffset_bank_sel_inv = 0
|
||||
self.xoffset_inputs = 0
|
||||
|
||||
self.yoffset_maxpoint = self.num_control_lines * self.inv4x.height
|
||||
|
||||
|
||||
def create_instances(self):
|
||||
|
||||
self.bank_sel_inv=self.add_inst(name="bank_sel_inv",
|
||||
self.bank_sel_inv=self.add_inst(name="bank_sel_inv",
|
||||
mod=self.inv_sel)
|
||||
self.connect_inst(["bank_sel", "bank_sel_bar", "vdd", "gnd"])
|
||||
|
||||
|
|
@ -125,36 +120,36 @@ class bank_select(design.design):
|
|||
# (writes occur on clk low)
|
||||
if input_name in ("clk_buf"):
|
||||
|
||||
self.logic_inst.append(self.add_inst(name=name_nor,
|
||||
mod=self.nor2))
|
||||
self.logic_inst.append(self.add_inst(name=name_nor,
|
||||
mod=self.nor2))
|
||||
self.connect_inst([input_name,
|
||||
"bank_sel_bar",
|
||||
gated_name+"_temp_bar",
|
||||
gated_name + "_temp_bar",
|
||||
"vdd",
|
||||
"gnd"])
|
||||
|
||||
# They all get inverters on the output
|
||||
self.inv_inst.append(self.add_inst(name=name_inv,
|
||||
self.inv_inst.append(self.add_inst(name=name_inv,
|
||||
mod=self.inv4x_nor))
|
||||
self.connect_inst([gated_name+"_temp_bar",
|
||||
self.connect_inst([gated_name + "_temp_bar",
|
||||
gated_name,
|
||||
"vdd",
|
||||
"gnd"])
|
||||
|
||||
# the rest are AND (nand2+inv) gates
|
||||
else:
|
||||
self.logic_inst.append(self.add_inst(name=name_nand,
|
||||
self.logic_inst.append(self.add_inst(name=name_nand,
|
||||
mod=self.nand2))
|
||||
self.connect_inst([input_name,
|
||||
"bank_sel",
|
||||
gated_name+"_temp_bar",
|
||||
gated_name + "_temp_bar",
|
||||
"vdd",
|
||||
"gnd"])
|
||||
|
||||
# They all get inverters on the output
|
||||
self.inv_inst.append(self.add_inst(name=name_inv,
|
||||
self.inv_inst.append(self.add_inst(name=name_inv,
|
||||
mod=self.inv4x))
|
||||
self.connect_inst([gated_name+"_temp_bar",
|
||||
self.connect_inst([gated_name + "_temp_bar",
|
||||
gated_name,
|
||||
"vdd",
|
||||
"gnd"])
|
||||
|
|
@ -177,9 +172,9 @@ class bank_select(design.design):
|
|||
if i == 0:
|
||||
y_offset = 0
|
||||
else:
|
||||
y_offset = self.inv4x_nor.height + self.inv4x.height * (i-1)
|
||||
y_offset = self.inv4x_nor.height + self.inv4x.height * (i - 1)
|
||||
|
||||
if i%2:
|
||||
if i % 2:
|
||||
y_offset += self.inv4x.height
|
||||
mirror = "MX"
|
||||
else:
|
||||
|
|
@ -200,7 +195,6 @@ class bank_select(design.design):
|
|||
# They all get inverters on the output
|
||||
inv_inst.place(offset=[logic_inst.rx(), y_offset],
|
||||
mirror=mirror)
|
||||
|
||||
|
||||
def route_instances(self):
|
||||
|
||||
|
|
@ -222,57 +216,56 @@ class bank_select(design.design):
|
|||
end=bank_sel_pin_end)
|
||||
self.add_via_center(layers=self.m2_stack,
|
||||
offset=bank_sel_pin_end,
|
||||
directions=("H","H"))
|
||||
directions=("H", "H"))
|
||||
|
||||
# bank_sel_bar is vertical wire
|
||||
bank_sel_bar_pin = self.bank_sel_inv.get_pin("Z")
|
||||
xoffset_bank_sel_bar = bank_sel_bar_pin.rx()
|
||||
self.add_label_pin(text="bank_sel_bar",
|
||||
layer="m2",
|
||||
offset=vector(xoffset_bank_sel_bar, 0),
|
||||
layer="m2",
|
||||
offset=vector(xoffset_bank_sel_bar, 0),
|
||||
height=self.inv4x.height)
|
||||
self.add_via_center(layers=self.m1_stack,
|
||||
offset=bank_sel_bar_pin.rc())
|
||||
|
||||
|
||||
for i in range(self.num_control_lines):
|
||||
|
||||
logic_inst = self.logic_inst[i]
|
||||
inv_inst = self.inv_inst[i]
|
||||
|
||||
input_name = self.input_control_signals[i]
|
||||
gated_name = self.control_signals[i]
|
||||
gated_name = self.control_signals[i]
|
||||
if input_name in ("clk_buf"):
|
||||
xoffset_bank_signal = xoffset_bank_sel_bar
|
||||
else:
|
||||
xoffset_bank_signal = xoffset_bank_sel
|
||||
|
||||
# Connect the logic output to inverter input
|
||||
pre = logic_inst.get_pin("Z").lc()
|
||||
out_position = logic_inst.get_pin("Z").rc() + vector(0.5*self.m1_width,0)
|
||||
in_position = inv_inst.get_pin("A").lc() + vector(0.5*self.m1_width,0)
|
||||
post = inv_inst.get_pin("A").rc()
|
||||
self.add_path("m1", [pre, out_position, in_position, post])
|
||||
out_pin = logic_inst.get_pin("Z")
|
||||
out_pos = out_pin.rc()
|
||||
in_pin = inv_inst.get_pin("A")
|
||||
in_pos = in_pin.lc()
|
||||
mid1_pos = vector(0.5 * (out_pos.x + in_pos.x), out_pos.y)
|
||||
mid2_pos = vector(0.5 * (out_pos.x + in_pos.x), in_pos.y)
|
||||
self.add_path("m1", [out_pos, mid1_pos, mid2_pos, in_pos])
|
||||
|
||||
|
||||
# Connect the logic B input to bank_sel/bank_sel_bar
|
||||
logic_pos = logic_inst.get_pin("B").lc() - vector(0.5*contact.m1_via.height,0)
|
||||
# Connect the logic B input to bank_sel / bank_sel_bar
|
||||
logic_pos = logic_inst.get_pin("B").lc() - vector(0.5 * contact.m1_via.height, 0)
|
||||
input_pos = vector(xoffset_bank_signal, logic_pos.y)
|
||||
self.add_path("m2",[logic_pos, input_pos])
|
||||
self.add_path("m2", [logic_pos, input_pos])
|
||||
self.add_via_center(layers=self.m1_stack,
|
||||
offset=logic_pos,
|
||||
directions=("H","H"))
|
||||
directions=("H", "H"))
|
||||
|
||||
|
||||
# Connect the logic A input to the input pin
|
||||
logic_pos = logic_inst.get_pin("A").lc()
|
||||
input_pos = vector(0,logic_pos.y)
|
||||
input_pos = vector(0, logic_pos.y)
|
||||
self.add_via_center(layers=self.m1_stack,
|
||||
offset=logic_pos,
|
||||
directions=("H","H"))
|
||||
directions=("H", "H"))
|
||||
self.add_via_center(layers=self.m2_stack,
|
||||
offset=logic_pos,
|
||||
directions=("H","H"))
|
||||
directions=("H", "H"))
|
||||
self.add_layout_pin_segment_center(text=input_name,
|
||||
layer="m3",
|
||||
start=input_pos,
|
||||
|
|
@ -286,7 +279,6 @@ class bank_select(design.design):
|
|||
width=inv_inst.rx() - out_pin.lx(),
|
||||
height=out_pin.height())
|
||||
|
||||
|
||||
# Find the x offsets for where the vias/pins should be placed
|
||||
a_xoffset = self.logic_inst[0].lx()
|
||||
b_xoffset = self.inv_inst[0].lx()
|
||||
|
|
@ -294,7 +286,7 @@ class bank_select(design.design):
|
|||
# Route both supplies
|
||||
for n in ["vdd", "gnd"]:
|
||||
supply_pin = self.inv_inst[num].get_pin(n)
|
||||
supply_offset = supply_pin.ll().scale(0,1)
|
||||
supply_offset = supply_pin.ll().scale(0, 1)
|
||||
self.add_rect(layer="m1",
|
||||
offset=supply_offset,
|
||||
width=self.width)
|
||||
|
|
@ -304,10 +296,10 @@ class bank_select(design.design):
|
|||
pin_pos = vector(xoffset, supply_pin.cy())
|
||||
self.add_via_center(layers=self.m1_stack,
|
||||
offset=pin_pos,
|
||||
directions=("H","H"))
|
||||
directions=("H", "H"))
|
||||
self.add_via_center(layers=self.m2_stack,
|
||||
offset=pin_pos,
|
||||
directions=("H","H"))
|
||||
directions=("H", "H"))
|
||||
self.add_layout_pin_rect_center(text=n,
|
||||
layer="m3",
|
||||
offset=pin_pos)
|
||||
|
|
|
|||
|
|
@ -5,18 +5,16 @@
|
|||
# (acting for and on behalf of Oklahoma State University)
|
||||
# All rights reserved.
|
||||
#
|
||||
from math import log
|
||||
import design
|
||||
from tech import drc, parameter
|
||||
from tech import cell_properties as props
|
||||
import debug
|
||||
import contact
|
||||
from sram_factory import factory
|
||||
import math
|
||||
from vector import vector
|
||||
from globals import OPTS
|
||||
import logical_effort
|
||||
|
||||
|
||||
class control_logic(design.design):
|
||||
"""
|
||||
Dynamically generated Control logic for the total SRAM circuit.
|
||||
|
|
@ -29,7 +27,7 @@ class control_logic(design.design):
|
|||
debug.info(1, "Creating {}".format(name))
|
||||
self.add_comment("num_rows: {0}".format(num_rows))
|
||||
self.add_comment("words_per_row: {0}".format(words_per_row))
|
||||
self.add_comment("word_size {0}".format(word_size))
|
||||
self.add_comment("word_size {0}".format(word_size))
|
||||
|
||||
self.sram=sram
|
||||
self.num_rows = num_rows
|
||||
|
|
@ -37,14 +35,15 @@ class control_logic(design.design):
|
|||
self.word_size = word_size
|
||||
self.port_type = port_type
|
||||
|
||||
self.num_cols = word_size*words_per_row
|
||||
self.num_words = num_rows*words_per_row
|
||||
self.num_cols = word_size * words_per_row
|
||||
self.num_words = num_rows * words_per_row
|
||||
|
||||
self.enable_delay_chain_resizing = False
|
||||
self.inv_parasitic_delay = logical_effort.logical_effort.pinv
|
||||
|
||||
# Determines how much larger the sen delay should be. Accounts for possible error in model.
|
||||
self.wl_timing_tolerance = 1
|
||||
# FIXME: This should be made a parameter
|
||||
self.wl_timing_tolerance = 1
|
||||
self.wl_stage_efforts = None
|
||||
self.sen_stage_efforts = None
|
||||
|
||||
|
|
@ -67,17 +66,16 @@ class control_logic(design.design):
|
|||
""" Create layout and route between modules """
|
||||
self.place_instances()
|
||||
self.route_all()
|
||||
#self.add_lvs_correspondence_points()
|
||||
# self.add_lvs_correspondence_points()
|
||||
self.add_boundary()
|
||||
self.DRC_LVS()
|
||||
|
||||
|
||||
def add_pins(self):
|
||||
""" Add the pins to the control logic module. """
|
||||
self.add_pin_list(self.input_list + ["clk"] + self.rbl_list, "INPUT")
|
||||
self.add_pin_list(self.output_list,"OUTPUT")
|
||||
self.add_pin("vdd","POWER")
|
||||
self.add_pin("gnd","GROUND")
|
||||
self.add_pin_list(self.output_list, "OUTPUT")
|
||||
self.add_pin("vdd", "POWER")
|
||||
self.add_pin("gnd", "GROUND")
|
||||
|
||||
def add_modules(self):
|
||||
""" Add all the required modules """
|
||||
|
|
@ -101,14 +99,13 @@ class control_logic(design.design):
|
|||
height=dff_height)
|
||||
self.add_mod(self.rbl_driver)
|
||||
|
||||
|
||||
# clk_buf drives a flop for every address
|
||||
addr_flops = math.log(self.num_words,2) + math.log(self.words_per_row,2)
|
||||
# clk_buf drives a flop for every address
|
||||
addr_flops = math.log(self.num_words, 2) + math.log(self.words_per_row, 2)
|
||||
# plus data flops and control flops
|
||||
num_flops = addr_flops + self.word_size + self.num_control_signals
|
||||
# each flop internally has a FO 5 approximately
|
||||
# plus about 5 fanouts for the control logic
|
||||
clock_fanout = 5*num_flops + 5
|
||||
clock_fanout = 5 * num_flops + 5
|
||||
self.clk_buf_driver = factory.create(module_type="pdriver",
|
||||
fanout=clock_fanout,
|
||||
height=dff_height)
|
||||
|
|
@ -117,7 +114,7 @@ class control_logic(design.design):
|
|||
|
||||
# We will use the maximum since this same value is used to size the wl_en
|
||||
# and the p_en_bar drivers
|
||||
max_fanout = max(self.num_rows,self.num_cols)
|
||||
max_fanout = max(self.num_rows, self.num_cols)
|
||||
|
||||
# wl_en drives every row in the bank
|
||||
self.wl_en_driver = factory.create(module_type="pdriver",
|
||||
|
|
@ -127,7 +124,7 @@ class control_logic(design.design):
|
|||
|
||||
# w_en drives every write driver
|
||||
self.wen_and = factory.create(module_type="pand3",
|
||||
size=self.word_size+8,
|
||||
size=self.word_size + 8,
|
||||
height=dff_height)
|
||||
self.add_mod(self.wen_and)
|
||||
|
||||
|
|
@ -137,7 +134,7 @@ class control_logic(design.design):
|
|||
height=dff_height)
|
||||
self.add_mod(self.sen_and3)
|
||||
|
||||
# used to generate inverted signals with low fanout
|
||||
# used to generate inverted signals with low fanout
|
||||
self.inv = factory.create(module_type="pinv",
|
||||
size=1,
|
||||
height=dff_height)
|
||||
|
|
@ -151,7 +148,6 @@ class control_logic(design.design):
|
|||
height=dff_height)
|
||||
self.add_mod(self.p_en_bar_driver)
|
||||
|
||||
|
||||
self.nand2 = factory.create(module_type="pnand2",
|
||||
height=dff_height)
|
||||
self.add_mod(self.nand2)
|
||||
|
|
@ -179,14 +175,14 @@ class control_logic(design.design):
|
|||
# delay_fanout_list=[delay_fanout_heuristic]*delay_stages_heuristic,
|
||||
# bitcell_loads=bitcell_loads)
|
||||
# #Resize if necessary, condition depends on resizing method
|
||||
# if self.sram != None and self.enable_delay_chain_resizing and not self.does_sen_rise_fall_timing_match():
|
||||
# if self.sram != None and self.enable_delay_chain_resizing and not self.does_sen_rise_fall_timing_match():
|
||||
# #This resizes to match fall and rise delays, can make the delay chain weird sizes.
|
||||
# stage_list = self.get_dynamic_delay_fanout_list(delay_stages_heuristic, delay_fanout_heuristic)
|
||||
# self.replica_bitline = factory.create(module_type="replica_bitline",
|
||||
# delay_fanout_list=stage_list,
|
||||
# bitcell_loads=bitcell_loads)
|
||||
|
||||
# #This resizes based on total delay.
|
||||
# #This resizes based on total delay.
|
||||
# # delay_stages, delay_fanout = self.get_dynamic_delay_chain_size(delay_stages_heuristic, delay_fanout_heuristic)
|
||||
# # self.replica_bitline = factory.create(module_type="replica_bitline",
|
||||
# # delay_fanout_list=[delay_fanout]*delay_stages,
|
||||
|
|
@ -195,9 +191,10 @@ class control_logic(design.design):
|
|||
# self.sen_delay_rise,self.sen_delay_fall = self.get_delays_to_sen() #get the new timing
|
||||
# self.delay_chain_resized = True
|
||||
|
||||
debug.check(OPTS.delay_chain_stages%2, "Must use odd number of delay chain stages for inverting delay chain.")
|
||||
debug.check(OPTS.delay_chain_stages % 2,
|
||||
"Must use odd number of delay chain stages for inverting delay chain.")
|
||||
self.delay_chain=factory.create(module_type="delay_chain",
|
||||
fanout_list = OPTS.delay_chain_stages*[OPTS.delay_chain_fanout_per_stage])
|
||||
fanout_list = OPTS.delay_chain_stages * [ OPTS.delay_chain_fanout_per_stage ])
|
||||
self.add_mod(self.delay_chain)
|
||||
|
||||
def get_heuristic_delay_chain_size(self):
|
||||
|
|
@ -219,17 +216,17 @@ class control_logic(design.design):
|
|||
|
||||
def set_sen_wl_delays(self):
|
||||
"""Set delays for wordline and sense amp enable"""
|
||||
self.wl_delay_rise,self.wl_delay_fall = self.get_delays_to_wl()
|
||||
self.sen_delay_rise,self.sen_delay_fall = self.get_delays_to_sen()
|
||||
self.wl_delay = self.wl_delay_rise+self.wl_delay_fall
|
||||
self.sen_delay = self.sen_delay_rise+self.sen_delay_fall
|
||||
self.wl_delay_rise, self.wl_delay_fall = self.get_delays_to_wl()
|
||||
self.sen_delay_rise, self.sen_delay_fall = self.get_delays_to_sen()
|
||||
self.wl_delay = self.wl_delay_rise + self.wl_delay_fall
|
||||
self.sen_delay = self.sen_delay_rise + self.sen_delay_fall
|
||||
|
||||
def does_sen_rise_fall_timing_match(self):
|
||||
"""Compare the relative rise/fall delays of the sense amp enable and wordline"""
|
||||
self.set_sen_wl_delays()
|
||||
# This is not necessarily more reliable than total delay in some cases.
|
||||
if (self.wl_delay_rise*self.wl_timing_tolerance >= self.sen_delay_rise or
|
||||
self.wl_delay_fall*self.wl_timing_tolerance >= self.sen_delay_fall):
|
||||
if (self.wl_delay_rise * self.wl_timing_tolerance >= self.sen_delay_rise or
|
||||
self.wl_delay_fall * self.wl_timing_tolerance >= self.sen_delay_fall):
|
||||
return False
|
||||
else:
|
||||
return True
|
||||
|
|
@ -240,91 +237,107 @@ class control_logic(design.design):
|
|||
# The sen delay must always be bigger than than the wl
|
||||
# delay. This decides how much larger the sen delay must be
|
||||
# before a re-size is warranted.
|
||||
if self.wl_delay*self.wl_timing_tolerance >= self.sen_delay:
|
||||
if self.wl_delay * self.wl_timing_tolerance >= self.sen_delay:
|
||||
return False
|
||||
else:
|
||||
return True
|
||||
return True
|
||||
|
||||
def get_dynamic_delay_chain_size(self, previous_stages, previous_fanout):
|
||||
"""Determine the size of the delay chain used for the Sense Amp Enable using path delays"""
|
||||
from math import ceil
|
||||
previous_delay_chain_delay = (previous_fanout+1+self.inv_parasitic_delay)*previous_stages
|
||||
previous_delay_chain_delay = (previous_fanout + 1 + self.inv_parasitic_delay) * previous_stages
|
||||
debug.info(2, "Previous delay chain produced {} delay units".format(previous_delay_chain_delay))
|
||||
|
||||
delay_fanout = 3 # This can be anything >=2
|
||||
|
||||
# This can be anything >=2
|
||||
delay_fanout = 3
|
||||
# The delay chain uses minimum sized inverters. There are (fanout+1)*stages inverters and each
|
||||
# inverter adds 1 unit of delay (due to minimum size). This also depends on the pinv value
|
||||
required_delay = self.wl_delay*self.wl_timing_tolerance - (self.sen_delay-previous_delay_chain_delay)
|
||||
required_delay = self.wl_delay * self.wl_timing_tolerance - (self.sen_delay - previous_delay_chain_delay)
|
||||
debug.check(required_delay > 0, "Cannot size delay chain to have negative delay")
|
||||
delay_stages = ceil(required_delay/(delay_fanout+1+self.inv_parasitic_delay))
|
||||
if delay_stages%2 == 1: #force an even number of stages.
|
||||
delay_stages+=1
|
||||
delay_per_stage = delay_fanout + 1 + self.inv_parasitic_delay
|
||||
delay_stages = ceil(required_delay / delay_per_stage)
|
||||
# force an even number of stages.
|
||||
if delay_stages % 2 == 1:
|
||||
delay_stages += 1
|
||||
# Fanout can be varied as well but is a little more complicated but potentially optimal.
|
||||
debug.info(1, "Setting delay chain to {} stages with {} fanout to match {} delay".format(delay_stages, delay_fanout, required_delay))
|
||||
return (delay_stages, delay_fanout)
|
||||
|
||||
def get_dynamic_delay_fanout_list(self, previous_stages, previous_fanout):
|
||||
"""Determine the size of the delay chain used for the Sense Amp Enable using path delays"""
|
||||
|
||||
previous_delay_chain_delay = (previous_fanout+1+self.inv_parasitic_delay)*previous_stages
|
||||
|
||||
previous_delay_per_stage = previous_fanout + 1 + self.inv_parasitic_delay
|
||||
previous_delay_chain_delay = previous_delay_per_stage * previous_stages
|
||||
debug.info(2, "Previous delay chain produced {} delay units".format(previous_delay_chain_delay))
|
||||
|
||||
fanout_rise = fanout_fall = 2 # This can be anything >=2
|
||||
# The delay chain uses minimum sized inverters. There are (fanout+1)*stages inverters and each
|
||||
# inverter adds 1 unit of delay (due to minimum size). This also depends on the pinv value
|
||||
required_delay_fall = self.wl_delay_fall*self.wl_timing_tolerance - (self.sen_delay_fall-previous_delay_chain_delay/2)
|
||||
required_delay_rise = self.wl_delay_rise*self.wl_timing_tolerance - (self.sen_delay_rise-previous_delay_chain_delay/2)
|
||||
debug.info(2,"Required delays from chain: fall={}, rise={}".format(required_delay_fall,required_delay_rise))
|
||||
required_delay_fall = self.wl_delay_fall * self.wl_timing_tolerance - \
|
||||
(self.sen_delay_fall - previous_delay_chain_delay / 2)
|
||||
required_delay_rise = self.wl_delay_rise * self.wl_timing_tolerance - \
|
||||
(self.sen_delay_rise - previous_delay_chain_delay / 2)
|
||||
debug.info(2,
|
||||
"Required delays from chain: fall={}, rise={}".format(required_delay_fall,
|
||||
required_delay_rise))
|
||||
|
||||
# If the fanout is different between rise/fall by this amount. Stage algorithm is made more pessimistic.
|
||||
WARNING_FANOUT_DIFF = 5
|
||||
stages_close = False
|
||||
# The stages need to be equal (or at least a even number of stages with matching rise/fall delays)
|
||||
while True:
|
||||
stages_fall = self.calculate_stages_with_fixed_fanout(required_delay_fall,fanout_fall)
|
||||
stages_rise = self.calculate_stages_with_fixed_fanout(required_delay_rise,fanout_rise)
|
||||
debug.info(1,"Fall stages={}, rise stages={}".format(stages_fall,stages_rise))
|
||||
if abs(stages_fall-stages_rise) == 1 and not stages_close:
|
||||
stages_fall = self.calculate_stages_with_fixed_fanout(required_delay_fall,
|
||||
fanout_fall)
|
||||
stages_rise = self.calculate_stages_with_fixed_fanout(required_delay_rise,
|
||||
fanout_rise)
|
||||
debug.info(1,
|
||||
"Fall stages={}, rise stages={}".format(stages_fall,
|
||||
stages_rise))
|
||||
if abs(stages_fall - stages_rise) == 1 and not stages_close:
|
||||
stages_close = True
|
||||
safe_fanout_rise = fanout_rise
|
||||
safe_fanout_fall = fanout_fall
|
||||
|
||||
if stages_fall == stages_rise:
|
||||
if stages_fall == stages_rise:
|
||||
break
|
||||
elif abs(stages_fall-stages_rise) == 1 and WARNING_FANOUT_DIFF < abs(fanout_fall-fanout_rise):
|
||||
elif abs(stages_fall - stages_rise) == 1 and WARNING_FANOUT_DIFF < abs(fanout_fall - fanout_rise):
|
||||
debug.info(1, "Delay chain fanouts between stages are large. Making chain size larger for safety.")
|
||||
fanout_rise = safe_fanout_rise
|
||||
fanout_fall = safe_fanout_fall
|
||||
break
|
||||
# There should also be a condition to make sure the fanout does not get too large.
|
||||
# There should also be a condition to make sure the fanout does not get too large.
|
||||
# Otherwise, increase the fanout of delay with the most stages, calculate new stages
|
||||
elif stages_fall>stages_rise:
|
||||
fanout_fall+=1
|
||||
else:
|
||||
fanout_rise+=1
|
||||
|
||||
total_stages = max(stages_fall,stages_rise)*2
|
||||
total_stages = max(stages_fall, stages_rise) * 2
|
||||
debug.info(1, "New Delay chain: stages={}, fanout_rise={}, fanout_fall={}".format(total_stages, fanout_rise, fanout_fall))
|
||||
|
||||
# Creates interleaved fanout list of rise/fall delays. Assumes fall is the first stage.
|
||||
stage_list = [fanout_fall if i%2==0 else fanout_rise for i in range(total_stages)]
|
||||
stage_list = [fanout_fall if i % 2==0 else fanout_rise for i in range(total_stages)]
|
||||
return stage_list
|
||||
|
||||
def calculate_stages_with_fixed_fanout(self, required_delay, fanout):
|
||||
from math import ceil
|
||||
# Delay being negative is not an error. It implies that any amount of stages would have a negative effect on the overall delay
|
||||
if required_delay <= 3+self.inv_parasitic_delay: #3 is the minimum delay per stage (with pinv=0).
|
||||
# 3 is the minimum delay per stage (with pinv=0).
|
||||
if required_delay <= 3 + self.inv_parasitic_delay:
|
||||
return 1
|
||||
delay_stages = ceil(required_delay/(fanout+1+self.inv_parasitic_delay))
|
||||
delay_per_stage = fanout + 1 + self.inv_parasitic_delay
|
||||
delay_stages = ceil(required_delay / delay_per_stage)
|
||||
return delay_stages
|
||||
|
||||
def calculate_stage_list(self, total_stages, fanout_rise, fanout_fall):
|
||||
"""Produces a list of fanouts which determine the size of the delay chain. List length is the number of stages.
|
||||
Assumes the first stage is falling.
|
||||
"""
|
||||
Produces a list of fanouts which determine the size of the delay chain.
|
||||
List length is the number of stages.
|
||||
Assumes the first stage is falling.
|
||||
"""
|
||||
stage_list = []
|
||||
for i in range(total_stages):
|
||||
if i%2 == 0:
|
||||
if i % 2 == 0:
|
||||
stage_list.append()
|
||||
|
||||
def setup_signal_busses(self):
|
||||
|
|
@ -351,7 +364,7 @@ class control_logic(design.design):
|
|||
else:
|
||||
self.internal_bus_list = ["rbl_bl_delay_bar", "rbl_bl_delay", "gated_clk_bar", "gated_clk_buf", "clk_buf", "cs"]
|
||||
# leave space for the bus plus one extra space
|
||||
self.internal_bus_width = (len(self.internal_bus_list)+1)*self.m2_pitch
|
||||
self.internal_bus_width = (len(self.internal_bus_list) + 1) * self.m2_pitch
|
||||
|
||||
# Outputs to the bank
|
||||
if self.port_type == "rw":
|
||||
|
|
@ -366,15 +379,13 @@ class control_logic(design.design):
|
|||
|
||||
self.supply_list = ["vdd", "gnd"]
|
||||
|
||||
|
||||
def route_rails(self):
|
||||
""" Add the input signal inverted tracks """
|
||||
height = self.control_logic_center.y - self.m2_pitch
|
||||
offset = vector(self.ctrl_dff_array.width,0)
|
||||
offset = vector(self.ctrl_dff_array.width, 0)
|
||||
|
||||
self.rail_offsets = self.create_vertical_bus("m2", self.m2_pitch, offset, self.internal_bus_list, height)
|
||||
|
||||
|
||||
|
||||
def create_instances(self):
|
||||
""" Create all the instances """
|
||||
self.create_dffs()
|
||||
|
|
@ -388,9 +399,7 @@ class control_logic(design.design):
|
|||
if (self.port_type == "rw") or (self.port_type == "r"):
|
||||
self.create_sen_row()
|
||||
self.create_delay()
|
||||
self.create_pen_row()
|
||||
|
||||
|
||||
self.create_pen_row()
|
||||
|
||||
def place_instances(self):
|
||||
""" Place all the instances """
|
||||
|
|
@ -406,13 +415,13 @@ class control_logic(design.design):
|
|||
|
||||
row = 0
|
||||
# Add the logic on the right of the bus
|
||||
self.place_clk_buf_row(row)
|
||||
self.place_clk_buf_row(row)
|
||||
row += 1
|
||||
self.place_gated_clk_bar_row(row)
|
||||
self.place_gated_clk_bar_row(row)
|
||||
row += 1
|
||||
self.place_gated_clk_buf_row(row)
|
||||
self.place_gated_clk_buf_row(row)
|
||||
row += 1
|
||||
self.place_wlen_row(row)
|
||||
self.place_wlen_row(row)
|
||||
row += 1
|
||||
if (self.port_type == "rw") or (self.port_type == "w"):
|
||||
self.place_wen_row(row)
|
||||
|
|
@ -421,10 +430,10 @@ class control_logic(design.design):
|
|||
row += 1
|
||||
self.place_pen_row(row)
|
||||
row += 1
|
||||
if (self.port_type == "rw") or (self.port_type == "w"):
|
||||
if (self.port_type == "rw") or (self.port_type == "w"):
|
||||
self.place_rbl_delay_row(row)
|
||||
row += 1
|
||||
if (self.port_type == "rw") or (self.port_type == "r"):
|
||||
if (self.port_type == "rw") or (self.port_type == "r"):
|
||||
self.place_sen_row(row)
|
||||
row += 1
|
||||
self.place_delay(row)
|
||||
|
|
@ -435,11 +444,11 @@ class control_logic(design.design):
|
|||
self.control_logic_center = vector(self.ctrl_dff_inst.rx(), control_center_y)
|
||||
|
||||
# Extra pitch on top and right
|
||||
self.height = height + 2*self.m1_pitch
|
||||
self.height = height + 2 * self.m1_pitch
|
||||
# Max of modules or logic rows
|
||||
self.width = max([inst.rx() for inst in self.row_end_inst])
|
||||
if (self.port_type == "rw") or (self.port_type == "r"):
|
||||
self.width = max(self.delay_inst.rx() , self.width)
|
||||
self.width = max(self.delay_inst.rx(), self.width)
|
||||
self.width += self.m2_pitch
|
||||
|
||||
def route_all(self):
|
||||
|
|
@ -459,7 +468,6 @@ class control_logic(design.design):
|
|||
self.route_gated_clk_buf()
|
||||
self.route_supply()
|
||||
|
||||
|
||||
def create_delay(self):
|
||||
""" Create the replica bitline """
|
||||
self.delay_inst=self.add_inst(name="delay_chain",
|
||||
|
|
@ -467,9 +475,9 @@ class control_logic(design.design):
|
|||
# rbl_bl_delay is asserted (1) when the bitline has been discharged
|
||||
self.connect_inst(["rbl_bl", "rbl_bl_delay", "vdd", "gnd"])
|
||||
|
||||
def place_delay(self,row):
|
||||
def place_delay(self, row):
|
||||
""" Place the replica bitline """
|
||||
y_off = row * self.and2.height + 2*self.m1_pitch
|
||||
y_off = row * self.and2.height + 2 * self.m1_pitch
|
||||
|
||||
# Add the RBL above the rows
|
||||
# Add to the right of the control rows and routing channel
|
||||
|
|
@ -482,24 +490,22 @@ class control_logic(design.design):
|
|||
# Connect to the rail level with the vdd rail
|
||||
# Use pen since it is in every type of control logic
|
||||
vdd_ypos = self.p_en_bar_nand_inst.get_pin("vdd").by()
|
||||
in_pos = vector(self.rail_offsets["rbl_bl_delay"].x,vdd_ypos)
|
||||
mid1 = vector(out_pos.x,in_pos.y)
|
||||
self.add_wire(self.m1_stack,[out_pos, mid1, in_pos])
|
||||
in_pos = vector(self.rail_offsets["rbl_bl_delay"].x, vdd_ypos)
|
||||
mid1 = vector(out_pos.x, in_pos.y)
|
||||
self.add_wire(self.m1_stack, [out_pos, mid1, in_pos])
|
||||
self.add_via_center(layers=self.m1_stack,
|
||||
offset=in_pos)
|
||||
|
||||
|
||||
# Input from RBL goes to the delay line for futher delay
|
||||
self.copy_layout_pin(self.delay_inst, "in", "rbl_bl")
|
||||
|
||||
|
||||
def create_clk_buf_row(self):
|
||||
""" Create the multistage and gated clock buffer """
|
||||
self.clk_buf_inst = self.add_inst(name="clkbuf",
|
||||
mod=self.clk_buf_driver)
|
||||
self.connect_inst(["clk","clk_buf","vdd","gnd"])
|
||||
self.connect_inst(["clk", "clk_buf", "vdd", "gnd"])
|
||||
|
||||
def place_clk_buf_row(self,row):
|
||||
def place_clk_buf_row(self, row):
|
||||
x_offset = self.control_x_offset
|
||||
|
||||
x_offset = self.place_util(self.clk_buf_inst, x_offset, row)
|
||||
|
|
@ -512,17 +518,16 @@ class control_logic(design.design):
|
|||
self.add_layout_pin_segment_center(text="clk",
|
||||
layer="m2",
|
||||
start=clk_pos,
|
||||
end=clk_pos.scale(1,0))
|
||||
end=clk_pos.scale(1, 0))
|
||||
self.add_via_center(layers=self.m1_stack,
|
||||
offset=clk_pos)
|
||||
|
||||
|
||||
# Connect this at the bottom of the buffer
|
||||
out_pos = self.clk_buf_inst.get_pin("Z").center()
|
||||
mid1 = vector(out_pos.x,2*self.m2_pitch)
|
||||
mid1 = vector(out_pos.x, 2 * self.m2_pitch)
|
||||
mid2 = vector(self.rail_offsets["clk_buf"].x, mid1.y)
|
||||
bus_pos = self.rail_offsets["clk_buf"]
|
||||
self.add_wire(("m3","via2","m2"),[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_center(layers=self.m1_stack,
|
||||
offset=self.clk_buf_inst.get_pin("Z").center())
|
||||
|
|
@ -532,40 +537,45 @@ class control_logic(design.design):
|
|||
def create_gated_clk_bar_row(self):
|
||||
self.clk_bar_inst = self.add_inst(name="inv_clk_bar",
|
||||
mod=self.inv)
|
||||
self.connect_inst(["clk_buf","clk_bar","vdd","gnd"])
|
||||
self.connect_inst(["clk_buf", "clk_bar", "vdd", "gnd"])
|
||||
|
||||
self.gated_clk_bar_inst = self.add_inst(name="and2_gated_clk_bar",
|
||||
mod=self.and2)
|
||||
self.connect_inst(["cs","clk_bar","gated_clk_bar","vdd","gnd"])
|
||||
self.connect_inst(["cs", "clk_bar", "gated_clk_bar", "vdd", "gnd"])
|
||||
|
||||
def place_gated_clk_bar_row(self,row):
|
||||
def place_gated_clk_bar_row(self, row):
|
||||
x_offset = self.control_x_offset
|
||||
|
||||
x_offset = self.place_util(self.clk_bar_inst, x_offset, row)
|
||||
x_offset = self.place_util(self.gated_clk_bar_inst, x_offset, row)
|
||||
x_offset = self.place_util(self.gated_clk_bar_inst, x_offset, row)
|
||||
|
||||
self.row_end_inst.append(self.gated_clk_bar_inst)
|
||||
|
||||
def route_gated_clk_bar(self):
|
||||
clkbuf_map = zip(["A"], ["clk_buf"])
|
||||
self.connect_vertical_bus(clkbuf_map, self.clk_bar_inst, self.rail_offsets)
|
||||
self.connect_vertical_bus(clkbuf_map, self.clk_bar_inst, self.rail_offsets)
|
||||
|
||||
out_pos = self.clk_bar_inst.get_pin("Z").center()
|
||||
in_pos = self.gated_clk_bar_inst.get_pin("B").center()
|
||||
mid1 = vector(in_pos.x,out_pos.y)
|
||||
self.add_path("m1",[out_pos, mid1, in_pos])
|
||||
mid1 = vector(in_pos.x, out_pos.y)
|
||||
self.add_path("m1", [out_pos, mid1, in_pos])
|
||||
|
||||
# This is the second gate over, so it needs to be on M3
|
||||
clkbuf_map = zip(["A"], ["cs"])
|
||||
self.connect_vertical_bus(clkbuf_map, self.gated_clk_bar_inst, self.rail_offsets, ("m3", "via2", "m2"))
|
||||
self.connect_vertical_bus(clkbuf_map,
|
||||
self.gated_clk_bar_inst,
|
||||
self.rail_offsets,
|
||||
self.m2_stack[::-1])
|
||||
# The pin is on M1, so we need another via as well
|
||||
self.add_via_center(layers=self.m1_stack,
|
||||
offset=self.gated_clk_bar_inst.get_pin("A").center())
|
||||
|
||||
|
||||
# This is the second gate over, so it needs to be on M3
|
||||
clkbuf_map = zip(["Z"], ["gated_clk_bar"])
|
||||
self.connect_vertical_bus(clkbuf_map, self.gated_clk_bar_inst, self.rail_offsets, ("m3", "via2", "m2"))
|
||||
self.connect_vertical_bus(clkbuf_map,
|
||||
self.gated_clk_bar_inst,
|
||||
self.rail_offsets,
|
||||
self.m2_stack[::-1])
|
||||
# The pin is on M1, so we need another via as well
|
||||
self.add_via_center(layers=self.m1_stack,
|
||||
offset=self.gated_clk_bar_inst.get_pin("Z").center())
|
||||
|
|
@ -573,9 +583,9 @@ class control_logic(design.design):
|
|||
def create_gated_clk_buf_row(self):
|
||||
self.gated_clk_buf_inst = self.add_inst(name="and2_gated_clk_buf",
|
||||
mod=self.and2)
|
||||
self.connect_inst(["clk_buf", "cs","gated_clk_buf","vdd","gnd"])
|
||||
self.connect_inst(["clk_buf", "cs", "gated_clk_buf", "vdd", "gnd"])
|
||||
|
||||
def place_gated_clk_buf_row(self,row):
|
||||
def place_gated_clk_buf_row(self, row):
|
||||
x_offset = self.control_x_offset
|
||||
|
||||
x_offset = self.place_util(self.gated_clk_buf_inst, x_offset, row)
|
||||
|
|
@ -584,11 +594,13 @@ class control_logic(design.design):
|
|||
|
||||
def route_gated_clk_buf(self):
|
||||
clkbuf_map = zip(["A", "B"], ["clk_buf", "cs"])
|
||||
self.connect_vertical_bus(clkbuf_map, self.gated_clk_buf_inst, self.rail_offsets)
|
||||
self.connect_vertical_bus(clkbuf_map, self.gated_clk_buf_inst, self.rail_offsets)
|
||||
|
||||
|
||||
clkbuf_map = zip(["Z"], ["gated_clk_buf"])
|
||||
self.connect_vertical_bus(clkbuf_map, self.gated_clk_buf_inst, self.rail_offsets, ("m3", "via2", "m2"))
|
||||
self.connect_vertical_bus(clkbuf_map,
|
||||
self.gated_clk_buf_inst,
|
||||
self.rail_offsets,
|
||||
self.m2_stack[::-1])
|
||||
# The pin is on M1, so we need another via as well
|
||||
self.add_via_center(layers=self.m1_stack,
|
||||
offset=self.gated_clk_buf_inst.get_pin("Z").center())
|
||||
|
|
@ -602,7 +614,7 @@ class control_logic(design.design):
|
|||
def place_wlen_row(self, row):
|
||||
x_offset = self.control_x_offset
|
||||
|
||||
x_offset = self.place_util(self.wl_en_inst, x_offset, row)
|
||||
x_offset = self.place_util(self.wl_en_inst, x_offset, row)
|
||||
|
||||
self.row_end_inst.append(self.wl_en_inst)
|
||||
|
||||
|
|
@ -623,11 +635,11 @@ class control_logic(design.design):
|
|||
mod=self.p_en_bar_driver)
|
||||
self.connect_inst(["p_en_bar_unbuf", "p_en_bar", "vdd", "gnd"])
|
||||
|
||||
def place_pen_row(self,row):
|
||||
def place_pen_row(self, row):
|
||||
x_offset = self.control_x_offset
|
||||
|
||||
x_offset = self.place_util(self.p_en_bar_nand_inst, x_offset, row)
|
||||
x_offset = self.place_util(self.p_en_bar_driver_inst, x_offset, row)
|
||||
x_offset = self.place_util(self.p_en_bar_nand_inst, x_offset, row)
|
||||
x_offset = self.place_util(self.p_en_bar_driver_inst, x_offset, row)
|
||||
|
||||
self.row_end_inst.append(self.p_en_bar_driver_inst)
|
||||
|
||||
|
|
@ -637,8 +649,8 @@ class control_logic(design.design):
|
|||
|
||||
out_pos = self.p_en_bar_nand_inst.get_pin("Z").rc()
|
||||
in_pos = self.p_en_bar_driver_inst.get_pin("A").lc()
|
||||
mid1 = vector(out_pos.x,in_pos.y)
|
||||
self.add_wire(self.m1_stack,[out_pos, mid1,in_pos])
|
||||
mid1 = vector(out_pos.x, in_pos.y)
|
||||
self.add_wire(self.m1_stack, [out_pos, mid1, in_pos])
|
||||
|
||||
self.connect_output(self.p_en_bar_driver_inst, "Z", "p_en_bar")
|
||||
|
||||
|
|
@ -656,14 +668,12 @@ class control_logic(design.design):
|
|||
# hence we use rbl_bl_delay as well.
|
||||
self.connect_inst(["rbl_bl_delay", "gated_clk_bar", input_name, "s_en", "vdd", "gnd"])
|
||||
|
||||
|
||||
def place_sen_row(self,row):
|
||||
def place_sen_row(self, row):
|
||||
x_offset = self.control_x_offset
|
||||
|
||||
x_offset = self.place_util(self.s_en_gate_inst, x_offset, row)
|
||||
|
||||
self.row_end_inst.append(self.s_en_gate_inst)
|
||||
|
||||
|
||||
def route_sen(self):
|
||||
|
||||
|
|
@ -683,7 +693,7 @@ class control_logic(design.design):
|
|||
mod=self.inv)
|
||||
self.connect_inst(["rbl_bl_delay", "rbl_bl_delay_bar", "vdd", "gnd"])
|
||||
|
||||
def place_rbl_delay_row(self,row):
|
||||
def place_rbl_delay_row(self, row):
|
||||
x_offset = self.control_x_offset
|
||||
|
||||
x_offset = self.place_util(self.rbl_bl_delay_inv_inst, x_offset, row)
|
||||
|
|
@ -700,11 +710,9 @@ class control_logic(design.design):
|
|||
self.add_via_center(layers=self.m1_stack,
|
||||
offset=self.rbl_bl_delay_inv_inst.get_pin("Z").center())
|
||||
|
||||
|
||||
rbl_map = zip(["A"], ["rbl_bl_delay"])
|
||||
self.connect_vertical_bus(rbl_map, self.rbl_bl_delay_inv_inst, self.rail_offsets)
|
||||
|
||||
|
||||
def create_wen_row(self):
|
||||
|
||||
# input: we (or cs) output: w_en
|
||||
|
|
@ -720,8 +728,7 @@ class control_logic(design.design):
|
|||
# Only drive the writes in the second half of the clock cycle during a write operation.
|
||||
self.connect_inst([input_name, "rbl_bl_delay_bar", "gated_clk_bar", "w_en", "vdd", "gnd"])
|
||||
|
||||
|
||||
def place_wen_row(self,row):
|
||||
def place_wen_row(self, row):
|
||||
x_offset = self.control_x_offset
|
||||
|
||||
x_offset = self.place_util(self.w_en_gate_inst, x_offset, row)
|
||||
|
|
@ -750,22 +757,22 @@ class control_logic(design.design):
|
|||
self.connect_inst(inst_pins)
|
||||
|
||||
def place_dffs(self):
|
||||
self.ctrl_dff_inst.place(vector(0,0))
|
||||
self.ctrl_dff_inst.place(vector(0, 0))
|
||||
|
||||
def route_dffs(self):
|
||||
if self.port_type == "rw":
|
||||
dff_out_map = zip(["dout_bar_0", "dout_bar_1", "dout_1"], ["cs", "we", "we_bar"])
|
||||
elif self.port_type == "r":
|
||||
dff_out_map = zip(["dout_bar_0", "dout_0"], ["cs", "cs_bar"])
|
||||
dff_out_map = zip(["dout_bar_0", "dout_0"], ["cs", "cs_bar"])
|
||||
else:
|
||||
dff_out_map = zip(["dout_bar_0"], ["cs"])
|
||||
self.connect_vertical_bus(dff_out_map, self.ctrl_dff_inst, self.rail_offsets, ("m3", "via2", "m2"))
|
||||
|
||||
# Connect the clock rail to the other clock rail
|
||||
in_pos = self.ctrl_dff_inst.get_pin("clk").uc()
|
||||
mid_pos = in_pos + vector(0,2*self.m2_pitch)
|
||||
mid_pos = in_pos + vector(0, 2 * self.m2_pitch)
|
||||
rail_pos = vector(self.rail_offsets["clk_buf"].x, mid_pos.y)
|
||||
self.add_wire(self.m1_stack,[in_pos, mid_pos, rail_pos])
|
||||
self.add_wire(self.m1_stack, [in_pos, mid_pos, rail_pos])
|
||||
self.add_via_center(layers=self.m1_stack,
|
||||
offset=rail_pos)
|
||||
|
||||
|
|
@ -773,34 +780,31 @@ class control_logic(design.design):
|
|||
if (self.port_type == "rw"):
|
||||
self.copy_layout_pin(self.ctrl_dff_inst, "din_1", "web")
|
||||
|
||||
def get_offset(self,row):
|
||||
def get_offset(self, row):
|
||||
""" Compute the y-offset and mirroring """
|
||||
y_off = row*self.and2.height
|
||||
y_off = row * self.and2.height
|
||||
if row % 2:
|
||||
y_off += self.and2.height
|
||||
mirror="MX"
|
||||
else:
|
||||
mirror="R0"
|
||||
|
||||
return (y_off,mirror)
|
||||
|
||||
return (y_off, mirror)
|
||||
|
||||
def connect_output(self, inst, pin_name, out_name):
|
||||
""" Create an output pin on the right side from the pin of a given instance. """
|
||||
|
||||
out_pin = inst.get_pin(pin_name)
|
||||
right_pos=out_pin.center() + vector(self.width-out_pin.cx(),0)
|
||||
right_pos = out_pin.center() + vector(self.width - out_pin.cx(), 0)
|
||||
self.add_layout_pin_segment_center(text=out_name,
|
||||
layer="m1",
|
||||
start=out_pin.center(),
|
||||
end=right_pos)
|
||||
|
||||
|
||||
|
||||
def route_supply(self):
|
||||
""" Add vdd and gnd to the instance cells """
|
||||
|
||||
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:
|
||||
pins = inst.get_pins("vdd")
|
||||
for pin in pins:
|
||||
|
|
@ -818,16 +822,14 @@ class control_logic(design.design):
|
|||
self.add_power_pin("gnd", pin_loc)
|
||||
self.add_path("m1", [row_loc, pin_loc])
|
||||
|
||||
self.copy_layout_pin(self.delay_inst,"gnd")
|
||||
self.copy_layout_pin(self.delay_inst,"vdd")
|
||||
self.copy_layout_pin(self.delay_inst, "gnd")
|
||||
self.copy_layout_pin(self.delay_inst, "vdd")
|
||||
|
||||
self.copy_layout_pin(self.ctrl_dff_inst,"gnd")
|
||||
self.copy_layout_pin(self.ctrl_dff_inst,"vdd")
|
||||
self.copy_layout_pin(self.ctrl_dff_inst, "gnd")
|
||||
self.copy_layout_pin(self.ctrl_dff_inst, "vdd")
|
||||
|
||||
|
||||
|
||||
def add_lvs_correspondence_points(self):
|
||||
""" This adds some points for easier debugging if LVS goes wrong.
|
||||
""" This adds some points for easier debugging if LVS goes wrong.
|
||||
These should probably be turned off by default though, since extraction
|
||||
will show these as ports in the extracted netlist.
|
||||
"""
|
||||
|
|
@ -851,74 +853,79 @@ class control_logic(design.design):
|
|||
offset=pin.ll(),
|
||||
height=pin.height(),
|
||||
width=pin.width())
|
||||
|
||||
|
||||
def get_delays_to_wl(self):
|
||||
"""Get the delay (in delay units) of the clk to a wordline in the bitcell array"""
|
||||
debug.check(self.sram.all_mods_except_control_done, "Cannot calculate sense amp enable delay unless all module have been added.")
|
||||
self.wl_stage_efforts = self.get_wordline_stage_efforts()
|
||||
clk_to_wl_rise,clk_to_wl_fall = logical_effort.calculate_relative_rise_fall_delays(self.wl_stage_efforts)
|
||||
total_delay = clk_to_wl_rise + clk_to_wl_fall
|
||||
debug.info(1, "Clock to wl delay is rise={:.3f}, fall={:.3f}, total={:.3f} in delay units".format(clk_to_wl_rise, clk_to_wl_fall,total_delay))
|
||||
return clk_to_wl_rise,clk_to_wl_fall
|
||||
clk_to_wl_rise, clk_to_wl_fall = logical_effort.calculate_relative_rise_fall_delays(self.wl_stage_efforts)
|
||||
total_delay = clk_to_wl_rise + clk_to_wl_fall
|
||||
debug.info(1,
|
||||
"Clock to wl delay is rise={:.3f}, fall={:.3f}, total={:.3f} in delay units".format(clk_to_wl_rise,
|
||||
clk_to_wl_fall,
|
||||
total_delay))
|
||||
return clk_to_wl_rise, clk_to_wl_fall
|
||||
|
||||
|
||||
def get_wordline_stage_efforts(self):
|
||||
"""Follows the gated_clk_bar -> wl_en -> wordline signal for the total path efforts"""
|
||||
stage_effort_list = []
|
||||
|
||||
#Initial direction of gated_clk_bar signal for this path
|
||||
# Initial direction of gated_clk_bar signal for this path
|
||||
is_clk_bar_rise = True
|
||||
|
||||
#Calculate the load on wl_en within the module and add it to external load
|
||||
# Calculate the load on wl_en within the module and add it to external load
|
||||
external_cout = self.sram.get_wl_en_cin()
|
||||
#First stage is the clock buffer
|
||||
# First stage is the clock buffer
|
||||
stage_effort_list += self.clk_buf_driver.get_stage_efforts(external_cout, is_clk_bar_rise)
|
||||
last_stage_is_rise = stage_effort_list[-1].is_rise
|
||||
|
||||
#Then ask the sram for the other path delays (from the bank)
|
||||
# Then ask the sram for the other path delays (from the bank)
|
||||
stage_effort_list += self.sram.get_wordline_stage_efforts(last_stage_is_rise)
|
||||
|
||||
return stage_effort_list
|
||||
|
||||
def get_delays_to_sen(self):
|
||||
"""Get the delay (in delay units) of the clk to a sense amp enable.
|
||||
This does not incorporate the delay of the replica bitline.
|
||||
"""
|
||||
Get the delay (in delay units) of the clk to a sense amp enable.
|
||||
This does not incorporate the delay of the replica bitline.
|
||||
"""
|
||||
debug.check(self.sram.all_mods_except_control_done, "Cannot calculate sense amp enable delay unless all module have been added.")
|
||||
self.sen_stage_efforts = self.get_sa_enable_stage_efforts()
|
||||
clk_to_sen_rise, clk_to_sen_fall = logical_effort.calculate_relative_rise_fall_delays(self.sen_stage_efforts)
|
||||
total_delay = clk_to_sen_rise + clk_to_sen_fall
|
||||
debug.info(1, "Clock to s_en delay is rise={:.3f}, fall={:.3f}, total={:.3f} in delay units".format(clk_to_sen_rise, clk_to_sen_fall,total_delay))
|
||||
return clk_to_sen_rise, clk_to_sen_fall
|
||||
total_delay = clk_to_sen_rise + clk_to_sen_fall
|
||||
debug.info(1,
|
||||
"Clock to s_en delay is rise={:.3f}, fall={:.3f}, total={:.3f} in delay units".format(clk_to_sen_rise,
|
||||
clk_to_sen_fall,
|
||||
total_delay))
|
||||
return clk_to_sen_rise, clk_to_sen_fall
|
||||
|
||||
def get_sa_enable_stage_efforts(self):
|
||||
"""Follows the gated_clk_bar signal to the sense amp enable signal adding each stages stage effort to a list"""
|
||||
stage_effort_list = []
|
||||
|
||||
#Initial direction of clock signal for this path
|
||||
# Initial direction of clock signal for this path
|
||||
last_stage_rise = True
|
||||
|
||||
#First stage, gated_clk_bar -(and2)-> rbl_in. Only for RW ports.
|
||||
# First stage, gated_clk_bar -(and2)-> rbl_in. Only for RW ports.
|
||||
if self.port_type == "rw":
|
||||
stage1_cout = self.replica_bitline.get_en_cin()
|
||||
stage_effort_list += self.and2.get_stage_efforts(stage1_cout, last_stage_rise)
|
||||
last_stage_rise = stage_effort_list[-1].is_rise
|
||||
|
||||
#Replica bitline stage, rbl_in -(rbl)-> pre_s_en
|
||||
# Replica bitline stage, rbl_in -(rbl)-> pre_s_en
|
||||
stage2_cout = self.sen_and2.get_cin()
|
||||
stage_effort_list += self.replica_bitline.determine_sen_stage_efforts(stage2_cout, last_stage_rise)
|
||||
last_stage_rise = stage_effort_list[-1].is_rise
|
||||
|
||||
#buffer stage, pre_s_en -(buffer)-> s_en
|
||||
# buffer stage, pre_s_en -(buffer)-> s_en
|
||||
stage3_cout = self.sram.get_sen_cin()
|
||||
stage_effort_list += self.s_en_driver.get_stage_efforts(stage3_cout, last_stage_rise)
|
||||
last_stage_rise = stage_effort_list[-1].is_rise
|
||||
|
||||
return stage_effort_list
|
||||
return stage_effort_list
|
||||
|
||||
def get_wl_sen_delays(self):
|
||||
"""Gets a list of the stages and delays in order of their path."""
|
||||
""" Gets a list of the stages and delays in order of their path. """
|
||||
|
||||
if self.sen_stage_efforts == None or self.wl_stage_efforts == None:
|
||||
debug.error("Model delays not calculated for SRAM.", 1)
|
||||
|
|
@ -927,45 +934,45 @@ class control_logic(design.design):
|
|||
return wl_delays, sen_delays
|
||||
|
||||
def analytical_delay(self, corner, slew, load):
|
||||
"""Gets the analytical delay from clk input to wl_en output"""
|
||||
""" Gets the analytical delay from clk input to wl_en output """
|
||||
|
||||
stage_effort_list = []
|
||||
#Calculate the load on clk_buf_bar
|
||||
ext_clk_buf_cout = self.sram.get_clk_bar_cin()
|
||||
# Calculate the load on clk_buf_bar
|
||||
# ext_clk_buf_cout = self.sram.get_clk_bar_cin()
|
||||
|
||||
#Operations logic starts on negative edge
|
||||
last_stage_rise = False
|
||||
# Operations logic starts on negative edge
|
||||
last_stage_rise = False
|
||||
|
||||
#First stage(s), clk -(pdriver)-> clk_buf.
|
||||
#clk_buf_cout = self.replica_bitline.get_en_cin()
|
||||
# First stage(s), clk -(pdriver)-> clk_buf.
|
||||
# clk_buf_cout = self.replica_bitline.get_en_cin()
|
||||
clk_buf_cout = 0
|
||||
stage_effort_list += self.clk_buf_driver.get_stage_efforts(clk_buf_cout, last_stage_rise)
|
||||
last_stage_rise = stage_effort_list[-1].is_rise
|
||||
|
||||
#Second stage, clk_buf -(inv)-> clk_bar
|
||||
# Second stage, clk_buf -(inv)-> clk_bar
|
||||
clk_bar_cout = self.and2.get_cin()
|
||||
stage_effort_list += self.and2.get_stage_efforts(clk_bar_cout, last_stage_rise)
|
||||
last_stage_rise = stage_effort_list[-1].is_rise
|
||||
|
||||
#Third stage clk_bar -(and)-> gated_clk_bar
|
||||
# Third stage clk_bar -(and)-> gated_clk_bar
|
||||
gated_clk_bar_cin = self.get_gated_clk_bar_cin()
|
||||
stage_effort_list.append(self.inv.get_stage_effort(gated_clk_bar_cin, last_stage_rise))
|
||||
last_stage_rise = stage_effort_list[-1].is_rise
|
||||
|
||||
#Stages from gated_clk_bar -------> wordline
|
||||
# Stages from gated_clk_bar -------> wordline
|
||||
stage_effort_list += self.get_wordline_stage_efforts()
|
||||
return stage_effort_list
|
||||
|
||||
def get_clk_buf_cin(self):
|
||||
"""
|
||||
Get the loads that are connected to the buffered clock.
|
||||
Get the loads that are connected to the buffered clock.
|
||||
Includes all the DFFs and some logic.
|
||||
"""
|
||||
|
||||
#Control logic internal load
|
||||
# Control logic internal load
|
||||
int_clk_buf_cap = self.inv.get_cin() + self.ctrl_dff_array.get_clk_cin() + self.and2.get_cin()
|
||||
|
||||
#Control logic external load (in the other parts of the SRAM)
|
||||
# Control logic external load (in the other parts of the SRAM)
|
||||
ext_clk_buf_cap = self.sram.get_clk_bar_cin()
|
||||
|
||||
return int_clk_buf_cap + ext_clk_buf_cap
|
||||
|
|
@ -976,7 +983,7 @@ class control_logic(design.design):
|
|||
total_cin = 0
|
||||
total_cin += self.wl_en_driver.get_cin()
|
||||
if self.port_type == 'rw':
|
||||
total_cin +=self.and2.get_cin()
|
||||
total_cin += self.and2.get_cin()
|
||||
return total_cin
|
||||
|
||||
def graph_exclude_dffs(self):
|
||||
|
|
@ -989,7 +996,7 @@ class control_logic(design.design):
|
|||
def place_util(self, inst, x_offset, row):
|
||||
""" Utility to place a row and compute the next offset """
|
||||
|
||||
(y_offset,mirror)=self.get_offset(row)
|
||||
(y_offset, mirror) = self.get_offset(row)
|
||||
offset = vector(x_offset, y_offset)
|
||||
inst.place(offset, mirror)
|
||||
return x_offset+inst.width
|
||||
return x_offset + inst.width
|
||||
|
|
|
|||
|
|
@ -7,13 +7,13 @@
|
|||
#
|
||||
import debug
|
||||
import design
|
||||
from tech import drc,parameter
|
||||
from tech import parameter
|
||||
from tech import cell_properties as props
|
||||
from math import log
|
||||
from vector import vector
|
||||
from globals import OPTS
|
||||
from sram_factory import factory
|
||||
|
||||
|
||||
class dff_buf(design.design):
|
||||
"""
|
||||
This is a simple buffered DFF. The output is buffered
|
||||
|
|
@ -107,13 +107,23 @@ class dff_buf(design.design):
|
|||
self.dff_inst.place(vector(0,0))
|
||||
|
||||
# Add INV1 to the right
|
||||
well_spacing = max(self.nwell_space,
|
||||
self.pwell_space,
|
||||
self.pwell_to_nwell)
|
||||
self.inv1_inst.place(vector(self.dff_inst.rx() + well_spacing + self.well_extend_active,0))
|
||||
well_spacing = 0
|
||||
try:
|
||||
well_spacing = max(well_spacing, self.nwell_space)
|
||||
except AttributeError:
|
||||
pass
|
||||
try:
|
||||
well_spacing = max(well_spacing, self.pwell_space)
|
||||
except AttributeError:
|
||||
pass
|
||||
try:
|
||||
well_spacing = max(well_spacing, self.pwell_to_nwell)
|
||||
except AttributeError:
|
||||
pass
|
||||
self.inv1_inst.place(vector(self.dff_inst.rx() + well_spacing + self.well_extend_active, 0))
|
||||
|
||||
# Add INV2 to the right
|
||||
self.inv2_inst.place(vector(self.inv1_inst.rx(),0))
|
||||
self.inv2_inst.place(vector(self.inv1_inst.rx(), 0))
|
||||
|
||||
def route_wires(self):
|
||||
# Route dff q to inv1 a
|
||||
|
|
|
|||
|
|
@ -11,13 +11,15 @@ import math
|
|||
from sram_factory import factory
|
||||
from vector import vector
|
||||
from globals import OPTS
|
||||
from errors import drc_error
|
||||
from tech import cell_properties
|
||||
|
||||
|
||||
class hierarchical_decoder(design.design):
|
||||
"""
|
||||
Dynamically generated hierarchical decoder.
|
||||
"""
|
||||
def __init__(self, name, rows):
|
||||
def __init__(self, name, num_outputs):
|
||||
design.design.__init__(self, name)
|
||||
|
||||
self.AND_FORMAT = "DEC_AND_{0}"
|
||||
|
|
@ -25,9 +27,16 @@ class hierarchical_decoder(design.design):
|
|||
self.pre2x4_inst = []
|
||||
self.pre3x8_inst = []
|
||||
|
||||
(self.cell_height, self.cell_multiple) = self.find_decoder_height()
|
||||
self.rows = rows
|
||||
self.num_inputs = math.ceil(math.log(self.rows, 2))
|
||||
b = factory.create(module_type="bitcell")
|
||||
try:
|
||||
self.cell_multiple = cell_properties.bitcell.decoder_bitcell_multiple
|
||||
except AttributeError:
|
||||
self.cell_multiple = 1
|
||||
# For debugging
|
||||
self.cell_height = self.cell_multiple * b.height
|
||||
|
||||
self.num_outputs = num_outputs
|
||||
self.num_inputs = math.ceil(math.log(self.num_outputs, 2))
|
||||
(self.no_of_pre2x4, self.no_of_pre3x8)=self.determine_predecodes(self.num_inputs)
|
||||
|
||||
self.create_netlist()
|
||||
|
|
@ -35,20 +44,37 @@ class hierarchical_decoder(design.design):
|
|||
self.create_layout()
|
||||
|
||||
def find_decoder_height(self):
|
||||
"""
|
||||
Dead code. This would dynamically determine the bitcell multiple,
|
||||
but I just decided to hard code it in the tech file if it is not 1
|
||||
because a DRC tool would be required even to run in front-end mode.
|
||||
"""
|
||||
b = factory.create(module_type="bitcell")
|
||||
|
||||
# Old behavior
|
||||
return (b.height, 1)
|
||||
if OPTS.netlist_only:
|
||||
return (b.height, 1)
|
||||
|
||||
# Search for the smallest multiple that works
|
||||
cell_multiple = 1
|
||||
while cell_multiple < 3:
|
||||
while cell_multiple < 5:
|
||||
cell_height = cell_multiple * b.height
|
||||
and3 = factory.create(module_type="pand3",
|
||||
height=cell_height)
|
||||
(drc_errors, lvs_errors) = and3.DRC_LVS(force_check=True)
|
||||
if drc_errors + lvs_errors == 0:
|
||||
return (cell_height, cell_multiple)
|
||||
# debug.info(2,"Trying mult = {0} height={1}".format(cell_multiple, cell_height))
|
||||
try:
|
||||
and3 = factory.create(module_type="pand3",
|
||||
height=cell_height)
|
||||
except drc_error:
|
||||
# debug.info(1, "Incrementing decoder height by 1 bitcell height {}".format(b.height))
|
||||
pass
|
||||
else:
|
||||
(drc_errors, lvs_errors) = and3.DRC_LVS(force_check=True)
|
||||
total_errors = drc_errors + lvs_errors
|
||||
if total_errors == 0:
|
||||
debug.info(1, "Decoder height is multiple of {} bitcells.".format(cell_multiple))
|
||||
return (cell_height, cell_multiple)
|
||||
|
||||
cell_multiple += 1
|
||||
|
||||
else:
|
||||
debug.error("Couldn't find a valid decoder height multiple.", -1)
|
||||
|
||||
|
|
@ -63,8 +89,8 @@ class hierarchical_decoder(design.design):
|
|||
self.setup_layout_constants()
|
||||
self.place_pre_decoder()
|
||||
self.place_row_decoder()
|
||||
self.route_input_rails()
|
||||
self.route_predecode_rails()
|
||||
self.route_inputs()
|
||||
self.route_decoder_bus()
|
||||
self.route_vdd_gnd()
|
||||
self.offset_all_coordinates()
|
||||
self.add_boundary()
|
||||
|
|
@ -118,7 +144,7 @@ class hierarchical_decoder(design.design):
|
|||
def setup_netlist_constants(self):
|
||||
self.predec_groups = [] # This array is a 2D array.
|
||||
|
||||
# Distributing vertical rails to different groups. One group belongs to one pre-decoder.
|
||||
# Distributing vertical bus to different groups. One group belongs to one pre-decoder.
|
||||
# For example, for two 2:4 pre-decoder and one 3:8 pre-decoder, we will
|
||||
# have total 16 output lines out of these 3 pre-decoders and they will
|
||||
# be distributed as [ [0,1,2,3] ,[4,5,6,7], [8,9,10,11,12,13,14,15] ]
|
||||
|
|
@ -157,39 +183,46 @@ class hierarchical_decoder(design.design):
|
|||
|
||||
self.predecoder_height = self.pre2_4.height * self.no_of_pre2x4 + self.pre3_8.height * self.no_of_pre3x8
|
||||
|
||||
# We may have more than one bitcell per decoder row
|
||||
self.num_rows = math.ceil(self.num_outputs / self.cell_multiple)
|
||||
# We will place this many final decoders per row
|
||||
self.decoders_per_row = math.ceil(self.num_outputs / self.num_rows)
|
||||
|
||||
# Calculates height and width of row-decoder
|
||||
if (self.num_inputs == 4 or self.num_inputs == 5):
|
||||
nand_width = self.and2.width
|
||||
else:
|
||||
nand_width = self.and3.width
|
||||
self.internal_routing_width = self.m2_pitch * self.total_number_of_predecoder_outputs
|
||||
self.row_decoder_height = self.inv.height * self.rows
|
||||
self.internal_routing_width = self.m2_pitch * (self.total_number_of_predecoder_outputs + 1)
|
||||
self.row_decoder_height = self.inv.height * self.num_rows
|
||||
|
||||
self.input_routing_width = (self.num_inputs + 1) * self.m2_pitch
|
||||
self.input_routing_width = (self.num_inputs + 1) * self.m2_pitch
|
||||
# Calculates height and width of hierarchical decoder
|
||||
self.height = self.row_decoder_height
|
||||
# Add extra pitch for good measure
|
||||
self.height = max(self.predecoder_height, self.row_decoder_height) + self.m3_pitch
|
||||
self.width = self.input_routing_width + self.predecoder_width \
|
||||
+ self.internal_routing_width + nand_width + self.inv.width
|
||||
+ self.internal_routing_width \
|
||||
+ self.decoders_per_row * nand_width + self.inv.width
|
||||
|
||||
def route_input_rails(self):
|
||||
""" Create input rails for the predecoders """
|
||||
def route_inputs(self):
|
||||
""" Create input bus for the predecoders """
|
||||
# inputs should be as high as the decoders
|
||||
input_height = self.no_of_pre2x4 * self.pre2_4.height + self.no_of_pre3x8 * self.pre3_8.height
|
||||
|
||||
# Find the left-most predecoder
|
||||
min_x = 0
|
||||
if self.no_of_pre2x4 > 0:
|
||||
min_x = min(min_x, -self.pre2_4.width)
|
||||
min_x = min(min_x, self.pre2x4_inst[0].lx())
|
||||
if self.no_of_pre3x8 > 0:
|
||||
min_x = min(min_x, -self.pre3_8.width)
|
||||
min_x = min(min_x, self.pre3x8_inst[0].lx())
|
||||
input_offset=vector(min_x - self.input_routing_width, 0)
|
||||
|
||||
input_bus_names = ["addr_{0}".format(i) for i in range(self.num_inputs)]
|
||||
self.input_rails = self.create_vertical_pin_bus(layer="m2",
|
||||
pitch=self.m2_pitch,
|
||||
offset=input_offset,
|
||||
names=input_bus_names,
|
||||
length=input_height)
|
||||
self.input_bus = self.create_vertical_pin_bus(layer="m2",
|
||||
pitch=self.m2_pitch,
|
||||
offset=input_offset,
|
||||
names=input_bus_names,
|
||||
length=input_height)
|
||||
|
||||
self.route_input_to_predecodes()
|
||||
|
||||
|
|
@ -199,7 +232,7 @@ class hierarchical_decoder(design.design):
|
|||
for i in range(2):
|
||||
index = pre_num * 2 + i
|
||||
|
||||
input_pos = self.input_rails["addr_{}".format(index)]
|
||||
input_pos = self.input_bus["addr_{}".format(index)]
|
||||
|
||||
in_name = "in_{}".format(i)
|
||||
decoder_pin = self.pre2x4_inst[pre_num].get_pin(in_name)
|
||||
|
|
@ -209,13 +242,13 @@ class hierarchical_decoder(design.design):
|
|||
decoder_offset = decoder_pin.bc() + vector(0, (i + 1) * self.inv.height)
|
||||
input_offset = input_pos.scale(1, 0) + decoder_offset.scale(0, 1)
|
||||
|
||||
self.route_input_rail(decoder_offset, input_offset)
|
||||
self.route_input_bus(decoder_offset, input_offset)
|
||||
|
||||
for pre_num in range(self.no_of_pre3x8):
|
||||
for i in range(3):
|
||||
index = pre_num * 3 + i + self.no_of_pre2x4 * 2
|
||||
|
||||
input_pos = self.input_rails["addr_{}".format(index)]
|
||||
input_pos = self.input_bus["addr_{}".format(index)]
|
||||
|
||||
in_name = "in_{}".format(i)
|
||||
decoder_pin = self.pre3x8_inst[pre_num].get_pin(in_name)
|
||||
|
|
@ -225,10 +258,13 @@ class hierarchical_decoder(design.design):
|
|||
decoder_offset = decoder_pin.bc() + vector(0, (i + 1) * self.inv.height)
|
||||
input_offset = input_pos.scale(1, 0) + decoder_offset.scale(0, 1)
|
||||
|
||||
self.route_input_rail(decoder_offset, input_offset)
|
||||
self.route_input_bus(decoder_offset, input_offset)
|
||||
|
||||
def route_input_rail(self, input_offset, output_offset):
|
||||
""" Route a vertical M2 coordinate to another vertical M2 coordinate to the predecode inputs """
|
||||
def route_input_bus(self, input_offset, output_offset):
|
||||
"""
|
||||
Route a vertical M2 coordinate to another
|
||||
vertical M2 coordinate to the predecode inputs
|
||||
"""
|
||||
|
||||
self.add_via_center(layers=self.m2_stack,
|
||||
offset=input_offset)
|
||||
|
|
@ -242,7 +278,7 @@ class hierarchical_decoder(design.design):
|
|||
for i in range(self.num_inputs):
|
||||
self.add_pin("addr_{0}".format(i), "INPUT")
|
||||
|
||||
for j in range(self.rows):
|
||||
for j in range(self.num_outputs):
|
||||
self.add_pin("decode_{0}".format(j), "OUTPUT")
|
||||
self.add_pin("vdd", "POWER")
|
||||
self.add_pin("gnd", "GROUND")
|
||||
|
|
@ -311,18 +347,17 @@ class hierarchical_decoder(design.design):
|
|||
else:
|
||||
base= vector(-self.pre2_4.width, num * self.pre2_4.height)
|
||||
|
||||
self.pre2x4_inst[num].place(base)
|
||||
self.pre2x4_inst[num].place(base - vector(2 * self.m2_pitch, 0))
|
||||
|
||||
def place_pre3x8(self, num):
|
||||
""" Place 3x8 predecoder to the left of the origin and above any 2x4 decoders """
|
||||
if (self.num_inputs == 3):
|
||||
offset = vector(-self.pre_3_8.width, 0)
|
||||
mirror = "R0"
|
||||
else:
|
||||
height = self.no_of_pre2x4 * self.pre2_4.height + num * self.pre3_8.height
|
||||
offset = vector(-self.pre3_8.width, height)
|
||||
|
||||
self.pre3x8_inst[num].place(offset)
|
||||
self.pre3x8_inst[num].place(offset - vector(2 * self.m2_pitch, 0))
|
||||
|
||||
def create_row_decoder(self):
|
||||
""" Create the row-decoder by placing AND2/AND3 and Inverters
|
||||
|
|
@ -339,14 +374,14 @@ class hierarchical_decoder(design.design):
|
|||
if (self.num_inputs == 4 or self.num_inputs == 5):
|
||||
for i in range(len(self.predec_groups[0])):
|
||||
for j in range(len(self.predec_groups[1])):
|
||||
row = len(self.predec_groups[0]) * j + i
|
||||
if (row < self.rows):
|
||||
name = self.AND_FORMAT.format(row)
|
||||
output = len(self.predec_groups[0]) * j + i
|
||||
if (output < self.num_outputs):
|
||||
name = self.AND_FORMAT.format(output)
|
||||
self.and_inst.append(self.add_inst(name=name,
|
||||
mod=self.and2))
|
||||
pins =["out_{0}".format(i),
|
||||
"out_{0}".format(j + len(self.predec_groups[0])),
|
||||
"decode_{0}".format(row),
|
||||
"decode_{0}".format(output),
|
||||
"vdd", "gnd"]
|
||||
self.connect_inst(pins)
|
||||
|
||||
|
|
@ -355,18 +390,18 @@ class hierarchical_decoder(design.design):
|
|||
for i in range(len(self.predec_groups[0])):
|
||||
for j in range(len(self.predec_groups[1])):
|
||||
for k in range(len(self.predec_groups[2])):
|
||||
row = (len(self.predec_groups[0]) * len(self.predec_groups[1])) * k \
|
||||
+ len(self.predec_groups[0]) * j + i
|
||||
output = (len(self.predec_groups[0]) * len(self.predec_groups[1])) * k \
|
||||
+ len(self.predec_groups[0]) * j + i
|
||||
|
||||
if (row < self.rows):
|
||||
name = self.AND_FORMAT.format(row)
|
||||
if (output < self.num_outputs):
|
||||
name = self.AND_FORMAT.format(output)
|
||||
self.and_inst.append(self.add_inst(name=name,
|
||||
mod=self.and3))
|
||||
|
||||
pins = ["out_{0}".format(i),
|
||||
"out_{0}".format(j + len(self.predec_groups[0])),
|
||||
"out_{0}".format(k + len(self.predec_groups[0]) + len(self.predec_groups[1])),
|
||||
"decode_{0}".format(row),
|
||||
"decode_{0}".format(output),
|
||||
"vdd", "gnd"]
|
||||
self.connect_inst(pins)
|
||||
|
||||
|
|
@ -380,7 +415,10 @@ class hierarchical_decoder(design.design):
|
|||
self.route_decoder()
|
||||
|
||||
def place_decoder_and_array(self):
|
||||
""" Add a column of AND gates for final decode """
|
||||
"""
|
||||
Add a column of AND gates for final decode.
|
||||
This may have more than one decoder per row to match the bitcell height.
|
||||
"""
|
||||
|
||||
# Row Decoder AND GATE array for address inputs <5.
|
||||
if (self.num_inputs == 4 or self.num_inputs == 5):
|
||||
|
|
@ -392,9 +430,13 @@ class hierarchical_decoder(design.design):
|
|||
self.place_and_array(and_mod=self.and3)
|
||||
|
||||
def place_and_array(self, and_mod):
|
||||
""" Add a column of AND gates for the decoder above the predecoders."""
|
||||
|
||||
for row in range(self.rows):
|
||||
"""
|
||||
Add a column of AND gates for the decoder above the predecoders.
|
||||
"""
|
||||
|
||||
for inst_index in range(self.num_outputs):
|
||||
row = math.floor(inst_index / self.decoders_per_row)
|
||||
dec = inst_index % self.decoders_per_row
|
||||
if ((row % 2) == 0):
|
||||
y_off = and_mod.height * row
|
||||
mirror = "R0"
|
||||
|
|
@ -402,46 +444,52 @@ class hierarchical_decoder(design.design):
|
|||
y_off = and_mod.height * (row + 1)
|
||||
mirror = "MX"
|
||||
|
||||
self.and_inst[row].place(offset=[self.internal_routing_width, y_off],
|
||||
mirror=mirror)
|
||||
x_off = self.internal_routing_width + dec * and_mod.width
|
||||
self.and_inst[inst_index].place(offset=vector(x_off, y_off),
|
||||
mirror=mirror)
|
||||
|
||||
def route_decoder(self):
|
||||
""" Add the pins. """
|
||||
|
||||
for row in range(self.rows):
|
||||
z_pin = self.and_inst[row].get_pin("Z")
|
||||
self.add_layout_pin(text="decode_{0}".format(row),
|
||||
for output in range(self.num_outputs):
|
||||
z_pin = self.and_inst[output].get_pin("Z")
|
||||
self.add_layout_pin(text="decode_{0}".format(output),
|
||||
layer="m1",
|
||||
offset=z_pin.ll(),
|
||||
width=z_pin.width(),
|
||||
height=z_pin.height())
|
||||
|
||||
def route_predecode_rails(self):
|
||||
""" Creates vertical metal 2 rails to connect predecoder and decoder stages."""
|
||||
def route_decoder_bus(self):
|
||||
"""
|
||||
Creates vertical metal 2 bus to connect predecoder and decoder stages.
|
||||
"""
|
||||
|
||||
# This is not needed for inputs <4 since they have no pre/decode stages.
|
||||
if (self.num_inputs >= 4):
|
||||
input_offset = vector(0.5 * self.m2_width, 0)
|
||||
# This leaves an offset for the predecoder output jogs
|
||||
input_bus_names = ["predecode_{0}".format(i) for i in range(self.total_number_of_predecoder_outputs)]
|
||||
self.predecode_rails = self.create_vertical_pin_bus(layer="m2",
|
||||
pitch=self.m2_pitch,
|
||||
offset=input_offset,
|
||||
names=input_bus_names,
|
||||
length=self.height)
|
||||
self.predecode_bus = self.create_vertical_pin_bus(layer="m2",
|
||||
pitch=self.m2_pitch,
|
||||
offset=vector(0, 0),
|
||||
names=input_bus_names,
|
||||
length=self.height)
|
||||
|
||||
self.route_rails_to_predecodes()
|
||||
self.route_rails_to_decoder()
|
||||
|
||||
def route_rails_to_predecodes(self):
|
||||
""" Iterates through all of the predecodes and connects to the rails including the offsets """
|
||||
self.route_predecodes_to_bus()
|
||||
self.route_bus_to_decoder()
|
||||
|
||||
def route_predecodes_to_bus(self):
|
||||
"""
|
||||
Iterates through all of the predecodes
|
||||
and connects to the rails including the offsets
|
||||
"""
|
||||
# FIXME: convert to connect_bus
|
||||
for pre_num in range(self.no_of_pre2x4):
|
||||
for i in range(4):
|
||||
predecode_name = "predecode_{}".format(pre_num * 4 + i)
|
||||
out_name = "out_{}".format(i)
|
||||
pin = self.pre2x4_inst[pre_num].get_pin(out_name)
|
||||
self.route_predecode_rail_m3(predecode_name, pin)
|
||||
x_offset = self.pre2x4_inst[pre_num].rx() + self.m2_pitch
|
||||
self.route_predecode_bus_inputs(predecode_name, pin, x_offset)
|
||||
|
||||
# FIXME: convert to connect_bus
|
||||
for pre_num in range(self.no_of_pre3x8):
|
||||
|
|
@ -449,52 +497,82 @@ class hierarchical_decoder(design.design):
|
|||
predecode_name = "predecode_{}".format(pre_num * 8 + i + self.no_of_pre2x4 * 4)
|
||||
out_name = "out_{}".format(i)
|
||||
pin = self.pre3x8_inst[pre_num].get_pin(out_name)
|
||||
self.route_predecode_rail_m3(predecode_name, pin)
|
||||
x_offset = self.pre3x8_inst[pre_num].rx() + self.m2_pitch
|
||||
self.route_predecode_bus_inputs(predecode_name, pin, x_offset)
|
||||
|
||||
def route_rails_to_decoder(self):
|
||||
""" Use the self.predec_groups to determine the connections to the decoder AND gates.
|
||||
Inputs of AND2/AND3 gates come from different groups.
|
||||
For example for these groups [ [0,1,2,3] ,[4,5,6,7],
|
||||
[8,9,10,11,12,13,14,15] ] the first AND3 inputs are connected to
|
||||
[0,4,8] and second AND3 is connected to [0,4,9] ........... and the
|
||||
128th AND3 is connected to [3,7,15]
|
||||
def route_bus_to_decoder(self):
|
||||
"""
|
||||
row_index = 0
|
||||
Use the self.predec_groups to determine the connections to the decoder AND gates.
|
||||
Inputs of AND2/AND3 gates come from different groups.
|
||||
For example for these groups
|
||||
[ [0,1,2,3] ,[4,5,6,7], [8,9,10,11,12,13,14,15] ]
|
||||
the first AND3 inputs are connected to [0,4,8],
|
||||
second AND3 is connected to [0,4,9],
|
||||
...
|
||||
and the 128th AND3 is connected to [3,7,15]
|
||||
"""
|
||||
output_index = 0
|
||||
|
||||
if (self.num_inputs == 4 or self.num_inputs == 5):
|
||||
for index_B in self.predec_groups[1]:
|
||||
for index_A in self.predec_groups[0]:
|
||||
# FIXME: convert to connect_bus?
|
||||
if (row_index < self.rows):
|
||||
if (output_index < self.num_outputs):
|
||||
row_index = math.floor(output_index / self.decoders_per_row)
|
||||
row_remainder = (output_index % self.decoders_per_row)
|
||||
row_offset = row_index * self.and_inst[0].height + (2 * row_remainder + 1) * self.m3_pitch
|
||||
predecode_name = "predecode_{}".format(index_A)
|
||||
self.route_predecode_rail(predecode_name, self.and_inst[row_index].get_pin("A"))
|
||||
self.route_predecode_bus_outputs(predecode_name,
|
||||
self.and_inst[output_index].get_pin("A"),
|
||||
row_offset)
|
||||
predecode_name = "predecode_{}".format(index_B)
|
||||
self.route_predecode_rail(predecode_name, self.and_inst[row_index].get_pin("B"))
|
||||
row_index = row_index + 1
|
||||
self.route_predecode_bus_outputs(predecode_name,
|
||||
self.and_inst[output_index].get_pin("B"),
|
||||
row_offset + self.m3_pitch)
|
||||
output_index = output_index + 1
|
||||
|
||||
elif (self.num_inputs > 5):
|
||||
for index_C in self.predec_groups[2]:
|
||||
for index_B in self.predec_groups[1]:
|
||||
for index_A in self.predec_groups[0]:
|
||||
# FIXME: convert to connect_bus?
|
||||
if (row_index < self.rows):
|
||||
if (output_index < self.num_outputs):
|
||||
row_index = math.floor(output_index / self.decoders_per_row)
|
||||
row_remainder = (output_index % self.decoders_per_row)
|
||||
row_offset = row_index * self.and_inst[0].height + (3 * row_remainder + 1) * self.m3_pitch
|
||||
predecode_name = "predecode_{}".format(index_A)
|
||||
self.route_predecode_rail(predecode_name, self.and_inst[row_index].get_pin("A"))
|
||||
self.route_predecode_bus_outputs(predecode_name,
|
||||
self.and_inst[output_index].get_pin("A"),
|
||||
row_offset)
|
||||
predecode_name = "predecode_{}".format(index_B)
|
||||
self.route_predecode_rail(predecode_name, self.and_inst[row_index].get_pin("B"))
|
||||
self.route_predecode_bus_outputs(predecode_name,
|
||||
self.and_inst[output_index].get_pin("B"),
|
||||
row_offset + self.m3_pitch)
|
||||
predecode_name = "predecode_{}".format(index_C)
|
||||
self.route_predecode_rail(predecode_name, self.and_inst[row_index].get_pin("C"))
|
||||
row_index = row_index + 1
|
||||
self.route_predecode_bus_outputs(predecode_name,
|
||||
self.and_inst[output_index].get_pin("C"),
|
||||
row_offset + 2 * self.m3_pitch)
|
||||
output_index = output_index + 1
|
||||
|
||||
def route_vdd_gnd(self):
|
||||
""" 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.
|
||||
"""
|
||||
|
||||
# The vias will be placed in the center and right of the cells, respectively.
|
||||
xoffset = self.and_inst[0].rx()
|
||||
for num in range(0, self.rows):
|
||||
# The vias will be placed at the right of the cells.
|
||||
xoffset = max(x.rx() for x in self.and_inst)
|
||||
for num in range(0, self.num_outputs):
|
||||
# Only add the power pin for the 1st in each row
|
||||
if num % self.decoders_per_row:
|
||||
continue
|
||||
|
||||
for pin_name in ["vdd", "gnd"]:
|
||||
# The nand and inv are the same height rows...
|
||||
supply_pin = self.and_inst[num].get_pin(pin_name)
|
||||
pin_pos = vector(xoffset, supply_pin.cy())
|
||||
self.add_path("m1",
|
||||
[supply_pin.lc(), vector(xoffset, supply_pin.cy())])
|
||||
self.add_power_pin(name=pin_name,
|
||||
loc=pin_pos)
|
||||
|
||||
|
|
@ -503,23 +581,42 @@ class hierarchical_decoder(design.design):
|
|||
self.copy_layout_pin(pre, "vdd")
|
||||
self.copy_layout_pin(pre, "gnd")
|
||||
|
||||
def route_predecode_rail(self, rail_name, pin):
|
||||
""" Connect the routing rail to the given metal1 pin """
|
||||
rail_pos = vector(self.predecode_rails[rail_name].x, pin.lc().y)
|
||||
self.add_path("m1", [rail_pos, pin.lc()])
|
||||
self.add_via_center(layers=self.m1_stack,
|
||||
offset=rail_pos)
|
||||
def route_predecode_bus_outputs(self, rail_name, pin, y_offset):
|
||||
"""
|
||||
Connect the routing rail to the given metal1 pin
|
||||
using a routing track at the given y_offset
|
||||
|
||||
"""
|
||||
pin_pos = pin.center()
|
||||
# If we have a single decoder per row, we can route on M1
|
||||
if self.decoders_per_row == 1:
|
||||
rail_pos = vector(self.predecode_bus[rail_name].x, pin_pos.y)
|
||||
self.add_path("m1", [rail_pos, pin_pos])
|
||||
self.add_via_center(layers=self.m1_stack,
|
||||
offset=rail_pos)
|
||||
# If not, we must route over the decoder cells on M3
|
||||
else:
|
||||
rail_pos = vector(self.predecode_bus[rail_name].x, y_offset)
|
||||
mid_pos = vector(pin_pos.x, rail_pos.y)
|
||||
self.add_wire(self.m2_stack[::-1], [rail_pos, mid_pos, pin_pos])
|
||||
self.add_via_center(layers=self.m2_stack,
|
||||
offset=rail_pos)
|
||||
self.add_via_center(layers=self.m1_stack,
|
||||
offset=pin_pos)
|
||||
|
||||
def route_predecode_rail_m3(self, rail_name, pin):
|
||||
""" Connect the routing rail to the given metal1 pin """
|
||||
def route_predecode_bus_inputs(self, rail_name, pin, x_offset):
|
||||
"""
|
||||
Connect the routing rail to the given metal1 pin using a jog
|
||||
to the right of the cell at the given x_offset.
|
||||
"""
|
||||
# This routes the pin up to the rail, basically, to avoid conflicts.
|
||||
# It would be fixed with a channel router.
|
||||
mid_point = vector(pin.cx(), pin.cy() + self.inv.height / 2)
|
||||
rail_pos = vector(self.predecode_rails[rail_name].x, mid_point.y)
|
||||
pin_pos = pin.center()
|
||||
mid_point1 = vector(x_offset, pin_pos.y)
|
||||
mid_point2 = vector(x_offset, pin_pos.y + self.inv.height / 2)
|
||||
rail_pos = vector(self.predecode_bus[rail_name].x, mid_point2.y)
|
||||
self.add_wire(self.m1_stack, [pin_pos, mid_point1, mid_point2, rail_pos])
|
||||
self.add_via_center(layers=self.m1_stack,
|
||||
offset=pin.center())
|
||||
self.add_wire(("m3", "via2", "m2"), [rail_pos, mid_point, pin.uc()])
|
||||
self.add_via_center(layers=self.m2_stack,
|
||||
offset=rail_pos)
|
||||
|
||||
def input_load(self):
|
||||
|
|
|
|||
|
|
@ -90,17 +90,14 @@ class port_address(design.design):
|
|||
# The pre/post is to access the pin from "outside" the cell to avoid DRCs
|
||||
decoder_out_pos = self.row_decoder_inst.get_pin("decode_{}".format(row)).rc()
|
||||
driver_in_pos = self.wordline_driver_inst.get_pin("in_{}".format(row)).lc()
|
||||
mid1 = decoder_out_pos.scale(0.5,1)+driver_in_pos.scale(0.5,0)
|
||||
mid2 = decoder_out_pos.scale(0.5,0)+driver_in_pos.scale(0.5,1)
|
||||
mid1 = decoder_out_pos.scale(0.5, 1) + driver_in_pos.scale(0.5, 0)
|
||||
mid2 = decoder_out_pos.scale(0.5, 0) + driver_in_pos.scale(0.5, 1)
|
||||
self.add_path("m1", [decoder_out_pos, mid1, mid2, driver_in_pos])
|
||||
|
||||
|
||||
|
||||
|
||||
def add_modules(self):
|
||||
|
||||
self.row_decoder = factory.create(module_type="decoder",
|
||||
rows=self.num_rows)
|
||||
num_outputs=self.num_rows)
|
||||
self.add_mod(self.row_decoder)
|
||||
|
||||
self.wordline_driver = factory.create(module_type="wordline_driver",
|
||||
|
|
@ -108,11 +105,10 @@ class port_address(design.design):
|
|||
cols=self.num_cols)
|
||||
self.add_mod(self.wordline_driver)
|
||||
|
||||
|
||||
def create_row_decoder(self):
|
||||
""" Create the hierarchical row decoder """
|
||||
|
||||
self.row_decoder_inst = self.add_inst(name="row_decoder",
|
||||
self.row_decoder_inst = self.add_inst(name="row_decoder",
|
||||
mod=self.row_decoder)
|
||||
|
||||
temp = []
|
||||
|
|
|
|||
|
|
@ -126,14 +126,16 @@ class sense_amp_array(design.design):
|
|||
for i in range(len(self.local_insts)):
|
||||
inst = self.local_insts[i]
|
||||
|
||||
gnd_pin = inst.get_pin("gnd")
|
||||
self.add_power_pin(name="gnd",
|
||||
loc=inst.get_pin("gnd").center(),
|
||||
start_layer="m2",
|
||||
loc=gnd_pin.center(),
|
||||
start_layer=gnd_pin.layer,
|
||||
vertical=True)
|
||||
|
||||
|
||||
vdd_pin = inst.get_pin("vdd")
|
||||
self.add_power_pin(name="vdd",
|
||||
loc=inst.get_pin("vdd").center(),
|
||||
start_layer="m2",
|
||||
loc=vdd_pin.center(),
|
||||
start_layer=vdd_pin.layer,
|
||||
vertical=True)
|
||||
|
||||
bl_pin = inst.get_pin(inst.mod.get_bl_names())
|
||||
|
|
|
|||
|
|
@ -166,7 +166,7 @@ class write_driver_array(design.design):
|
|||
self.add_power_pin(name=n,
|
||||
loc=pin.center(),
|
||||
vertical=True,
|
||||
start_layer="m2")
|
||||
start_layer=pin.layer)
|
||||
if self.write_size:
|
||||
for bit in range(self.num_wmasks):
|
||||
inst = self.driver_insts[bit * self.write_size]
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ from globals import OPTS
|
|||
from utils import round_to_grid
|
||||
import logical_effort
|
||||
from sram_factory import factory
|
||||
from errors import drc_error
|
||||
|
||||
|
||||
class pinv(pgate.pgate):
|
||||
|
|
@ -105,11 +106,14 @@ class pinv(pgate.pgate):
|
|||
# This is a poly-to-poly of a flipped cell
|
||||
self.top_bottom_space = max(0.5*self.m1_width + self.m1_space + extra_contact_space,
|
||||
self.poly_extend_active + self.poly_space)
|
||||
total_height = tx_height + min_channel + 2 * self.top_bottom_space
|
||||
|
||||
debug.check(self.height > total_height,
|
||||
"Cell height {0} too small for simple min height {1}.".format(self.height,
|
||||
total_height))
|
||||
total_height = tx_height + min_channel + 2 * self.top_bottom_space
|
||||
# debug.check(self.height > total_height,
|
||||
# "Cell height {0} too small for simple min height {1}.".format(self.height,
|
||||
# total_height))
|
||||
if total_height > self.height:
|
||||
msg = "Cell height {0} too small for simple min height {1}.".format(self.height, total_height)
|
||||
raise drc_error(msg)
|
||||
|
||||
# Determine the height left to the transistors to determine
|
||||
# the number of fingers
|
||||
|
|
@ -141,12 +145,16 @@ class pinv(pgate.pgate):
|
|||
# with LVS property mismatch errors when fingers are not a grid
|
||||
# length and get rounded in the offset geometry.
|
||||
self.nmos_width = round_to_grid(self.nmos_width / self.tx_mults)
|
||||
debug.check(self.nmos_width >= drc("minwidth_tx"),
|
||||
"Cannot finger NMOS transistors to fit cell height.")
|
||||
self.pmos_width = round_to_grid(self.pmos_width / self.tx_mults)
|
||||
debug.check(self.pmos_width >= drc("minwidth_tx"),
|
||||
"Cannot finger PMOS transistors to fit cell height.")
|
||||
# debug.check(self.nmos_width >= drc("minwidth_tx"),
|
||||
# "Cannot finger NMOS transistors to fit cell height.")
|
||||
if self.nmos_width < drc("minwidth_tx"):
|
||||
raise drc_error("Cannot finger NMOS transistors to fit cell height.")
|
||||
|
||||
self.pmos_width = round_to_grid(self.pmos_width / self.tx_mults)
|
||||
#debug.check(self.pmos_width >= drc("minwidth_tx"),
|
||||
# "Cannot finger PMOS transistors to fit cell height.")
|
||||
if self.pmos_width < drc("minwidth_tx"):
|
||||
raise drc_error("Cannot finger NMOS transistors to fit cell height.")
|
||||
|
||||
def add_ptx(self):
|
||||
""" Create the PMOS and NMOS transistors. """
|
||||
|
|
|
|||
|
|
@ -98,7 +98,7 @@ class pnand2(pgate.pgate):
|
|||
# metal spacing to allow contacts on any layer
|
||||
self.input_spacing = max(self.poly_space + contact.poly_contact.first_layer_width,
|
||||
self.m1_space + contact.m1_via.first_layer_width,
|
||||
self.m2_space + contact.m2_via.first_layer_width,
|
||||
self.m2_space + contact.m2_via.first_layer_width,
|
||||
self.m3_space + contact.m2_via.second_layer_width)
|
||||
|
||||
|
||||
|
|
@ -173,13 +173,15 @@ class pnand2(pgate.pgate):
|
|||
0.5 * (pmos1_pos.y + nmos1_pos.y + self.nmos_nd.active_height))
|
||||
|
||||
def add_well_contacts(self):
|
||||
"""
|
||||
"""
|
||||
Add n/p well taps to the layout and connect to supplies
|
||||
AFTER the wells are created
|
||||
"""
|
||||
|
||||
self.add_nwell_contact(self.pmos, self.pmos2_pos)
|
||||
self.add_pwell_contact(self.nmos_nd, self.nmos2_pos)
|
||||
self.add_nwell_contact(self.pmos,
|
||||
self.pmos2_pos + vector(self.m1_pitch, 0))
|
||||
self.add_pwell_contact(self.nmos_nd,
|
||||
self.nmos2_pos + vector(self.m1_pitch, 0))
|
||||
|
||||
def connect_rails(self):
|
||||
""" Connect the nmos and pmos to its respective power rails """
|
||||
|
|
@ -197,7 +199,7 @@ class pnand2(pgate.pgate):
|
|||
self.nmos2_inst,
|
||||
inputB_yoffset,
|
||||
"B",
|
||||
position="right")
|
||||
position="center")
|
||||
|
||||
# This will help with the wells and the input/output placement
|
||||
self.inputA_yoffset = self.pmos2_inst.by() - self.poly_extend_active \
|
||||
|
|
@ -209,6 +211,7 @@ class pnand2(pgate.pgate):
|
|||
|
||||
def route_output(self):
|
||||
""" Route the Z output """
|
||||
|
||||
# PMOS1 drain
|
||||
pmos_pin = self.pmos1_inst.get_pin("D")
|
||||
top_pin_offset = pmos_pin.center()
|
||||
|
|
@ -217,29 +220,46 @@ class pnand2(pgate.pgate):
|
|||
bottom_pin_offset = nmos_pin.center()
|
||||
|
||||
# Output pin
|
||||
out_offset = vector(nmos_pin.center().x + self.m1_pitch,
|
||||
c_pin = self.get_pin("B")
|
||||
out_offset = vector(c_pin.cx() + self.m1_pitch,
|
||||
self.inputA_yoffset)
|
||||
|
||||
# Midpoints of the L routes go horizontal first then vertical
|
||||
mid1_offset = vector(out_offset.x, top_pin_offset.y)
|
||||
# This routes on M2
|
||||
# # Midpoints of the L routes go horizontal first then vertical
|
||||
# mid1_offset = vector(out_offset.x, top_pin_offset.y)
|
||||
# mid2_offset = vector(out_offset.x, bottom_pin_offset.y)
|
||||
|
||||
# # Non-preferred active contacts
|
||||
# self.add_via_center(layers=self.m1_stack,
|
||||
# directions=("V", "H"),
|
||||
# offset=pmos_pin.center())
|
||||
# # Non-preferred active contacts
|
||||
# self.add_via_center(layers=self.m1_stack,
|
||||
# directions=("V", "H"),
|
||||
# offset=nmos_pin.center())
|
||||
# self.add_via_center(layers=self.m1_stack,
|
||||
# offset=out_offset)
|
||||
|
||||
# # PMOS1 to mid-drain to NMOS2 drain
|
||||
# self.add_path("m2",
|
||||
# [top_pin_offset, mid1_offset, out_offset,
|
||||
# mid2_offset, bottom_pin_offset])
|
||||
|
||||
# This routes on M1
|
||||
# Midpoints of the L routes goes vertical first then horizontal
|
||||
mid1_offset = vector(top_pin_offset.x, out_offset.y)
|
||||
# Midpoints of the L routes goes horizontal first then vertical
|
||||
mid2_offset = vector(out_offset.x, bottom_pin_offset.y)
|
||||
|
||||
# Non-preferred active contacts
|
||||
self.add_via_center(layers=self.m1_stack,
|
||||
directions=("V", "H"),
|
||||
offset=pmos_pin.center())
|
||||
# Non-preferred active contacts
|
||||
self.add_via_center(layers=self.m1_stack,
|
||||
directions=("V", "H"),
|
||||
offset=nmos_pin.center())
|
||||
self.add_via_center(layers=self.m1_stack,
|
||||
offset=out_offset)
|
||||
self.add_path("m1",
|
||||
[top_pin_offset, mid1_offset, out_offset])
|
||||
# Route in two segments to have the width rule
|
||||
self.add_path("m1",
|
||||
[bottom_pin_offset, mid2_offset + vector(0.5 * self.m1_width, 0)],
|
||||
width=nmos_pin.height())
|
||||
self.add_path("m1",
|
||||
[mid2_offset, out_offset])
|
||||
|
||||
# PMOS1 to mid-drain to NMOS2 drain
|
||||
self.add_path("m2",
|
||||
[top_pin_offset, mid1_offset, out_offset,
|
||||
mid2_offset, bottom_pin_offset])
|
||||
|
||||
# This extends the output to the edge of the cell
|
||||
self.add_layout_pin_rect_center(text="Z",
|
||||
layer="m1",
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ from tech import drc, parameter, spice
|
|||
from vector import vector
|
||||
import logical_effort
|
||||
from sram_factory import factory
|
||||
from globals import OPTS
|
||||
|
||||
|
||||
class pnand3(pgate.pgate):
|
||||
|
|
@ -190,8 +191,10 @@ class pnand3(pgate.pgate):
|
|||
def add_well_contacts(self):
|
||||
""" Add n/p well taps to the layout and connect to supplies """
|
||||
|
||||
self.add_nwell_contact(self.pmos, self.pmos3_pos)
|
||||
self.add_pwell_contact(self.nmos_ns, self.nmos3_pos)
|
||||
self.add_nwell_contact(self.pmos,
|
||||
self.pmos3_pos + vector(self.m1_pitch, 0))
|
||||
self.add_pwell_contact(self.nmos_ns,
|
||||
self.nmos3_pos + vector(self.m1_pitch, 0))
|
||||
|
||||
def connect_rails(self):
|
||||
""" Connect the nmos and pmos to its respective power rails """
|
||||
|
|
@ -220,18 +223,22 @@ class pnand3(pgate.pgate):
|
|||
self.nmos3_inst,
|
||||
self.inputC_yoffset,
|
||||
"C",
|
||||
position="center")
|
||||
position="right")
|
||||
|
||||
# FIXME: constant hack
|
||||
self.inputA_yoffset = self.inputB_yoffset + 1.12 * m1_pitch
|
||||
if OPTS.tech_name == "s8":
|
||||
self.inputA_yoffset = self.inputB_yoffset + 1.15 * m1_pitch
|
||||
else:
|
||||
self.inputA_yoffset = self.inputB_yoffset + 1.12 * m1_pitch
|
||||
self.route_input_gate(self.pmos1_inst,
|
||||
self.nmos1_inst,
|
||||
self.inputA_yoffset,
|
||||
"A",
|
||||
position="center")
|
||||
position="left")
|
||||
|
||||
def route_output(self):
|
||||
""" Route the Z output """
|
||||
|
||||
# PMOS1 drain
|
||||
pmos1_pin = self.pmos1_inst.get_pin("D")
|
||||
# PMOS3 drain
|
||||
|
|
@ -239,29 +246,56 @@ class pnand3(pgate.pgate):
|
|||
# NMOS3 drain
|
||||
nmos3_pin = self.nmos3_inst.get_pin("D")
|
||||
|
||||
# Go up to metal2 for ease on all output pins
|
||||
self.add_via_center(layers=self.m1_stack,
|
||||
offset=pmos1_pin.center(),
|
||||
directions=("V", "V"))
|
||||
self.add_via_center(layers=self.m1_stack,
|
||||
offset=pmos3_pin.center(),
|
||||
directions=("V", "V"))
|
||||
self.add_via_center(layers=self.m1_stack,
|
||||
offset=nmos3_pin.center(),
|
||||
directions=("V", "V"))
|
||||
# midpoint for routing
|
||||
mid_offset = vector(nmos3_pin.cx() + self.m1_pitch,
|
||||
self.inputA_yoffset)
|
||||
|
||||
# Aligned with the well taps
|
||||
out_offset = vector(self.nwell_contact.cx(),
|
||||
self.inputA_yoffset)
|
||||
|
||||
# PMOS3 and NMOS3 are drain aligned
|
||||
self.add_path("m2", [pmos3_pin.center(), nmos3_pin.center()])
|
||||
# Route in the A input track (top track)
|
||||
mid_offset = vector(nmos3_pin.center().x, self.inputA_yoffset)
|
||||
self.add_path("m2", [pmos1_pin.center(), mid_offset, nmos3_pin.uc()])
|
||||
# Go up to metal2 for ease on all output pins
|
||||
# self.add_via_center(layers=self.m1_stack,
|
||||
# offset=pmos1_pin.center(),
|
||||
# directions=("V", "V"))
|
||||
# self.add_via_center(layers=self.m1_stack,
|
||||
# offset=pmos3_pin.center(),
|
||||
# directions=("V", "V"))
|
||||
# self.add_via_center(layers=self.m1_stack,
|
||||
# offset=nmos3_pin.center(),
|
||||
# directions=("V", "V"))
|
||||
|
||||
# # Route in the A input track (top track)
|
||||
# mid_offset = vector(nmos3_pin.center().x, self.inputA_yoffset)
|
||||
# self.add_path("m1", [pmos1_pin.center(), mid_offset, nmos3_pin.uc()])
|
||||
|
||||
# This extends the output to the edge of the cell
|
||||
self.add_via_center(layers=self.m1_stack,
|
||||
offset=mid_offset)
|
||||
# self.add_via_center(layers=self.m1_stack,
|
||||
# offset=mid_offset)
|
||||
|
||||
top_left_pin_offset = pmos1_pin.center()
|
||||
top_right_pin_offset = pmos3_pin.center()
|
||||
bottom_pin_offset = nmos3_pin.center()
|
||||
|
||||
# PMOS1 to output
|
||||
self.add_path("m1", [top_left_pin_offset,
|
||||
vector(top_left_pin_offset.x, out_offset.y),
|
||||
out_offset])
|
||||
# PMOS3 to output
|
||||
self.add_path("m1", [top_right_pin_offset,
|
||||
vector(top_right_pin_offset.x, mid_offset.y),
|
||||
mid_offset])
|
||||
# NMOS3 to output
|
||||
mid2_offset = vector(mid_offset.x, bottom_pin_offset.y)
|
||||
self.add_path("m1",
|
||||
[bottom_pin_offset, mid2_offset],
|
||||
width=nmos3_pin.height())
|
||||
mid3_offset = vector(mid_offset.x, nmos3_pin.by())
|
||||
self.add_path("m1", [mid3_offset, mid_offset])
|
||||
|
||||
self.add_layout_pin_rect_center(text="Z",
|
||||
layer="m1",
|
||||
offset=mid_offset,
|
||||
offset=out_offset,
|
||||
width=contact.m1_via.first_layer_width,
|
||||
height=contact.m1_via.first_layer_height)
|
||||
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ from tech import layer, drc, spice
|
|||
from vector import vector
|
||||
from sram_factory import factory
|
||||
import contact
|
||||
import logical_effort
|
||||
import os
|
||||
from globals import OPTS
|
||||
|
||||
|
|
@ -106,20 +107,32 @@ class ptx(design.design):
|
|||
# be decided in the layout later.
|
||||
area_sd = 2.5 * self.poly_width * self.tx_width
|
||||
perimeter_sd = 2 * self.poly_width + 2 * self.tx_width
|
||||
main_str = "M{{0}} {{1}} {0} m={1} w={2}u l={3}u ".format(spice[self.tx_type],
|
||||
self.mults,
|
||||
self.tx_width,
|
||||
drc("minwidth_poly"))
|
||||
area_str = "pd={0:.2f}u ps={0:.2f}u as={1:.2f}p ad={1:.2f}p".format(perimeter_sd,
|
||||
area_sd)
|
||||
if OPTS.tech_name == "s8":
|
||||
# s8 technology is in microns
|
||||
main_str = "M{{0}} {{1}} {0} m={1} w={2} l={3} ".format(spice[self.tx_type],
|
||||
self.mults,
|
||||
self.tx_width,
|
||||
drc("minwidth_poly"))
|
||||
# Perimeters are in microns
|
||||
# Area is in u since it is microns square
|
||||
area_str = "pd={0:.2f} ps={0:.2f} as={1:.2f}u ad={1:.2f}u".format(perimeter_sd,
|
||||
area_sd)
|
||||
else:
|
||||
main_str = "M{{0}} {{1}} {0} m={1} w={2}u l={3}u ".format(spice[self.tx_type],
|
||||
self.mults,
|
||||
self.tx_width,
|
||||
drc("minwidth_poly"))
|
||||
area_str = "pd={0:.2f}u ps={0:.2f}u as={1:.2f}p ad={1:.2f}p".format(perimeter_sd,
|
||||
area_sd)
|
||||
self.spice_device = main_str + area_str
|
||||
self.spice.append("\n* ptx " + self.spice_device)
|
||||
|
||||
# LVS lib is always in SI units
|
||||
if os.path.exists(OPTS.openram_tech + "lvs_lib"):
|
||||
self.lvs_device = "M{{0}} {{1}} {0} m={1} w={2} l={3} ".format(spice[self.tx_type],
|
||||
self.mults,
|
||||
self.tx_width,
|
||||
drc("minwidth_poly"))
|
||||
self.lvs_device = "M{{0}} {{1}} {0} m={1} w={2}u l={3}u ".format(spice[self.tx_type],
|
||||
self.mults,
|
||||
self.tx_width,
|
||||
drc("minwidth_poly"))
|
||||
|
||||
|
||||
def setup_layout_constants(self):
|
||||
|
|
@ -453,6 +466,26 @@ class ptx(design.design):
|
|||
|
||||
if self.connect_active:
|
||||
self.connect_fingered_active(drain_positions, source_positions)
|
||||
|
||||
def get_stage_effort(self, cout):
|
||||
"""Returns an object representing the parameters for delay in tau units."""
|
||||
|
||||
# FIXME: Using the same definition as the pinv.py.
|
||||
parasitic_delay = 1
|
||||
size = self.mults * self.tx_width / drc("minwidth_tx")
|
||||
return logical_effort.logical_effort(self.name,
|
||||
size,
|
||||
self.input_load(),
|
||||
cout,
|
||||
parasitic_delay)
|
||||
|
||||
def input_load(self):
|
||||
"""
|
||||
Returns the relative gate cin of the tx
|
||||
"""
|
||||
|
||||
# FIXME: this will be applied for the loads of the drain/source
|
||||
return self.mults * self.tx_width / drc("minwidth_tx")
|
||||
|
||||
def add_diff_contact(self, label, pos):
|
||||
contact=self.add_via_center(layers=self.active_stack,
|
||||
|
|
@ -463,14 +496,25 @@ class ptx(design.design):
|
|||
well_type=self.well_type)
|
||||
|
||||
if hasattr(self, "li_stack"):
|
||||
self.add_via_center(layers=self.li_stack,
|
||||
offset=pos)
|
||||
|
||||
contact=self.add_via_center(layers=self.li_stack,
|
||||
offset=pos,
|
||||
directions=("V", "V"))
|
||||
|
||||
# contact_area = contact.mod.second_layer_width * contact.mod.second_layer_height
|
||||
# min_area = drc("minarea_m1")
|
||||
# width = contact.mod.second_layer_width
|
||||
# if contact_area < min_area:
|
||||
# height = min_area / width
|
||||
# else:
|
||||
# height = contact.mod.second_layer_height
|
||||
width = contact.mod.second_layer_width
|
||||
height = contact.mod.second_layer_height
|
||||
self.add_layout_pin_rect_center(text=label,
|
||||
layer="m1",
|
||||
offset=pos,
|
||||
width=contact.mod.second_layer_width,
|
||||
height=contact.mod.second_layer_height)
|
||||
width=width,
|
||||
height=height)
|
||||
|
||||
return(contact)
|
||||
|
||||
def get_cin(self):
|
||||
|
|
|
|||
|
|
@ -28,39 +28,39 @@ class hierarchical_decoder_test(openram_test):
|
|||
|
||||
factory.reset()
|
||||
debug.info(1, "Testing 16 row sample for hierarchical_decoder (multi-port case)")
|
||||
a = factory.create(module_type="hierarchical_decoder", rows=16)
|
||||
a = factory.create(module_type="hierarchical_decoder", num_outputs=16)
|
||||
self.local_check(a)
|
||||
|
||||
factory.reset()
|
||||
debug.info(1, "Testing 17 row sample for hierarchical_decoder (multi-port case)")
|
||||
a = factory.create(module_type="hierarchical_decoder", rows=17)
|
||||
a = factory.create(module_type="hierarchical_decoder", num_outputs=17)
|
||||
self.local_check(a)
|
||||
|
||||
factory.reset()
|
||||
debug.info(1, "Testing 23 row sample for hierarchical_decoder (multi-port case)")
|
||||
a = factory.create(module_type="hierarchical_decoder", rows=23)
|
||||
a = factory.create(module_type="hierarchical_decoder", num_outputs=23)
|
||||
self.local_check(a)
|
||||
|
||||
debug.info(1, "Testing 32 row sample for hierarchical_decoder (multi-port case)")
|
||||
a = factory.create(module_type="hierarchical_decoder", rows=32)
|
||||
a = factory.create(module_type="hierarchical_decoder", num_outputs=32)
|
||||
self.local_check(a)
|
||||
|
||||
factory.reset()
|
||||
debug.info(1, "Testing 65 row sample for hierarchical_decoder (multi-port case)")
|
||||
a = factory.create(module_type="hierarchical_decoder", rows=65)
|
||||
a = factory.create(module_type="hierarchical_decoder", num_outputs=65)
|
||||
self.local_check(a)
|
||||
|
||||
debug.info(1, "Testing 128 row sample for hierarchical_decoder (multi-port case)")
|
||||
a = factory.create(module_type="hierarchical_decoder", rows=128)
|
||||
a = factory.create(module_type="hierarchical_decoder", num_outputs=128)
|
||||
self.local_check(a)
|
||||
|
||||
factory.reset()
|
||||
debug.info(1, "Testing 341 row sample for hierarchical_decoder (multi-port case)")
|
||||
a = factory.create(module_type="hierarchical_decoder", rows=341)
|
||||
a = factory.create(module_type="hierarchical_decoder", num_outputs=341)
|
||||
self.local_check(a)
|
||||
|
||||
debug.info(1, "Testing 512 row sample for hierarchical_decoder (multi-port case)")
|
||||
a = factory.create(module_type="hierarchical_decoder", rows=512)
|
||||
a = factory.create(module_type="hierarchical_decoder", num_outputs=512)
|
||||
self.local_check(a)
|
||||
|
||||
globals.end_openram()
|
||||
|
|
|
|||
|
|
@ -20,47 +20,38 @@ class hierarchical_decoder_test(openram_test):
|
|||
def runTest(self):
|
||||
config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME"))
|
||||
globals.init_openram(config_file)
|
||||
# Doesn't require hierarchical decoder
|
||||
# debug.info(1, "Testing 4 row sample for hierarchical_decoder")
|
||||
# a = hierarchical_decoder.hierarchical_decoder(name="hd1, rows=4)
|
||||
# self.local_check(a)
|
||||
|
||||
# Doesn't require hierarchical decoder
|
||||
# debug.info(1, "Testing 8 row sample for hierarchical_decoder")
|
||||
# a = hierarchical_decoder.hierarchical_decoder(name="hd2", rows=8)
|
||||
# self.local_check(a)
|
||||
|
||||
# check hierarchical decoder for single port
|
||||
debug.info(1, "Testing 16 row sample for hierarchical_decoder")
|
||||
a = factory.create(module_type="hierarchical_decoder", rows=16)
|
||||
a = factory.create(module_type="hierarchical_decoder", num_outputs=16)
|
||||
self.local_check(a)
|
||||
|
||||
debug.info(1, "Testing 17 row sample for hierarchical_decoder")
|
||||
a = factory.create(module_type="hierarchical_decoder", rows=17)
|
||||
a = factory.create(module_type="hierarchical_decoder", num_outputs=17)
|
||||
self.local_check(a)
|
||||
|
||||
debug.info(1, "Testing 23 row sample for hierarchical_decoder")
|
||||
a = factory.create(module_type="hierarchical_decoder", rows=23)
|
||||
a = factory.create(module_type="hierarchical_decoder", num_outputs=23)
|
||||
self.local_check(a)
|
||||
|
||||
debug.info(1, "Testing 32 row sample for hierarchical_decoder")
|
||||
a = factory.create(module_type="hierarchical_decoder", rows=32)
|
||||
a = factory.create(module_type="hierarchical_decoder", num_outputs=32)
|
||||
self.local_check(a)
|
||||
|
||||
debug.info(1, "Testing 65 row sample for hierarchical_decoder")
|
||||
a = factory.create(module_type="hierarchical_decoder", rows=65)
|
||||
a = factory.create(module_type="hierarchical_decoder", num_outputs=65)
|
||||
self.local_check(a)
|
||||
|
||||
debug.info(1, "Testing 128 row sample for hierarchical_decoder")
|
||||
a = factory.create(module_type="hierarchical_decoder", rows=128)
|
||||
a = factory.create(module_type="hierarchical_decoder", num_outputs=128)
|
||||
self.local_check(a)
|
||||
|
||||
debug.info(1, "Testing 341 row sample for hierarchical_decoder")
|
||||
a = factory.create(module_type="hierarchical_decoder", rows=341)
|
||||
a = factory.create(module_type="hierarchical_decoder", num_outputs=341)
|
||||
self.local_check(a)
|
||||
|
||||
debug.info(1, "Testing 512 row sample for hierarchical_decoder")
|
||||
a = factory.create(module_type="hierarchical_decoder", rows=512)
|
||||
a = factory.create(module_type="hierarchical_decoder", num_outputs=512)
|
||||
self.local_check(a)
|
||||
|
||||
globals.end_openram()
|
||||
|
|
|
|||
|
|
@ -51,7 +51,7 @@ class openram_back_end_test(openram_test):
|
|||
debug.warning("Failed to find coverage installation. This can be installed with pip3 install coverage")
|
||||
exe_name = "{0}/openram.py ".format(OPENRAM_HOME)
|
||||
else:
|
||||
exe_name = "coverage run -p {0}/openram.py ".format(OPENRAM_HOME)
|
||||
exe_name = "coverage run -p {0}/openram.py ".format(OPENRAM_HOME)
|
||||
config_name = "{0}/tests/configs/config_back_end.py".format(OPENRAM_HOME)
|
||||
cmd = "{0} -n -o {1} -p {2} {3} {4} 2>&1 > {5}/output.log".format(exe_name,
|
||||
out_file,
|
||||
|
|
@ -64,33 +64,32 @@ class openram_back_end_test(openram_test):
|
|||
|
||||
# assert an error until we actually check a resul
|
||||
for extension in ["gds", "v", "lef", "sp"]:
|
||||
filename = "{0}/{1}.{2}".format(out_path,out_file,extension)
|
||||
debug.info(1,"Checking for file: " + filename)
|
||||
self.assertEqual(os.path.exists(filename),True)
|
||||
filename = "{0}/{1}.{2}".format(out_path, out_file, extension)
|
||||
debug.info(1, "Checking for file: " + filename)
|
||||
self.assertEqual(os.path.exists(filename), True)
|
||||
|
||||
# Make sure there is any .lib file
|
||||
import glob
|
||||
files = glob.glob('{0}/*.lib'.format(out_path))
|
||||
self.assertTrue(len(files)>0)
|
||||
|
||||
# Make sure there is any .html file
|
||||
# Make sure there is any .html file
|
||||
if os.path.exists(out_path):
|
||||
datasheets = glob.glob('{0}/*html'.format(out_path))
|
||||
self.assertTrue(len(datasheets)>0)
|
||||
|
||||
# grep any errors from the output
|
||||
output_log = open("{0}/output.log".format(out_path),"r")
|
||||
output_log = open("{0}/output.log".format(out_path), "r")
|
||||
output = output_log.read()
|
||||
output_log.close()
|
||||
self.assertEqual(len(re.findall('ERROR',output)),0)
|
||||
self.assertEqual(len(re.findall('WARNING',output)),0)
|
||||
|
||||
self.assertEqual(len(re.findall('ERROR', output)), 0)
|
||||
self.assertEqual(len(re.findall('WARNING', output)), 0)
|
||||
|
||||
# now clean up the directory
|
||||
if OPTS.purge_temp:
|
||||
if os.path.exists(out_path):
|
||||
shutil.rmtree(out_path, ignore_errors=True)
|
||||
self.assertEqual(os.path.exists(out_path),False)
|
||||
self.assertEqual(os.path.exists(out_path), False)
|
||||
|
||||
globals.end_openram()
|
||||
|
||||
|
|
|
|||
|
|
@ -51,7 +51,7 @@ class openram_front_end_test(openram_test):
|
|||
debug.warning("Failed to find coverage installation. This can be installed with pip3 install coverage")
|
||||
exe_name = "{0}/openram.py ".format(OPENRAM_HOME)
|
||||
else:
|
||||
exe_name = "coverage run -p {0}/openram.py ".format(OPENRAM_HOME)
|
||||
exe_name = "coverage run -p {0}/openram.py ".format(OPENRAM_HOME)
|
||||
config_name = "{0}/tests/configs/config_front_end.py".format(OPENRAM_HOME)
|
||||
cmd = "{0} -n -o {1} -p {2} {3} {4} 2>&1 > {5}/output.log".format(exe_name,
|
||||
out_file,
|
||||
|
|
@ -64,33 +64,32 @@ class openram_front_end_test(openram_test):
|
|||
|
||||
# assert an error until we actually check a result
|
||||
for extension in ["v", "lef", "sp", "gds"]:
|
||||
filename = "{0}/{1}.{2}".format(out_path,out_file,extension)
|
||||
debug.info(1,"Checking for file: " + filename)
|
||||
self.assertEqual(os.path.exists(filename),True)
|
||||
filename = "{0}/{1}.{2}".format(out_path, out_file, extension)
|
||||
debug.info(1, "Checking for file: " + filename)
|
||||
self.assertEqual(os.path.exists(filename), True)
|
||||
|
||||
# Make sure there is any .lib file
|
||||
import glob
|
||||
files = glob.glob('{0}/*.lib'.format(out_path))
|
||||
self.assertTrue(len(files)>0)
|
||||
|
||||
# Make sure there is any .html file
|
||||
# Make sure there is any .html file
|
||||
if os.path.exists(out_path):
|
||||
datasheets = glob.glob('{0}/*html'.format(out_path))
|
||||
self.assertTrue(len(datasheets)>0)
|
||||
|
||||
# grep any errors from the output
|
||||
output_log = open("{0}/output.log".format(out_path),"r")
|
||||
output_log = open("{0}/output.log".format(out_path), "r")
|
||||
output = output_log.read()
|
||||
output_log.close()
|
||||
self.assertEqual(len(re.findall('ERROR',output)),0)
|
||||
self.assertEqual(len(re.findall('WARNING',output)),0)
|
||||
self.assertEqual(len(re.findall('ERROR', output)), 0)
|
||||
self.assertEqual(len(re.findall('WARNING', output)), 0)
|
||||
|
||||
|
||||
# now clean up the directory
|
||||
# now clean up the directory
|
||||
if OPTS.purge_temp:
|
||||
if os.path.exists(out_path):
|
||||
shutil.rmtree(out_path, ignore_errors=True)
|
||||
self.assertEqual(os.path.exists(out_path),False)
|
||||
self.assertEqual(os.path.exists(out_path), False)
|
||||
|
||||
globals.end_openram()
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue