merged in outdated dev in previous merge

This commit is contained in:
Jesse Cirimelli-Low 2018-10-25 10:30:50 -07:00
commit 85a99bb364
153 changed files with 4908 additions and 3431 deletions

View File

@ -7,9 +7,9 @@ https://github.com/mguthaus/OpenRAM/blob/master/OpenRAM_ICCAD_2016_presentation.
The OpenRAM compiler has very few dependencies:
* ngspice-26 (or later) or HSpice I-2013.12-1 (or later) or CustomSim 2017 (or later)
* Python 3.5 and higher
* Python numpy
* flask_table
* a setup script for each technology
* Python numpy (pip3 install numpy to install)
* flask_table (pip3 install flask to install)
* a setup script for each technology you want to use
* a technology directory for each technology with the base cells
If you want to perform DRC and LVS, you will need either:
@ -21,13 +21,13 @@ the compiler source directory. OPENERAM_TECH should point to a root
technology directory that contains subdirs of all other technologies.
For example, in bash, add to your .bashrc:
```
export OPENRAM_HOME="$HOME/OpenRAM/compiler"
export OPENRAM_TECH="$HOME/OpenRAM/technology"
export OPENRAM_HOME="$HOME/openram/compiler"
export OPENRAM_TECH="$HOME/openram/technology"
```
For example, in csh/tcsh, add to your .cshrc/.tcshrc:
```
setenv OPENRAM_HOME "$HOME/OpenRAM/compiler"
setenv OPENRAM_TECH "$HOME/OpenRAM/technology"
setenv OPENRAM_HOME "$HOME/openram/compiler"
setenv OPENRAM_TECH "$HOME/openram/technology"
```
We include the tech files necessary for FreePDK and SCMOS. The SCMOS
@ -49,7 +49,7 @@ We do not distribute the PDK, but you may get it from:
If you are using SCMOS, you should install Magic and netgen from:
http://opencircuitdesign.com/magic/
http://opencircuitdesign.com/netgen/
We have included the SCN3ME design rules from QFlow:
We have included the SCN4M design rules from QFlow:
http://opencircuitdesign.com/qflow/
# DIRECTORY STRUCTURE
@ -57,17 +57,19 @@ We have included the SCN3ME design rules from QFlow:
* compiler - openram compiler itself (pointed to by OPENRAM_HOME)
* compiler/base - base data structure modules
* compiler/pgates - parameterized cells (e.g. logic gates)
* compiler/bitcells - various bitcell styles
* compiler/modules - high-level modules (e.g. decoders, etc.)
* compiler/verify - DRC and LVS verification wrappers
* compiler/verify - DRC and LVS verification wrappers
* compiler/characterizer - timing characterization code
* compiler/gdsMill - GDSII reader/writer
* compiler/router - detailed router
* compiler/router - router for signals and power supplies
* compiler/tests - unit tests
* technology - openram technology directory (pointed to by OPENRAM_TECH)
* technology/freepdk45 - example configuration library for freepdk45 technology node
* technology/scn3me_subm - example configuration library SCMOS technology node
* technology/scn4m_subm - example configuration library SCMOS technology node
* technology/scn3me_subm - unsupported configuration (not enough metal layers)
* technology/setup_scripts - setup scripts to customize your PDKs and OpenRAM technologies
* docs - LaTeX manual (likely outdated)
* docs - LaTeX manual (outdated)
* lib - IP library of pregenerated memories
@ -90,8 +92,8 @@ To increase the verbosity of the test, add one (or more) -v options:
python tests/00_code_format_check_test.py -v -t freepdk45
```
To specify a particular technology use "-t <techname>" such as
"-t scn3me_subm". The default for a unit test is freepdk45 whereas
the default for openram.py is specified in the configuration file.
"-t freepdk45" or "-t scn4m_subm". The default for a unit test is scn4m_subm.
The default for openram.py is specified in the configuration file.
# CREATING CUSTOM TECHNOLOGIES

View File

@ -73,21 +73,21 @@ class contact(hierarchy_design.hierarchy_design):
self.second_layer_name = second_layer
def setup_layout_constants(self):
self.contact_width = drc["minwidth_{0}". format(self.via_layer_name)]
contact_to_contact = drc["{0}_to_{0}".format(self.via_layer_name)]
self.contact_width = drc("minwidth_{0}". format(self.via_layer_name))
contact_to_contact = drc("{0}_to_{0}".format(self.via_layer_name))
self.contact_pitch = self.contact_width + contact_to_contact
self.contact_array_width = self.contact_width + (self.dimensions[0] - 1) * self.contact_pitch
self.contact_array_height = self.contact_width + (self.dimensions[1] - 1) * self.contact_pitch
# DRC rules
first_layer_minwidth = drc["minwidth_{0}".format(self.first_layer_name)]
first_layer_minarea = drc["minarea_{0}".format(self.first_layer_name)]
first_layer_enclosure = drc["{0}_enclosure_{1}".format(self.first_layer_name, self.via_layer_name)]
first_layer_extend = drc["{0}_extend_{1}".format(self.first_layer_name, self.via_layer_name)]
second_layer_minwidth = drc["minwidth_{0}".format(self.second_layer_name)]
second_layer_minarea = drc["minarea_{0}".format(self.second_layer_name)]
second_layer_enclosure = drc["{0}_enclosure_{1}".format(self.second_layer_name, self.via_layer_name)]
second_layer_extend = drc["{0}_extend_{1}".format(self.second_layer_name, self.via_layer_name)]
first_layer_minwidth = drc("minwidth_{0}".format(self.first_layer_name))
first_layer_minarea = drc("minarea_{0}".format(self.first_layer_name))
first_layer_enclosure = drc("{0}_enclosure_{1}".format(self.first_layer_name, self.via_layer_name))
first_layer_extend = drc("{0}_extend_{1}".format(self.first_layer_name, self.via_layer_name))
second_layer_minwidth = drc("minwidth_{0}".format(self.second_layer_name))
second_layer_minarea = drc("minarea_{0}".format(self.second_layer_name))
second_layer_enclosure = drc("{0}_enclosure_{1}".format(self.second_layer_name, self.via_layer_name))
second_layer_extend = drc("{0}_extend_{1}".format(self.second_layer_name, self.via_layer_name))
self.first_layer_horizontal_enclosure = max((first_layer_minwidth - self.contact_array_width) / 2,
first_layer_enclosure)
@ -145,16 +145,16 @@ class contact(hierarchy_design.hierarchy_design):
height=self.second_layer_height)
def create_implant_well_enclosures(self):
implant_position = self.first_layer_position - [drc["implant_enclosure_active"]]*2
implant_width = self.first_layer_width + 2*drc["implant_enclosure_active"]
implant_height = self.first_layer_height + 2*drc["implant_enclosure_active"]
implant_position = self.first_layer_position - [drc("implant_enclosure_active")]*2
implant_width = self.first_layer_width + 2*drc("implant_enclosure_active")
implant_height = self.first_layer_height + 2*drc("implant_enclosure_active")
self.add_rect(layer="{}implant".format(self.implant_type),
offset=implant_position,
width=implant_width,
height=implant_height)
well_position = self.first_layer_position - [drc["well_enclosure_active"]]*2
well_width = self.first_layer_width + 2*drc["well_enclosure_active"]
well_height = self.first_layer_height + 2*drc["well_enclosure_active"]
well_position = self.first_layer_position - [drc("well_enclosure_active")]*2
well_width = self.first_layer_width + 2*drc("well_enclosure_active")
well_height = self.first_layer_height + 2*drc("well_enclosure_active")
self.add_rect(layer="{}well".format(self.well_type),
offset=well_position,
width=well_width,

View File

@ -29,25 +29,25 @@ class design(hierarchy_design):
def setup_drc_constants(self):
""" These are some DRC constants used in many places in the compiler."""
from tech import drc
self.well_width = drc["minwidth_well"]
self.poly_width = drc["minwidth_poly"]
self.poly_space = drc["poly_to_poly"]
self.m1_width = drc["minwidth_metal1"]
self.m1_space = drc["metal1_to_metal1"]
self.m2_width = drc["minwidth_metal2"]
self.m2_space = drc["metal2_to_metal2"]
self.m3_width = drc["minwidth_metal3"]
self.m3_space = drc["metal3_to_metal3"]
self.active_width = drc["minwidth_active"]
self.contact_width = drc["minwidth_contact"]
self.well_width = drc("minwidth_well")
self.poly_width = drc("minwidth_poly")
self.poly_space = drc("poly_to_poly")
self.m1_width = drc("minwidth_metal1")
self.m1_space = drc("metal1_to_metal1")
self.m2_width = drc("minwidth_metal2")
self.m2_space = drc("metal2_to_metal2")
self.m3_width = drc("minwidth_metal3")
self.m3_space = drc("metal3_to_metal3")
self.active_width = drc("minwidth_active")
self.contact_width = drc("minwidth_contact")
self.poly_to_active = drc["poly_to_active"]
self.poly_extend_active = drc["poly_extend_active"]
self.poly_to_polycontact = drc["poly_to_polycontact"]
self.contact_to_gate = drc["contact_to_gate"]
self.well_enclose_active = drc["well_enclosure_active"]
self.implant_enclose_active = drc["implant_enclosure_active"]
self.implant_space = drc["implant_to_implant"]
self.poly_to_active = drc("poly_to_active")
self.poly_extend_active = drc("poly_extend_active")
self.poly_to_polycontact = drc("poly_to_polycontact")
self.contact_to_gate = drc("contact_to_gate")
self.well_enclose_active = drc("well_enclosure_active")
self.implant_enclose_active = drc("implant_enclosure_active")
self.implant_space = drc("implant_to_implant")
def setup_multiport_constants(self):
""" These are contants and lists that aid multiport design """
@ -83,3 +83,14 @@ class design(hierarchy_design):
for inst in self.insts:
total_module_power += inst.mod.analytical_power(proc, vdd, temp, load)
return total_module_power
def __str__(self):
""" override print function output """
pins = ",".join(self.pins)
insts = [" {}".format(x) for x in self.insts]
objs = [" {}".format(x) for x in self.objs]
s = "********** design {0} **********\n".format(self.name)
s += "\n pins ({0})={1}\n".format(len(self.pins), pins)
s += "\n objs ({0})=\n{1}".format(len(self.objs), "\n".join(objs))
s += "\n insts ({0})=\n{1}\n".format(len(self.insts), "\n".join(insts))
return s

View File

@ -6,6 +6,7 @@ from vector import vector
import tech
import math
from globals import OPTS
from utils import round_to_grid
class geometry:
"""
@ -46,8 +47,8 @@ class geometry:
def normalize(self):
""" Re-find the LL and UR points after a transform """
(first,second)=self.boundary
ll = vector(min(first[0],second[0]),min(first[1],second[1]))
ur = vector(max(first[0],second[0]),max(first[1],second[1]))
ll = vector(min(first[0],second[0]),min(first[1],second[1])).snap_to_grid()
ur = vector(max(first[0],second[0]),max(first[1],second[1])).snap_to_grid()
self.boundary=[ll,ur]
def update_boundary(self):
@ -142,8 +143,12 @@ class instance(geometry):
self.rotate = rotate
self.offset = vector(offset).snap_to_grid()
self.mirror = mirror
self.width = mod.width
self.height = mod.height
if OPTS.netlist_only:
self.width = 0
self.height = 0
else:
self.width = round_to_grid(mod.width)
self.height = round_to_grid(mod.height)
self.compute_boundary(offset,mirror,rotate)
debug.info(4, "creating instance: " + self.name)
@ -191,15 +196,15 @@ class instance(geometry):
self.mod.gds_write_file(self.gds)
# now write an instance of my module/structure
new_layout.addInstance(self.gds,
offsetInMicrons=self.offset,
mirror=self.mirror,
rotate=self.rotate)
offsetInMicrons=self.offset,
mirror=self.mirror,
rotate=self.rotate)
def place(self, offset, mirror="R0", rotate=0):
""" This updates the placement of an instance. """
debug.info(3, "placing instance {}".format(self.name))
# Update the placement of an already added instance
self.offset = vector(offset)
self.offset = vector(offset).snap_to_grid()
self.mirror = mirror
self.rotate = rotate
self.update_boundary()
@ -238,7 +243,7 @@ class instance(geometry):
def __str__(self):
""" override print function output """
return "inst: " + self.name + " mod=" + self.mod.name
return "( inst: " + self.name + " @" + str(self.offset) + " mod=" + self.mod.name + " " + self.mirror + " R=" + str(self.rotate) + ")"
def __repr__(self):
""" override print function output """
@ -260,13 +265,13 @@ class path(geometry):
# supported right now. It might not work in gdsMill.
assert(0)
def gds_write_file(self, newLayout):
def gds_write_file(self, new_layout):
"""Writes the path to GDS"""
debug.info(4, "writing path (" + str(self.layerNumber) + "): " + self.coordinates)
newLayout.addPath(layerNumber=self.layerNumber,
purposeNumber=0,
coordinates=self.coordinates,
width=self.path_width)
new_layout.addPath(layerNumber=self.layerNumber,
purposeNumber=0,
coordinates=self.coordinates,
width=self.path_width)
def get_blockages(self, layer):
""" Fail since we don't support paths yet. """
@ -301,15 +306,15 @@ class label(geometry):
debug.info(4,"creating label " + self.text + " " + str(self.layerNumber) + " " + str(self.offset))
def gds_write_file(self, newLayout):
def gds_write_file(self, new_layout):
"""Writes the text label to GDS"""
debug.info(4, "writing label (" + str(self.layerNumber) + "): " + self.text)
newLayout.addText(text=self.text,
layerNumber=self.layerNumber,
purposeNumber=0,
offsetInMicrons=self.offset,
magnification=self.zoom,
rotate=None)
new_layout.addText(text=self.text,
layerNumber=self.layerNumber,
purposeNumber=0,
offsetInMicrons=self.offset,
magnification=self.zoom,
rotate=None)
def get_blockages(self, layer):
""" Returns an empty list since text cannot be blockages. """
@ -321,7 +326,7 @@ class label(geometry):
def __repr__(self):
""" override print function output """
return "( label: " + self.text + " @" + str(self.offset) + " layer=" + self.layerNumber + " )"
return "( label: " + self.text + " @" + str(self.offset) + " layer=" + str(self.layerNumber) + " )"
class rectangle(geometry):
"""Represents a rectangular shape"""
@ -333,8 +338,8 @@ class rectangle(geometry):
self.layerNumber = layerNumber
self.offset = vector(offset).snap_to_grid()
self.size = vector(width, height).snap_to_grid()
self.width = self.size.x
self.height = self.size.y
self.width = round_to_grid(self.size.x)
self.height = round_to_grid(self.size.y)
self.compute_boundary(offset,"",0)
debug.info(4, "creating rectangle (" + str(self.layerNumber) + "): "
@ -348,16 +353,16 @@ class rectangle(geometry):
else:
return []
def gds_write_file(self, newLayout):
def gds_write_file(self, new_layout):
"""Writes the rectangular shape to GDS"""
debug.info(4, "writing rectangle (" + str(self.layerNumber) + "):"
+ str(self.width) + "x" + str(self.height) + " @ " + str(self.offset))
newLayout.addBox(layerNumber=self.layerNumber,
purposeNumber=0,
offsetInMicrons=self.offset,
width=self.width,
height=self.height,
center=False)
new_layout.addBox(layerNumber=self.layerNumber,
purposeNumber=0,
offsetInMicrons=self.offset,
width=self.width,
height=self.height,
center=False)
def __str__(self):
""" override print function output """

View File

@ -28,7 +28,7 @@ class layout(lef.lef):
self.insts = [] # Holds module/cell layout instances
self.objs = [] # Holds all other objects (labels, geometries, etc)
self.pin_map = {} # Holds name->pin_layout map for all pins
self.visited = False # Flag for traversing the hierarchy
self.visited = [] # List of modules we have already visited
self.is_library_cell = False # Flag for library cells
self.gds_read()
@ -134,11 +134,13 @@ class layout(lef.lef):
return inst
return None
def add_rect(self, layer, offset, width=0, height=0):
"""Adds a rectangle on a given layer,offset with width and height"""
if width==0:
def add_rect(self, layer, offset, width=None, height=None):
"""
Adds a rectangle on a given layer,offset with width and height
"""
if not width:
width=drc["minwidth_{}".format(layer)]
if height==0:
if not height:
height=drc["minwidth_{}".format(layer)]
# negative layers indicate "unused" layers in a given technology
layer_num = techlayer[layer]
@ -147,11 +149,13 @@ class layout(lef.lef):
return self.objs[-1]
return None
def add_rect_center(self, layer, offset, width=0, height=0):
"""Adds a rectangle on a given layer at the center point with width and height"""
if width==0:
def add_rect_center(self, layer, offset, width=None, height=None):
"""
Adds a rectangle on a given layer at the center point with width and height
"""
if not width:
width=drc["minwidth_{}".format(layer)]
if height==0:
if not height:
height=drc["minwidth_{}".format(layer)]
# negative layers indicate "unused" layers in a given technology
layer_num = techlayer[layer]
@ -163,7 +167,9 @@ class layout(lef.lef):
def add_segment_center(self, layer, start, end):
""" Add a min-width rectanglular segment using center line on the start to end point """
"""
Add a min-width rectanglular segment using center line on the start to end point
"""
minwidth_layer = drc["minwidth_{}".format(layer)]
if start.x!=end.x and start.y!=end.y:
debug.error("Nonrectilinear center rect!",-1)
@ -177,7 +183,9 @@ class layout(lef.lef):
def get_pin(self, text):
""" Return the pin or list of pins """
"""
Return the pin or list of pins
"""
try:
if len(self.pin_map[text])>1:
debug.error("Should use a pin iterator since more than one pin {}".format(text),-1)
@ -192,8 +200,13 @@ class layout(lef.lef):
def get_pins(self, text):
""" Return a pin list (instead of a single pin) """
return self.pin_map[text]
"""
Return a pin list (instead of a single pin)
"""
if text in self.pin_map.keys():
return self.pin_map[text]
else:
return []
def copy_layout_pin(self, instance, pin_name, new_name=""):
"""
@ -207,7 +220,9 @@ class layout(lef.lef):
self.add_layout_pin(new_name, pin.layer, pin.ll(), pin.width(), pin.height())
def add_layout_pin_segment_center(self, text, layer, start, end):
""" Creates a path like pin with center-line convention """
"""
Creates a path like pin with center-line convention
"""
debug.check(start.x==end.x or start.y==end.y,"Cannot have a non-manhatten layout pin.")
@ -232,9 +247,9 @@ class layout(lef.lef):
def add_layout_pin_rect_center(self, text, layer, offset, width=None, height=None):
""" Creates a path like pin with center-line convention """
if width==None:
if not width:
width=drc["minwidth_{0}".format(layer)]
if height==None:
if not height:
height=drc["minwidth_{0}".format(layer)]
ll_offset = offset - vector(0.5*width,0.5*height)
@ -243,14 +258,18 @@ class layout(lef.lef):
def remove_layout_pin(self, text):
"""Delete a labeled pin (or all pins of the same name)"""
"""
Delete a labeled pin (or all pins of the same name)
"""
self.pin_map[text]=[]
def add_layout_pin(self, text, layer, offset, width=None, height=None):
"""Create a labeled pin """
if width==None:
"""
Create a labeled pin
"""
if not width:
width=drc["minwidth_{0}".format(layer)]
if height==None:
if not height:
height=drc["minwidth_{0}".format(layer)]
new_pin = pin_layout(text, [offset,offset+vector(width,height)], layer)
@ -270,13 +289,14 @@ class layout(lef.lef):
return new_pin
def add_label_pin(self, text, layer, offset, width=None, height=None):
"""Create a labeled pin WITHOUT the pin data structure. This is not an
"""
Create a labeled pin WITHOUT the pin data structure. This is not an
actual pin but a named net so that we can add a correspondence point
in LVS.
"""
if width==None:
if not width:
width=drc["minwidth_{0}".format(layer)]
if height==None:
if not height:
height=drc["minwidth_{0}".format(layer)]
self.add_rect(layer=layer,
offset=offset,
@ -313,7 +333,7 @@ class layout(lef.lef):
position_list=coordinates,
width=width)
def add_route(self, layers, coordinates):
def add_route(self, layers, coordinates, layer_widths):
"""Connects a routing path on given layer,coordinates,width. The
layers are the (horizontal, via, vertical). add_wire assumes
preferred direction routing whereas this includes layers in
@ -324,7 +344,8 @@ class layout(lef.lef):
# add an instance of our path that breaks down into rectangles and contacts
route.route(obj=self,
layer_stack=layers,
path=coordinates)
path=coordinates,
layer_widths=layer_widths)
def add_wire(self, layers, coordinates):
@ -432,59 +453,66 @@ class layout(lef.lef):
# open the gds file if it exists or else create a blank layout
if os.path.isfile(self.gds_file):
debug.info(3, "opening %s" % self.gds_file)
debug.info(3, "opening {}".format(self.gds_file))
self.is_library_cell=True
self.gds = gdsMill.VlsiLayout(units=GDS["unit"])
reader = gdsMill.Gds2reader(self.gds)
reader.loadFromFile(self.gds_file)
else:
debug.info(4, "creating structure %s" % self.name)
debug.info(3, "Creating layout structure {}".format(self.name))
self.gds = gdsMill.VlsiLayout(name=self.name, units=GDS["unit"])
def print_gds(self, gds_file=None):
"""Print the gds file (not the vlsi class) to the terminal """
if gds_file == None:
gds_file = self.gds_file
debug.info(4, "Printing %s" % gds_file)
debug.info(4, "Printing {}".format(gds_file))
arrayCellLayout = gdsMill.VlsiLayout(units=GDS["unit"])
reader = gdsMill.Gds2reader(arrayCellLayout, debugToTerminal=1)
reader.loadFromFile(gds_file)
def clear_visited(self):
""" Recursively clear the visited flag """
if not self.visited:
for i in self.insts:
i.mod.clear_visited()
self.visited = False
self.visited = []
def gds_write_file(self, newLayout):
def gds_write_file(self, gds_layout):
"""Recursive GDS write function"""
# Visited means that we already prepared self.gds for this subtree
if self.visited:
if self.name in self.visited:
return
for i in self.insts:
i.gds_write_file(newLayout)
i.gds_write_file(gds_layout)
for i in self.objs:
i.gds_write_file(newLayout)
i.gds_write_file(gds_layout)
for pin_name in self.pin_map.keys():
for pin in self.pin_map[pin_name]:
pin.gds_write_file(newLayout)
self.visited = True
pin.gds_write_file(gds_layout)
self.visited.append(self.name)
def gds_write(self, gds_name):
"""Write the entire gds of the object to the file."""
debug.info(3, "Writing to {0}".format(gds_name))
debug.info(3, "Writing to {}".format(gds_name))
# If we already wrote a GDS, we need to reset and traverse it again in
# case we made changes.
if not self.is_library_cell and self.visited:
debug.info(3, "Creating layout structure {}".format(self.name))
self.gds = gdsMill.VlsiLayout(name=self.name, units=GDS["unit"])
writer = gdsMill.Gds2writer(self.gds)
# MRG: 3/2/18 We don't want to clear the visited flag since
# this would result in duplicates of all instances being placed in self.gds
# which may have been previously processed!
#self.clear_visited()
# MRG: 10/4/18 We need to clear if we make changes and write a second GDS!
self.clear_visited()
# recursively create all the remaining objects
self.gds_write_file(self.gds)
# populates the xyTree data structure for gds
# self.gds.prepareForWrite()
writer.writeToFile(gds_name)
debug.info(3, "Done writing to {}".format(gds_name))
def get_boundary(self):
""" Return the lower-left and upper-right coordinates of boundary """
@ -875,6 +903,22 @@ class layout(lef.lef):
width=xmax-xmin,
height=ymax-ymin)
def copy_power_pins(self, inst, name):
"""
This will copy a power pin if it is on M3. If it is on M1, it will add a power via too.
"""
pins=inst.get_pins(name)
for pin in pins:
if pin.layer=="metal3":
self.add_layout_pin(name, pin.layer, pin.ll(), pin.width(), pin.height())
elif pin.layer=="metal1":
self.add_power_pin(name, pin.center())
else:
debug.warning("{0} pins of {1} should be on metal3 or metal1 for supply router.".format(name,inst.name))
def add_power_pin(self, name, loc, rotate=90):
"""
Add a single power pin from M3 down to M1 at the given center location
@ -1008,7 +1052,7 @@ class layout(lef.lef):
def pdf_write(self, pdf_name):
# NOTE: Currently does not work (Needs further research)
#self.pdf_name = self.name + ".pdf"
debug.info(0, "Writing to %s" % pdf_name)
debug.info(0, "Writing to {}".format(pdf_name))
pdf = gdsMill.pdfLayout(self.gds)
return

View File

@ -91,9 +91,9 @@ class spice(verilog.verilog):
group of modules are generated."""
if (check and (len(self.insts[-1].mod.pins) != len(args))):
import pprint
modpins_string=pprint.pformat(self.insts[-1].mod.pins)
argpins_string=pprint.pformat(args)
from pprint import pformat
modpins_string=pformat(self.insts[-1].mod.pins)
argpins_string=pformat(args)
debug.error("Connections: {}".format(modpins_string))
debug.error("Connections: {}".format(argpins_string))
debug.error("Number of net connections ({0}) does not match last instance ({1})".format(len(self.insts[-1].mod.pins),
@ -101,9 +101,9 @@ class spice(verilog.verilog):
self.conns.append(args)
if check and (len(self.insts)!=len(self.conns)):
import pprint
insts_string=pprint.pformat(self.insts)
conns_string=pprint.pformat(self.conns)
from pprint import pformat
insts_string=pformat(self.insts)
conns_string=pformat(self.conns)
debug.error("{0} : Not all instance pins ({1}) are connected ({2}).".format(self.name,
len(self.insts),

View File

@ -30,9 +30,26 @@ class pin_layout:
return "({} layer={} ll={} ur={})".format(self.name,self.layer,self.rect[0],self.rect[1])
def __repr__(self):
""" override print function output """
return "({} layer={} ll={} ur={})".format(self.name,self.layer,self.rect[0],self.rect[1])
"""
override repr function output (don't include
name since pin shapes could have same shape but diff name e.g. blockage vs A)
"""
return "(layer={} ll={} ur={})".format(self.layer,self.rect[0],self.rect[1])
def __hash__(self):
""" Implement the hash function for sets etc. """
return hash(repr(self))
def __lt__(self, other):
""" Provide a function for ordering items by the ll point """
(ll, ur) = self.rect
(oll, our) = other.rect
if ll.x < oll.x and ll.y < oll.y:
return True
return False
def __eq__(self, other):
""" Check if these are the same pins for duplicate checks """
if isinstance(other, self.__class__):
@ -46,7 +63,7 @@ class pin_layout:
and return the new rectangle.
"""
if not spacing:
spacing = drc["{0}_to_{0}".format(self.layer)]
spacing = 0.5*drc("{0}_to_{0}".format(self.layer))
(ll,ur) = self.rect
spacing = vector(spacing, spacing)
@ -54,21 +71,39 @@ class pin_layout:
newur = ur + spacing
return (newll, newur)
def overlaps(self, other):
def intersection(self, other):
""" Check if a shape overlaps with a rectangle """
(ll,ur) = self.rect
(oll,our) = other.rect
# Start assuming no overlaps
min_x = max(ll.x, oll.x)
max_x = min(ll.x, oll.x)
min_y = max(ll.y, oll.y)
max_y = min(ll.y, oll.y)
return [vector(min_x,min_y),vector(max_x,max_y)]
def xoverlaps(self, other):
""" Check if shape has x overlap """
(ll,ur) = self.rect
(oll,our) = other.rect
x_overlaps = False
y_overlaps = False
# check if self is within other x range
if (ll.x >= oll.x and ll.x <= our.x) or (ur.x >= oll.x and ur.x <= our.x):
x_overlaps = True
# check if other is within self x range
if (oll.x >= ll.x and oll.x <= ur.x) or (our.x >= ll.x and our.x <= ur.x):
x_overlaps = True
return x_overlaps
def yoverlaps(self, other):
""" Check if shape has x overlap """
(ll,ur) = self.rect
(oll,our) = other.rect
y_overlaps = False
# check if self is within other y range
if (ll.y >= oll.y and ll.y <= our.y) or (ur.y >= oll.y and ur.y <= our.y):
y_overlaps = True
@ -76,7 +111,42 @@ class pin_layout:
if (oll.y >= ll.y and oll.y <= ur.y) or (our.y >= ll.y and our.y <= ur.y):
y_overlaps = True
return y_overlaps
def contains(self, other):
""" Check if a shape contains another rectangle """
# Can only overlap on the same layer
if self.layer != other.layer:
return False
(ll,ur) = self.rect
(oll,our) = other.rect
if not (oll.y >= ll.y and oll.y <= ur.y):
return False
if not (oll.x >= ll.x and oll.x <= ur.x):
return False
return True
def overlaps(self, other):
""" Check if a shape overlaps with a rectangle """
# Can only overlap on the same layer
if self.layer != other.layer:
return False
x_overlaps = self.xoverlaps(other)
y_overlaps = self.yoverlaps(other)
return x_overlaps and y_overlaps
def area(self):
""" Return the area. """
return self.height()*self.width()
def height(self):
""" Return height. Abs is for pre-normalized value."""
return abs(self.rect[1].y-self.rect[0].y)

View File

@ -10,15 +10,17 @@ class route(design):
"""
Object route (used by the router module)
Add a route of minimium metal width between a set of points.
The widths are the layer widths of the layer stack.
(Vias are in numer of vias.)
The wire must be completely rectilinear and the
z-dimension of the points refers to the layers (plus via)
z-dimension of the points refers to the layers.
The points are the center of the wire.
This can have non-preferred direction routing.
"""
unique_route_id = 0
def __init__(self, obj, layer_stack, path):
def __init__(self, obj, layer_stack, path, layer_widths=[None,1,None]):
name = "route_{0}".format(route.unique_route_id)
route.unique_route_id += 1
design.__init__(self, name)
@ -26,6 +28,7 @@ class route(design):
self.obj = obj
self.layer_stack = layer_stack
self.layer_widths = layer_widths
self.path = path
self.setup_layers()
@ -33,16 +36,16 @@ class route(design):
def setup_layers(self):
(horiz_layer, via_layer, vert_layer) = self.layer_stack
self.via_layer_name = via_layer
self.vert_layer_name = vert_layer
self.vert_layer_width = drc["minwidth_{0}".format(vert_layer)]
self.horiz_layer_name = horiz_layer
self.horiz_layer_width = drc["minwidth_{0}".format(horiz_layer)]
(self.horiz_layer_name, self.via_layer, self.vert_layer_name) = self.layer_stack
(self.horiz_layer_width, self.num_vias, self.vert_layer_width) = self.layer_widths
if not self.vert_layer_width:
self.vert_layer_width = drc("minwidth_{0}".format(self.vert_layer_name))
if not self.horiz_layer_width:
self.horiz_layer_width = drc("minwidth_{0}".format(self.horiz_layer_name))
# offset this by 1/2 the via size
self.c=contact(self.layer_stack, (1, 1))
self.c=contact(self.layer_stack, (self.num_vias, self.num_vias))
def create_wires(self):
@ -63,7 +66,8 @@ class route(design):
#via_offset = vector(p0.x+0.5*self.c.width,p0.y+0.5*self.c.height)
# offset if rotated
via_offset = vector(p0.x+0.5*self.c.height,p0.y-0.5*self.c.width)
self.obj.add_via(self.layer_stack,via_offset,rotate=90)
via_size = [self.num_vias]*2
self.obj.add_via(self.layer_stack,via_offset,size=via_size,rotate=90)
elif p0.x != p1.x and p0.y != p1.y: # diagonal!
debug.error("Non-changing direction!")
else:
@ -79,14 +83,36 @@ class route(design):
self.draw_corner_wire(plist[-1][1])
def get_layer_width(self, layer_zindex):
"""
Return the layer width
"""
if layer_zindex==0:
return self.horiz_layer_width
elif layer_zindex==1:
return self.vert_layer_width
else:
debug.error("Incorrect layer zindex.",-1)
def get_layer_name(self, layer_zindex):
"""
Return the layer name
"""
if layer_zindex==0:
return self.horiz_layer_name
elif layer_zindex==1:
return self.vert_layer_name
else:
debug.error("Incorrect layer zindex.",-1)
def draw_wire(self, p0, p1):
"""
This draws a straight wire with layer_minwidth
"""
layer_name = self.layer_stack[2*p0.z]
layer_width = drc["minwidth_{0}".format(layer_name)]
layer_width = self.get_layer_width(p0.z)
layer_name = self.get_layer_name(p0.z)
# always route left to right or bottom to top
if p0.z != p1.z:
@ -120,8 +146,8 @@ class route(design):
""" This function adds the corner squares since the center
line convention only draws to the center of the corner."""
layer_name = self.layer_stack[2*p0.z]
layer_width = drc["minwidth_{0}".format(layer_name)]
layer_width = self.get_layer_width(p0.z)
layer_name = self.get_layer_name(p0.z)
offset = vector(p0.x-0.5*layer_width,p0.y-0.5*layer_width)
self.obj.add_rect(layer=layer_name,
offset=offset,

View File

@ -3,6 +3,7 @@ import gdsMill
import tech
import math
import globals
import debug
from vector import vector
from pin_layout import pin_layout
@ -65,6 +66,7 @@ def get_gds_size(name, gds_filename, units, layer):
Open a GDS file and return the size from either the
bounding box or a border layer.
"""
debug.info(2,"Creating VLSI layout for {}".format(name))
cell_vlsi = gdsMill.VlsiLayout(units=units)
reader = gdsMill.Gds2reader(cell_vlsi)
reader.loadFromFile(gds_filename)
@ -72,6 +74,7 @@ def get_gds_size(name, gds_filename, units, layer):
cell = {}
measure_result = cell_vlsi.getLayoutBorder(layer)
if measure_result == None:
debug.info(2,"Layout border failed. Trying to measure size for {}".format(name))
measure_result = cell_vlsi.measureSize(name)
# returns width,height
return measure_result

View File

@ -11,24 +11,24 @@ class vector():
concise vector operations, output, and other more complex
data structures like lists.
"""
def __init__(self, x, y=None):
def __init__(self, x, y=0):
""" init function support two init method"""
# will take single input as a coordinate
if y==None:
self.x = x[0]
self.y = x[1]
if isinstance(x, (list,tuple,vector)):
self.x = float(x[0])
self.y = float(x[1])
#will take two inputs as the values of a coordinate
else:
self.x = x
self.y = y
self.x = float(x)
self.y = float(y)
def __str__(self):
""" override print function output """
return "["+str(self.x)+","+str(self.y)+"]"
return "v["+str(self.x)+","+str(self.y)+"]"
def __repr__(self):
""" override print function output """
return "["+str(self.x)+","+str(self.y)+"]"
return "v["+str(self.x)+","+str(self.y)+"]"
def __setitem__(self, index, value):
"""
@ -36,12 +36,12 @@ class vector():
can set value by vector[index]=value
"""
if index==0:
self.x=value
self.x=float(value)
elif index==1:
self.y=value
self.y=float(value)
else:
self.x=value[0]
self.y=value[1]
self.x=float(value[0])
self.y=float(value[1])
def __getitem__(self, index):
"""
@ -84,6 +84,14 @@ class vector():
"""
return vector(other[0]- self.x, other[1] - self.y)
def __hash__(self):
"""
Override - function (hash)
Note: This assumes that you DON'T CHANGE THE VECTOR or it will
break things.
"""
return hash((self.x,self.y))
def snap_to_grid(self):
self.x = self.snap_offset_to_grid(self.x)
self.y = self.snap_offset_to_grid(self.y)

View File

@ -33,14 +33,14 @@ class wire(path):
self.via_layer_name = via_layer
self.vert_layer_name = vert_layer
self.vert_layer_width = drc["minwidth_{0}".format(vert_layer)]
self.vert_layer_width = drc("minwidth_{0}".format(vert_layer))
self.horiz_layer_name = horiz_layer
self.horiz_layer_width = drc["minwidth_{0}".format(horiz_layer)]
self.horiz_layer_width = drc("minwidth_{0}".format(horiz_layer))
via_connect = contact(self.layer_stack,
(1, 1))
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.node_to_node = [drc("minwidth_" + str(self.horiz_layer_name)) + via_connect.width,
drc("minwidth_" + str(self.horiz_layer_name)) + via_connect.height]
# create a 1x1 contact
def create_vias(self):

View File

@ -0,0 +1,98 @@
import design
import debug
import utils
from tech import GDS,layer
class bitcell_1rw_1r(design.design):
"""
A single bit cell (6T, 8T, etc.) This module implements the
single memory cell used in the design. It is a hand-made cell, so
the layout and netlist should be available in the technology
library.
"""
pin_names = ["bl0", "br0", "bl1", "br1", "wl0", "wl1", "vdd", "gnd"]
(width,height) = utils.get_libcell_size("cell_1rw_1r", GDS["unit"], layer["boundary"])
pin_map = utils.get_libcell_pins(pin_names, "cell_1rw_1r", GDS["unit"], layer["boundary"])
def __init__(self):
design.design.__init__(self, "cell_1rw_1r")
debug.info(2, "Create bitcell with 1RW and 1R Port")
self.width = bitcell.width
self.height = bitcell.height
self.pin_map = bitcell.pin_map
def analytical_delay(self, slew, load=0, swing = 0.5):
# delay of bit cell is not like a driver(from WL)
# so the slew used should be 0
# it should not be slew dependent?
# because the value is there
# the delay is only over half transsmission gate
from tech import spice
r = spice["min_tx_r"]*3
c_para = spice["min_tx_drain_c"]
result = self.cal_delay_with_rc(r = r, c = c_para+load, slew = slew, swing = swing)
return result
def list_bitcell_pins(self, col, row):
""" Creates a list of connections in the bitcell, indexed by column and row, for instance use in bitcell_array """
bitcell_pins = ["bl0[{0}]".format(col),
"br0[{0}]".format(col),
"bl1[{0}]".format(col),
"br1[{0}]".format(col),
"wl0[{0}]".format(row),
"wl1[{0}]".format(row),
"vdd",
"gnd"]
return bitcell_pins
def list_all_wl_names(self):
""" Creates a list of all wordline pin names """
row_pins = ["wl0", "wl1"]
return row_pins
def list_all_bitline_names(self):
""" Creates a list of all bitline pin names (both bl and br) """
column_pins = ["bl0", "br0", "bl1", "br1"]
return column_pins
def list_all_bl_names(self):
""" Creates a list of all bl pins names """
column_pins = ["bl0", "bl1"]
return column_pins
def list_all_br_names(self):
""" Creates a list of all br pins names """
column_pins = ["br0", "br1"]
return column_pins
def list_read_bl_names(self):
""" Creates a list of bl pin names associated with read ports """
column_pins = ["bl0", "bl1"]
return column_pins
def list_read_br_names(self):
""" Creates a list of br pin names associated with read ports """
column_pins = ["br0", "br1"]
return column_pins
def list_write_bl_names(self):
""" Creates a list of bl pin names associated with write ports """
column_pins = ["bl0"]
return column_pins
def list_write_br_names(self):
""" Creates a list of br pin names asscociated with write ports"""
column_pins = ["br0"]
return column_pins
def analytical_power(self, proc, vdd, temp, load):
"""Bitcell power in nW. Only characterizes leakage."""
from tech import spice
leakage = spice["bitcell_leakage"]
dynamic = 0 #temporary
total_power = self.return_power(dynamic, leakage)
return total_power

File diff suppressed because it is too large Load Diff

View File

@ -6,6 +6,7 @@ from .lib import *
from .delay import *
from .setup_hold import *
from .functional import *
from .worst_case import *
from .simulation import *
@ -17,7 +18,7 @@ if not OPTS.analytical_delay:
if OPTS.spice_name != "":
OPTS.spice_exe=find_exe(OPTS.spice_name)
if OPTS.spice_exe=="":
if OPTS.spice_exe=="" or OPTS.spice_exe==None:
debug.error("{0} not found. Unable to perform characterization.".format(OPTS.spice_name),1)
else:
(OPTS.spice_name,OPTS.spice_exe) = get_tool("spice",["xa", "hspice", "ngspice", "ngspice.exe"])

View File

@ -7,8 +7,9 @@ from .trim_spice import *
from .charutils import *
import utils
from globals import OPTS
from .simulation import simulation
class delay():
class delay(simulation):
"""Functions to measure the delay and power of an SRAM at a given address and
data bit.
@ -26,27 +27,14 @@ class delay():
"""
def __init__(self, sram, spfile, corner):
self.sram = sram
self.name = sram.name
self.word_size = self.sram.word_size
self.addr_size = self.sram.addr_size
self.num_cols = self.sram.num_cols
self.num_rows = self.sram.num_rows
self.num_banks = self.sram.num_banks
self.sp_file = spfile
self.total_ports = self.sram.total_ports
self.total_write = self.sram.total_write
self.total_read = self.sram.total_read
self.read_index = self.sram.read_index
self.write_index = self.sram.write_index
self.port_id = self.sram.port_id
simulation.__init__(self, sram, spfile, corner)
# These are the member variables for a simulation
self.targ_read_ports = []
self.targ_write_ports = []
self.period = 0
self.set_load_slew(0,0)
self.set_corner(corner)
self.create_port_names()
self.create_signal_names()
#Create global measure names. Should maybe be an input at some point.
@ -66,34 +54,6 @@ class delay():
#This is TODO once multiport control has been finalized.
#self.control_name = "CSB"
def create_port_names(self):
"""Generates the port names to be used in characterization and sets default simulation target ports"""
self.write_ports = []
self.read_ports = []
self.total_port_num = OPTS.num_rw_ports + OPTS.num_w_ports + OPTS.num_r_ports
#save a member variable to avoid accessing global. readwrite ports have different control signals.
self.readwrite_port_num = OPTS.num_rw_ports
#Generate the port names. readwrite ports are required to be added first for this to work.
for readwrite_port_num in range(OPTS.num_rw_ports):
self.read_ports.append(readwrite_port_num)
self.write_ports.append(readwrite_port_num)
#This placement is intentional. It makes indexing input data easier. See self.data_values
for write_port_num in range(OPTS.num_rw_ports, OPTS.num_rw_ports+OPTS.num_w_ports):
self.write_ports.append(write_port_num)
for read_port_num in range(OPTS.num_rw_ports+OPTS.num_w_ports, OPTS.num_rw_ports+OPTS.num_w_ports+OPTS.num_r_ports):
self.read_ports.append(read_port_num)
#Set the default target ports for simulation. Default is all the ports.
self.targ_read_ports = self.read_ports
self.targ_write_ports = self.write_ports
def set_corner(self,corner):
""" Set the corner values """
self.corner = corner
(self.process, self.vdd_voltage, self.temperature) = corner
def set_load_slew(self,load,slew):
""" Set the load and slew """
self.load = load
@ -113,9 +73,9 @@ class delay():
debug.error("Given probe_data is not an integer to specify a data bit",1)
#Adding port options here which the characterizer cannot handle. Some may be added later like ROM
if len(self.read_ports) == 0:
if len(self.read_index) == 0:
debug.error("Characterizer does not currently support SRAMs without read ports.",1)
if len(self.write_ports) == 0:
if len(self.write_index) == 0:
debug.error("Characterizer does not currently support SRAMs without write ports.",1)
def write_generic_stimulus(self):
@ -129,12 +89,12 @@ class delay():
self.sf.write("\n* Instantiation of the SRAM\n")
self.stim.inst_sram(sram=self.sram,
port_signal_names=(self.addr_name,self.din_name,self.dout_name),
port_info=(self.total_port_num,self.write_ports,self.read_ports),
port_info=(self.total_ports,self.write_index,self.read_index),
abits=self.addr_size,
dbits=self.word_size,
sram_name=self.name)
self.sf.write("\n* SRAM output loads\n")
for port in self.read_ports:
for port in self.read_index:
for i in range(self.word_size):
self.sf.write("CD{0}{1} {2}{0}_{1} 0 {3}f\n".format(port,i,self.dout_name,self.load))
@ -172,7 +132,7 @@ class delay():
self.gen_control()
self.sf.write("\n* Generation of Port clock signal\n")
for port in range(self.total_port_num):
for port in range(self.total_ports):
self.stim.gen_pulse(sig_name="CLK{0}".format(port),
v1=0,
v2=self.vdd_voltage,
@ -195,9 +155,6 @@ class delay():
"""
self.check_arguments()
# obtains list of time-points for each rising clk edge
#self.create_test_cycles()
# creates and opens stimulus file for writing
temp_stim = "{0}/stim.sp".format(OPTS.openram_temp)
self.sf = open(temp_stim, "w")
@ -214,24 +171,24 @@ class delay():
# generate data and addr signals
self.sf.write("\n* Generation of data and address signals\n")
for write_port in self.write_ports:
for write_port in self.write_index:
for i in range(self.word_size):
self.stim.gen_constant(sig_name="{0}{1}_{2} ".format(self.din_name,write_port, i),
v_val=0)
for port in range(self.total_port_num):
for port in range(self.total_ports):
for i in range(self.addr_size):
self.stim.gen_constant(sig_name="{0}{1}_{2}".format(self.addr_name,port, i),
v_val=0)
# generate control signals
self.sf.write("\n* Generation of control signals\n")
for port in range(self.total_port_num):
for port in range(self.total_ports):
self.stim.gen_constant(sig_name="CSB{0}".format(port), v_val=self.vdd_voltage)
if port in self.write_ports and port in self.read_ports:
if port in self.write_index and port in self.read_index:
self.stim.gen_constant(sig_name="WEB{0}".format(port), v_val=self.vdd_voltage)
self.sf.write("\n* Generation of global clock signal\n")
for port in range(self.total_port_num):
for port in range(self.total_ports):
self.stim.gen_constant(sig_name="CLK{0}".format(port), v_val=0)
self.write_power_measures()
@ -257,10 +214,10 @@ class delay():
trig_name = trig_clk_name
if 'lh' in delay_name:
targ_dir="RISE"
trig_td = targ_td = self.cycle_times[self.measure_cycles["read1_{0}".format(port)]]
trig_td = targ_td = self.cycle_times[self.measure_cycles[port]["read1"]]
else:
targ_dir="FALL"
trig_td = targ_td = self.cycle_times[self.measure_cycles["read0_{0}".format(port)]]
trig_td = targ_td = self.cycle_times[self.measure_cycles[port]["read0"]]
elif 'slew' in delay_name:
trig_name = targ_name
@ -268,12 +225,12 @@ class delay():
trig_val = trig_slew_low
targ_val = targ_slew_high
targ_dir = trig_dir = "RISE"
trig_td = targ_td = self.cycle_times[self.measure_cycles["read1_{0}".format(port)]]
trig_td = targ_td = self.cycle_times[self.measure_cycles[port]["read1"]]
else:
trig_val = targ_slew_high
targ_val = trig_slew_low
targ_dir = trig_dir = "FALL"
trig_td = targ_td = self.cycle_times[self.measure_cycles["read0_{0}".format(port)]]
trig_td = targ_td = self.cycle_times[self.measure_cycles[port]["read0"]]
else:
debug.error(1, "Measure command {0} not recognized".format(delay_name))
return (meas_name,trig_name,targ_name,trig_val,targ_val,trig_dir,targ_dir,trig_td,targ_td)
@ -294,11 +251,11 @@ class delay():
#Different naming schemes are used for the measure cycle dict and measurement names.
#TODO: make them the same so they can be indexed the same.
if '1' in pname:
t_initial = self.cycle_times[self.measure_cycles["read1_{0}".format(port)]]
t_final = self.cycle_times[self.measure_cycles["read1_{0}".format(port)]+1]
t_initial = self.cycle_times[self.measure_cycles[port]["read1"]]
t_final = self.cycle_times[self.measure_cycles[port]["read1"]+1]
elif '0' in pname:
t_initial = self.cycle_times[self.measure_cycles["read0_{0}".format(port)]]
t_final = self.cycle_times[self.measure_cycles["read0_{0}".format(port)]+1]
t_initial = self.cycle_times[self.measure_cycles[port]["read0"]]
t_final = self.cycle_times[self.measure_cycles[port]["read0"]+1]
self.stim.gen_meas_power(meas_name="{0}{1}".format(pname, port),
t_initial=t_initial,
t_final=t_final)
@ -311,11 +268,11 @@ class delay():
for pname in self.power_meas_names:
if "write" not in pname:
continue
t_initial = self.cycle_times[self.measure_cycles["write0_{0}".format(port)]]
t_final = self.cycle_times[self.measure_cycles["write0_{0}".format(port)]+1]
t_initial = self.cycle_times[self.measure_cycles[port]["write0"]]
t_final = self.cycle_times[self.measure_cycles[port]["write0"]+1]
if '1' in pname:
t_initial = self.cycle_times[self.measure_cycles["write1_{0}".format(port)]]
t_final = self.cycle_times[self.measure_cycles["write1_{0}".format(port)]+1]
t_initial = self.cycle_times[self.measure_cycles[port]["write1"]]
t_final = self.cycle_times[self.measure_cycles[port]["write1"]+1]
self.stim.gen_meas_power(meas_name="{0}{1}".format(pname, port),
t_initial=t_initial,
@ -360,10 +317,9 @@ class delay():
double the period until we find a valid period to use as a
starting point.
"""
debug.check(port in self.read_ports, "Characterizer requires a read port to determine a period.")
debug.check(port in self.read_index, "Characterizer requires a read port to determine a period.")
feasible_period = float(tech.spice["feasible_period"])
#feasible_period = float(2.5)#What happens if feasible starting point is wrong?
time_out = 9
while True:
time_out -= 1
@ -406,19 +362,18 @@ class delay():
Loops through all read ports determining the feasible period and collecting
delay information from each port.
"""
feasible_delays = [{} for i in range(self.total_port_num)]
self.period = float(tech.spice["feasible_period"])
feasible_delays = [{} for i in range(self.total_ports)]
#Get initial feasible delays from first port
feasible_delays[self.read_ports[0]] = self.find_feasible_period_one_port(self.read_ports[0])
feasible_delays[self.read_index[0]] = self.find_feasible_period_one_port(self.read_index[0])
previous_period = self.period
#Loops through all the ports checks if the feasible period works. Everything restarts it if does not.
#Write ports do not produce delays which is why they are not included here.
i = 1
while i < len(self.read_ports):
port = self.read_ports[i]
while i < len(self.read_index):
port = self.read_index[i]
#Only extract port values from the specified port, not the entire results.
feasible_delays[port].update(self.find_feasible_period_one_port(port))
#Function sets the period. Restart the entire process if period changes to collect accurate delays
@ -461,7 +416,7 @@ class delay():
#Sanity Check
debug.check(self.period > 0, "Target simulation period non-positive")
result = [{} for i in range(self.total_port_num)]
result = [{} for i in range(self.total_ports)]
# Checking from not data_value to data_value
self.write_delay_stimulus()
@ -563,7 +518,7 @@ class delay():
#Find the minimum period for all ports. Start at one port and perform binary search then use that delay as a starting position.
#For testing purposes, only checks read ports.
for port in self.read_ports:
for port in self.read_index:
target_period = self.find_min_period_one_port(feasible_delays, port, lb_period, ub_period, target_period)
#The min period of one port becomes the new lower bound. Reset the upper_bound.
lb_period = target_period
@ -728,8 +683,8 @@ class delay():
"""Simulate all specified output loads and input slews pairs of all ports"""
measure_data = self.get_empty_measure_data_dict()
#Set the target simulation ports to all available ports. This make sims slower but failed sims exit anyways.
self.targ_read_ports = self.read_ports
self.targ_write_ports = self.write_ports
self.targ_read_ports = self.read_index
self.targ_write_ports = self.write_index
for slew in slews:
for load in loads:
self.set_load_slew(load,slew)
@ -738,7 +693,7 @@ class delay():
debug.check(success,"Couldn't run a simulation. slew={0} load={1}\n".format(self.slew,self.load))
debug.info(1, "Simulation Passed: Port {0} slew={1} load={2}".format("All", self.slew,self.load))
#The results has a dict for every port but dicts can be empty (e.g. ports were not targeted).
for port in range(self.total_port_num):
for port in range(self.total_ports):
for mname,value in delay_results[port].items():
if "power" in mname:
# Subtract partial array leakage and add full array leakage for the power measures
@ -746,119 +701,8 @@ class delay():
else:
measure_data[port][mname].append(value)
return measure_data
def add_data(self, data, port):
""" Add the array of data values """
debug.check(len(data)==self.word_size, "Invalid data word size.")
debug.check(port < len(self.data_values), "Port number cannot index data values.")
index = 0
for c in data:
if c=="0":
self.data_values[port][index].append(0)
elif c=="1":
self.data_values[port][index].append(1)
else:
debug.error("Non-binary data string",1)
index += 1
def add_address(self, address, port):
""" Add the array of address values """
debug.check(len(address)==self.addr_size, "Invalid address size.")
index = 0
for c in address:
if c=="0":
self.addr_values[port][index].append(0)
elif c=="1":
self.addr_values[port][index].append(1)
else:
debug.error("Non-binary address string",1)
index += 1
def add_noop_one_port(self, address, data, port):
""" Add the control values for a noop to a single port. """
#This is to be used as a helper function for the other add functions. Cycle and comments are omitted.
self.add_control_one_port(port, "noop")
if port in self.write_ports:
self.add_data(data,port)
self.add_address(address, port)
def add_noop_all_ports(self, comment, address, data):
""" Add the control values for a noop to all ports. """
self.add_comment("All", comment)
self.cycle_times.append(self.t_current)
self.t_current += self.period
for port in range(self.total_port_num):
self.add_noop_one_port(address, data, port)
def add_read(self, comment, address, data, port):
""" Add the control values for a read cycle. """
debug.check(port in self.read_ports, "Cannot add read cycle to a write port.")
self.add_comment(port, comment)
self.cycle_times.append(self.t_current)
self.t_current += self.period
self.add_control_one_port(port, "read")
#If the port is also a readwrite then add data.
if port in self.write_ports:
self.add_data(data,port)
self.add_address(address, port)
#This value is hard coded here. Possibly change to member variable or set in add_noop_one_port
noop_data = "0"*self.word_size
#Add noops to all other ports.
for unselected_port in range(self.total_port_num):
if unselected_port != port:
self.add_noop_one_port(address, noop_data, unselected_port)
def add_write(self, comment, address, data, port):
""" Add the control values for a write cycle. """
debug.check(port in self.write_ports, "Cannot add read cycle to a read port.")
self.add_comment(port, comment)
self.cycle_times.append(self.t_current)
self.t_current += self.period
self.add_control_one_port(port, "write")
self.add_data(data,port)
self.add_address(address,port)
#This value is hard coded here. Possibly change to member variable or set in add_noop_one_port
noop_data = "0"*self.word_size
#Add noops to all other ports.
for unselected_port in range(self.total_port_num):
if unselected_port != port:
self.add_noop_one_port(address, noop_data, unselected_port)
def add_control_one_port(self, port, op):
"""Appends control signals for operation to a given port"""
#Determine values to write to port
web_val = 1
csb_val = 1
if op == "read":
csb_val = 0
elif op == "write":
csb_val = 0
web_val = 0
elif op != "noop":
debug.error("Could not add control signals for port {0}. Command {1} not recognized".format(port,op),1)
#Append the values depending on the type of port
self.csb_values[port].append(csb_val)
#If port is in both lists, add rw control signal. Condition indicates its a RW port.
if port in self.write_ports and port in self.read_ports:
self.web_values[port].append(web_val)
def add_comment(self, port, comment):
"""Add comment to list to be printed in stimulus file"""
#Clean up time before appending. Make spacing dynamic as well.
time = "{0:.2f} ns:".format(self.t_current)
time_spacing = len(time)+6
self.cycle_comments.append("Cycle {0:<6d} Port {1:<6} {2:<{3}}: {4}".format(len(self.cycle_times),
port,
time,
time_spacing,
comment))
def gen_test_cycles_one_port(self, read_port, write_port):
"""Intended but not implemented: Returns a list of key time-points [ns] of the waveform (each rising edge)
of the cycles to do a timing evaluation of a single port. Current: Values overwritten for multiple calls"""
@ -886,8 +730,7 @@ class delay():
self.add_write("W data 0 address 11..11 to write value",
self.probe_address,data_zeros,write_port)
self.measure_cycles["write0_{0}".format(write_port)] = len(self.cycle_times)-1
#self.write0_cycle=len(self.cycle_times)-1 # Remember for power measure
self.measure_cycles[write_port]["write0"] = len(self.cycle_times)-1
# This also ensures we will have a H->L transition on the next read
self.add_read("R data 1 address 00..00 to set DOUT caps",
@ -895,18 +738,14 @@ class delay():
self.add_read("R data 0 address 11..11 to check W0 worked",
self.probe_address,data_zeros,read_port)
self.measure_cycles["read0_{0}".format(read_port)] = len(self.cycle_times)-1
#self.read0_cycle=len(self.cycle_times)-1 # Remember for power measure
self.measure_cycles[read_port]["read0"] = len(self.cycle_times)-1
self.add_noop_all_ports("Idle cycle (if read takes >1 cycle)",
inverse_address,data_zeros)
#Does not seem like is is used anywhere commenting out for now.
#self.idle_cycle=len(self.cycle_times)-1 # Remember for power measure
self.add_write("W data 1 address 11..11 to write value",
self.probe_address,data_ones,write_port)
self.measure_cycles["write1_{0}".format(write_port)] = len(self.cycle_times)-1
#self.write1_cycle=len(self.cycle_times)-1 # Remember for power measure
self.measure_cycles[write_port]["write1"] = len(self.cycle_times)-1
self.add_write("W data 0 address 00..00 to clear DIN caps",
inverse_address,data_zeros,write_port)
@ -917,19 +756,22 @@ class delay():
self.add_read("R data 1 address 11..11 to check W1 worked",
self.probe_address,data_zeros,read_port)
self.measure_cycles["read1_{0}".format(read_port)] = len(self.cycle_times)-1
#self.read1_cycle=len(self.cycle_times)-1 # Remember for power measure
self.measure_cycles[read_port]["read1"] = len(self.cycle_times)-1
self.add_noop_all_ports("Idle cycle (if read takes >1 cycle))",
self.probe_address,data_zeros)
def get_available_port(self,get_read_port):
"""Returns the first accessible read or write port. """
if get_read_port and len(self.read_ports) > 0:
return self.read_ports[0]
elif not get_read_port and len(self.write_ports) > 0:
return self.write_ports[0]
if get_read_port and len(self.read_index) > 0:
return self.read_index[0]
elif not get_read_port and len(self.write_index) > 0:
return self.write_index[0]
return None
def set_stimulus_variables(self):
simulation.set_stimulus_variables(self)
self.measure_cycles = [{} for port in range(self.total_ports)]
def create_test_cycles(self):
"""Returns a list of key time-points [ns] of the waveform (each rising edge)
@ -937,35 +779,16 @@ class delay():
and does not need a rising edge."""
#Using this requires setting at least one port to target for simulation.
if len(self.targ_write_ports) == 0 and len(self.targ_read_ports) == 0:
debug.error("No ports selected for characterization.",1)
# Start at time 0
self.t_current = 0
# Cycle times (positive edge) with comment
self.cycle_comments = []
self.cycle_times = []
self.measure_cycles = {}
# Control signals for ports. These are not the final signals and will likely be changed later.
#web is the enable for write ports. Dicts used for simplicity as ports are not necessarily incremental.
self.web_values = {port:[] for port in self.write_ports}
#csb acts as an enable for the read ports.
self.csb_values = {port:[] for port in range(self.total_port_num)}
# Address and data values for each address/data bit. A 3d list of size #ports x bits x cycles.
self.data_values=[[[] for bit in range(self.word_size)] for port in range(len(self.write_ports))]
self.addr_values=[[[] for bit in range(self.addr_size)] for port in range(self.total_port_num)]
debug.error("No port selected for characterization.",1)
self.set_stimulus_variables()
#Get any available read/write port in case only a single write or read ports is being characterized.
cur_read_port = self.get_available_port(get_read_port=True)
cur_write_port = self.get_available_port(get_read_port=False)
#These checks should be superceded by check_arguments which should have been called earlier, so this is a double check.
debug.check(cur_read_port != None, "Characterizer requires at least 1 read port")
debug.check(cur_write_port != None, "Characterizer requires at least 1 write port")
#Characterizing the remaining target ports. Not the final design.
#Create test cycles for specified target ports.
write_pos = 0
read_pos = 0
while True:
@ -997,7 +820,7 @@ class delay():
for slew in slews:
for load in loads:
self.set_load_slew(load,slew)
bank_delay = sram.analytical_delay(self.slew,self.load)
bank_delay = sram.analytical_delay(self.vdd_voltage, self.slew,self.load)
# Convert from ps to ns
delay_lh.append(bank_delay.delay/1e3)
delay_hl.append(bank_delay.delay/1e3)
@ -1026,7 +849,7 @@ class delay():
def gen_data(self):
""" Generates the PWL data inputs for a simulation timing test. """
for write_port in self.write_ports:
for write_port in self.write_index:
for i in range(self.word_size):
sig_name="{0}{1}_{2} ".format(self.din_name,write_port, i)
self.stim.gen_pwl(sig_name, self.cycle_times, self.data_values[write_port][i], self.period, self.slew, 0.05)
@ -1036,16 +859,16 @@ class delay():
Generates the address inputs for a simulation timing test.
This alternates between all 1's and all 0's for the address.
"""
for port in range(self.total_port_num):
for port in range(self.total_ports):
for i in range(self.addr_size):
sig_name = "{0}{1}_{2}".format(self.addr_name,port,i)
self.stim.gen_pwl(sig_name, self.cycle_times, self.addr_values[port][i], self.period, self.slew, 0.05)
def gen_control(self):
""" Generates the control signals """
for port in range(self.total_port_num):
for port in range(self.total_ports):
self.stim.gen_pwl("CSB{0}".format(port), self.cycle_times, self.csb_values[port], self.period, self.slew, 0.05)
if port in self.read_ports and port in self.write_ports:
if port in self.read_index and port in self.write_index:
self.stim.gen_pwl("WEB{0}".format(port), self.cycle_times, self.web_values[port], self.period, self.slew, 0.05)
@ -1053,5 +876,5 @@ class delay():
"""Make a dict of lists for each type of delay and power measurement to append results to"""
measure_names = self.delay_meas_names + self.power_meas_names
#Create list of dicts. List lengths is # of ports. Each dict maps the measurement names to lists.
measure_data = [{mname:[] for mname in measure_names} for i in range(self.total_port_num)]
measure_data = [{mname:[] for mname in measure_names} for i in range(self.total_ports)]
return measure_data

View File

@ -52,18 +52,18 @@ class functional(simulation):
rw_ops = ["noop", "write", "read"]
w_ops = ["noop", "write"]
r_ops = ["noop", "read"]
rw_read_data = "0"*self.word_size
rw_read_din_data = "0"*self.word_size
check = 0
# First cycle idle
self.add_noop_all_ports("Idle at time {0}n".format(self.t_current),
"0"*self.addr_size, "0"*self.word_size)
comment = self.gen_cycle_comment("noop", "0"*self.word_size, "0"*self.addr_size, 0, self.t_current)
self.add_noop_all_ports(comment, "0"*self.addr_size, "0"*self.word_size)
# Write at least once
addr = self.gen_addr()
word = self.gen_data()
self.add_write("Writing {0} to address {1} (from port {2}) at time {3}n".format(word, addr, 0, self.t_current),
addr, word, 0)
comment = self.gen_cycle_comment("write", word, addr, 0, self.t_current)
self.add_write(comment, addr, word, 0)
self.stored_words[addr] = word
# Read at least once. For multiport, it is important that one read cycle uses all RW and R port to read from the same address simultaniously.
@ -72,8 +72,8 @@ class functional(simulation):
if self.port_id[port] == "w":
self.add_noop_one_port("0"*self.addr_size, "0"*self.word_size, port)
else:
self.add_read_one_port("Reading {0} from address {1} (from port {2}) at time {3}n".format(word, addr, port, self.t_current),
addr, rw_read_data, port)
comment = self.gen_cycle_comment("read", word, addr, port, self.t_current)
self.add_read_one_port(comment, addr, rw_read_din_data, port)
self.write_check.append([word, "{0}{1}".format(self.dout_name,port), self.t_current+self.period, check])
check += 1
self.cycle_times.append(self.t_current)
@ -101,8 +101,8 @@ class functional(simulation):
if addr in w_addrs:
self.add_noop_one_port("0"*self.addr_size, "0"*self.word_size, port)
else:
self.add_write_one_port("Writing {0} to address {1} (from port {2}) at time {3}n".format(word, addr, port, self.t_current),
addr, word, port)
comment = self.gen_cycle_comment("write", word, addr, port, self.t_current)
self.add_write_one_port(comment, addr, word, port)
self.stored_words[addr] = word
w_addrs.append(addr)
else:
@ -111,8 +111,8 @@ class functional(simulation):
if addr in w_addrs:
self.add_noop_one_port("0"*self.addr_size, "0"*self.word_size, port)
else:
self.add_read_one_port("Reading {0} from address {1} (from port {2}) at time {3}n".format(word, addr, port, self.t_current),
addr, rw_read_data, port)
comment = self.gen_cycle_comment("read", word, addr, port, self.t_current)
self.add_read_one_port(comment, addr, rw_read_din_data, port)
self.write_check.append([word, "{0}{1}".format(self.dout_name,port), self.t_current+self.period, check])
check += 1
@ -120,8 +120,8 @@ class functional(simulation):
self.t_current += self.period
# Last cycle idle needed to correctly measure the value on the second to last clock edge
self.add_noop_all_ports("Idle at time {0}n".format(self.t_current),
"0"*self.addr_size, "0"*self.word_size)
comment = self.gen_cycle_comment("noop", "0"*self.word_size, "0"*self.addr_size, 0, self.t_current)
self.add_noop_all_ports(comment, "0"*self.addr_size, "0"*self.word_size)
def read_stim_results(self):
# Extrat DOUT values from spice timing.lis
@ -129,17 +129,17 @@ class functional(simulation):
sp_read_value = ""
for bit in range(self.word_size):
value = parse_spice_list("timing", "v{0}.{1}ck{2}".format(dout_port.lower(),bit,check))
if value > 0.9 * self.vdd_voltage:
if value > 0.88 * self.vdd_voltage:
sp_read_value = "1" + sp_read_value
elif value < 0.1 * self.vdd_voltage:
elif value < 0.12 * self.vdd_voltage:
sp_read_value = "0" + sp_read_value
else:
error ="FAILED: {0}_{1} value {2} at time {3}n does not fall within noise margins <{4} or >{5}.".format(dout_port,
bit,
value,
eo_period,
0.1*self.vdd_voltage,
0.9*self.vdd_voltage)
0.12*self.vdd_voltage,
0.88*self.vdd_voltage)
return (0, error)
self.read_check.append([sp_read_value, dout_port, eo_period, check])
@ -148,10 +148,11 @@ class functional(simulation):
def check_stim_results(self):
for i in range(len(self.write_check)):
if self.write_check[i][0] != self.read_check[i][0]:
error = "FAILED: {0} value {1} does not match written value {2} read at time {3}n".format(self.read_check[i][1],
self.read_check[i][0],
self.write_check[i][0],
self.read_check[i][2])
error = "FAILED: {0} value {1} does not match written value {2} read during cycle {3} at time {4}n".format(self.read_check[i][1],
self.read_check[i][0],
self.write_check[i][0],
int((self.read_check[i][2]-self.period)/self.period),
self.read_check[i][2])
return(0, error)
return(1, "SUCCESS")
@ -224,6 +225,11 @@ class functional(simulation):
sig_name="{0}{1}_{2} ".format(self.dout_name, self.read_index[port], bit)
self.sf.write("CD{0}{1} {2} 0 {3}f\n".format(self.read_index[port], bit, sig_name, self.load))
# Write debug comments to stim file
self.sf.write("\n\n * Sequence of operations\n")
for comment in self.fn_cycle_comments:
self.sf.write("*{}\n".format(comment))
# Generate data input bits
self.sf.write("\n* Generation of data and address signals\n")
for port in range(self.total_write):

View File

@ -17,7 +17,8 @@ class lib:
self.sram = sram
self.sp_file = sp_file
self.use_model = use_model
self.gen_port_names() #copy and paste from delay.py, names are not final will likely be changed later.
#self.gen_port_names() #copy and paste from delay.py, names are not final will likely be changed later.
self.set_port_indices()
self.prepare_tables()
@ -25,7 +26,10 @@ class lib:
self.characterize_corners()
def set_port_indices(self):
self.total_port_num = self.sram.total_ports
self.read_ports = self.sram.read_index
self.write_ports = self.sram.write_index
def gen_port_names(self):
"""Generates the port names to be written to the lib file"""
@ -108,21 +112,21 @@ class lib:
self.write_header()
#Loop over all readwrite ports. This is debugging. Will change later.
#Loop over all ports.
for port in range(self.total_port_num):
#set the read and write port as inputs.
self.write_data_bus(port)
self.write_addr_bus(port)
self.write_control_pins(port) #need to split this into sram and port control signals
self.write_clk_timing_power()
self.write_clk_timing_power(port)
self.write_footer()
def write_footer(self):
""" Write the footer """
self.lib.write("}\n")
self.lib.write(" }\n") #Closing brace for the cell
self.lib.write("}\n") #Closing brace for the library
def write_header(self):
""" Write the header information """
@ -151,7 +155,7 @@ class lib:
self.lib.write(" dont_touch : true;\n")
self.lib.write(" area : {};\n\n".format(self.sram.width * self.sram.height))
#Build string of all control signals. This is subject to change once control signals finalized.
#Build string of all control signals.
control_str = 'CSb0' #assume at least 1 port
for i in range(1, self.total_port_num):
control_str += ' & CSb{0}'.format(i)
@ -296,12 +300,12 @@ class lib:
self.lib.write(" }\n\n")
def write_FF_setuphold(self):
def write_FF_setuphold(self, port):
""" Adds Setup and Hold timing results"""
self.lib.write(" timing(){ \n")
self.lib.write(" timing_type : setup_rising; \n")
self.lib.write(" related_pin : \"clk\"; \n")
self.lib.write(" related_pin : \"clk{0}\"; \n".format(port))
self.lib.write(" rise_constraint(CONSTRAINT_TABLE) {\n")
rounded_values = list(map(round_time,self.times["setup_times_LH"]))
self.write_values(rounded_values,len(self.slews)," ")
@ -313,7 +317,7 @@ class lib:
self.lib.write(" }\n")
self.lib.write(" timing(){ \n")
self.lib.write(" timing_type : hold_rising; \n")
self.lib.write(" related_pin : \"clk\"; \n")
self.lib.write(" related_pin : \"clk{0}\"; \n".format(port))
self.lib.write(" rise_constraint(CONSTRAINT_TABLE) {\n")
rounded_values = list(map(round_time,self.times["hold_times_LH"]))
self.write_values(rounded_values,len(self.slews)," ")
@ -339,10 +343,9 @@ class lib:
self.lib.write(" pin(DOUT{1}[{0}:0]){{\n".format(self.sram.word_size - 1, read_port))
self.write_FF_setuphold()
self.lib.write(" timing(){ \n")
self.lib.write(" timing_sense : non_unate; \n")
self.lib.write(" related_pin : \"clk\"; \n")
self.lib.write(" related_pin : \"clk{0}\"; \n".format(read_port))
self.lib.write(" timing_type : rising_edge; \n")
self.lib.write(" cell_rise(CELL_TABLE) {\n")
self.write_values(self.char_port_results[read_port]["delay_lh"],len(self.loads)," ")
@ -361,7 +364,7 @@ class lib:
self.lib.write(" }\n\n") # bus
def write_data_bus_input(self, write_port):
""" Adds data bus timing results."""
""" Adds DIN data bus timing results."""
self.lib.write(" bus(DIN{0}){{\n".format(write_port))
self.lib.write(" bus_type : DATA; \n")
@ -370,9 +373,12 @@ class lib:
self.lib.write(" capacitance : {0}; \n".format(tech.spice["dff_in_cap"]))
self.lib.write(" memory_write(){ \n")
self.lib.write(" address : ADDR{0}; \n".format(write_port))
self.lib.write(" clocked_on : clk; \n")
self.lib.write(" }\n")
self.lib.write(" }\n")
self.lib.write(" clocked_on : clk{0}; \n".format(write_port))
self.lib.write(" }\n")
self.lib.write(" pin(DIN{1}[{0}:0]){{\n".format(self.sram.word_size - 1, write_port))
self.write_FF_setuphold(write_port)
self.lib.write(" }\n") # pin
self.lib.write(" }\n") #bus
def write_data_bus(self, port):
""" Adds data bus timing results."""
@ -392,7 +398,7 @@ class lib:
self.lib.write(" pin(ADDR{1}[{0}:0])".format(self.sram.addr_size - 1, port))
self.lib.write("{\n")
self.write_FF_setuphold()
self.write_FF_setuphold(port)
self.lib.write(" }\n")
self.lib.write(" }\n\n")
@ -409,28 +415,25 @@ class lib:
self.lib.write("{\n")
self.lib.write(" direction : input; \n")
self.lib.write(" capacitance : {0}; \n".format(tech.spice["dff_in_cap"]))
self.write_FF_setuphold()
self.write_FF_setuphold(port)
self.lib.write(" }\n\n")
def write_clk_timing_power(self):
def write_clk_timing_power(self, port):
""" Adds clk pin timing results."""
self.lib.write(" pin(clk){\n")
self.lib.write(" pin(clk{0}){{\n".format(port))
self.lib.write(" clock : true;\n")
self.lib.write(" direction : input; \n")
# FIXME: This depends on the clock buffer size in the control logic
self.lib.write(" capacitance : {0}; \n".format(tech.spice["dff_in_cap"]))
#Add power values for the ports. lib generated with this is not syntactically correct. TODO once
#top level is done.
for port in range(self.total_port_num):
self.add_clk_control_power(port)
self.add_clk_control_power(port)
min_pulse_width = round_time(self.char_sram_results["min_period"])/2.0
min_period = round_time(self.char_sram_results["min_period"])
self.lib.write(" timing(){ \n")
self.lib.write(" timing_type :\"min_pulse_width\"; \n")
self.lib.write(" related_pin : clk; \n")
self.lib.write(" related_pin : clk{0}; \n".format(port))
self.lib.write(" rise_constraint(scalar) {\n")
self.lib.write(" values(\"{0}\"); \n".format(min_pulse_width))
self.lib.write(" }\n")
@ -440,7 +443,7 @@ class lib:
self.lib.write(" }\n")
self.lib.write(" timing(){ \n")
self.lib.write(" timing_type :\"minimum_period\"; \n")
self.lib.write(" related_pin : clk; \n")
self.lib.write(" related_pin : clk{0}; \n".format(port))
self.lib.write(" rise_constraint(scalar) {\n")
self.lib.write(" values(\"{0}\"); \n".format(min_period))
self.lib.write(" }\n")
@ -448,8 +451,7 @@ class lib:
self.lib.write(" values(\"{0}\"); \n".format(min_period))
self.lib.write(" }\n")
self.lib.write(" }\n")
self.lib.write(" }\n")
self.lib.write(" }\n")
self.lib.write(" }\n\n")
def add_clk_control_power(self, port):
"""Writes powers under the clock pin group for a specified port"""
@ -461,7 +463,7 @@ class lib:
web_name = " & !WEb{0}".format(port)
avg_write_power = np.mean(self.char_port_results[port]["write1_power"] + self.char_port_results[port]["write0_power"])
self.lib.write(" internal_power(){\n")
self.lib.write(" when : \"!CSb{0} & clk{1}\"; \n".format(port, web_name))
self.lib.write(" when : \"!CSb{0} & clk{0}{1}\"; \n".format(port, web_name))
self.lib.write(" rise_power(scalar){\n")
self.lib.write(" values(\"{0}\");\n".format(avg_write_power/2.0))
self.lib.write(" }\n")
@ -475,7 +477,7 @@ class lib:
web_name = " & WEb{0}".format(port)
avg_read_power = np.mean(self.char_port_results[port]["read1_power"] + self.char_port_results[port]["read0_power"])
self.lib.write(" internal_power(){\n")
self.lib.write(" when : \"!CSb{0} & !clk{1}\"; \n".format(port, web_name))
self.lib.write(" when : \"!CSb{0} & !clk{0}{1}\"; \n".format(port, web_name))
self.lib.write(" rise_power(scalar){\n")
self.lib.write(" values(\"{0}\");\n".format(avg_read_power/2.0))
self.lib.write(" }\n")

View File

@ -58,6 +58,7 @@ class simulation():
# For generating comments in SPICE stimulus
self.cycle_comments = []
self.fn_cycle_comments = []
def add_control_one_port(self, port, op):
"""Appends control signals for operation to a given port"""
@ -81,7 +82,6 @@ class simulation():
def add_data(self, data, port):
""" Add the array of data values """
debug.check(len(data)==self.word_size, "Invalid data word size.")
#debug.check(port < len(self.data_values), "Port number cannot index data values.")
bit = self.word_size - 1
for c in data:
@ -109,12 +109,11 @@ class simulation():
def add_write(self, comment, address, data, port):
""" Add the control values for a write cycle. """
debug.info(1, comment)
debug.check(port in self.write_index, "Cannot add write cycle to a read port. Port {0}, Write Ports {1}".format(port, self.write_index))
self.cycle_comments.append("Cycle {0:2d}\tPort {3}\t{1:5.2f}ns:\t{2}".format(len(self.cycle_comments),
self.t_current,
comment,
port))
debug.info(2, comment)
self.fn_cycle_comments.append(comment)
self.append_cycle_comment(port, comment)
self.cycle_times.append(self.t_current)
self.t_current += self.period
@ -129,21 +128,20 @@ class simulation():
if unselected_port != port:
self.add_noop_one_port(address, noop_data, unselected_port)
def add_read(self, comment, address, data, port):
def add_read(self, comment, address, din_data, port):
""" Add the control values for a read cycle. """
debug.info(1, comment)
debug.check(port in self.read_index, "Cannot add read cycle to a write port. Port {0}, Read Ports {1}".format(port, self.read_index))
self.cycle_comments.append("Cycle {0:2d}\tPort {3}\t{1:5.2f}ns:\t{2}".format(len(self.cycle_comments),
self.t_current,
comment,
port))
debug.info(2, comment)
self.fn_cycle_comments.append(comment)
self.append_cycle_comment(port, comment)
self.cycle_times.append(self.t_current)
self.t_current += self.period
self.add_control_one_port(port, "read")
#If the port is also a readwrite then add data.
if port in self.write_index:
self.add_data(data,port)
self.add_data(din_data,port)
self.add_address(address, port)
#This value is hard coded here. Possibly change to member variable or set in add_noop_one_port
@ -155,10 +153,10 @@ class simulation():
def add_noop_all_ports(self, comment, address, data):
""" Add the control values for a noop to all ports. """
debug.info(1, comment)
self.cycle_comments.append("Cycle {0:2d}\tPort All\t{1:5.2f}ns:\t{2}".format(len(self.cycle_times),
self.t_current,
comment))
debug.info(2, comment)
self.fn_cycle_comments.append(comment)
self.append_cycle_comment("All", comment)
self.cycle_times.append(self.t_current)
self.t_current += self.period
@ -168,27 +166,23 @@ class simulation():
def add_write_one_port(self, comment, address, data, port):
""" Add the control values for a write cycle. Does not increment the period. """
debug.check(port in self.write_index, "Cannot add write cycle to a read port. Port {0}, Write Ports {1}".format(port, self.write_index))
debug.info(1, comment)
self.cycle_comments.append("Cycle {0:2d}\tPort {3}\t{1:5.2f}ns:\t{2}".format(len(self.cycle_comments),
self.t_current,
comment,
port))
debug.info(2, comment)
self.fn_cycle_comments.append(comment)
self.add_control_one_port(port, "write")
self.add_data(data,port)
self.add_address(address,port)
def add_read_one_port(self, comment, address, data, port):
def add_read_one_port(self, comment, address, din_data, port):
""" Add the control values for a read cycle. Does not increment the period. """
debug.check(port in self.read_index, "Cannot add read cycle to a write port. Port {0}, Read Ports {1}".format(port, self.read_index))
debug.info(1, comment)
self.cycle_comments.append("Cycle {0:2d}\tPort {3}\t{1:5.2f}ns:\t{2}".format(len(self.cycle_comments),
self.t_current,
comment,
port))
debug.info(2, comment)
self.fn_cycle_comments.append(comment)
self.add_control_one_port(port, "read")
#If the port is also a readwrite then add data.
if port in self.write_index:
self.add_data(data,port)
self.add_data(din_data,port)
self.add_address(address, port)
def add_noop_one_port(self, address, data, port):
@ -197,4 +191,36 @@ class simulation():
if port in self.write_index:
self.add_data(data,port)
self.add_address(address, port)
def append_cycle_comment(self, port, comment):
"""Add comment to list to be printed in stimulus file"""
#Clean up time before appending. Make spacing dynamic as well.
time = "{0:.2f} ns:".format(self.t_current)
time_spacing = len(time)+6
self.cycle_comments.append("Cycle {0:<6d} Port {1:<6} {2:<{3}}: {4}".format(len(self.cycle_times),
port,
time,
time_spacing,
comment))
def gen_cycle_comment(self, op, word, addr, port, t_current):
if op == "noop":
comment = "\tIdle during cycle {0} ({1}ns - {2}ns)".format(int(t_current/self.period),
t_current,
t_current+self.period)
elif op == "write":
comment = "\tWriting {0} to address {1} (from port {2}) during cylce {3} ({4}ns - {5}ns)".format(word,
addr,
port,
int(t_current/self.period),
t_current,
t_current+self.period)
else:
comment = "\tReading {0} from address {1} (from port {2}) during cylce {3} ({4}ns - {5}ns)".format(word,
addr,
port,
int(t_current/self.period),
t_current,
t_current+self.period)
return comment

View File

@ -232,8 +232,19 @@ class stimuli():
measure_string=".meas tran {0} AVG v({1}) FROM={2}n TO={3}n\n\n".format(meas_name, dout, t_intital, t_final)
self.sf.write(measure_string)
def write_control(self, end_time):
def write_control(self, end_time, runlvl=4):
""" Write the control cards to run and end the simulation """
# These are guesses...
if runlvl==1:
reltol = 0.02 # 2%
elif runlvl==2:
reltol = 0.01 # 1%
elif runlvl==3:
reltol = 0.005 # 0.5%
else:
reltol = 0.001 # 0.1%
# UIC is needed for ngspice to converge
self.sf.write(".TRAN 5p {0}n UIC\n".format(end_time))
if OPTS.spice_name == "ngspice":
@ -241,9 +252,9 @@ class stimuli():
# which is more accurate, but slower than the default trapezoid method
# Do not remove this or it may not converge due to some "pa_00" nodes
# unless you figure out what these are.
self.sf.write(".OPTIONS POST=1 RUNLVL=4 PROBE method=gear TEMP={}\n".format(self.temperature))
self.sf.write(".OPTIONS POST=1 RELTOL={0} PROBE method=gear TEMP={1}\n".format(reltol,self.temperature))
else:
self.sf.write(".OPTIONS POST=1 RUNLVL=4 PROBE TEMP={}\n".format(self.temperature))
self.sf.write(".OPTIONS POST=1 RUNLVL={0} PROBE TEMP={1}\n".format(runlvl,self.temperature))
# create plots for all signals
self.sf.write("* probe is used for hspice/xa, while plot is used in ngspice\n")

View File

@ -111,6 +111,7 @@ class trim_spice():
match of the line with a term so you can search for a single
net connection, the instance name, anything..
"""
removed_insts = 0
#Expects keep_inst_list are regex patterns. Compile them here.
compiled_patterns = [re.compile(pattern) for pattern in keep_inst_list]
@ -127,11 +128,14 @@ class trim_spice():
new_buffer.append(line)
in_subckt=False
elif in_subckt:
removed_insts += 1
for pattern in compiled_patterns:
if pattern.search(line) != None:
new_buffer.append(line)
removed_insts -= 1
break
else:
new_buffer.append(line)
self.sp_buffer = new_buffer
debug.info(2, "Removed {} instances from {} subcircuit.".format(removed_insts, subckt_name))

View File

@ -0,0 +1,77 @@
import sys,re,shutil
import debug
import tech
import math
from .stimuli import *
from .trim_spice import *
from .charutils import *
import utils
from globals import OPTS
from .delay import delay
class worst_case(delay):
"""Functions to test for the worst case delay in a target SRAM
The current worst case determines a feasible period for the SRAM then tests
several bits and record the delay and differences between the bits.
"""
def __init__(self, sram, spfile, corner):
delay.__init__(self,sram,spfile,corner)
def analyze(self,probe_address, probe_data, slews, loads):
"""
Main function to test the delays of different bits.
"""
debug.check(OPTS.num_rw_ports < 2 and OPTS.num_w_ports < 1 and OPTS.num_r_ports < 1 ,
"Bit testing does not currently support multiport.")
#Dict to hold all characterization values
char_sram_data = {}
self.set_probe(probe_address, probe_data)
#self.prepare_netlist()
self.load=max(loads)
self.slew=max(slews)
# 1) Find a feasible period and it's corresponding delays using the trimmed array.
feasible_delays = self.find_feasible_period()
# 2) Find the delays of several bits
test_bits = self.get_test_bits()
bit_delays = self.simulate_for_bit_delays(test_bits)
for i in range(len(test_bits)):
debug.info(1, "Bit tested: addr {0[0]} data_pos {0[1]}\n Values {1}".format(test_bits[i], bit_delays[i]))
def simulate_for_bit_delays(self, test_bits):
"""Simulates the delay of the sram of over several bits."""
bit_delays = [{} for i in range(len(test_bits))]
#Assumes a bitcell with only 1 rw port. (6t, port 0)
port = 0
self.targ_read_ports = [self.read_ports[port]]
self.targ_write_ports = [self.write_ports[port]]
for i in range(len(test_bits)):
(bit_addr, bit_data) = test_bits[i]
self.set_probe(bit_addr, bit_data)
debug.info(1,"Delay bit test: period {}, addr {}, data_pos {}".format(self.period, bit_addr, bit_data))
(success, results)=self.run_delay_simulation()
debug.check(success, "Bit Test Failed: period {}, addr {}, data_pos {}".format(self.period, bit_addr, bit_data))
bit_delays[i] = results[port]
return bit_delays
def get_test_bits(self):
"""Statically determines address and bit values to test"""
#First and last address, first middle, and last bit. Last bit is repeated twice with different data position.
bit_addrs = ["0"*self.addr_size, "0"+"1"*(self.addr_size-1), "1"*self.addr_size, "1"*self.addr_size]
data_positions = [0, (self.word_size-1)//2, 0, self.word_size-1]
#Return them in a tuple form
return [(bit_addrs[i], data_positions[i]) for i in range(len(bit_addrs))]

View File

@ -0,0 +1,40 @@
import debug
from drc_value import *
from drc_lut import *
class design_rules():
"""
This is a class that implements the design rules structures.
"""
def __init__(self, name):
self.tech_name = name
self.rules = {}
def add(self, name, value):
self.rules[name] = value
def __call__(self, name, *args):
rule = self.rules[name]
if callable(rule):
return rule(*args)
else:
return rule
def __setitem__(self, b, c):
"""
For backward compatibility with existing rules.
"""
self.rules[b] = c
def __getitem__(self, b):
"""
For backward compatibility with existing rules.
"""
rule = self.rules[b]
if not callable(rule):
return rule
else:
debug.error("Must call complex DRC rule {} with arguments.".format(b),-1)

43
compiler/drc/drc_lut.py Normal file
View File

@ -0,0 +1,43 @@
import debug
class drc_lut():
"""
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.
For exampe, the key values can be width and length,
and it would return the rule for a wire of at least a given width and length.
A dimension can be ignored by passing inf.
"""
def __init__(self, table):
self.table = table
def __call__(self, *key):
"""
Lookup a given tuple in the table.
"""
if len(key)==0:
first_key = list(sorted(self.table.keys()))[0]
return self.table[first_key]
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):
"""
Determine if key1>=key2 for all tuple pairs.
(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):
if k1 < k2:
return False
return True

17
compiler/drc/drc_value.py Normal file
View File

@ -0,0 +1,17 @@
class drc_value():
"""
A single DRC value.
"""
def __init__(self, value):
self.value = value
def __call__(self, *args):
"""
Return the value.
"""
return self.value

View File

@ -1,6 +1,5 @@
word_size = 2
num_words = 16
num_banks = 1
tech_name = "freepdk45"
process_corners = ["TT"]

View File

@ -1,6 +1,5 @@
word_size = 2
num_words = 16
num_banks = 1
tech_name = "scn4m_subm"
process_corners = ["TT"]
@ -9,3 +8,11 @@ temperatures = [ 25 ]
output_path = "temp"
output_name = "sram_{0}_{1}_{2}".format(word_size,num_words,tech_name)
#Setting for multiport
netlist_only = True
bitcell = "pbitcell"
replica_bitcell="replica_pbitcell"
num_rw_ports = 1
num_r_ports = 1
num_w_ports = 1

View File

@ -155,10 +155,10 @@ class VlsiLayout:
def traverseTheHierarchy(self, startingStructureName=None, delegateFunction = None,
transformPath = [], rotateAngle = 0, transFlags = [0,0,0], coordinates = (0,0)):
#since this is a recursive function, must deal with the default
#parameters explicitly
#parameters explicitly
if startingStructureName == None:
startingStructureName = self.rootStructureName
#set up the rotation matrix
if(rotateAngle == None or rotateAngle == ""):
angle = 0
@ -619,7 +619,8 @@ class VlsiLayout:
def updateBoundary(self,thisBoundary,cellBoundary):
[left_bott_X,left_bott_Y,right_top_X,right_top_Y]=thisBoundary
if cellBoundary==[None,None,None,None]:
# If any are None
if not (cellBoundary[0] and cellBoundary[1] and cellBoundary[2] and cellBoundary[3]):
cellBoundary=thisBoundary
else:
if cellBoundary[0]>left_bott_X:
@ -713,9 +714,9 @@ class VlsiLayout:
# Convert to user units
new_boundaries = []
for pin_boundary in pin_boundaries:
new_boundaries.append([pin_boundary[0]*self.units[0],pin_boundary[1]*self.units[0],
pin_boundary[2]*self.units[0],pin_boundary[3]*self.units[0]])
new_pin_boundary = [pin_boundary[0]*self.units[0],pin_boundary[1]*self.units[0],
pin_boundary[2]*self.units[0],pin_boundary[3]*self.units[0]]
new_boundaries.append(["p"+str(coordinate)+"_"+str(layer), layer, new_pin_boundary])
return new_boundaries
def getPinShapeByLabel(self,label_name):

View File

@ -24,7 +24,7 @@ def parse_args():
global OPTS
option_list = {
optparse.make_option("-b", "--backannotated", action="store_true", dest="run_pex",
optparse.make_option("-b", "--backannotated", action="store_true", dest="use_pex",
help="Back annotate simulation"),
optparse.make_option("-o", "--output", dest="output_name",
help="Base output file name(s) prefix", metavar="FILE"),
@ -295,7 +295,8 @@ def setup_paths():
# Add all of the subdirs to the python path
# These subdirs are modules and don't need to be added: characterizer, verify
for subdir in ["gdsMill", "tests", "modules", "base", "pgates", "datasheet"]:
subdirlist = [ item for item in os.listdir(OPENRAM_HOME) if os.path.isdir(os.path.join(OPENRAM_HOME, item)) ]
for subdir in subdirlist:
full_path = "{0}/{1}".format(OPENRAM_HOME,subdir)
debug.check(os.path.isdir(full_path),
"$OPENRAM_HOME/{0} does not exist: {1}".format(subdir,full_path))

View File

@ -107,8 +107,7 @@ class bank(design.design):
if self.num_banks > 1:
self.route_bank_select()
self.route_vdd_gnd()
self.route_supplies()
def create_modules(self):
""" Add modules. The order should not matter! """
@ -201,7 +200,7 @@ class bank(design.design):
self.central_bus_width = self.m2_pitch * self.num_control_lines + 2*self.m2_width
# A space for wells or jogging m2
self.m2_gap = max(2*drc["pwell_to_nwell"] + drc["well_enclosure_active"],
self.m2_gap = max(2*drc("pwell_to_nwell") + drc("well_enclosure_active"),
2*self.m2_pitch)
@ -527,10 +526,13 @@ class bank(design.design):
# FIXME: place for multiport
for port in range(self.total_ports):
col_decoder_inst = self.col_decoder_inst[port]
# Place the col decoder right aligned with row decoder
x_off = -(self.central_bus_width + self.wordline_driver.width + self.col_decoder.width)
y_off = -(self.col_decoder.height + 2*drc["well_to_well"])
self.col_decoder_inst[port].place(vector(x_off,y_off))
y_off = -(self.col_decoder.height + 2*drc("well_to_well"))
col_decoder_inst.place(vector(x_off,y_off))
def create_bank_select(self):
@ -565,47 +567,17 @@ class bank(design.design):
y_off = min(self.col_decoder_inst[port].by(), self.col_mux_array_inst[port].by())
else:
y_off = self.row_decoder_inst[port].by()
y_off -= (self.bank_select.height + drc["well_to_well"])
y_off -= (self.bank_select.height + drc("well_to_well"))
self.bank_select_pos = vector(x_off,y_off)
self.bank_select_inst[port].place(self.bank_select_pos)
def route_vdd_gnd(self):
def route_supplies(self):
""" Propagate all vdd/gnd pins up to this level for all modules """
for inst in self.insts:
self.copy_power_pins(inst,"vdd")
self.copy_power_pins(inst,"gnd")
# These are the instances that every bank has
top_instances = [self.bitcell_array_inst]
for port in range(self.total_read):
#top_instances.append(self.precharge_array_inst[port])
top_instances.append(self.sense_amp_array_inst[port])
for port in range(self.total_write):
top_instances.append(self.write_driver_array_inst[port])
for port in range(self.total_ports):
top_instances.extend([self.row_decoder_inst[port],
self.wordline_driver_inst[port]])
# Add these if we use the part...
if self.col_addr_size > 0:
top_instances.append(self.col_decoder_inst[port])
#top_instances.append(self.col_mux_array_inst[port])
if self.num_banks > 1:
top_instances.append(self.bank_select_inst[port])
if self.col_addr_size > 0:
for port in range(self.total_ports):
self.copy_layout_pin(self.col_mux_array_inst[port], "gnd")
for port in range(self.total_read):
self.copy_layout_pin(self.precharge_array_inst[port], "vdd")
for inst in top_instances:
# Column mux has no vdd
#if self.col_addr_size==0 or (self.col_addr_size>0 and inst != self.col_mux_array_inst[0]):
self.copy_layout_pin(inst, "vdd")
# Precharge has no gnd
#if inst != self.precharge_array_inst[port]:
self.copy_layout_pin(inst, "gnd")
def route_bank_select(self):
""" Route the bank select logic. """
@ -953,7 +925,7 @@ class bank(design.design):
rotate=90)
def analytical_delay(self, slew, load):
def analytical_delay(self, vdd, slew, load):
""" return analytical delay of the bank"""
decoder_delay = self.row_decoder.analytical_delay(slew, self.wordline_driver.input_load())
@ -961,10 +933,17 @@ class bank(design.design):
bitcell_array_delay = self.bitcell_array.analytical_delay(word_driver_delay.slew)
bl_t_data_out_delay = self.sense_amp_array.analytical_delay(bitcell_array_delay.slew,
if self.words_per_row > 1:
port = 0 #Analytical delay only supports single port
column_mux_delay = self.column_mux_array[port].analytical_delay(vdd, bitcell_array_delay.slew,
self.sense_amp_array.input_load())
else:
column_mux_delay = self.return_delay(delay = 0.0, slew=word_driver_delay.slew)
bl_t_data_out_delay = self.sense_amp_array.analytical_delay(column_mux_delay.slew,
self.bitcell_array.output_load())
# output load of bitcell_array is set to be only small part of bl for sense amp.
result = decoder_delay + word_driver_delay + bitcell_array_delay + bl_t_data_out_delay
result = decoder_delay + word_driver_delay + bitcell_array_delay + column_mux_delay + bl_t_data_out_delay
return result

View File

@ -67,7 +67,7 @@ class bank_select(design.design):
self.mod_bitcell = getattr(c, OPTS.bitcell)
self.bitcell = self.mod_bitcell()
height = self.bitcell.height + drc["poly_to_active"]
height = self.bitcell.height + drc("poly_to_active")
# 1x Inverter
self.inv_sel = pinv(height=height)
@ -88,8 +88,8 @@ 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_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_inv = max(self.xoffset_nand + self.nand2.width, self.xoffset_nor + self.nor2.width)
self.xoffset_bank_sel_inv = 0
self.xoffset_inputs = 0

View File

@ -39,7 +39,7 @@ class bitcell_array(design.design):
def create_layout(self):
# We increase it by a well enclosure so the precharges don't overlap our wells
self.height = self.row_size*self.cell.height + drc["well_enclosure_active"] + self.m1_width
self.height = self.row_size*self.cell.height + drc("well_enclosure_active") + self.m1_width
self.width = self.column_size*self.cell.width + self.m1_width
xoffset = 0.0
@ -199,13 +199,13 @@ class bitcell_array(design.design):
return total_power
def gen_wl_wire(self):
wl_wire = self.generate_rc_net(int(self.column_size), self.width, drc["minwidth_metal1"])
wl_wire = self.generate_rc_net(int(self.column_size), self.width, drc("minwidth_metal1"))
wl_wire.wire_c = 2*spice["min_tx_gate_c"] + wl_wire.wire_c # 2 access tx gate per cell
return wl_wire
def gen_bl_wire(self):
bl_pos = 0
bl_wire = self.generate_rc_net(int(self.row_size-bl_pos), self.height, drc["minwidth_metal1"])
bl_wire = self.generate_rc_net(int(self.row_size-bl_pos), self.height, drc("minwidth_metal1"))
bl_wire.wire_c =spice["min_tx_drain_c"] + bl_wire.wire_c # 1 access tx d/s per cell
return bl_wire

View File

@ -136,11 +136,12 @@ class dff_array(design.design):
# Create vertical spines to a single horizontal rail
clk_pin = self.dff_insts[0,0].get_pin("clk")
clk_ypos = 2*self.m3_pitch+self.m3_width
debug.check(clk_pin.layer=="metal2","DFF clk pin not on metal2")
self.add_layout_pin_segment_center(text="clk",
layer="metal3",
start=vector(0,self.m3_pitch+self.m3_width),
end=vector(self.width,self.m3_pitch+self.m3_width))
start=vector(0,clk_ypos),
end=vector(self.width,clk_ypos))
for col in range(self.columns):
clk_pin = self.dff_insts[0,col].get_pin("clk")
# Make a vertical strip for each column
@ -150,7 +151,7 @@ class dff_array(design.design):
height=self.height)
# Drop a via to the M3 pin
self.add_via_center(layers=("metal2","via2","metal3"),
offset=vector(clk_pin.cx(),self.m3_pitch+self.m3_width))
offset=vector(clk_pin.cx(),clk_ypos))

View File

@ -153,6 +153,7 @@ class dff_buf_array(design.design):
# Create vertical spines to a single horizontal rail
clk_pin = self.dff_insts[0,0].get_pin("clk")
clk_ypos = 2*self.m3_pitch+self.m3_width
debug.check(clk_pin.layer=="metal2","DFF clk pin not on metal2")
if self.columns==1:
self.add_layout_pin(text="clk",
@ -163,8 +164,8 @@ class dff_buf_array(design.design):
else:
self.add_layout_pin_segment_center(text="clk",
layer="metal3",
start=vector(0,self.m3_pitch+self.m3_width),
end=vector(self.width,self.m3_pitch+self.m3_width))
start=vector(0,clk_ypos),
end=vector(self.width,clk_ypos))
for col in range(self.columns):
clk_pin = self.dff_insts[0,col].get_pin("clk")
@ -175,7 +176,7 @@ class dff_buf_array(design.design):
height=self.height)
# Drop a via to the M3 pin
self.add_via_center(layers=("metal2","via2","metal3"),
offset=vector(clk_pin.cx(),self.m3_pitch+self.m3_width))
offset=vector(clk_pin.cx(),clk_ypos))

View File

@ -153,6 +153,7 @@ class dff_inv_array(design.design):
# Create vertical spines to a single horizontal rail
clk_pin = self.dff_insts[0,0].get_pin("clk")
clk_ypos = 2*self.m3_pitch+self.m3_width
debug.check(clk_pin.layer=="metal2","DFF clk pin not on metal2")
if self.columns==1:
self.add_layout_pin(text="clk",
@ -163,8 +164,8 @@ class dff_inv_array(design.design):
else:
self.add_layout_pin_segment_center(text="clk",
layer="metal3",
start=vector(0,self.m3_pitch+self.m3_width),
end=vector(self.width,self.m3_pitch+self.m3_width))
start=vector(0,clk_ypos),
end=vector(self.width,clk_ypos))
for col in range(self.columns):
clk_pin = self.dff_insts[0,col].get_pin("clk")
# Make a vertical strip for each column
@ -174,7 +175,7 @@ class dff_inv_array(design.design):
height=self.height)
# Drop a via to the M3 pin
self.add_via_center(layers=("metal2","via2","metal3"),
offset=vector(clk_pin.cx(),self.m3_pitch+self.m3_width))
offset=vector(clk_pin.cx(),clk_ypos))

View File

@ -548,30 +548,27 @@ class hierarchical_decoder(design.design):
def route_vdd_gnd(self):
""" Add a pin for each row of vdd/gnd which are must-connects next level up. """
# Find the x offsets for where the vias/pins should be placed
a_xoffset = self.inv_inst[0].lx()
b_xoffset = self.inv_inst[0].rx()
# The vias will be placed in the center and right of the cells, respectively.
xoffset = self.nand_inst[0].cx()
for num in range(0,self.rows):
# this will result in duplicate polygons for rails, but who cares
# Route both supplies
for n in ["vdd", "gnd"]:
supply_pin = self.inv_inst[num].get_pin(n)
# Add pins in two locations
for xoffset in [a_xoffset, b_xoffset]:
pin_pos = vector(xoffset, supply_pin.cy())
self.add_via_center(layers=("metal1", "via1", "metal2"),
offset=pin_pos,
rotate=90)
self.add_via_center(layers=("metal2", "via2", "metal3"),
offset=pin_pos,
rotate=90)
self.add_layout_pin_rect_center(text=n,
layer="metal3",
offset=pin_pos)
for pin_name in ["vdd", "gnd"]:
# The nand and inv are the same height rows...
supply_pin = self.nand_inst[num].get_pin(pin_name)
pin_pos = vector(xoffset, supply_pin.cy())
self.add_power_pin(name=pin_name,
loc=pin_pos)
# Make a redundant rail too
for num in range(0,self.rows,2):
for pin_name in ["vdd", "gnd"]:
start = self.nand_inst[num].get_pin(pin_name).lc()
end = self.inv_inst[num].get_pin(pin_name).rc()
mid = (start+end).scale(0.5,0.5)
self.add_rect_center(layer="metal1",
offset=mid,
width=end.x-start.x)
# Copy the pins from the predecoders
for pre in self.pre2x4_inst + self.pre3x8_inst:
self.copy_layout_pin(pre, "vdd")

View File

@ -282,15 +282,7 @@ class hierarchical_predecode(design.design):
# Add pins in two locations
for xoffset in [in_xoffset, out_xoffset]:
pin_pos = vector(xoffset, nand_pin.cy())
self.add_via_center(layers=("metal1", "via1", "metal2"),
offset=pin_pos,
rotate=90)
self.add_via_center(layers=("metal2", "via2", "metal3"),
offset=pin_pos,
rotate=90)
self.add_layout_pin_rect_center(text=n,
layer="metal3",
offset=pin_pos)
self.add_power_pin(n, pin_pos)

View File

@ -109,7 +109,7 @@ class multibank(design.design):
if self.num_banks > 1:
self.route_bank_select()
self.route_vdd_gnd()
self.route_supplies()
def add_modules(self):
""" Add modules. The order should not matter! """
@ -170,7 +170,7 @@ class multibank(design.design):
self.central_bus_width = self.m2_pitch * self.num_control_lines + 2*self.m2_width
# A space for wells or jogging m2
self.m2_gap = max(2*drc["pwell_to_nwell"] + drc["well_enclosure_active"],
self.m2_gap = max(2*drc("pwell_to_nwell"] + drc["well_enclosure_active"),
2*self.m2_pitch)
@ -382,7 +382,7 @@ class multibank(design.design):
"""
# Place the col decoder right aligned with row decoder
x_off = -(self.central_bus_width + self.wordline_driver.width + self.col_decoder.width)
y_off = -(self.col_decoder.height + 2*drc["well_to_well"])
y_off = -(self.col_decoder.height + 2*drc("well_to_well"))
self.col_decoder_inst=self.add_inst(name="col_address_decoder",
mod=self.col_decoder,
offset=vector(x_off,y_off))
@ -427,7 +427,7 @@ class multibank(design.design):
y_off = min(self.col_decoder_inst.by(), self.col_mux_array_inst.by())
else:
y_off = self.row_decoder_inst.by()
y_off -= (self.bank_select.height + drc["well_to_well"])
y_off -= (self.bank_select.height + drc("well_to_well"))
self.bank_select_pos = vector(x_off,y_off)
self.bank_select_inst = self.add_inst(name="bank_select",
mod=self.bank_select,
@ -440,33 +440,11 @@ class multibank(design.design):
temp.extend(["vdd", "gnd"])
self.connect_inst(temp)
def route_vdd_gnd(self):
def route_supplies(self):
""" Propagate all vdd/gnd pins up to this level for all modules """
# These are the instances that every bank has
top_instances = [self.bitcell_array_inst,
self.precharge_array_inst,
self.sense_amp_array_inst,
self.write_driver_array_inst,
# self.tri_gate_array_inst,
self.row_decoder_inst,
self.wordline_driver_inst]
# Add these if we use the part...
if self.col_addr_size > 0:
top_instances.append(self.col_decoder_inst)
top_instances.append(self.col_mux_array_inst)
if self.num_banks > 1:
top_instances.append(self.bank_select_inst)
for inst in top_instances:
# Column mux has no vdd
if self.col_addr_size==0 or (self.col_addr_size>0 and inst != self.col_mux_array_inst):
self.copy_layout_pin(inst, "vdd")
# Precharge has no gnd
if inst != self.precharge_array_inst:
self.copy_layout_pin(inst, "gnd")
for inst in self.insts:
self.copy_power_pins(inst,"vdd")
self.copy_power_pins(inst,"gnd")
def route_bank_select(self):
""" Route the bank select logic. """

View File

@ -63,7 +63,7 @@ class precharge_array(design.design):
layer="metal1",
offset=self.pc_cell.get_pin("en").ll(),
width=self.width,
height=drc["minwidth_metal1"])
height=drc("minwidth_metal1"))
for inst in self.local_insts:
self.copy_layout_pin(inst, "vdd")
@ -74,13 +74,13 @@ class precharge_array(design.design):
self.add_layout_pin(text="bl_{0}".format(i),
layer="metal2",
offset=bl_pin.ll(),
width=drc["minwidth_metal2"],
width=drc("minwidth_metal2"),
height=bl_pin.height())
br_pin = inst.get_pin("br")
self.add_layout_pin(text="br_{0}".format(i),
layer="metal2",
offset=br_pin.ll(),
width=drc["minwidth_metal2"],
width=drc("minwidth_metal2"),
height=bl_pin.height())

View File

@ -196,7 +196,7 @@ class replica_bitline(design.design):
wl_last = self.wl_list[self.total_ports-1]+"_{}".format(row)
pin_last = self.rbl_inst.get_pin(wl_last)
correct = vector(0.5*drc["minwidth_metal1"], 0)
correct = vector(0.5*drc("minwidth_metal1"), 0)
self.add_path("metal1", [pin.rc()-correct, pin_last.rc()-correct])
def route_supplies(self):
@ -255,7 +255,7 @@ class replica_bitline(design.design):
# 3. Route the contact of previous route to the bitcell WL
# route bend of previous net to bitcell WL
wl_offset = self.rbc_inst.get_pin(self.wl_list[0]).lc()
wl_mid1 = wl_offset - vector(1.5*drc["minwidth_metal1"], 0)
wl_mid1 = wl_offset - vector(1.5*drc("minwidth_metal1"), 0)
wl_mid2 = vector(wl_mid1.x, contact_offset.y)
#xmid_point= 0.5*(wl_offset.x+contact_offset.x)
#wl_mid1 = vector(xmid_point,contact_offset.y)
@ -268,7 +268,7 @@ class replica_bitline(design.design):
pin = self.rbc_inst.get_pin(wl)
pin_last = self.rbc_inst.get_pin(wl_last)
correct = vector(0.5*drc["minwidth_metal1"], 0)
correct = vector(0.5*drc("minwidth_metal1"), 0)
self.add_path("metal1", [pin.lc()+correct, pin_last.lc()+correct])
# DRAIN ROUTE

View File

@ -23,6 +23,11 @@ class sense_amp(design.design):
self.height = sense_amp.height
self.pin_map = sense_amp.pin_map
def input_load(self):
#Input load for the bitlines which are connected to the source/drain of a TX. Not the selects.
bitline_pmos_size = 8 #FIXME: This should be set somewhere and referenced. Probably in tech file.
return spice["min_tx_drain_c"]*(bitline_pmos_size/parameter["min_tx_size"])#ff
def analytical_delay(self, slew, load=0.0):
from tech import spice
r = spice["min_tx_r"]/(10)

View File

@ -132,8 +132,11 @@ class sense_amp_array(design.design):
layer="metal1",
offset=sclk_offset,
width=self.width,
height=drc["minwidth_metal1"])
height=drc("minwidth_metal1"))
def input_load(self):
return self.amp.input_load()
def analytical_delay(self, slew, load=0.0):
return self.amp.analytical_delay(slew=slew, load=load)

View File

@ -170,23 +170,23 @@ class single_level_column_mux_array(design.design):
self.add_rect(layer="metal1",
offset=bl_out_offset,
width=width,
height=drc["minwidth_metal2"])
height=drc("minwidth_metal2"))
self.add_rect(layer="metal1",
offset=br_out_offset,
width=width,
height=drc["minwidth_metal2"])
height=drc("minwidth_metal2"))
# Extend the bitline output rails and gnd downward on the first bit of each n-way mux
self.add_layout_pin(text="bl_out_{}".format(int(j/self.words_per_row)),
layer="metal2",
offset=bl_out_offset.scale(1,0),
width=drc['minwidth_metal2'],
width=drc('minwidth_metal2'),
height=self.route_height)
self.add_layout_pin(text="br_out_{}".format(int(j/self.words_per_row)),
layer="metal2",
offset=br_out_offset.scale(1,0),
width=drc['minwidth_metal2'],
width=drc('minwidth_metal2'),
height=self.route_height)
# This via is on the right of the wire
@ -202,7 +202,7 @@ class single_level_column_mux_array(design.design):
self.add_rect(layer="metal2",
offset=bl_out_offset,
width=drc['minwidth_metal2'],
width=drc('minwidth_metal2'),
height=self.route_height-bl_out_offset.y)
# This via is on the right of the wire
self.add_via(layers=("metal1", "via1", "metal2"),
@ -210,12 +210,20 @@ class single_level_column_mux_array(design.design):
rotate=90)
self.add_rect(layer="metal2",
offset=br_out_offset,
width=drc['minwidth_metal2'],
width=drc('minwidth_metal2'),
height=self.route_height-br_out_offset.y)
# This via is on the left of the wire
self.add_via(layers=("metal1", "via1", "metal2"),
offset= br_out_offset,
rotate=90)
def analytical_delay(self, vdd, slew, load=0.0):
from tech import spice
r = spice["min_tx_r"]/(self.mux.ptx_width/parameter["min_tx_size"])
#Drains of mux transistors make up capacitance.
c_para = spice["min_tx_drain_c"]*(self.mux.ptx_width/parameter["min_tx_size"])*self.words_per_row#ff
volt_swing = spice["v_threshold_typical"]/vdd
result = self.cal_delay_with_rc(r = r, c = c_para+load, slew = slew, swing = volt_swing)
return self.return_delay(result.delay, result.slew)

View File

@ -107,14 +107,14 @@ class tri_gate_array(design.design):
layer="metal1",
offset=en_pin.ll().scale(0, 1),
width=width,
height=drc["minwidth_metal1"])
height=drc("minwidth_metal1"))
enbar_pin = self.tri_inst[0].get_pin("en_bar")
self.add_layout_pin(text="en_bar",
layer="metal1",
offset=enbar_pin.ll().scale(0, 1),
width=width,
height=drc["minwidth_metal1"])
height=drc("minwidth_metal1"))

View File

@ -130,7 +130,7 @@ class write_driver_array(design.design):
layer="metal1",
offset=self.driver_insts[0].get_pin("en").ll().scale(0,1),
width=self.width,
height=drc['minwidth_metal1'])
height=drc('minwidth_metal1'))

View File

@ -7,7 +7,6 @@ a spice (.sp) file for circuit simulation
a GDS2 (.gds) file containing the layout
a LEF (.lef) file for preliminary P&R (real one should be from layout)
a Liberty (.lib) file for timing analysis/optimization
"""
import sys,os

View File

@ -1,7 +1,7 @@
import contact
import design
import debug
from tech import drc, parameter, spice, info
from tech import drc, parameter, spice
from ptx import ptx
from vector import vector
from globals import OPTS
@ -110,7 +110,7 @@ class pgate(design.design):
max_y_offset = self.height + 0.5*self.m1_width
self.nwell_position = middle_position
nwell_height = max_y_offset - middle_position.y
if info["has_nwell"]:
if drc("has_nwell"):
self.add_rect(layer="nwell",
offset=middle_position,
width=self.well_width,
@ -122,7 +122,7 @@ class pgate(design.design):
pwell_position = vector(0,-0.5*self.m1_width)
pwell_height = middle_position.y-pwell_position.y
if info["has_pwell"]:
if drc("has_pwell"):
self.add_rect(layer="pwell",
offset=pwell_position,
width=self.well_width,
@ -138,7 +138,7 @@ class pgate(design.design):
layer_stack = ("active", "contact", "metal1")
# To the right a spacing away from the pmos right active edge
contact_xoffset = pmos_pos.x + pmos.active_width + drc["active_to_body_active"]
contact_xoffset = pmos_pos.x + pmos.active_width + drc("active_to_body_active")
# Must be at least an well enclosure of active down from the top of the well
# OR align the active with the top of PMOS active.
max_y_offset = self.height + 0.5*self.m1_width
@ -185,7 +185,7 @@ class pgate(design.design):
pwell_position = vector(0,-0.5*self.m1_width)
# To the right a spacing away from the nmos right active edge
contact_xoffset = nmos_pos.x + nmos.active_width + drc["active_to_body_active"]
contact_xoffset = nmos_pos.x + nmos.active_width + drc("active_to_body_active")
# Must be at least an well enclosure of active up from the bottom of the well
contact_yoffset = max(nmos_pos.y,
self.well_enclose_active - nmos.active_contact.first_layer_height/2)

View File

@ -1,7 +1,7 @@
import contact
import pgate
import debug
from tech import drc, parameter, spice, info
from tech import drc, parameter, spice
from ptx import ptx
from vector import vector
from math import ceil
@ -76,8 +76,8 @@ class pinv(pgate.pgate):
# This may make the result differ when the layout is created...
if OPTS.netlist_only:
self.tx_mults = 1
self.nmos_width = self.nmos_size*drc["minwidth_tx"]
self.pmos_width = self.pmos_size*drc["minwidth_tx"]
self.nmos_width = self.nmos_size*drc("minwidth_tx")
self.pmos_width = self.pmos_size*drc("minwidth_tx")
return
# Do a quick sanity check and bail if unlikely feasible height
@ -85,16 +85,16 @@ class pinv(pgate.pgate):
# Assume we need 3 metal 1 pitches (2 power rails, one between the tx for the drain)
# plus the tx height
nmos = ptx(tx_type="nmos")
pmos = ptx(width=drc["minwidth_tx"], tx_type="pmos")
pmos = ptx(width=drc("minwidth_tx"), tx_type="pmos")
tx_height = nmos.poly_height + pmos.poly_height
# rotated m1 pitch or poly to active spacing
min_channel = max(contact.poly.width + self.m1_space,
contact.poly.width + 2*drc["poly_to_active"])
contact.poly.width + 2*drc("poly_to_active"))
# This is the extra space needed to ensure DRC rules to the active contacts
extra_contact_space = max(-nmos.get_pin("D").by(),0)
# 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,
drc["poly_extend_active"], self.poly_space)
drc("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))
@ -103,16 +103,16 @@ class pinv(pgate.pgate):
# Divide the height in half. Could divide proportional to beta, but this makes
# connecting wells of multiple cells easier.
# Subtract the poly space under the rail of the tx
nmos_height_available = 0.5 * tx_height_available - 0.5*drc["poly_to_poly"]
pmos_height_available = 0.5 * tx_height_available - 0.5*drc["poly_to_poly"]
nmos_height_available = 0.5 * tx_height_available - 0.5*drc("poly_to_poly")
pmos_height_available = 0.5 * tx_height_available - 0.5*drc("poly_to_poly")
debug.info(2,"Height avail {0:.4f} PMOS {1:.4f} NMOS {2:.4f}".format(tx_height_available,
nmos_height_available,
pmos_height_available))
# Determine the number of mults for each to fit width into available space
self.nmos_width = self.nmos_size*drc["minwidth_tx"]
self.pmos_width = self.pmos_size*drc["minwidth_tx"]
self.nmos_width = self.nmos_size*drc("minwidth_tx")
self.pmos_width = self.pmos_size*drc("minwidth_tx")
nmos_required_mults = max(int(ceil(self.nmos_width/nmos_height_available)),1)
pmos_required_mults = max(int(ceil(self.pmos_width/pmos_height_available)),1)
# The mults must be the same for easy connection of poly
@ -124,9 +124,9 @@ class pinv(pgate.pgate):
# We also need to round the width to the grid or we will end up 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.")
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.pmos_width>=drc("minwidth_tx"),"Cannot finger PMOS transistors to fit cell height.")
def setup_layout_constants(self):
@ -137,7 +137,7 @@ class pinv(pgate.pgate):
# the well width is determined the multi-finger PMOS device width plus
# the well contact width and half well enclosure on both sides
self.well_width = self.pmos.active_width + self.pmos.active_contact.width \
+ drc["active_to_body_active"] + 2*drc["well_enclosure_active"]
+ drc("active_to_body_active") + 2*drc("well_enclosure_active")
self.width = self.well_width
# Height is an input parameter, so it is not recomputed.

View File

@ -94,16 +94,16 @@ class pinvbuf(design.design):
self.connect_inst(["zb_int", "Z", "vdd", "gnd"])
def place_modules(self):
# Add INV1 to the right (capacitance shield)
# Add INV1 to the left (capacitance shield)
self.inv1_inst.place(vector(0,0))
# Add INV2 to the right
# Add INV2 to the right of INV1
self.inv2_inst.place(vector(self.inv1_inst.rx(),0))
# Add INV3 to the right
# Add INV3 to the right of INV2
self.inv3_inst.place(vector(self.inv2_inst.rx(),0))
# Add INV4 to the bottom
# Add INV4 flipped to the bottom aligned with INV2
self.inv4_inst.place(offset=vector(self.inv2_inst.rx(),2*self.inv2.height),
mirror = "MX")

View File

@ -23,8 +23,8 @@ class pnand2(pgate.pgate):
self.nmos_size = 2*size
self.pmos_size = parameter["beta"]*size
self.nmos_width = self.nmos_size*drc["minwidth_tx"]
self.pmos_width = self.pmos_size*drc["minwidth_tx"]
self.nmos_width = self.nmos_size*drc("minwidth_tx")
self.pmos_width = self.pmos_size*drc("minwidth_tx")
# FIXME: Allow these to be sized
debug.check(size==1,"Size 1 pnand2 is only supported now.")
@ -91,7 +91,7 @@ class pnand2(pgate.pgate):
# Two PMOS devices and a well contact. Separation between each.
# Enclosure space on the sides.
self.well_width = 2*self.pmos.active_width + contact.active.width \
+ 2*drc["active_to_body_active"] + 2*drc["well_enclosure_active"]
+ 2*drc("active_to_body_active") + 2*drc("well_enclosure_active")
self.width = self.well_width
# Height is an input parameter, so it is not recomputed.
@ -100,7 +100,7 @@ class pnand2(pgate.pgate):
extra_contact_space = max(-self.nmos.get_pin("D").by(),0)
# 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,
drc["poly_extend_active"], self.poly_space)
drc("poly_extend_active"), self.poly_space)
def route_supply_rails(self):
""" Add vdd/gnd rails to the top and bottom. """

View File

@ -25,8 +25,8 @@ class pnand3(pgate.pgate):
# If we relax this, we could size this better.
self.nmos_size = 2*size
self.pmos_size = parameter["beta"]*size
self.nmos_width = self.nmos_size*drc["minwidth_tx"]
self.pmos_width = self.pmos_size*drc["minwidth_tx"]
self.nmos_width = self.nmos_size*drc("minwidth_tx")
self.pmos_width = self.pmos_size*drc("minwidth_tx")
# FIXME: Allow these to be sized
debug.check(size==1,"Size 1 pnand3 is only supported now.")
@ -83,7 +83,7 @@ class pnand3(pgate.pgate):
# Two PMOS devices and a well contact. Separation between each.
# Enclosure space on the sides.
self.well_width = 3*self.pmos.active_width + self.pmos.active_contact.width \
+ 2*drc["active_to_body_active"] + 2*drc["well_enclosure_active"] \
+ 2*drc("active_to_body_active") + 2*drc("well_enclosure_active") \
- self.overlap_offset.x
self.width = self.well_width
# Height is an input parameter, so it is not recomputed.
@ -96,7 +96,7 @@ class pnand3(pgate.pgate):
extra_contact_space = max(-nmos.get_pin("D").by(),0)
# 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,
drc["poly_extend_active"], self.poly_space)
drc("poly_extend_active"), self.poly_space)
def route_supply_rails(self):
""" Add vdd/gnd rails to the top and bottom. """
@ -191,7 +191,7 @@ class pnand3(pgate.pgate):
metal_spacing = max(self.m1_space + self.m1_width, self.m2_space + self.m2_width,
self.m1_space + 0.5*contact.poly.width + 0.5*self.m1_width)
active_spacing = max(self.m1_space, 0.5*contact.poly.first_layer_width + drc["poly_to_active"])
active_spacing = max(self.m1_space, 0.5*contact.poly.first_layer_width + drc("poly_to_active"))
inputC_yoffset = self.nmos3_pos.y + self.nmos.active_height + active_spacing
self.route_input_gate(self.pmos3_inst, self.nmos3_inst, inputC_yoffset, "C", position="center")

View File

@ -24,8 +24,8 @@ class pnor2(pgate.pgate):
self.nmos_size = size
# We will just make this 1.5 times for now. NORs are not ideal anyhow.
self.pmos_size = 1.5*parameter["beta"]*size
self.nmos_width = self.nmos_size*drc["minwidth_tx"]
self.pmos_width = self.pmos_size*drc["minwidth_tx"]
self.nmos_width = self.nmos_size*drc("minwidth_tx")
self.pmos_width = self.pmos_size*drc("minwidth_tx")
# FIXME: Allow these to be sized
debug.check(size==1,"Size 1 pnor2 is only supported now.")
@ -92,7 +92,7 @@ class pnor2(pgate.pgate):
# Two PMOS devices and a well contact. Separation between each.
# Enclosure space on the sides.
self.well_width = 2*self.pmos.active_width + self.pmos.active_contact.width \
+ 2*drc["active_to_body_active"] + 2*drc["well_enclosure_active"]
+ 2*drc("active_to_body_active") + 2*drc("well_enclosure_active")
self.width = self.well_width
# Height is an input parameter, so it is not recomputed.
@ -101,7 +101,7 @@ class pnor2(pgate.pgate):
extra_contact_space = max(-self.nmos.get_pin("D").by(),0)
# 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,
drc["poly_extend_active"], self.poly_space)
drc("poly_extend_active"), self.poly_space)
def add_supply_rails(self):
""" Add vdd/gnd rails to the top and bottom. """

View File

@ -163,7 +163,7 @@ class precharge(pgate.pgate):
"""Adds a nwell tap to connect to the vdd rail"""
# adds the contact from active to metal1
well_contact_pos = self.upper_pmos1_inst.get_pin("D").center().scale(1,0) \
+ vector(0, self.upper_pmos1_inst.uy() + contact.well.height/2 + drc["well_extend_active"])
+ vector(0, self.upper_pmos1_inst.uy() + contact.well.height/2 + drc("well_extend_active"))
self.add_contact_center(layers=("active", "contact", "metal1"),
offset=well_contact_pos,
implant_type="n",
@ -185,7 +185,7 @@ class precharge(pgate.pgate):
self.add_layout_pin(text="bl",
layer="metal2",
offset=offset,
width=drc['minwidth_metal2'],
width=drc("minwidth_metal2"),
height=self.height)
# adds the BR on metal 2
@ -193,7 +193,7 @@ class precharge(pgate.pgate):
self.add_layout_pin(text="br",
layer="metal2",
offset=offset,
width=drc['minwidth_metal2'],
width=drc("minwidth_metal2"),
height=self.height)
def connect_to_bitlines(self):

View File

@ -1,6 +1,6 @@
import design
import debug
from tech import drc, info, spice
from tech import drc, spice
from vector import vector
from contact import contact
from globals import OPTS
@ -15,7 +15,7 @@ class ptx(design.design):
you to connect the fingered gates and active for parallel devices.
"""
def __init__(self, width=drc["minwidth_tx"], mults=1, tx_type="nmos", connect_active=False, connect_poly=False, num_contacts=None):
def __init__(self, width=drc("minwidth_tx"), mults=1, tx_type="nmos", connect_active=False, connect_poly=False, num_contacts=None):
# We need to keep unique names because outputting to GDSII
# will use the last record with a given name. I.e., you will
# over-write a design in GDS if one has and the other doesn't
@ -66,12 +66,12 @@ class ptx(design.design):
# self.spice.append("\n.SUBCKT {0} {1}".format(self.name,
# " ".join(self.pins)))
# Just make a guess since these will actually be decided in the layout later.
area_sd = 2.5*drc["minwidth_poly"]*self.tx_width
perimeter_sd = 2*drc["minwidth_poly"] + 2*self.tx_width
area_sd = 2.5*drc("minwidth_poly")*self.tx_width
perimeter_sd = 2*drc("minwidth_poly") + 2*self.tx_width
self.spice_device="M{{0}} {{1}} {0} m={1} w={2}u l={3}u pd={4}u ps={4}u as={5}p ad={5}p".format(spice[self.tx_type],
self.mults,
self.tx_width,
drc["minwidth_poly"],
drc("minwidth_poly"),
perimeter_sd,
area_sd)
self.spice.append("\n* ptx " + self.spice_device)
@ -109,7 +109,7 @@ class ptx(design.design):
self.contact_pitch = 2*self.contact_to_gate + self.contact_width + self.poly_width
# The enclosure of an active contact. Not sure about second term.
active_enclose_contact = max(drc["active_enclosure_contact"],
active_enclose_contact = max(drc("active_enclosure_contact"),
(self.active_width - self.contact_width)/2)
# This is the distance from the edge of poly to the contacted end of active
self.end_to_poly = active_enclose_contact + self.contact_width + self.contact_to_gate
@ -129,7 +129,7 @@ class ptx(design.design):
self.active_offset = vector([self.well_enclose_active]*2)
# Well enclosure of active, ensure minwidth as well
if info["has_{}well".format(self.well_type)]:
if drc("has_{}well".format(self.well_type)):
self.cell_well_width = max(self.active_width + 2*self.well_enclose_active,
self.well_width)
self.cell_well_height = max(self.tx_width + 2*self.well_enclose_active,
@ -151,9 +151,9 @@ class ptx(design.design):
# Min area results are just flagged for now.
debug.check(self.active_width*self.active_height>=drc["minarea_active"],"Minimum active area violated.")
debug.check(self.active_width*self.active_height>=drc("minarea_active"),"Minimum active area violated.")
# We do not want to increase the poly dimensions to fix an area problem as it would cause an LVS issue.
debug.check(self.poly_width*self.poly_height>=drc["minarea_poly"],"Minimum poly area violated.")
debug.check(self.poly_width*self.poly_height>=drc("minarea_poly"),"Minimum poly area violated.")
def connect_fingered_poly(self, poly_positions):
"""
@ -181,7 +181,7 @@ class ptx(design.design):
layer="poly",
offset=poly_offset,
width=poly_width,
height=drc["minwidth_poly"])
height=drc("minwidth_poly"))
def connect_fingered_active(self, drain_positions, source_positions):
@ -269,7 +269,7 @@ class ptx(design.design):
height=self.active_height)
# If the implant must enclose the active, shift offset
# and increase width/height
enclose_width = drc["implant_enclosure_active"]
enclose_width = drc("implant_enclosure_active")
enclose_offset = [enclose_width]*2
self.add_rect(layer="{}implant".format(self.implant_type),
offset=self.active_offset - enclose_offset,
@ -280,7 +280,7 @@ class ptx(design.design):
"""
Add an (optional) well and implant for the type of transistor.
"""
if info["has_{}well".format(self.well_type)]:
if drc("has_{}well".format(self.well_type)):
self.add_rect(layer="{}well".format(self.well_type),
offset=(0,0),
width=self.cell_well_width,

View File

@ -1,6 +1,6 @@
import design
import debug
from tech import drc, info
from tech import drc
from vector import vector
import contact
from ptx import ptx
@ -52,7 +52,7 @@ class single_level_column_mux(design.design):
self.bitcell = self.mod_bitcell()
# Adds nmos_lower,nmos_upper to the module
self.ptx_width = self.tx_size * drc["minwidth_tx"]
self.ptx_width = self.tx_size * drc("minwidth_tx")
self.nmos = ptx(width=self.ptx_width)
self.add_mod(self.nmos)

View File

@ -0,0 +1,9 @@
from enum import Enum
class direction(Enum):
NORTH = 1
SOUTH = 2
EAST = 3
WEST = 4
UP = 5
DOWN = 6

View File

@ -1,95 +1,138 @@
import numpy as np
import string
from itertools import tee
import debug
from vector3d import vector3d
from cell import cell
import os
from grid_cell import grid_cell
class grid:
"""
A two layer routing map. Each cell can be blocked in the vertical
or horizontal layer.
"""
# costs are relative to a unit grid
# non-preferred cost allows an off-direction jog of 1 grid
# rather than 2 vias + preferred direction (cost 5)
VIA_COST = 2
NONPREFERRED_COST = 4
PREFERRED_COST = 1
def __init__(self):
def __init__(self, ll, ur, track_width):
""" Initialize the map and define the costs. """
# costs are relative to a unit grid
# non-preferred cost allows an off-direction jog of 1 grid
# rather than 2 vias + preferred direction (cost 5)
self.VIA_COST = 2
self.NONPREFERRED_COST = 4
self.PREFERRED_COST = 1
# list of the source/target grid coordinates
self.source = []
self.target = []
self.track_width = track_width
self.track_widths = [self.track_width, self.track_width, 1.0]
self.track_factor = [1/self.track_width, 1/self.track_width, 1.0]
# The bounds are in grids for this
# This is really lower left bottom layer and upper right top layer in 3D.
self.ll = vector3d(ll.x,ll.y,0).scale(self.track_factor).round()
self.ur = vector3d(ur.x,ur.y,1).scale(self.track_factor).round()
# let's leave the map sparse, cells are created on demand to reduce memory
self.map={}
def set_blocked(self,n):
self.add_map(n)
self.map[n].blocked=True
def add_all_grids(self):
for x in range(self.ll.x, self.ur.x, 1):
for y in range(self.ll.y, self.ur.y, 1):
self.add_map(vector3d(x,y,0))
self.add_map(vector3d(x,y,1))
def set_blocked(self,n,value=True):
if isinstance(n, (list,tuple,set,frozenset)):
for item in n:
self.set_blocked(item,value)
else:
self.add_map(n)
self.map[n].blocked=value
def is_blocked(self,n):
self.add_map(n)
return self.map[n].blocked
if isinstance(n, (list,tuple,set,frozenset)):
for item in n:
if self.is_blocked(item):
return True
else:
return False
else:
self.add_map(n)
return self.map[n].blocked
def add_blockage_shape(self,ll,ur,z):
debug.info(3,"Adding blockage ll={0} ur={1} z={2}".format(str(ll),str(ur),z))
block_list = []
for x in range(int(ll[0]),int(ur[0])+1):
for y in range(int(ll[1]),int(ur[1])+1):
block_list.append(vector3d(x,y,z))
def set_path(self,n,value=True):
if isinstance(n, (list,tuple,set,frozenset)):
for item in n:
self.set_path(item,value)
else:
self.add_map(n)
self.map[n].path=value
self.add_blockage(block_list)
def clear_blockages(self):
self.set_blocked(set(self.map.keys()),False)
def set_source(self,n,value=True):
if isinstance(n, (list,tuple,set,frozenset)):
for item in n:
self.set_source(item,value)
else:
self.add_map(n)
self.map[n].source=value
self.source.append(n)
def set_target(self,n,value=True):
if isinstance(n, (list,tuple,set,frozenset)):
for item in n:
self.set_target(item,value)
else:
self.add_map(n)
self.map[n].target=value
self.target.append(n)
def add_blockage(self,block_list):
debug.info(2,"Adding blockage list={0}".format(str(block_list)))
for n in block_list:
self.set_blocked(n)
def add_source(self,track_list,value=True):
debug.info(3,"Adding source list={0}".format(str(track_list)))
for n in track_list:
debug.info(4,"Adding source ={0}".format(str(n)))
self.set_source(n,value)
self.set_blocked(n,False)
def add_map(self,p):
def add_target(self,track_list,value=True):
debug.info(3,"Adding target list={0}".format(str(track_list)))
for n in track_list:
debug.info(4,"Adding target ={0}".format(str(n)))
self.set_target(n,value)
self.set_blocked(n,False)
def is_target(self,point):
"""
Point is in the target set, so we are done.
"""
return point in self.target
def add_map(self,n):
"""
Add a point to the map if it doesn't exist.
"""
if p not in self.map.keys():
self.map[p]=cell()
if isinstance(n, (list,tuple,set,frozenset)):
for item in n:
self.add_map(item)
else:
if n not in self.map.keys():
self.map[n]=grid_cell()
def add_path(self,path):
def block_path(self,path):
"""
Mark the path in the routing grid for visualization
Mark the path in the routing grid as blocked.
Also unsets the path flag.
"""
self.path=path
for p in path:
self.map[p].path=True
def cost(self,path):
"""
The cost of the path is the length plus a penalty for the number
of vias. We assume that non-preferred direction is penalized.
"""
# Ignore the source pin layer change, FIXME?
def pairwise(iterable):
"s -> (s0,s1), (s1,s2), (s2, s3), ..."
a, b = tee(iterable)
next(b, None)
return zip(a, b)
plist = pairwise(path)
cost = 0
for p0,p1 in plist:
if p0.z != p1.z: # via
cost += self.VIA_COST
elif p0.x != p1.x: # horizontal
cost += self.NONPREFERRED_COST if (p0.z == 1) else self.PREFERRED_COST
elif p0.y != p1.y: # vertical
cost += self.NONPREFERRED_COST if (p0.z == 0) else self.PREFERRED_COST
else:
debug.error("Non-changing direction!")
return cost
path.set_path(False)
path.set_blocked(True)

View File

@ -1,10 +1,9 @@
class cell:
class grid_cell:
"""
A single cell that can be occupied in a given layer, blocked,
visited, etc.
"""
def __init__(self):
self.visited = False
self.path = False
self.blocked = False
self.source = False
@ -17,7 +16,6 @@ class cell:
Reset the dynamic info about routing. The pins/blockages are not reset so
that they can be reused.
"""
self.visited=False
self.min_cost=-1
self.min_path=None
self.blocked=False

View File

@ -0,0 +1,227 @@
import debug
from vector3d import vector3d
from itertools import tee
from grid import grid
from grid_cell import grid_cell
from direction import direction
class grid_path:
"""
A grid path is a list of lists of grid cells.
It can have a width that is more than one cell.
All of the sublists will be the same dimension.
Cells should be continguous.
It can have a name to define pin shapes as well.
"""
def __init__(self, items=[], name=""):
self.name = name
if items:
self.pathlist = [items]
else:
self.pathlist = []
def __str__(self):
#import pprint
p = str(self.pathlist) #pprint.pformat(self.pathlist)
if self.name != "":
return (str(self.name) + " : " + p)
return p
def __setitem__(self, index, value):
"""
override setitem function
can set value by pathinstance[index]=value
"""
self.pathlist[index]=value
def __getitem__(self, index):
"""
override getitem function
can get value by value=pathinstance[index]
"""
return self.pathlist[index]
def __contains__(self, key):
"""
Determine if cell exists in this path
"""
# FIXME: Could maintain a hash to make in O(1)
for sublist in self.pathlist:
for item in sublist:
if item == key:
return True
else:
return False
def __add__(self, items):
"""
Override add to do append
"""
return self.pathlist.extend(items)
def __len__(self):
return len(self.pathlist)
def trim_last(self):
"""
Drop the last item
"""
if len(self.pathlist)>0:
self.pathlist.pop()
def trim_first(self):
"""
Drop the first item
"""
if len(self.pathlist)>0:
self.pathlist.pop(0)
def append(self,item):
"""
Append the list of items to the cells
"""
self.pathlist.append(item)
def extend(self,item):
"""
Extend the list of items to the cells
"""
self.pathlist.extend(item)
def set_path(self,value=True):
for sublist in self.pathlist:
for p in sublist:
p.path=value
def set_blocked(self,value=True):
for sublist in self.pathlist:
for p in sublist:
p.blocked=value
def get_grids(self):
"""
Return a set of all the grids in this path.
"""
newset = set()
for sublist in self.pathlist:
newset.update(sublist)
return newset
def get_wire_grids(self, start_index, end_index):
"""
Return a set of all the wire grids in this path.
These are the indices in the wave path in a certain range.
"""
newset = set()
for sublist in self.pathlist:
newset.update(sublist[start_index:end_index])
return newset
def cost(self):
"""
The cost of the path is the length plus a penalty for the number
of vias. We assume that non-preferred direction is penalized.
This cost only works with 1 wide tracks.
"""
# Ignore the source pin layer change, FIXME?
def pairwise(iterable):
"s -> (s0,s1), (s1,s2), (s2, s3), ..."
a, b = tee(iterable)
next(b, None)
return zip(a, b)
plist = list(pairwise(self.pathlist))
cost = 0
for p0list,p1list in plist:
# This is because they are "waves" so pick the first item
p0=p0list[0]
p1=p1list[0]
if p0.z != p1.z: # via
cost += grid.VIA_COST
elif p0.x != p1.x and p0.z==1: # horizontal on vertical layer
cost += grid.NONPREFERRED_COST
elif p0.y != p1.y and p0.z==0: # vertical on horizontal layer
cost += grid.NONPREFERRED_COST
else:
cost += grid.PREFERRED_COST
return cost
def expand_dirs(self,up_down_too=True):
"""
Expand from the end in each of the four cardinal directions plus up
or down but not expanding to blocked cells. Expands in all
directions regardless of preferred directions.
If the width is more than one, it can only expand in one direction
(for now). This is assumed for the supply router for now.
"""
neighbors = []
for d in list(direction):
if not up_down_too and (d==direction.UP or d==direction.DOWN):
continue
n = self.neighbor(d)
if n:
neighbors.append(n)
return neighbors
def neighbor(self, d):
if d==direction.EAST:
offset = vector3d(1,0,0)
elif d==direction.WEST:
offset = vector3d(-1,0,0)
elif d==direction.NORTH:
offset = vector3d(0,1,0)
elif d==direction.SOUTH:
offset = vector3d(0,-1,0)
elif d==direction.UP:
offset = vector3d(0,0,1)
elif d==direction.DOWN:
offset = vector3d(0,0,-1)
else:
debug.error("Invalid direction {}".format(d),-1)
newwave = [point + offset for point in self.pathlist[-1]]
if newwave in self.pathlist:
return None
elif newwave[0].z>1 or newwave[0].z<0:
return None
return newwave
def set_layer(self, zindex):
new_pathlist = [vector3d(item.x, item.y, zindex) for wave in self.pathlist for item in wave]
self.pathlist = new_pathlist
def overlap(self, other):
"""
Return the overlap waves ignoring different layers
"""
my_zindex = self.pathlist[0][0].z
other_flat_cells = [vector3d(item.x,item.y,my_zindex) for wave in other.pathlist for item in wave]
# This keeps the wave structure of the self layer
shared_waves = []
for wave in self.pathlist:
for item in wave:
# If any item in the wave is not contained, skip it
if not item in other_flat_cells:
break
else:
shared_waves.append(wave)
if len(shared_waves)>0:
ll = shared_waves[0][0]
ur = shared_waves[-1][-1]
return [ll,ur]
return None

View File

@ -0,0 +1,111 @@
"""
Some utility functions for sets of grid cells.
"""
import debug
from direction import direction
from vector3d import vector3d
def increment_set(curset, direct):
"""
Return the cells incremented in given direction
"""
if direct==direction.NORTH:
offset = vector3d(0,1,0)
elif direct==direction.SOUTH:
offset = vector3d(0,-1,0)
elif direct==direction.EAST:
offset = vector3d(1,0,0)
elif direct==direction.WEST:
offset = vector3d(-1,0,0)
elif direct==direction.UP:
offset = vector3d(0,0,1)
elif direct==direction.DOWN:
offset = vector3d(0,0,-1)
else:
debug.error("Invalid direction {}".format(dirct))
newset = set()
for c in curset:
newc = c+offset
newset.add(newc)
return newset
def remove_border(curset, direct):
"""
Remove the cells on a given border.
"""
border = get_border(curset, direct)
curset.difference_update(border)
def get_upper_right(curset):
ur = None
for p in curset:
if ur == None or (p.x>=ur.x and p.y>=ur.y):
ur = p
return ur
def get_lower_left(curset):
ll = None
for p in curset:
if ll == None or (p.x<=ll.x and p.y<=ll.y):
ll = p
return ll
def get_border( curset, direct):
"""
Return the furthest cell(s) in a given direction.
"""
# find direction-most cell(s)
maxc = []
if direct==direction.NORTH:
for c in curset:
if len(maxc)==0 or c.y>maxc[0].y:
maxc = [c]
elif c.y==maxc[0].y:
maxc.append(c)
elif direct==direct.SOUTH:
for c in curset:
if len(maxc)==0 or c.y<maxc[0].y:
maxc = [c]
elif c.y==maxc[0].y:
maxc.append(c)
elif direct==direct.EAST:
for c in curset:
if len(maxc)==0 or c.x>maxc[0].x:
maxc = [c]
elif c.x==maxc[0].x:
maxc.append(c)
elif direct==direct.WEST:
for c in curset:
if len(maxc)==0 or c.x<maxc[0].x:
maxc = [c]
elif c.x==maxc[0].x:
maxc.append(c)
newset = set(maxc)
return newset
def expand_border(curset, direct):
"""
Expand the current set of sells in a given direction.
Only return the contiguous cells.
"""
border_set = get_border(curset, direct)
next_border_set = increment_set(border_set, direct)
return next_border_set
def expand_borders(curset):
"""
Return the expansions in planar directions.
"""
north_set=expand_border(curset,direction.NORTH)
south_set=expand_border(curset,direction.SOUTH)
east_set=expand_border(curset,direction.EAST)
west_set=expand_border(curset,direction.WEST)
return(north_set, east_set, south_set, west_set)

File diff suppressed because it is too large Load Diff

View File

@ -1,55 +1,24 @@
from itertools import tee
import debug
from vector3d import vector3d
import grid
from heapq import heappush,heappop
from copy import deepcopy
class astar_grid(grid.grid):
from grid import grid
from grid_path import grid_path
from vector3d import vector3d
class signal_grid(grid):
"""
Expand the two layer grid to include A* search functions for a source and target.
"""
def __init__(self):
def __init__(self, ll, ur, track_factor):
""" Create a routing map of width x height cells and 2 in the z-axis. """
grid.grid.__init__(self)
grid.__init__(self, ll, ur, track_factor)
# list of the source/target grid coordinates
self.source = []
self.target = []
# priority queue for the maze routing
self.q = []
def set_source(self,n):
self.add_map(n)
self.map[n].source=True
self.source.append(n)
def set_target(self,n):
self.add_map(n)
self.map[n].target=True
self.target.append(n)
def add_source(self,track_list):
debug.info(2,"Adding source list={0}".format(str(track_list)))
for n in track_list:
if not self.is_blocked(n):
debug.info(3,"Adding source ={0}".format(str(n)))
self.set_source(n)
def add_target(self,track_list):
debug.info(2,"Adding target list={0}".format(str(track_list)))
for n in track_list:
if not self.is_blocked(n):
self.set_target(n)
def is_target(self,point):
"""
Point is in the target set, so we are done.
"""
return point in self.target
def reinit(self):
""" Reinitialize everything for a new route. """
@ -73,112 +42,98 @@ class astar_grid(grid.grid):
We will use an A* search, so this cost must be pessimistic.
Cost so far will be the length of the path.
"""
debug.info(4,"Initializing queue.")
# uniquify the source (and target while we are at it)
self.source = list(set(self.source))
self.target = list(set(self.target))
#debug.info(3,"Initializing queue.")
# Counter is used to not require data comparison in Python 3.x
# Items will be returned in order they are added during cost ties
self.counter = 0
for s in self.source:
cost = self.cost_to_target(s)
debug.info(1,"Init: cost=" + str(cost) + " " + str([s]))
heappush(self.q,(cost,self.counter,[s]))
debug.info(3,"Init: cost=" + str(cost) + " " + str([s]))
heappush(self.q,(cost,self.counter,grid_path([vector3d(s)])))
self.counter+=1
def route(self,detour_scale):
"""
This does the A* maze routing with preferred direction routing.
This only works for 1 track wide routes!
"""
# We set a cost bound of the HPWL for run-time. This can be
# over-ridden if the route fails due to pruning a feasible solution.
cost_bound = detour_scale*self.cost_to_target(self.source[0])*self.PREFERRED_COST
cost_bound = detour_scale*self.cost_to_target(self.source[0])*grid.PREFERRED_COST
# Check if something in the queue is already a source and a target!
for s in self.source:
if self.is_target(s):
return((grid_path([vector3d(s)]),0))
# Make sure the queue is empty if we run another route
while len(self.q)>0:
heappop(self.q)
# Put the source items into the queue
self.init_queue()
cheapest_path = None
cheapest_cost = None
# Keep expanding and adding to the priority queue until we are done
while len(self.q)>0:
# should we keep the path in the queue as well or just the final node?
(cost,count,path) = heappop(self.q)
debug.info(2,"Queue size: size=" + str(len(self.q)) + " " + str(cost))
debug.info(3,"Expanding: cost=" + str(cost) + " " + str(path))
(cost,count,curpath) = heappop(self.q)
debug.info(3,"Queue size: size=" + str(len(self.q)) + " " + str(cost))
debug.info(4,"Expanding: cost=" + str(cost) + " " + str(curpath))
# expand the last element
neighbors = self.expand_dirs(path)
debug.info(3,"Neighbors: " + str(neighbors))
neighbors = self.expand_dirs(curpath)
debug.info(4,"Neighbors: " + str(neighbors))
for n in neighbors:
# make a new copy of the path to not update the old ones
newpath = deepcopy(curpath)
# node is added to the map by the expand routine
newpath = path + [n]
newpath.append(n)
# check if we hit the target and are done
if self.is_target(n):
return (newpath,self.cost(newpath))
elif not self.map[n].visited:
if self.is_target(n[0]): # This uses the [0] item because we are assuming 1-track wide
return (newpath,newpath.cost())
else:
# current path cost + predicted cost
current_cost = self.cost(newpath)
target_cost = self.cost_to_target(n)
current_cost = newpath.cost()
target_cost = self.cost_to_target(n[0])
predicted_cost = current_cost + target_cost
# only add the cost if it is less than our bound
if (predicted_cost < cost_bound):
if (self.map[n].min_cost==-1 or current_cost<self.map[n].min_cost):
self.map[n].visited=True
self.map[n].min_path = newpath
self.map[n].min_cost = predicted_cost
debug.info(3,"Enqueuing: cost=" + str(current_cost) + "+" + str(target_cost) + " " + str(newpath))
if (self.map[n[0]].min_cost==-1 or predicted_cost<self.map[n[0]].min_cost):
self.map[n[0]].min_path = newpath
self.map[n[0]].min_cost = predicted_cost
debug.info(4,"Enqueuing: cost=" + str(current_cost) + "+" + str(target_cost) + " " + str(newpath))
# add the cost to get to this point if we haven't reached it yet
heappush(self.q,(predicted_cost,self.counter,newpath))
self.counter += 1
#else:
# print("Better previous cost.")
#else:
# print("Cost bounded")
debug.warning("Unable to route path. Expand the detour_scale to allow detours.")
return (None,None)
def expand_dirs(self,path):
def expand_dirs(self,curpath):
"""
Expand each of the four cardinal directions plus up or down
but not expanding to blocked cells. Expands in all directions
regardless of preferred directions.
"""
# expand from the last point
point = path[-1]
neighbors = []
east = point + vector3d(1,0,0)
if not self.is_blocked(east) and not east in path:
neighbors.append(east)
# Expand all directions.
neighbors = curpath.expand_dirs()
west= point + vector3d(-1,0,0)
if not self.is_blocked(west) and not west in path:
neighbors.append(west)
# Filter the blocked ones
unblocked_neighbors = [x for x in neighbors if not self.is_blocked(x)]
up = point + vector3d(0,0,1)
if up.z<2 and not self.is_blocked(up) and not up in path:
neighbors.append(up)
north = point + vector3d(0,1,0)
if not self.is_blocked(north) and not north in path:
neighbors.append(north)
south = point + vector3d(0,-1,0)
if not self.is_blocked(south) and not south in path:
neighbors.append(south)
down = point + vector3d(0,0,-1)
if down.z>=0 and not self.is_blocked(down) and not down in path:
neighbors.append(down)
return neighbors
return unblocked_neighbors
def hpwl(self, src, dest):
@ -191,7 +146,7 @@ class astar_grid(grid.grid):
hpwl += max(abs(src.y-dest.y),abs(dest.y-src.y))
hpwl += max(abs(src.z-dest.z),abs(dest.z-src.z))
if src.x!=dest.x or src.y!=dest.y:
hpwl += self.VIA_COST
hpwl += grid.VIA_COST
return hpwl
def cost_to_target(self,source):
@ -202,37 +157,10 @@ class astar_grid(grid.grid):
cost = self.hpwl(source,self.target[0])
for t in self.target:
cost = min(self.hpwl(source,t),cost)
return cost
def cost(self,path):
"""
The cost of the path is the length plus a penalty for the number
of vias. We assume that non-preferred direction is penalized.
"""
# Ignore the source pin layer change, FIXME?
def pairwise(iterable):
"s -> (s0,s1), (s1,s2), (s2, s3), ..."
a, b = tee(iterable)
next(b, None)
return zip(a, b)
plist = pairwise(path)
cost = 0
for p0,p1 in plist:
if p0.z != p1.z: # via
cost += self.VIA_COST
elif p0.x != p1.x: # horizontal
cost += self.NONPREFERRED_COST if (p0.z == 1) else self.PREFERRED_COST
elif p0.y != p1.y: # vertical
cost += self.NONPREFERRED_COST if (p0.z == 0) else self.PREFERRED_COST
else:
debug.error("Non-changing direction!")
return cost
def get_inertia(self,p0,p1):
"""
Sets the direction based on the previous direction we came from.

View File

@ -4,24 +4,21 @@ from contact import contact
import math
import debug
from pin_layout import pin_layout
from vector import vector
from vector3d import vector3d
from globals import OPTS
from router import router
class signal_router(router):
"""A router class to read an obstruction map from a gds and plan a
"""
A router class to read an obstruction map from a gds and plan a
route on a given layer. This is limited to two layer routes.
"""
def __init__(self, gds_name=None, module=None):
"""Use the gds file for the blockages with the top module topName and
layers for the layers to route on
def __init__(self, layers, design, gds_filename=None):
"""
router.__init__(self, gds_name, module)
# all the paths we've routed so far (to supplement the blockages)
self.paths = []
This will route on layers in design. It will get the blockages from
either the gds file name or the design itself (by saving to a gds file).
"""
router.__init__(self, layers, design, gds_filename)
def create_routing_grid(self):
@ -33,130 +30,51 @@ class signal_router(router):
size = self.ur - self.ll
debug.info(1,"Size: {0} x {1}".format(size.x,size.y))
import astar_grid
self.rg = astar_grid.astar_grid()
import signal_grid
self.rg = signal_grid.signal_grid(self.ll, self.ur, self.track_width)
def route(self, cell, layers, src, dest, detour_scale=5):
def route(self, src, dest, detour_scale=5):
"""
Route a single source-destination net and return
the simplified rectilinear path. Cost factor is how sub-optimal to explore for a feasible route.
This is used to speed up the routing when there is not much detouring needed.
"""
debug.info(1,"Running signal router from {0} to {1}...".format(src,dest))
self.cell = cell
self.source_pin_name = src
self.target_pin_name = dest
self.pins[src] = []
self.pins[dest] = []
# Clear the pins if we have previously routed
if (hasattr(self,'rg')):
self.clear_pins()
else:
# Set up layers and track sizes
self.set_layers(layers)
# Creat a routing grid over the entire area
# FIXME: This could be created only over the routing region,
# but this is simplest for now.
self.create_routing_grid()
# This will get all shapes as blockages
self.find_blockages()
# Get the pin shapes
self.get_pin(src)
self.get_pin(dest)
self.find_pins_and_blockages([src, dest])
# Now add the blockages (all shapes except the src/tgt pins)
self.add_blockages()
# Add blockages from previous paths
self.add_path_blockages()
# Block everything
self.prepare_blockages()
# Clear the pins we are routing
self.set_blockages(self.pin_components[src],False)
self.set_blockages(self.pin_components[dest],False)
# Now add the src/tgt if they are not blocked by other shapes
self.add_pin(src,True)
self.add_pin(dest,False)
self.add_source(src)
self.add_target(dest)
# returns the path in tracks
(path,cost) = self.rg.route(detour_scale)
if path:
debug.info(1,"Found path: cost={0} ".format(cost))
debug.info(2,str(path))
self.add_route(path)
return True
else:
self.write_debug_gds()
# clean up so we can try a reroute
self.clear_pins()
return False
if not self.run_router(detour_scale=detour_scale):
self.write_debug_gds(stop_program=False)
return False
self.write_debug_gds(stop_program=False)
return True
def add_route(self,path):
"""
Add the current wire route to the given design instance.
"""
debug.info(3,"Set path: " + str(path))
# Keep track of path for future blockages
self.paths.append(path)
# This is marked for debug
self.rg.add_path(path)
# For debugging... if the path failed to route.
if False or path==None:
self.write_debug_gds()
# First, simplify the path for
#debug.info(1,str(self.path))
contracted_path = self.contract_path(path)
debug.info(1,str(contracted_path))
# convert the path back to absolute units from tracks
abs_path = map(self.convert_point_to_units,contracted_path)
debug.info(1,str(abs_path))
self.cell.add_route(self.layers,abs_path)
def get_inertia(self,p0,p1):
"""
Sets the direction based on the previous direction we came from.
"""
# direction (index) of movement
if p0.x!=p1.x:
return 0
elif p0.y!=p1.y:
return 1
else:
# z direction
return 2
def contract_path(self,path):
"""
Remove intermediate points in a rectilinear path.
"""
newpath = [path[0]]
for i in range(1,len(path)-1):
prev_inertia=self.get_inertia(path[i-1],path[i])
next_inertia=self.get_inertia(path[i],path[i+1])
# if we switch directions, add the point, otherwise don't
if prev_inertia!=next_inertia:
newpath.append(path[i])
# always add the last path
newpath.append(path[-1])
return newpath
def add_path_blockages(self):
"""
Go through all of the past paths and add them as blockages.
This is so we don't have to write/reload the GDS.
"""
for path in self.paths:
for grid in path:
self.rg.set_blocked(grid)

View File

@ -1,27 +1,79 @@
import debug
from vector3d import vector3d
import grid
from grid import grid
from signal_grid import signal_grid
from grid_path import grid_path
from direction import direction
class supply_grid(grid.grid):
class supply_grid(signal_grid):
"""
A two layer routing map. Each cell can be blocked in the vertical
or horizontal layer.
This routes a supply grid. It is derived from a signal grid because it still
routes the pins to the supply rails using the same routines.
It has a few extra routines to support "waves" which are multiple track wide
directional routes (no bends).
"""
def __init__(self):
def __init__(self, ll, ur, track_width):
""" Create a routing map of width x height cells and 2 in the z-axis. """
grid.grid.__init__(self)
signal_grid.__init__(self, ll, ur, track_width)
# list of the vdd/gnd rail cells
self.vdd_rails = []
self.gnd_rails = []
def reinit(self):
""" Reinitialize everything for a new route. """
self.source = []
self.target = []
# Reset all the cells in the map
for p in self.map.values():
p.reset()
def find_start_wave(self, wave, width, direct):
"""
Finds the first loc starting at loc and up that is open.
Returns None if it reaches max size first.
"""
# Don't expand outside the bounding box
if wave[0].x > self.ur.x:
return None
if wave[-1].y > self.ur.y:
return None
while wave and self.is_wave_blocked(wave):
wf=grid_path(wave)
wave=wf.neighbor(direct)
# Bail out if we couldn't increment futher
if wave[0].x > self.ur.x or wave[-1].y > self.ur.y:
return None
# Return a start if it isn't blocked
if not self.is_wave_blocked(wave):
return wave
return wave
def is_wave_blocked(self, wave):
"""
Checks if any of the locations are blocked
"""
for v in wave:
if self.is_blocked(v):
return True
else:
return False
def probe(self, wave, direct):
"""
Expand the wave until there is a blockage and return
the wave path.
"""
wave_path = grid_path()
while wave and not self.is_wave_blocked(wave):
if wave[0].x > self.ur.x or wave[-1].y > self.ur.y:
break
wave_path.append(wave)
wave = wave_path.neighbor(direct)
return wave_path

View File

@ -3,12 +3,13 @@ import tech
from contact import contact
import math
import debug
import grid
from pin_layout import pin_layout
from vector import vector
from vector3d import vector3d
from globals import OPTS
from pin_layout import pin_layout
from vector3d import vector3d
from router import router
from direction import direction
import grid
import grid_utils
class supply_router(router):
"""
@ -16,131 +17,504 @@ class supply_router(router):
routes a grid to connect the supply on the two layers.
"""
def __init__(self, gds_name=None, module=None):
"""Use the gds file for the blockages with the top module topName and
layers for the layers to route on
def __init__(self, layers, design, gds_filename=None):
"""
router.__init__(self, gds_name, module)
self.pins = {}
This will route on layers in design. It will get the blockages from
either the gds file name or the design itself (by saving to a gds file).
"""
router.__init__(self, layers, design, gds_filename)
def clear_pins(self):
"""
Convert the routed path to blockages.
Keep the other blockages unchanged.
"""
self.pins = {}
self.rg.reinit()
# We over-ride the regular router costs to allow
# more off-direction router in the supply grid
grid.VIA_COST = 1
grid.NONPREFERRED_COST = 1
grid.PREFERRED_COST = 1
def route(self, cell, layers, vdd_name="vdd", gnd_name="gnd"):
# The list of supply rails (grid sets) that may be routed
self.supply_rails = {}
self.supply_rail_wires = {}
# This is the same as above but as a sigle set for the all the rails
self.supply_rail_tracks = {}
self.supply_rail_wire_tracks = {}
# Power rail width in grid units.
self.rail_track_width = 2
def create_routing_grid(self):
"""
Route a single source-destination net and return
the simplified rectilinear path.
Create a sprase routing grid with A* expansion functions.
"""
size = self.ur - self.ll
debug.info(1,"Size: {0} x {1}".format(size.x,size.y))
import supply_grid
self.rg = supply_grid.supply_grid(self.ll, self.ur, self.track_width)
def route(self, vdd_name="vdd", gnd_name="gnd"):
"""
Add power supply rails and connect all pins to these rails.
"""
debug.info(1,"Running supply router on {0} and {1}...".format(vdd_name, gnd_name))
self.cell = cell
self.pins[vdd_name] = []
self.pins[gnd_name] = []
self.vdd_name = vdd_name
self.gnd_name = gnd_name
# Clear the pins if we have previously routed
if (hasattr(self,'rg')):
self.clear_pins()
else:
# Set up layers and track sizes
self.set_layers(layers)
# Creat a routing grid over the entire area
# FIXME: This could be created only over the routing region,
# but this is simplest for now.
self.create_routing_grid()
# This will get all shapes as blockages
self.find_blockages()
# Get the pin shapes
self.get_pin(vdd_name)
self.get_pin(gnd_name)
# Now add the blockages (all shapes except the src/tgt pins)
self.add_blockages()
# Add blockages from previous routes
self.add_path_blockages()
self.find_pins_and_blockages([self.vdd_name, self.gnd_name])
#self.write_debug_gds("pin_enclosures.gds",stop_program=False)
# source pin will be a specific layout pin
# target pin will be the rails only
# Add the supply rails in a mesh network and connect H/V with vias
# Block everything
self.prepare_blockages(self.gnd_name)
# Determine the rail locations
self.route_supply_rails(self.gnd_name,0)
# Block everything
self.prepare_blockages(self.vdd_name)
# Determine the rail locations
self.route_supply_rails(self.vdd_name,1)
#self.write_debug_gds("debug_rails.gds",stop_program=True)
remaining_vdd_pin_indices = self.route_simple_overlaps(vdd_name)
remaining_gnd_pin_indices = self.route_simple_overlaps(gnd_name)
#self.write_debug_gds("debug_simple_route.gds",stop_program=True)
# Route the supply pins to the supply rails
# Route vdd first since we want it to be shorter
self.route_pins_to_rails(vdd_name, remaining_vdd_pin_indices)
self.route_pins_to_rails(gnd_name, remaining_gnd_pin_indices)
#self.write_debug_gds("debug_pin_routes.gds",stop_program=True)
#self.write_debug_gds("final.gds")
return True
def route_simple_overlaps(self, pin_name):
"""
This checks for simple cases where a pin component already overlaps a supply rail.
It will add an enclosure to ensure the overlap in wide DRC rule cases.
"""
num_components = self.num_pin_components(pin_name)
remaining_pins = []
supply_tracks = self.supply_rail_tracks[pin_name]
for index in range(num_components):
pin_in_tracks = self.pin_grids[pin_name][index]
common_set = supply_tracks & pin_in_tracks
if len(common_set)==0:
# if no overlap, add it to the complex route pins
remaining_pins.append(index)
else:
self.create_simple_overlap_enclosure(pin_name, common_set)
# returns the path in tracks
# (path,cost) = self.rg.route(detour_scale)
# if path:
# debug.info(1,"Found path: cost={0} ".format(cost))
# debug.info(2,str(path))
# self.add_route(path)
# return True
# else:
# self.write_debug_gds()
# # clean up so we can try a reroute
# self.clear_pins()
return remaining_pins
def recurse_simple_overlap_enclosure(self, pin_name, start_set, direct):
"""
Recursive function to return set of tracks that connects to
the actual supply rail wire in a given direction (or terminating
when any track is no longer in the supply rail.
"""
next_set = grid_utils.expand_border(start_set, direct)
supply_tracks = self.supply_rail_tracks[pin_name]
supply_wire_tracks = self.supply_rail_wire_tracks[pin_name]
supply_overlap = next_set & supply_tracks
wire_overlap = next_set & supply_wire_tracks
# If the rail overlap is the same, we are done, since we connected to the actual wire
if len(wire_overlap)==len(start_set):
new_set = start_set | wire_overlap
# If the supply overlap is the same, keep expanding unti we hit the wire or move out of the rail region
elif len(supply_overlap)==len(start_set):
recurse_set = self.recurse_simple_overlap_enclosure(pin_name, supply_overlap, direct)
new_set = start_set | supply_overlap | recurse_set
else:
# If we got no next set, we are done, can't expand!
new_set = set()
self.write_debug_gds()
return new_set
def create_simple_overlap_enclosure(self, pin_name, start_set):
"""
This takes a set of tracks that overlap a supply rail and creates an enclosure
that is ensured to overlap the supply rail wire.
It then adds rectangle(s) for the enclosure.
"""
additional_set = set()
# Check the layer of any element in the pin to determine which direction to route it
e = next(iter(start_set))
new_set = start_set.copy()
if e.z==0:
new_set = self.recurse_simple_overlap_enclosure(pin_name, start_set, direction.NORTH)
if not new_set:
new_set = self.recurse_simple_overlap_enclosure(pin_name, start_set, direction.SOUTH)
else:
new_set = self.recurse_simple_overlap_enclosure(pin_name, start_set, direction.EAST)
if not new_set:
new_set = self.recurse_simple_overlap_enclosure(pin_name, start_set, direction.WEST)
enclosure_list = self.compute_enclosures(new_set)
for pin in enclosure_list:
debug.info(2,"Adding simple overlap enclosure {0} {1}".format(pin_name, pin))
self.cell.add_rect(layer=pin.layer,
offset=pin.ll(),
width=pin.width(),
height=pin.height())
def finalize_supply_rails(self, name):
"""
Determine which supply rails overlap and can accomodate a via.
Remove any supply rails that do not have a via since they are disconnected.
NOTE: It is still possible though unlikely that there are disconnected groups of rails.
"""
all_rails = self.supply_rail_wires[name]
connections = set()
via_areas = []
for i1,r1 in enumerate(all_rails):
# Only consider r1 horizontal rails
e = next(iter(r1))
if e.z==1:
continue
# We need to move this rail to the other layer for the z indices to match
# during the intersection. This also makes a copy.
new_r1 = {vector3d(i.x,i.y,1) for i in r1}
# If horizontal, subtract off the left/right track to prevent end of rail via
#ll = grid_utils.get_lower_left(new_r1)
#ur = grid_utils.get_upper_right(new_r1)
grid_utils.remove_border(new_r1, direction.EAST)
grid_utils.remove_border(new_r1, direction.WEST)
for i2,r2 in enumerate(all_rails):
# Never compare to yourself
if i1==i2:
continue
# Only consider r2 vertical rails
e = next(iter(r2))
if e.z==0:
continue
# Need to maek a copy to consider via overlaps to ignore the end-caps
new_r2 = r2.copy()
grid_utils.remove_border(new_r2, direction.NORTH)
grid_utils.remove_border(new_r2, direction.SOUTH)
# Determine if we hhave sufficient overlap and, if so,
# remember:
# the indices to determine a rail is connected to another
# the overlap area for placement of a via
overlap = new_r1 & new_r2
if len(overlap) >= self.supply_rail_wire_width**2:
debug.info(2,"Via overlap {0} {1} {2}".format(len(overlap),self.supply_rail_wire_width**2,overlap))
connections.add(i1)
connections.add(i2)
via_areas.append(overlap)
# Go through and add the vias at the center of the intersection
for area in via_areas:
ll = grid_utils.get_lower_left(area)
ur = grid_utils.get_upper_right(area)
center = (ll + ur).scale(0.5,0.5,0)
self.add_via(center,self.rail_track_width)
# Determien which indices were not connected to anything above
all_indices = set([x for x in range(len(self.supply_rails[name]))])
missing_indices = all_indices ^ connections
# Go through and remove those disconnected indices
# (No via was added, so that doesn't need to be removed)
for rail_index in missing_indices:
ll = grid_utils.get_lower_left(all_rails[rail_index])
ur = grid_utils.get_upper_right(all_rails[rail_index])
debug.info(1,"Removing disconnected supply rail {0} .. {1}".format(ll,ur))
self.supply_rails[name].pop(rail_index)
self.supply_rail_wires[name].pop(rail_index)
# Make the supply rails into a big giant set of grids for easy blockages.
# Must be done after we determine which ones are connected.
self.create_supply_track_set(name)
def add_supply_rails(self, name):
"""
Add the shapes that represent the routed supply rails.
This is after the paths have been pruned and only include rails that are
connected with vias.
"""
for rail in self.supply_rails[name]:
ll = grid_utils.get_lower_left(rail)
ur = grid_utils.get_upper_right(rail)
z = ll.z
pin = self.compute_wide_enclosure(ll, ur, z, name)
debug.info(1,"Adding supply rail {0} {1}->{2} {3}".format(name,ll,ur,pin))
self.cell.add_layout_pin(text=name,
layer=pin.layer,
offset=pin.ll(),
width=pin.width(),
height=pin.height())
def compute_supply_rail_dimensions(self):
"""
Compute the supply rail dimensions including wide metal spacing rules.
"""
self.max_yoffset = self.rg.ur.y
self.max_xoffset = self.rg.ur.x
# Longest length is conservative
rail_length = max(self.max_yoffset,self.max_xoffset)
# Convert the number of tracks to dimensions to get the design rule spacing
rail_width = self.track_width*self.rail_track_width
# Get the conservative width and spacing of the top rails
(horizontal_width, horizontal_space) = self.get_layer_width_space(0, rail_width, rail_length)
(vertical_width, vertical_space) = self.get_layer_width_space(1, rail_width, rail_length)
width = max(horizontal_width, vertical_width)
space = max(horizontal_space, vertical_space)
# This is the supply rail pitch in terms of routing grids
# i.e. a rail of self.rail_track_width needs this many tracks including
# space
track_pitch = self.rail_track_width*width + space
# Determine the pitch (in tracks) of the rail wire + spacing
self.supply_rail_width = math.ceil(track_pitch/self.track_width)
debug.info(1,"Rail step: {}".format(self.supply_rail_width))
# Conservatively determine the number of tracks that the rail actually occupies
space_tracks = math.ceil(space/self.track_width)
self.supply_rail_wire_width = self.supply_rail_width - space_tracks
debug.info(1,"Rail wire tracks: {}".format(self.supply_rail_wire_width))
total_space = self.supply_rail_width - self.supply_rail_wire_width
self.supply_rail_space_width = math.floor(0.5*total_space)
debug.info(1,"Rail space tracks: {} (on both sides)".format(self.supply_rail_space_width))
def compute_supply_rails(self, name, supply_number):
"""
Compute the unblocked locations for the horizontal and vertical supply rails.
Go in a raster order from bottom to the top (for horizontal) and left to right
(for vertical). Start with an initial start_offset in x and y direction.
"""
self.supply_rails[name]=[]
self.supply_rail_wires[name]=[]
start_offset = supply_number*self.supply_rail_width
# Horizontal supply rails
for offset in range(start_offset, self.max_yoffset, 2*self.supply_rail_width):
# Seed the function at the location with the given width
wave = [vector3d(0,offset+i,0) for i in range(self.supply_rail_width)]
# While we can keep expanding east in this horizontal track
while wave and wave[0].x < self.max_xoffset:
added_rail = self.find_supply_rail(name, wave, direction.EAST)
if added_rail:
wave = added_rail.neighbor(direction.EAST)
else:
wave = None
# Vertical supply rails
max_offset = self.rg.ur.x
for offset in range(start_offset, self.max_xoffset, 2*self.supply_rail_width):
# Seed the function at the location with the given width
wave = [vector3d(offset+i,0,1) for i in range(self.supply_rail_width)]
# While we can keep expanding north in this vertical track
while wave and wave[0].y < self.max_yoffset:
added_rail = self.find_supply_rail(name, wave, direction.NORTH)
if added_rail:
wave = added_rail.neighbor(direction.NORTH)
else:
wave = None
def find_supply_rail(self, name, seed_wave, direct):
"""
Find a start location, probe in the direction, and see if the rail is big enough
to contain a via, and, if so, add it.
"""
start_wave = self.find_supply_rail_start(name, seed_wave, direct)
if not start_wave:
return None
wave_path = self.probe_supply_rail(name, start_wave, direct)
if self.approve_supply_rail(name, wave_path):
return wave_path
else:
return None
def find_supply_rail_start(self, name, seed_wave, direct):
"""
This finds the first valid starting location and routes a supply rail
in the given direction.
It returns the space after the end of the rail to seed another call for multiple
supply rails in the same "track" when there is a blockage.
"""
# Sweep to find an initial unblocked valid wave
start_wave = self.rg.find_start_wave(seed_wave, len(seed_wave), direct)
return start_wave
def probe_supply_rail(self, name, start_wave, direct):
"""
This finds the first valid starting location and routes a supply rail
in the given direction.
It returns the space after the end of the rail to seed another call for multiple
supply rails in the same "track" when there is a blockage.
"""
# Expand the wave to the right
wave_path = self.rg.probe(start_wave, direct)
if not wave_path:
return None
# drop the first and last steps to leave escape routing room
# around the blockage that stopped the probe
# except, don't drop the first if it is the first in a row/column
if (direct==direction.NORTH and start_wave[0].y>0):
wave_path.trim_first()
elif (direct == direction.EAST and start_wave[0].x>0):
wave_path.trim_first()
wave_path.trim_last()
return wave_path
def approve_supply_rail(self, name, wave_path):
"""
Check if the supply rail is sufficient (big enough) and add it to the
data structure. Return whether it was added or not.
"""
# We must have at least 2 tracks to drop plus 2 tracks for a via
if len(wave_path)>=4*self.rail_track_width:
grid_set = wave_path.get_grids()
self.supply_rails[name].append(grid_set)
start_wire_index = self.supply_rail_space_width
end_wire_index = self.supply_rail_width - self.supply_rail_space_width
wire_set = wave_path.get_wire_grids(start_wire_index,end_wire_index)
self.supply_rail_wires[name].append(wire_set)
return True
return False
def add_route(self,path):
"""
Add the current wire route to the given design instance.
def route_supply_rails(self, name, supply_number):
"""
Route the horizontal and vertical supply rails across the entire design.
Must be done with lower left at 0,0
"""
debug.info(3,"Set path: " + str(path))
# Keep track of path for future blockages
self.paths.append(path)
# Compute the grid dimensions
self.compute_supply_rail_dimensions()
# This is marked for debug
self.rg.add_path(path)
# Compute the grid locations of the supply rails
self.compute_supply_rails(name, supply_number)
# Add the supply rail vias (and prune disconnected rails)
self.finalize_supply_rails(name)
# For debugging... if the path failed to route.
if False or path==None:
self.write_debug_gds()
# Add the rails themselves
self.add_supply_rails(name)
# First, simplify the path for
#debug.info(1,str(self.path))
contracted_path = self.contract_path(path)
debug.info(1,str(contracted_path))
def create_supply_track_set(self, pin_name):
"""
Make a single set of all the tracks for the rail and wire itself.
"""
rail_set = set()
for rail in self.supply_rails[pin_name]:
rail_set.update(rail)
self.supply_rail_tracks[pin_name] = rail_set
# convert the path back to absolute units from tracks
abs_path = map(self.convert_point_to_units,contracted_path)
debug.info(1,str(abs_path))
self.cell.add_route(self.layers,abs_path)
wire_set = set()
for rail in self.supply_rail_wires[pin_name]:
wire_set.update(rail)
self.supply_rail_wire_tracks[pin_name] = wire_set
def route_pins_to_rails(self, pin_name, remaining_component_indices):
"""
This will route each of the remaining pin components to the supply rails.
After it is done, the cells are added to the pin blockage list.
"""
debug.info(1,"Pin {0} has {1} remaining components to route.".format(pin_name,
len(remaining_component_indices)))
recent_paths = []
# For every component
for index in remaining_component_indices:
debug.info(2,"Routing component {0} {1}".format(pin_name, index))
self.rg.reinit()
self.prepare_blockages(pin_name)
# Add the single component of the pin as the source
# which unmarks it as a blockage too
self.add_pin_component_source(pin_name,index)
# Add all of the rails as targets
# Don't add the other pins, but we could?
self.add_supply_rail_target(pin_name)
# Add the previous paths as targets too
#self.add_path_target(recent_paths)
#print(self.rg.target)
# Actually run the A* router
if not self.run_router(detour_scale=5):
self.write_debug_gds()
recent_paths.append(self.paths[-1])
def create_routing_grid(self):
"""
Create a sprase routing grid with A* expansion functions.
def add_supply_rail_target(self, pin_name):
"""
# We will add a halo around the boundary
# of this many tracks
size = self.ur - self.ll
debug.info(1,"Size: {0} x {1}".format(size.x,size.y))
Add the supply rails of given name as a routing target.
"""
debug.info(2,"Add supply rail target {}".format(pin_name))
# Add the wire itself as the target
self.rg.set_target(self.supply_rail_wire_tracks[pin_name])
# But unblock all the rail tracks including the space
self.rg.set_blocked(self.supply_rail_tracks[pin_name],False)
import supply_grid
self.rg = supply_grid.supply_grid()
##########################
# Gridded supply route functions
##########################
def create_grid(self, ll, ur):
""" Create alternating vdd/gnd lines horizontally """
self.create_horizontal_grid()
self.create_vertical_grid()
def create_horizontal_grid(self):
""" Create alternating vdd/gnd lines horizontally """
pass
def create_vertical_grid(self):
""" Create alternating vdd/gnd lines horizontally """
pass
def set_supply_rail_blocked(self, value=True):
"""
Add the supply rails of given name as a routing target.
"""
debug.info(3,"Blocking supply rail")
for rail_name in self.supply_rail_tracks:
self.rg.set_blocked(self.supply_rail_tracks[rail_name])

View File

@ -37,9 +37,9 @@ class no_blockages_test(openram_test):
offset=[0,0])
self.connect_inst([])
r=router(gds_file)
layer_stack =("metal1","via1","metal2")
self.assertTrue(r.route(self,layer_stack,src="A",dest="B"))
r=router(layer_stack,self,gds_file)
self.assertTrue(r.route(src="A",dest="B"))
r=routing("01_no_blockages_test_{0}".format(OPTS.tech_name))
self.local_drc_check(r)

View File

@ -37,9 +37,9 @@ class blockages_test(openram_test):
offset=[0,0])
self.connect_inst([])
r=router(gds_file)
layer_stack =("metal1","via1","metal2")
self.assertTrue(r.route(self,layer_stack,src="A",dest="B"))
r=router(layer_stack,self,gds_file)
self.assertTrue(r.route(src="A",dest="B"))
r=routing("02_blockages_test_{0}".format(OPTS.tech_name))
self.local_drc_check(r)

View File

@ -36,9 +36,9 @@ class same_layer_pins_test(openram_test):
offset=[0,0])
self.connect_inst([])
r=router(gds_file)
layer_stack =("metal1","via1","metal2")
self.assertTrue(r.route(self,layer_stack,src="A",dest="B"))
r=router(layer_stack,self,gds_file)
self.assertTrue(r.route(src="A",dest="B"))
r = routing("03_same_layer_pins_test_{0}".format(OPTS.tech_name))
self.local_drc_check(r)

View File

@ -38,9 +38,9 @@ class diff_layer_pins_test(openram_test):
offset=[0,0])
self.connect_inst([])
r=router(gds_file)
layer_stack =("metal1","via1","metal2")
self.assertTrue(r.route(self,layer_stack,src="A",dest="B"))
r=router(layer_stack,self,gds_file)
self.assertTrue(r.route(src="A",dest="B"))
r = routing("04_diff_layer_pins_test_{0}".format(OPTS.tech_name))
self.local_drc_check(r)

View File

@ -38,10 +38,10 @@ class two_nets_test(openram_test):
offset=[0,0])
self.connect_inst([])
r=router(gds_file)
layer_stack =("metal1","via1","metal2")
self.assertTrue(r.route(self,layer_stack,src="A",dest="B"))
self.assertTrue(r.route(self,layer_stack,src="C",dest="D"))
r=router(layer_stack,self,gds_file)
self.assertTrue(r.route(src="A",dest="B"))
self.assertTrue(r.route(src="C",dest="D"))
r = routing("05_two_nets_test_{0}".format(OPTS.tech_name))
self.local_drc_check(r)

View File

@ -37,13 +37,13 @@ class pin_location_test(openram_test):
offset=[0,0])
self.connect_inst([])
r=router(gds_file)
layer_stack =("metal1","via1","metal2")
r=router(layer_stack,self,gds_file)
# these are user coordinates and layers
src_pin = [[0.52, 4.099],11]
tgt_pin = [[3.533, 1.087],11]
#r.route(layer_stack,src="A",dest="B")
self.assertTrue(r.route(self,layer_stack,src=src_pin,dest=tgt_pin))
self.assertTrue(r.route(src=src_pin,dest=tgt_pin))
# This only works for freepdk45 since the coordinates are hard coded
if OPTS.tech_name == "freepdk45":

View File

@ -37,8 +37,8 @@ class big_test(openram_test):
offset=[0,0])
self.connect_inst([])
r=router(gds_file)
layer_stack =("metal1","via1","metal2")
r=router(layer_stack,self,gds_file)
connections=[('out_0_2', 'a_0_0'),
('out_0_3', 'b_0_0'),
('out_0_0', 'a_0_1'),
@ -61,7 +61,7 @@ class big_test(openram_test):
('out_4_1', 'a_4_3'),
('out_4_5', 'b_4_3')]
for (src,tgt) in connections:
self.assertTrue(r.route(self,layer_stack,src=src,dest=tgt))
self.assertTrue(r.route(src=src,dest=tgt))
# This test only runs on scn3me_subm tech
if OPTS.tech_name=="scn3me_subm":

View File

@ -37,12 +37,12 @@ class expand_region_test(openram_test):
offset=[0,0])
self.connect_inst([])
r=router(gds_file)
layer_stack =("metal1","via1","metal2")
r=router(layer_stack,self,gds_file)
# This should be infeasible because it is blocked without a detour.
self.assertFalse(r.route(self,layer_stack,src="A",dest="B",detour_scale=1))
self.assertFalse(r.route(src="A",dest="B",detour_scale=1))
# This should be feasible because we allow it to detour
self.assertTrue(r.route(self,layer_stack,src="A",dest="B",detour_scale=3))
self.assertTrue(r.route(src="A",dest="B",detour_scale=3))
r = routing("08_expand_region_test_{0}".format(OPTS.tech_name))
self.local_drc_check(r)

View File

@ -17,36 +17,26 @@ class no_blockages_test(openram_test):
def runTest(self):
globals.init_openram("config_{0}".format(OPTS.tech_name))
from gds_cell import gds_cell
from design import design
from supply_router import supply_router as router
class routing(design, openram_test):
"""
A generic GDS design that we can route on.
"""
def __init__(self, name):
design.__init__(self, "top")
if False:
from control_logic import control_logic
cell = control_logic(16)
else:
from sram import sram
from sram_config import sram_config
c = sram_config(word_size=4,
num_words=32,
num_banks=1)
c.words_per_row=1
sram = sram(c, "sram1")
cell = sram.s
# Instantiate a GDS cell with the design
globals.setup_paths()
from control_logic import control_logic
cell = control_logic(16)
#from pinv import pinv
#cell = pinv()
#gds_file = "{0}/{1}.gds".format(os.path.dirname(os.path.realpath(__file__)),"control_logic")
#cell = gds_cell(name, gds_file)
self.add_inst(name=name,
mod=cell,
offset=[0,0])
self.connect_inst(cell.pin_map.keys())
r=router(module=cell)
layer_stack =("metal3","via2","metal2")
self.assertTrue(r.route(self,layer_stack))
r=routing("10_supply_grid_test_{0}".format(OPTS.tech_name))
self.local_drc_check(r)
layer_stack =("metal3","via3","metal4")
rtr=router(layer_stack, cell)
self.assertTrue(rtr.route())
self.local_check(cell,True)
# fails if there are any DRC errors on any cells
globals.end_openram()

View File

@ -1,7 +1,7 @@
word_size = 1
num_words = 16
tech_name = "scn3me_subm"
tech_name = "scn4m_subm"
process_corners = ["TT"]
supply_voltages = [5.0]
temperatures = [25]

View File

@ -4,7 +4,6 @@ import re
import unittest
import sys,os
sys.path.append(os.path.join(sys.path[0],"../../compiler"))
print(sys.path)
import globals
(OPTS, args) = globals.parse_args()

View File

@ -15,20 +15,20 @@ class vector3d():
self.x = x[0]
self.y = x[1]
self.z = x[2]
#will take two inputs as the values of a coordinate
#will take inputs as the values of a coordinate
else:
self.x = x
self.y = y
self.z = z
self.tpl=(x,y,z)
def __str__(self):
""" override print function output """
return "vector3d:["+str(self.x)+", "+str(self.y)+", "+str(self.z)+"]"
return "v3d["+str(self.x)+", "+str(self.y)+", "+str(self.z)+"]"
def __repr__(self):
""" override print function output """
return "["+str(self.x)+", "+str(self.y)+", "+str(self.z)+"]"
return "v3d["+str(self.x)+", "+str(self.y)+", "+str(self.z)+"]"
def __setitem__(self, index, value):
"""
@ -89,7 +89,7 @@ class vector3d():
Note: This assumes that you DON'T CHANGE THE VECTOR or it will
break things.
"""
return hash(self.tpl)
return hash((self.x,self.y,self.z))
def __rsub__(self, other):
@ -118,12 +118,39 @@ class vector3d():
x_factor=x_factor[0]
return vector3d(self.y*x_factor,self.x*y_factor,self.z*z_factor)
def floor(self):
"""
Override floor function
"""
return vector3d(int(math.floor(self.x)),int(math.floor(self.y)), self.z)
def ceil(self):
"""
Override ceil function
"""
return vector3d(int(math.ceil(self.x)),int(math.ceil(self.y)), self.z)
def round(self):
"""
Override round function
"""
return vector3d(int(round(self.x)),int(round(self.y)), self.z)
def __eq__(self, other):
"""Override the default Equals behavior"""
if isinstance(other, self.__class__):
return self.__dict__ == other.__dict__
return self.x==other.x and self.y==other.y and self.z==other.z
return False
def __lt__(self, other):
"""Override the default less than behavior"""
if isinstance(other, self.__class__):
if self.x<other.x:
return True
if self.x==other.x and self.y<other.y:
return True
return False
def __ne__(self, other):
"""Override the default non-equality behavior"""
return not self.__eq__(other)

View File

@ -61,6 +61,21 @@ class sram():
def save(self):
""" Save all the output files while reporting time to do it as well. """
if not OPTS.netlist_only:
# Write the layout
start_time = datetime.datetime.now()
gdsname = OPTS.output_path + self.s.name + ".gds"
print("GDS: Writing to {0}".format(gdsname))
self.s.gds_write(gdsname)
print_time("GDS", datetime.datetime.now(), start_time)
# Create a LEF physical model
start_time = datetime.datetime.now()
lefname = OPTS.output_path + self.s.name + ".lef"
print("LEF: Writing to {0}".format(lefname))
self.s.lef_write(lefname)
print_time("LEF", datetime.datetime.now(), start_time)
# Save the spice file
start_time = datetime.datetime.now()
spname = OPTS.output_path + self.s.name + ".sp"
@ -70,6 +85,7 @@ class sram():
# Save the extracted spice file
if OPTS.use_pex:
import verify
start_time = datetime.datetime.now()
# Output the extracted design if requested
sp_file = OPTS.output_path + "temp_pex.sp"
@ -92,21 +108,6 @@ class sram():
print("Trimming netlist to speed up characterization.")
lib(out_dir=OPTS.output_path, sram=self.s, sp_file=sp_file)
print_time("Characterization", datetime.datetime.now(), start_time)
if not OPTS.netlist_only:
# Write the layout
start_time = datetime.datetime.now()
gdsname = OPTS.output_path + self.s.name + ".gds"
print("GDS: Writing to {0}".format(gdsname))
self.s.gds_write(gdsname)
print_time("GDS", datetime.datetime.now(), start_time)
# Create a LEF physical model
start_time = datetime.datetime.now()
lefname = OPTS.output_path + self.s.name + ".lef"
print("LEF: Writing to {0}".format(lefname))
self.s.lef_write(lefname)
print_time("LEF", datetime.datetime.now(), start_time)
# Write the datasheet
start_time = datetime.datetime.now()

View File

@ -117,8 +117,6 @@ class sram_1bank(sram_base):
self.add_layout_pins()
self.route_vdd_gnd()
self.route_clk()
self.route_control_logic()
@ -171,103 +169,6 @@ class sram_1bank(sram_base):
# the control logic to the bank
self.add_wire(("metal3","via2","metal2"),[row_addr_clk_pos, mid1_pos, mid2_pos, control_clk_buf_pos])
def route_vdd_gnd(self):
""" Propagate all vdd/gnd pins up to this level for all modules """
# These are the instances that every bank has
top_instances = [self.bank_inst]
for port in range(self.total_write):
top_instances.append(self.data_dff_inst[port])
for port in range(self.total_ports):
top_instances.append(self.row_addr_dff_inst[port])
top_instances.append(self.control_logic_inst[port])
if self.col_addr_dff:
top_instances.append(self.col_addr_dff_inst[port])
for inst in top_instances:
self.copy_layout_pin(inst, "vdd")
self.copy_layout_pin(inst, "gnd")
def new_route_vdd_gnd(self):
""" Propagate all vdd/gnd pins up to this level for all modules """
# These are the instances that every bank has
top_instances = [self.bank_inst]
for port in range(self.total_write):
top_instances.append(self.data_dff_inst[port])
for port in range(self.total_ports):
top_instances.append(self.row_addr_dff_inst[port])
top_instances.append(self.control_logic_inst[port])
if self.col_addr_dff:
top_instances.append(self.col_addr_dff_inst[port])
# for inst in top_instances:
# self.copy_layout_pin(inst, "vdd")
# self.copy_layout_pin(inst, "gnd")
blockages=self.get_blockages("metal3", top_level=True)
# Gather all of the vdd/gnd pins
vdd_pins=[]
gnd_pins=[]
for inst in top_instances:
vdd_pins.extend([x for x in inst.get_pins("vdd") if x.layer == "metal3"])
gnd_pins.extend([x for x in inst.get_pins("gnd") if x.layer == "metal3"])
# Create candidate stripes on M3/M4
lowest=self.find_lowest_coords()
highest=self.find_highest_coords()
m3_y_coords = np.arange(lowest[1],highest[1],self.m2_pitch)
# These are the rails that will be available for vdd/gnd
m3_rects = []
# These are the "inflated" shapes for DRC checks
m3_drc_rects = []
for y in m3_y_coords:
# This is just what metal will be drawn
ll = vector(lowest[0], y - 0.5*self.m3_width)
ur = vector(highest[0], y + 0.5*self.m3_width)
m3_rects.append([ll, ur])
# This is a full m3 pitch for DRC conflict checking
ll = vector(lowest[0], y - 0.5*self.m3_pitch )
ur = vector(highest[0], y + 0.5*self.m3_pitch)
m3_drc_rects.append([ll, ur])
vdd_rects = []
gnd_rects = []
# Now, figure how if the rails intersect a blockage, vdd, or gnd pin
# Divide the rails up alternately
# This should be done in less than n^2 using a kd-tree or something
# for drc_rect,rect in zip(m3_drc_rects,m3_rects):
# for b in blockages:
# if rect_overlaps(b,drc_rect):
# break
# else:
# gnd_rects.append(rect)
# Create the vdd and gnd rails
for rect in m3_rects:
(ll,ur) = rect
for rect in gnd_rects:
(ll,ur) = rect
self.add_layout_pin(text="gnd",
layer="metal3",
offset=ll,
width=ur.x-ll.x,
height=ur.y-ll.y)
for rect in vdd_rects:
(ll,ur) = rect
self.add_layout_pin(text="vdd",
layer="metal3",
offset=ll,
width=ur.x-ll.x,
height=ur.y-ll.y)
def route_control_logic(self):
""" Route the outputs from the control logic module """

View File

@ -7,7 +7,7 @@ from vector import vector
from globals import OPTS, print_time
from design import design
class sram_base(design):
"""
Dynamically generated SRAM by connecting banks to control logic. The
@ -77,17 +77,37 @@ class sram_base(design):
""" Layout creation """
self.place_modules()
self.route()
self.add_lvs_correspondence_points()
self.offset_all_coordinates()
# Must be done after offsetting lower-left
self.route_supplies()
highest_coord = self.find_highest_coords()
self.width = highest_coord[0]
self.height = highest_coord[1]
self.DRC_LVS(final_verification=True)
def route_supplies(self):
""" Route the supply grid and connect the pins to them. """
for inst in self.insts:
self.copy_power_pins(inst,"vdd")
self.copy_power_pins(inst,"gnd")
from supply_router import supply_router as router
layer_stack =("metal3","via3","metal4")
rtr=router(layer_stack, self)
rtr.route()
def compute_bus_sizes(self):
""" Compute the independent bus widths shared between two and four bank SRAMs """
@ -176,34 +196,6 @@ class sram_base(design):
length=self.control_bus_width))
def route_vdd_gnd(self):
""" Propagate all vdd/gnd pins up to this level for all modules """
# These are the instances that every bank has
top_instances = [self.bitcell_array_inst,
self.precharge_array_inst,
self.sense_amp_array_inst,
self.write_driver_array_inst,
self.tri_gate_array_inst,
self.row_decoder_inst,
self.wordline_driver_inst]
# Add these if we use the part...
if self.col_addr_size > 0:
top_instances.append(self.col_decoder_inst)
top_instances.append(self.col_mux_array_inst)
if self.num_banks > 1:
top_instances.append(self.bank_select_inst)
for inst in top_instances:
# Column mux has no vdd
if self.col_addr_size==0 or (self.col_addr_size>0 and inst != self.col_mux_array_inst):
self.copy_layout_pin(inst, "vdd")
# Precharge has no gnd
if inst != self.precharge_array_inst:
self.copy_layout_pin(inst, "gnd")
def add_multi_bank_modules(self):
""" Create the multibank address flops and bank decoder """
@ -459,8 +451,8 @@ class sram_base(design):
sp.close()
def analytical_delay(self,slew,load):
def analytical_delay(self, vdd, slew,load):
""" LH and HL are the same in analytical model. """
return self.bank.analytical_delay(slew,load)
return self.bank.analytical_delay(vdd,slew,load)

View File

@ -53,7 +53,7 @@ class sram_config:
# Estimate the number of rows given the tentative words per row
self.tentative_num_rows = self.num_bits_per_bank / (self.words_per_row*self.word_size)
self.words_per_row = self.amend_words_per_row(self.tentative_num_rows, self.words_per_row)
# Fix the number of columns and rows
self.num_cols = int(self.words_per_row*self.word_size)
self.num_rows = int(self.num_words_per_bank/self.words_per_row)

View File

@ -31,12 +31,12 @@ class code_format_test(openram_test):
continue
if re.search("testutils.py$", code):
continue
if re.search("grid.py$", code):
continue
if re.search("globals.py$", code):
continue
if re.search("openram.py$", code):
continue
if re.search("sram.py$", code):
continue
if re.search("gen_stimulus.py$", code):
continue
errors += check_print_output(code)
@ -52,7 +52,7 @@ def setup_files(path):
for f in current_files:
files.append(os.path.join(dir, f))
nametest = re.compile("\.py$", re.IGNORECASE)
select_files = filter(nametest.search, files)
select_files = list(filter(nametest.search, files))
return select_files
@ -102,16 +102,17 @@ def check_print_output(file_name):
"""Check if any files (except debug.py) call the _print_ function. We should
use the debug output with verbosity instead!"""
file = open(file_name, "r+b")
line = file.read()
line = file.read().decode('utf-8')
# skip comments with a hash
line = re.sub(r'#.*', '', line)
# skip doc string comments
line=re.sub(r'\"\"\"[^\"]*\"\"\"', '', line, flags=re.S|re.M)
count = len(re.findall("\s*print\s+", line))
count = len(re.findall("[^p]+print\(", line))
if count > 0:
debug.info(0, "\nFound " + str(count) +
" _print_ calls " + str(file_name))
file.close()
return(count)

View File

@ -0,0 +1,42 @@
#!/usr/bin/env python3
"""
Run regresion tests on a parameterized bitcell
"""
import unittest
from testutils import header,openram_test
import sys,os
sys.path.append(os.path.join(sys.path[0],".."))
import globals
from globals import OPTS
import debug
OPTS = globals.OPTS
@unittest.skip("SKIPPING 04_bitcell_1rw_1r_test")
class bitcell_1rw_1r_test(openram_test):
def runTest(self):
OPTS.bitcell = "bitcell_1rw_1r"
globals.init_openram("config_20_{0}".format(OPTS.tech_name))
from bitcell import bitcell
from bitcell_1rw_1r import bitcell_1rw_1r
import tech
OPTS.num_rw_ports=1
OPTS.num_w_ports=0
OPTS.num_r_ports=1
debug.info(2, "Bitcell with 1 read/write and 1 read port")
#tx = bitcell_1rw_1r()
tx = bitcell()
self.local_check(tx)
globals.end_openram()
# instantiate a copy of the class to actually run the test
if __name__ == "__main__":
(OPTS, args) = globals.parse_args()
del sys.argv[1:]
header(__file__, OPTS.tech_name)
unittest.main()

0
compiler/tests/04_replica_pbitcell_test.py Normal file → Executable file
View File

0
compiler/tests/06_hierarchical_decoder_test.py Normal file → Executable file
View File

0
compiler/tests/06_hierarchical_predecode2x4_test.py Normal file → Executable file
View File

0
compiler/tests/06_hierarchical_predecode3x8_test.py Normal file → Executable file
View File

0
compiler/tests/14_replica_bitline_test.py Normal file → Executable file
View File

0
compiler/tests/16_control_logic_test.py Normal file → Executable file
View File

0
compiler/tests/19_bank_select_test.py Normal file → Executable file
View File

Some files were not shown because too many files have changed in this diff Show More