Merge branch 'dev' into laptop_checkpoint

This commit is contained in:
Jesse Cirimelli-Low 2021-04-23 22:50:23 -07:00
commit 6ea4bdc5e5
70 changed files with 1097 additions and 490 deletions

3
.gitattributes vendored
View File

@ -1 +1,2 @@
*.sp linguist-vendored
*.sp linguist-language=Spice
*.tf linquist-language=Tech File

View File

@ -9,17 +9,17 @@ jobs:
- name: SCMOS test
run: |
. /home/github-runner/setup-paths.sh
export OPENRAM_HOME="`pwd`/compiler"
export OPENRAM_TECH="`pwd`/technology:/software/PDKs/skywater-tech"
export OPENRAM_HOME="${{ github.workspace }}/compiler"
export OPENRAM_TECH="${{ github.workspace }}/technology:/software/PDKs/skywater-tech"
export OPENRAM_TMP="${{ github.workspace }}/scn4me_subm_temp"
#python3-coverage run -p $OPENRAM_HOME/tests/regress.py -j 12 -t scn4m_subm
$OPENRAM_HOME/tests/regress.py -j 12 -t scn4m_subm
$OPENRAM_HOME/tests/regress.py -j 24 -t scn4m_subm
- name: Archive
if: ${{ failure() }}
uses: actions/upload-artifact@v2
with:
name: scn4me_subm Archives
path: ${{ github.workspace }}/scn4me_subm_temp/*/*
path: ${{ github.workspace }}/*.zip
freepdk45:
runs-on: self-hosted
steps:
@ -28,17 +28,17 @@ jobs:
- name: FreePDK45 test
run: |
. /home/github-runner/setup-paths.sh
export OPENRAM_HOME="`pwd`/compiler"
export OPENRAM_TECH="`pwd`/technology:/software/PDKs/skywater-tech"
export OPENRAM_HOME="${{ github.workspace }}/compiler"
export OPENRAM_TECH="${{ github.workspace }}/technology:/software/PDKs/skywater-tech"
export OPENRAM_TMP="${{ github.workspace }}/freepdk45_temp"
#python3-coverage run -p $OPENRAM_HOME/tests/regress.py -j 12 -t freepdk45
$OPENRAM_HOME/tests/regress.py -j 12 -t freepdk45
$OPENRAM_HOME/tests/regress.py -j 24 -t freepdk45
- name: Archive
if: ${{ failure() }}
uses: actions/upload-artifact@v2
with:
name: FreePDK45 Archives
path: ${{ github.workspace }}/freepdk45_temp/*/*
path: ${{ github.workspace }}/*.zip
# coverage_stats:
# if: ${{ always() }}
# needs: [scn4me_subm, freepdk45]

View File

@ -1,12 +1,9 @@
![](./images/OpenRAM_logo_yellow_transparent.svg)
# OpenRAM
[![Python 3.5](https://img.shields.io/badge/Python-3.5-green.svg)](https://www.python.org/)
[![License: BSD 3-clause](./images/license_badge.svg)](./LICENSE)
Master:
[![Download](./images/download-stable-blue.svg)](https://github.com/VLSIDA/OpenRAM/archive/master.zip)
Dev:
[![Download](./images/download-stable-blue.svg)](https://github.com/VLSIDA/OpenRAM/archive/stable.zip)
[![Download](./images/download-unstable-blue.svg)](https://github.com/VLSIDA/OpenRAM/archive/dev.zip)
An open-source static random access memory (SRAM) compiler.
@ -34,8 +31,7 @@ things that need to be fixed.
The OpenRAM compiler has very few dependencies:
+ [Ngspice] 26 (or later) or HSpice I-2013.12-1 (or later) or CustomSim 2017 (or later)
+ Python 3.5 or higher
+ Python numpy (pip3 install numpy to install)
+ Python scipy (pip3 install scipy to install)
+ Various Python packages (pip install -r requirements.txt)
If you want to perform DRC and LVS, you will need either:
+ Calibre (for [FreePDK45])
@ -145,7 +141,7 @@ The default for openram.py is specified in the configuration file.
# Porting to a New Technology
If you want to support a enw technology, you will need to create:
If you want to support a new technology, you will need to create:
+ a setup script for each technology you want to use
+ a technology directory for each technology with the base cells

View File

@ -123,6 +123,14 @@ class _wordline_driver:
self.vertical_supply = vertical_supply
class _bitcell_array:
def __init__(self,
wordline_layer,
wordline_pitch_factor=2):
self.wordline_layer = wordline_layer
self.wordline_pitch_factor = wordline_pitch_factor
class layer_properties():
"""
This contains meta information about the module routing layers. These
@ -159,6 +167,10 @@ class layer_properties():
self._wordline_driver = _wordline_driver(vertical_supply=False)
self._local_bitcell_array = _bitcell_array(wordline_layer="m2")
self._global_bitcell_array = _bitcell_array(wordline_layer="m3")
@property
def bank(self):
return self._bank
@ -191,3 +203,11 @@ class layer_properties():
def wordline_driver(self):
return self._wordline_driver
@property
def global_bitcell_array(self):
return self._global_bitcell_array
@property
def local_bitcell_array(self):
return self._local_bitcell_array

View File

@ -53,7 +53,7 @@ class hierarchy_design(hierarchy_spice.spice, hierarchy_layout.layout):
elif (OPTS.inline_lvsdrc or force_check or final_verification):
tempspice = "{}.sp".format(self.name)
self.lvs_write("{0}{1}".format(OPTS.openram_temp, tempspice))
self.sp_write("{0}{1}".format(OPTS.openram_temp, tempspice), lvs=True)
tempgds = "{}.gds".format(self.name)
self.gds_write("{0}{1}".format(OPTS.openram_temp, tempgds))
# Final verification option does not allow nets to be connected by label.
@ -82,7 +82,7 @@ class hierarchy_design(hierarchy_spice.spice, hierarchy_layout.layout):
return
elif (not OPTS.is_unit_test and OPTS.check_lvsdrc and (OPTS.inline_lvsdrc or final_verification)):
tempspice = "{}.sp".format(self.name)
self.lvs_write("{0}{1}".format(OPTS.openram_temp, tempspice))
self.sp_write("{0}{1}".format(OPTS.openram_temp, tempspice), lvs=True)
tempgds = "{}.gds".format(self.cell_name)
self.gds_write("{0}{1}".format(OPTS.openram_temp, tempgds))
num_errors = verify.run_drc(self.cell_name, tempgds, tempspice, final_verification=final_verification)
@ -102,7 +102,7 @@ class hierarchy_design(hierarchy_spice.spice, hierarchy_layout.layout):
return
elif (not OPTS.is_unit_test and OPTS.check_lvsdrc and (OPTS.inline_lvsdrc or final_verification)):
tempspice = "{}.sp".format(self.cell_name)
self.lvs_write("{0}{1}".format(OPTS.openram_temp, tempspice))
self.sp_write("{0}{1}".format(OPTS.openram_temp, tempspice), lvs=True)
tempgds = "{}.gds".format(self.name)
self.gds_write("{0}{1}".format(OPTS.openram_temp, tempgds))
num_errors = verify.run_lvs(self.name, tempgds, tempspice, final_verification=final_verification)

View File

@ -15,6 +15,7 @@ from tech import layer_indices
from tech import layer_stacks
from tech import preferred_directions
import os
import sys
from globals import OPTS
from vector import vector
from pin_layout import pin_layout
@ -111,52 +112,48 @@ class layout():
Finds the lowest set of 2d cartesian coordinates within
this layout
"""
lowestx = lowesty = sys.maxsize
if len(self.objs) > 0:
lowestx1 = min(obj.lx() for obj in self.objs if obj.name != "label")
lowesty1 = min(obj.by() for obj in self.objs if obj.name != "label")
else:
lowestx1 = lowesty1 = None
lowestx = min(min(obj.lx() for obj in self.objs if obj.name != "label"), lowestx)
lowesty = min(min(obj.by() for obj in self.objs if obj.name != "label"), lowesty)
if len(self.insts) > 0:
lowestx2 = min(inst.lx() for inst in self.insts)
lowesty2 = min(inst.by() for inst in self.insts)
else:
lowestx2 = lowesty2 = None
lowestx = min(min(inst.lx() for inst in self.insts), lowestx)
lowesty = min(min(inst.by() for inst in self.insts), lowesty)
if lowestx1 == None and lowestx2 == None:
return None
elif lowestx1 == None:
return vector(lowestx2, lowesty2)
elif lowestx2 == None:
return vector(lowestx1, lowesty1)
else:
return vector(min(lowestx1, lowestx2), min(lowesty1, lowesty2))
if len(self.pin_map) > 0:
for pin_set in self.pin_map.values():
if len(pin_set) == 0:
continue
lowestx = min(min(pin.lx() for pin in pin_set), lowestx)
lowesty = min(min(pin.by() for pin in pin_set), lowesty)
return vector(lowestx, lowesty)
def find_highest_coords(self):
"""
Finds the highest set of 2d cartesian coordinates within
this layout
"""
highestx = highesty = -sys.maxsize - 1
if len(self.objs) > 0:
highestx1 = max(obj.rx() for obj in self.objs if obj.name != "label")
highesty1 = max(obj.uy() for obj in self.objs if obj.name != "label")
else:
highestx1 = highesty1 = None
if len(self.insts) > 0:
highestx2 = max(inst.rx() for inst in self.insts)
highesty2 = max(inst.uy() for inst in self.insts)
else:
highestx2 = highesty2 = None
highestx = max(max(obj.rx() for obj in self.objs if obj.name != "label"), highestx)
highesty = max(max(obj.uy() for obj in self.objs if obj.name != "label"), highesty)
if highestx1 == None and highestx2 == None:
return None
elif highestx1 == None:
return vector(highestx2, highesty2)
elif highestx2 == None:
return vector(highestx1, highesty1)
else:
return vector(max(highestx1, highestx2),
max(highesty1, highesty2))
if len(self.insts) > 0:
highestx = max(max(inst.rx() for inst in self.insts), highestx)
highesty = max(max(inst.uy() for inst in self.insts), highesty)
if len(self.pin_map) > 0:
for pin_set in self.pin_map.values():
if len(pin_set) == 0:
continue
highestx = max(max(pin.rx() for pin in pin_set), highestx)
highesty = max(max(pin.uy() for pin in pin_set), highesty)
return vector(highestx, highesty)
def find_highest_layer_coords(self, layer):
"""
@ -677,7 +674,8 @@ class layout():
directions=None,
size=[1, 1],
implant_type=None,
well_type=None):
well_type=None,
min_area=False):
"""
Punch a stack of vias from a start layer to a target layer by the center.
"""
@ -711,7 +709,7 @@ class layout():
implant_type=implant_type,
well_type=well_type)
if cur_layer != from_layer:
if cur_layer != from_layer or min_area:
self.add_min_area_rect_center(cur_layer,
offset,
via.mod.first_layer_width,

View File

@ -63,6 +63,8 @@ class spice():
self.conns = []
# If this is set, it will out output subckt or isntances of this (for row/col caps etc.)
self.no_instances = False
# If we are doing a trimmed netlist, these are the instance that will be filtered
self.trim_insts = set()
# Keep track of any comments to add the the spice
try:
self.commments
@ -312,10 +314,11 @@ class spice():
return True
return False
def sp_write_file(self, sp, usedMODS, lvs_netlist=False):
def sp_write_file(self, sp, usedMODS, lvs=False, trim=False):
"""
Recursive spice subcircuit write;
Writes the spice subcircuit from the library or the dynamically generated one
Writes the spice subcircuit from the library or the dynamically generated one.
Trim netlist is intended ONLY for bitcell arrays.
"""
if self.no_instances:
@ -328,7 +331,7 @@ class spice():
if self.contains(i, usedMODS):
continue
usedMODS.append(i)
i.sp_write_file(sp, usedMODS, lvs_netlist)
i.sp_write_file(sp, usedMODS, lvs, trim)
if len(self.insts) == 0:
return
@ -371,10 +374,16 @@ class spice():
# these are wires and paths
if self.conns[i] == []:
continue
# Instance with no devices in it needs no subckt/instance
if self.insts[i].mod.no_instances:
continue
if lvs_netlist and hasattr(self.insts[i].mod, "lvs_device"):
# If this is a trimmed netlist, skip it by adding comment char
if trim and self.insts[i].name in self.trim_insts:
sp.write("* ")
if lvs and hasattr(self.insts[i].mod, "lvs_device"):
sp.write(self.insts[i].mod.lvs_device.format(self.insts[i].name,
" ".join(self.conns[i])))
sp.write("\n")
@ -394,30 +403,20 @@ class spice():
# Including the file path makes the unit test fail for other users.
# if os.path.isfile(self.sp_file):
# sp.write("\n* {0}\n".format(self.sp_file))
if lvs_netlist and hasattr(self, "lvs"):
if lvs and hasattr(self, "lvs"):
sp.write("\n".join(self.lvs))
else:
sp.write("\n".join(self.spice))
sp.write("\n")
def sp_write(self, spname):
def sp_write(self, spname, lvs=False, trim=False):
"""Writes the spice to files"""
debug.info(3, "Writing to {0}".format(spname))
spfile = open(spname, 'w')
spfile.write("*FIRST LINE IS A COMMENT\n")
usedMODS = list()
self.sp_write_file(spfile, usedMODS)
del usedMODS
spfile.close()
def lvs_write(self, spname):
"""Writes the lvs to files"""
debug.info(3, "Writing to {0}".format(spname))
spfile = open(spname, 'w')
spfile.write("*FIRST LINE IS A COMMENT\n")
usedMODS = list()
self.sp_write_file(spfile, usedMODS, True)
self.sp_write_file(spfile, usedMODS, lvs=lvs, trim=trim)
del usedMODS
spfile.close()

View File

@ -7,6 +7,11 @@
#
import debug
from tech import layer_names
import os
import shutil
from globals import OPTS
from vector import vector
from pin_layout import pin_layout
class lef:
@ -23,11 +28,105 @@ class lef:
# Round to ensure float values are divisible by 0.0025 (the manufacturing grid)
self.round_grid = 4
def lef_write(self, lef_name):
"""Write the entire lef of the object to the file."""
debug.info(3, "Writing to {0}".format(lef_name))
def magic_lef_write(self, lef_name):
""" Use a magic script to perform LEF creation. """
debug.info(3, "Writing abstracted LEF to {0}".format(lef_name))
self.indent = "" # To maintain the indent level easily
# Copy .magicrc file into the output directory
magic_file = OPTS.openram_tech + "tech/.magicrc"
if os.path.exists(magic_file):
shutil.copy(magic_file, OPTS.openram_temp)
else:
debug.warning("Could not locate .magicrc file: {}".format(magic_file))
gds_name = OPTS.openram_temp + "{}.gds".format(self.name)
self.gds_write(gds_name)
run_file = OPTS.openram_temp + "run_lef.sh"
f = open(run_file, "w")
f.write("#!/bin/sh\n")
f.write('export OPENRAM_TECH="{}"\n'.format(os.environ['OPENRAM_TECH']))
f.write('echo "$(date): Starting GDS to MAG using Magic {}"\n'.format(OPTS.drc_exe[1]))
f.write('\n')
f.write("{} -dnull -noconsole << EOF\n".format(OPTS.drc_exe[1]))
f.write("drc off\n")
f.write("gds polygon subcell true\n")
f.write("gds warning default\n")
f.write("gds flatten true\n")
f.write("gds ordering true\n")
f.write("gds readonly true\n")
f.write("gds read {}\n".format(gds_name))
f.write('puts "Finished reading gds {}"\n'.format(gds_name))
f.write("load {}\n".format(self.name))
f.write('puts "Finished loading cell {}"\n'.format(self.name))
f.write("cellname delete \\(UNNAMED\\)\n")
f.write("lef write {} -hide\n".format(lef_name))
f.write('puts "Finished writing LEF cell {}"\n'.format(self.name))
f.close()
os.system("chmod u+x {}".format(run_file))
from run_script import run_script
(outfile, errfile, resultsfile) = run_script(self.name, "lef")
def lef_write(self, lef_name):
""" Write the entire lef of the object to the file. """
if OPTS.detailed_lef:
debug.info(3, "Writing detailed LEF to {0}".format(lef_name))
self.detailed_lef_write(lef_name)
else:
debug.info(3, "Writing abstract LEF to {0}".format(lef_name))
# Can possibly use magic lef write to create the LEF
# if OPTS.drc_exe and OPTS.drc_exe[0] == "magic":
# self.magic_lef_write(lef_name)
# return
self.abstract_lef_write(lef_name)
def abstract_lef_write(self, lef_name):
# To maintain the indent level easily
self.indent = ""
self.lef = open(lef_name, "w")
self.lef_write_header()
# Start with blockages on all layers the size of the block
# minus the pin escape margin (hard coded to 4 x m3 pitch)
# These are a pin_layout to use their geometric functions
perimeter_margin = self.m3_pitch
self.blockages = {}
for layer_name in self.lef_layers:
self.blockages[layer_name]=[]
for layer_name in self.lef_layers:
ll = vector(perimeter_margin, perimeter_margin)
ur = vector(self.width - perimeter_margin, self.height - perimeter_margin)
self.blockages[layer_name].append(pin_layout("",
[ll, ur],
layer_name))
# For each pin, remove the blockage and add the pin
for pin_name in self.pins:
pin = self.get_pin(pin_name)
inflated_pin = pin.inflated_pin(multiple=1)
for blockage in self.blockages[pin.layer]:
if blockage.overlaps(inflated_pin):
intersection_shape = blockage.intersection(inflated_pin)
# If it is zero area, don't add the pin
if intersection_shape[0][0]==intersection_shape[1][0] or intersection_shape[0][1]==intersection_shape[1][1]:
continue
# Remove the old blockage and add the new ones
self.blockages[pin.layer].remove(blockage)
intersection_pin = pin_layout("", intersection_shape, inflated_pin.layer)
new_blockages = blockage.cut(intersection_pin)
self.blockages[pin.layer].extend(new_blockages)
self.lef_write_pin(pin_name)
self.lef_write_obstructions(abstracted=True)
self.lef_write_footer()
self.lef.close()
def detailed_lef_write(self, lef_name):
# To maintain the indent level easily
self.indent = ""
self.lef = open(lef_name, "w")
self.lef_write_header()
@ -89,24 +188,29 @@ class lef:
self.indent = self.indent[:-3]
self.lef.write("{0}END {1}\n".format(self.indent, name))
def lef_write_obstructions(self):
def lef_write_obstructions(self, abstracted=False):
""" Write all the obstructions on each layer """
self.lef.write("{0}OBS\n".format(self.indent))
for layer in self.lef_layers:
self.lef.write("{0}LAYER {1} ;\n".format(self.indent, layer_names[layer]))
self.indent += " "
blockages = self.get_blockages(layer, True)
for b in blockages:
self.lef_write_shape(b)
if abstracted:
blockages = self.blockages[layer]
for b in blockages:
self.lef_write_shape(b.rect)
else:
blockages = self.get_blockages(layer, True)
for b in blockages:
self.lef_write_shape(b)
self.indent = self.indent[:-3]
self.lef.write("{0}END\n".format(self.indent))
def lef_write_shape(self, rect):
if len(rect) == 2:
def lef_write_shape(self, obj):
if len(obj) == 2:
""" Write a LEF rectangle """
self.lef.write("{0}RECT ".format(self.indent))
for item in rect:
# print(rect)
for item in obj:
# print(obj)
self.lef.write(" {0} {1}".format(round(item[0],
self.round_grid),
round(item[1],
@ -115,12 +219,10 @@ class lef:
else:
""" Write a LEF polygon """
self.lef.write("{0}POLYGON ".format(self.indent))
for item in rect:
for item in obj:
self.lef.write(" {0} {1}".format(round(item[0],
self.round_grid),
round(item[1],
self.round_grid)))
# for i in range(0,len(rect)):
# self.lef.write(" {0} {1}".format(round(rect[i][0],self.round_grid), round(rect[i][1],self.round_grid)))
self.lef.write(" ;\n")

View File

@ -139,13 +139,13 @@ class pin_layout:
min_area = drc("{}_minarea".format(self.layer))
pass
def inflate(self, spacing=None):
def inflate(self, spacing=None, multiple=0.5):
"""
Inflate the rectangle by the spacing (or other rule)
and return the new rectangle.
"""
if not spacing:
spacing = 0.5*drc("{0}_to_{0}".format(self.layer))
spacing = multiple*drc("{0}_to_{0}".format(self.layer))
(ll, ur) = self.rect
spacing = vector(spacing, spacing)
@ -154,15 +154,23 @@ class pin_layout:
return (newll, newur)
def inflated_pin(self, spacing=None, multiple=0.5):
"""
Inflate the rectangle by the spacing (or other rule)
and return the new rectangle.
"""
inflated_area = self.inflate(spacing, multiple)
return pin_layout(self.name, inflated_area, self.layer)
def intersection(self, other):
""" Check if a shape overlaps with a rectangle """
(ll, ur) = self.rect
(oll, our) = other.rect
min_x = max(ll.x, oll.x)
max_x = min(ll.x, oll.x)
max_x = min(ur.x, our.x)
min_y = max(ll.y, oll.y)
max_y = min(ll.y, oll.y)
max_y = min(ur.y, our.y)
return [vector(min_x, min_y), vector(max_x, max_y)]
@ -369,11 +377,23 @@ class pin_layout:
debug.info(4, "writing pin (" + str(self.layer) + "):"
+ str(self.width()) + "x"
+ str(self.height()) + " @ " + str(self.ll()))
(layer_num, purpose) = layer[self.layer]
# Try to use the pin layer if it exists, otherwise
# use the regular layer
try:
from tech import pin_purpose
(pin_layer_num, pin_purpose) = layer[self.layer + "p"]
except KeyError:
(pin_layer_num, pin_purpose) = layer[self.layer]
(layer_num, purpose) = layer[self.layer]
# Try to use a global pin purpose if it exists,
# otherwise, use the regular purpose
try:
from tech import pin_purpose as global_pin_purpose
pin_purpose = global_pin_purpose
except ImportError:
pin_purpose = purpose
pass
try:
from tech import label_purpose
except ImportError:
@ -385,9 +405,9 @@ class pin_layout:
width=self.width(),
height=self.height(),
center=False)
# Draw a second pin shape too
if pin_purpose != purpose:
newLayout.addBox(layerNumber=layer_num,
# Draw a second pin shape too if it is different
if not self.same_lpp((pin_layer_num, pin_purpose), (layer_num, purpose)):
newLayout.addBox(layerNumber=pin_layer_num,
purposeNumber=pin_purpose,
offsetInMicrons=self.ll(),
width=self.width(),
@ -566,6 +586,30 @@ class pin_layout:
return None
def cut(self, shape):
"""
Return a set of shapes that are this shape minus the argument shape.
"""
# Make the unique coordinates in X and Y directions
x_offsets = sorted([self.lx(), self.rx(), shape.lx(), shape.rx()])
y_offsets = sorted([self.by(), self.uy(), shape.by(), shape.uy()])
new_shapes = []
# Create all of the shapes
for x1, x2 in zip(x_offsets[0:], x_offsets[1:]):
if x1==x2:
continue
for y1, y2 in zip(y_offsets[0:], y_offsets[1:]):
if y1==y2:
continue
new_shape = pin_layout("", [vector(x1, y1), vector(x2, y2)], self.lpp)
# Don't add the existing shape in if it overlaps the pin shape
if new_shape.contains(shape):
continue
new_shapes.append(new_shape)
return new_shapes
def same_lpp(self, lpp1, lpp2):
"""
Check if the layers and purposes are the same.

View File

@ -61,6 +61,8 @@ class verilog:
self.vf.write(" parameter RAM_DEPTH = 1 << ADDR_WIDTH;\n")
self.vf.write(" // FIXME: This delay is arbitrary.\n")
self.vf.write(" parameter DELAY = 3 ;\n")
self.vf.write(" parameter VERBOSE = 1 ; //Set to 0 to only display warnings\n")
self.vf.write(" parameter T_HOLD = 1 ; //Delay to hold dout value after posedge. Value is arbitrary\n")
self.vf.write("\n")
for port in self.all_ports:
@ -128,21 +130,21 @@ class verilog:
if port in self.write_ports:
self.vf.write(" din{0}_reg = din{0};\n".format(port))
if port in self.read_ports:
self.vf.write(" dout{0} = {1}'bx;\n".format(port, self.word_size))
self.vf.write(" #(T_HOLD) dout{0} = {1}'bx;\n".format(port, self.word_size))
if port in self.readwrite_ports:
self.vf.write(" if ( !csb{0}_reg && web{0}_reg ) \n".format(port))
self.vf.write(" if ( !csb{0}_reg && web{0}_reg && VERBOSE ) \n".format(port))
self.vf.write(" $display($time,\" Reading %m addr{0}=%b dout{0}=%b\",addr{0}_reg,mem[addr{0}_reg]);\n".format(port))
elif port in self.read_ports:
self.vf.write(" if ( !csb{0}_reg ) \n".format(port))
self.vf.write(" if ( !csb{0}_reg && VERBOSE ) \n".format(port))
self.vf.write(" $display($time,\" Reading %m addr{0}=%b dout{0}=%b\",addr{0}_reg,mem[addr{0}_reg]);\n".format(port))
if port in self.readwrite_ports:
self.vf.write(" if ( !csb{0}_reg && !web{0}_reg )\n".format(port))
self.vf.write(" if ( !csb{0}_reg && !web{0}_reg && VERBOSE )\n".format(port))
if self.write_size:
self.vf.write(" $display($time,\" Writing %m addr{0}=%b din{0}=%b wmask{0}=%b\",addr{0}_reg,din{0}_reg,wmask{0}_reg);\n".format(port))
else:
self.vf.write(" $display($time,\" Writing %m addr{0}=%b din{0}=%b\",addr{0}_reg,din{0}_reg);\n".format(port))
elif port in self.write_ports:
self.vf.write(" if ( !csb{0}_reg )\n".format(port))
self.vf.write(" if ( !csb{0}_reg && VERBOSE )\n".format(port))
if self.write_size:
self.vf.write(" $display($time,\" Writing %m addr{0}=%b din{0}=%b wmask{0}=%b\",addr{0}_reg,din{0}_reg,wmask{0}_reg);\n".format(port))
else:

View File

@ -31,6 +31,8 @@ def parse_spice_list(filename, key):
f = open(full_filename, "r")
except IOError:
debug.error("Unable to open spice output file: {0}".format(full_filename),1)
debug.archive()
contents = f.read()
f.close()
# val = re.search(r"{0}\s*=\s*(-?\d+.?\d*\S*)\s+.*".format(key), contents)

View File

@ -1100,14 +1100,8 @@ class delay(simulation):
# Set up to trim the netlist here if that is enabled
if OPTS.trim_netlist:
self.trim_sp_file = "{}reduced.sp".format(OPTS.openram_temp)
self.trimsp=trim_spice(self.sp_file, self.trim_sp_file)
self.trimsp.set_configuration(self.num_banks,
self.num_rows,
self.num_cols,
self.word_size,
self.num_spare_rows)
self.trimsp.trim(self.probe_address, self.probe_data)
self.trim_sp_file = "{}trimmed.sp".format(OPTS.openram_temp)
self.sram.sp_write(self.trim_sp_file, lvs=False, trim=True)
else:
# The non-reduced netlist file when it is disabled
self.trim_sp_file = "{}sram.sp".format(OPTS.openram_temp)

View File

@ -78,8 +78,6 @@ class elmore(simulation):
port_data[port][mname].append(total_delay.delay / 1e3)
elif "slew" in mname and port in self.read_ports:
port_data[port][mname].append(total_delay.slew / 1e3)
else:
debug.error("Measurement name not recognized: {}".format(mname), 1)
# Margin for error in period. Calculated by averaging required margin for a small and large
# memory. FIXME: margin is quite large, should be looked into.

View File

@ -9,6 +9,7 @@ import collections
import debug
import random
import math
from numpy import binary_repr
from .stimuli import *
from .charutils import *
from globals import OPTS
@ -21,13 +22,17 @@ class functional(simulation):
for successful SRAM operation.
"""
def __init__(self, sram, spfile, corner=None, cycles=15, period=None, output_path=None):
def __init__(self, sram, spfile=None, corner=None, cycles=15, period=None, output_path=None):
super().__init__(sram, spfile, corner)
# Seed the characterizer with a constant seed for unit tests
if OPTS.is_unit_test:
random.seed(12345)
if not spfile:
# self.sp_file is assigned in base class
sram.sp_write(self.sp_file, trim=OPTS.trim_netlist)
if not corner:
corner = (OPTS.process_corners[0], OPTS.supply_voltages[0], OPTS.temperatures[0])
@ -47,6 +52,22 @@ class functional(simulation):
if not self.num_spare_cols:
self.num_spare_cols = 0
self.max_data = 2 ** self.word_size - 1
self.max_col_data = 2 ** self.num_spare_cols - 1
if self.words_per_row>1:
# This will truncate bits for word addressing in a row_addr_dff
# This makes one set of spares per row by using top bits of the address
self.addr_spare_index = -int(math.log(self.words_per_row) / math.log(2))
else:
# This will select the entire address when one word per row
self.addr_spare_index = self.addr_size
# If trim is set, specify the valid addresses
self.valid_addresses = set()
self.max_address = 2**self.addr_size - 1 + (self.num_spare_rows * self.words_per_row)
if OPTS.trim_netlist:
for i in range(self.words_per_row):
self.valid_addresses.add(i)
self.valid_addresses.add(self.max_address - i)
self.probe_address, self.probe_data = '0' * self.addr_size, 0
self.set_corner(corner)
self.set_spice_constants()
@ -66,6 +87,7 @@ class functional(simulation):
self.num_cycles = cycles
# This is to have ordered keys for random selection
self.stored_words = collections.OrderedDict()
self.stored_spares = collections.OrderedDict()
self.read_check = []
self.read_results = []
@ -121,10 +143,12 @@ class functional(simulation):
# 1. Write all the write ports first to seed a bunch of locations.
for port in self.write_ports:
addr = self.gen_addr()
word = self.gen_data()
comment = self.gen_cycle_comment("write", word, addr, "1" * self.num_wmasks, port, self.t_current)
self.add_write_one_port(comment, addr, word, "1" * self.num_wmasks, port)
(word, spare) = self.gen_data()
combined_word = "{}+{}".format(word, spare)
comment = self.gen_cycle_comment("write", combined_word, addr, "1" * self.num_wmasks, port, self.t_current)
self.add_write_one_port(comment, addr, word + spare, "1" * self.num_wmasks, port)
self.stored_words[addr] = word
self.stored_spares[addr[:self.addr_spare_index]] = spare
# All other read-only ports are noops.
for port in self.read_ports:
@ -142,7 +166,9 @@ class functional(simulation):
if port in self.write_ports:
self.add_noop_one_port(port)
else:
comment = self.gen_cycle_comment("read", word, addr, "0" * self.num_wmasks, port, self.t_current)
(addr, word, spare) = self.get_data()
combined_word = "{}+{}".format(word, spare)
comment = self.gen_cycle_comment("read", combined_word, addr, "0" * self.num_wmasks, port, self.t_current)
self.add_read_one_port(comment, addr, port)
self.add_read_check(word, port)
self.cycle_times.append(self.t_current)
@ -170,27 +196,33 @@ class functional(simulation):
if addr in w_addrs:
self.add_noop_one_port(port)
else:
word = self.gen_data()
comment = self.gen_cycle_comment("write", word, addr, "1" * self.num_wmasks, port, self.t_current)
self.add_write_one_port(comment, addr, word, "1" * self.num_wmasks, port)
(word, spare) = self.gen_data()
combined_word = "{}+{}".format(word, spare)
comment = self.gen_cycle_comment("write", combined_word, addr, "1" * self.num_wmasks, port, self.t_current)
self.add_write_one_port(comment, addr, word + spare, "1" * self.num_wmasks, port)
self.stored_words[addr] = word
self.stored_spares[addr[:self.addr_spare_index]] = spare
w_addrs.append(addr)
elif op == "partial_write":
# write only to a word that's been written to
(addr, old_word) = self.get_data()
(addr, old_word, old_spare) = self.get_data()
# two ports cannot write to the same address
if addr in w_addrs:
self.add_noop_one_port(port)
else:
word = self.gen_data()
(word, spare) = self.gen_data()
wmask = self.gen_wmask()
new_word = self.gen_masked_data(old_word, word, wmask)
comment = self.gen_cycle_comment("partial_write", word, addr, wmask, port, self.t_current)
self.add_write_one_port(comment, addr, word, wmask, port)
combined_word = "{}+{}".format(word, spare)
comment = self.gen_cycle_comment("partial_write", combined_word, addr, wmask, port, self.t_current)
self.add_write_one_port(comment, addr, word + spare, wmask, port)
self.stored_words[addr] = new_word
self.stored_spares[addr[:self.addr_spare_index]] = spare
w_addrs.append(addr)
else:
(addr, word) = random.choice(list(self.stored_words.items()))
spare = self.stored_spares[addr[:self.addr_spare_index]]
combined_word = "{}+{}".format(word, spare)
# The write driver is not sized sufficiently to drive through the two
# bitcell access transistors to the read port. So, for now, we do not allow
# a simultaneous write and read to the same address on different ports. This
@ -198,9 +230,9 @@ class functional(simulation):
if addr in w_addrs:
self.add_noop_one_port(port)
else:
comment = self.gen_cycle_comment("read", word, addr, "0" * self.num_wmasks, port, self.t_current)
comment = self.gen_cycle_comment("read", combined_word, addr, "0" * self.num_wmasks, port, self.t_current)
self.add_read_one_port(comment, addr, port)
self.add_read_check(word, port)
self.add_read_check(word + spare, port)
self.cycle_times.append(self.t_current)
self.t_current += self.period
@ -227,18 +259,18 @@ class functional(simulation):
def add_read_check(self, word, port):
""" Add to the check array to ensure a read works. """
try:
self.check
self.check_count
except:
self.check = 0
self.read_check.append([word, "{0}{1}".format(self.dout_name, port), self.t_current + self.period, self.check])
self.check += 1
self.check_count = 0
self.read_check.append([word, "{0}{1}".format(self.dout_name, port), self.t_current + self.period, self.check_count])
self.check_count += 1
def read_stim_results(self):
# Extract dout values from spice timing.lis
for (word, dout_port, eo_period, check) in self.read_check:
for (word, dout_port, eo_period, check_count) in self.read_check:
sp_read_value = ""
for bit in range(self.word_size + self.num_spare_cols):
value = parse_spice_list("timing", "v{0}.{1}ck{2}".format(dout_port.lower(), bit, check))
value = parse_spice_list("timing", "v{0}.{1}ck{2}".format(dout_port.lower(), bit, check_count))
try:
value = float(value)
if value > self.v_high:
@ -260,13 +292,13 @@ class functional(simulation):
return (0, error)
self.read_results.append([sp_read_value, dout_port, eo_period, check])
self.read_results.append([sp_read_value, dout_port, eo_period, check_count])
return (1, "SUCCESS")
def check_stim_results(self):
for i in range(len(self.read_check)):
if self.read_check[i][0] != self.read_results[i][0]:
str = "FAILED: {0} value {1} does not match written value {2} read during cycle {3} at time {4}n"
str = "FAILED: {0} read value {1} does not match written value {2} during cycle {3} at time {4}n"
error = str.format(self.read_results[i][1],
self.read_results[i][0],
self.read_check[i][0],
@ -300,22 +332,22 @@ class functional(simulation):
def gen_data(self):
""" Generates a random word to write. """
if not self.num_spare_cols:
random_value = random.randint(0, (2 ** self.word_size) - 1)
random_value = random.randint(0, self.max_data)
data_bits = binary_repr(random_value, self.word_size)
if self.num_spare_cols>0:
random_value = random.randint(0, self.max_col_data)
spare_bits = binary_repr(random_value, self.num_spare_cols)
else:
random_value1 = random.randint(0, (2 ** self.word_size) - 1)
random_value2 = random.randint(0, (2 ** self.num_spare_cols) - 1)
random_value = random_value1 + random_value2
data_bits = self.convert_to_bin(random_value, False)
return data_bits
spare_bits = ""
return data_bits, spare_bits
def gen_addr(self):
""" Generates a random address value to write to. """
if self.num_spare_rows==0:
random_value = random.randint(0, (2 ** self.addr_size) - 1)
if self.valid_addresses:
random_value = random.sample(self.valid_addresses, 1)[0]
else:
random_value = random.randint(0, ((2 ** (self.addr_size - 1) - 1)) + (self.num_spare_rows * self.words_per_row))
addr_bits = self.convert_to_bin(random_value, True)
random_value = random.randint(0, self.max_address)
addr_bits = binary_repr(random_value, self.addr_size)
return addr_bits
def get_data(self):
@ -323,20 +355,8 @@ class functional(simulation):
# Used for write masks since they should be writing to previously written addresses
addr = random.choice(list(self.stored_words.keys()))
word = self.stored_words[addr]
return (addr, word)
def convert_to_bin(self, value, is_addr):
""" Converts addr & word to usable binary values. """
new_value = str.replace(bin(value), "0b", "")
if(is_addr):
expected_value = self.addr_size
else:
expected_value = self.word_size + self.num_spare_cols
for i in range(expected_value - len(new_value)):
new_value = "0" + new_value
# print("Binary Conversion: {} to {}".format(value, new_value))
return new_value
spare = self.stored_spares[addr[:self.addr_spare_index]]
return (addr, word, spare)
def write_functional_stimulus(self):
""" Writes SPICE stimulus. """
@ -432,12 +452,21 @@ class functional(simulation):
# Generate dout value measurements
self.sf.write("\n * Generation of dout measurements\n")
for (word, dout_port, eo_period, check) in self.read_check:
t_intital = eo_period - 0.01 * self.period
t_initial = eo_period - 0.01 * self.period
t_final = eo_period + 0.01 * self.period
for bit in range(self.word_size + self.num_spare_cols):
self.stim.gen_meas_value(meas_name="V{0}_{1}ck{2}".format(dout_port, bit, check),
dout="{0}_{1}".format(dout_port, bit),
t_intital=t_intital,
num_bits = self.word_size + self.num_spare_cols
for bit in range(num_bits):
measure_name = "V{0}_{1}ck{2}".format(dout_port, bit, check)
signal_name = "{0}_{1}".format(dout_port, bit)
voltage_value = self.stim.get_voltage(word[num_bits - bit - 1])
self.stim.add_comment("* CHECK {0} {1} = {2} time = {3}".format(signal_name,
measure_name,
voltage_value,
eo_period))
self.stim.gen_meas_value(meas_name=measure_name,
dout=signal_name,
t_initial=t_initial,
t_final=t_final)
self.stim.write_control(self.cycle_times[-1] + self.period)

View File

@ -5,9 +5,8 @@
# (acting for and on behalf of Oklahoma State University)
# All rights reserved.
#
import os,sys,re
import os
import debug
import math
import datetime
from .setup_hold import *
from .delay import *
@ -16,6 +15,7 @@ import tech
import numpy as np
from globals import OPTS
class lib:
""" lib file generation."""
@ -601,7 +601,6 @@ class lib:
from .elmore import elmore as model
else:
debug.error("{} model not recognized. See options.py for available models.".format(OPTS.model_name))
import math
m = model(self.sram, self.sp_file, self.corner)
char_results = m.get_lib_values(self.slews,self.loads)
@ -834,4 +833,4 @@ class lib:
#FIXME: should be read_fall_power
datasheet.write("{0},{1},".format('write_fall_power_{}'.format(port), read0_power))

View File

@ -82,7 +82,13 @@ class setup_hold():
"""
self.sf.write("\n* Generation of the data and clk signals\n")
incorrect_value = self.stim.get_inverse_value(correct_value)
if correct_value == 1:
incorrect_value = 0
elif correct_value == 0:
incorrect_value = 1
else:
debug.error("Invalid value {}".format(correct_value))
if mode=="HOLD":
init_value = incorrect_value
start_value = correct_value

View File

@ -27,7 +27,10 @@ class simulation():
self.num_spare_cols = 0
else:
self.num_spare_cols = self.sram.num_spare_cols
self.sp_file = spfile
if not spfile:
self.sp_file = OPTS.openram_temp + "sram.sp"
else:
self.sp_file = spfile
self.all_ports = self.sram.all_ports
self.readwrite_ports = self.sram.readwrite_ports

View File

@ -169,22 +169,14 @@ class stimuli():
def gen_constant(self, sig_name, v_val):
""" Generates a constant signal with reference voltage and the voltage value """
self.sf.write("V{0} {0} 0 DC {1}\n".format(sig_name, v_val))
def get_inverse_voltage(self, value):
if value > 0.5 * self.voltage:
def get_voltage(self, value):
if value == "0" or value == 0:
return 0
elif value <= 0.5 * self.voltage:
elif value == "1" or value == 1:
return self.voltage
else:
debug.error("Invalid value to get an inverse of: {0}".format(value))
def get_inverse_value(self, value):
if value > 0.5:
return 0
elif value <= 0.5:
return 1
else:
debug.error("Invalid value to get an inverse of: {0}".format(value))
debug.error("Invalid value to get a voltage of: {0}".format(value))
def gen_meas_delay(self, meas_name, trig_name, targ_name, trig_val, targ_val, trig_dir, targ_dir, trig_td, targ_td):
""" Creates the .meas statement for the measurement of delay """
@ -228,8 +220,8 @@ class stimuli():
t_initial,
t_final))
def gen_meas_value(self, meas_name, dout, t_intital, t_final):
measure_string=".meas tran {0} AVG v({1}) FROM={2}n TO={3}n\n\n".format(meas_name, dout, t_intital, t_final)
def gen_meas_value(self, meas_name, dout, t_initial, t_final):
measure_string=".meas tran {0} AVG v({1}) FROM={2}n TO={3}n\n\n".format(meas_name, dout, t_initial, t_final)
self.sf.write(measure_string)
def write_control(self, end_time, runlvl=4):
@ -254,7 +246,7 @@ class stimuli():
# which is more accurate, but slower than the default trapezoid method
# Do not remove this or it may not converge due to some "pa_00" nodes
# unless you figure out what these are.
self.sf.write(".OPTIONS POST=1 RELTOL={0} PROBE method=gear\n".format(reltol))
self.sf.write(".OPTIONS POST=1 RELTOL={0} PROBE method=gear ACCT\n".format(reltol))
elif OPTS.spice_name == "spectre":
self.sf.write("simulator lang=spectre\n")
if OPTS.use_pex:
@ -310,6 +302,9 @@ class stimuli():
for item in list(includes):
self.sf.write(".include \"{0}\"\n".format(item))
def add_comment(self, msg):
self.sf.write(msg + "\n")
def write_supply(self):
""" Writes supply voltage statements """
gnd_node_name = "0"
@ -360,6 +355,8 @@ class stimuli():
# -r {2}timing.raw
ng_cfg = open("{}.spiceinit".format(OPTS.openram_temp), "w")
ng_cfg.write("set num_threads={}\n".format(OPTS.num_sim_threads))
ng_cfg.write("set ngbehavior=hsa\n")
ng_cfg.write("set ng_nomodcheck\n")
ng_cfg.close()
cmd = "{0} -b -o {2}timing.lis {1}".format(OPTS.spice_exe,

View File

@ -9,6 +9,7 @@ import debug
from math import log,ceil
import re
class trim_spice():
"""
A utility to trim redundant parts of an SRAM spice netlist.
@ -29,7 +30,6 @@ class trim_spice():
for i in range(len(self.spice)):
self.spice[i] = self.spice[i].rstrip(" \n")
self.sp_buffer = self.spice
def set_configuration(self, banks, rows, columns, word_size):
@ -46,21 +46,23 @@ class trim_spice():
self.col_addr_size = int(log(self.words_per_row, 2))
self.bank_addr_size = self.col_addr_size + self.row_addr_size
self.addr_size = self.bank_addr_size + int(log(self.num_banks, 2))
def trim(self, address, data_bit):
""" Reduce the spice netlist but KEEP the given bits at the
address (and things that will add capacitive load!)"""
"""
Reduce the spice netlist but KEEP the given bits at the
address (and things that will add capacitive load!)
"""
# Always start fresh if we do multiple reductions
self.sp_buffer = self.spice
# Split up the address and convert to an int
wl_address = int(address[self.col_addr_size:],2)
if self.col_addr_size>0:
col_address = int(address[0:self.col_addr_size],2)
wl_address = int(address[self.col_addr_size:], 2)
if self.col_addr_size > 0:
col_address = int(address[0:self.col_addr_size], 2)
else:
col_address = 0
# 1. Keep cells in the bitcell array based on WL and BL
wl_name = "wl_{}".format(wl_address)
bl_name = "bl_{}".format(int(self.words_per_row*data_bit + col_address))
@ -81,7 +83,6 @@ class trim_spice():
self.sp_buffer.insert(0, "* It should NOT be used for LVS!!")
self.sp_buffer.insert(0, "* WARNING: This is a TRIMMED NETLIST.")
wl_regex = r"wl\d*_{}".format(wl_address)
bl_regex = r"bl\d*_{}".format(int(self.words_per_row*data_bit + col_address))
self.remove_insts("bitcell_array",[wl_regex,bl_regex])
@ -91,11 +92,11 @@ class trim_spice():
#self.remove_insts("sense_amp_array",[bl_regex])
# 3. Keep column muxes basd on BL
self.remove_insts("column_mux_array",[bl_regex])
self.remove_insts("column_mux_array", [bl_regex])
# 4. Keep write driver based on DATA
data_regex = r"data_{}".format(data_bit)
self.remove_insts("write_driver_array",[data_regex])
self.remove_insts("write_driver_array", [data_regex])
# 5. Keep wordline driver based on WL
# Need to keep the gater too
@ -111,7 +112,6 @@ class trim_spice():
sp.write("\n".join(self.sp_buffer))
sp.close()
def remove_insts(self, subckt_name, keep_inst_list):
"""This will remove all of the instances in the list from the named
subckt that DO NOT contain a term in the list. It just does a
@ -119,7 +119,7 @@ class trim_spice():
net connection, the instance name, anything..
"""
removed_insts = 0
#Expects keep_inst_list are regex patterns. Compile them here.
# Expects keep_inst_list are regex patterns. Compile them here.
compiled_patterns = [re.compile(pattern) for pattern in keep_inst_list]
start_name = ".SUBCKT {}".format(subckt_name)

View File

@ -43,7 +43,7 @@ def error(str, return_value=0):
if globals.OPTS.debug:
pdb.set_trace()
assert return_value == 0
@ -108,7 +108,20 @@ def info(lev, str):
print_raw("[{0}/{1}]: {2}".format(class_name,
frm[0].f_code.co_name, str))
def archive():
from globals import OPTS
try:
OPENRAM_HOME = os.path.abspath(os.environ.get("OPENRAM_HOME"))
except:
error("$OPENRAM_HOME is not properly defined.", 1)
import shutil
zip_file = "{0}/{1}_{2}".format(OPENRAM_HOME, "fail_", os.getpid())
info(0, "Archiving failed files to {}.zip".format(zip_file))
shutil.make_archive(zip_file, 'zip', OPTS.openram_temp)
def bp():
"""
An empty function so you can set soft breakpoints in pdb.

View File

@ -22,7 +22,7 @@ import getpass
import subprocess
VERSION = "1.1.13"
VERSION = "1.1.15"
NAME = "OpenRAM v{}".format(VERSION)
USAGE = "openram.py [options] <config file>\nUse -h for help.\n"
@ -61,8 +61,13 @@ def parse_args():
optparse.make_option("-j", "--threads",
action="store",
type="int",
help="Specify the number of threads (default: 2)",
help="Specify the number of threads (default: 1)",
dest="num_threads"),
optparse.make_option("-m", "--sim_threads",
action="store",
type="int",
help="Specify the number of spice simulation threads (default: 2)",
dest="num_sim_threads"),
optparse.make_option("-v",
"--verbose",
action="count",
@ -381,6 +386,10 @@ def purge_temp():
""" Remove the temp directory. """
debug.info(1,
"Purging temp directory: {}".format(OPTS.openram_temp))
#import inspect
#s = inspect.stack()
#print("Purge {0} in dir {1}".format(s[3].filename, OPTS.openram_temp))
# This annoyingly means you have to re-cd into
# the directory each debug iteration
# shutil.rmtree(OPTS.openram_temp, ignore_errors=True)
@ -429,9 +438,15 @@ def setup_paths():
if "__pycache__" not in full_path:
sys.path.append("{0}".format(full_path))
# Use a unique temp subdirectory
OPTS.openram_temp += "/openram_{0}_{1}_temp/".format(getpass.getuser(),
os.getpid())
# Use a unique temp subdirectory if multithreaded
if OPTS.num_threads > 1 or OPTS.openram_temp == "/tmp":
# Make a unique subdir
tempdir = "/openram_{0}_{1}_temp".format(getpass.getuser(),
os.getpid())
# Only add the unique subdir one time
if tempdir not in OPTS.openram_temp:
OPTS.openram_temp += tempdir
if not OPTS.openram_temp.endswith('/'):
OPTS.openram_temp += "/"
@ -470,6 +485,12 @@ def init_paths():
except OSError as e:
if e.errno == 17: # errno.EEXIST
os.chmod(OPTS.openram_temp, 0o750)
#import inspect
#s = inspect.stack()
#from pprint import pprint
#pprint(s)
#print("Test {0} in dir {1}".format(s[2].filename, OPTS.openram_temp))
# Don't delete the output dir, it may have other files!
# make the directory if it doesn't exist

View File

@ -329,13 +329,13 @@ class bank(design.design):
self.input_control_signals = []
port_num = 0
for port in range(OPTS.num_rw_ports):
self.input_control_signals.append(["s_en{}".format(port_num), "w_en{}".format(port_num), "p_en_bar{}".format(port_num), "wl_en{}".format(port_num)])
self.input_control_signals.append(["p_en_bar{}".format(port_num), "s_en{}".format(port_num), "w_en{}".format(port_num)])
port_num += 1
for port in range(OPTS.num_w_ports):
self.input_control_signals.append(["w_en{}".format(port_num), "p_en_bar{}".format(port_num), "wl_en{}".format(port_num)])
self.input_control_signals.append(["p_en_bar{}".format(port_num), "w_en{}".format(port_num)])
port_num += 1
for port in range(OPTS.num_r_ports):
self.input_control_signals.append(["s_en{}".format(port_num), "p_en_bar{}".format(port_num), "wl_en{}".format(port_num)])
self.input_control_signals.append(["p_en_bar{}".format(port_num), "s_en{}".format(port_num)])
port_num += 1
# Number of control lines in the bus for each port
@ -530,13 +530,16 @@ class bank(design.design):
height=self.dff.height)
elif self.col_addr_size == 2:
self.column_decoder = factory.create(module_type="hierarchical_predecode2x4",
column_decoder=True,
height=self.dff.height)
elif self.col_addr_size == 3:
self.column_decoder = factory.create(module_type="hierarchical_predecode3x8",
column_decoder=True,
height=self.dff.height)
elif self.col_addr_size == 4:
self.column_decoder = factory.create(module_type="hierarchical_predecode4x16",
column_decoder=True,
height=self.dff.height)
else:
# No error checking before?
@ -692,6 +695,8 @@ class bank(design.design):
make_pins=(self.num_banks==1),
pitch=self.m3_pitch)
self.copy_layout_pin(self.port_address_inst[0], "wl_en", self.prefix + "wl_en0")
# Port 1
if len(self.all_ports)==2:
# The other control bus is routed up to two pitches above the bitcell array
@ -707,6 +712,8 @@ class bank(design.design):
make_pins=(self.num_banks==1),
pitch=self.m3_pitch)
self.copy_layout_pin(self.port_address_inst[1], "wl_en", self.prefix + "wl_en1")
def route_port_data_to_bitcell_array(self, port):
""" Routing of BL and BR between port data and bitcell array """
@ -1055,21 +1062,6 @@ class bank(design.design):
to_layer="m2",
offset=control_pos)
# clk to wordline_driver
control_signal = self.prefix + "wl_en{}".format(port)
if port % 2:
pin_pos = self.port_address_inst[port].get_pin("wl_en").uc()
control_y_offset = self.bus_pins[port][control_signal].by()
mid_pos = vector(pin_pos.x, control_y_offset + self.m1_pitch)
else:
pin_pos = self.port_address_inst[port].get_pin("wl_en").bc()
control_y_offset = self.bus_pins[port][control_signal].uy()
mid_pos = vector(pin_pos.x, control_y_offset - self.m1_pitch)
control_x_offset = self.bus_pins[port][control_signal].cx()
control_pos = vector(control_x_offset, mid_pos.y)
self.add_wire(self.m1_stack, [pin_pos, mid_pos, control_pos])
self.add_via_center(layers=self.m1_stack,
offset=control_pos)
def graph_exclude_precharge(self):
"""

View File

@ -64,7 +64,11 @@ class bitcell_array(bitcell_base_array):
self.cell_inst[row, col]=self.add_inst(name=name,
mod=self.cell)
self.connect_inst(self.get_bitcell_pins(row, col))
# If it is a "core" cell, it could be trimmed for sim time
if col>0 and col<self.column_size-1 and row>0 and row<self.row_size-1:
self.trim_insts.add(name)
def analytical_power(self, corner, load):
"""Power of Bitcell array and bitline in nW."""

View File

@ -333,21 +333,19 @@ class control_logic(design.design):
row += 1
self.place_gated_clk_buf_row(row)
row += 1
self.place_wlen_row(row)
row += 1
if (self.port_type == "rw") or (self.port_type == "r"):
self.place_sen_row(row)
row += 1
if (self.port_type == "rw") or (self.port_type == "w"):
self.place_wen_row(row)
height = self.w_en_gate_inst.uy()
control_center_y = self.w_en_gate_inst.uy()
row += 1
self.place_pen_row(row)
row += 1
if (self.port_type == "rw") or (self.port_type == "w"):
self.place_rbl_delay_row(row)
row += 1
if (self.port_type == "rw") or (self.port_type == "r"):
self.place_sen_row(row)
row += 1
self.place_wlen_row(row)
row += 1
self.place_delay(row)
height = self.delay_inst.uy()
control_center_y = self.delay_inst.by()

View File

@ -11,6 +11,7 @@ from sram_factory import factory
from vector import vector
import debug
from numpy import cumsum
from tech import layer_properties as layer_props
class global_bitcell_array(bitcell_base_array.bitcell_base_array):
@ -223,11 +224,20 @@ class global_bitcell_array(bitcell_base_array.bitcell_base_array):
new_name = "{0}_{1}".format(base_name, col + col_value)
self.copy_layout_pin(inst, pin_name, new_name)
# Add the global word lines
wl_layer = layer_props.global_bitcell_array.wordline_layer
for wl_name in self.local_mods[0].get_inputs():
for local_inst in self.local_insts:
wl_pin = local_inst.get_pin(wl_name)
self.add_via_stack_center(from_layer=wl_pin.layer,
to_layer=wl_layer,
offset=wl_pin.center())
left_pin = self.local_insts[0].get_pin(wl_name)
right_pin = self.local_insts[-1].get_pin(wl_name)
self.add_layout_pin_segment_center(text=wl_name,
layer=left_pin.layer,
layer=wl_layer,
start=left_pin.lc(),
end=right_pin.rc())

View File

@ -18,19 +18,17 @@ class hierarchical_predecode(design.design):
"""
Pre 2x4 and 3x8 and TBD 4x16 decoder shared code.
"""
def __init__(self, name, input_number, height=None):
def __init__(self, name, input_number, column_decoder=False, height=None):
self.number_of_inputs = input_number
b = factory.create(module_type=OPTS.bitcell)
if not height:
self.cell_height = b.height
self.column_decoder = False
else:
self.cell_height = height
# If we are pitch matched to the bitcell, it's a predecoder
# otherwise it's a column decoder (out of pgates)
self.column_decoder = (height != b.height)
self.column_decoder = column_decoder
self.number_of_outputs = int(math.pow(2, self.number_of_inputs))
super().__init__(name)
@ -87,8 +85,14 @@ class hierarchical_predecode(design.design):
self.bus_layer = layer_props.hierarchical_predecode.bus_layer
self.bus_directions = layer_props.hierarchical_predecode.bus_directions
self.bus_pitch = getattr(self, self.bus_layer + "_pitch")
self.bus_space = layer_props.hierarchical_predecode.bus_space_factor * getattr(self, self.bus_layer + "_space")
if self.column_decoder:
# Column decoders may be routed on M2/M3 if there's a write mask
self.bus_pitch = self.m3_pitch
self.bus_space = self.m3_space
else:
self.bus_pitch = getattr(self, self.bus_layer + "_pitch")
self.bus_space = getattr(self, self.bus_layer + "_space")
self.bus_space = layer_props.hierarchical_predecode.bus_space_factor * self.bus_space
self.input_layer = layer_props.hierarchical_predecode.input_layer
self.output_layer = layer_props.hierarchical_predecode.output_layer
self.output_layer_pitch = getattr(self, self.output_layer + "_pitch")
@ -114,7 +118,8 @@ class hierarchical_predecode(design.design):
self.input_rails = self.create_vertical_bus(layer=self.bus_layer,
offset=offset,
names=input_names,
length=self.height - 2 * self.bus_pitch)
length=self.height - 2 * self.bus_pitch,
pitch=self.bus_pitch)
invert_names = ["Abar_{}".format(x) for x in range(self.number_of_inputs)]
non_invert_names = ["A_{}".format(x) for x in range(self.number_of_inputs)]
@ -123,7 +128,8 @@ class hierarchical_predecode(design.design):
self.decode_rails = self.create_vertical_bus(layer=self.bus_layer,
offset=offset,
names=decode_names,
length=self.height - 2 * self.bus_pitch)
length=self.height - 2 * self.bus_pitch,
pitch=self.bus_pitch)
def create_input_inverters(self):
""" Create the input inverters to invert input signals for the decode stage. """
@ -175,10 +181,12 @@ class hierarchical_predecode(design.design):
mirror=mirror)
def route(self):
self.route_input_inverters()
self.route_output_inverters()
self.route_inputs_to_rails()
self.route_and_to_rails()
self.route_output_and()
self.route_input_ands()
self.route_output_ands()
self.route_vdd_gnd()
def route_inputs_to_rails(self):
@ -210,7 +218,7 @@ class hierarchical_predecode(design.design):
to_layer=self.bus_layer,
offset=[self.decode_rails[a_pin].cx(), y_offset])
def route_output_and(self):
def route_output_ands(self):
"""
Route all conections of the outputs and gates
"""
@ -225,12 +233,40 @@ class hierarchical_predecode(design.design):
def route_input_inverters(self):
"""
Route all conections of the inputs inverters [Inputs, outputs, vdd, gnd]
Route all conections of the inverter inputs
"""
for inv_num in range(self.number_of_inputs):
in_pin = "in_{}".format(inv_num)
# route input
pin = self.inv_inst[inv_num].get_pin("A")
inv_in_pos = pin.center()
in_pos = vector(self.input_rails[in_pin].cx(), inv_in_pos.y)
self.add_path(self.input_layer, [in_pos, inv_in_pos])
# Inverter input pin
self.add_via_stack_center(from_layer=pin.layer,
to_layer=self.input_layer,
offset=inv_in_pos)
# Input rail pin position
via=self.add_via_stack_center(from_layer=self.input_layer,
to_layer=self.bus_layer,
offset=in_pos,
directions=self.bus_directions)
# Create the input pin at this location on the rail
self.add_layout_pin_rect_center(text=in_pin,
layer=self.bus_layer,
offset=in_pos,
height=via.mod.second_layer_height,
width=via.mod.second_layer_width)
def route_output_inverters(self):
"""
Route all conections of the inverter outputs
"""
for inv_num in range(self.number_of_inputs):
out_pin = "Abar_{}".format(inv_num)
in_pin = "in_{}".format(inv_num)
inv_out_pin = self.inv_inst[inv_num].get_pin("Z")
# add output so that it is just below the vdd or gnd rail
@ -250,31 +286,11 @@ class hierarchical_predecode(design.design):
offset=rail_pos,
directions=self.bus_directions)
# route input
pin = self.inv_inst[inv_num].get_pin("A")
inv_in_pos = pin.center()
in_pos = vector(self.input_rails[in_pin].cx(), inv_in_pos.y)
self.add_path(self.input_layer, [in_pos, inv_in_pos])
self.add_via_stack_center(from_layer=pin.layer,
to_layer=self.input_layer,
offset=inv_in_pos)
via=self.add_via_stack_center(from_layer=self.input_layer,
to_layer=self.bus_layer,
offset=in_pos)
# Create the input pin at this location on the rail
self.add_layout_pin_rect_center(text=in_pin,
layer=self.bus_layer,
offset=in_pos,
height=via.mod.second_layer_height,
width=via.mod.second_layer_width)
def route_input_ands(self):
"""
Route the different permutations of the NAND/AND decocer cells.
"""
# This is a hack to fix via-to-via spacing issues, but it is currently
# causing its own DRC problems.
# if layer_props.hierarchical_predecode.vertical_supply:
# below_rail = vector(self.decode_rails[out_pin].cx(), y_offset - (self.cell_height / 2))
# self.add_path(self.bus_layer, [rail_pos, below_rail], width=self.li_width + self.m1_enclose_mcon * 2)
def route_and_to_rails(self):
# This 2D array defines the connection mapping
and_input_line_combination = self.get_and_input_line_combination()
for k in range(self.number_of_outputs):

View File

@ -13,8 +13,8 @@ class hierarchical_predecode2x4(hierarchical_predecode):
"""
Pre 2x4 decoder used in hierarchical_decoder.
"""
def __init__(self, name, height=None):
super().__init__( name, 2, height)
def __init__(self, name, column_decoder=False, height=None):
super().__init__(name, 2, column_decoder, height)
self.create_netlist()
if not OPTS.netlist_only:

View File

@ -13,8 +13,8 @@ class hierarchical_predecode3x8(hierarchical_predecode):
"""
Pre 3x8 decoder used in hierarchical_decoder.
"""
def __init__(self, name, height=None):
super().__init__(name, 3, height)
def __init__(self, name, column_decoder=False, height=None):
super().__init__(name, 3, column_decoder, height)
self.create_netlist()
if not OPTS.netlist_only:

View File

@ -13,8 +13,8 @@ class hierarchical_predecode4x16(hierarchical_predecode):
"""
Pre 4x16 decoder used in hierarchical_decoder.
"""
def __init__(self, name, height=None):
super().__init__(name, 4, height)
def __init__(self, name, column_decoder=False, height=None):
super().__init__(name, 4, column_decoder, height)
self.create_netlist()
if not OPTS.netlist_only:

View File

@ -10,6 +10,7 @@ from globals import OPTS
from sram_factory import factory
from vector import vector
import debug
from tech import layer_properties as layer_props
class local_bitcell_array(bitcell_base_array.bitcell_base_array):
@ -190,6 +191,11 @@ class local_bitcell_array(bitcell_base_array.bitcell_base_array):
def route(self):
global_wl_layer = layer_props.global_bitcell_array.wordline_layer
global_wl_pitch = getattr(self, "{}_pitch".format(global_wl_layer))
global_wl_pitch_factor = layer_props.global_bitcell_array.wordline_pitch_factor
local_wl_layer = layer_props.local_bitcell_array.wordline_layer
# Route the global wordlines
for port in self.all_ports:
if port == 0:
@ -204,25 +210,33 @@ class local_bitcell_array(bitcell_base_array.bitcell_base_array):
in_pin = self.wl_insts[port].get_pin(in_pin_name)
y_offset = in_pin.cy()
if port == 0:
y_offset -= 2 * self.m3_pitch
y_offset -= global_wl_pitch_factor * global_wl_pitch
else:
y_offset += 2 * self.m3_pitch
self.add_layout_pin_segment_center(text=wl_name,
layer="m3",
start=vector(self.wl_insts[port].lx(), y_offset),
end=vector(self.wl_insts[port].lx() + self.wl_array.width, y_offset))
y_offset += global_wl_pitch_factor * global_wl_pitch
mid = vector(in_pin.cx(), y_offset)
self.add_path("m2", [in_pin.center(), mid])
self.add_layout_pin_rect_center(text=wl_name,
layer=global_wl_layer,
offset=mid)
self.add_path(local_wl_layer, [in_pin.center(), mid])
# A short jog to the global line
self.add_via_stack_center(from_layer=in_pin.layer,
to_layer="m2",
offset=in_pin.center())
self.add_via_center(self.m2_stack,
offset=mid)
to_layer=local_wl_layer,
offset=in_pin.center(),
min_area=True)
self.add_path(local_wl_layer, [in_pin.center(), mid])
self.add_via_stack_center(from_layer=local_wl_layer,
to_layer=global_wl_layer,
offset=mid,
min_area=True)
# Add the global WL pin
self.add_layout_pin_rect_center(text=wl_name,
layer=global_wl_layer,
offset=mid)
# Route the buffers
for port in self.all_ports:
driver_outputs = self.driver_wordline_outputs[port]
@ -239,7 +253,12 @@ class local_bitcell_array(bitcell_base_array.bitcell_base_array):
out_loc = out_pin.lc()
mid_loc = vector(self.wl_insts[port].lx() - 1.5 * self.m3_pitch, out_loc.y)
in_loc = in_pin.rc()
self.add_path(out_pin.layer, [out_loc, mid_loc, in_loc])
self.add_path(out_pin.layer, [out_loc, mid_loc])
self.add_via_stack_center(from_layer=out_pin.layer,
to_layer=in_pin.layer,
offset=mid_loc)
self.add_path(in_pin.layer, [mid_loc, in_loc])
def get_main_array_top(self):
return self.bitcell_array_inst.by() + self.bitcell_array.get_main_array_top()

View File

@ -104,7 +104,7 @@ class options(optparse.Values):
# This determines whether LVS and DRC is checked for every submodule.
inline_lvsdrc = False
# Remove noncritical memory cells for characterization speed-up
trim_netlist = False
trim_netlist = True
# Run with extracted parasitics
use_pex = False
# Output config with all options
@ -135,7 +135,7 @@ class options(optparse.Values):
magic_exe = None
# Number of threads to use
num_threads = 2
num_threads = 1
# Number of threads to use in ngspice/hspice
num_sim_threads = 2
@ -155,6 +155,9 @@ class options(optparse.Values):
# Route the input/output pins to the perimeter
perimeter_pins = True
# Detailed or abstract LEF view
detailed_lef = False
keep_temp = False

View File

@ -87,13 +87,11 @@ class pdriver(pgate.pgate):
def add_modules(self):
self.inv_list = []
add_well = self.add_wells
for size in self.size_list:
temp_inv = factory.create(module_type="pinv",
size=size,
height=self.height,
add_wells=add_well)
add_well=False
add_wells=self.add_wells)
self.inv_list.append(temp_inv)
self.add_mod(temp_inv)

View File

@ -148,8 +148,9 @@ class pin_group:
enclosure = self.router.compute_pin_enclosure(ll, ur, ll.z)
pin_list.append(enclosure)
debug.check(len(pin_list) > 0,
"Did not find any enclosures.")
if len(pin_list) == 0:
debug.error("Did not find any enclosures for {}".format(self.name))
self.router.write_debug_gds("pin_enclosure_error.gds")
# Now simplify the enclosure list
new_pin_list = self.remove_redundant_shapes(pin_list)

View File

@ -28,7 +28,7 @@ class router(router_tech):
route on a given layer. This is limited to two layer routes.
It populates blockages on a grid class.
"""
def __init__(self, layers, design, gds_filename=None, bbox=None, route_track_width=1):
def __init__(self, layers, design, gds_filename=None, bbox=None, margin=0, route_track_width=1):
"""
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
@ -83,9 +83,11 @@ class router(router_tech):
# A list of path blockages (they might be expanded for wide metal DRC)
self.path_blockages = []
self.init_bbox(bbox)
# The perimeter pins should be placed outside the SRAM macro by a distance
self.margin = margin
self.init_bbox(bbox, margin)
def init_bbox(self, bbox=None):
def init_bbox(self, bbox=None, margin=0):
"""
Initialize the ll,ur values with the paramter or using the layout boundary.
"""
@ -99,18 +101,19 @@ class router(router_tech):
else:
self.ll, self.ur = bbox
self.bbox = (self.ll, self.ur)
margin_offset = vector(margin, margin)
self.bbox = (self.ll - margin_offset, self.ur + margin_offset)
size = self.ur - self.ll
debug.info(1, "Size: {0} x {1}".format(size.x, size.y))
debug.info(1, "Size: {0} x {1} with perimeter margin {2}".format(size.x, size.y, margin))
def get_bbox(self):
return self.bbox
def create_routing_grid(self, router_type, bbox=None):
def create_routing_grid(self, router_type):
"""
Create a sprase routing grid with A* expansion functions.
"""
self.init_bbox(bbox)
self.init_bbox(self.bbox, self.margin)
self.rg = router_type(self.ll, self.ur, self.track_width)
def clear_pins(self):
@ -1212,8 +1215,9 @@ class router(router_tech):
return None
def get_pin(self, pin_name):
def get_ll_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:

View File

@ -17,12 +17,17 @@ class signal_escape_router(router):
A router that routes signals to perimeter and makes pins.
"""
def __init__(self, layers, design, bbox=None, gds_filename=None):
def __init__(self, layers, design, bbox=None, margin=0, 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, bbox)
router.__init__(self,
layers=layers,
design=design,
gds_filename=gds_filename,
bbox=bbox,
margin=margin)
def perimeter_dist(self, pin_name):
"""
@ -54,8 +59,8 @@ class signal_escape_router(router):
start_time = datetime.now()
for pin_name in ordered_pin_names:
self.route_signal(pin_name)
#if pin_name == "dout1[1]":
# self.write_debug_gds("postroute.gds", False)
# if pin_name == "dout0[1]":
# self.write_debug_gds("postroute.gds", True)
print_time("Maze routing pins",datetime.now(), start_time, 3)

View File

@ -79,8 +79,8 @@ class supply_tree_router(router):
"""
remaining_components = sum(not x.is_routed() for x in self.pin_groups[pin_name])
debug.info(1, "Routing {0} with {1} pin components to connect.".format(pin_name,
remaining_components))
debug.info(1, "Routing {0} with {1} pins.".format(pin_name,
remaining_components))
# Create full graph
debug.info(2, "Creating adjacency matrix")
@ -108,7 +108,9 @@ class supply_tree_router(router):
connections.append((x, y))
# Route MST components
for (src, dest) in connections:
for index, (src, dest) in enumerate(connections):
if not (index % 100):
debug.info(1, "{0} supply segments routed, {1} remaining.".format(index, len(connections) - index))
self.route_signal(pin_name, src, dest)
# if pin_name == "gnd":
# print("\nSRC {}: ".format(src) + str(self.pin_groups[pin_name][src].grids) + str(self.pin_groups[pin_name][src].blockages))

View File

@ -57,11 +57,8 @@ class sram():
if not OPTS.is_unit_test:
print_time("SRAM creation", datetime.datetime.now(), start_time)
def sp_write(self, name):
self.s.sp_write(name)
def lvs_write(self, name):
self.s.lvs_write(name)
def sp_write(self, name, lvs=False, trim=False):
self.s.sp_write(name, lvs, trim)
def lef_write(self, name):
self.s.lef_write(name)
@ -123,7 +120,7 @@ class sram():
start_time = datetime.datetime.now()
lvsname = OPTS.output_path + self.s.name + ".lvs.sp"
debug.print_raw("LVS: Writing to {0}".format(lvsname))
self.lvs_write(lvsname)
self.sp_write(lvsname, lvs=True)
if not OPTS.netlist_only and OPTS.check_lvsdrc:
verify.write_lvs_script(cell_name=self.s.name,
gds_name=os.path.basename(gdsname),

View File

@ -340,6 +340,15 @@ class sram_1bank(sram_base):
def route_dff(self, port, add_routes):
# This is only done when we add_routes because the data channel will be larger
# so that can be used for area estimation.
if add_routes:
self.route_col_addr_dffs(port)
self.route_data_dffs(port, add_routes)
def route_col_addr_dffs(self, port):
route_map = []
# column mux dff is routed on it's own since it is to the far end
@ -351,6 +360,38 @@ class sram_1bank(sram_base):
bank_pins = [self.bank_inst.get_pin(x) for x in bank_names]
route_map.extend(list(zip(bank_pins, dff_pins)))
if len(route_map) > 0:
layer_stack = self.m1_stack
if port == 0:
offset = vector(self.control_logic_insts[port].rx() + self.dff.width,
- self.data_bus_size[port] + 2 * self.m3_pitch)
cr = channel_route(netlist=route_map,
offset=offset,
layer_stack=layer_stack,
parent=self)
# This causes problem in magic since it sometimes cannot extract connectivity of isntances
# with no active devices.
self.add_inst(cr.name, cr)
self.connect_inst([])
#self.add_flat_inst(cr.name, cr)
else:
offset = vector(0,
self.bank.height + self.m3_pitch)
cr = channel_route(netlist=route_map,
offset=offset,
layer_stack=layer_stack,
parent=self)
# This causes problem in magic since it sometimes cannot extract connectivity of isntances
# with no active devices.
self.add_inst(cr.name, cr)
self.connect_inst([])
#self.add_flat_inst(cr.name, cr)
def route_data_dffs(self, port, add_routes):
route_map = []
# wmask dff
if self.num_wmasks > 0 and port in self.write_ports:
dff_names = ["dout_{}".format(x) for x in range(self.num_wmasks)]
@ -377,6 +418,7 @@ class sram_1bank(sram_base):
if len(route_map) > 0:
# The write masks will have blockages on M1
if self.num_wmasks > 0 and port in self.write_ports:
layer_stack = self.m3_stack
else:

View File

@ -196,7 +196,7 @@ class sram_base(design, verilog, lef):
self.add_lvs_correspondence_points()
# self.offset_all_coordinates()
self.offset_all_coordinates()
highest_coord = self.find_highest_coords()
self.width = highest_coord[0]
@ -248,22 +248,33 @@ class sram_base(design, verilog, lef):
# Find the lowest leftest pin for vdd and gnd
for pin_name in ["vdd", "gnd"]:
# Copy the pin shape to rectangles
# Copy the pin shape(s) to rectangles
for pin in self.get_pins(pin_name):
self.add_rect(pin.layer,
pin.ll(),
pin.width(),
pin.height())
# Remove the pins
# Remove the pin shape(s)
self.remove_layout_pin(pin_name)
pin = rtr.get_pin(pin_name)
# Get the lowest, leftest pin
pin = rtr.get_ll_pin(pin_name)
# Add it as an IO pin to the perimeter
lowest_coord = self.find_lowest_coords()
route_width = pin.rx() - lowest_coord.x
pin_width = 2 * getattr(self, "{}_width".format(pin.layer))
pin_offset = vector(lowest_coord.x, pin.by())
self.add_layout_pin(pin_name,
pin.layer,
pin.ll(),
pin.width(),
pin_offset,
pin_width,
pin.height())
self.add_rect(pin.layer,
pin_offset,
route_width,
pin.height())
def route_escape_pins(self):
"""
@ -306,7 +317,9 @@ class sram_base(design, verilog, lef):
pins_to_route.append("spare_wen{0}[{1}]".format(port, bit))
from signal_escape_router import signal_escape_router as router
rtr=router(self.m3_stack, self)
rtr=router(layers=self.m3_stack,
design=self,
margin=4 * self.m3_pitch)
rtr.escape_route(pins_to_route)
def compute_bus_sizes(self):
@ -675,7 +688,7 @@ class sram_base(design, verilog, lef):
return insts
def sp_write(self, sp_name, lvs_netlist=False):
def sp_write(self, sp_name, lvs=False, trim=False):
# Write the entire spice of the object to the file
############################################################
# Spice circuit
@ -688,6 +701,8 @@ class sram_base(design, verilog, lef):
sp.write("* Data bits: {}\n".format(self.word_size))
sp.write("* Banks: {}\n".format(self.num_banks))
sp.write("* Column mux: {}:1\n".format(self.words_per_row))
sp.write("* Trimmed: {}\n".format(trim))
sp.write("* LVS: {}\n".format(lvs))
sp.write("**************************************************\n")
# This causes unit test mismatch
@ -696,13 +711,10 @@ class sram_base(design, verilog, lef):
# sp.write(".global {0} {1}\n".format(spice["vdd_name"],
# spice["gnd_name"]))
usedMODS = list()
self.sp_write_file(sp, usedMODS, lvs_netlist=lvs_netlist)
self.sp_write_file(sp, usedMODS, lvs=lvs, trim=trim)
del usedMODS
sp.close()
def lvs_write(self, sp_name):
self.sp_write(sp_name, lvs_netlist=True)
def graph_exclude_bits(self, targ_row, targ_col):
"""
Excludes bits in column from being added to graph except target

View File

@ -0,0 +1,56 @@
#!/usr/bin/env python3
# See LICENSE for licensing information.
#
# Copyright (c) 2016-2021 Regents of the University of California and The Board
# of Regents for the Oklahoma Agricultural and Mechanical College
# (acting for and on behalf of Oklahoma State University)
# All rights reserved.
#
import unittest
from testutils import *
import sys, os
sys.path.append(os.getenv("OPENRAM_HOME"))
import globals
from globals import OPTS
from sram_factory import factory
import debug
class sram_1bank_4mux_1rw_1r_test(openram_test):
def runTest(self):
config_file = "{}/tests/configs/config".format(os.getenv("OPENRAM_HOME"))
globals.init_openram(config_file)
from sram_config import sram_config
OPTS.num_rw_ports = 1
OPTS.num_r_ports = 1
OPTS.num_w_ports = 0
globals.setup_bitcell()
c = sram_config(word_size=4,
num_words=64,
num_banks=1)
c.words_per_row=4
c.recompute_sizes()
debug.info(1, "Layout test for {}rw,{}r,{}w sram "
"with {} bit words, {} words, {} words per "
"row, {} banks".format(OPTS.num_rw_ports,
OPTS.num_r_ports,
OPTS.num_w_ports,
c.word_size,
c.num_words,
c.words_per_row,
c.num_banks))
a = factory.create(module_type="sram", sram_config=c)
self.local_check(a, final_verification=True)
globals.end_openram()
# run the test from the command line
if __name__ == "__main__":
(OPTS, args) = globals.parse_args()
del sys.argv[1:]
header(__file__, OPTS.tech_name)
unittest.main(testRunner=debugTestRunner())

View File

@ -30,19 +30,13 @@ class timing_sram_test(openram_test):
reload(characterizer)
from characterizer import delay
from sram_config import sram_config
c = sram_config(word_size=1,
c = sram_config(word_size=4,
num_words=16,
num_banks=1)
c.words_per_row=1
# c = sram_config(word_size=32,
# num_words=256,
# num_banks=1)
# c.words_per_row=2
c.recompute_sizes()
debug.info(1, "Testing timing for sample 1bit, 16words SRAM with 1 bank")
s = factory.create(module_type="sram", sram_config=c)
#import sys
#sys.exit(1)
tempspice = OPTS.openram_temp + "temp.sp"
s.sp_write(tempspice)
@ -61,34 +55,35 @@ class timing_sram_test(openram_test):
data.update(port_data[0])
if OPTS.tech_name == "freepdk45":
golden_data = {'min_period': 0.898,
'write1_power': [0.2659137999999999],
'disabled_write0_power': [0.1782495],
'disabled_read0_power': [0.14490679999999997],
'write0_power': [0.3330119],
'disabled_write1_power': [0.1865223],
'leakage_power': 0.0014532,
'disabled_read1_power': [0.1627516],
'slew_lh': [0.25367799999999996],
'slew_hl': [0.25367799999999996],
'delay_lh': [0.23820930000000004],
'delay_hl': [0.23820930000000004],
'read1_power': [0.3005756],
'read0_power': [0.3005888]}
golden_data = {'delay_hl': [0.23941909999999997],
'delay_lh': [0.23941909999999997],
'disabled_read0_power': [0.18183159999999998],
'disabled_read1_power': [0.1979447],
'disabled_write0_power': [0.2129604],
'disabled_write1_power': [0.23266849999999997],
'leakage_power': 0.0019882,
'min_period': 0.938,
'read0_power': [0.4115467],
'read1_power': [0.41158859999999997],
'slew_hl': [0.2798571],
'slew_lh': [0.2798571],
'write0_power': [0.45873749999999996],
'write1_power': [0.40716199999999997]}
elif OPTS.tech_name == "scn4m_subm":
golden_data = {'leakage_power': 0.0006356576000000001,
'write1_power': [11.292700000000002],
'read0_power': [12.98],
'disabled_write1_power': [8.3707],
'write0_power': [14.4447], 'delay_hl': [1.7445000000000002],
'disabled_read0_power': [6.4325],
'slew_hl': [1.7437],
'disabled_write0_power': [8.1307],
'slew_lh': [1.7437],
'read1_power': [12.9869],
'disabled_read1_power': [7.706],
'min_period': 6.25,
'delay_lh': [1.7445000000000002]}
golden_data = {'delay_hl': [1.7652000000000003],
'delay_lh': [1.7652000000000003],
'disabled_read0_power': [8.2716],
'disabled_read1_power': [9.5857],
'disabled_write0_power': [9.9825],
'disabled_write1_power': [10.598400000000002],
'leakage_power': 0.0006681718,
'min_period': 6.562,
'read0_power': [18.6446],
'read1_power': [18.5126],
'slew_hl': [1.9026],
'slew_lh': [1.9026],
'write0_power': [21.022600000000004],
'write1_power': [16.6377]}
else:
self.assertTrue(False) # other techs fail
# Check if no too many or too few results

View File

@ -12,8 +12,7 @@ import sys, os
sys.path.append(os.getenv("OPENRAM_HOME"))
import globals
from globals import OPTS
from sram_factory import factory
import debug
class timing_setup_test(openram_test):
@ -29,14 +28,12 @@ class timing_setup_test(openram_test):
import characterizer
reload(characterizer)
from characterizer import setup_hold
import sram
import tech
slews = [tech.spice["rise_time"]*2]
corner = (OPTS.process_corners[0], OPTS.supply_voltages[0], OPTS.temperatures[0])
sh = setup_hold(corner)
data = sh.analyze(slews,slews)
#print data
if OPTS.tech_name == "freepdk45":
golden_data = {'hold_times_HL': [-0.0158691],
'hold_times_LH': [-0.0158691],
@ -47,6 +44,11 @@ class timing_setup_test(openram_test):
'hold_times_LH': [-0.11718749999999999],
'setup_times_HL': [0.16357419999999998],
'setup_times_LH': [0.1757812]}
elif OPTS.tech_name == "sky130":
golden_data = {'hold_times_HL': [-0.05615234],
'hold_times_LH': [-0.03173828],
'setup_times_HL': [0.078125],
'setup_times_LH': [0.1025391]}
else:
self.assertTrue(False) # other techs fail

View File

@ -30,7 +30,7 @@ class timing_sram_test(openram_test):
reload(characterizer)
from characterizer import delay
from sram_config import sram_config
c = sram_config(word_size=1,
c = sram_config(word_size=4,
num_words=16,
num_banks=1)
c.words_per_row=1
@ -55,35 +55,35 @@ class timing_sram_test(openram_test):
data.update(port_data[0])
if OPTS.tech_name == "freepdk45":
golden_data = {'slew_lh': [0.2592187],
'slew_hl': [0.2592187],
'delay_lh': [0.2465583],
'disabled_write0_power': [0.1924678],
'disabled_read0_power': [0.152483],
'write0_power': [0.3409064],
'disabled_read1_power': [0.1737818],
'read0_power': [0.3096708],
'read1_power': [0.3107916],
'delay_hl': [0.2465583],
'write1_power': [0.26915849999999997],
'leakage_power': 0.002044307,
'min_period': 0.898,
'disabled_write1_power': [0.201411]}
golden_data = {'delay_hl': [0.24671600000000002],
'delay_lh': [0.24671600000000002],
'disabled_read0_power': [0.1749204],
'disabled_read1_power': [0.1873704],
'disabled_write0_power': [0.204619],
'disabled_write1_power': [0.2262653],
'leakage_power': 0.0021375310000000002,
'min_period': 0.977,
'read0_power': [0.3856875],
'read1_power': [0.38856060000000003],
'slew_hl': [0.2842019],
'slew_lh': [0.2842019],
'write0_power': [0.45274410000000004],
'write1_power': [0.38727789999999995]}
elif OPTS.tech_name == "scn4m_subm":
golden_data = {'delay_hl': [1.8435739999999998],
'delay_lh': [1.8435739999999998],
'disabled_read0_power': [5.917947],
'disabled_read1_power': [7.154297],
'disabled_write0_power': [7.696351],
'disabled_write1_power': [7.999409000000001],
'leakage_power': 0.004809726,
'min_period': 6.875,
'read0_power': [11.833079999999999],
'read1_power': [11.99236],
'slew_hl': [1.8668490000000002],
'slew_lh': [1.8668490000000002],
'write0_power': [13.287510000000001],
'write1_power': [10.416369999999999]}
golden_data = {'delay_hl': [1.882508],
'delay_lh': [1.882508],
'disabled_read0_power': [7.487227],
'disabled_read1_power': [8.749013],
'disabled_write0_power': [9.268901],
'disabled_write1_power': [9.962973],
'leakage_power': 0.0046686359999999994,
'min_period': 7.188,
'read0_power': [16.64011],
'read1_power': [17.20825],
'slew_hl': [2.039655],
'slew_lh': [2.039655],
'write0_power': [19.31883],
'write1_power': [15.297369999999999]}
else:
self.assertTrue(False) # other techs fail

View File

@ -12,8 +12,7 @@ import sys, os
sys.path.append(os.getenv("OPENRAM_HOME"))
import globals
from globals import OPTS
from sram_factory import factory
import debug
class timing_setup_test(openram_test):
@ -29,14 +28,12 @@ class timing_setup_test(openram_test):
import characterizer
reload(characterizer)
from characterizer import setup_hold
import sram
import tech
slews = [tech.spice["rise_time"]*2]
corner = (OPTS.process_corners[0], OPTS.supply_voltages[0], OPTS.temperatures[0])
sh = setup_hold(corner)
data = sh.analyze(slews,slews)
#print data
if OPTS.tech_name == "freepdk45":
golden_data = {'hold_times_HL': [-0.01586914],
'hold_times_LH': [-0.01586914],
@ -47,6 +44,11 @@ class timing_setup_test(openram_test):
'hold_times_LH': [-0.1293945],
'setup_times_HL': [0.1757812],
'setup_times_LH': [0.1879883]}
elif OPTS.tech_name == "sky130":
golden_data = {'hold_times_HL': [-0.05615234],
'hold_times_LH': [-0.03173828],
'setup_times_HL': [0.078125],
'setup_times_LH': [0.1025391]}
else:
self.assertTrue(False) # other techs fail

View File

@ -24,7 +24,7 @@ class psram_1bank_2mux_func_test(openram_test):
OPTS.analytical_delay = False
OPTS.netlist_only = True
OPTS.trim_netlist = False
OPTS.bitcell = "pbitcell"
OPTS.replica_bitcell="replica_pbitcell"
OPTS.dummy_bitcell="dummy_pbitcell"
@ -53,10 +53,7 @@ class psram_1bank_2mux_func_test(openram_test):
c.words_per_row,
c.num_banks))
s = factory.create(module_type="sram", sram_config=c)
tempspice = OPTS.openram_temp + "sram.sp"
s.sp_write(tempspice)
f = functional(s.s, tempspice)
f = functional(s.s)
(fail, error) = f.run()
self.assertTrue(fail, error)

View File

@ -25,7 +25,7 @@ class psram_1bank_4mux_func_test(openram_test):
OPTS.analytical_delay = False
OPTS.netlist_only = True
OPTS.trim_netlist = False
OPTS.bitcell = "pbitcell"
OPTS.replica_bitcell="replica_pbitcell"
OPTS.dummy_bitcell="dummy_pbitcell"
@ -54,11 +54,8 @@ class psram_1bank_4mux_func_test(openram_test):
c.words_per_row,
c.num_banks))
s = factory.create(module_type="sram", sram_config=c)
tempspice = OPTS.openram_temp + "sram.sp"
s.sp_write(tempspice)
corner = (OPTS.process_corners[0], OPTS.supply_voltages[0], OPTS.temperatures[0])
f = functional(s.s, tempspice, corner)
f = functional(s.s, corner=corner)
(fail, error) = f.run()
self.assertTrue(fail, error)

View File

@ -25,7 +25,7 @@ class psram_1bank_8mux_func_test(openram_test):
OPTS.analytical_delay = False
OPTS.netlist_only = True
OPTS.trim_netlist = False
OPTS.bitcell = "pbitcell"
OPTS.replica_bitcell="replica_pbitcell"
OPTS.dummy_bitcell="dummy_pbitcell"
@ -54,10 +54,7 @@ class psram_1bank_8mux_func_test(openram_test):
c.words_per_row,
c.num_banks))
s = factory.create(module_type="sram", sram_config=c)
tempspice = OPTS.openram_temp + "sram.sp"
s.sp_write(tempspice)
f = functional(s.s, tempspice)
f = functional(s.s)
(fail, error) = f.run()
self.assertTrue(fail, error)

View File

@ -24,7 +24,7 @@ class psram_1bank_nomux_func_test(openram_test):
OPTS.analytical_delay = False
OPTS.netlist_only = True
OPTS.trim_netlist = False
OPTS.bitcell = "pbitcell"
OPTS.replica_bitcell="replica_pbitcell"
OPTS.dummy_bitcell="dummy_pbitcell"
@ -53,10 +53,7 @@ class psram_1bank_nomux_func_test(openram_test):
c.words_per_row,
c.num_banks))
s = factory.create(module_type="sram", sram_config=c)
tempspice = OPTS.openram_temp + "sram.sp"
s.sp_write(tempspice)
f = functional(s.s, tempspice)
f = functional(s.s)
(fail, error) = f.run()
self.assertTrue(fail, error)

View File

@ -25,7 +25,7 @@ class sram_1bank_2mux_func_test(openram_test):
OPTS.analytical_delay = False
OPTS.netlist_only = True
OPTS.trim_netlist = False
# This is a hack to reload the characterizer __init__ with the spice version
from importlib import reload
import characterizer
@ -43,10 +43,7 @@ class sram_1bank_2mux_func_test(openram_test):
c.words_per_row,
c.num_banks))
s = factory.create(module_type="sram", sram_config=c)
tempspice = OPTS.openram_temp + "sram.sp"
s.sp_write(tempspice)
f = functional(s.s, tempspice)
f = functional(s.s)
(fail, error) = f.run()
self.assertTrue(fail, error)

View File

@ -25,7 +25,7 @@ class sram_1bank_2mux_func_test(openram_test):
OPTS.analytical_delay = False
OPTS.netlist_only = True
OPTS.trim_netlist = False
# This is a hack to reload the characterizer __init__ with the spice version
from importlib import reload
import characterizer
@ -44,10 +44,7 @@ class sram_1bank_2mux_func_test(openram_test):
c.words_per_row,
c.num_banks))
s = factory.create(module_type="sram", sram_config=c)
tempspice = OPTS.openram_temp + "sram.sp"
s.sp_write(tempspice)
f = functional(s.s, tempspice)
f = functional(s.s)
(fail, error) = f.run()
self.assertTrue(fail, error)

View File

@ -45,10 +45,7 @@ class sram_1bank_2mux_sparecols_func_test(openram_test):
c.num_spare_cols,
c.num_banks))
s = factory.create(module_type="sram", sram_config=c)
tempspice = OPTS.openram_temp + "sram.sp"
s.sp_write(tempspice)
f = functional(s.s, tempspice)
f = functional(s.s)
(fail, error) = f.run()
self.assertTrue(fail, error)

View File

@ -25,7 +25,7 @@ class sram_1bank_4mux_func_test(openram_test):
OPTS.analytical_delay = False
OPTS.netlist_only = True
OPTS.trim_netlist = False
# This is a hack to reload the characterizer __init__ with the spice version
from importlib import reload
import characterizer
@ -43,10 +43,7 @@ class sram_1bank_4mux_func_test(openram_test):
c.words_per_row,
c.num_banks))
s = factory.create(module_type="sram", sram_config=c)
tempspice = OPTS.openram_temp + "sram.sp"
s.sp_write(tempspice)
f = functional(s.s, tempspice)
f = functional(s.s)
(fail, error) = f.run()
self.assertTrue(fail, error)

View File

@ -25,7 +25,7 @@ class sram_1bank_8mux_func_test(openram_test):
OPTS.analytical_delay = False
OPTS.netlist_only = True
OPTS.trim_netlist = False
# This is a hack to reload the characterizer __init__ with the spice version
from importlib import reload
import characterizer
@ -46,10 +46,7 @@ class sram_1bank_8mux_func_test(openram_test):
c.words_per_row,
c.num_banks))
s = factory.create(module_type="sram", sram_config=c)
tempspice = OPTS.openram_temp + "sram.sp"
s.sp_write(tempspice)
f = functional(s.s, tempspice)
f = functional(s.s)
(fail, error) = f.run()
self.assertTrue(fail, error)

View File

@ -24,6 +24,7 @@ class psram_1bank_nomux_func_test(openram_test):
OPTS.analytical_delay = False
OPTS.netlist_only = True
OPTS.trim_netlist = False
OPTS.num_rw_ports = 1
OPTS.num_w_ports = 0
OPTS.num_r_ports = 1
@ -46,10 +47,7 @@ class psram_1bank_nomux_func_test(openram_test):
c.words_per_row,
c.num_banks))
s = factory.create(module_type="sram", sram_config=c)
tempspice = OPTS.openram_temp + "sram.sp"
s.sp_write(tempspice)
f = functional(s.s, tempspice)
f = functional(s.s)
(fail, error) = f.run()
self.assertTrue(fail, error)

View File

@ -24,6 +24,7 @@ class sram_1bank_nomux_func_test(openram_test):
globals.init_openram(config_file)
OPTS.analytical_delay = False
OPTS.netlist_only = True
OPTS.trim_netlist = False
# This is a hack to reload the characterizer __init__ with the spice version
from importlib import reload
@ -42,10 +43,7 @@ class sram_1bank_nomux_func_test(openram_test):
c.words_per_row,
c.num_banks))
s = factory.create(module_type="sram", sram_config=c)
tempspice = OPTS.openram_temp + "sram.sp"
s.sp_write(tempspice)
f = functional(s.s, tempspice)
f = functional(s.s)
(fail, error) = f.run()
self.assertTrue(fail, error)

View File

@ -24,6 +24,7 @@ class sram_1bank_nomux_sparecols_func_test(openram_test):
globals.init_openram(config_file)
OPTS.analytical_delay = False
OPTS.netlist_only = True
OPTS.trim_netlist = False
# This is a hack to reload the characterizer __init__ with the spice version
from importlib import reload
@ -43,10 +44,7 @@ class sram_1bank_nomux_sparecols_func_test(openram_test):
c.words_per_row,
c.num_banks))
s = factory.create(module_type="sram", sram_config=c)
tempspice = OPTS.openram_temp + "sram.sp"
s.sp_write(tempspice)
f = functional(s.s, tempspice)
f = functional(s.s)
(fail, error) = f.run()
self.assertTrue(fail, error)

View File

@ -24,6 +24,7 @@ class sram_wmask_1w_1r_func_test(openram_test):
OPTS.analytical_delay = False
OPTS.netlist_only = True
OPTS.trim_netlist = False
OPTS.num_rw_ports = 1
OPTS.num_w_ports = 0
OPTS.num_r_ports = 1
@ -49,10 +50,7 @@ class sram_wmask_1w_1r_func_test(openram_test):
c.write_size,
c.num_banks))
s = factory.create(module_type="sram", sram_config=c)
tempspice = OPTS.openram_temp + "sram.sp"
s.sp_write(tempspice)
f = functional(s.s, tempspice)
f = functional(s.s)
(fail, error) = f.run()
self.assertTrue(fail, error)

View File

@ -45,10 +45,7 @@ class sram_wmask_func_test(openram_test):
c.write_size,
c.num_banks))
s = factory.create(module_type="sram", sram_config=c)
tempspice = OPTS.openram_temp + "sram.sp"
s.sp_write(tempspice)
f = functional(s.s, tempspice)
f = functional(s.s)
(fail, error) = f.run()
self.assertTrue(fail, error)

View File

@ -21,7 +21,6 @@ class lib_test(openram_test):
globals.init_openram(config_file)
OPTS.analytical_delay = False
OPTS.netlist_only = True
OPTS.trim_netlist = False
# This is a hack to reload the characterizer __init__ with the spice version
from importlib import reload

View File

@ -45,7 +45,7 @@ class sram_pex_test(openram_test):
tempspice = self.run_pex(s)
corner = (OPTS.process_corners[0], OPTS.supply_voltages[0], OPTS.temperatures[0])
f = functional(s.s, tempspice, corner)
f = functional(s.s, spfile=tempspice, corner=corner)
(fail, error) = f.run()
self.assertTrue(fail, error)

View File

@ -49,6 +49,8 @@ class openram_back_end_test(openram_test):
if OPTS.tech_name:
options += " -t {}".format(OPTS.tech_name)
options += " -j 2"
# Always perform code coverage
if OPTS.coverage == 0:
debug.warning("Failed to find coverage installation. This can be installed with pip3 install coverage")

View File

@ -49,6 +49,8 @@ class openram_front_end_test(openram_test):
if OPTS.tech_name:
options += " -t {}".format(OPTS.tech_name)
options += " -j 2"
# Always perform code coverage
if OPTS.coverage == 0:
debug.warning("Failed to find coverage installation. This can be installed with pip3 install coverage")

View File

@ -24,7 +24,6 @@ class riscv_func_test(openram_test):
globals.init_openram(config_file)
OPTS.analytical_delay = False
OPTS.netlist_only = True
OPTS.trim_netlist = False
OPTS.local_array_size = 16
OPTS.num_rw_ports = 1
OPTS.num_w_ports = 0
@ -49,11 +48,8 @@ class riscv_func_test(openram_test):
c.words_per_row,
c.num_banks))
s = factory.create(module_type="sram", sram_config=c)
tempspice = OPTS.openram_temp + "sram.sp"
s.sp_write(tempspice)
corner = (OPTS.process_corners[0], OPTS.supply_voltages[0], OPTS.temperatures[0])
f = functional(s.s, tempspice, corner)
f = functional(s.s, corner=corner)
(fail, error) = f.run()
self.assertTrue(fail, error)

View File

@ -12,6 +12,8 @@ module sram_2_16_1_freepdk45(
parameter RAM_DEPTH = 1 << ADDR_WIDTH;
// FIXME: This delay is arbitrary.
parameter DELAY = 3 ;
parameter VERBOSE = 1 ; //Set to 0 to only display warnings
parameter T_HOLD = 1 ; //Delay to hold dout value after posedge. Value is arbitrary
input clk0; // clock
input csb0; // active low chip select
@ -33,10 +35,10 @@ module sram_2_16_1_freepdk45(
web0_reg = web0;
addr0_reg = addr0;
din0_reg = din0;
dout0 = 2'bx;
if ( !csb0_reg && web0_reg )
#(T_HOLD) dout0 = 2'bx;
if ( !csb0_reg && web0_reg && VERBOSE )
$display($time," Reading %m addr0=%b dout0=%b",addr0_reg,mem[addr0_reg]);
if ( !csb0_reg && !web0_reg )
if ( !csb0_reg && !web0_reg && VERBOSE )
$display($time," Writing %m addr0=%b din0=%b",addr0_reg,din0_reg);
end

View File

@ -12,6 +12,8 @@ module sram_2_16_1_scn4m_subm(
parameter RAM_DEPTH = 1 << ADDR_WIDTH;
// FIXME: This delay is arbitrary.
parameter DELAY = 3 ;
parameter VERBOSE = 1 ; //Set to 0 to only display warnings
parameter T_HOLD = 1 ; //Delay to hold dout value after posedge. Value is arbitrary
input clk0; // clock
input csb0; // active low chip select
@ -33,10 +35,10 @@ module sram_2_16_1_scn4m_subm(
web0_reg = web0;
addr0_reg = addr0;
din0_reg = din0;
dout0 = 2'bx;
if ( !csb0_reg && web0_reg )
#(T_HOLD) dout0 = 2'bx;
if ( !csb0_reg && web0_reg && VERBOSE )
$display($time," Reading %m addr0=%b dout0=%b",addr0_reg,mem[addr0_reg]);
if ( !csb0_reg && !web0_reg )
if ( !csb0_reg && !web0_reg && VERBOSE )
$display($time," Writing %m addr0=%b din0=%b",addr0_reg,din0_reg);
end

View File

@ -13,7 +13,6 @@ import sys, os
sys.path.append(os.getenv("OPENRAM_HOME"))
import globals
from subunit import ProtocolTestCase, TestProtocolClient
from subunit.test_results import AutoTimingTestResultDecorator
from testtools import ConcurrentTestSuite
(OPTS, args) = globals.parse_args()
@ -71,7 +70,7 @@ def fork_tests(num_threads):
stream = os.fdopen(c2pwrite, 'wb', 0)
os.close(c2pread)
sys.stdin.close()
test_suite_result = AutoTimingTestResultDecorator(TestProtocolClient(stream))
test_suite_result = TestProtocolClient(stream)
test_suite.run(test_suite_result)
except EBADF:
try:

View File

@ -12,11 +12,36 @@ from globals import OPTS
import debug
import pdb
import traceback
import time
class openram_test(unittest.TestCase):
""" Base unit test that we have some shared classes in. """
def setUp(self):
self.start_time = time.time()
def tearDown(self):
duration = time.time() - self.start_time
print('%s: %.3fs' % (self.id(), duration))
def fail(self, msg):
import inspect
s = inspect.stack()
base_filename = os.path.splitext(os.path.basename(s[2].filename))[0]
try:
OPENRAM_HOME = os.path.abspath(os.environ.get("OPENRAM_HOME"))
except:
debug.error("$OPENRAM_HOME is not properly defined.", 1)
import shutil
zip_file = "{0}/../{1}_{2}".format(OPENRAM_HOME, base_filename, os.getpid())
debug.info(0, "Archiving failed temp files {0} to {1}".format(OPTS.openram_temp, zip_file))
shutil.make_archive(zip_file, 'zip', OPTS.openram_temp)
super().fail(msg)
def local_drc_check(self, w):
self.reset()
@ -28,10 +53,9 @@ class openram_test(unittest.TestCase):
result=verify.run_drc(w.name, tempgds, None)
if result != 0:
self.fail("DRC failed: {}".format(w.name))
if not OPTS.keep_temp:
elif not OPTS.keep_temp:
self.cleanup()
def local_check(self, a, final_verification=False):
self.reset()
@ -39,7 +63,7 @@ class openram_test(unittest.TestCase):
tempspice = "{}.sp".format(a.name)
tempgds = "{}.gds".format(a.name)
a.lvs_write("{0}{1}".format(OPTS.openram_temp, tempspice))
a.sp_write("{0}{1}".format(OPTS.openram_temp, tempspice), lvs=True)
# cannot write gds in netlist_only mode
if not OPTS.netlist_only:
a.gds_write("{0}{1}".format(OPTS.openram_temp, tempgds))
@ -74,10 +98,10 @@ class openram_test(unittest.TestCase):
# shutil.make_archive(zip_file, 'zip', OPTS.openram_temp)
self.fail("LVS mismatch: {}".format(a.name))
if lvs_result == 0 and drc_result == 0 and not OPTS.keep_temp:
self.cleanup()
# For debug...
# import pdb; pdb.set_trace()
if not OPTS.keep_temp:
self.cleanup()
def run_pex(self, a, output=None):
tempspice = "{}.sp".format(a.name)
@ -104,6 +128,7 @@ class openram_test(unittest.TestCase):
def cleanup(self):
""" Reset the duplicate checker and cleanup files. """
files = glob.glob(OPTS.openram_temp + '*')
for f in files:
# Only remove the files

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

View File

@ -0,0 +1,236 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="605.40302"
height="165.26472"
viewBox="0 0 605.40301 165.26473"
id="svg2"
version="1.1"
inkscape:version="0.92.5 (2060ec1f9f, 2020-04-08)"
sodipodi:docname="OpenRAM_logo_yellow_transparent.svg"
inkscape:export-filename="/home/mrg/openram/images/OpenRAM_logo_yellow_transparent.png"
inkscape:export-xdpi="150"
inkscape:export-ydpi="150">
<sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="2634"
inkscape:window-height="1333"
id="namedview3551"
showgrid="false"
inkscape:zoom="2.8592481"
inkscape:cx="232.75305"
inkscape:cy="82.632362"
inkscape:window-x="208"
inkscape:window-y="96"
inkscape:window-maximized="0"
inkscape:current-layer="svg2"
fit-margin-top="0"
fit-margin-left="0"
fit-margin-right="0"
fit-margin-bottom="0" />
<defs
id="defs4" />
<metadata
id="metadata7">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<text
id="text3336"
y="113.18625"
x="173.17645"
style="font-style:normal;font-weight:normal;font-size:22.5px;line-height:125%;font-family:Sans;letter-spacing:0px;word-spacing:0px;fill:#003c6c;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
xml:space="preserve"><tspan
id="tspan4140"
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:80px;font-family:Futura;-inkscape-font-specification:'Futura Bold';fill:#003c6c;fill-opacity:1"
y="113.18625"
x="173.17645">OpenRAM</tspan></text>
<rect
y="39.336884"
x="36.581963"
height="87.463478"
width="89.973282"
id="rect4144"
style="fill:#003c6c;fill-opacity:1;stroke:#003c6c;stroke-width:8.3992691;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
<path
id="path8080"
d="m 53.960768,13.421563 v 21.96078"
style="fill:none;fill-rule:evenodd;stroke:#003c6c;stroke-width:3.69754982;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
inkscape:connector-curvature="0" />
<path
style="fill:none;fill-rule:evenodd;stroke:#003c6c;stroke-width:3.69754982;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 81.568617,13.421563 v 21.96078"
id="path8112"
inkscape:connector-curvature="0" />
<path
id="path8118"
d="m 109.17646,13.421563 v 21.96078"
style="fill:none;fill-rule:evenodd;stroke:#003c6c;stroke-width:3.69754982;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
inkscape:connector-curvature="0" />
<path
style="fill:none;fill-rule:evenodd;stroke:#003c6c;stroke-width:3.69754982;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="M 53.960768,151.84317 V 129.88239"
id="path8137"
inkscape:connector-curvature="0" />
<path
id="path8143"
d="M 81.568617,151.84317 V 129.88239"
style="fill:none;fill-rule:evenodd;stroke:#003c6c;stroke-width:3.69754982;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
inkscape:connector-curvature="0" />
<path
style="fill:none;fill-rule:evenodd;stroke:#003c6c;stroke-width:3.69754982;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="M 109.17646,151.84317 V 129.88239"
id="path8149"
inkscape:connector-curvature="0" />
<path
style="fill:none;fill-rule:evenodd;stroke:#003c6c;stroke-width:3.69754982;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="M 151.21568,56.715693 H 129.2549"
id="path8157"
inkscape:connector-curvature="0" />
<path
id="path8163"
d="M 151.21568,84.323543 H 129.2549"
style="fill:none;fill-rule:evenodd;stroke:#003c6c;stroke-width:3.69754982;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
inkscape:connector-curvature="0" />
<path
style="fill:none;fill-rule:evenodd;stroke:#003c6c;stroke-width:3.69754982;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="M 151.21568,111.93138 H 129.2549"
id="path8169"
inkscape:connector-curvature="0" />
<path
id="path8177"
d="m 13.421548,56.715693 h 21.96078"
style="fill:none;fill-rule:evenodd;stroke:#003c6c;stroke-width:3.69754982;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
inkscape:connector-curvature="0" />
<path
style="fill:none;fill-rule:evenodd;stroke:#003c6c;stroke-width:3.69754982;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 13.421548,84.323543 h 21.96078"
id="path8183"
inkscape:connector-curvature="0" />
<path
id="path8189"
d="m 13.421548,111.93138 h 21.96078"
style="fill:none;fill-rule:evenodd;stroke:#003c6c;stroke-width:3.69754982;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
inkscape:connector-curvature="0" />
<ellipse
ry="5.9607844"
rx="5.647059"
cy="7.4607844"
cx="53.96077"
id="path8078"
style="fill:#003c6c;fill-opacity:1;stroke:#003c6c;stroke-width:3;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
<ellipse
style="fill:#003c6c;fill-opacity:1;stroke:#003c6c;stroke-width:3;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="ellipse8110"
cx="81.568619"
cy="7.4607844"
rx="5.647059"
ry="5.9607844" />
<ellipse
ry="5.9607844"
rx="5.647059"
cy="7.4607844"
cx="109.17645"
id="ellipse8116"
style="fill:#003c6c;fill-opacity:1;stroke:#003c6c;stroke-width:3;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
<ellipse
transform="scale(1,-1)"
style="fill:#003c6c;fill-opacity:1;stroke:#003c6c;stroke-width:3;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="ellipse8135"
cx="53.96077"
cy="-157.80394"
rx="5.647059"
ry="5.9607844" />
<ellipse
transform="scale(1,-1)"
ry="5.9607844"
rx="5.647059"
cy="-157.80394"
cx="81.568619"
id="ellipse8141"
style="fill:#003c6c;fill-opacity:1;stroke:#003c6c;stroke-width:3;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
<ellipse
transform="scale(1,-1)"
style="fill:#003c6c;fill-opacity:1;stroke:#003c6c;stroke-width:3;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="ellipse8147"
cx="109.17645"
cy="-157.80394"
rx="5.647059"
ry="5.9607844" />
<ellipse
transform="rotate(90)"
style="fill:#003c6c;fill-opacity:1;stroke:#003c6c;stroke-width:3;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="ellipse8155"
cx="56.715668"
cy="-157.17644"
rx="5.647059"
ry="5.9607844" />
<ellipse
transform="rotate(90)"
ry="5.9607844"
rx="5.647059"
cy="-157.17644"
cx="84.323517"
id="ellipse8161"
style="fill:#003c6c;fill-opacity:1;stroke:#003c6c;stroke-width:3;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
<ellipse
transform="rotate(90)"
style="fill:#003c6c;fill-opacity:1;stroke:#003c6c;stroke-width:3;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="ellipse8167"
cx="111.93137"
cy="-157.17644"
rx="5.647059"
ry="5.9607844" />
<ellipse
transform="matrix(0,1,1,0,0,0)"
ry="5.9607844"
rx="5.647059"
cy="7.4607844"
cx="56.715668"
id="ellipse8175"
style="fill:#003c6c;fill-opacity:1;stroke:#003c6c;stroke-width:3;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
<ellipse
transform="matrix(0,1,1,0,0,0)"
style="fill:#003c6c;fill-opacity:1;stroke:#003c6c;stroke-width:3;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="ellipse8181"
cx="84.323517"
cy="7.4607844"
rx="5.647059"
ry="5.9607844" />
<ellipse
transform="matrix(0,1,1,0,0,0)"
ry="5.9607844"
rx="5.647059"
cy="7.4607844"
cx="111.93137"
id="ellipse8187"
style="fill:#003c6c;fill-opacity:1;stroke:#003c6c;stroke-width:3;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
<rect
style="fill:#fdc700;fill-opacity:1;stroke:none;stroke-width:4.87900019;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="rect3553"
width="88.83606"
height="86.559578"
x="37.165073"
y="39.245544"
ry="4.3552427" />
</svg>

After

Width:  |  Height:  |  Size: 9.7 KiB