mirror of https://github.com/VLSIDA/OpenRAM.git
Merged with dev, fixed import conflict in lib
This commit is contained in:
commit
5dad0f2c0e
|
|
@ -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/
|
||||
|
||||
|
|
|
|||
|
|
@ -10,3 +10,4 @@
|
|||
**/model_data
|
||||
outputs
|
||||
technology/freepdk45/ncsu_basekit
|
||||
.idea
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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]
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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]
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -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."""
|
||||
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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])
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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())
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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()]
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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())
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
Binary file not shown.
|
|
@ -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
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue