Merged with dev, fixed import conflict in lib

This commit is contained in:
Hunter Nichols 2021-04-18 23:59:35 -07:00
commit 5dad0f2c0e
73 changed files with 953 additions and 511 deletions

View File

@ -4,52 +4,54 @@ jobs:
scn4me_subm:
runs-on: self-hosted
steps:
- name: Check out repository
- name: Checkout code
uses: actions/checkout@v1
- 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_TMP="`pwd`/scn4me_subm"
python3-coverage run -p $OPENRAM_HOME/tests/regress.py -j 48 -t scn4m_subm
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 24 -t scn4m_subm
- name: Archive
if: ${{ failure() }}
uses: actions/upload-artifact@v2
with:
name: scn4me_subm Archives
path: $OPENRAM_TMP/
path: ${{ github.workspace }}/*.zip
freepdk45:
runs-on: self-hosted
steps:
- name: Check out repository
- name: Checkout code
uses: actions/checkout@v1
- 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_TMP="`pwd`/freepdk45"
python3-coverage run -p $OPENRAM_HOME/tests/regress.py -j 48 -t freepdk45
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 24 -t freepdk45
- name: Archive
if: ${{ failure() }}
uses: actions/upload-artifact@v2
with:
name: FreePDK45 Archives
path: $OPENRAM_TMP/
coverage:
if: ${{ always() }}
needs: [scn4me_subm, freepdk45]
runs-on: self-hosted
steps:
- name: Coverage stats
run: |
python3-coverage combine
python3-coverage report
python3-coverage html -d coverage_html
- name: Archive coverage
uses: actions/upload-artifact@v2
with:
name: code-coverage-report
path: coverage_html/
path: ${{ github.workspace }}/*.zip
# coverage_stats:
# if: ${{ always() }}
# needs: [scn4me_subm, freepdk45]
# runs-on: self-hosted
# steps:
# - name: Coverage stats
# run: |
# python3-coverage combine
# python3-coverage report
# python3-coverage html -d ${{ github.workspace }}/coverage_html
# - name: Archive coverage
# uses: actions/upload-artifact@v2
# with:
# name: code-coverage-report
# path: ${{ github.workspace }}/coverage_html/

1
.gitignore vendored
View File

@ -10,3 +10,4 @@
**/model_data
outputs
technology/freepdk45/ncsu_basekit
.idea

View File

@ -145,7 +145,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

@ -359,7 +359,9 @@ class instance(geometry):
for offset in range(len(normalized_br_offsets)):
for port in range(len(br_names)):
cell_br_meta.append([br_names[offset], row, col, port])
if normalized_storage_nets == []:
debug.error("normalized storage nets should not be empty! Check if the GDS labels Q and Q_bar are correctly set on M1 of the cell",1)
Q_x = normalized_storage_nets[0][0]
Q_y = normalized_storage_nets[0][1]

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):
"""

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,9 @@
#
import debug
from tech import layer_names
import os
import shutil
from globals import OPTS
class lef:
@ -23,9 +26,53 @@ class lef:
# Round to ensure float values are divisible by 0.0025 (the manufacturing grid)
self.round_grid = 4
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))
# 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."""
debug.info(3, "Writing to {0}".format(lef_name))
""" Write the entire lef of the object to the file. """
if OPTS.drc_exe and OPTS.drc_exe[0] == "magic":
self.magic_lef_write(lef_name)
return
debug.info(3, "Writing detailed LEF to {0}".format(lef_name))
self.indent = "" # To maintain the indent level easily

View File

@ -369,11 +369,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 +397,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(),
@ -396,9 +408,14 @@ class pin_layout:
# Add the text in the middle of the pin.
# This fixes some pin label offsetting when GDS gets
# imported into Magic.
try:
zoom = GDS["zoom"]
except KeyError:
zoom = None
newLayout.addText(text=self.name,
layerNumber=layer_num,
purposeNumber=label_purpose,
magnification=zoom,
offsetInMicrons=self.center())
def compute_overlap(self, other):

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

@ -5,6 +5,7 @@
# (acting for and on behalf of Oklahoma State University)
# All rights reserved.
#
import os
import re
import debug
from globals import OPTS
@ -20,6 +21,8 @@ def parse_spice_list(filename, key):
if OPTS.spice_name == "xa" :
# customsim has a different output file name
full_filename="{0}xa.meas".format(OPTS.openram_temp)
elif OPTS.spice_name == "spectre":
full_filename = os.path.join(OPTS.openram_temp, "delay_stim.measure")
else:
# ngspice/hspice using a .lis file
full_filename="{0}{1}.lis".format(OPTS.openram_temp, filename)
@ -28,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

@ -170,10 +170,10 @@ class delay(simulation):
meas.targ_name_no_port))
self.dout_volt_meas[-1].meta_str = meas.meta_str
if not OPTS.use_pex:
self.sen_meas = delay_measure("delay_sen", self.clk_frmt, self.sen_name + "{}", "FALL", "RISE", measure_scale=1e9)
else:
if OPTS.use_pex and OPTS.pex_exe[0] != 'calibre':
self.sen_meas = delay_measure("delay_sen", self.clk_frmt, self.sen_name, "FALL", "RISE", measure_scale=1e9)
else:
self.sen_meas = delay_measure("delay_sen", self.clk_frmt, self.sen_name + "{}", "FALL", "RISE", measure_scale=1e9)
self.sen_meas.meta_str = sram_op.READ_ZERO
self.sen_meas.meta_add_delay = True
@ -220,13 +220,13 @@ class delay(simulation):
storage_names = cell_inst.mod.get_storage_net_names()
debug.check(len(storage_names) == 2, ("Only inverting/non-inverting storage nodes"
"supported for characterization. Storage nets={}").format(storage_names))
if not OPTS.use_pex:
q_name = cell_name + '.' + str(storage_names[0])
qbar_name = cell_name + '.' + str(storage_names[1])
else:
if OPTS.use_pex and OPTS.pex_exe[0] != "calibre":
bank_num = self.sram.get_bank_num(self.sram.name, bit_row, bit_col)
q_name = "bitcell_Q_b{0}_r{1}_c{2}".format(bank_num, bit_row, bit_col)
qbar_name = "bitcell_Q_bar_b{0}_r{1}_c{2}".format(bank_num, bit_row, bit_col)
else:
q_name = cell_name + '.' + str(storage_names[0])
qbar_name = cell_name + '.' + str(storage_names[1])
# Bit measures, measurements times to be defined later. The measurement names must be unique
# but they is enforced externally. {} added to names to differentiate between ports allow the
@ -387,6 +387,9 @@ class delay(simulation):
self.delay_stim_sp = "delay_stim.sp"
temp_stim = "{0}/{1}".format(OPTS.openram_temp, self.delay_stim_sp)
self.sf = open(temp_stim, "w")
if OPTS.spice_name == "spectre":
self.sf.write("simulator lang=spice\n")
self.sf.write("* Delay stimulus for period of {0}n load={1}fF slew={2}ns\n\n".format(self.period,
self.load,
self.slew))
@ -415,7 +418,9 @@ class delay(simulation):
t_rise=self.slew,
t_fall=self.slew)
# self.load_all_measure_nets()
self.write_delay_measures()
# self.write_simulation_saves()
# run until the end of the cycle time
self.stim.write_control(self.cycle_times[-1] + self.period)
@ -593,6 +598,69 @@ class delay(simulation):
self.sf.write("* Write ports {}\n".format(write_port))
self.write_delay_measures_write_port(write_port)
def load_pex_net(self, net: str):
from subprocess import check_output, CalledProcessError
prefix = (self.sram_instance_name + ".").lower()
if not net.lower().startswith(prefix) or not OPTS.use_pex or not OPTS.calibre_pex:
return net
original_net = net
net = net[len(prefix):]
net = net.replace(".", "_").replace("[", "\[").replace("]", "\]")
for pattern in ["\sN_{}_[MXmx]\S+_[gsd]".format(net), net]:
try:
match = check_output(["grep", "-m1", "-o", "-iE", pattern, self.sp_file])
return prefix + match.decode().strip()
except CalledProcessError:
pass
return original_net
def load_all_measure_nets(self):
measurement_nets = set()
for port, meas in zip(self.targ_read_ports * len(self.read_meas_lists) +
self.targ_write_ports * len(self.write_meas_lists),
self.read_meas_lists + self.write_meas_lists):
for measurement in meas:
visited = getattr(measurement, 'pex_visited', False)
for prop in ["trig_name_no_port", "targ_name_no_port"]:
if hasattr(measurement, prop):
net = getattr(measurement, prop).format(port)
if not visited:
net = self.load_pex_net(net)
setattr(measurement, prop, net)
measurement_nets.add(net)
measurement.pex_visited = True
self.measurement_nets = measurement_nets
return measurement_nets
def write_simulation_saves(self):
for net in self.measurement_nets:
self.sf.write(".plot V({0}) \n".format(net))
probe_nets = set()
sram_name = self.sram_instance_name
col = self.bitline_column
row = self.wordline_row
for port in set(self.targ_read_ports + self.targ_write_ports):
probe_nets.add("WEB{}".format(port))
probe_nets.add("{}.w_en{}".format(self.sram_instance_name, port))
probe_nets.add("{0}.Xbank0.Xport_data{1}.Xwrite_driver_array{1}.Xwrite_driver{2}.en_bar".format(
self.sram_instance_name, port, self.bitline_column))
probe_nets.add("{}.Xbank0.br_{}_{}".format(self.sram_instance_name, port,
self.bitline_column))
if not OPTS.use_pex:
continue
probe_nets.add(
"{0}.vdd_Xbank0_Xbitcell_array_xbitcell_array_xbit_r{1}_c{2}".format(sram_name, row, col - 1))
probe_nets.add(
"{0}.p_en_bar{1}_Xbank0_Xport_data{1}_Xprecharge_array{1}_Xpre_column_{2}".format(sram_name, port, col))
probe_nets.add(
"{0}.vdd_Xbank0_Xport_data{1}_Xprecharge_array{1}_xpre_column_{2}".format(sram_name, port, col))
probe_nets.add("{0}.vdd_Xbank0_Xport_data{1}_Xwrite_driver_array{1}_xwrite_driver{2}".format(sram_name,
port, col))
probe_nets.update(self.measurement_nets)
for net in probe_nets:
debug.info(2, "Probe: {}".format(net))
self.sf.write(".plot V({}) \n".format(self.load_pex_net(net)))
def write_power_measures(self):
"""
Write the measure statements to quantify the leakage power only.
@ -1032,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

@ -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

@ -8,7 +8,6 @@
import os,sys,re
import time
import debug
import math
import datetime
from .setup_hold import *
from .delay import *
@ -17,6 +16,7 @@ import tech
import numpy as np
from globals import OPTS
class lib:
""" lib file generation."""
@ -261,10 +261,9 @@ class lib:
self.lib.write(" slew_lower_threshold_pct_rise : 10.0 ;\n")
self.lib.write(" slew_upper_threshold_pct_rise : 90.0 ;\n\n")
self.lib.write(" nom_voltage : {};\n".format(tech.spice["nom_supply_voltage"]))
self.lib.write(" nom_temperature : {};\n".format(tech.spice["nom_temperature"]))
self.lib.write(" nom_process : {};\n".format(1.0))
self.lib.write(" nom_voltage : {};\n".format(self.voltage))
self.lib.write(" nom_temperature : {};\n".format(self.temperature))
self.lib.write(" nom_process : 1.0;\n")
self.lib.write(" default_cell_leakage_power : 0.0 ;\n")
self.lib.write(" default_leakage_power_density : 0.0 ;\n")
self.lib.write(" default_input_pin_cap : 1.0 ;\n")
@ -275,7 +274,7 @@ class lib:
self.lib.write(" default_max_fanout : 4.0 ;\n")
self.lib.write(" default_connection_class : universal ;\n\n")
self.lib.write(" voltage_map ( VDD, {} );\n".format(tech.spice["nom_supply_voltage"]))
self.lib.write(" voltage_map ( VDD, {} );\n".format(self.voltage))
self.lib.write(" voltage_map ( GND, 0 );\n\n")
def create_list(self,values):
@ -627,7 +626,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.load_slews)
@ -867,4 +865,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
@ -467,7 +470,7 @@ class simulation():
"""
port = self.read_ports[0]
if not OPTS.use_pex:
if not OPTS.use_pex or (OPTS.use_pex and OPTS.pex_exe[0] == "calibre"):
self.graph.get_all_paths('{}{}'.format("clk", port),
'{}{}_{}'.format(self.dout_name, port, self.probe_data))
@ -523,7 +526,7 @@ class simulation():
debug.check(len(sa_mods) == 1, "Only expected one type of Sense Amp. Cannot perform s_en checks.")
enable_name = sa_mods[0].get_enable_name()
sen_name = self.get_alias_in_path(paths, enable_name, sa_mods[0])
if OPTS.use_pex:
if OPTS.use_pex and OPTS.pex_exe[0] != "calibre":
sen_name = sen_name.split('.')[-1]
return sen_name
@ -581,7 +584,7 @@ class simulation():
exclude_set = self.get_bl_name_search_exclusions()
for int_net in [cell_bl, cell_br]:
bl_names.append(self.get_alias_in_path(paths, int_net, cell_mod, exclude_set))
if OPTS.use_pex:
if OPTS.use_pex and OPTS.pex_exe[0] != "calibre":
for i in range(len(bl_names)):
bl_names[i] = bl_names[i].split('.')[-1]
return bl_names[0], bl_names[1]

View File

@ -52,7 +52,7 @@ class stimuli():
def inst_model(self, pins, model_name):
""" Function to instantiate a generic model with a set of pins """
if OPTS.use_pex:
if OPTS.use_pex and OPTS.pex_exe[0] != "calibre":
self.inst_pex_model(pins, model_name)
else:
self.sf.write("X{0} ".format(model_name))
@ -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):
@ -246,28 +238,51 @@ class stimuli():
reltol = 0.001 # 0.1%
timestep = 10 # ps, was 5ps but ngspice was complaining the timestep was too small in certain tests.
# UIC is needed for ngspice to converge
self.sf.write(".TRAN {0}p {1}n UIC\n".format(timestep, end_time))
self.sf.write(".TEMP {}\n".format(self.temperature))
if OPTS.spice_name == "ngspice":
# UIC is needed for ngspice to converge
self.sf.write(".TRAN {0}p {1}n UIC\n".format(timestep, end_time))
# ngspice sometimes has convergence problems if not using gear method
# 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:
nestlvl = 1
spectre_save = "selected"
else:
nestlvl = 10
spectre_save = "lvlpub"
self.sf.write('saveOptions options save={} nestlvl={} pwr=total \n'.format(
spectre_save, nestlvl))
self.sf.write("simulatorOptions options reltol=1e-3 vabstol=1e-6 iabstol=1e-12 temp={0} try_fast_op=no "
"rforce=10m maxnotes=10 maxwarns=10 "
" preservenode=all topcheck=fixall "
"digits=5 cols=80 dc_pivot_check=yes pivrel=1e-3 "
" \n".format(self.temperature))
self.sf.write('tran tran step={} stop={}n ic=node write=spectre.dc errpreset=moderate '
' annotate=status maxiters=5 \n'.format("5p", end_time))
self.sf.write("simulator lang=spice\n")
else:
self.sf.write(".TRAN {0}p {1}n UIC\n".format(timestep, end_time))
self.sf.write(".OPTIONS POST=1 RUNLVL={0} PROBE\n".format(runlvl))
if OPTS.spice_name == "hspice": # for cadence plots
self.sf.write(".OPTIONS PSF=1 \n")
self.sf.write(".OPTIONS HIER_DELIM=1 \n")
# create plots for all signals
self.sf.write("* probe is used for hspice/xa, while plot is used in ngspice\n")
if OPTS.verbose_level>0:
if OPTS.spice_name in ["hspice", "xa"]:
self.sf.write(".probe V(*)\n")
if not OPTS.use_pex: # Don't save all for extracted simulations
self.sf.write("* probe is used for hspice/xa, while plot is used in ngspice\n")
if OPTS.verbose_level>0:
if OPTS.spice_name in ["hspice", "xa"]:
self.sf.write(".probe V(*)\n")
else:
self.sf.write(".plot V(*)\n")
else:
self.sf.write(".plot V(*)\n")
else:
self.sf.write("*.probe V(*)\n")
self.sf.write("*.plot V(*)\n")
self.sf.write("*.probe V(*)\n")
self.sf.write("*.plot V(*)\n")
# end the stimulus file
self.sf.write(".end\n\n")
@ -287,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"
@ -314,6 +332,16 @@ class stimuli():
OPTS.openram_temp,
OPTS.num_sim_threads)
valid_retcode=0
elif OPTS.spice_name == "spectre":
if OPTS.use_pex:
extra_options = " +dcopt +postlayout "
else:
extra_options = ""
cmd = ("{0} -64 {1} -format psfbin -raw {2} {3} -maxwarnstolog 1000 "
" +mt={4} -maxnotestolog 1000 "
.format(OPTS.spice_exe, temp_stim, OPTS.openram_temp, extra_options,
OPTS.num_sim_threads))
valid_retcode = 0
elif OPTS.spice_name == "hspice":
# TODO: Should make multithreading parameter a configuration option
cmd = "{0} -mt {1} -i {2} -o {3}timing".format(OPTS.spice_exe,
@ -327,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.9"
VERSION = "1.1.14"
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
@ -526,13 +526,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?
@ -688,6 +691,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
@ -703,6 +708,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 """
@ -1051,21 +1058,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

@ -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,15 @@ 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")

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

@ -239,7 +239,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

@ -275,7 +275,7 @@ class port_data(design.design):
self.br_names = self.bitcell.get_all_br_names()
self.wl_names = self.bitcell.get_all_wl_names()
# used for bl/br names
self.precharge = factory.create(module_type="precharge",
self.precharge = factory.create(module_type=OPTS.precharge,
bitcell_bl=self.bl_names[0],
bitcell_br=self.br_names[0])

View File

@ -72,7 +72,7 @@ class precharge_array(design.design):
self.DRC_LVS()
def add_modules(self):
self.pc_cell = factory.create(module_type="precharge",
self.pc_cell = factory.create(module_type=OPTS.precharge,
size=self.size,
bitcell_bl=self.bitcell_bl,
bitcell_br=self.bitcell_br)

View File

@ -146,10 +146,10 @@ class sense_amp_array(design.design):
inst = self.local_insts[i]
for gnd_pin in inst.get_pins("gnd"):
self.copy_power_pin(gnd_pin, directions=("V", "V"))
self.copy_power_pin(gnd_pin)
for vdd_pin in inst.get_pins("vdd"):
self.copy_power_pin(vdd_pin, directions=("V", "V"))
self.copy_power_pin(vdd_pin)
bl_pin = inst.get_pin(inst.mod.get_bl_names())
br_pin = inst.get_pin(inst.mod.get_br_names())

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
@ -173,6 +173,7 @@ class options(optparse.Values):
nand2_dec = "pnand2"
nand3_dec = "pnand3"
nand4_dec = "pnand4" # Not available right now
precharge = "precharge"
precharge_array = "precharge_array"
ptx = "ptx"
replica_bitline = "replica_bitline"

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

@ -7,6 +7,8 @@
#
from enum import Enum
from vector3d import vector3d
import debug
class direction(Enum):
NORTH = 1
@ -20,31 +22,30 @@ class direction(Enum):
SOUTHEAST = 9
SOUTHWEST = 10
def get_offset(direct):
"""
Returns the vector offset for a given direction.
"""
if direct==direction.NORTH:
offset = vector3d(0,1,0)
offset = vector3d(0, 1, 0)
elif direct==direction.SOUTH:
offset = vector3d(0,-1,0)
offset = vector3d(0, -1 ,0)
elif direct==direction.EAST:
offset = vector3d(1,0,0)
offset = vector3d(1, 0, 0)
elif direct==direction.WEST:
offset = vector3d(-1,0,0)
offset = vector3d(-1, 0, 0)
elif direct==direction.UP:
offset = vector3d(0,0,1)
offset = vector3d(0, 0, 1)
elif direct==direction.DOWN:
offset = vector3d(0,0,-1)
offset = vector3d(0, 0, -1)
elif direct==direction.NORTHEAST:
offset = vector3d(1,1,0)
offset = vector3d(1, 1, 0)
elif direct==direction.NORTHWEST:
offset = vector3d(-1,1,0)
offset = vector3d(-1, 1, 0)
elif direct==direction.SOUTHEAST:
offset = vector3d(1,-1,0)
offset = vector3d(1, -1, 0)
elif direct==direction.SOUTHWEST:
offset = vector3d(-1,-1,0)
offset = vector3d(-1, -1, 0)
else:
debug.error("Invalid direction {}".format(direct))
@ -67,8 +68,8 @@ class direction(Enum):
return [direction.get_offset(d) for d in direction.all_directions()]
def all_neighbors(cell):
return [cell+x for x in direction.all_offsets()]
return [cell + x for x in direction.all_offsets()]
def cardinal_neighbors(cell):
return [cell+x for x in direction.cardinal_offsets()]
return [cell + x for x in direction.cardinal_offsets()]

View File

@ -34,7 +34,6 @@ class pin_group:
# Remove any redundant pins (i.e. contained in other pins)
self.remove_redundant_pins()
self.router = router
# These are the corresponding pin grids for each pin group.
self.grids = set()
@ -101,13 +100,11 @@ class pin_group:
if local_debug:
debug.info(0, "INITIAL: {}".format(pin_list))
new_pin_list = pin_list.copy()
remove_indices = set()
add_indices = set(range(len(pin_list)))
# This is n^2, but the number is small
for index1, pin1 in enumerate(pin_list):
# If we remove this pin, it can't contain other pins
if index1 in remove_indices:
if index1 not in add_indices:
continue
for index2, pin2 in enumerate(pin_list):
@ -117,17 +114,15 @@ class pin_group:
if index1 == index2:
continue
# If we already removed it, can't remove it again...
if index2 in remove_indices:
if index2 not in add_indices:
continue
if pin1.contains(pin2):
if local_debug:
debug.info(0, "{0} contains {1}".format(pin1, pin2))
remove_indices.add(index2)
add_indices.remove(index2)
# Remove them in decreasing order to not invalidate the indices
for i in sorted(remove_indices, reverse=True):
del new_pin_list[i]
new_pin_list = [pin_list[x] for x in add_indices]
if local_debug:
debug.info(0, "FINAL : {}".format(new_pin_list))
@ -153,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)
@ -423,13 +419,15 @@ class pin_group:
# We may have started with an empty set
debug.check(len(self.grids) > 0, "Cannot seed an grid empty set.")
common_blockages = self.router.get_blocked_grids() & self.grids
# Start with the ll and make the widest row
row = [ll]
# Move in dir1 while we can
while True:
next_cell = row[-1] + offset1
# Can't move if not in the pin shape
if next_cell in self.grids and next_cell not in self.router.get_blocked_grids():
if next_cell in self.grids and next_cell not in common_blockages:
row.append(next_cell)
else:
break
@ -438,7 +436,7 @@ class pin_group:
next_row = [x + offset2 for x in row]
for cell in next_row:
# Can't move if any cell is not in the pin shape
if cell not in self.grids or cell in self.router.get_blocked_grids():
if cell not in self.grids or cell in common_blockages:
break
else:
row = next_row
@ -619,6 +617,11 @@ class pin_group:
# Set of track adjacent to or paritally overlap a pin (not full DRC connection)
partial_set = set()
# for pin in self.pins:
# lx = pin.lx()
# ly = pin.by()
# if lx > 87.9 and lx < 87.99 and ly > 18.56 and ly < 18.6:
# breakpoint()
for pin in self.pins:
debug.info(4, " Converting {0}".format(pin))
# Determine which tracks the pin overlaps
@ -632,7 +635,8 @@ class pin_group:
blockage_in_tracks = self.router.convert_blockage(pin)
# Must include the pins here too because these are computed in a different
# way than blockages.
self.blockages.update(sufficient | insufficient | blockage_in_tracks)
blockages = sufficient | insufficient | blockage_in_tracks
self.blockages.update(blockages)
# If we have a blockage, we must remove the grids
# Remember, this excludes the pin blockages already

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):
@ -504,14 +507,21 @@ class router(router_tech):
ll = vector(boundary[0], boundary[1])
ur = vector(boundary[2], boundary[3])
rect = [ll, ur]
new_pin = pin_layout("blockage{}".format(len(self.blockages)),
rect,
lpp)
new_shape = pin_layout("blockage{}".format(len(self.blockages)),
rect,
lpp)
# If there is a rectangle that is the same in the pins,
# it isn't a blockage!
if new_pin not in self.all_pins:
self.blockages.append(new_pin)
if new_shape not in self.all_pins and not self.pin_contains(new_shape):
self.blockages.append(new_shape)
def pin_contains(self, shape):
for pin in self.all_pins:
if pin.contains(shape):
return True
return False
def convert_point_to_units(self, p):
"""
Convert a path set of tracks to center line path.
@ -1048,6 +1058,7 @@ class router(router_tech):
# Double check source and taget are not same node, if so, we are done!
for k, v in self.rg.map.items():
if v.source and v.target:
self.paths.append([k])
return True
# returns the path in tracks
@ -1204,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

@ -37,12 +37,12 @@ class supply_tree_router(router):
"""
Route the two nets in a single layer)
"""
debug.info(1,"Running supply router on {0} and {1}...".format(vdd_name, gnd_name))
debug.info(1, "Running supply router on {0} and {1}...".format(vdd_name, gnd_name))
self.vdd_name = vdd_name
self.gnd_name = gnd_name
# Clear the pins if we have previously routed
if (hasattr(self,'rg')):
if (hasattr(self, 'rg')):
self.clear_pins()
else:
# Creat a routing grid over the entire area
@ -53,14 +53,14 @@ class supply_tree_router(router):
# Get the pin shapes
start_time = datetime.now()
self.find_pins_and_blockages([self.vdd_name, self.gnd_name])
print_time("Finding pins and blockages",datetime.now(), start_time, 3)
print_time("Finding pins and blockages", datetime.now(), start_time, 3)
# Route the supply pins to the supply rails
# Route vdd first since we want it to be shorter
start_time = datetime.now()
self.route_pins(vdd_name)
self.route_pins(gnd_name)
print_time("Maze routing supplies",datetime.now(), start_time, 3)
print_time("Maze routing supplies", datetime.now(), start_time, 3)
# self.write_debug_gds("final_tree_router.gds",False)
@ -79,11 +79,11 @@ 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")
debug.info(2, "Creating adjacency matrix")
pin_size = len(self.pin_groups[pin_name])
adj_matrix = [[0] * pin_size for i in range(pin_size)]
@ -95,7 +95,7 @@ class supply_tree_router(router):
adj_matrix[index1][index2] = dist
# Find MST
debug.info(2,"Finding MinimumSpanning Tree")
debug.info(2, "Finding MinimumSpanning Tree")
X = csr_matrix(adj_matrix)
Tcsr = minimum_spanning_tree(X)
mst = Tcsr.toarray().astype(int)
@ -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))
@ -144,6 +146,7 @@ class supply_tree_router(router):
self.add_pin_component_source(pin_name, src_idx)
# Marks all pin components except index as target
# which unmarks it as a blockage too
self.add_pin_component_target(pin_name, dest_idx)
# Actually run the A* router

View File

@ -51,11 +51,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)
@ -117,7 +114,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

@ -325,13 +325,13 @@ class sram_1bank(sram_base):
# they might create some blockages
self.add_layout_pins()
# Route the supplies first since the MST is not blockage aware
# and signals can route to anywhere on sides (it is flexible)
self.route_supplies()
# Route the pins to the perimeter
if OPTS.perimeter_pins:
self.route_escape_pins()
# Route the supplies first since the MST is not blockage aware
# and signals can route to anywhere on sides (it is flexible)
self.route_supplies()
def route_dffs(self, add_routes=True):
@ -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,12 +196,13 @@ 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]
self.height = highest_coord[1]
if OPTS.use_pex:
if OPTS.use_pex and OPTS.pex_exe[0] != "calibre":
debug.info(2, "adding global pex labels")
self.add_global_pex_labels()
self.add_boundary(ll=vector(0, 0),
ur=vector(self.width, self.height))
@ -247,21 +248,27 @@ 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()
pin_width = pin.rx() - lowest_coord.x
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())
def route_escape_pins(self):
@ -305,7 +312,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):
@ -674,7 +683,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
@ -687,6 +696,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
@ -695,13 +706,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

@ -46,7 +46,17 @@ class openram_back_end_test(openram_test):
if OPTS.spice_name:
options += " -s {}".format(OPTS.spice_name)
exe_name = "{0}{1}/openram.py ".format(OPTS.coverage_exe, OPENRAM_HOME)
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")
exe_name = "{0}/openram.py ".format(OPENRAM_HOME)
else:
exe_name = "{0}{1}/openram.py ".format(OPTS.coverage_exe, OPENRAM_HOME)
config_name = "{0}/tests/configs/config_back_end.py".format(OPENRAM_HOME)
cmd = "{0} -o {1} -p {2} {3} {4} 2>&1 > {5}/output.log".format(exe_name,
out_file,

View File

@ -46,7 +46,17 @@ class openram_front_end_test(openram_test):
if OPTS.spice_name:
options += " -s {}".format(OPTS.spice_name)
exe_name = "{0}{1}/openram.py ".format(OPTS.coverage_exe, OPENRAM_HOME)
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")
exe_name = "{0}/openram.py ".format(OPENRAM_HOME)
else:
exe_name = "{0}{1}/openram.py ".format(OPTS.coverage_exe, OPENRAM_HOME)
config_name = "{0}/tests/configs/config_front_end.py".format(OPENRAM_HOME)
cmd = "{0} -n -o {1} -p {2} {3} {4} 2>&1 > {5}/output.log".format(exe_name,
out_file,

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

View File

@ -21,6 +21,7 @@ import os
import shutil
import re
import debug
import utils
from globals import OPTS
from run_script import run_script
@ -141,7 +142,7 @@ def write_pex_script(cell_name, extract, output, final_verification=False, outpu
if not output_path:
output_path = OPTS.openram_temp
if output == None:
if not output:
output = cell_name + ".pex.sp"
# check if lvs report has been done
@ -166,6 +167,17 @@ def write_pex_script(cell_name, extract, output, final_verification=False, outpu
'pexPexReportFile': cell_name + ".pex.report",
'pexMaskDBFile': cell_name + ".maskdb",
'cmnFDIDEFLayoutPath': cell_name + ".def",
'cmnRunMT': "1",
'cmnNumTurbo': "16",
'pexPowerNames': "vdd",
'pexGroundNames': "gnd",
'pexPexGroundName': "1",
'pexPexGroundNameValue': "gnd",
'pexPexSeparator': "1",
'pexPexSeparatorValue': "_",
'pexPexNetlistNameSource': 'SOURCENAMES',
'pexSVRFCmds': '{SOURCE CASE YES} {LAYOUT CASE YES}',
'pexIncludeCmdsType': 'SVRF',
}
# write the runset file
@ -174,12 +186,73 @@ def write_pex_script(cell_name, extract, output, final_verification=False, outpu
f.write("*{0}: {1}\n".format(k, pex_runset[k]))
f.close()
# write the rules file
f = open(output_path + "pex_rules", "w")
f.write('// Rules file, created by OpenRAM, (c) Bob Vanhoof\n')
f.write('\n')
f.write('LAYOUT PATH "' + output_path + cell_name + '.gds"\n')
f.write('LAYOUT PRIMARY ' + cell_name + '\n')
f.write('LAYOUT SYSTEM GDSII\n')
f.write('\n')
f.write('SOURCE PATH "' + output_path + cell_name + '.sp"\n')
f.write('SOURCE PRIMARY ' + cell_name +'\n')
f.write('SOURCE SYSTEM SPICE\n')
f.write('SOURCE CASE YES\n')
f.write('\n')
f.write('MASK SVDB DIRECTORY "svdb" QUERY XRC\n')
f.write('\n')
f.write('LVS REPORT "' + output_path + cell_name + '.pex.report"\n')
f.write('LVS REPORT OPTION NONE\n')
f.write('LVS FILTER UNUSED OPTION NONE SOURCE\n')
f.write('LVS FILTER UNUSED OPTION NONE LAYOUT\n')
f.write('LVS POWER NAME vdd\n')
f.write('LVS GROUND NAME gnd\n')
f.write('LVS RECOGNIZE GATES ALL\n')
f.write('LVS CELL SUPPLY YES\n')
f.write('LVS PUSH DEVICES SEPARATE PROPERTIES YES\n')
f.write('\n')
f.write('PEX NETLIST "' + output + '" HSPICE 1 SOURCENAMES GROUND gnd\n')
f.write('PEX REDUCE ANALOG NO\n')
f.write('PEX NETLIST UPPERCASE KEYWORDS NO\n')
f.write('PEX NETLIST VIRTUAL CONNECT YES\n')
f.write('PEX NETLIST NOXREF NET NAMES YES\n')
f.write('PEX NETLIST MUTUAL RESISTANCE YES\n')
f.write('PEX NETLIST EXPORT PORTS YES\n')
f.write('PEX PROBE FILE "probe_file"\n')
f.write('\n')
f.write('VIRTUAL CONNECT COLON NO\n')
f.write('VIRTUAL CONNECT REPORT NO\n')
f.write('VIRTUAL CONNECT NAME vdd gnd\n')
f.write('\n')
f.write('DRC ICSTATION YES\n')
f.write('\n')
f.write('INCLUDE "'+ pex_rules +'"\n')
f.close()
# write probe file
# TODO: get from cell name
f = open(output_path + "probe_file", "w")
f.write('CELL cell_1rw\n')
f.write(' Q 0.100 0.510 11\n')
f.write(' Q_bar 0.520 0.510 11\n')
f.close()
# Create an auxiliary script to run calibre with the runset
run_file = output_path + "run_pex.sh"
f = open(run_file, "w")
f.write("#!/bin/sh\n")
cmd = "{0} -gui -pex pex_runset -batch".format(OPTS.pex_exe[1])
cmd = "{0} -lvs -hier -genhcells -spice svdb/{1}.sp -turbo -hyper cmp {2}".format(OPTS.pex_exe[1],
cell_name,
'pex_rules')
f.write(cmd)
f.write("\n")
cmd = "sed '/dummy/d' svdb/{0}.hcells | sed '/replica_column/d' | sed '/replica_cell/d' > hcell_file".format(cell_name)
f.write(cmd)
f.write("\n")
cmd = "{0} -xrc -pdb -turbo -xcell hcell_file -full -rc {1}".format(OPTS.pex_exe[1], 'pex_rules')
f.write(cmd)
f.write("\n")
cmd = "{0} -xrc -fmt -full {1}".format(OPTS.pex_exe[1], 'pex_rules')
f.write(cmd)
f.write("\n")
f.close()

View File

@ -11,5 +11,5 @@ MM5 Q Q_bar vdd vdd PMOS_VTG W=90n L=50n
* Access transistors
MM3 bl_noconn wl Q gnd NMOS_VTG W=135.00n L=50n
MM2 br_noconn wl Q_bar gnd NMOS_VTG W=135.00n L=50n
.ENDS cell_1rw
.ENDS dummy_cell_1rw