Merged and addressed conflict in delay.py

This commit is contained in:
Hunter Nichols 2021-01-06 01:37:16 -08:00
commit cd84cf1973
86 changed files with 916 additions and 17029 deletions

View File

@ -79,25 +79,6 @@ You may get the entire [FreePDK45 PDK here][FreePDK45].
If you are using [SCMOS], you should install [Magic] and [Netgen]. If you are using [SCMOS], you should install [Magic] and [Netgen].
We have included the most recent SCN4M_SUBM design rules from [Qflow]. We have included the most recent SCN4M_SUBM design rules from [Qflow].
## Docker Image
**WARNING! Some OpenRAM dependency tools installed in the Docker image are out-of-date.**
We have a pre-configured Ubuntu [Docker](https://www.docker.com/) image
available that has all tools installed for the [SCMOS] process. It is
available at [docker hub](https://hub.docker.com/r/vlsida/openram-ubuntu).
Please see
[our README.md](https://github.com/VLSIDA/openram-docker-images/blob/master/README.md)
for information on how to use this docker image.
## Vagrant Image
We have a pre-configured Ubuntu [Vagrant](https://www.vagrantup.com/) image
available that has all tools installed for the [SCMOS] process.
Please see
[our README.md](https://github.com/VLSIDA/openram-vagrant-image/blob/master/README.md)
for information on how to use this image.
# Basic Usage # Basic Usage
Once you have defined the environment, you can run OpenRAM from the command line Once you have defined the environment, you can run OpenRAM from the command line
@ -142,7 +123,6 @@ python3 $OPENRAM_HOME/openram.py myconfig
You can see all of the options for the configuration file in You can see all of the options for the configuration file in
$OPENRAM\_HOME/options.py $OPENRAM\_HOME/options.py
# Unit Tests # Unit Tests
Regression testing performs a number of tests for all modules in OpenRAM. Regression testing performs a number of tests for all modules in OpenRAM.

View File

@ -446,7 +446,7 @@ class path(geometry):
class label(geometry): class label(geometry):
"""Represents a text label""" """Represents a text label"""
def __init__(self, text, lpp, offset, zoom=-1): def __init__(self, text, lpp, offset, zoom=None):
"""Initializes a text label for specified layer""" """Initializes a text label for specified layer"""
super().__init__() super().__init__()
self.name = "label" self.name = "label"
@ -455,8 +455,11 @@ class label(geometry):
self.layerPurpose = lpp[1] self.layerPurpose = lpp[1]
self.offset = vector(offset).snap_to_grid() self.offset = vector(offset).snap_to_grid()
if zoom<0: if not zoom:
self.zoom = tech.GDS["zoom"] try:
self.zoom = tech.GDS["zoom"]
except:
self.zoom = None
else: else:
self.zoom = zoom self.zoom = zoom

View File

@ -52,10 +52,10 @@ class hierarchy_design(hierarchy_spice.spice, hierarchy_layout.layout):
# Do not run if disabled in options. # Do not run if disabled in options.
elif (OPTS.inline_lvsdrc or force_check or final_verification): elif (OPTS.inline_lvsdrc or force_check or final_verification):
tempspice = "{0}/{1}.sp".format(OPTS.openram_temp, self.name) tempspice = "{}.sp".format(self.name)
self.lvs_write(tempspice) self.lvs_write("{0}{1}".format(OPTS.openram_temp, tempspice))
tempgds = "{0}/{1}.gds".format(OPTS.openram_temp, self.name) tempgds = "{}.gds".format(self.name)
self.gds_write(tempgds) self.gds_write("{0}{1}".format(OPTS.openram_temp, tempgds))
# Final verification option does not allow nets to be connected by label. # Final verification option does not allow nets to be connected by label.
self.drc_errors = verify.run_drc(self.cell_name, tempgds, tempspice, extract=True, final_verification=final_verification) self.drc_errors = verify.run_drc(self.cell_name, tempgds, tempspice, extract=True, final_verification=final_verification)
self.lvs_errors = verify.run_lvs(self.cell_name, tempgds, tempspice, final_verification=final_verification) self.lvs_errors = verify.run_lvs(self.cell_name, tempgds, tempspice, final_verification=final_verification)
@ -81,10 +81,10 @@ class hierarchy_design(hierarchy_spice.spice, hierarchy_layout.layout):
if OPTS.netlist_only: if OPTS.netlist_only:
return return
elif (not OPTS.is_unit_test and OPTS.check_lvsdrc and (OPTS.inline_lvsdrc or final_verification)): elif (not OPTS.is_unit_test and OPTS.check_lvsdrc and (OPTS.inline_lvsdrc or final_verification)):
tempspice = "{0}{1}.sp".format(OPTS.openram_temp, self.name) tempspice = "{}.sp".format(self.name)
self.lvs_write(tempspice) self.lvs_write("{0}{1}".format(OPTS.openram_temp, tempspice))
tempgds = "{0}{1}.gds".format(OPTS.openram_temp, self.cell_name) tempgds = "{}.gds".format(self.cell_name)
self.gds_write(tempgds) self.gds_write("{0}{1}".format(OPTS.openram_temp, tempgds))
num_errors = verify.run_drc(self.cell_name, tempgds, tempspice, final_verification=final_verification) num_errors = verify.run_drc(self.cell_name, tempgds, tempspice, final_verification=final_verification)
debug.check(num_errors == 0, debug.check(num_errors == 0,
"DRC failed for {0} with {1} error(s)".format(self.cell_name, "DRC failed for {0} with {1} error(s)".format(self.cell_name,
@ -101,10 +101,10 @@ class hierarchy_design(hierarchy_spice.spice, hierarchy_layout.layout):
if OPTS.netlist_only: if OPTS.netlist_only:
return return
elif (not OPTS.is_unit_test and OPTS.check_lvsdrc and (OPTS.inline_lvsdrc or final_verification)): elif (not OPTS.is_unit_test and OPTS.check_lvsdrc and (OPTS.inline_lvsdrc or final_verification)):
tempspice = "{0}{1}.sp".format(OPTS.openram_temp, self.cell_name) tempspice = "{}.sp".format(self.cell_name)
self.lvs_write(tempspice) self.lvs_write("{0}{1}".format(OPTS.openram_temp, tempspice))
tempgds = "{0}{1}.gds".format(OPTS.openram_temp, self.name) tempgds = "{}.gds".format(self.name)
self.gds_write(tempgds) self.gds_write("{0}{1}".format(OPTS.openram_temp, tempgds))
num_errors = verify.run_lvs(self.name, tempgds, tempspice, final_verification=final_verification) num_errors = verify.run_lvs(self.name, tempgds, tempspice, final_verification=final_verification)
debug.check(num_errors == 0, debug.check(num_errors == 0,
"LVS failed for {0} with {1} error(s)".format(self.cell_name, "LVS failed for {0} with {1} error(s)".format(self.cell_name,

View File

@ -244,6 +244,19 @@ class layout():
return inst return inst
return None return None
def add_flat_inst(self, name, mod, offset=[0, 0]):
""" Copies all of the items in instance into this module """
for item in mod.objs:
item.offset += offset
self.objs.append(item)
for item in mod.insts:
item.offset += offset
self.insts.append(item)
debug.check(len(item.mod.pins) == 0, "Cannot add flat instance with subinstances.")
self.connect_inst([])
debug.info(3, "adding flat instance {}".format(name))
return None
def add_rect(self, layer, offset, width=None, height=None): def add_rect(self, layer, offset, width=None, height=None):
""" """
Adds a rectangle on a given layer,offset with width and height Adds a rectangle on a given layer,offset with width and height
@ -454,6 +467,23 @@ class layout():
""" """
self.pin_map[text] = set() self.pin_map[text] = set()
def remove_layout_pins(self):
"""
Delete all the layout pins
"""
self.pin_map = {}
def replace_layout_pin(self, text, pin):
"""
Remove the old pin and replace with a new one
"""
self.remove_layout_pin(text)
self.add_layout_pin(text=text,
layer=pin.layer,
offset=pin.ll(),
width=pin.width(),
height=pin.height())
def add_layout_pin(self, text, layer, offset, width=None, height=None): def add_layout_pin(self, text, layer, offset, width=None, height=None):
""" """
Create a labeled pin Create a labeled pin
@ -498,7 +528,7 @@ class layout():
offset=offset + vector(0.5 * width, offset=offset + vector(0.5 * width,
0.5 * height)) 0.5 * height))
def add_label(self, text, layer, offset=[0, 0], zoom=-1): def add_label(self, text, layer, offset=[0, 0], zoom=None):
"""Adds a text label on the given layer,offset, and zoom level""" """Adds a text label on the given layer,offset, and zoom level"""
debug.info(5, "add label " + str(text) + " " + layer + " " + str(offset)) debug.info(5, "add label " + str(text) + " " + layer + " " + str(offset))
lpp = techlayer[layer] lpp = techlayer[layer]
@ -976,7 +1006,7 @@ class layout():
(horizontal_layer, via_layer, vertical_layer) = layer_stack (horizontal_layer, via_layer, vertical_layer) = layer_stack
if horizontal: if horizontal:
route_layer = vertical_layer route_layer = vertical_layer
bys_layer = horizontal_layer bus_layer = horizontal_layer
else: else:
route_layer = horizontal_layer route_layer = horizontal_layer
bus_layer = vertical_layer bus_layer = vertical_layer
@ -1078,8 +1108,11 @@ class layout():
""" """
import channel_route import channel_route
cr = channel_route.channel_route(netlist, offset, layer_stack, directions, vertical=True, parent=self) cr = channel_route.channel_route(netlist, offset, layer_stack, directions, vertical=True, parent=self)
self.add_inst(cr.name, cr) # This causes problem in magic since it sometimes cannot extract connectivity of isntances
self.connect_inst([]) # with no active devices.
# self.add_inst(cr.name, cr)
# self.connect_inst([])
self.add_flat_inst(cr.name, cr)
def create_horizontal_channel_route(self, netlist, offset, layer_stack, directions=None): def create_horizontal_channel_route(self, netlist, offset, layer_stack, directions=None):
""" """
@ -1087,8 +1120,11 @@ class layout():
""" """
import channel_route import channel_route
cr = channel_route.channel_route(netlist, offset, layer_stack, directions, vertical=False, parent=self) cr = channel_route.channel_route(netlist, offset, layer_stack, directions, vertical=False, parent=self)
self.add_inst(cr.name, cr) # This causes problem in magic since it sometimes cannot extract connectivity of isntances
self.connect_inst([]) # with no active devices.
# self.add_inst(cr.name, cr)
# self.connect_inst([])
self.add_flat_inst(cr.name, cr)
def add_boundary(self, ll=vector(0, 0), ur=None): def add_boundary(self, ll=vector(0, 0), ur=None):
""" Add boundary for debugging dimensions """ """ Add boundary for debugging dimensions """
@ -1172,6 +1208,21 @@ class layout():
elif add_vias: elif add_vias:
self.add_power_pin(name, pin.center(), start_layer=pin.layer) self.add_power_pin(name, pin.center(), start_layer=pin.layer)
def add_io_pin(self, instance, pin_name, new_name="", start_layer=None):
"""
Add a signle input or output pin up to metal 3.
"""
pin = instance.get_pin(pin_name)
if new_name == "":
new_name = pin_name
if not start_layer:
start_layer = pin.layer
# Just use the power pin function for now to save code
self.add_power_pin(name=new_name, loc=pin.center(), start_layer=start_layer)
def add_power_pin(self, name, loc, size=[1, 1], directions=None, start_layer="m1"): def add_power_pin(self, name, loc, size=[1, 1], directions=None, start_layer="m1"):
""" """
Add a single power pin from the lowest power_grid layer down to M1 (or li) at Add a single power pin from the lowest power_grid layer down to M1 (or li) at

View File

@ -380,20 +380,26 @@ class pin_layout:
label_purpose = purpose label_purpose = purpose
newLayout.addBox(layerNumber=layer_num, newLayout.addBox(layerNumber=layer_num,
purposeNumber=pin_purpose, purposeNumber=purpose,
offsetInMicrons=self.ll(), offsetInMicrons=self.ll(),
width=self.width(), width=self.width(),
height=self.height(), height=self.height(),
center=False) center=False)
# Add the tet in the middle of the pin. # Draw a second pin shape too
if pin_purpose != purpose:
newLayout.addBox(layerNumber=layer_num,
purposeNumber=pin_purpose,
offsetInMicrons=self.ll(),
width=self.width(),
height=self.height(),
center=False)
# Add the text in the middle of the pin.
# This fixes some pin label offsetting when GDS gets # This fixes some pin label offsetting when GDS gets
# imported into Magic. # imported into Magic.
newLayout.addText(text=self.name, newLayout.addText(text=self.name,
layerNumber=layer_num, layerNumber=layer_num,
purposeNumber=label_purpose, purposeNumber=label_purpose,
offsetInMicrons=self.center(), offsetInMicrons=self.center())
magnification=GDS["zoom"],
rotate=None)
def compute_overlap(self, other): def compute_overlap(self, other):
""" Calculate the rectangular overlap of two rectangles. """ """ Calculate the rectangular overlap of two rectangles. """

View File

@ -0,0 +1,22 @@
# See LICENSE for licensing information.
#
# Copyright (c) 2016-2019 Regents of the University of California and The Board
# of Regents for the Oklahoma Agricultural and Mechanical College
# (acting for and on behalf of Oklahoma State University)
# All rights reserved.
#
import debug
from tech import cell_properties as props
import bitcell_base
class col_cap_bitcell_1port(bitcell_base.bitcell_base):
"""
Column end cap cell.
"""
def __init__(self, name="col_cap_bitcell_1port"):
bitcell_base.bitcell_base.__init__(self, name, prop=props.col_cap_1port)
debug.info(2, "Create col_cap bitcell 1 port object")
self.no_instances = True

View File

@ -0,0 +1,22 @@
# See LICENSE for licensing information.
#
# Copyright (c) 2016-2019 Regents of the University of California and The Board
# of Regents for the Oklahoma Agricultural and Mechanical College
# (acting for and on behalf of Oklahoma State University)
# All rights reserved.
#
import debug
from tech import cell_properties as props
import bitcell_base
class row_cap_bitcell_1port(bitcell_base.bitcell_base):
"""
Row end cap cell.
"""
def __init__(self, name="row_cap_bitcell_1port"):
bitcell_base.bitcell_base.__init__(self, name, prop=props.row_cap_1port)
debug.info(2, "Create row_cap bitcell 1 port object")
self.no_instances = True

View File

@ -11,7 +11,7 @@ nominal_corner_only = True
route_supplies = True route_supplies = True
check_lvsdrc = True check_lvsdrc = True
perimeter_pins = True perimeter_pins = False
#netlist_only = True #netlist_only = True
#analytical_delay = False #analytical_delay = False
output_name = "sram_{0}rw{1}r{2}w_{3}_{4}_{5}".format(num_rw_ports, output_name = "sram_{0}rw{1}r{2}w_{3}_{4}_{5}".format(num_rw_ports,

View File

@ -11,8 +11,8 @@ num_w_ports = 0
tech_name = "scn4m_subm" tech_name = "scn4m_subm"
nominal_corner_only = True nominal_corner_only = True
route_supplies = False route_supplies = "tree"
check_lvsdrc = False check_lvsdrc = True
perimeter_pins = False perimeter_pins = False
#netlist_only = True #netlist_only = True
#analytical_delay = False #analytical_delay = False

View File

@ -1,4 +1,4 @@
import math
class GdsStructure: class GdsStructure:
"""Class represent a GDS Structure Object""" """Class represent a GDS Structure Object"""
@ -15,6 +15,7 @@ class GdsStructure:
self.nodes=[] self.nodes=[]
self.boxes=[] self.boxes=[]
class GdsBoundary: class GdsBoundary:
"""Class represent a GDS Boundary Object""" """Class represent a GDS Boundary Object"""
def __init__(self): def __init__(self):
@ -24,6 +25,7 @@ class GdsBoundary:
self.purposeLayer=0 self.purposeLayer=0
self.coordinates="" self.coordinates=""
class GdsPath: class GdsPath:
"""Class represent a GDS Path Object""" """Class represent a GDS Path Object"""
def __init__(self): def __init__(self):
@ -112,6 +114,7 @@ class GdsPath:
lastY = y lastY = y
return boundaryEquivalent return boundaryEquivalent
class GdsSref: class GdsSref:
"""Class represent a GDS structure reference Object""" """Class represent a GDS structure reference Object"""
def __init__(self): def __init__(self):
@ -123,6 +126,7 @@ class GdsSref:
self.rotateAngle="" self.rotateAngle=""
self.coordinates="" self.coordinates=""
class GdsAref: class GdsAref:
"""Class represent a GDS array reference Object""" """Class represent a GDS array reference Object"""
def __init__(self): def __init__(self):
@ -134,6 +138,7 @@ class GdsAref:
self.rotateAngle="" self.rotateAngle=""
self.coordinates="" self.coordinates=""
class GdsText: class GdsText:
"""Class represent a GDS text Object""" """Class represent a GDS text Object"""
def __init__(self): def __init__(self):
@ -150,6 +155,7 @@ class GdsText:
self.coordinates="" self.coordinates=""
self.textString = "" self.textString = ""
class GdsNode: class GdsNode:
"""Class represent a GDS Node Object""" """Class represent a GDS Node Object"""
def __init__(self): def __init__(self):
@ -159,6 +165,7 @@ class GdsNode:
self.nodeType="" self.nodeType=""
self.coordinates="" self.coordinates=""
class GdsBox: class GdsBox:
"""Class represent a GDS Box Object""" """Class represent a GDS Box Object"""
def __init__(self): def __init__(self):

View File

@ -1,26 +1,25 @@
from .gdsPrimitives import * from .gdsPrimitives import *
from datetime import * from datetime import *
#from mpmath import matrix
#from numpy import matrix
import numpy as np import numpy as np
#import gdsPrimitives import math
import debug import debug
class VlsiLayout: class VlsiLayout:
"""Class represent a hierarchical layout""" """Class represent a hierarchical layout"""
def __init__(self, name=None, units=(0.001,1e-9), libraryName = "DEFAULT.DB", gdsVersion=5): def __init__(self, name=None, units=(0.001,1e-9), libraryName="DEFAULT.DB", gdsVersion=5):
#keep a list of all the structures in this layout # keep a list of all the structures in this layout
self.units = units self.units = units
#print(units) # print(units)
modDate = datetime.now() modDate = datetime.now()
self.structures=dict() self.structures=dict()
self.layerNumbersInUse = [] self.layerNumbersInUse = []
self.debug = False self.debug = False
if name: if name:
#take the root structure and copy it to a new structure with the new name # take the root structure and copy it to a new structure with the new name
self.rootStructureName=self.padText(name) self.rootStructureName=self.padText(name)
#create the ROOT structure # create the ROOT structure
self.structures[self.rootStructureName] = GdsStructure() self.structures[self.rootStructureName] = GdsStructure()
self.structures[self.rootStructureName].name = name self.structures[self.rootStructureName].name = name
self.structures[self.rootStructureName].createDate = (modDate.year, self.structures[self.rootStructureName].createDate = (modDate.year,
@ -36,7 +35,7 @@ class VlsiLayout:
modDate.minute, modDate.minute,
modDate.second) modDate.second)
self.info = dict() #information gathered from the GDSII header self.info = dict() # information gathered from the GDSII header
self.info['units']=self.units self.info['units']=self.units
self.info['dates']=(modDate.year, self.info['dates']=(modDate.year,
modDate.month, modDate.month,
@ -53,12 +52,13 @@ class VlsiLayout:
self.info['libraryName']=libraryName self.info['libraryName']=libraryName
self.info['gdsVersion']=gdsVersion self.info['gdsVersion']=gdsVersion
self.xyTree = [] #This will contain a list of all structure names # This will contain a list of all structure names
#expanded to include srefs / arefs separately. # expanded to include srefs / arefs separately.
#each structure will have an X,Y,offset, and rotate associated # each structure will have an X,Y,offset, and rotate associated
#with it. Populate via traverseTheHierarchy method. # with it. Populate via traverseTheHierarchy method.
self.xyTree = []
#temp variables used in delegate functions # temp variables used in delegate functions
self.tempCoordinates=None self.tempCoordinates=None
self.tempPassFail = True self.tempPassFail = True
@ -68,12 +68,12 @@ class VlsiLayout:
self.pins = {} self.pins = {}
def rotatedCoordinates(self,coordinatesToRotate,rotateAngle): def rotatedCoordinates(self,coordinatesToRotate,rotateAngle):
#helper method to rotate a list of coordinates # helper method to rotate a list of coordinates
angle=math.radians(float(0)) angle=math.radians(float(0))
if(rotateAngle): if(rotateAngle):
angle = math.radians(float(rotateAngle)) angle = math.radians(float(rotateAngle))
coordinatesRotate = [] #this will hold the rotated values coordinatesRotate = [] # this will hold the rotated values
for coordinate in coordinatesToRotate: for coordinate in coordinatesToRotate:
# This is the CCW rotation matrix # This is the CCW rotation matrix
newX = coordinate[0]*math.cos(angle) - coordinate[1]*math.sin(angle) newX = coordinate[0]*math.cos(angle) - coordinate[1]*math.sin(angle)
@ -82,53 +82,51 @@ class VlsiLayout:
return coordinatesRotate return coordinatesRotate
def rename(self,newName): def rename(self,newName):
#take the root structure and copy it to a new structure with the new name # take the root structure and copy it to a new structure with the new name
self.structures[newName] = self.structures[self.rootStructureName] self.structures[newName] = self.structures[self.rootStructureName]
self.structures[newName].name = self.padText(newName) self.structures[newName].name = self.padText(newName)
#and delete the old root # and delete the old root
del self.structures[self.rootStructureName] del self.structures[self.rootStructureName]
self.rootStructureName = newName self.rootStructureName = newName
#repopulate the 2d map so drawing occurs correctly # repopulate the 2d map so drawing occurs correctly
del self.xyTree[:] del self.xyTree[:]
self.populateCoordinateMap() self.populateCoordinateMap()
def newLayout(self,newName): def newLayout(self,newName):
#if (newName == "" | newName == 0): # if (newName == "" | newName == 0):
# print("ERROR: vlsiLayout.py:newLayout newName is null") # print("ERROR: vlsiLayout.py:newLayout newName is null")
#make sure the newName is a multiple of 2 characters # make sure the newName is a multiple of 2 characters
#if(len(newName)%2 == 1): # if(len(newName)%2 == 1):
#pad with a zero # pad with a zero
#newName = newName + '\x00' # newName = newName + '\x00'
#take the root structure and copy it to a new structure with the new name # take the root structure and copy it to a new structure with the new name
#self.structures[newName] = self.structures[self.rootStructureName] # self.structures[newName] = self.structures[self.rootStructureName]
modDate = datetime.now() modDate = datetime.now()
self.structures[newName] = GdsStructure() self.structures[newName] = GdsStructure()
self.structures[newName].name = newName self.structures[newName].name = newName
self.rootStructureName = newName self.rootStructureName = newName
self.rootStructureName=newName self.rootStructureName=newName
#create the ROOT structure # create the ROOT structure
self.structures[self.rootStructureName] = GdsStructure() self.structures[self.rootStructureName] = GdsStructure()
#self.structures[self.rootStructureName].name = name # self.structures[self.rootStructureName].name = name
self.structures[self.rootStructureName].createDate = (modDate.year, self.structures[self.rootStructureName].createDate = (modDate.year,
modDate.month, modDate.month,
modDate.day, modDate.day,
modDate.hour, modDate.hour,
modDate.minute, modDate.minute,
modDate.second) modDate.second)
self.structures[self.rootStructureName].modDate = (modDate.year, self.structures[self.rootStructureName].modDate = (modDate.year,
modDate.month, modDate.month,
modDate.day, modDate.day,
modDate.hour, modDate.hour,
modDate.minute, modDate.minute,
modDate.second) modDate.second)
#repopulate the 2d map so drawing occurs correctly #repopulate the 2d map so drawing occurs correctly
@ -155,47 +153,50 @@ class VlsiLayout:
debug.check(len(structureNames)==1,"Multiple possible root structures in the layout: {}".format(str(structureNames))) debug.check(len(structureNames)==1,"Multiple possible root structures in the layout: {}".format(str(structureNames)))
self.rootStructureName = structureNames[0] self.rootStructureName = structureNames[0]
def traverseTheHierarchy(self, startingStructureName=None, delegateFunction=None,
def traverseTheHierarchy(self, startingStructureName=None, delegateFunction = None, transformPath=[], rotateAngle=0, transFlags=[0, 0, 0], coordinates=(0, 0)):
transformPath = [], rotateAngle = 0, transFlags = [0,0,0], coordinates = (0,0)): # since this is a recursive function, must deal with the default
#since this is a recursive function, must deal with the default # parameters explicitly
#parameters explicitly
if startingStructureName == None: if startingStructureName == None:
startingStructureName = self.rootStructureName startingStructureName = self.rootStructureName
#set up the rotation matrix # set up the rotation matrix
if(rotateAngle == None or rotateAngle == ""): if(rotateAngle == None or rotateAngle == ""):
angle = 0 angle = 0
else: else:
# MRG: Added negative to make CCW rotate 8/29/18 # MRG: Added negative to make CCW rotate 8/29/18
angle = math.radians(float(rotateAngle)) angle = math.radians(float(rotateAngle))
mRotate = np.array([[math.cos(angle),-math.sin(angle),0.0], mRotate = np.array([[math.cos(angle), -math.sin(angle), 0.0],
[math.sin(angle),math.cos(angle),0.0], [math.sin(angle), math.cos(angle), 0.0],
[0.0,0.0,1.0]]) [0.0, 0.0, 1.0]])
#set up the translation matrix # set up the translation matrix
translateX = float(coordinates[0]) translateX = float(coordinates[0])
translateY = float(coordinates[1]) translateY = float(coordinates[1])
mTranslate = np.array([[1.0,0.0,translateX],[0.0,1.0,translateY],[0.0,0.0,1.0]]) mTranslate = np.array([[1.0, 0.0, translateX],
#set up the scale matrix (handles mirror X) [0.0, 1.0, translateY],
[0.0, 0.0, 1.0]])
# set up the scale matrix (handles mirror X)
scaleX = 1.0 scaleX = 1.0
if(transFlags[0]): if (transFlags[0]):
scaleY = -1.0 scaleY = -1.0
else: else:
scaleY = 1.0 scaleY = 1.0
mScale = np.array([[scaleX,0.0,0.0],[0.0,scaleY,0.0],[0.0,0.0,1.0]]) mScale = np.array([[scaleX, 0.0, 0.0],
#we need to keep track of all transforms in the hierarchy [0.0, scaleY, 0.0],
#when we add an element to the xy tree, we apply all transforms from the bottom up [0.0, 0.0, 1.0]])
# we need to keep track of all transforms in the hierarchy
# when we add an element to the xy tree, we apply all transforms from the bottom up
transformPath.append((mRotate,mScale,mTranslate)) transformPath.append((mRotate,mScale,mTranslate))
if delegateFunction != None: if delegateFunction != None:
delegateFunction(startingStructureName, transformPath) delegateFunction(startingStructureName, transformPath)
#starting with a particular structure, we will recursively traverse the tree # starting with a particular structure, we will recursively traverse the tree
#********might have to set the recursion level deeper for big layouts! # ********might have to set the recursion level deeper for big layouts!
try: try:
if(len(self.structures[startingStructureName].srefs)>0): #does this structure reference any others? if(len(self.structures[startingStructureName].srefs)>0): #does this structure reference any others?
#if so, go through each and call this function again # if so, go through each and call this function again
#if not, return back to the caller (caller can be this function) # if not, return back to the caller (caller can be this function)
for sref in self.structures[startingStructureName].srefs: for sref in self.structures[startingStructureName].srefs:
#here, we are going to modify the sref coordinates based on the parent objects rotation # here, we are going to modify the sref coordinates based on the parent objects rotation
self.traverseTheHierarchy(startingStructureName = sref.sName, self.traverseTheHierarchy(startingStructureName = sref.sName,
delegateFunction = delegateFunction, delegateFunction = delegateFunction,
transformPath = transformPath, transformPath = transformPath,
@ -205,8 +206,8 @@ class VlsiLayout:
except KeyError: except KeyError:
debug.error("Could not find structure {} in GDS file.".format(startingStructureName),-1) debug.error("Could not find structure {} in GDS file.".format(startingStructureName),-1)
#MUST HANDLE AREFs HERE AS WELL # MUST HANDLE AREFs HERE AS WELL
#when we return, drop the last transform from the transformPath # when we return, drop the last transform from the transformPath
del transformPath[-1] del transformPath[-1]
return return
@ -218,7 +219,6 @@ class VlsiLayout:
for layerNumber in self.layerNumbersInUse: for layerNumber in self.layerNumbersInUse:
self.processLabelPins((layerNumber, None)) self.processLabelPins((layerNumber, None))
def populateCoordinateMap(self): def populateCoordinateMap(self):
def addToXyTree(startingStructureName = None,transformPath = None): def addToXyTree(startingStructureName = None,transformPath = None):
uVector = np.array([[1.0],[0.0],[0.0]]) #start with normal basis vectors uVector = np.array([[1.0],[0.0],[0.0]]) #start with normal basis vectors
@ -281,8 +281,6 @@ class VlsiLayout:
self.newLayout(newRoot) self.newLayout(newRoot)
self.rootStructureName = newRoot self.rootStructureName = newRoot
def addInstance(self,layoutToAdd,nameOfLayout=0,offsetInMicrons=(0,0),mirror=None,rotate=None): def addInstance(self,layoutToAdd,nameOfLayout=0,offsetInMicrons=(0,0),mirror=None,rotate=None):
""" """
Method to insert one layout into another at a particular offset. Method to insert one layout into another at a particular offset.
@ -406,7 +404,7 @@ class VlsiLayout:
#add the sref to the root structure #add the sref to the root structure
self.structures[self.rootStructureName].paths.append(pathToAdd) self.structures[self.rootStructureName].paths.append(pathToAdd)
def addText(self, text, layerNumber=0, purposeNumber=0, offsetInMicrons=(0,0), magnification=0.1, rotate = None): def addText(self, text, layerNumber=0, purposeNumber=0, offsetInMicrons=(0,0), magnification=None, rotate=None):
offsetInLayoutUnits = (self.userUnits(offsetInMicrons[0]),self.userUnits(offsetInMicrons[1])) offsetInLayoutUnits = (self.userUnits(offsetInMicrons[0]),self.userUnits(offsetInMicrons[1]))
textToAdd = GdsText() textToAdd = GdsText()
textToAdd.drawingLayer = layerNumber textToAdd.drawingLayer = layerNumber
@ -415,7 +413,8 @@ class VlsiLayout:
textToAdd.transFlags = [0,0,0] textToAdd.transFlags = [0,0,0]
textToAdd.textString = self.padText(text) textToAdd.textString = self.padText(text)
#textToAdd.transFlags[1] = 1 #textToAdd.transFlags[1] = 1
textToAdd.magFactor = magnification if magnification:
textToAdd.magFactor = magnification
if rotate: if rotate:
#textToAdd.transFlags[2] = 1 #textToAdd.transFlags[2] = 1
textToAdd.rotateAngle = rotate textToAdd.rotateAngle = rotate

View File

@ -19,7 +19,7 @@ import re
import copy import copy
import importlib import importlib
VERSION = "1.1.7" VERSION = "1.1.9"
NAME = "OpenRAM v{}".format(VERSION) NAME = "OpenRAM v{}".format(VERSION)
USAGE = "openram.py [options] <config file>\nUse -h for help.\n" USAGE = "openram.py [options] <config file>\nUse -h for help.\n"
@ -295,6 +295,14 @@ def read_config(config_file, is_unit_test=True):
dir_name = os.path.dirname(config_file) dir_name = os.path.dirname(config_file)
module_name = os.path.basename(config_file) module_name = os.path.basename(config_file)
# Check that the module name adheres to Python's module naming conventions.
# This will assist the user in interpreting subsequent errors in loading
# the module. Valid Python module naming is described here:
# https://docs.python.org/3/reference/simple_stmts.html#the-import-statement
if not module_name.isidentifier():
debug.error("Configuration file name is not a valid Python module name: "
"{0}. It should be a valid identifier.".format(module_name))
# Prepend the path to avoid if we are using the example config # Prepend the path to avoid if we are using the example config
sys.path.insert(0, dir_name) sys.path.insert(0, dir_name)
# Import the configuration file of which modules to use # Import the configuration file of which modules to use
@ -574,9 +582,8 @@ def report_status():
debug.print_raw("Technology: {0}".format(OPTS.tech_name)) debug.print_raw("Technology: {0}".format(OPTS.tech_name))
total_size = OPTS.word_size*OPTS.num_words*OPTS.num_banks total_size = OPTS.word_size*OPTS.num_words*OPTS.num_banks
debug.print_raw("Total size: {} bits".format(total_size)) debug.print_raw("Total size: {} bits".format(total_size))
if total_size >= 2**14: if total_size >= 2**14 and not OPTS.analytical_delay:
debug.warning("Requesting such a large memory size ({0}) will have a large run-time. ".format(total_size) + debug.warning("Characterizing large memories ({0}) will have a large run-time. ".format(total_size))
"Consider using multiple smaller banks.")
debug.print_raw("Word size: {0}\nWords: {1}\nBanks: {2}".format(OPTS.word_size, debug.print_raw("Word size: {0}\nWords: {1}\nBanks: {2}".format(OPTS.word_size,
OPTS.num_words, OPTS.num_words,
OPTS.num_banks)) OPTS.num_banks))

View File

@ -391,19 +391,17 @@ class replica_bitcell_array(bitcell_base_array):
for bit in range(self.rbl[1]): for bit in range(self.rbl[1]):
dummy_offset = self.bitcell_offset.scale(0, bit + bit % 2) + self.bitcell_array_inst.ul() dummy_offset = self.bitcell_offset.scale(0, bit + bit % 2) + self.bitcell_array_inst.ul()
self.dummy_row_replica_insts[self.rbl[0] + bit].place(offset=dummy_offset, self.dummy_row_replica_insts[self.rbl[0] + bit].place(offset=dummy_offset,
mirror="MX" if bit % 2 else "R0") mirror="MX" if (self.row_size + bit) % 2 else "R0")
def add_end_caps(self): def add_end_caps(self):
""" Add dummy cells or end caps around the array """ """ Add dummy cells or end caps around the array """
# FIXME: These depend on the array size itself # Far top dummy row (first row above array is NOT flipped if even number of rows)
# Far top dummy row (first row above array is NOT flipped) flip_dummy = (self.row_size + self.rbl[1]) % 2
flip_dummy = self.rbl[1] % 2
dummy_row_offset = self.bitcell_offset.scale(0, self.rbl[1] + flip_dummy) + self.bitcell_array_inst.ul() dummy_row_offset = self.bitcell_offset.scale(0, self.rbl[1] + flip_dummy) + self.bitcell_array_inst.ul()
self.dummy_row_insts[1].place(offset=dummy_row_offset, self.dummy_row_insts[1].place(offset=dummy_row_offset,
mirror="MX" if flip_dummy else "R0") mirror="MX" if flip_dummy else "R0")
# FIXME: These depend on the array size itself
# Far bottom dummy row (first row below array IS flipped) # Far bottom dummy row (first row below array IS flipped)
flip_dummy = (self.rbl[0] + 1) % 2 flip_dummy = (self.rbl[0] + 1) % 2
dummy_row_offset = self.bitcell_offset.scale(0, -self.rbl[0] - 1 + flip_dummy) + self.unused_offset dummy_row_offset = self.bitcell_offset.scale(0, -self.rbl[0] - 1 + flip_dummy) + self.unused_offset
@ -422,9 +420,8 @@ class replica_bitcell_array(bitcell_base_array):
def add_layout_pins(self): def add_layout_pins(self):
""" Add the layout pins """ """ Add the layout pins """
#All wordlines # All wordlines
#Main array wl and bl/br # Main array wl and bl/br
for pin_name in self.all_wordline_names: for pin_name in self.all_wordline_names:
pin_list = self.bitcell_array_inst.get_pins(pin_name) pin_list = self.bitcell_array_inst.get_pins(pin_name)
for pin in pin_list: for pin in pin_list:

View File

@ -180,7 +180,7 @@ class replica_column(bitcell_base_array):
for port in self.all_ports: for port in self.all_ports:
for row in range(row_range_min, row_range_max): for row in range(row_range_min, row_range_max):
wl_pin = self.cell_inst[row].get_pin(self.cell.get_wl_name(port)) wl_pin = self.cell_inst[row].get_pin(self.cell.get_wl_name(port))
self.add_layout_pin(text="wl_{0}_{1}".format(port, row), self.add_layout_pin(text="wl_{0}_{1}".format(port, row - row_range_min),
layer=wl_pin.layer, layer=wl_pin.layer,
offset=wl_pin.ll().scale(0, 1), offset=wl_pin.ll().scale(0, 1),
width=self.width, width=self.width,

View File

@ -11,7 +11,7 @@ import math
from sram_factory import factory from sram_factory import factory
from vector import vector from vector import vector
from globals import OPTS from globals import OPTS
from tech import layer
class write_mask_and_array(design.design): class write_mask_and_array(design.design):
""" """
@ -95,9 +95,9 @@ class write_mask_and_array(design.design):
if not self.offsets: if not self.offsets:
self.offsets = [] self.offsets = []
for i in range(self.columns): for i in range(self.columns):
self.offsets.append(i * self.driver_spacing) self.offsets.append((i + self.write_size - 1) * self.driver_spacing)
self.width = self.offsets[-1] + self.driver_spacing self.width = self.offsets[-1] + self.bitcell.width
self.height = self.and2.height self.height = self.and2.height
write_bits = self.columns / self.num_wmasks write_bits = self.columns / self.num_wmasks
@ -139,11 +139,12 @@ class write_mask_and_array(design.design):
to_layer="m3", to_layer="m3",
offset=en_pos) offset=en_pos)
for supply in ["gnd", "vdd"]:
supply_pin=self.and2_insts[i].get_pin(supply)
self.add_power_pin(supply, supply_pin.center(), start_layer=supply_pin.layer)
for supply in ["gnd", "vdd"]: for supply in ["gnd", "vdd"]:
supply_pin_left = self.and2_insts[0].get_pin(supply) supply_pin = self.and2_insts[0].get_pin(supply)
supply_pin_right = self.and2_insts[self.num_wmasks - 1].get_pin(supply) supply_pin_yoffset = supply_pin.cy()
self.add_path(supply_pin_left.layer, [supply_pin_left.lc(), supply_pin_right.rc()]) left_loc = vector(0, supply_pin_yoffset)
right_loc = vector(self.width, supply_pin_yoffset)
self.add_path(supply_pin.layer, [left_loc, right_loc])
self.add_power_pin(supply, left_loc, start_layer=supply_pin.layer)
self.add_power_pin(supply, right_loc, start_layer=supply_pin.layer)

View File

@ -94,7 +94,7 @@ class options(optparse.Values):
# When enabled, layout is not generated (and no DRC or LVS are performed) # When enabled, layout is not generated (and no DRC or LVS are performed)
netlist_only = False netlist_only = False
# Whether we should do the final power routing # Whether we should do the final power routing
route_supplies = False route_supplies = "tree"
# This determines whether LVS and DRC is checked at all. # This determines whether LVS and DRC is checked at all.
check_lvsdrc = False check_lvsdrc = False
# This determines whether LVS and DRC is checked for every submodule. # This determines whether LVS and DRC is checked for every submodule.
@ -145,7 +145,7 @@ class options(optparse.Values):
# run (doesn't purge on errors, anyhow) # run (doesn't purge on errors, anyhow)
# Route the input/output pins to the perimeter # Route the input/output pins to the perimeter
perimeter_pins = False perimeter_pins = True
keep_temp = False keep_temp = False

View File

@ -158,9 +158,9 @@ class ptx(design.design):
# TEMP FIX: Use old device names if using Calibre. # TEMP FIX: Use old device names if using Calibre.
self.lvs_device = "M{{0}} {{1}} {0} m={1} w={2} l={3} mult=1".format("nshort" if self.tx_type == "nmos" else "pshort", self.lvs_device = "M{{0}} {{1}} {0} m={1} w={2} l={3} mult=1".format("nshort" if self.tx_type == "nmos" else "pshort",
self.mults, self.mults,
self.tx_width, self.tx_width,
drc("minwidth_poly")) drc("minwidth_poly"))
elif cell_props.ptx.model_is_subckt: elif cell_props.ptx.model_is_subckt:
# sky130 requires mult parameter too # sky130 requires mult parameter too
self.lvs_device = "X{{0}} {{1}} {0} m={1} w={2}u l={3}u".format(spice[self.tx_type], self.lvs_device = "X{{0}} {{1}} {0} m={1} w={2}u l={3}u".format(spice[self.tx_type],

25
compiler/processGDS.py Executable file
View File

@ -0,0 +1,25 @@
#!/usr/bin/env python3
import sys
from gdsMill import gdsMill
if len(sys.argv) < 2:
print("Usage: {0} in.gds out.gds".format(sys.argv[0]))
sys.exit(1)
in_gds_file = sys.argv[1]
out_gds_file = sys.argv[2]
layout = gdsMill.VlsiLayout()
reader = gdsMill.Gds2reader(layout)
reader.loadFromFile(in_gds_file)
struct = layout.structures[layout.rootStructureName]
# Do something to the structure
for text in struct.texts:
print(text.textString)
text.magFactor=""
writer = gdsMill.Gds2writer(layout)
writer.writeToFile(out_gds_file)

View File

@ -1,7 +0,0 @@
[globals/init_openram]: Initializing OpenRAM...
[globals/setup_paths]: Setting up paths...
[globals/setup_paths]: Temporary files saved in /home/jesse/output/
[globals/read_config]: Configuration file is /home/jesse/skywater-tech/riscv_1k_s8.py
[globals/read_config]: Output saved in /home/jesse/openram/compiler/riscv/
[globals/import_tech]: Importing technology: s8
[globals/import_tech]: Adding technology path: /home/jesse/openram/technology

File diff suppressed because it is too large Load Diff

View File

@ -1,61 +0,0 @@
[globals/init_openram]: Initializing OpenRAM...
[globals/setup_paths]: Setting up paths...
[globals/setup_paths]: Temporary files saved in /home/jesse/output/
[globals/read_config]: Configuration file is /home/jesse/skywater-tech/riscv_1k_s8.py
[globals/read_config]: Output saved in /home/jesse/openram/compiler/riscv/
[globals/import_tech]: Importing technology: sky130
[globals/import_tech]: Adding technology path: /home/jesse/openram/technology
[globals/init_paths]: Creating temp directory: /home/jesse/output/
[verify/<module>]: Initializing verify...
[verify/<module>]: LVS/DRC/PEX disabled.
[characterizer/<module>]: Initializing characterizer...
[characterizer/<module>]: Analytical model enabled.
[globals/setup_bitcell]: Using bitcell: bitcell_1rw_1r
|==============================================================================|
|========= OpenRAM v1.1.5 =========|
|========= =========|
|========= VLSI Design and Automation Lab =========|
|========= Computer Science and Engineering Department =========|
|========= University of California Santa Cruz =========|
|========= =========|
|========= Usage help: openram-user-group@ucsc.edu =========|
|========= Development help: openram-dev-group@ucsc.edu =========|
|========= Temp dir: /home/jesse/output/ =========|
|========= See LICENSE for license info =========|
|==============================================================================|
** Start: 06/25/2020 07:53:43
Technology: sky130
Total size: 8192 bits
Word size: 32
Words: 256
Banks: 1
Write size: 8
RW ports: 1
R-only ports: 1
W-only ports: 0
DRC/LVS/PEX is only run on the top-level design to save run-time (inline_lvsdrc=True to do inline checking).
DRC/LVS/PEX is disabled (check_lvsdrc=True to enable).
Characterization is disabled (using analytical delay models) (analytical_delay=False to simulate).
[bitcell_1rw_1r/__init__]: Create bitcell with 1RW and 1R Port
[sram_config/recompute_sizes]: Recomputing with words per row: 2
[sram_config/recompute_sizes]: Rows: 128 Cols: 64
[sram_config/recompute_sizes]: Row addr size: 7 Col addr size: 1 Bank addr size: 8
Words per row: 2
Output files are:
/home/jesse/openram/compiler/riscv/sram_1rw1r_32_256_8_sky130.sp
/home/jesse/openram/compiler/riscv/sram_1rw1r_32_256_8_sky130.v
/home/jesse/openram/compiler/riscv/sram_1rw1r_32_256_8_sky130.lib
/home/jesse/openram/compiler/riscv/sram_1rw1r_32_256_8_sky130.py
/home/jesse/openram/compiler/riscv/sram_1rw1r_32_256_8_sky130.html
/home/jesse/openram/compiler/riscv/sram_1rw1r_32_256_8_sky130.log
/home/jesse/openram/compiler/riscv/sram_1rw1r_32_256_8_sky130.lef
/home/jesse/openram/compiler/riscv/sram_1rw1r_32_256_8_sky130.gds
[sram/__init__]: create sram of size 32 with 256 num of words 1 banks
[dff_array/__init__]: Creating row_addr_dff rows=7 cols=1
[dff_array/__init__]: Creating col_addr_dff rows=1 cols=1
[dff_array/__init__]: Creating data_dff rows=1 cols=32
[dff_array/__init__]: Creating wmask_dff rows=1 cols=4
[bank/__init__]: create sram of size 32 with 256 words
[port_data/__init__]: create data port of size 32 with 2 words per row
[precharge/__init__]: creating precharge cell precharge
[pgate/bin_width]: binning pmos tx, target: 0.44999999999999996, found 0.55 x 1 = 0.55

View File

@ -5,12 +5,11 @@
# (acting for and on behalf of Oklahoma State University) # (acting for and on behalf of Oklahoma State University)
# All rights reserved. # All rights reserved.
# #
import numpy as np
import string
import debug import debug
from vector3d import vector3d from vector3d import vector3d
from grid_cell import grid_cell from grid_cell import grid_cell
class grid: class grid:
""" """
A two layer routing map. Each cell can be blocked in the vertical A two layer routing map. Each cell can be blocked in the vertical
@ -23,7 +22,6 @@ class grid:
NONPREFERRED_COST = 4 NONPREFERRED_COST = 4
PREFERRED_COST = 1 PREFERRED_COST = 1
def __init__(self, ll, ur, track_width): def __init__(self, ll, ur, track_width):
""" Initialize the map and define the costs. """ """ Initialize the map and define the costs. """
@ -33,12 +31,12 @@ class grid:
self.track_width = track_width self.track_width = track_width
self.track_widths = [self.track_width, self.track_width, 1.0] self.track_widths = [self.track_width, self.track_width, 1.0]
self.track_factor = [1/self.track_width, 1/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 # The bounds are in grids for this
# This is really lower left bottom layer and upper right top layer in 3D. # 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.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() 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 # let's leave the map sparse, cells are created on demand to reduce memory
self.map={} self.map={}
@ -46,18 +44,18 @@ class grid:
def add_all_grids(self): def add_all_grids(self):
for x in range(self.ll.x, self.ur.x, 1): for x in range(self.ll.x, self.ur.x, 1):
for y in range(self.ll.y, self.ur.y, 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, 0))
self.add_map(vector3d(x,y,1)) self.add_map(vector3d(x, y, 1))
def set_blocked(self,n,value=True): def set_blocked(self, n, value=True):
if not isinstance(n, vector3d): if not isinstance(n, vector3d):
for item in n: for item in n:
self.set_blocked(item,value) self.set_blocked(item, value)
else: else:
self.add_map(n) self.add_map(n)
self.map[n].blocked=value self.map[n].blocked=value
def is_blocked(self,n): def is_blocked(self, n):
if not isinstance(n, vector3d): if not isinstance(n, vector3d):
for item in n: for item in n:
if self.is_blocked(item): if self.is_blocked(item):
@ -68,11 +66,10 @@ class grid:
self.add_map(n) self.add_map(n)
return self.map[n].blocked return self.map[n].blocked
def set_path(self, n, value=True):
def set_path(self,n,value=True): if isinstance(n, (list, tuple, set, frozenset)):
if isinstance(n, (list,tuple,set,frozenset)):
for item in n: for item in n:
self.set_path(item,value) self.set_path(item, value)
else: else:
self.add_map(n) self.add_map(n)
self.map[n].path=value self.map[n].path=value
@ -81,47 +78,89 @@ class grid:
for k in self.map: for k in self.map:
self.map[k].blocked=False self.map[k].blocked=False
def set_source(self,n,value=True): def set_source(self, n, value=True):
if not isinstance(n, vector3d): if not isinstance(n, vector3d):
for item in n: for item in n:
self.set_source(item,value) self.set_source(item, value)
else: else:
self.add_map(n) self.add_map(n)
self.map[n].source=value self.map[n].source=value
self.source.add(n) self.source.add(n)
def set_target(self,n,value=True): def set_target(self, n, value=True):
if not isinstance(n, vector3d): if not isinstance(n, vector3d):
for item in n: for item in n:
self.set_target(item,value) self.set_target(item, value)
else: else:
self.add_map(n) self.add_map(n)
self.map[n].target=value self.map[n].target=value
self.target.add(n) self.target.add(n)
def add_source(self, track_list, value=True):
def add_source(self,track_list,value=True): debug.info(3, "Adding source list={0}".format(str(track_list)))
debug.info(3,"Adding source list={0}".format(str(track_list)))
for n in track_list: for n in track_list:
debug.info(4,"Adding source ={0}".format(str(n))) debug.info(4, "Adding source ={0}".format(str(n)))
self.set_source(n,value) self.set_source(n, value)
self.set_blocked(n,False) self.set_blocked(n, False)
def add_target(self, track_list, value=True):
def add_target(self,track_list,value=True): debug.info(3, "Adding target list={0}".format(str(track_list)))
debug.info(3,"Adding target list={0}".format(str(track_list)))
for n in track_list: for n in track_list:
debug.info(4,"Adding target ={0}".format(str(n))) debug.info(4, "Adding target ={0}".format(str(n)))
self.set_target(n,value) self.set_target(n, value)
self.set_blocked(n,False) self.set_blocked(n, False)
def is_target(self,point): def add_perimeter_target(self, side="all", value=True):
debug.info(3, "Adding perimeter target")
# Add the left/right columns
if side=="all" or side=="left":
x = self.ll.x
for y in range(self.ll.y, self.ur.y, 1):
n = vector3d(x, y, 0)
self.set_target(n, value)
self.set_blocked(n, False)
n = vector3d(x, y, 1)
self.set_target(n, value)
self.set_blocked(n, False)
if side=="all" or side=="right":
x = self.ur.x
for y in range(self.ll.y, self.ur.y, 1):
n = vector3d(x, y, 0)
self.set_target(n, value)
self.set_blocked(n, False)
n = vector3d(x, y, 1)
self.set_target(n, value)
self.set_blocked(n, False)
if side=="all" or side=="bottom":
y = self.ll.y
for x in range(self.ll.x, self.ur.x, 1):
n = vector3d(x, y, 0)
self.set_target(n, value)
self.set_blocked(n, False)
n = vector3d(x, y, 1)
self.set_target(n, value)
self.set_blocked(n, False)
if side=="all" or side=="top":
y = self.ur.y
for x in range(self.ll.x, self.ur.x, 1):
n = vector3d(x, y, 0)
self.set_target(n, value)
self.set_blocked(n, False)
n = vector3d(x, y, 1)
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. Point is in the target set, so we are done.
""" """
return point in self.target return point in self.target
def add_map(self,n): def add_map(self, n):
""" """
Add a point to the map if it doesn't exist. Add a point to the map if it doesn't exist.
""" """
@ -132,8 +171,7 @@ class grid:
if n not in self.map: if n not in self.map:
self.map[n]=grid_cell() self.map[n]=grid_cell()
def block_path(self, path):
def block_path(self,path):
""" """
Mark the path in the routing grid as blocked. Mark the path in the routing grid as blocked.
Also unsets the path flag. Also unsets the path flag.

View File

@ -5,13 +5,12 @@
# (acting for and on behalf of Oklahoma State University) # (acting for and on behalf of Oklahoma State University)
# All rights reserved. # All rights reserved.
# #
import debug
from vector3d import vector3d from vector3d import vector3d
from itertools import tee from itertools import tee
from grid import grid from grid import grid
from grid_cell import grid_cell
from direction import direction from direction import direction
class grid_path: class grid_path:
""" """
A grid path is a list of lists of grid cells. A grid path is a list of lists of grid cells.
@ -29,8 +28,7 @@ class grid_path:
self.pathlist = [] self.pathlist = []
def __str__(self): def __str__(self):
#import pprint p = str(self.pathlist)
p = str(self.pathlist) #pprint.pformat(self.pathlist)
if self.name != "": if self.name != "":
return (str(self.name) + " : " + p) return (str(self.name) + " : " + p)
return p return p
@ -188,12 +186,10 @@ class grid_path:
return newwave return newwave
def set_layer(self, zindex): def set_layer(self, zindex):
new_pathlist = [vector3d(item.x, item.y, zindex) for wave in self.pathlist for item in wave] new_pathlist = [vector3d(item.x, item.y, zindex) for wave in self.pathlist for item in wave]
self.pathlist = new_pathlist self.pathlist = new_pathlist
def overlap(self, other): def overlap(self, other):
""" """
Return the overlap waves ignoring different layers Return the overlap waves ignoring different layers

View File

@ -9,7 +9,6 @@
Some utility functions for sets of grid cells. Some utility functions for sets of grid cells.
""" """
import debug
import math import math
from direction import direction from direction import direction
from vector3d import vector3d from vector3d import vector3d
@ -44,6 +43,7 @@ def get_upper_right(curset):
ur = p ur = p
return ur return ur
def get_lower_left(curset): def get_lower_left(curset):
ll = None ll = None
for p in curset: for p in curset:
@ -51,7 +51,8 @@ def get_lower_left(curset):
ll = p ll = p
return ll return ll
def get_border( curset, direct):
def get_border(curset, direct):
""" """
Return the furthest cell(s) in a given direction. Return the furthest cell(s) in a given direction.
""" """
@ -86,6 +87,7 @@ def get_border( curset, direct):
newset = set(maxc) newset = set(maxc)
return newset return newset
def expand_border(curset, direct): def expand_border(curset, direct):
""" """
Expand the current set of sells in a given direction. Expand the current set of sells in a given direction.
@ -95,6 +97,7 @@ def expand_border(curset, direct):
next_border_set = increment_set(border_set, direct) next_border_set = increment_set(border_set, direct)
return next_border_set return next_border_set
def expand_borders(curset): def expand_borders(curset):
""" """
Return the expansions in planar directions. Return the expansions in planar directions.
@ -106,6 +109,7 @@ def expand_borders(curset):
return(north_set, east_set, south_set, west_set) return(north_set, east_set, south_set, west_set)
def inflate_cell(cell, distance): def inflate_cell(cell, distance):
""" """
Expand the current cell in all directions and return the set. Expand the current cell in all directions and return the set.
@ -122,6 +126,7 @@ def inflate_cell(cell, distance):
return newset return newset
def inflate_set(curset, distance): def inflate_set(curset, distance):
""" """
Expand the set in all directions by the given number of grids. Expand the set in all directions by the given number of grids.
@ -136,6 +141,7 @@ def inflate_set(curset, distance):
# Recurse with less depth # Recurse with less depth
return inflate_set(newset,distance-1) return inflate_set(newset,distance-1)
def flatten_set(curset): def flatten_set(curset):
""" """
Flatten until we have a set of vector3d objects. Flatten until we have a set of vector3d objects.
@ -149,7 +155,6 @@ def flatten_set(curset):
return newset return newset
def distance_set(coord, curset): def distance_set(coord, curset):
""" """
Return the distance from a coordinate to any item in the set Return the distance from a coordinate to any item in the set

View File

@ -493,7 +493,6 @@ class pin_group:
debug.error("Could not find a connector for {} with {}".format(self.pins, debug.error("Could not find a connector for {} with {}".format(self.pins,
self.enclosures)) self.enclosures))
self.router.write_debug_gds("no_connector.gds") self.router.write_debug_gds("no_connector.gds")
import pdb; pdb.set_trace()
self.enclosures.append(connector) self.enclosures.append(connector)
# At this point, the pins are overlapping, # At this point, the pins are overlapping,

View File

@ -27,13 +27,14 @@ class router(router_tech):
route on a given layer. This is limited to two layer routes. route on a given layer. This is limited to two layer routes.
It populates blockages on a grid class. It populates blockages on a grid class.
""" """
def __init__(self, layers, design, gds_filename=None, rail_track_width=1): def __init__(self, layers, design, gds_filename=None, route_track_width=1):
""" """
This will instantiate a copy of the gds file or the module at (0,0) and This will instantiate a copy of the gds file or the module at (0,0) and
route on top of this. The blockages from the gds/module will be route on top of this. The blockages from the gds/module will be
considered. considered.
""" """
router_tech.__init__(self, layers, rail_track_width)
router_tech.__init__(self, layers, route_track_width)
self.cell = design self.cell = design
@ -285,7 +286,7 @@ class router(router_tech):
If so, reduce the pin group grid to not include the adjacent grid. If so, reduce the pin group grid to not include the adjacent grid.
Try to do this intelligently to keep th pins enclosed. Try to do this intelligently to keep th pins enclosed.
""" """
debug.info(1, debug.info(2,
"Comparing {0} and {1} adjacency".format(pin_name1, "Comparing {0} and {1} adjacency".format(pin_name1,
pin_name2)) pin_name2))
removed_grids = 0 removed_grids = 0
@ -301,7 +302,7 @@ class router(router_tech):
adj_grids)) adj_grids))
self.remove_adjacent_grid(pg1, pg2, adj_grids) self.remove_adjacent_grid(pg1, pg2, adj_grids)
debug.info(1, "Removed {} adjacent grids.".format(removed_grids)) debug.info(2, "Removed {} adjacent grids.".format(removed_grids))
def remove_adjacent_grid(self, pg1, pg2, adj_grids): def remove_adjacent_grid(self, pg1, pg2, adj_grids):
""" """
@ -347,6 +348,10 @@ class router(router_tech):
smaller)) smaller))
smaller.grids.remove(adj) smaller.grids.remove(adj)
def set_supply_rail_blocked(self, value):
# This is just a virtual function
pass
def prepare_blockages(self, pin_name): def prepare_blockages(self, pin_name):
""" """
Reset and add all of the blockages in the design. Reset and add all of the blockages in the design.
@ -362,7 +367,11 @@ class router(router_tech):
# Block all of the supply rails # Block all of the supply rails
# (some will be unblocked if they're a target) # (some will be unblocked if they're a target)
self.set_supply_rail_blocked(True) try:
self.set_supply_rail_blocked(True)
except AttributeError:
# If function doesn't exist, it isn't a supply router
pass
# Block all of the pin components # Block all of the pin components
# (some will be unblocked if they're a source/target) # (some will be unblocked if they're a source/target)
@ -797,7 +806,7 @@ class router(router_tech):
""" """
Convert the pin groups into pin tracks and blockage tracks. Convert the pin groups into pin tracks and blockage tracks.
""" """
debug.info(1, "Converting pins for {}.".format(pin_name)) debug.info(2, "Converting pins for {}.".format(pin_name))
for pg in self.pin_groups[pin_name]: for pg in self.pin_groups[pin_name]:
pg.convert_pin() pg.convert_pin()
@ -808,7 +817,7 @@ class router(router_tech):
that are blocked by other shapes. that are blocked by other shapes.
""" """
for pin_name in self.pin_groups: for pin_name in self.pin_groups:
debug.info(1, "Enclosing pins for {}".format(pin_name)) debug.info(2, "Enclosing pins for {}".format(pin_name))
for pg in self.pin_groups[pin_name]: for pg in self.pin_groups[pin_name]:
pg.enclose_pin() pg.enclose_pin()
pg.add_enclosure(self.cell) pg.add_enclosure(self.cell)
@ -829,6 +838,12 @@ class router(router_tech):
for i in range(self.num_pin_components(pin_name)): for i in range(self.num_pin_components(pin_name)):
self.add_pin_component_target(pin_name, i) self.add_pin_component_target(pin_name, i)
def add_perimeter_target(self, side="all"):
"""
This will mark all the cells on the perimeter of the original layout as a target.
"""
self.rg.add_perimeter_target(side=side)
def num_pin_components(self, pin_name): def num_pin_components(self, pin_name):
""" """
This returns how many disconnected pin components there are. This returns how many disconnected pin components there are.
@ -865,7 +880,7 @@ class router(router_tech):
debug.check(index<self.num_pin_components(pin_name),"Pin component index too large.") debug.check(index<self.num_pin_components(pin_name),"Pin component index too large.")
pin_in_tracks = self.pin_groups[pin_name][index].grids pin_in_tracks = self.pin_groups[pin_name][index].grids
debug.info(2, "Set target: " + str(pin_name) + " " + str(pin_in_tracks)) debug.info(3, "Set target: " + str(pin_name) + " " + str(pin_in_tracks))
self.rg.add_target(pin_in_tracks) self.rg.add_target(pin_in_tracks)
def add_pin_component_target_except(self, pin_name, index): def add_pin_component_target_except(self, pin_name, index):
@ -901,7 +916,7 @@ class router(router_tech):
# self.write_debug_gds() # self.write_debug_gds()
# First, simplify the path for # First, simplify the path for
# debug.info(1, str(self.path)) # debug.info(3, str(self.path))
contracted_path = self.contract_path(path) contracted_path = self.contract_path(path)
debug.info(3, "Contracted path: " + str(contracted_path)) debug.info(3, "Contracted path: " + str(contracted_path))
@ -1000,25 +1015,20 @@ class router(router_tech):
# Double check source and taget are not same node, if so, we are done! # Double check source and taget are not same node, if so, we are done!
for k, v in self.rg.map.items(): for k, v in self.rg.map.items():
if v.source and v.target: if v.source and v.target:
debug.error("Grid cell is source and target! {}".format(k)) return True
return False
# returns the path in tracks # returns the path in tracks
(path, cost) = self.rg.route(detour_scale) (path, cost) = self.rg.route(detour_scale)
if path: if path:
debug.info(2, "Found path: cost={0} {1}".format(cost, str(path))) debug.info(2, "Found path: cost={0} {1}".format(cost, str(path)))
self.paths.append(path) self.paths.append(grid_utils.flatten_set(path))
self.add_route(path) self.add_route(path)
path_set = grid_utils.flatten_set(path) self.path_blockages.append(self.paths[-1])
self.path_blockages.append(path_set) return True
else: else:
self.write_debug_gds("failed_route.gds")
# clean up so we can try a reroute
self.rg.reinit()
return False return False
return True
def annotate_pin_and_tracks(self, pin, tracks): def annotate_pin_and_tracks(self, pin, tracks):
"""" """"
@ -1122,7 +1132,7 @@ class router(router_tech):
show_all_grids = True show_all_grids = True
if show_all_grids: if show_all_grids:
self.rg.add_all_grids() # self.rg.add_all_grids()
for g in self.rg.map: for g in self.rg.map:
self.annotate_grid(g) self.annotate_grid(g)
@ -1156,6 +1166,35 @@ class router(router_tech):
width=pin.width(), width=pin.width(),
height=pin.height()) height=pin.height())
def get_perimeter_pin(self):
""" Return the shape of the last routed path that was on the perimeter """
for v in self.paths[-1]:
if self.rg.is_target(v):
return self.convert_track_to_pin(v)
return None
def get_pin(self, pin_name):
""" Return the lowest, leftest pin group """
keep_pin = None
for index,pg in enumerate(self.pin_groups[pin_name]):
for pin in pg.enclosures:
if not keep_pin:
keep_pin = pin
else:
if pin.lx() <= keep_pin.lx() and pin.by() <= keep_pin.by():
keep_pin = pin
return keep_pin
def check_all_routed(self, pin_name):
"""
Check that all pin groups are routed.
"""
for pg in self.pin_groups[pin_name]:
if not pg.is_routed():
return False
# FIXME: This should be replaced with vector.snap_to_grid at some point # FIXME: This should be replaced with vector.snap_to_grid at some point
def snap_to_grid(offset): def snap_to_grid(offset):

View File

@ -16,20 +16,20 @@ class router_tech:
""" """
This is a class to hold the router tech constants. This is a class to hold the router tech constants.
""" """
def __init__(self, layers, rail_track_width): def __init__(self, layers, route_track_width):
""" """
Allows us to change the layers that we are routing on. Allows us to change the layers that we are routing on.
This uses the preferreed directions. This uses the preferreed directions.
""" """
self.layers = layers self.layers = layers
self.rail_track_width = rail_track_width self.route_track_width = route_track_width
if len(self.layers) == 1: if len(self.layers) == 1:
self.horiz_layer_name = self.vert_layer_name = self.layers[0] self.horiz_layer_name = self.vert_layer_name = self.layers[0]
self.horiz_lpp = self.vert_lpp = layer[self.layers[0]] self.horiz_lpp = self.vert_lpp = layer[self.layers[0]]
(self.vert_layer_minwidth, self.vert_layer_spacing) = self.get_supply_layer_width_space(1) (self.vert_layer_minwidth, self.vert_layer_spacing) = self.get_layer_width_space(1)
(self.horiz_layer_minwidth, self.horiz_layer_spacing) = self.get_supply_layer_width_space(0) (self.horiz_layer_minwidth, self.horiz_layer_spacing) = self.get_layer_width_space(0)
self.horiz_track_width = self.horiz_layer_minwidth + self.horiz_layer_spacing self.horiz_track_width = self.horiz_layer_minwidth + self.horiz_layer_spacing
self.vert_track_width = self.vert_layer_minwidth + self.vert_layer_spacing self.vert_track_width = self.vert_layer_minwidth + self.vert_layer_spacing
@ -55,13 +55,13 @@ class router_tech:
"preferred_directions '{}' and '{}'.") "preferred_directions '{}' and '{}'.")
via_connect = contact(self.layers, (1, 1)) via_connect = contact(self.layers, (1, 1))
max_via_size = max(via_connect.width,via_connect.height) max_via_size = max(via_connect.width, via_connect.height)
self.horiz_lpp = layer[self.horiz_layer_name] self.horiz_lpp = layer[self.horiz_layer_name]
self.vert_lpp = layer[self.vert_layer_name] self.vert_lpp = layer[self.vert_layer_name]
(self.vert_layer_minwidth, self.vert_layer_spacing) = self.get_supply_layer_width_space(1) (self.vert_layer_minwidth, self.vert_layer_spacing) = self.get_layer_width_space(1)
(self.horiz_layer_minwidth, self.horiz_layer_spacing) = self.get_supply_layer_width_space(0) (self.horiz_layer_minwidth, self.horiz_layer_spacing) = self.get_layer_width_space(0)
# For supplies, we will make the wire wider than the vias # For supplies, we will make the wire wider than the vias
self.vert_layer_minwidth = max(self.vert_layer_minwidth, max_via_size) self.vert_layer_minwidth = max(self.vert_layer_minwidth, max_via_size)
@ -71,16 +71,16 @@ class router_tech:
self.vert_track_width = self.vert_layer_minwidth + self.vert_layer_spacing self.vert_track_width = self.vert_layer_minwidth + self.vert_layer_spacing
# We'll keep horizontal and vertical tracks the same for simplicity. # We'll keep horizontal and vertical tracks the same for simplicity.
self.track_width = max(self.horiz_track_width,self.vert_track_width) self.track_width = max(self.horiz_track_width, self.vert_track_width)
debug.info(1,"Track width: {:.3f}".format(self.track_width)) debug.info(1, "Minimum track width: {:.3f}".format(self.track_width))
self.track_space = max(self.horiz_layer_spacing,self.vert_layer_spacing) self.track_space = max(self.horiz_layer_spacing, self.vert_layer_spacing)
debug.info(1,"Track space: {:.3f}".format(self.track_space)) debug.info(1, "Minimum track space: {:.3f}".format(self.track_space))
self.track_wire = self.track_width - self.track_space self.track_wire = self.track_width - self.track_space
debug.info(1,"Track wire width: {:.3f}".format(self.track_wire)) debug.info(1, "Minimum track wire width: {:.3f}".format(self.track_wire))
self.track_widths = vector([self.track_width] * 2) self.track_widths = vector([self.track_width] * 2)
self.track_factor = vector([1/self.track_width] * 2) self.track_factor = vector([1/self.track_width] * 2)
debug.info(2,"Track factor: {}".format(self.track_factor)) debug.info(2, "Track factor: {}".format(self.track_factor))
# When we actually create the routes, make them the width of the track (minus 1/2 spacing on each side) # When we actually create the routes, make them the width of the track (minus 1/2 spacing on each side)
self.layer_widths = [self.track_wire, 1, self.track_wire] self.layer_widths = [self.track_wire, 1, self.track_wire]
@ -107,9 +107,9 @@ class router_tech:
elif zindex==0: elif zindex==0:
return self.horiz_layer_name return self.horiz_layer_name
else: else:
debug.error("Invalid zindex {}".format(zindex),-1) debug.error("Invalid zindex {}".format(zindex), -1)
def get_supply_layer_width_space(self, zindex): def get_layer_width_space(self, zindex):
""" """
These are the width and spacing of a supply layer given a supply rail These are the width and spacing of a supply layer given a supply rail
of the given number of min wire widths. of the given number of min wire widths.
@ -123,9 +123,9 @@ class router_tech:
min_wire_width = drc("minwidth_{0}".format(layer_name), 0, math.inf) min_wire_width = drc("minwidth_{0}".format(layer_name), 0, math.inf)
min_width = drc("minwidth_{0}".format(layer_name), self.rail_track_width*min_wire_width, math.inf) min_width = drc("minwidth_{0}".format(layer_name), self.route_track_width * min_wire_width, math.inf)
min_spacing = drc(str(layer_name)+"_to_"+str(layer_name), self.rail_track_width*min_wire_width, math.inf) min_spacing = drc(str(layer_name)+"_to_"+str(layer_name), self.route_track_width * min_wire_width, math.inf)
return (min_width,min_spacing) return (min_width, min_spacing)

View File

@ -0,0 +1,92 @@
# See LICENSE for licensing information.
#
# Copyright (c) 2016-2019 Regents of the University of California and The Board
# of Regents for the Oklahoma Agricultural and Mechanical College
# (acting for and on behalf of Oklahoma State University)
# All rights reserved.
#
import debug
from globals import print_time
from router import router
from datetime import datetime
from supply_grid import supply_grid
class signal_escape_router(router):
"""
A router that routes signals to perimeter and makes pins.
"""
def __init__(self, layers, design, gds_filename=None):
"""
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, 1)
def create_routing_grid(self):
"""
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))
self.rg = supply_grid(self.ll, self.ur, self.track_width)
def escape_route(self, pin_list):
"""
Takes a list of tuples (name, side) and routes them. After routing,
it removes the old pin and places a new one on the perimeter.
"""
pin_names = [x[0] for x in pin_list]
# Clear the pins if we have previously routed
if (hasattr(self,'rg')):
self.clear_pins()
else:
self.create_routing_grid()
# Get the pin shapes
start_time = datetime.now()
self.find_pins_and_blockages(pin_names)
print_time("Finding pins and blockages",datetime.now(), start_time, 3)
# Route the supply pins to the supply rails
# Route vdd first since we want it to be shorter
start_time = datetime.now()
for pin_name, side in pin_list:
self.route_signal(pin_name, side)
print_time("Maze routing pins",datetime.now(), start_time, 3)
# self.write_debug_gds("final_escape_router.gds",False)
return True
def route_signal(self, pin_name, side):
for detour_scale in [5 * pow(2, x) for x in range(5)]:
debug.info(1, "Exit routing {0} with scale {1}".format(pin_name, detour_scale))
# Clear everything in the routing grid.
self.rg.reinit()
# This is inefficient since it is non-incremental, but it was
# easier to debug.
self.prepare_blockages(pin_name)
# Add the single component of the pin as the source
# which unmarks it as a blockage too
self.add_source(pin_name)
# Marks the grid cells all along the perimeter as a target
self.add_perimeter_target(side)
# Actually run the A* router
if self.run_router(detour_scale=detour_scale):
new_pin = self.get_perimeter_pin()
self.cell.replace_layout_pin(pin_name, new_pin)
return
self.write_debug_gds("debug_route.gds", True)

View File

@ -5,7 +5,6 @@
# (acting for and on behalf of Oklahoma State University) # (acting for and on behalf of Oklahoma State University)
# All rights reserved. # All rights reserved.
# #
from itertools import tee
import debug import debug
from heapq import heappush,heappop from heapq import heappush,heappop
from copy import deepcopy from copy import deepcopy
@ -14,6 +13,7 @@ from grid import grid
from grid_path import grid_path from grid_path import grid_path
from vector3d import vector3d from vector3d import vector3d
class signal_grid(grid): class signal_grid(grid):
""" """
Expand the two layer grid to include A* search functions for a source and target. Expand the two layer grid to include A* search functions for a source and target.
@ -34,11 +34,11 @@ class signal_grid(grid):
p.reset() p.reset()
# clear source and target pins # clear source and target pins
self.source=[] self.source = set()
self.target=[] self.target = set()
# Clear the queue # Clear the queue
while len(self.q)>0: while len(self.q) > 0:
heappop(self.q) heappop(self.q)
self.counter = 0 self.counter = 0
@ -49,19 +49,16 @@ class signal_grid(grid):
We will use an A* search, so this cost must be pessimistic. We will use an A* search, so this cost must be pessimistic.
Cost so far will be the length of the path. Cost so far will be the length of the path.
""" """
#debug.info(3,"Initializing queue.")
# Counter is used to not require data comparison in Python 3.x # Counter is used to not require data comparison in Python 3.x
# Items will be returned in order they are added during cost ties # Items will be returned in order they are added during cost ties
self.counter = 0 self.counter = 0
for s in self.source: for s in self.source:
cost = self.cost_to_target(s) cost = self.cost_to_target(s)
debug.info(3,"Init: cost=" + str(cost) + " " + str([s])) debug.info(3, "Init: cost=" + str(cost) + " " + str([s]))
heappush(self.q,(cost,self.counter,grid_path([vector3d(s)]))) heappush(self.q, (cost, self.counter, grid_path([vector3d(s)])))
self.counter+=1 self.counter+=1
def route(self, detour_scale):
def route(self,detour_scale):
""" """
This does the A* maze routing with preferred direction routing. This does the A* maze routing with preferred direction routing.
This only works for 1 track wide routes! This only works for 1 track wide routes!
@ -70,7 +67,7 @@ class signal_grid(grid):
# We set a cost bound of the HPWL for run-time. This can be # 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. # over-ridden if the route fails due to pruning a feasible solution.
any_source_element = next(iter(self.source)) any_source_element = next(iter(self.source))
cost_bound = detour_scale*self.cost_to_target(any_source_element)*grid.PREFERRED_COST cost_bound = detour_scale * self.cost_to_target(any_source_element) * grid.PREFERRED_COST
# Check if something in the queue is already a source and a target! # Check if something in the queue is already a source and a target!
for s in self.source: for s in self.source:
@ -83,20 +80,17 @@ class signal_grid(grid):
# Put the source items into the queue # Put the source items into the queue
self.init_queue() self.init_queue()
cheapest_path = None
cheapest_cost = None
# Keep expanding and adding to the priority queue until we are done # Keep expanding and adding to the priority queue until we are done
while len(self.q)>0: while len(self.q)>0:
# should we keep the path in the queue as well or just the final node? # should we keep the path in the queue as well or just the final node?
(cost,count,curpath) = heappop(self.q) (cost, count, curpath) = heappop(self.q)
debug.info(3,"Queue size: size=" + str(len(self.q)) + " " + str(cost)) debug.info(3, "Queue size: size=" + str(len(self.q)) + " " + str(cost))
debug.info(4,"Expanding: cost=" + str(cost) + " " + str(curpath)) debug.info(4, "Expanding: cost=" + str(cost) + " " + str(curpath))
# expand the last element # expand the last element
neighbors = self.expand_dirs(curpath) neighbors = self.expand_dirs(curpath)
debug.info(4,"Neighbors: " + str(neighbors)) debug.info(4, "Neighbors: " + str(neighbors))
for n in neighbors: for n in neighbors:
# make a new copy of the path to not update the old ones # make a new copy of the path to not update the old ones
@ -105,7 +99,7 @@ class signal_grid(grid):
newpath.append(n) newpath.append(n)
# check if we hit the target and are done # check if we hit the target and are done
if self.is_target(n[0]): # This uses the [0] item because we are assuming 1-track wide if self.is_target(n[0]): # This uses the [0] item because we are assuming 1-track wide
return (newpath,newpath.cost()) return (newpath, newpath.cost())
else: else:
# current path cost + predicted cost # current path cost + predicted cost
current_cost = newpath.cost() current_cost = newpath.cost()
@ -116,19 +110,18 @@ class signal_grid(grid):
if (self.map[n[0]].min_cost==-1 or predicted_cost<self.map[n[0]].min_cost): 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_path = newpath
self.map[n[0]].min_cost = predicted_cost self.map[n[0]].min_cost = predicted_cost
debug.info(4,"Enqueuing: cost=" + str(current_cost) + "+" + str(target_cost) + " " + str(newpath)) 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 # add the cost to get to this point if we haven't reached it yet
heappush(self.q,(predicted_cost,self.counter,newpath)) heappush(self.q, (predicted_cost, self.counter, newpath))
self.counter += 1 self.counter += 1
#else: #else:
# print("Better previous cost.") # print("Better previous cost.")
#else: #else:
# print("Cost bounded") # print("Cost bounded")
debug.warning("Unable to route path. Expand the detour_scale to allow detours.") return (None, None)
return (None,None)
def expand_dirs(self,curpath): def expand_dirs(self, curpath):
""" """
Expand each of the four cardinal directions plus up or down Expand each of the four cardinal directions plus up or down
but not expanding to blocked cells. Expands in all directions but not expanding to blocked cells. Expands in all directions
@ -143,33 +136,31 @@ class signal_grid(grid):
return unblocked_neighbors return unblocked_neighbors
def hpwl(self, src, dest): def hpwl(self, src, dest):
""" """
Return half perimeter wire length from point to another. Return half perimeter wire length from point to another.
Either point can have positive or negative coordinates. Either point can have positive or negative coordinates.
Include the via penalty if there is one. Include the via penalty if there is one.
""" """
hpwl = abs(src.x-dest.x) hpwl = abs(src.x - dest.x)
hpwl += abs(src.y-dest.y) hpwl += abs(src.y - dest.y)
if src.x!=dest.x and src.y!=dest.y: if src.x!=dest.x and src.y!=dest.y:
hpwl += grid.VIA_COST hpwl += grid.VIA_COST
return hpwl return hpwl
def cost_to_target(self,source): def cost_to_target(self, source):
""" """
Find the cheapest HPWL distance to any target point ignoring Find the cheapest HPWL distance to any target point ignoring
blockages for A* search. blockages for A* search.
""" """
any_target_element = next(iter(self.target)) any_target_element = next(iter(self.target))
cost = self.hpwl(source,any_target_element) cost = self.hpwl(source, any_target_element)
for t in self.target: for t in self.target:
cost = min(self.hpwl(source,t),cost) cost = min(self.hpwl(source, t), cost)
return cost return cost
def get_inertia(self, p0, p1):
def get_inertia(self,p0,p1):
""" """
Sets the direction based on the previous direction we came from. Sets the direction based on the previous direction we came from.
""" """

View File

@ -5,15 +5,10 @@
# (acting for and on behalf of Oklahoma State University) # (acting for and on behalf of Oklahoma State University)
# All rights reserved. # All rights reserved.
# #
import gdsMill
import tech
from contact import contact
import math
import debug import debug
from pin_layout import pin_layout
from globals import OPTS
from router import router from router import router
class signal_router(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
@ -27,7 +22,6 @@ class signal_router(router):
""" """
router.__init__(self, layers, design, gds_filename) router.__init__(self, layers, design, gds_filename)
def create_routing_grid(self): def create_routing_grid(self):
""" """
Create a sprase routing grid with A* expansion functions. Create a sprase routing grid with A* expansion functions.
@ -35,11 +29,10 @@ class signal_router(router):
# We will add a halo around the boundary # We will add a halo around the boundary
# of this many tracks # of this many tracks
size = self.ur - self.ll size = self.ur - self.ll
debug.info(1,"Size: {0} x {1}".format(size.x,size.y)) debug.info(1, "Size: {0} x {1}".format(size.x, size.y))
import signal_grid import signal_grid
self.rg = signal_grid.signal_grid(self.ll, self.ur, self.track_width) self.rg = signal_grid.signal_grid(self.ll, self.ur, self.route_track_width)
def route(self, src, dest, detour_scale=5): def route(self, src, dest, detour_scale=5):
""" """
@ -47,13 +40,13 @@ class signal_router(router):
the simplified rectilinear path. Cost factor is how sub-optimal to explore for a feasible route. 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. 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)) debug.info(1, "Running signal router from {0} to {1}...".format(src, dest))
self.pins[src] = [] self.pins[src] = []
self.pins[dest] = [] self.pins[dest] = []
# Clear the pins if we have previously routed # Clear the pins if we have previously routed
if (hasattr(self,'rg')): if (hasattr(self, 'rg')):
self.clear_pins() self.clear_pins()
else: else:
# Creat a routing grid over the entire area # Creat a routing grid over the entire area
@ -67,8 +60,8 @@ class signal_router(router):
# Block everything # Block everything
self.prepare_blockages() self.prepare_blockages()
# Clear the pins we are routing # Clear the pins we are routing
self.set_blockages(self.pin_components[src],False) self.set_blockages(self.pin_components[src], False)
self.set_blockages(self.pin_components[dest],False) self.set_blockages(self.pin_components[dest], False)
# Now add the src/tgt if they are not blocked by other shapes # Now add the src/tgt if they are not blocked by other shapes
self.add_source(src) self.add_source(src)

View File

@ -5,12 +5,8 @@
# (acting for and on behalf of Oklahoma State University) # (acting for and on behalf of Oklahoma State University)
# All rights reserved. # All rights reserved.
# #
import debug
from vector3d import vector3d
from grid import grid
from signal_grid import signal_grid from signal_grid import signal_grid
from grid_path import grid_path from grid_path import grid_path
from direction import direction
class supply_grid(signal_grid): class supply_grid(signal_grid):
@ -27,13 +23,14 @@ class supply_grid(signal_grid):
def reinit(self): def reinit(self):
""" Reinitialize everything for a new route. """ """ Reinitialize everything for a new route. """
self.source = set() self.source = set()
self.target = set() self.target = set()
# Reset all the cells in the map # Reset all the cells in the map
for p in self.map.values(): for p in self.map.values():
p.reset() p.reset()
def find_start_wave(self, wave, direct): def find_start_wave(self, wave, direct):
""" """
Finds the first loc starting at loc and up that is open. Finds the first loc starting at loc and up that is open.
@ -46,8 +43,8 @@ class supply_grid(signal_grid):
return None return None
while wave and self.is_wave_blocked(wave): while wave and self.is_wave_blocked(wave):
wf=grid_path(wave) wf = grid_path(wave)
wave=wf.neighbor(direct) wave = wf.neighbor(direct)
# Bail out if we couldn't increment futher # Bail out if we couldn't increment futher
if wave[0].x > self.ur.x or wave[-1].y > self.ur.y: if wave[0].x > self.ur.x or wave[-1].y > self.ur.y:
return None return None
@ -57,7 +54,6 @@ class supply_grid(signal_grid):
return wave return wave
def is_wave_blocked(self, wave): def is_wave_blocked(self, wave):
""" """
Checks if any of the locations are blocked Checks if any of the locations are blocked
@ -68,7 +64,6 @@ class supply_grid(signal_grid):
else: else:
return False return False
def probe(self, wave, direct): def probe(self, wave, direct):
""" """
Expand the wave until there is a blockage and return Expand the wave until there is a blockage and return

View File

@ -28,9 +28,9 @@ class supply_grid_router(router):
start_time = datetime.now() start_time = datetime.now()
# Power rail width in minimum wire widths # Power rail width in minimum wire widths
self.rail_track_width = 3 self.route_track_width = 2
router.__init__(self, layers, design, gds_filename, self.rail_track_width) router.__init__(self, layers, design, gds_filename, self.route_track_width)
# The list of supply rails (grid sets) that may be routed # The list of supply rails (grid sets) that may be routed
self.supply_rails = {} self.supply_rails = {}
@ -47,7 +47,7 @@ class supply_grid_router(router):
debug.info(1, "Size: {0} x {1}".format(size.x, size.y)) debug.info(1, "Size: {0} x {1}".format(size.x, size.y))
import supply_grid import supply_grid
self.rg = supply_grid.supply_grid(self.ll, self.ur, self.track_width) self.rg = supply_grid.supply_grid(self.ll, self.ur, self.route_track_width)
def route(self, vdd_name="vdd", gnd_name="gnd"): def route(self, vdd_name="vdd", gnd_name="gnd"):
""" """
@ -74,6 +74,7 @@ class supply_grid_router(router):
start_time = datetime.now() start_time = datetime.now()
# Block everything # Block everything
self.prepare_blockages(self.gnd_name) self.prepare_blockages(self.gnd_name)
# Determine the rail locations # Determine the rail locations
self.route_supply_rails(self.gnd_name, 0) self.route_supply_rails(self.gnd_name, 0)
@ -104,14 +105,6 @@ class supply_grid_router(router):
return True return True
def check_all_routed(self, pin_name):
"""
Check that all pin groups are routed.
"""
for pg in self.pin_groups[pin_name]:
if not pg.is_routed():
return False
def route_simple_overlaps(self, pin_name): def route_simple_overlaps(self, pin_name):
""" """
This checks for simple cases where a pin component already overlaps a supply rail. This checks for simple cases where a pin component already overlaps a supply rail.
@ -317,7 +310,7 @@ class supply_grid_router(router):
data structure. Return whether it was added or not. data structure. Return whether it was added or not.
""" """
# We must have at least 2 tracks to drop plus 2 tracks for a via # We must have at least 2 tracks to drop plus 2 tracks for a via
if len(wave_path) >= 4 * self.rail_track_width: if len(wave_path) >= 4 * self.route_track_width:
grid_set = wave_path.get_grids() grid_set = wave_path.get_grids()
self.supply_rails[name].append(grid_set) self.supply_rails[name].append(grid_set)
return True return True

View File

@ -1,24 +1,19 @@
# See LICENSE for licensing information. # See LICENSE for licensing information.
# #
#Copyright (c) 2016-2019 Regents of the University of California and The Board # Copyright (c) 2016-2019 Regents of the University of California and The Board
#of Regents for the Oklahoma Agricultural and Mechanical College # of Regents for the Oklahoma Agricultural and Mechanical College
#(acting for and on behalf of Oklahoma State University) # (acting for and on behalf of Oklahoma State University)
#All rights reserved. # All rights reserved.
# #
import gdsMill
import tech
import math
import debug import debug
from globals import OPTS,print_time from globals import print_time
from contact import contact
from pin_group import pin_group
from pin_layout import pin_layout
from vector3d import vector3d
from router import router from router import router
from direction import direction
from datetime import datetime from datetime import datetime
import grid
import grid_utils import grid_utils
from scipy.sparse import csr_matrix
from scipy.sparse.csgraph import minimum_spanning_tree
from signal_grid import signal_grid
class supply_tree_router(router): class supply_tree_router(router):
""" """
@ -32,10 +27,9 @@ class supply_tree_router(router):
either the gds file name or the design itself (by saving to a gds file). either the gds file name or the design itself (by saving to a gds file).
""" """
# Power rail width in minimum wire widths # Power rail width in minimum wire widths
self.rail_track_width = 3 self.route_track_width = 2
router.__init__(self, layers, design, gds_filename, self.rail_track_width)
router.__init__(self, layers, design, gds_filename, self.route_track_width)
def create_routing_grid(self): def create_routing_grid(self):
""" """
@ -43,9 +37,7 @@ class supply_tree_router(router):
""" """
size = self.ur - self.ll size = self.ur - self.ll
debug.info(1,"Size: {0} x {1}".format(size.x,size.y)) debug.info(1,"Size: {0} x {1}".format(size.x,size.y))
self.rg = signal_grid(self.ll, self.ur, self.route_track_width)
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"): def route(self, vdd_name="vdd", gnd_name="gnd"):
""" """
@ -82,7 +74,7 @@ class supply_tree_router(router):
self.route_pins(gnd_name) self.route_pins(gnd_name)
print_time("Maze routing supplies",datetime.now(), start_time, 3) print_time("Maze routing supplies",datetime.now(), start_time, 3)
#self.write_debug_gds("final.gds",False) # self.write_debug_gds("final_tree_router.gds",False)
# Did we route everything?? # Did we route everything??
if not self.check_all_routed(vdd_name): if not self.check_all_routed(vdd_name):
@ -92,48 +84,6 @@ class supply_tree_router(router):
return True return True
def check_all_routed(self, pin_name):
"""
Check that all pin groups are routed.
"""
for pg in self.pin_groups[pin_name]:
if not pg.is_routed():
return False
def prepare_blockages(self, pin_name):
"""
Reset and add all of the blockages in the design.
Names is a list of pins to add as a blockage.
"""
debug.info(3,"Preparing blockages.")
# Start fresh. Not the best for run-time, but simpler.
self.clear_blockages()
# This adds the initial blockges of the design
#print("BLOCKING:",self.blocked_grids)
self.set_blockages(self.blocked_grids,True)
# Block all of the pin components (some will be unblocked if they're a source/target)
# Also block the previous routes
for name in self.pin_groups:
blockage_grids = {y for x in self.pin_groups[name] for y in x.grids}
self.set_blockages(blockage_grids,True)
blockage_grids = {y for x in self.pin_groups[name] for y in x.blockages}
self.set_blockages(blockage_grids,True)
# FIXME: These duplicate a bit of work
# These are the paths that have already been routed.
self.set_blockages(self.path_blockages)
# Don't mark the other components as targets since we want to route
# directly to a rail, but unblock all the source components so we can
# route over them
blockage_grids = {y for x in self.pin_groups[pin_name] for y in x.grids}
self.set_blockages(blockage_grids,False)
def route_pins(self, pin_name): def route_pins(self, pin_name):
""" """
This will route each of the remaining pin components to the other pins. This will route each of the remaining pin components to the other pins.
@ -141,14 +91,45 @@ class supply_tree_router(router):
""" """
remaining_components = sum(not x.is_routed() for x in self.pin_groups[pin_name]) remaining_components = sum(not x.is_routed() for x in self.pin_groups[pin_name])
debug.info(1,"Maze routing {0} with {1} pin components to connect.".format(pin_name, debug.info(1,"Routing {0} with {1} pin components to connect.".format(pin_name,
remaining_components)) remaining_components))
for index,pg in enumerate(self.pin_groups[pin_name]): # Create full graph
if pg.is_routed(): debug.info(2,"Creating adjacency matrix")
continue pin_size = len(self.pin_groups[pin_name])
adj_matrix = [[0] * pin_size for i in range(pin_size)]
debug.info(1,"Routing component {0} {1}".format(pin_name, index)) for index1,pg1 in enumerate(self.pin_groups[pin_name]):
for index2,pg2 in enumerate(self.pin_groups[pin_name]):
if index1>=index2:
continue
dist = int(grid_utils.distance_set(list(pg1.grids)[0], pg2.grids))
adj_matrix[index1][index2] = dist
# Find MST
debug.info(2,"Finding MinimumSpanning Tree")
X = csr_matrix(adj_matrix)
Tcsr = minimum_spanning_tree(X)
mst = Tcsr.toarray().astype(int)
connections = []
for x in range(pin_size):
for y in range(pin_size):
if x >= y:
continue
if mst[x][y]>0:
connections.append((x, y))
# Route MST components
for (src, dest) in connections:
self.route_signal(pin_name, src, dest)
#self.write_debug_gds("final.gds", True)
#return
def route_signal(self, pin_name, src_idx, dest_idx):
for detour_scale in [5 * pow(2, x) for x in range(5)]:
debug.info(2, "Routing {0} to {1} with scale {2}".format(src_idx, dest_idx, detour_scale))
# Clear everything in the routing grid. # Clear everything in the routing grid.
self.rg.reinit() self.rg.reinit()
@ -159,35 +140,27 @@ class supply_tree_router(router):
# Add the single component of the pin as the source # Add the single component of the pin as the source
# which unmarks it as a blockage too # which unmarks it as a blockage too
self.add_pin_component_source(pin_name,index) self.add_pin_component_source(pin_name, src_idx)
# Marks all pin components except index as target # Marks all pin components except index as target
self.add_pin_component_target_except(pin_name,index) self.add_pin_component_target(pin_name, dest_idx)
# Add the prevous paths as a target too
self.add_path_target(self.paths)
print("SOURCE: ")
for k,v in self.rg.map.items():
if v.source:
print(k)
print("TARGET: ")
for k,v in self.rg.map.items():
if v.target:
print(k)
import pdb; pdb.set_trace()
if index==1:
self.write_debug_gds("debug{}.gds".format(pin_name),False)
# Actually run the A* router # Actually run the A* router
if not self.run_router(detour_scale=5): if self.run_router(detour_scale=detour_scale):
self.write_debug_gds("debug_route.gds",True) return
#if index==3 and pin_name=="vdd": self.write_debug_gds("debug_route.gds", True)
# self.write_debug_gds("route.gds",False)
def add_io_pin(self, instance, pin_name, new_name=""):
"""
Add a signle input or output pin up to metal 3.
"""
pin = instance.get_pins(pin_name)
if new_name == "":
new_name = pin_name
# Just use the power pin function for now to save code
self.add_power_pin(name=new_name, loc=pin.center(), start_layer=pin.layer)

View File

@ -1,62 +0,0 @@
# See LICENSE for licensing information.
#
# Copyright (c) 2016-2019 Regents of the University of California and The Board
# of Regents for the Oklahoma Agricultural and Mechanical College
# (acting for and on behalf of Oklahoma State University)
# All rights reserved.
#
#!/usr/bin/env python3
"Run a regresion test the library cells for DRC"
import unittest
from testutils import header,openram_test
import sys,os
sys.path.append(os.path.join(sys.path[0],"../.."))
sys.path.append(os.path.join(sys.path[0],".."))
import globals
import debug
OPTS = globals.OPTS
class no_blockages_test(openram_test):
"""
Simplest two pin route test with no blockages.
"""
def runTest(self):
globals.init_openram("config_{0}".format(OPTS.tech_name))
from gds_cell import gds_cell
from design import design
from signal_router import signal_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")
# Instantiate a GDS cell with the design
gds_file = "{0}/{1}.gds".format(os.path.dirname(os.path.realpath(__file__)),name)
cell = gds_cell(name, gds_file)
self.add_inst(name=name,
mod=cell,
offset=[0,0])
self.connect_inst([])
layer_stack =("metal1","via1","metal2")
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)
# fails if there are any DRC errors on any cells
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()

View File

@ -1,3 +0,0 @@
.SUBCKT cell
.ENDS cell

View File

@ -1,66 +0,0 @@
# See LICENSE for licensing information.
#
# Copyright (c) 2016-2019 Regents of the University of California and The Board
# of Regents for the Oklahoma Agricultural and Mechanical College
# (acting for and on behalf of Oklahoma State University)
# All rights reserved.
#
#!/usr/bin/env python3
"Run a regresion test the library cells for DRC"
import unittest
from testutils import header,openram_test
import sys,os
sys.path.append(os.path.join(sys.path[0],"../.."))
sys.path.append(os.path.join(sys.path[0],".."))
import globals
import debug
OPTS = globals.OPTS
class blockages_test(openram_test):
"""
Simple two pin route test with multilayer blockages.
"""
def runTest(self):
globals.init_openram("config_{0}".format(OPTS.tech_name))
from gds_cell import gds_cell
from design import design
from signal_router import signal_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")
# Instantiate a GDS cell with the design
gds_file = "{0}/{1}.gds".format(os.path.dirname(os.path.realpath(__file__)),name)
cell = gds_cell(name, gds_file)
self.add_inst(name=name,
mod=cell,
offset=[0,0])
self.connect_inst([])
layer_stack =("metal1","via1","metal2")
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)
# fails if there are any DRC errors on any cells
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()

View File

@ -1,3 +0,0 @@
.SUBCKT cell
.ENDS cell

View File

@ -1,61 +0,0 @@
# See LICENSE for licensing information.
#
# Copyright (c) 2016-2019 Regents of the University of California and The Board
# of Regents for the Oklahoma Agricultural and Mechanical College
# (acting for and on behalf of Oklahoma State University)
# All rights reserved.
#
#!/usr/bin/env python3
"Run a regresion test the library cells for DRC"
import unittest
from testutils import header,openram_test
import sys,os
sys.path.append(os.path.join(sys.path[0],"../.."))
sys.path.append(os.path.join(sys.path[0],".."))
import globals
import debug
OPTS = globals.OPTS
class same_layer_pins_test(openram_test):
"""
Checks two pins on the same layer with positive and negative coordinates.
"""
def runTest(self):
globals.init_openram("config_{0}".format(OPTS.tech_name))
from gds_cell import gds_cell
from design import design
from signal_router import signal_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")
# Instantiate a GDS cell with the design
gds_file = "{0}/{1}.gds".format(os.path.dirname(os.path.realpath(__file__)),name)
cell = gds_cell(name, gds_file)
self.add_inst(name=name,
mod=cell,
offset=[0,0])
self.connect_inst([])
layer_stack =("metal1","via1","metal2")
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)
# fails if there are any DRC errors on any cells
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()

View File

@ -1,3 +0,0 @@
.SUBCKT cell
.ENDS cell

View File

@ -1,63 +0,0 @@
# See LICENSE for licensing information.
#
# Copyright (c) 2016-2019 Regents of the University of California and The Board
# of Regents for the Oklahoma Agricultural and Mechanical College
# (acting for and on behalf of Oklahoma State University)
# All rights reserved.
#
#!/usr/bin/env python3
"Run a regresion test the library cells for DRC"
import unittest
from testutils import header,openram_test
import sys,os
sys.path.append(os.path.join(sys.path[0],"../.."))
sys.path.append(os.path.join(sys.path[0],".."))
import globals
import debug
OPTS = globals.OPTS
class diff_layer_pins_test(openram_test):
"""
Two pin route test with pins on different layers and blockages.
Pins are smaller than grid size.
"""
def runTest(self):
globals.init_openram("config_{0}".format(OPTS.tech_name))
from gds_cell import gds_cell
from design import design
from signal_router import signal_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")
# Instantiate a GDS cell with the design
gds_file = "{0}/{1}.gds".format(os.path.dirname(os.path.realpath(__file__)),name)
cell = gds_cell(name, gds_file)
self.add_inst(name=name,
mod=cell,
offset=[0,0])
self.connect_inst([])
layer_stack =("metal1","via1","metal2")
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)
# fails if there are any DRC errors on any cells
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()

View File

@ -1,3 +0,0 @@
.SUBCKT cell
.ENDS cell

View File

@ -1,65 +0,0 @@
# See LICENSE for licensing information.
#
# Copyright (c) 2016-2019 Regents of the University of California and The Board
# of Regents for the Oklahoma Agricultural and Mechanical College
# (acting for and on behalf of Oklahoma State University)
# All rights reserved.
#
#!/usr/bin/env python3
"Run a regresion test the library cells for DRC"
import unittest
from testutils import header,openram_test
import sys,os
sys.path.append(os.path.join(sys.path[0],"../.."))
sys.path.append(os.path.join(sys.path[0],".."))
import globals
import debug
OPTS = globals.OPTS
class two_nets_test(openram_test):
"""
Route two nets in the same GDS file. The routes will interact,
so they must block eachother.
"""
def runTest(self):
globals.init_openram("config_{0}".format(OPTS.tech_name))
from gds_cell import gds_cell
from design import design
from signal_router import signal_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")
# Instantiate a GDS cell with the design
gds_file = "{0}/{1}.gds".format(os.path.dirname(os.path.realpath(__file__)),name)
cell = gds_cell(name, gds_file)
self.add_inst(name=name,
mod=cell,
offset=[0,0])
self.connect_inst([])
layer_stack =("metal1","via1","metal2")
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)
# fails if there are any DRC errors on any cells
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()

View File

@ -1,3 +0,0 @@
.SUBCKT cell
.ENDS cell

View File

@ -1,72 +0,0 @@
# See LICENSE for licensing information.
#
# Copyright (c) 2016-2019 Regents of the University of California and The Board
# of Regents for the Oklahoma Agricultural and Mechanical College
# (acting for and on behalf of Oklahoma State University)
# All rights reserved.
#
#!/usr/bin/env python3
"Run a regresion test the library cells for DRC"
import unittest
from testutils import header,openram_test
import sys,os
sys.path.append(os.path.join(sys.path[0],"../.."))
sys.path.append(os.path.join(sys.path[0],".."))
import globals
import debug
OPTS = globals.OPTS
class pin_location_test(openram_test):
"""
Simplest two pin route test with no blockages using the pin locations instead of labels.
"""
def runTest(self):
globals.init_openram("config_{0}".format(OPTS.tech_name))
from gds_cell import gds_cell
from design import design
from signal_router import signal_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")
# Instantiate a GDS cell with the design
gds_file = "{0}/{1}.gds".format(os.path.dirname(os.path.realpath(__file__)),name)
cell = gds_cell(name, gds_file)
self.add_inst(name=name,
mod=cell,
offset=[0,0])
self.connect_inst([])
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(src=src_pin,dest=tgt_pin))
# This only works for freepdk45 since the coordinates are hard coded
if OPTS.tech_name == "freepdk45":
r = routing("06_pin_location_test_{0}".format(OPTS.tech_name))
self.local_drc_check(r)
else:
debug.warning("This test does not support technology {0}".format(OPTS.tech_name))
# fails if there are any DRC errors on any cells
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()

View File

@ -1,90 +0,0 @@
# See LICENSE for licensing information.
#
# Copyright (c) 2016-2019 Regents of the University of California and The Board
# of Regents for the Oklahoma Agricultural and Mechanical College
# (acting for and on behalf of Oklahoma State University)
# All rights reserved.
#
#!/usr/bin/env python3
"Run a regresion test the library cells for DRC"
import unittest
from testutils import header,openram_test
import sys,os
sys.path.append(os.path.join(sys.path[0],"../.."))
sys.path.append(os.path.join(sys.path[0],".."))
import globals
import debug
OPTS = globals.OPTS
class big_test(openram_test):
"""
Simplest two pin route test with no blockages using the pin locations instead of labels.
"""
def runTest(self):
globals.init_openram("config_{0}".format(OPTS.tech_name))
from gds_cell import gds_cell
from design import design
from signal_router import signal_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")
# Instantiate a GDS cell with the design
gds_file = "{0}/{1}.gds".format(os.path.dirname(os.path.realpath(__file__)),name)
cell = gds_cell(name, gds_file)
self.add_inst(name=name,
mod=cell,
offset=[0,0])
self.connect_inst([])
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'),
('out_1_2', 'a_1_0'),
('out_1_3', 'b_1_0'),
('out_1_0', 'a_1_1'),
('out_2_1', 'a_2_0'),
('out_2_2', 'b_2_0'),
('out_3_1', 'a_3_0'),
('out_3_2', 'b_3_0'),
('out_4_6', 'a_4_0'),
('out_4_7', 'b_4_0'),
('out_4_8', 'a_4_2'),
('out_4_9', 'b_4_2'),
('out_4_10', 'a_4_4'),
('out_4_11', 'b_4_4'),
('out_4_0', 'a_4_1'),
('out_4_2', 'b_4_1'),
('out_4_4', 'a_4_5'),
('out_4_1', 'a_4_3'),
('out_4_5', 'b_4_3')]
for (src,tgt) in connections:
self.assertTrue(r.route(src=src,dest=tgt))
# This test only runs on scn3me_subm tech
if OPTS.tech_name=="scn3me_subm":
r = routing("07_big_test_{0}".format(OPTS.tech_name))
self.local_drc_check(r)
else:
debug.warning("This test does not support technology {0}".format(OPTS.tech_name))
# fails if there are any DRC errors on any cells
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()

View File

@ -1,66 +0,0 @@
# See LICENSE for licensing information.
#
# Copyright (c) 2016-2019 Regents of the University of California and The Board
# of Regents for the Oklahoma Agricultural and Mechanical College
# (acting for and on behalf of Oklahoma State University)
# All rights reserved.
#
#!/usr/bin/env python3
"Run a regresion test the library cells for DRC"
import unittest
from testutils import header,openram_test
import sys,os
sys.path.append(os.path.join(sys.path[0],"../.."))
sys.path.append(os.path.join(sys.path[0],".."))
import globals
import debug
OPTS = globals.OPTS
class expand_region_test(openram_test):
"""
Test an infeasible route followed by a feasible route with an expanded region.
"""
def runTest(self):
globals.init_openram("config_{0}".format(OPTS.tech_name))
from gds_cell import gds_cell
from design import design
from signal_router import signal_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")
# Instantiate a GDS cell with the design
gds_file = "{0}/{1}.gds".format(os.path.dirname(os.path.realpath(__file__)),name)
cell = gds_cell(name, gds_file)
self.add_inst(name=name,
mod=cell,
offset=[0,0])
self.connect_inst([])
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(src="A",dest="B",detour_scale=1))
# This should be feasible because we allow it to detour
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)
# fails if there are any DRC errors on any cells
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()

View File

@ -1,56 +0,0 @@
# See LICENSE for licensing information.
#
# Copyright (c) 2016-2019 Regents of the University of California and The Board
# of Regents for the Oklahoma Agricultural and Mechanical College
# (acting for and on behalf of Oklahoma State University)
# All rights reserved.
#
#!/usr/bin/env python3
"Run a regresion test the library cells for DRC"
import unittest
from testutils import header,openram_test
import sys,os
sys.path.append(os.path.join(sys.path[0],".."))
import globals
import debug
OPTS = globals.OPTS
class no_blockages_test(openram_test):
"""
Simplest two pin route test with no blockages.
"""
def runTest(self):
globals.init_openram("config_{0}".format(OPTS.tech_name))
from supply_router import supply_router as router
if False:
from control_logic import control_logic
cell = control_logic(16)
layer_stack =("metal3","via3","metal4")
rtr=router(layer_stack, cell)
self.assertTrue(rtr.route())
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
self.local_check(cell,True)
# fails if there are any DRC errors on any cells
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()

View File

@ -1,16 +0,0 @@
# See LICENSE for licensing information.
#
# Copyright (c) 2016-2019 Regents of the University of California and The Board
# of Regents for the Oklahoma Agricultural and Mechanical College
# (acting for and on behalf of Oklahoma State University)
# All rights reserved.
#
word_size = 1
num_words = 16
tech_name = "freepdk45"
process_corners = ["TT"]
supply_voltages = [1.0]
temperatures = [25]

View File

@ -1,15 +0,0 @@
# See LICENSE for licensing information.
#
# Copyright (c) 2016-2019 Regents of the University of California and The Board
# of Regents for the Oklahoma Agricultural and Mechanical College
# (acting for and on behalf of Oklahoma State University)
# All rights reserved.
#
word_size = 1
num_words = 16
tech_name = "scn4m_subm"
process_corners = ["TT"]
supply_voltages = [5.0]
temperatures = [25]

View File

@ -1,23 +0,0 @@
# See LICENSE for licensing information.
#
# Copyright (c) 2016-2019 Regents of the University of California and The Board
# of Regents for the Oklahoma Agricultural and Mechanical College
# (acting for and on behalf of Oklahoma State University)
# All rights reserved.
#
from design import design
class gds_cell(design):
"""
A generic GDS design.
"""
def __init__(self, name, gds_file):
self.name = name
self.gds_file = gds_file
self.sp_file = None
design.__init__(self, name)
# The dimensions will not be defined, so do this...
self.width=0
self.height=0

View File

@ -1,46 +0,0 @@
# See LICENSE for licensing information.
#
# Copyright (c) 2016-2019 Regents of the University of California and The Board
# of Regents for the Oklahoma Agricultural and Mechanical College
# (acting for and on behalf of Oklahoma State University)
# All rights reserved.
#
#!/usr/bin/env python3
import re
import unittest
import sys,os
sys.path.append(os.path.join(sys.path[0],"../../compiler"))
import globals
(OPTS, args) = globals.parse_args()
del sys.argv[1:]
from testutils import header,openram_test
header(__file__, OPTS.tech_name)
# get a list of all files in the tests directory
files = os.listdir(sys.path[0])
# assume any file that ends in "test.py" in it is a regression test
nametest = re.compile("test\.py$", re.IGNORECASE)
tests = list(filter(nametest.search, files))
tests.sort()
# import all of the modules
filenameToModuleName = lambda f: os.path.splitext(f)[0]
moduleNames = map(filenameToModuleName, tests)
modules = map(__import__, moduleNames)
suite = unittest.TestSuite()
load = unittest.defaultTestLoader.loadTestsFromModule
suite.addTests(map(load, modules))
test_runner = unittest.TextTestRunner(verbosity=2,stream=sys.stderr)
test_result = test_runner.run(suite)
import verify
verify.print_drc_stats()
verify.print_lvs_stats()
verify.print_pex_stats()
sys.exit(not test_result.wasSuccessful())

View File

@ -1,265 +0,0 @@
# See LICENSE for licensing information.
#
# Copyright (c) 2016-2019 Regents of the University of California and The Board
# of Regents for the Oklahoma Agricultural and Mechanical College
# (acting for and on behalf of Oklahoma State University)
# All rights reserved.
#
import unittest,warnings
import sys,os,glob,copy
sys.path.append(os.path.join(sys.path[0],"../.."))
from globals import OPTS
import debug
class openram_test(unittest.TestCase):
""" Base unit test that we have some shared classes in. """
def local_drc_check(self, w):
self.reset()
tempgds = OPTS.openram_temp + "temp.gds"
w.gds_write(tempgds)
import verify
result=verify.run_drc(w.name, tempgds)
if result != 0:
self.fail("DRC failed: {}".format(w.name))
if not OPTS.keep_temp:
self.cleanup()
def local_check(self, a, final_verification=False):
self.reset()
tempspice = OPTS.openram_temp + "temp.sp"
tempgds = OPTS.openram_temp + "temp.gds"
a.sp_write(tempspice)
a.gds_write(tempgds)
import verify
result=verify.run_drc(a.name, tempgds)
if result != 0:
self.fail("DRC failed: {}".format(a.name))
result=verify.run_lvs(a.name, tempgds, tempspice, final_verification)
if result != 0:
self.fail("LVS mismatch: {}".format(a.name))
if not OPTS.keep_temp:
self.cleanup()
def cleanup(self):
""" Reset the duplicate checker and cleanup files. """
files = glob.glob(OPTS.openram_temp + '*')
for f in files:
# Only remove the files
if os.path.isfile(f):
os.remove(f)
def reset(self):
"""
Reset everything after each test.
"""
# Reset the static duplicate name checker for unit tests.
import hierarchy_design
hierarchy_design.hierarchy_design.name_map=[]
def check_golden_data(self, data, golden_data, error_tolerance=1e-2):
"""
This function goes through two dictionaries, key by key and compares
each item. It uses relative comparisons for the items and returns false
if there is a mismatch.
"""
# Check each result
data_matches = True
for k in data.keys():
if type(data[k])==list:
for i in range(len(data[k])):
if not self.isclose(k,data[k][i],golden_data[k][i],error_tolerance):
data_matches = False
else:
self.isclose(k,data[k],golden_data[k],error_tolerance)
if not data_matches:
import pprint
data_string=pprint.pformat(data)
debug.error("Results exceeded {:.1f}% tolerance compared to golden results:\n".format(error_tolerance*100)+data_string)
return data_matches
def isclose(self,key,value,actual_value,error_tolerance=1e-2):
""" This is used to compare relative values. """
import debug
relative_diff = self.relative_diff(value,actual_value)
check = relative_diff <= error_tolerance
if check:
debug.info(2,"CLOSE\t{0: <10}\t{1:.3f}\t{2:.3f}\tdiff={3:.1f}%".format(key,value,actual_value,relative_diff*100))
return True
else:
debug.error("NOT CLOSE\t{0: <10}\t{1:.3f}\t{2:.3f}\tdiff={3:.1f}%".format(key,value,actual_value,relative_diff*100))
return False
def relative_diff(self, value1, value2):
""" Compute the relative difference of two values and normalize to the largest.
If largest value is 0, just return the difference."""
# Edge case to avoid divide by zero
if value1==0 and value2==0:
return 0.0
# Don't need relative, exact compare
if value1==value2:
return 0.0
# Get normalization value
norm_value = abs(max(value1, value2))
# Edge case where greater is a zero
if norm_value == 0:
min_value = abs(min(value1, value2))
return abs(value1 - value2) / norm_value
def relative_compare(self, value,actual_value,error_tolerance):
""" This is used to compare relative values. """
if (value==actual_value): # if we don't need a relative comparison!
return True
return (abs(value - actual_value) / max(value,actual_value) <= error_tolerance)
def isapproxdiff(self, filename1, filename2, error_tolerance=0.001):
"""Compare two files.
Arguments:
filename1 -- First file name
filename2 -- Second file name
Return value:
True if the files are the same, False otherwise.
"""
import re
import debug
numeric_const_pattern = r"""
[-+]? # optional sign
(?:
(?: \d* \. \d+ ) # .1 .12 .123 etc 9.1 etc 98.1 etc
|
(?: \d+ \.? ) # 1. 12. 123. etc 1 12 123 etc
)
# followed by optional exponent part if desired
(?: [Ee] [+-]? \d+ ) ?
"""
rx = re.compile(numeric_const_pattern, re.VERBOSE)
fp1 = open(filename1, 'rb')
fp2 = open(filename2, 'rb')
mismatches=0
line_num=0
while True:
line_num+=1
line1 = fp1.readline().decode('utf-8')
line2 = fp2.readline().decode('utf-8')
#print("line1:",line1)
#print("line2:",line2)
# 1. Find all of the floats using a regex
line1_floats=rx.findall(line1)
line2_floats=rx.findall(line2)
debug.info(3,"line1_floats: "+str(line1_floats))
debug.info(3,"line2_floats: "+str(line2_floats))
# 2. Remove the floats from the string
for f in line1_floats:
line1=line1.replace(f,"",1)
for f in line2_floats:
line2=line2.replace(f,"",1)
#print("line1:",line1)
#print("line2:",line2)
# 3. Convert to floats rather than strings
line1_floats = [float(x) for x in line1_floats]
line2_floats = [float(x) for x in line1_floats]
# 4. Check if remaining string matches
if line1 != line2:
if mismatches==0:
debug.error("Mismatching files:\nfile1={0}\nfile2={1}".format(filename1,filename2))
mismatches += 1
debug.error("MISMATCH Line ({0}):\n{1}\n!=\n{2}".format(line_num,line1.rstrip('\n'),line2.rstrip('\n')))
# 5. Now compare that the floats match
elif len(line1_floats)!=len(line2_floats):
if mismatches==0:
debug.error("Mismatching files:\nfile1={0}\nfile2={1}".format(filename1,filename2))
mismatches += 1
debug.error("MISMATCH Line ({0}) Length {1} != {2}".format(line_num,len(line1_floats),len(line2_floats)))
else:
for (float1,float2) in zip(line1_floats,line2_floats):
relative_diff = self.relative_diff(float1,float2)
check = relative_diff <= error_tolerance
if not check:
if mismatches==0:
debug.error("Mismatching files:\nfile1={0}\nfile2={1}".format(filename1,filename2))
mismatches += 1
debug.error("MISMATCH Line ({0}) Float {1} != {2} diff: {3:.1f}%".format(line_num,float1,float2,relative_diff*100))
# Only show the first 10 mismatch lines
if not line1 and not line2 or mismatches>10:
fp1.close()
fp2.close()
return mismatches==0
# Never reached
return False
def isdiff(self,filename1,filename2):
""" This is used to compare two files and display the diff if they are different.. """
import debug
import filecmp
import difflib
check = filecmp.cmp(filename1,filename2)
if not check:
debug.error("MISMATCH file1={0} file2={1}".format(filename1,filename2))
f1 = open(filename1,"r")
s1 = f1.readlines().decode('utf-8')
f1.close()
f2 = open(filename2,"r").decode('utf-8')
s2 = f2.readlines()
f2.close()
mismatches=0
for line in difflib.unified_diff(s1, s2):
mismatches += 1
self.error("DIFF LINES:",line)
if mismatches>10:
return False
return False
else:
debug.info(2,"MATCH {0} {1}".format(filename1,filename2))
return True
def header(filename, technology):
# Skip the header for gitlab regression
import getpass
if getpass.getuser() == "gitlab-runner":
return
tst = "Running Test for:"
print("\n")
print(" ______________________________________________________________________________ ")
print("|==============================================================================|")
print("|=========" + tst.center(60) + "=========|")
print("|=========" + technology.center(60) + "=========|")
print("|=========" + filename.center(60) + "=========|")
from globals import OPTS
print("|=========" + OPTS.openram_temp.center(60) + "=========|")
print("|==============================================================================|")

View File

@ -87,26 +87,6 @@ class sram():
def save(self): def save(self):
""" Save all the output files while reporting time to do it as well. """ """ 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"
debug.print_raw("GDS: Writing to {0}".format(gdsname))
self.gds_write(gdsname)
verify.write_drc_script(cell_name=self.s.name,
gds_name=os.path.basename(gdsname),
extract=True,
final_verification=True,
output_path=OPTS.output_path)
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"
debug.print_raw("LEF: Writing to {0}".format(lefname))
self.lef_write(lefname)
print_time("LEF", datetime.datetime.now(), start_time)
# Save the spice file # Save the spice file
start_time = datetime.datetime.now() start_time = datetime.datetime.now()
spname = OPTS.output_path + self.s.name + ".sp" spname = OPTS.output_path + self.s.name + ".sp"
@ -118,12 +98,33 @@ class sram():
output_path=OPTS.output_path) output_path=OPTS.output_path)
print_time("Spice writing", datetime.datetime.now(), start_time) print_time("Spice writing", 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"
debug.print_raw("GDS: Writing to {0}".format(gdsname))
self.gds_write(gdsname)
if OPTS.check_lvsdrc:
verify.write_drc_script(cell_name=self.s.name,
gds_name=os.path.basename(gdsname),
extract=True,
final_verification=True,
output_path=OPTS.output_path)
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"
debug.print_raw("LEF: Writing to {0}".format(lefname))
self.lef_write(lefname)
print_time("LEF", datetime.datetime.now(), start_time)
# Save the LVS file # Save the LVS file
start_time = datetime.datetime.now() start_time = datetime.datetime.now()
lvsname = OPTS.output_path + self.s.name + ".lvs.sp" lvsname = OPTS.output_path + self.s.name + ".lvs.sp"
debug.print_raw("LVS: Writing to {0}".format(lvsname)) debug.print_raw("LVS: Writing to {0}".format(lvsname))
self.lvs_write(lvsname) self.lvs_write(lvsname)
if not OPTS.netlist_only: if not OPTS.netlist_only and OPTS.check_lvsdrc:
verify.write_lvs_script(cell_name=self.s.name, verify.write_lvs_script(cell_name=self.s.name,
gds_name=os.path.basename(gdsname), gds_name=os.path.basename(gdsname),
sp_name=os.path.basename(lvsname), sp_name=os.path.basename(lvsname),

View File

@ -5,12 +5,12 @@
# (acting for and on behalf of Oklahoma State University) # (acting for and on behalf of Oklahoma State University)
# All rights reserved. # All rights reserved.
# #
import debug
from vector import vector from vector import vector
from sram_base import sram_base from sram_base import sram_base
from contact import m2_via from contact import m2_via
from channel_route import channel_route
from signal_escape_router import signal_escape_router as router
from globals import OPTS from globals import OPTS
import channel_route
class sram_1bank(sram_base): class sram_1bank(sram_base):
@ -102,12 +102,17 @@ class sram_1bank(sram_base):
# Place with an initial wide channel (from above) # Place with an initial wide channel (from above)
self.place_dffs() self.place_dffs()
# Route the channel and set to the new data bus size # Route the channel and set to the new data bus size
# We need to temporarily add some pins for the x offsets
# but we'll remove them so that they have the right y
# offsets after the DFF placement.
self.add_layout_pins(add_vias=False)
self.route_dffs(add_routes=False) self.route_dffs(add_routes=False)
self.remove_layout_pins()
# Re-place with the new channel size # Re-place with the new channel size
self.place_dffs() self.place_dffs()
# Now route the channel
self.route_dffs()
def place_row_addr_dffs(self): def place_row_addr_dffs(self):
""" """
@ -241,14 +246,13 @@ class sram_1bank(sram_base):
self.data_pos[port] = vector(x_offset, y_offset) self.data_pos[port] = vector(x_offset, y_offset)
self.spare_wen_pos[port] = vector(x_offset, y_offset) self.spare_wen_pos[port] = vector(x_offset, y_offset)
def add_layout_pins(self): def route_escape_pins(self):
""" """
Add the top-level pins for a single bank SRAM with control. Add the top-level pins for a single bank SRAM with control.
""" """
highest_coord = self.find_highest_coords()
lowest_coord = self.find_lowest_coords()
bbox = [lowest_coord, highest_coord]
# List of pin to new pin name
pins_to_route = []
for port in self.all_ports: for port in self.all_ports:
# Depending on the port, use the bottom/top or left/right sides # Depending on the port, use the bottom/top or left/right sides
# Port 0 is left/bottom # Port 0 is left/bottom
@ -258,132 +262,127 @@ class sram_1bank(sram_base):
# Connect the control pins as inputs # Connect the control pins as inputs
for signal in self.control_logic_inputs[port]: for signal in self.control_logic_inputs[port]:
if signal == "clk":
continue
if signal.startswith("rbl"): if signal.startswith("rbl"):
continue continue
if OPTS.perimeter_pins: if signal=="clk":
self.add_perimeter_pin(name=signal + "{}".format(port), pins_to_route.append(("{0}{1}".format(signal, port), bottom_or_top))
pin=self.control_logic_insts[port].get_pin(signal),
side=left_or_right,
bbox=bbox)
else: else:
self.copy_layout_pin(self.control_logic_insts[port], pins_to_route.append(("{0}{1}".format(signal, port), left_or_right))
signal,
signal + "{}".format(port))
if OPTS.perimeter_pins:
self.add_perimeter_pin(name="clk{}".format(port),
pin=self.control_logic_insts[port].get_pin("clk"),
side=bottom_or_top,
bbox=bbox)
else:
self.copy_layout_pin(self.control_logic_insts[port],
"clk",
"clk{}".format(port))
# Data input pins go to BOTTOM/TOP
din_ports = []
if port in self.write_ports: if port in self.write_ports:
for bit in range(self.word_size + self.num_spare_cols): for bit in range(self.word_size + self.num_spare_cols):
if OPTS.perimeter_pins: pins_to_route.append(("din{0}[{1}]".format(port, bit), bottom_or_top))
p = self.add_perimeter_pin(name="din{0}[{1}]".format(port, bit),
pin=self.data_dff_insts[port].get_pin("din_{0}".format(bit)),
side=bottom_or_top,
bbox=bbox)
din_ports.append(p)
else:
self.copy_layout_pin(self.data_dff_insts[port],
"din_{}".format(bit),
"din{0}[{1}]".format(port, bit))
# Data output pins go to BOTTOM/TOP if port in self.readwrite_ports or port in self.read_ports:
if port in self.readwrite_ports and OPTS.perimeter_pins:
for bit in range(self.word_size + self.num_spare_cols): for bit in range(self.word_size + self.num_spare_cols):
# This should be routed next to the din pin pins_to_route.append(("dout{0}[{1}]".format(port, bit), bottom_or_top))
p = din_ports[bit]
self.add_layout_pin_rect_center(text="dout{0}[{1}]".format(port, bit),
layer=p.layer,
offset=p.center() + vector(self.m3_pitch, 0),
width=p.width(),
height=p.height())
elif port in self.read_ports:
for bit in range(self.word_size + self.num_spare_cols):
if OPTS.perimeter_pins:
# This should have a clear route to the perimeter if there are no din routes
self.add_perimeter_pin(name="dout{0}[{1}]".format(port, bit),
pin=self.bank_inst.get_pin("dout{0}_{1}".format(port, bit)),
side=bottom_or_top,
bbox=bbox)
else:
self.copy_layout_pin(self.bank_inst,
"dout{0}_{1}".format(port, bit),
"dout{0}[{1}]".format(port, bit))
# Lower address bits go to BOTTOM/TOP
for bit in range(self.col_addr_size): for bit in range(self.col_addr_size):
if OPTS.perimeter_pins: pins_to_route.append(("addr{0}[{1}]".format(port, bit), bottom_or_top))
self.add_perimeter_pin(name="addr{0}[{1}]".format(port, bit),
pin=self.col_addr_dff_insts[port].get_pin("din_{}".format(bit)),
side=bottom_or_top,
bbox=bbox)
else:
self.copy_layout_pin(self.col_addr_dff_insts[port],
"din_{}".format(bit),
"addr{0}[{1}]".format(port, bit))
# Upper address bits go to LEFT/RIGHT
for bit in range(self.row_addr_size): for bit in range(self.row_addr_size):
if OPTS.perimeter_pins: pins_to_route.append(("addr{0}[{1}]".format(port, bit + self.col_addr_size), left_or_right))
self.add_perimeter_pin(name="addr{0}[{1}]".format(port, bit + self.col_addr_size),
pin=self.row_addr_dff_insts[port].get_pin("din_{}".format(bit)),
side=left_or_right,
bbox=bbox)
else:
self.copy_layout_pin(self.row_addr_dff_insts[port],
"din_{}".format(bit),
"addr{0}[{1}]".format(port, bit + self.col_addr_size))
# Write mask pins go to BOTTOM/TOP
if port in self.write_ports: if port in self.write_ports:
if self.write_size: if self.write_size:
for bit in range(self.num_wmasks): for bit in range(self.num_wmasks):
if OPTS.perimeter_pins: pins_to_route.append(("wmask{0}[{1}]".format(port, bit), bottom_or_top))
self.add_perimeter_pin(name="wmask{0}[{1}]".format(port, bit),
pin=self.wmask_dff_insts[port].get_pin("din_{}".format(bit)),
side=bottom_or_top,
bbox=bbox)
else:
self.copy_layout_pin(self.wmask_dff_insts[port],
"din_{}".format(bit),
"wmask{0}[{1}]".format(port, bit))
# Spare wen pins go to BOTTOM/TOP
if port in self.write_ports: if port in self.write_ports:
for bit in range(self.num_spare_cols): for bit in range(self.num_spare_cols):
if OPTS.perimeter_pins: pins_to_route.append(("spare_wen{0}[{1}]".format(port, bit), bottom_or_top))
self.add_perimeter_pin(name="spare_wen{0}[{1}]".format(port, bit),
pin=self.spare_wen_dff_insts[port].get_pin("din_{}".format(bit)), rtr=router(self.m3_stack, self)
side=left_or_right, rtr.escape_route(pins_to_route)
bbox=bbox)
else: def add_layout_pins(self, add_vias=True):
self.copy_layout_pin(self.spare_wen_dff_insts[port], """
"din_{}".format(bit), Add the top-level pins for a single bank SRAM with control.
"spare_wen{0}[{1}]".format(port, bit)) """
for port in self.all_ports:
# Hack: If we are escape routing, set the pin layer to
# None so that we will start from the pin layer
# Otherwise, set it as the pin layer so that no vias are added.
# Otherwise, when we remove pins to move the dff array dynamically,
# we will leave some remaining vias when the pin locations change.
if add_vias:
pin_layer = None
else:
pin_layer = self.pwr_grid_layer
# Connect the control pins as inputs
for signal in self.control_logic_inputs[port]:
if signal.startswith("rbl"):
continue
self.add_io_pin(self.control_logic_insts[port],
signal,
signal + "{}".format(port),
start_layer=pin_layer)
if port in self.write_ports:
for bit in range(self.word_size + self.num_spare_cols):
self.add_io_pin(self.data_dff_insts[port],
"din_{}".format(bit),
"din{0}[{1}]".format(port, bit),
start_layer=pin_layer)
if port in self.readwrite_ports or port in self.read_ports:
for bit in range(self.word_size + self.num_spare_cols):
self.add_io_pin(self.bank_inst,
"dout{0}_{1}".format(port, bit),
"dout{0}[{1}]".format(port, bit),
start_layer=pin_layer)
for bit in range(self.col_addr_size):
self.add_io_pin(self.col_addr_dff_insts[port],
"din_{}".format(bit),
"addr{0}[{1}]".format(port, bit),
start_layer=pin_layer)
for bit in range(self.row_addr_size):
self.add_io_pin(self.row_addr_dff_insts[port],
"din_{}".format(bit),
"addr{0}[{1}]".format(port, bit + self.col_addr_size),
start_layer=pin_layer)
if port in self.write_ports:
if self.write_size:
for bit in range(self.num_wmasks):
self.add_io_pin(self.wmask_dff_insts[port],
"din_{}".format(bit),
"wmask{0}[{1}]".format(port, bit),
start_layer=pin_layer)
if port in self.write_ports:
for bit in range(self.num_spare_cols):
self.add_io_pin(self.spare_wen_dff_insts[port],
"din_{}".format(bit),
"spare_wen{0}[{1}]".format(port, bit),
start_layer=pin_layer)
def route_layout(self): def route_layout(self):
""" Route a single bank SRAM """ """ Route a single bank SRAM """
self.add_layout_pins()
self.route_clk() self.route_clk()
self.route_control_logic() self.route_control_logic()
self.route_row_addr_dff() self.route_row_addr_dff()
self.route_dffs()
# We add the vias to M3 before routing supplies because
# they might create some blockages
self.add_layout_pins()
# Route the supplies first since the MST is not blockage aware
# and signals can route to anywhere on sides (it is flexible)
self.route_supplies()
# Route the pins to the perimeter
if OPTS.perimeter_pins:
self.route_escape_pins()
def route_dffs(self, add_routes=True): def route_dffs(self, add_routes=True):
for port in self.all_ports: for port in self.all_ports:
@ -402,25 +401,6 @@ class sram_1bank(sram_base):
bank_pins = [self.bank_inst.get_pin(x) for x in bank_names] bank_pins = [self.bank_inst.get_pin(x) for x in bank_names]
route_map.extend(list(zip(bank_pins, dff_pins))) route_map.extend(list(zip(bank_pins, dff_pins)))
if port == 0:
offset = vector(self.control_logic_insts[port].rx() + self.dff.width,
- self.data_bus_size[port] + 2 * self.m1_pitch)
else:
offset = vector(0,
self.bank.height + 2 * self.m1_space)
cr = channel_route.channel_route(netlist=route_map,
offset=offset,
layer_stack=self.m1_stack,
parent=self)
if add_routes:
self.add_inst(cr.name, cr)
self.connect_inst([])
else:
self.col_addr_bus_size[port] = cr.height
route_map = []
# wmask dff # wmask dff
if self.num_wmasks > 0 and port in self.write_ports: if self.num_wmasks > 0 and port in self.write_ports:
dff_names = ["dout_{}".format(x) for x in range(self.num_wmasks)] dff_names = ["dout_{}".format(x) for x in range(self.num_wmasks)]
@ -437,16 +417,6 @@ class sram_1bank(sram_base):
bank_pins = [self.bank_inst.get_pin(x) for x in bank_names] bank_pins = [self.bank_inst.get_pin(x) for x in bank_names]
route_map.extend(list(zip(bank_pins, dff_pins))) route_map.extend(list(zip(bank_pins, dff_pins)))
if port in self.readwrite_ports and OPTS.perimeter_pins:
# outputs from sense amp
# These are the output pins which had their pin placed on the perimeter, so route from the
# sense amp which should not align with write driver input
sram_names = ["dout{0}[{1}]".format(port, x) for x in range(self.word_size + self.num_spare_cols)]
sram_pins = [self.get_pin(x) for x in sram_names]
bank_names = ["dout{0}_{1}".format(port, x) for x in range(self.word_size + self.num_spare_cols)]
bank_pins = [self.bank_inst.get_pin(x) for x in bank_names]
route_map.extend(list(zip(bank_pins, sram_pins)))
# spare wen dff # spare wen dff
if self.num_spare_cols > 0 and port in self.write_ports: if self.num_spare_cols > 0 and port in self.write_ports:
dff_names = ["dout_{}".format(x) for x in range(self.num_spare_cols)] dff_names = ["dout_{}".format(x) for x in range(self.num_spare_cols)]
@ -464,26 +434,32 @@ class sram_1bank(sram_base):
if port == 0: if port == 0:
offset = vector(self.control_logic_insts[port].rx() + self.dff.width, offset = vector(self.control_logic_insts[port].rx() + self.dff.width,
- self.data_bus_size[port] + 2 * self.m1_pitch) - self.data_bus_size[port] + 2 * self.m3_pitch)
cr = channel_route.channel_route(netlist=route_map, cr = channel_route(netlist=route_map,
offset=offset, offset=offset,
layer_stack=layer_stack, layer_stack=layer_stack,
parent=self) parent=self)
if add_routes: if add_routes:
self.add_inst(cr.name, cr) # This causes problem in magic since it sometimes cannot extract connectivity of isntances
self.connect_inst([]) # with no active devices.
# self.add_inst(cr.name, cr)
# self.connect_inst([])
self.add_flat_inst(cr.name, cr)
else: else:
self.data_bus_size[port] = max(cr.height, self.col_addr_bus_size[port]) + self.data_bus_gap self.data_bus_size[port] = max(cr.height, self.col_addr_bus_size[port]) + self.data_bus_gap
else: else:
offset = vector(0, offset = vector(0,
self.bank.height + 2 * self.m1_space) self.bank.height + self.m3_pitch)
cr = channel_route.channel_route(netlist=route_map, cr = channel_route(netlist=route_map,
offset=offset, offset=offset,
layer_stack=layer_stack, layer_stack=layer_stack,
parent=self) parent=self)
if add_routes: if add_routes:
self.add_inst(cr.name, cr) # This causes problem in magic since it sometimes cannot extract connectivity of isntances
self.connect_inst([]) # with no active devices.
# self.add_inst(cr.name, cr)
# self.connect_inst([])
self.add_flat_inst(cr.name, cr)
else: else:
self.data_bus_size[port] = max(cr.height, self.col_addr_bus_size[port]) + self.data_bus_gap self.data_bus_size[port] = max(cr.height, self.col_addr_bus_size[port]) + self.data_bus_gap

View File

@ -190,13 +190,13 @@ class sram_base(design, verilog, lef):
start_time = datetime.datetime.now() start_time = datetime.datetime.now()
self.route_layout() self.route_layout()
self.route_supplies()
if not OPTS.is_unit_test: if not OPTS.is_unit_test:
print_time("Routing", datetime.datetime.now(), start_time) print_time("Routing", datetime.datetime.now(), start_time)
self.add_lvs_correspondence_points() self.add_lvs_correspondence_points()
self.offset_all_coordinates() #self.offset_all_coordinates()
highest_coord = self.find_highest_coords() highest_coord = self.find_highest_coords()
self.width = highest_coord[0] self.width = highest_coord[0]
@ -207,9 +207,10 @@ class sram_base(design, verilog, lef):
ur=vector(self.width, self.height)) ur=vector(self.width, self.height))
start_time = datetime.datetime.now() start_time = datetime.datetime.now()
# We only enable final verification if we have routed the design
self.DRC_LVS(final_verification=OPTS.route_supplies, force_check=OPTS.check_lvsdrc)
if not OPTS.is_unit_test: if not OPTS.is_unit_test:
# We only enable final verification if we have routed the design
# Only run this if not a unit test, because unit test will also verify it.
self.DRC_LVS(final_verification=OPTS.route_supplies, force_check=OPTS.check_lvsdrc)
print_time("Verification", datetime.datetime.now(), start_time) print_time("Verification", datetime.datetime.now(), start_time)
def create_modules(self): def create_modules(self):
@ -220,31 +221,49 @@ class sram_base(design, verilog, lef):
# Copy the pins to the top level # Copy the pins to the top level
# This will either be used to route or left unconnected. # This will either be used to route or left unconnected.
for inst in self.insts: for pin_name in ["vdd", "gnd"]:
self.copy_power_pins(inst, "vdd") for inst in self.insts:
self.copy_power_pins(inst, "gnd") self.copy_power_pins(inst, pin_name)
if not OPTS.route_supplies: if not OPTS.route_supplies:
# Do not route the power supply (leave as must-connect pins) # Do not route the power supply (leave as must-connect pins)
return return
grid_stack = set()
try: try:
from tech import power_grid from tech import power_grid
grid_stack = power_grid grid_stack = power_grid
except ImportError: except ImportError:
# if no power_grid is specified by tech we use sensible defaults # if no power_grid is specified by tech we use sensible defaults
import tech # Route a M3/M4 grid
if "m4" in tech.layer: grid_stack = self.m3_stack
# Route a M3/M4 grid
grid_stack = self.m3_stack if OPTS.route_supplies == "grid":
elif "m3" in tech.layer: from supply_grid_router import supply_grid_router as router
grid_stack =("m3",) elif OPTS.route_supplies:
from supply_tree_router import supply_tree_router as router
from supply_grid_router import supply_grid_router as router
rtr=router(grid_stack, self) rtr=router(grid_stack, self)
rtr.route() rtr.route()
# Find the lowest leftest pin for vdd and gnd
for pin_name in ["vdd", "gnd"]:
# Copy the pin shape to rectangles
for pin in self.get_pins(pin_name):
self.add_rect(pin.layer,
pin.ll(),
pin.width(),
pin.height())
# Remove the pins
self.remove_layout_pin(pin_name)
pin = rtr.get_pin(pin_name)
self.add_layout_pin(pin_name,
pin.layer,
pin.ll(),
pin.width(),
pin.height())
def compute_bus_sizes(self): def compute_bus_sizes(self):
""" Compute the independent bus widths shared between two and four bank SRAMs """ """ Compute the independent bus widths shared between two and four bank SRAMs """

View File

@ -27,8 +27,8 @@ class bitcell_array_1rw_1r_test(openram_test):
OPTS.num_w_ports = 0 OPTS.num_w_ports = 0
globals.setup_bitcell() globals.setup_bitcell()
debug.info(2, "Testing 4x4 array for cell_1rw_1r") debug.info(2, "Testing 2x2 array for cell_2port")
a = factory.create(module_type="bitcell_array", cols=4, rows=4) a = factory.create(module_type="bitcell_array", cols=2, rows=2)
self.local_check(a) self.local_check(a)
globals.end_openram() globals.end_openram()

View File

@ -25,7 +25,7 @@ class replica_bitcell_array_test(openram_test):
factory.reset() factory.reset()
debug.info(2, "Testing 4x4 array for bitcell") debug.info(2, "Testing 4x4 array for bitcell")
a = factory.create(module_type="replica_bitcell_array", cols=4, rows=4, rbl=[1, 0]) a = factory.create(module_type="replica_bitcell_array", cols=7, rows=5, rbl=[1, 0])
self.local_check(a) self.local_check(a)
globals.end_openram() globals.end_openram()

View File

@ -23,6 +23,7 @@ class sram_1bank_2mux_1rw_1r_test(openram_test):
globals.init_openram(config_file) globals.init_openram(config_file)
from sram_config import sram_config from sram_config import sram_config
OPTS.route_supplies = False
OPTS.num_rw_ports = 1 OPTS.num_rw_ports = 1
OPTS.num_r_ports = 1 OPTS.num_r_ports = 1
OPTS.num_w_ports = 0 OPTS.num_w_ports = 0

View File

@ -66,7 +66,9 @@ class model_delay_test(openram_test):
# Only compare the delays # Only compare the delays
spice_delays = {key:value for key, value in spice_data.items() if 'delay' in key} spice_delays = {key:value for key, value in spice_data.items() if 'delay' in key}
spice_delays['min_period'] = spice_data['min_period']
model_delays = {key:value for key, value in model_data.items() if 'delay' in key} model_delays = {key:value for key, value in model_data.items() if 'delay' in key}
model_delays['min_period'] = model_data['min_period']
debug.info(1,"Spice Delays={}".format(spice_delays)) debug.info(1,"Spice Delays={}".format(spice_delays))
debug.info(1,"Model Delays={}".format(model_delays)) debug.info(1,"Model Delays={}".format(model_delays))

View File

@ -17,6 +17,7 @@ from globals import OPTS
import debug import debug
@unittest.skip("SKIPPING 26_hspice_pex_pinv_test")
class hspice_pex_pinv_test(openram_test): class hspice_pex_pinv_test(openram_test):
def runTest(self): def runTest(self):
@ -39,10 +40,10 @@ class hspice_pex_pinv_test(openram_test):
OPTS.keep_temp = True OPTS.keep_temp = True
debug.info(2, "Checking 1x size inverter") debug.info(2, "Checking 1x size inverter")
tx = pinv.pinv(name="pinv", size=1) tx = pinv.pinv(name="pinv", size=1)
tempgds = "{0}{1}.gds".format(OPTS.openram_temp, tx.name) tempgds = "{}.gds".format(tx.name)
tx.gds_write(tempgds) tx.gds_write("{0}{1}".format(OPTS.openram_temp, tempgds))
tempsp = "{0}{1}.sp".format(OPTS.openram_temp, tx.name) tempsp = "{}.sp".format(tx.name)
tx.sp_write(tempsp) tx.sp_write("{0}{1}".format(OPTS.openram_temp, tempsp))
# make sure that the library simulation is successful # make sure that the library simulation is successful
sp_delay = self.simulate_delay(test_module=tempsp, sp_delay = self.simulate_delay(test_module=tempsp,
@ -74,7 +75,7 @@ class hspice_pex_pinv_test(openram_test):
def simulate_delay(self, test_module, top_level_name): def simulate_delay(self, test_module, top_level_name):
from charutils import parse_spice_list from charutils import parse_spice_list
# setup simulation # setup simulation
sim_file = OPTS.openram_temp + "stim.sp" sim_file = "stim.sp"
log_file_name = "timing" log_file_name = "timing"
test_sim = self.write_simulation(sim_file, test_module, top_level_name) test_sim = self.write_simulation(sim_file, test_module, top_level_name)
test_sim.run_sim("stim.sp") test_sim.run_sim("stim.sp")
@ -86,7 +87,7 @@ class hspice_pex_pinv_test(openram_test):
import tech import tech
from characterizer import measurements, stimuli from characterizer import measurements, stimuli
corner = (OPTS.process_corners[0], OPTS.supply_voltages[0], OPTS.temperatures[0]) corner = (OPTS.process_corners[0], OPTS.supply_voltages[0], OPTS.temperatures[0])
sim_file = open(sim_file, "w") sim_file = open(OPTS.openram_temp + sim_file, "w")
simulation = stimuli(sim_file, corner) simulation = stimuli(sim_file, corner)
# library files # library files

View File

@ -16,7 +16,7 @@ import globals
from globals import OPTS from globals import OPTS
import debug import debug
@unittest.skip("SKIPPING 26_ngspice_pex_pinv_test")
class ngspice_pex_pinv_test(openram_test): class ngspice_pex_pinv_test(openram_test):
def runTest(self): def runTest(self):
config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME")) config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME"))
@ -37,10 +37,10 @@ class ngspice_pex_pinv_test(openram_test):
OPTS.keep_temp = True # force set keep to true to save the sp file OPTS.keep_temp = True # force set keep to true to save the sp file
debug.info(2, "Checking 1x size inverter") debug.info(2, "Checking 1x size inverter")
tx = pinv.pinv(name="pinv", size=1) tx = pinv.pinv(name="pinv", size=1)
tempgds = "{0}{1}.gds".format(OPTS.openram_temp, tx.name) tempgds = "{}.gds".format(tx.name)
tx.gds_write(tempgds) tx.gds_write("{0}{1}".format(OPTS.openram_temp, tempgds))
tempsp = "{0}{1}.sp".format(OPTS.openram_temp, tx.name) tempsp = "{}.sp".format(tx.name)
tx.sp_write(tempsp) tx.sp_write("{0}{1}".format(OPTS.openram_temp, tempsp))
# make sure that the library simulation is successful # make sure that the library simulation is successful
sp_delay = self.simulate_delay(test_module=tempsp, sp_delay = self.simulate_delay(test_module=tempsp,
@ -72,12 +72,17 @@ class ngspice_pex_pinv_test(openram_test):
def simulate_delay(self, test_module, top_level_name): def simulate_delay(self, test_module, top_level_name):
from charutils import parse_spice_list from charutils import parse_spice_list
cwd = os.getcwd()
os.chdir(OPTS.openram_temp)
# setup simulation # setup simulation
sim_file = OPTS.openram_temp + "stim.sp" sim_file = "stim.sp"
log_file_name = "timing" log_file_name = "timing"
test_sim = self.write_simulation(sim_file, test_module, top_level_name) test_sim = self.write_simulation(sim_file, test_module, top_level_name)
test_sim.run_sim("stim.sp") test_sim.run_sim("stim.sp")
delay = parse_spice_list(log_file_name, "pinv_delay") delay = parse_spice_list(log_file_name, "pinv_delay")
os.chdir(cwd)
return delay return delay
def write_simulation(self, sim_file, cir_file, top_module_name): def write_simulation(self, sim_file, cir_file, top_module_name):
@ -89,6 +94,7 @@ class ngspice_pex_pinv_test(openram_test):
simulation = stimuli(sim_file, corner) simulation = stimuli(sim_file, corner)
# library files # library files
import pdb; pdb.set_trace()
simulation.write_include(cir_file) simulation.write_include(cir_file)
# supply voltages # supply voltages

View File

@ -53,18 +53,24 @@ class openram_back_end_test(openram_test):
else: else:
exe_name = "coverage run -p {0}/openram.py ".format(OPENRAM_HOME) exe_name = "coverage run -p {0}/openram.py ".format(OPENRAM_HOME)
config_name = "{0}/tests/configs/config_back_end.py".format(OPENRAM_HOME) config_name = "{0}/tests/configs/config_back_end.py".format(OPENRAM_HOME)
cmd = "{0} -n -o {1} -p {2} {3} {4} 2>&1 > {5}/output.log".format(exe_name, cmd = "{0} -o {1} -p {2} {3} {4} 2>&1 > {5}/output.log".format(exe_name,
out_file, out_file,
out_path, out_path,
options, options,
config_name, config_name,
out_path) out_path)
debug.info(1, cmd) debug.info(1, cmd)
os.system(cmd) os.system(cmd)
# assert an error until we actually check a resul # assert an error until we actually check a resul
for extension in ["gds", "v", "lef", "sp"]: for extension in ["gds", "v", "lef", "sp", "lvs.sp"]:
filename = "{0}/{1}.{2}".format(out_path, out_file, extension) filename = "{0}{1}.{2}".format(out_path, out_file, extension)
debug.info(1, "Checking for file: " + filename)
self.assertEqual(os.path.exists(filename), True)
# check if the auxiliary scripts were created
for out_file in ["run_drc.sh", "run_lvs.sh"]:
filename = "{0}{1}".format(out_path, out_file)
debug.info(1, "Checking for file: " + filename) debug.info(1, "Checking for file: " + filename)
self.assertEqual(os.path.exists(filename), True) self.assertEqual(os.path.exists(filename), True)
@ -79,7 +85,7 @@ class openram_back_end_test(openram_test):
self.assertTrue(len(datasheets)>0) self.assertTrue(len(datasheets)>0)
# grep any errors from the output # grep any errors from the output
output_log = open("{0}/output.log".format(out_path), "r") output_log = open("{0}output.log".format(out_path), "r")
output = output_log.read() output = output_log.read()
output_log.close() output_log.close()
self.assertEqual(len(re.findall('ERROR', output)), 0) self.assertEqual(len(re.findall('ERROR', output)), 0)

View File

@ -12,6 +12,5 @@ num_words = 16
tech_name = OPTS.tech_name tech_name = OPTS.tech_name
nominal_corner_only = True nominal_corner_only = True
route_supplies = True
check_lvsdrc = True check_lvsdrc = True

View File

@ -12,9 +12,7 @@ num_words = 16
tech_name = OPTS.tech_name tech_name = OPTS.tech_name
nominal_corner_only = True nominal_corner_only = True
route_supplies = True
check_lvsdrc = True check_lvsdrc = True
inline_lvsdrc = True spice_name = "ngspice"
analytical_delay = False

View File

@ -3,7 +3,6 @@
04_precharge_pbitcell_test.py 04_precharge_pbitcell_test.py
04_replica_pbitcell_test.py 04_replica_pbitcell_test.py
04_column_mux_pbitcell_test.py 04_column_mux_pbitcell_test.py
05_bitcell_1rw_1r_array_test.py
05_bitcell_array_test.py 05_bitcell_array_test.py
05_dummy_array_test.py 05_dummy_array_test.py
05_pbitcell_array_test.py 05_pbitcell_array_test.py

View File

@ -21,8 +21,8 @@ class openram_test(unittest.TestCase):
self.reset() self.reset()
tempgds = "{0}{1}.gds".format(OPTS.openram_temp, w.name) tempgds = "{}.gds".format(w.name)
w.gds_write(tempgds) w.gds_write("{0}{1}".format(OPTS.openram_temp, tempgds))
import verify import verify
result=verify.run_drc(w.name, tempgds, None) result=verify.run_drc(w.name, tempgds, None)
@ -36,13 +36,13 @@ class openram_test(unittest.TestCase):
self.reset() self.reset()
tempspice = "{0}{1}.sp".format(OPTS.openram_temp, a.name) tempspice = "{}.sp".format(a.name)
tempgds = "{0}{1}.gds".format(OPTS.openram_temp, a.name) tempgds = "{}.gds".format(a.name)
a.lvs_write(tempspice) a.lvs_write("{0}{1}".format(OPTS.openram_temp, tempspice))
# cannot write gds in netlist_only mode # cannot write gds in netlist_only mode
if not OPTS.netlist_only: if not OPTS.netlist_only:
a.gds_write(tempgds) a.gds_write("{0}{1}".format(OPTS.openram_temp, tempgds))
import verify import verify
# Run both DRC and LVS even if DRC might fail # Run both DRC and LVS even if DRC might fail
@ -80,15 +80,13 @@ class openram_test(unittest.TestCase):
self.cleanup() self.cleanup()
def run_pex(self, a, output=None): def run_pex(self, a, output=None):
if output == None: tempspice = "{}.sp".format(a.name)
output = OPTS.openram_temp + a.name + ".pex.netlist" tempgds = "{}.gds".format(a.name)
tempspice = "{0}{1}.sp".format(OPTS.openram_temp, a.name)
tempgds = "{0}{1}.gds".format(OPTS.openram_temp, a.name)
a.gds_write(tempgds) a.gds_write("{0}{1}".format(OPTS.openram_temp, tempgds))
import verify import verify
result=verify.run_pex(a.name, tempgds, tempspice, output=output, final_verification=False) result=verify.run_pex(a.name, tempgds, tempspice, final_verification=False)
if result != 0: if result != 0:
self.fail("PEX ERROR: {}".format(a.name)) self.fail("PEX ERROR: {}".format(a.name))
return output return output

View File

@ -23,7 +23,6 @@ from tech import lvs_name
from tech import pex_name from tech import pex_name
debug.info(1, "Initializing verify...") debug.info(1, "Initializing verify...")
if not OPTS.check_lvsdrc: if not OPTS.check_lvsdrc:
debug.info(1, "LVS/DRC/PEX disabled.") debug.info(1, "LVS/DRC/PEX disabled.")
OPTS.drc_exe = None OPTS.drc_exe = None

View File

@ -63,8 +63,8 @@ def write_drc_script(cell_name, gds_name, extract, final_verification=False, out
run_file = output_path + "run_drc.sh" run_file = output_path + "run_drc.sh"
f = open(run_file, "w") f = open(run_file, "w")
f.write("#!/bin/sh\n") f.write("#!/bin/sh\n")
cmd = "{0} -gui -drc {1}drc_runset -batch".format(OPTS.drc_exe[1], cmd = "{0} -gui -drc drc_runset -batch".format(OPTS.drc_exe[1])
output_path)
f.write(cmd) f.write(cmd)
f.write("\n") f.write("\n")
f.close() f.close()
@ -125,8 +125,8 @@ def write_lvs_script(cell_name, gds_name, sp_name, final_verification=False, out
run_file = output_path + "run_lvs.sh" run_file = output_path + "run_lvs.sh"
f = open(run_file, "w") f = open(run_file, "w")
f.write("#!/bin/sh\n") f.write("#!/bin/sh\n")
cmd = "{0} -gui -lvs {1}lvs_runset -batch".format(OPTS.lvs_exe[1], cmd = "{0} -gui -lvs lvs_runset -batch".format(OPTS.lvs_exe[1])
output_path)
f.write(cmd) f.write(cmd)
f.write("\n") f.write("\n")
f.close() f.close()
@ -178,8 +178,8 @@ def write_pex_script(cell_name, extract, output, final_verification=False, outpu
run_file = output_path + "run_pex.sh" run_file = output_path + "run_pex.sh"
f = open(run_file, "w") f = open(run_file, "w")
f.write("#!/bin/sh\n") f.write("#!/bin/sh\n")
cmd = "{0} -gui -pex {1}pex_runset -batch".format(OPTS.pex_exe[1], cmd = "{0} -gui -pex pex_runset -batch".format(OPTS.pex_exe[1])
OPTS.openram_temp)
f.write(cmd) f.write(cmd)
f.write("\n") f.write("\n")
f.close() f.close()
@ -195,22 +195,8 @@ def run_drc(cell_name, gds_name, sp_name, extract=False, final_verification=Fals
global num_drc_runs global num_drc_runs
num_drc_runs += 1 num_drc_runs += 1
# Filter the layouts through magic as a GDS filter for nsdm/psdm/nwell merging
# Disabled for now
if False and OPTS.tech_name == "sky130":
shutil.copy(gds_name, OPTS.openram_temp + "temp.gds")
from magic import filter_gds
filter_gds(cell_name, OPTS.openram_temp + "temp.gds", OPTS.openram_temp + cell_name + ".gds")
else:
# Copy file to local dir if it isn't already
if not os.path.isfile(OPTS.openram_temp + os.path.basename(gds_name)):
shutil.copy(gds_name, OPTS.openram_temp)
drc_runset = write_drc_script(cell_name, gds_name, extract, final_verification, OPTS.openram_temp) drc_runset = write_drc_script(cell_name, gds_name, extract, final_verification, OPTS.openram_temp)
if not os.path.isfile(OPTS.openram_temp + os.path.basename(gds_name)):
shutil.copy(gds_name, OPTS.openram_temp + os.path.basename(gds_name))
(outfile, errfile, resultsfile) = run_script(cell_name, "drc") (outfile, errfile, resultsfile) = run_script(cell_name, "drc")
# check the result for these lines in the summary: # check the result for these lines in the summary:
@ -251,12 +237,6 @@ def run_lvs(cell_name, gds_name, sp_name, final_verification=False):
lvs_runset = write_lvs_script(cell_name, gds_name, sp_name, final_verification, OPTS.openram_temp) lvs_runset = write_lvs_script(cell_name, gds_name, sp_name, final_verification, OPTS.openram_temp)
# Copy file to local dir if it isn't already
if not os.path.isfile(OPTS.openram_temp + os.path.basename(gds_name)):
shutil.copy(gds_name, OPTS.openram_temp)
if not os.path.isfile(OPTS.openram_temp + os.path.basename(sp_name)):
shutil.copy(sp_name, OPTS.openram_temp)
(outfile, errfile, resultsfile) = run_script(cell_name, "lvs") (outfile, errfile, resultsfile) = run_script(cell_name, "lvs")
# check the result for these lines in the summary: # check the result for these lines in the summary:
@ -338,12 +318,6 @@ def run_pex(cell_name, gds_name, sp_name, output=None, final_verification=False)
write_pex_script(cell_name, True, output, final_verification, OPTS.openram_temp) write_pex_script(cell_name, True, output, final_verification, OPTS.openram_temp)
# Copy file to local dir if it isn't already
if not os.path.isfile(OPTS.openram_temp + os.path.basename(gds_name)):
shutil.copy(gds_name, OPTS.openram_temp)
if not os.path.isfile(OPTS.openram_temp + os.path.basename(sp_name)):
shutil.copy(sp_name, OPTS.openram_temp)
(outfile, errfile, resultsfile) = run_script(cell_name, "pex") (outfile, errfile, resultsfile) = run_script(cell_name, "pex")

View File

@ -68,7 +68,6 @@ num_pex_runs = 0
def write_drc_script(cell_name, gds_name, extract, final_verification, output_path, sp_name=None): def write_drc_script(cell_name, gds_name, extract, final_verification, output_path, sp_name=None):
""" Write a magic script to perform DRC and optionally extraction. """ """ Write a magic script to perform DRC and optionally extraction. """
global OPTS global OPTS
# Copy .magicrc file into the output directory # Copy .magicrc file into the output directory
@ -78,33 +77,28 @@ def write_drc_script(cell_name, gds_name, extract, final_verification, output_pa
else: else:
debug.warning("Could not locate .magicrc file: {}".format(magic_file)) debug.warning("Could not locate .magicrc file: {}".format(magic_file))
run_file = output_path + "run_drc.sh" run_file = output_path + "run_ext.sh"
f = open(run_file, "w") f = open(run_file, "w")
f.write("#!/bin/sh\n") f.write("#!/bin/sh\n")
f.write('export OPENRAM_TECH="{}"\n'.format(os.environ['OPENRAM_TECH'])) f.write('export OPENRAM_TECH="{}"\n'.format(os.environ['OPENRAM_TECH']))
f.write('echo "$(date): Starting DRC using Magic {}"\n'.format(OPTS.drc_exe[1])) f.write('echo "$(date): Starting GDS to MAG using Magic {}"\n'.format(OPTS.drc_exe[1]))
f.write('\n') f.write('\n')
f.write("{} -dnull -noconsole << EOF\n".format(OPTS.drc_exe[1])) f.write("{} -dnull -noconsole << EOF\n".format(OPTS.drc_exe[1]))
# Do not run DRC for extraction/conversion
f.write("drc off\n")
f.write("gds polygon subcell true\n") f.write("gds polygon subcell true\n")
f.write("gds warning default\n") f.write("gds warning default\n")
f.write("gds flatten true\n") f.write("gds flatten true\n")
f.write("gds readonly true\n") f.write("gds readonly true\n")
f.write("gds ordering true\n") f.write("#gds ordering true\n")
f.write("gds read {}\n".format(gds_name)) f.write("gds read {}\n".format(gds_name))
f.write('puts "Finished reading gds {}"\n'.format(gds_name)) f.write('puts "Finished reading gds {}"\n'.format(gds_name))
f.write("load {}\n".format(cell_name)) f.write("load {}\n".format(cell_name))
f.write('puts "Finished loading cell {}"\n'.format(cell_name)) f.write('puts "Finished loading cell {}"\n'.format(cell_name))
f.write("cellname delete \\(UNNAMED\\)\n") f.write("cellname delete \\(UNNAMED\\)\n")
f.write("writeall force\n") f.write("writeall force\n")
f.write("select top cell\n")
f.write("expand\n") # Extract
f.write('puts "Finished expanding"\n')
f.write("drc check\n")
f.write('puts "Finished drc check"\n')
f.write("drc catchup\n")
f.write('puts "Finished drc catchup"\n')
f.write("drc count total\n")
f.write("drc count\n")
if not sp_name: if not sp_name:
f.write("port makeall\n") f.write("port makeall\n")
else: else:
@ -139,6 +133,45 @@ def write_drc_script(cell_name, gds_name, extract, final_verification, output_pa
f.write(pre + "ext2spice format ngspice\n") f.write(pre + "ext2spice format ngspice\n")
f.write(pre + "ext2spice {}\n".format(cell_name)) f.write(pre + "ext2spice {}\n".format(cell_name))
f.write('puts "Finished ext2spice"\n') f.write('puts "Finished ext2spice"\n')
f.write("quit -noprompt\n")
f.write("EOF\n")
f.write("magic_retcode=$?\n")
f.write('echo "$(date): Finished ($magic_retcode) GDS to MAG using Magic {}"\n'.format(OPTS.drc_exe[1]))
f.write("exit $magic_retcode\n")
f.close()
os.system("chmod u+x {}".format(run_file))
run_file = output_path + "run_drc.sh"
f = open(run_file, "w")
f.write("#!/bin/sh\n")
f.write('export OPENRAM_TECH="{}"\n'.format(os.environ['OPENRAM_TECH']))
# Copy the bitcell mag files if they exist
try:
from tech import blackbox_cells
except ImportError:
blackbox_cells = []
for blackbox_cell_name in blackbox_cells:
mag_file = OPTS.openram_tech + "maglef_lib/" + blackbox_cell_name + ".mag"
debug.check(os.path.isfile(mag_file), "Could not find blackbox cell {}".format(mag_file))
f.write('cp {0} .\n'.format(mag_file))
f.write('echo "$(date): Starting DRC using Magic {}"\n'.format(OPTS.drc_exe[1]))
f.write('\n')
f.write("{} -dnull -noconsole << EOF\n".format(OPTS.drc_exe[1]))
f.write("load {} -dereference\n".format(cell_name))
f.write('puts "Finished loading cell {}"\n'.format(cell_name))
f.write("cellname delete \\(UNNAMED\\)\n")
f.write("select top cell\n")
f.write("expand\n")
f.write('puts "Finished expanding"\n')
f.write("drc euclidean on\n")
f.write("drc check\n")
f.write('puts "Finished drc check"\n')
f.write("drc catchup\n")
f.write('puts "Finished drc catchup"\n')
f.write("drc count total\n")
f.write("quit -noprompt\n") f.write("quit -noprompt\n")
f.write("EOF\n") f.write("EOF\n")
f.write("magic_retcode=$?\n") f.write("magic_retcode=$?\n")
@ -155,12 +188,10 @@ def run_drc(cell_name, gds_name, sp_name=None, extract=True, final_verification=
global num_drc_runs global num_drc_runs
num_drc_runs += 1 num_drc_runs += 1
# Copy file to local dir if it isn't already
if os.path.dirname(gds_name)!=OPTS.openram_temp.rstrip('/'):
shutil.copy(gds_name, OPTS.openram_temp)
write_drc_script(cell_name, gds_name, extract, final_verification, OPTS.openram_temp, sp_name=sp_name) write_drc_script(cell_name, gds_name, extract, final_verification, OPTS.openram_temp, sp_name=sp_name)
(outfile, errfile, resultsfile) = run_script(cell_name, "ext")
(outfile, errfile, resultsfile) = run_script(cell_name, "drc") (outfile, errfile, resultsfile) = run_script(cell_name, "drc")
# Check the result for these lines in the summary: # Check the result for these lines in the summary:
@ -244,12 +275,6 @@ def run_lvs(cell_name, gds_name, sp_name, final_verification=False, output_path=
if not output_path: if not output_path:
output_path = OPTS.openram_temp output_path = OPTS.openram_temp
# Copy file to local dir if it isn't already
if os.path.dirname(gds_name) != output_path.rstrip('/'):
shutil.copy(gds_name, output_path)
if os.path.dirname(sp_name) != output_path.rstrip('/'):
shutil.copy(sp_name, output_path)
write_lvs_script(cell_name, gds_name, sp_name, final_verification) write_lvs_script(cell_name, gds_name, sp_name, final_verification)
(outfile, errfile, resultsfile) = run_script(cell_name, "lvs") (outfile, errfile, resultsfile) = run_script(cell_name, "lvs")

View File

@ -17,8 +17,8 @@ lvs_warned = False
pex_warned = False pex_warned = False
def write_drc_script(cell_name, gds_name, extract, final_verification=False, output_path=None): def write_drc_script(cell_name, gds_name, extract, final_verification=False, output_path=None, sp_name=None):
pass debug.error("Cannot write DRC script for unknown tool", -1)
def run_drc(cell_name, gds_name, sp_name, extract=False, final_verification=False, output_path=None): def run_drc(cell_name, gds_name, sp_name, extract=False, final_verification=False, output_path=None):