Merged with dev, fixed import conflict in lib

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

View File

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

1
.gitignore vendored
View File

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

View File

@ -145,7 +145,7 @@ The default for openram.py is specified in the configuration file.
# Porting to a New Technology # 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 setup script for each technology you want to use
+ a technology directory for each technology with the base cells + a technology directory for each technology with the base cells

View File

@ -360,6 +360,8 @@ class instance(geometry):
for port in range(len(br_names)): for port in range(len(br_names)):
cell_br_meta.append([br_names[offset], row, col, port]) 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_x = normalized_storage_nets[0][0]
Q_y = normalized_storage_nets[0][1] Q_y = normalized_storage_nets[0][1]

View File

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

View File

@ -15,6 +15,7 @@ from tech import layer_indices
from tech import layer_stacks from tech import layer_stacks
from tech import preferred_directions from tech import preferred_directions
import os import os
import sys
from globals import OPTS from globals import OPTS
from vector import vector from vector import vector
from pin_layout import pin_layout from pin_layout import pin_layout
@ -111,52 +112,48 @@ class layout():
Finds the lowest set of 2d cartesian coordinates within Finds the lowest set of 2d cartesian coordinates within
this layout this layout
""" """
lowestx = lowesty = sys.maxsize
if len(self.objs) > 0: if len(self.objs) > 0:
lowestx1 = min(obj.lx() for obj in self.objs if obj.name != "label") lowestx = min(min(obj.lx() for obj in self.objs if obj.name != "label"), lowestx)
lowesty1 = min(obj.by() for obj in self.objs if obj.name != "label") lowesty = min(min(obj.by() for obj in self.objs if obj.name != "label"), lowesty)
else:
lowestx1 = lowesty1 = None
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
if lowestx1 == None and lowestx2 == None: if len(self.insts) > 0:
return None lowestx = min(min(inst.lx() for inst in self.insts), lowestx)
elif lowestx1 == None: lowesty = min(min(inst.by() for inst in self.insts), lowesty)
return vector(lowestx2, lowesty2)
elif lowestx2 == None: if len(self.pin_map) > 0:
return vector(lowestx1, lowesty1) for pin_set in self.pin_map.values():
else: if len(pin_set) == 0:
return vector(min(lowestx1, lowestx2), min(lowesty1, lowesty2)) 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): def find_highest_coords(self):
""" """
Finds the highest set of 2d cartesian coordinates within Finds the highest set of 2d cartesian coordinates within
this layout this layout
""" """
if len(self.objs) > 0: highestx = highesty = -sys.maxsize - 1
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
if highestx1 == None and highestx2 == None: if len(self.objs) > 0:
return None highestx = max(max(obj.rx() for obj in self.objs if obj.name != "label"), highestx)
elif highestx1 == None: highesty = max(max(obj.uy() for obj in self.objs if obj.name != "label"), highesty)
return vector(highestx2, highesty2)
elif highestx2 == None: if len(self.insts) > 0:
return vector(highestx1, highesty1) highestx = max(max(inst.rx() for inst in self.insts), highestx)
else: highesty = max(max(inst.uy() for inst in self.insts), highesty)
return vector(max(highestx1, highestx2),
max(highesty1, highesty2)) 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): def find_highest_layer_coords(self, layer):
""" """

View File

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

View File

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

View File

@ -369,11 +369,23 @@ class pin_layout:
debug.info(4, "writing pin (" + str(self.layer) + "):" debug.info(4, "writing pin (" + str(self.layer) + "):"
+ str(self.width()) + "x" + str(self.width()) + "x"
+ str(self.height()) + " @ " + str(self.ll())) + 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: 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: except ImportError:
pin_purpose = purpose pass
try: try:
from tech import label_purpose from tech import label_purpose
except ImportError: except ImportError:
@ -385,9 +397,9 @@ class pin_layout:
width=self.width(), width=self.width(),
height=self.height(), height=self.height(),
center=False) center=False)
# Draw a second pin shape too # Draw a second pin shape too if it is different
if pin_purpose != purpose: if not self.same_lpp((pin_layer_num, pin_purpose), (layer_num, purpose)):
newLayout.addBox(layerNumber=layer_num, newLayout.addBox(layerNumber=pin_layer_num,
purposeNumber=pin_purpose, purposeNumber=pin_purpose,
offsetInMicrons=self.ll(), offsetInMicrons=self.ll(),
width=self.width(), width=self.width(),
@ -396,9 +408,14 @@ class pin_layout:
# Add the text in the middle of the pin. # Add the text in the middle of the pin.
# This fixes some pin label offsetting when GDS gets # This fixes some pin label offsetting when GDS gets
# imported into Magic. # imported into Magic.
try:
zoom = GDS["zoom"]
except KeyError:
zoom = None
newLayout.addText(text=self.name, newLayout.addText(text=self.name,
layerNumber=layer_num, layerNumber=layer_num,
purposeNumber=label_purpose, purposeNumber=label_purpose,
magnification=zoom,
offsetInMicrons=self.center()) offsetInMicrons=self.center())
def compute_overlap(self, other): def compute_overlap(self, other):

View File

@ -61,6 +61,8 @@ class verilog:
self.vf.write(" parameter RAM_DEPTH = 1 << ADDR_WIDTH;\n") self.vf.write(" parameter RAM_DEPTH = 1 << ADDR_WIDTH;\n")
self.vf.write(" // FIXME: This delay is arbitrary.\n") self.vf.write(" // FIXME: This delay is arbitrary.\n")
self.vf.write(" parameter DELAY = 3 ;\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") self.vf.write("\n")
for port in self.all_ports: for port in self.all_ports:
@ -128,21 +130,21 @@ class verilog:
if port in self.write_ports: if port in self.write_ports:
self.vf.write(" din{0}_reg = din{0};\n".format(port)) self.vf.write(" din{0}_reg = din{0};\n".format(port))
if port in self.read_ports: 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: 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)) 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: 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)) 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: 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: 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)) 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: else:
self.vf.write(" $display($time,\" Writing %m addr{0}=%b din{0}=%b\",addr{0}_reg,din{0}_reg);\n".format(port)) 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: 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: 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)) 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: else:

View File

@ -5,6 +5,7 @@
# (acting for and on behalf of Oklahoma State University) # (acting for and on behalf of Oklahoma State University)
# All rights reserved. # All rights reserved.
# #
import os
import re import re
import debug import debug
from globals import OPTS from globals import OPTS
@ -20,6 +21,8 @@ def parse_spice_list(filename, key):
if OPTS.spice_name == "xa" : if OPTS.spice_name == "xa" :
# customsim has a different output file name # customsim has a different output file name
full_filename="{0}xa.meas".format(OPTS.openram_temp) 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: else:
# ngspice/hspice using a .lis file # ngspice/hspice using a .lis file
full_filename="{0}{1}.lis".format(OPTS.openram_temp, filename) 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") f = open(full_filename, "r")
except IOError: except IOError:
debug.error("Unable to open spice output file: {0}".format(full_filename),1) debug.error("Unable to open spice output file: {0}".format(full_filename),1)
debug.archive()
contents = f.read() contents = f.read()
f.close() f.close()
# val = re.search(r"{0}\s*=\s*(-?\d+.?\d*\S*)\s+.*".format(key), contents) # val = re.search(r"{0}\s*=\s*(-?\d+.?\d*\S*)\s+.*".format(key), contents)

View File

@ -170,10 +170,10 @@ class delay(simulation):
meas.targ_name_no_port)) meas.targ_name_no_port))
self.dout_volt_meas[-1].meta_str = meas.meta_str self.dout_volt_meas[-1].meta_str = meas.meta_str
if not OPTS.use_pex: 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 = 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_str = sram_op.READ_ZERO
self.sen_meas.meta_add_delay = True self.sen_meas.meta_add_delay = True
@ -220,13 +220,13 @@ class delay(simulation):
storage_names = cell_inst.mod.get_storage_net_names() storage_names = cell_inst.mod.get_storage_net_names()
debug.check(len(storage_names) == 2, ("Only inverting/non-inverting storage nodes" debug.check(len(storage_names) == 2, ("Only inverting/non-inverting storage nodes"
"supported for characterization. Storage nets={}").format(storage_names)) "supported for characterization. Storage nets={}").format(storage_names))
if not OPTS.use_pex: if OPTS.use_pex and OPTS.pex_exe[0] != "calibre":
q_name = cell_name + '.' + str(storage_names[0])
qbar_name = cell_name + '.' + str(storage_names[1])
else:
bank_num = self.sram.get_bank_num(self.sram.name, bit_row, bit_col) 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) 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) 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 # 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 # 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" self.delay_stim_sp = "delay_stim.sp"
temp_stim = "{0}/{1}".format(OPTS.openram_temp, self.delay_stim_sp) temp_stim = "{0}/{1}".format(OPTS.openram_temp, self.delay_stim_sp)
self.sf = open(temp_stim, "w") 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.sf.write("* Delay stimulus for period of {0}n load={1}fF slew={2}ns\n\n".format(self.period,
self.load, self.load,
self.slew)) self.slew))
@ -415,7 +418,9 @@ class delay(simulation):
t_rise=self.slew, t_rise=self.slew,
t_fall=self.slew) t_fall=self.slew)
# self.load_all_measure_nets()
self.write_delay_measures() self.write_delay_measures()
# self.write_simulation_saves()
# run until the end of the cycle time # run until the end of the cycle time
self.stim.write_control(self.cycle_times[-1] + self.period) 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.sf.write("* Write ports {}\n".format(write_port))
self.write_delay_measures_write_port(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): def write_power_measures(self):
""" """
Write the measure statements to quantify the leakage power only. 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 # Set up to trim the netlist here if that is enabled
if OPTS.trim_netlist: if OPTS.trim_netlist:
self.trim_sp_file = "{}reduced.sp".format(OPTS.openram_temp) self.trim_sp_file = "{}trimmed.sp".format(OPTS.openram_temp)
self.trimsp=trim_spice(self.sp_file, self.trim_sp_file) self.sram.sp_write(self.trim_sp_file, lvs=False, trim=True)
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)
else: else:
# The non-reduced netlist file when it is disabled # The non-reduced netlist file when it is disabled
self.trim_sp_file = "{}sram.sp".format(OPTS.openram_temp) self.trim_sp_file = "{}sram.sp".format(OPTS.openram_temp)

View File

@ -9,6 +9,7 @@ import collections
import debug import debug
import random import random
import math import math
from numpy import binary_repr
from .stimuli import * from .stimuli import *
from .charutils import * from .charutils import *
from globals import OPTS from globals import OPTS
@ -21,13 +22,17 @@ class functional(simulation):
for successful SRAM operation. 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) super().__init__(sram, spfile, corner)
# Seed the characterizer with a constant seed for unit tests # Seed the characterizer with a constant seed for unit tests
if OPTS.is_unit_test: if OPTS.is_unit_test:
random.seed(12345) 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: if not corner:
corner = (OPTS.process_corners[0], OPTS.supply_voltages[0], OPTS.temperatures[0]) 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: if not self.num_spare_cols:
self.num_spare_cols = 0 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.probe_address, self.probe_data = '0' * self.addr_size, 0
self.set_corner(corner) self.set_corner(corner)
self.set_spice_constants() self.set_spice_constants()
@ -66,6 +87,7 @@ class functional(simulation):
self.num_cycles = cycles self.num_cycles = cycles
# This is to have ordered keys for random selection # This is to have ordered keys for random selection
self.stored_words = collections.OrderedDict() self.stored_words = collections.OrderedDict()
self.stored_spares = collections.OrderedDict()
self.read_check = [] self.read_check = []
self.read_results = [] self.read_results = []
@ -121,10 +143,12 @@ class functional(simulation):
# 1. Write all the write ports first to seed a bunch of locations. # 1. Write all the write ports first to seed a bunch of locations.
for port in self.write_ports: for port in self.write_ports:
addr = self.gen_addr() addr = self.gen_addr()
word = self.gen_data() (word, spare) = self.gen_data()
comment = self.gen_cycle_comment("write", word, addr, "1" * self.num_wmasks, port, self.t_current) combined_word = "{}+{}".format(word, spare)
self.add_write_one_port(comment, addr, word, "1" * self.num_wmasks, port) 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_words[addr] = word
self.stored_spares[addr[:self.addr_spare_index]] = spare
# All other read-only ports are noops. # All other read-only ports are noops.
for port in self.read_ports: for port in self.read_ports:
@ -142,7 +166,9 @@ class functional(simulation):
if port in self.write_ports: if port in self.write_ports:
self.add_noop_one_port(port) self.add_noop_one_port(port)
else: 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_one_port(comment, addr, port)
self.add_read_check(word, port) self.add_read_check(word, port)
self.cycle_times.append(self.t_current) self.cycle_times.append(self.t_current)
@ -170,27 +196,33 @@ class functional(simulation):
if addr in w_addrs: if addr in w_addrs:
self.add_noop_one_port(port) self.add_noop_one_port(port)
else: else:
word = self.gen_data() (word, spare) = self.gen_data()
comment = self.gen_cycle_comment("write", word, addr, "1" * self.num_wmasks, port, self.t_current) combined_word = "{}+{}".format(word, spare)
self.add_write_one_port(comment, addr, word, "1" * self.num_wmasks, port) 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_words[addr] = word
self.stored_spares[addr[:self.addr_spare_index]] = spare
w_addrs.append(addr) w_addrs.append(addr)
elif op == "partial_write": elif op == "partial_write":
# write only to a word that's been written to # 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 # two ports cannot write to the same address
if addr in w_addrs: if addr in w_addrs:
self.add_noop_one_port(port) self.add_noop_one_port(port)
else: else:
word = self.gen_data() (word, spare) = self.gen_data()
wmask = self.gen_wmask() wmask = self.gen_wmask()
new_word = self.gen_masked_data(old_word, word, 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) combined_word = "{}+{}".format(word, spare)
self.add_write_one_port(comment, addr, word, wmask, port) 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_words[addr] = new_word
self.stored_spares[addr[:self.addr_spare_index]] = spare
w_addrs.append(addr) w_addrs.append(addr)
else: else:
(addr, word) = random.choice(list(self.stored_words.items())) (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 # 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 # 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 # 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: if addr in w_addrs:
self.add_noop_one_port(port) self.add_noop_one_port(port)
else: 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_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.cycle_times.append(self.t_current)
self.t_current += self.period self.t_current += self.period
@ -227,18 +259,18 @@ class functional(simulation):
def add_read_check(self, word, port): def add_read_check(self, word, port):
""" Add to the check array to ensure a read works. """ """ Add to the check array to ensure a read works. """
try: try:
self.check self.check_count
except: except:
self.check = 0 self.check_count = 0
self.read_check.append([word, "{0}{1}".format(self.dout_name, port), self.t_current + self.period, self.check]) self.read_check.append([word, "{0}{1}".format(self.dout_name, port), self.t_current + self.period, self.check_count])
self.check += 1 self.check_count += 1
def read_stim_results(self): def read_stim_results(self):
# Extract dout values from spice timing.lis # 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 = "" sp_read_value = ""
for bit in range(self.word_size + self.num_spare_cols): 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: try:
value = float(value) value = float(value)
if value > self.v_high: if value > self.v_high:
@ -260,13 +292,13 @@ class functional(simulation):
return (0, error) 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") return (1, "SUCCESS")
def check_stim_results(self): def check_stim_results(self):
for i in range(len(self.read_check)): for i in range(len(self.read_check)):
if self.read_check[i][0] != self.read_results[i][0]: 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], error = str.format(self.read_results[i][1],
self.read_results[i][0], self.read_results[i][0],
self.read_check[i][0], self.read_check[i][0],
@ -300,22 +332,22 @@ class functional(simulation):
def gen_data(self): def gen_data(self):
""" Generates a random word to write. """ """ Generates a random word to write. """
if not self.num_spare_cols: random_value = random.randint(0, self.max_data)
random_value = random.randint(0, (2 ** self.word_size) - 1) 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: else:
random_value1 = random.randint(0, (2 ** self.word_size) - 1) spare_bits = ""
random_value2 = random.randint(0, (2 ** self.num_spare_cols) - 1) return data_bits, spare_bits
random_value = random_value1 + random_value2
data_bits = self.convert_to_bin(random_value, False)
return data_bits
def gen_addr(self): def gen_addr(self):
""" Generates a random address value to write to. """ """ Generates a random address value to write to. """
if self.num_spare_rows==0: if self.valid_addresses:
random_value = random.randint(0, (2 ** self.addr_size) - 1) random_value = random.sample(self.valid_addresses, 1)[0]
else: else:
random_value = random.randint(0, ((2 ** (self.addr_size - 1) - 1)) + (self.num_spare_rows * self.words_per_row)) random_value = random.randint(0, self.max_address)
addr_bits = self.convert_to_bin(random_value, True) addr_bits = binary_repr(random_value, self.addr_size)
return addr_bits return addr_bits
def get_data(self): def get_data(self):
@ -323,20 +355,8 @@ class functional(simulation):
# Used for write masks since they should be writing to previously written addresses # Used for write masks since they should be writing to previously written addresses
addr = random.choice(list(self.stored_words.keys())) addr = random.choice(list(self.stored_words.keys()))
word = self.stored_words[addr] word = self.stored_words[addr]
return (addr, word) spare = self.stored_spares[addr[:self.addr_spare_index]]
return (addr, word, spare)
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
def write_functional_stimulus(self): def write_functional_stimulus(self):
""" Writes SPICE stimulus. """ """ Writes SPICE stimulus. """
@ -432,12 +452,21 @@ class functional(simulation):
# Generate dout value measurements # Generate dout value measurements
self.sf.write("\n * Generation of dout measurements\n") self.sf.write("\n * Generation of dout measurements\n")
for (word, dout_port, eo_period, check) in self.read_check: 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 t_final = eo_period + 0.01 * self.period
for bit in range(self.word_size + self.num_spare_cols): num_bits = self.word_size + self.num_spare_cols
self.stim.gen_meas_value(meas_name="V{0}_{1}ck{2}".format(dout_port, bit, check), for bit in range(num_bits):
dout="{0}_{1}".format(dout_port, bit), measure_name = "V{0}_{1}ck{2}".format(dout_port, bit, check)
t_intital=t_intital, 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) t_final=t_final)
self.stim.write_control(self.cycle_times[-1] + self.period) self.stim.write_control(self.cycle_times[-1] + self.period)

View File

@ -8,7 +8,6 @@
import os,sys,re import os,sys,re
import time import time
import debug import debug
import math
import datetime import datetime
from .setup_hold import * from .setup_hold import *
from .delay import * from .delay import *
@ -17,6 +16,7 @@ import tech
import numpy as np import numpy as np
from globals import OPTS from globals import OPTS
class lib: class lib:
""" lib file generation.""" """ lib file generation."""
@ -261,10 +261,9 @@ class lib:
self.lib.write(" slew_lower_threshold_pct_rise : 10.0 ;\n") 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(" 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_voltage : {};\n".format(self.voltage))
self.lib.write(" nom_temperature : {};\n".format(tech.spice["nom_temperature"])) self.lib.write(" nom_temperature : {};\n".format(self.temperature))
self.lib.write(" nom_process : {};\n".format(1.0)) self.lib.write(" nom_process : 1.0;\n")
self.lib.write(" default_cell_leakage_power : 0.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_leakage_power_density : 0.0 ;\n")
self.lib.write(" default_input_pin_cap : 1.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_max_fanout : 4.0 ;\n")
self.lib.write(" default_connection_class : universal ;\n\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") self.lib.write(" voltage_map ( GND, 0 );\n\n")
def create_list(self,values): def create_list(self,values):
@ -627,7 +626,6 @@ class lib:
from .elmore import elmore as model from .elmore import elmore as model
else: else:
debug.error("{} model not recognized. See options.py for available models.".format(OPTS.model_name)) 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) m = model(self.sram, self.sp_file, self.corner)
char_results = m.get_lib_values(self.load_slews) char_results = m.get_lib_values(self.load_slews)

View File

@ -82,7 +82,13 @@ class setup_hold():
""" """
self.sf.write("\n* Generation of the data and clk signals\n") 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": if mode=="HOLD":
init_value = incorrect_value init_value = incorrect_value
start_value = correct_value start_value = correct_value

View File

@ -27,7 +27,10 @@ class simulation():
self.num_spare_cols = 0 self.num_spare_cols = 0
else: else:
self.num_spare_cols = self.sram.num_spare_cols 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.all_ports = self.sram.all_ports
self.readwrite_ports = self.sram.readwrite_ports self.readwrite_ports = self.sram.readwrite_ports
@ -467,7 +470,7 @@ class simulation():
""" """
port = self.read_ports[0] 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), self.graph.get_all_paths('{}{}'.format("clk", port),
'{}{}_{}'.format(self.dout_name, port, self.probe_data)) '{}{}_{}'.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.") 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() enable_name = sa_mods[0].get_enable_name()
sen_name = self.get_alias_in_path(paths, enable_name, sa_mods[0]) 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] sen_name = sen_name.split('.')[-1]
return sen_name return sen_name
@ -581,7 +584,7 @@ class simulation():
exclude_set = self.get_bl_name_search_exclusions() exclude_set = self.get_bl_name_search_exclusions()
for int_net in [cell_bl, cell_br]: for int_net in [cell_bl, cell_br]:
bl_names.append(self.get_alias_in_path(paths, int_net, cell_mod, exclude_set)) 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)): for i in range(len(bl_names)):
bl_names[i] = bl_names[i].split('.')[-1] bl_names[i] = bl_names[i].split('.')[-1]
return bl_names[0], bl_names[1] return bl_names[0], bl_names[1]

View File

@ -52,7 +52,7 @@ class stimuli():
def inst_model(self, pins, model_name): def inst_model(self, pins, model_name):
""" Function to instantiate a generic model with a set of pins """ """ 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) self.inst_pex_model(pins, model_name)
else: else:
self.sf.write("X{0} ".format(model_name)) self.sf.write("X{0} ".format(model_name))
@ -170,21 +170,13 @@ class stimuli():
""" Generates a constant signal with reference voltage and the voltage value """ """ 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)) self.sf.write("V{0} {0} 0 DC {1}\n".format(sig_name, v_val))
def get_inverse_voltage(self, value): def get_voltage(self, value):
if value > 0.5 * self.voltage: if value == "0" or value == 0:
return 0 return 0
elif value <= 0.5 * self.voltage: elif value == "1" or value == 1:
return self.voltage return self.voltage
else: 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 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))
def gen_meas_delay(self, meas_name, trig_name, targ_name, trig_val, targ_val, trig_dir, targ_dir, trig_td, targ_td): 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 """ """ Creates the .meas statement for the measurement of delay """
@ -228,8 +220,8 @@ class stimuli():
t_initial, t_initial,
t_final)) t_final))
def gen_meas_value(self, 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_intital, 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) self.sf.write(measure_string)
def write_control(self, end_time, runlvl=4): def write_control(self, end_time, runlvl=4):
@ -246,28 +238,51 @@ class stimuli():
reltol = 0.001 # 0.1% reltol = 0.001 # 0.1%
timestep = 10 # ps, was 5ps but ngspice was complaining the timestep was too small in certain tests. 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)) self.sf.write(".TEMP {}\n".format(self.temperature))
if OPTS.spice_name == "ngspice": 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 # ngspice sometimes has convergence problems if not using gear method
# which is more accurate, but slower than the default trapezoid 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 # Do not remove this or it may not converge due to some "pa_00" nodes
# unless you figure out what these are. # 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: 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)) 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 # create plots for all signals
self.sf.write("* probe is used for hspice/xa, while plot is used in ngspice\n") if not OPTS.use_pex: # Don't save all for extracted simulations
if OPTS.verbose_level>0: self.sf.write("* probe is used for hspice/xa, while plot is used in ngspice\n")
if OPTS.spice_name in ["hspice", "xa"]: if OPTS.verbose_level>0:
self.sf.write(".probe V(*)\n") if OPTS.spice_name in ["hspice", "xa"]:
self.sf.write(".probe V(*)\n")
else:
self.sf.write(".plot V(*)\n")
else: else:
self.sf.write(".plot V(*)\n") self.sf.write("*.probe V(*)\n")
else: self.sf.write("*.plot V(*)\n")
self.sf.write("*.probe V(*)\n")
self.sf.write("*.plot V(*)\n")
# end the stimulus file # end the stimulus file
self.sf.write(".end\n\n") self.sf.write(".end\n\n")
@ -287,6 +302,9 @@ class stimuli():
for item in list(includes): for item in list(includes):
self.sf.write(".include \"{0}\"\n".format(item)) self.sf.write(".include \"{0}\"\n".format(item))
def add_comment(self, msg):
self.sf.write(msg + "\n")
def write_supply(self): def write_supply(self):
""" Writes supply voltage statements """ """ Writes supply voltage statements """
gnd_node_name = "0" gnd_node_name = "0"
@ -314,6 +332,16 @@ class stimuli():
OPTS.openram_temp, OPTS.openram_temp,
OPTS.num_sim_threads) OPTS.num_sim_threads)
valid_retcode=0 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": elif OPTS.spice_name == "hspice":
# TODO: Should make multithreading parameter a configuration option # TODO: Should make multithreading parameter a configuration option
cmd = "{0} -mt {1} -i {2} -o {3}timing".format(OPTS.spice_exe, cmd = "{0} -mt {1} -i {2} -o {3}timing".format(OPTS.spice_exe,
@ -327,6 +355,8 @@ class stimuli():
# -r {2}timing.raw # -r {2}timing.raw
ng_cfg = open("{}.spiceinit".format(OPTS.openram_temp), "w") 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 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() ng_cfg.close()
cmd = "{0} -b -o {2}timing.lis {1}".format(OPTS.spice_exe, cmd = "{0} -b -o {2}timing.lis {1}".format(OPTS.spice_exe,

View File

@ -9,6 +9,7 @@ import debug
from math import log,ceil from math import log,ceil
import re import re
class trim_spice(): class trim_spice():
""" """
A utility to trim redundant parts of an SRAM spice netlist. 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)): for i in range(len(self.spice)):
self.spice[i] = self.spice[i].rstrip(" \n") self.spice[i] = self.spice[i].rstrip(" \n")
self.sp_buffer = self.spice self.sp_buffer = self.spice
def set_configuration(self, banks, rows, columns, word_size): def set_configuration(self, banks, rows, columns, word_size):
@ -47,20 +47,22 @@ class trim_spice():
self.bank_addr_size = self.col_addr_size + self.row_addr_size 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)) self.addr_size = self.bank_addr_size + int(log(self.num_banks, 2))
def trim(self, address, data_bit): 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 # Always start fresh if we do multiple reductions
self.sp_buffer = self.spice self.sp_buffer = self.spice
# Split up the address and convert to an int # Split up the address and convert to an int
wl_address = int(address[self.col_addr_size:],2) wl_address = int(address[self.col_addr_size:], 2)
if self.col_addr_size>0: if self.col_addr_size > 0:
col_address = int(address[0:self.col_addr_size],2) col_address = int(address[0:self.col_addr_size], 2)
else: else:
col_address = 0 col_address = 0
# 1. Keep cells in the bitcell array based on WL and BL # 1. Keep cells in the bitcell array based on WL and BL
wl_name = "wl_{}".format(wl_address) wl_name = "wl_{}".format(wl_address)
bl_name = "bl_{}".format(int(self.words_per_row*data_bit + col_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, "* It should NOT be used for LVS!!")
self.sp_buffer.insert(0, "* WARNING: This is a TRIMMED NETLIST.") self.sp_buffer.insert(0, "* WARNING: This is a TRIMMED NETLIST.")
wl_regex = r"wl\d*_{}".format(wl_address) wl_regex = r"wl\d*_{}".format(wl_address)
bl_regex = r"bl\d*_{}".format(int(self.words_per_row*data_bit + col_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]) self.remove_insts("bitcell_array",[wl_regex,bl_regex])
@ -91,11 +92,11 @@ class trim_spice():
#self.remove_insts("sense_amp_array",[bl_regex]) #self.remove_insts("sense_amp_array",[bl_regex])
# 3. Keep column muxes basd on BL # 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 # 4. Keep write driver based on DATA
data_regex = r"data_{}".format(data_bit) 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 # 5. Keep wordline driver based on WL
# Need to keep the gater too # Need to keep the gater too
@ -111,7 +112,6 @@ class trim_spice():
sp.write("\n".join(self.sp_buffer)) sp.write("\n".join(self.sp_buffer))
sp.close() sp.close()
def remove_insts(self, subckt_name, keep_inst_list): def remove_insts(self, subckt_name, keep_inst_list):
"""This will remove all of the instances in the list from the named """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 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.. net connection, the instance name, anything..
""" """
removed_insts = 0 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] compiled_patterns = [re.compile(pattern) for pattern in keep_inst_list]
start_name = ".SUBCKT {}".format(subckt_name) start_name = ".SUBCKT {}".format(subckt_name)

View File

@ -109,6 +109,19 @@ def info(lev, str):
frm[0].f_code.co_name, str)) 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(): def bp():
""" """
An empty function so you can set soft breakpoints in pdb. An empty function so you can set soft breakpoints in pdb.

View File

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

View File

@ -329,13 +329,13 @@ class bank(design.design):
self.input_control_signals = [] self.input_control_signals = []
port_num = 0 port_num = 0
for port in range(OPTS.num_rw_ports): 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 port_num += 1
for port in range(OPTS.num_w_ports): 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 port_num += 1
for port in range(OPTS.num_r_ports): 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 port_num += 1
# Number of control lines in the bus for each port # Number of control lines in the bus for each port
@ -526,13 +526,16 @@ class bank(design.design):
height=self.dff.height) height=self.dff.height)
elif self.col_addr_size == 2: elif self.col_addr_size == 2:
self.column_decoder = factory.create(module_type="hierarchical_predecode2x4", self.column_decoder = factory.create(module_type="hierarchical_predecode2x4",
column_decoder=True,
height=self.dff.height) height=self.dff.height)
elif self.col_addr_size == 3: elif self.col_addr_size == 3:
self.column_decoder = factory.create(module_type="hierarchical_predecode3x8", self.column_decoder = factory.create(module_type="hierarchical_predecode3x8",
column_decoder=True,
height=self.dff.height) height=self.dff.height)
elif self.col_addr_size == 4: elif self.col_addr_size == 4:
self.column_decoder = factory.create(module_type="hierarchical_predecode4x16", self.column_decoder = factory.create(module_type="hierarchical_predecode4x16",
column_decoder=True,
height=self.dff.height) height=self.dff.height)
else: else:
# No error checking before? # No error checking before?
@ -688,6 +691,8 @@ class bank(design.design):
make_pins=(self.num_banks==1), make_pins=(self.num_banks==1),
pitch=self.m3_pitch) pitch=self.m3_pitch)
self.copy_layout_pin(self.port_address_inst[0], "wl_en", self.prefix + "wl_en0")
# Port 1 # Port 1
if len(self.all_ports)==2: if len(self.all_ports)==2:
# The other control bus is routed up to two pitches above the bitcell array # 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), make_pins=(self.num_banks==1),
pitch=self.m3_pitch) 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): def route_port_data_to_bitcell_array(self, port):
""" Routing of BL and BR between port data and bitcell array """ """ Routing of BL and BR between port data and bitcell array """
@ -1051,21 +1058,6 @@ class bank(design.design):
to_layer="m2", to_layer="m2",
offset=control_pos) 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): def graph_exclude_precharge(self):
""" """

View File

@ -65,6 +65,10 @@ class bitcell_array(bitcell_base_array):
mod=self.cell) mod=self.cell)
self.connect_inst(self.get_bitcell_pins(row, col)) 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): def analytical_power(self, corner, load):
"""Power of Bitcell array and bitline in nW.""" """Power of Bitcell array and bitline in nW."""

View File

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

View File

@ -18,19 +18,17 @@ class hierarchical_predecode(design.design):
""" """
Pre 2x4 and 3x8 and TBD 4x16 decoder shared code. 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 self.number_of_inputs = input_number
b = factory.create(module_type=OPTS.bitcell) b = factory.create(module_type=OPTS.bitcell)
if not height: if not height:
self.cell_height = b.height self.cell_height = b.height
self.column_decoder = False
else: else:
self.cell_height = height 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 = column_decoder
self.column_decoder = (height != b.height)
self.number_of_outputs = int(math.pow(2, self.number_of_inputs)) self.number_of_outputs = int(math.pow(2, self.number_of_inputs))
super().__init__(name) super().__init__(name)
@ -87,8 +85,15 @@ class hierarchical_predecode(design.design):
self.bus_layer = layer_props.hierarchical_predecode.bus_layer self.bus_layer = layer_props.hierarchical_predecode.bus_layer
self.bus_directions = layer_props.hierarchical_predecode.bus_directions 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.input_layer = layer_props.hierarchical_predecode.input_layer
self.output_layer = layer_props.hierarchical_predecode.output_layer self.output_layer = layer_props.hierarchical_predecode.output_layer
self.output_layer_pitch = getattr(self, self.output_layer + "_pitch") self.output_layer_pitch = getattr(self, self.output_layer + "_pitch")

View File

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

View File

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

View File

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

View File

@ -239,7 +239,12 @@ class local_bitcell_array(bitcell_base_array.bitcell_base_array):
out_loc = out_pin.lc() out_loc = out_pin.lc()
mid_loc = vector(self.wl_insts[port].lx() - 1.5 * self.m3_pitch, out_loc.y) mid_loc = vector(self.wl_insts[port].lx() - 1.5 * self.m3_pitch, out_loc.y)
in_loc = in_pin.rc() 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): def get_main_array_top(self):
return self.bitcell_array_inst.by() + self.bitcell_array.get_main_array_top() return self.bitcell_array_inst.by() + self.bitcell_array.get_main_array_top()

View File

@ -275,7 +275,7 @@ class port_data(design.design):
self.br_names = self.bitcell.get_all_br_names() self.br_names = self.bitcell.get_all_br_names()
self.wl_names = self.bitcell.get_all_wl_names() self.wl_names = self.bitcell.get_all_wl_names()
# used for bl/br 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_bl=self.bl_names[0],
bitcell_br=self.br_names[0]) bitcell_br=self.br_names[0])

View File

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

View File

@ -146,10 +146,10 @@ class sense_amp_array(design.design):
inst = self.local_insts[i] inst = self.local_insts[i]
for gnd_pin in inst.get_pins("gnd"): 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"): 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()) bl_pin = inst.get_pin(inst.mod.get_bl_names())
br_pin = inst.get_pin(inst.mod.get_br_names()) br_pin = inst.get_pin(inst.mod.get_br_names())

View File

@ -104,7 +104,7 @@ class options(optparse.Values):
# This determines whether LVS and DRC is checked for every submodule. # This determines whether LVS and DRC is checked for every submodule.
inline_lvsdrc = False inline_lvsdrc = False
# Remove noncritical memory cells for characterization speed-up # Remove noncritical memory cells for characterization speed-up
trim_netlist = False trim_netlist = True
# Run with extracted parasitics # Run with extracted parasitics
use_pex = False use_pex = False
# Output config with all options # Output config with all options
@ -135,7 +135,7 @@ class options(optparse.Values):
magic_exe = None magic_exe = None
# Number of threads to use # Number of threads to use
num_threads = 2 num_threads = 1
# Number of threads to use in ngspice/hspice # Number of threads to use in ngspice/hspice
num_sim_threads = 2 num_sim_threads = 2
@ -173,6 +173,7 @@ class options(optparse.Values):
nand2_dec = "pnand2" nand2_dec = "pnand2"
nand3_dec = "pnand3" nand3_dec = "pnand3"
nand4_dec = "pnand4" # Not available right now nand4_dec = "pnand4" # Not available right now
precharge = "precharge"
precharge_array = "precharge_array" precharge_array = "precharge_array"
ptx = "ptx" ptx = "ptx"
replica_bitline = "replica_bitline" replica_bitline = "replica_bitline"

View File

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

View File

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

View File

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

View File

@ -28,7 +28,7 @@ class router(router_tech):
route on a given layer. This is limited to two layer routes. route on a given layer. This is limited to two layer routes.
It populates blockages on a grid class. It populates blockages on a grid class.
""" """
def __init__(self, layers, design, gds_filename=None, 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 This will instantiate a copy of the gds file or the module at (0,0) and
route on top of this. The blockages from the gds/module will be route on top of this. The blockages from the gds/module will be
@ -83,9 +83,11 @@ class router(router_tech):
# A list of path blockages (they might be expanded for wide metal DRC) # A list of path blockages (they might be expanded for wide metal DRC)
self.path_blockages = [] 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. Initialize the ll,ur values with the paramter or using the layout boundary.
""" """
@ -99,18 +101,19 @@ class router(router_tech):
else: else:
self.ll, self.ur = bbox 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 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): def get_bbox(self):
return self.bbox 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. 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) self.rg = router_type(self.ll, self.ur, self.track_width)
def clear_pins(self): def clear_pins(self):
@ -504,13 +507,20 @@ class router(router_tech):
ll = vector(boundary[0], boundary[1]) ll = vector(boundary[0], boundary[1])
ur = vector(boundary[2], boundary[3]) ur = vector(boundary[2], boundary[3])
rect = [ll, ur] rect = [ll, ur]
new_pin = pin_layout("blockage{}".format(len(self.blockages)), new_shape = pin_layout("blockage{}".format(len(self.blockages)),
rect, rect,
lpp) lpp)
# If there is a rectangle that is the same in the pins, # If there is a rectangle that is the same in the pins,
# it isn't a blockage! # it isn't a blockage!
if new_pin not in self.all_pins: if new_shape not in self.all_pins and not self.pin_contains(new_shape):
self.blockages.append(new_pin) 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): def convert_point_to_units(self, p):
""" """
@ -1048,6 +1058,7 @@ class router(router_tech):
# Double check source and taget are not same node, if so, we are done! # Double check source and taget are not same node, if so, we are done!
for k, v in self.rg.map.items(): for k, v in self.rg.map.items():
if v.source and v.target: if v.source and v.target:
self.paths.append([k])
return True return True
# returns the path in tracks # returns the path in tracks
@ -1204,8 +1215,9 @@ class router(router_tech):
return None return None
def get_pin(self, pin_name): def get_ll_pin(self, pin_name):
""" Return the lowest, leftest pin group """ """ Return the lowest, leftest pin group """
keep_pin = None keep_pin = None
for index,pg in enumerate(self.pin_groups[pin_name]): for index,pg in enumerate(self.pin_groups[pin_name]):
for pin in pg.enclosures: for pin in pg.enclosures:

View File

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

View File

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

View File

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

View File

@ -325,14 +325,14 @@ class sram_1bank(sram_base):
# they might create some blockages # they might create some blockages
self.add_layout_pins() 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 # Route the pins to the perimeter
if OPTS.perimeter_pins: if OPTS.perimeter_pins:
self.route_escape_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): def route_dffs(self, add_routes=True):
for port in self.all_ports: for port in self.all_ports:
@ -340,6 +340,15 @@ class sram_1bank(sram_base):
def route_dff(self, port, add_routes): 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 = [] route_map = []
# column mux dff is routed on it's own since it is to the far end # 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] bank_pins = [self.bank_inst.get_pin(x) for x in bank_names]
route_map.extend(list(zip(bank_pins, dff_pins))) route_map.extend(list(zip(bank_pins, dff_pins)))
if 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 # wmask dff
if self.num_wmasks > 0 and port in self.write_ports: if self.num_wmasks > 0 and port in self.write_ports:
dff_names = ["dout_{}".format(x) for x in range(self.num_wmasks)] dff_names = ["dout_{}".format(x) for x in range(self.num_wmasks)]
@ -377,6 +418,7 @@ class sram_1bank(sram_base):
if len(route_map) > 0: if len(route_map) > 0:
# The write masks will have blockages on M1
if self.num_wmasks > 0 and port in self.write_ports: if self.num_wmasks > 0 and port in self.write_ports:
layer_stack = self.m3_stack layer_stack = self.m3_stack
else: else:

View File

@ -196,12 +196,13 @@ class sram_base(design, verilog, lef):
self.add_lvs_correspondence_points() self.add_lvs_correspondence_points()
#self.offset_all_coordinates() self.offset_all_coordinates()
highest_coord = self.find_highest_coords() highest_coord = self.find_highest_coords()
self.width = highest_coord[0] self.width = highest_coord[0]
self.height = highest_coord[1] 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_global_pex_labels()
self.add_boundary(ll=vector(0, 0), self.add_boundary(ll=vector(0, 0),
ur=vector(self.width, self.height)) 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 # Find the lowest leftest pin for vdd and gnd
for pin_name in ["vdd", "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): for pin in self.get_pins(pin_name):
self.add_rect(pin.layer, self.add_rect(pin.layer,
pin.ll(), pin.ll(),
pin.width(), pin.width(),
pin.height()) pin.height())
# Remove the pins
# Remove the pin shape(s)
self.remove_layout_pin(pin_name) 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, self.add_layout_pin(pin_name,
pin.layer, pin.layer,
pin.ll(), pin_offset,
pin.width(), pin_width,
pin.height()) pin.height())
def route_escape_pins(self): 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)) pins_to_route.append("spare_wen{0}[{1}]".format(port, bit))
from signal_escape_router import signal_escape_router as router 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) rtr.escape_route(pins_to_route)
def compute_bus_sizes(self): def compute_bus_sizes(self):
@ -674,7 +683,7 @@ class sram_base(design, verilog, lef):
return insts 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 # Write the entire spice of the object to the file
############################################################ ############################################################
# Spice circuit # Spice circuit
@ -687,6 +696,8 @@ class sram_base(design, verilog, lef):
sp.write("* Data bits: {}\n".format(self.word_size)) sp.write("* Data bits: {}\n".format(self.word_size))
sp.write("* Banks: {}\n".format(self.num_banks)) sp.write("* Banks: {}\n".format(self.num_banks))
sp.write("* Column mux: {}:1\n".format(self.words_per_row)) 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") sp.write("**************************************************\n")
# This causes unit test mismatch # 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"], # sp.write(".global {0} {1}\n".format(spice["vdd_name"],
# spice["gnd_name"])) # spice["gnd_name"]))
usedMODS = list() usedMODS = list()
self.sp_write_file(sp, usedMODS, lvs_netlist=lvs_netlist) self.sp_write_file(sp, usedMODS, lvs=lvs, trim=trim)
del usedMODS del usedMODS
sp.close() 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): def graph_exclude_bits(self, targ_row, targ_col):
""" """
Excludes bits in column from being added to graph except target Excludes bits in column from being added to graph except target

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -54,11 +54,8 @@ class psram_1bank_4mux_func_test(openram_test):
c.words_per_row, c.words_per_row,
c.num_banks)) c.num_banks))
s = factory.create(module_type="sram", sram_config=c) 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]) 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() (fail, error) = f.run()
self.assertTrue(fail, error) self.assertTrue(fail, error)

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -45,7 +45,7 @@ class sram_pex_test(openram_test):
tempspice = self.run_pex(s) tempspice = self.run_pex(s)
corner = (OPTS.process_corners[0], OPTS.supply_voltages[0], OPTS.temperatures[0]) 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() (fail, error) = f.run()
self.assertTrue(fail, error) self.assertTrue(fail, error)

View File

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

View File

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

View File

@ -24,7 +24,6 @@ class riscv_func_test(openram_test):
globals.init_openram(config_file) globals.init_openram(config_file)
OPTS.analytical_delay = False OPTS.analytical_delay = False
OPTS.netlist_only = True OPTS.netlist_only = True
OPTS.trim_netlist = False
OPTS.local_array_size = 16 OPTS.local_array_size = 16
OPTS.num_rw_ports = 1 OPTS.num_rw_ports = 1
OPTS.num_w_ports = 0 OPTS.num_w_ports = 0
@ -49,11 +48,8 @@ class riscv_func_test(openram_test):
c.words_per_row, c.words_per_row,
c.num_banks)) c.num_banks))
s = factory.create(module_type="sram", sram_config=c) 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]) 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() (fail, error) = f.run()
self.assertTrue(fail, error) self.assertTrue(fail, error)

View File

@ -12,6 +12,8 @@ module sram_2_16_1_freepdk45(
parameter RAM_DEPTH = 1 << ADDR_WIDTH; parameter RAM_DEPTH = 1 << ADDR_WIDTH;
// FIXME: This delay is arbitrary. // FIXME: This delay is arbitrary.
parameter DELAY = 3 ; 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 clk0; // clock
input csb0; // active low chip select input csb0; // active low chip select
@ -33,10 +35,10 @@ module sram_2_16_1_freepdk45(
web0_reg = web0; web0_reg = web0;
addr0_reg = addr0; addr0_reg = addr0;
din0_reg = din0; din0_reg = din0;
dout0 = 2'bx; #(T_HOLD) dout0 = 2'bx;
if ( !csb0_reg && web0_reg ) if ( !csb0_reg && web0_reg && VERBOSE )
$display($time," Reading %m addr0=%b dout0=%b",addr0_reg,mem[addr0_reg]); $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); $display($time," Writing %m addr0=%b din0=%b",addr0_reg,din0_reg);
end end

View File

@ -12,6 +12,8 @@ module sram_2_16_1_scn4m_subm(
parameter RAM_DEPTH = 1 << ADDR_WIDTH; parameter RAM_DEPTH = 1 << ADDR_WIDTH;
// FIXME: This delay is arbitrary. // FIXME: This delay is arbitrary.
parameter DELAY = 3 ; 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 clk0; // clock
input csb0; // active low chip select input csb0; // active low chip select
@ -33,10 +35,10 @@ module sram_2_16_1_scn4m_subm(
web0_reg = web0; web0_reg = web0;
addr0_reg = addr0; addr0_reg = addr0;
din0_reg = din0; din0_reg = din0;
dout0 = 2'bx; #(T_HOLD) dout0 = 2'bx;
if ( !csb0_reg && web0_reg ) if ( !csb0_reg && web0_reg && VERBOSE )
$display($time," Reading %m addr0=%b dout0=%b",addr0_reg,mem[addr0_reg]); $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); $display($time," Writing %m addr0=%b din0=%b",addr0_reg,din0_reg);
end end

View File

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

View File

@ -12,11 +12,36 @@ from globals import OPTS
import debug import debug
import pdb import pdb
import traceback import traceback
import time
class openram_test(unittest.TestCase): class openram_test(unittest.TestCase):
""" Base unit test that we have some shared classes in. """ """ 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): def local_drc_check(self, w):
self.reset() self.reset()
@ -28,8 +53,7 @@ class openram_test(unittest.TestCase):
result=verify.run_drc(w.name, tempgds, None) result=verify.run_drc(w.name, tempgds, None)
if result != 0: if result != 0:
self.fail("DRC failed: {}".format(w.name)) self.fail("DRC failed: {}".format(w.name))
elif not OPTS.keep_temp:
if not OPTS.keep_temp:
self.cleanup() self.cleanup()
def local_check(self, a, final_verification=False): def local_check(self, a, final_verification=False):
@ -39,7 +63,7 @@ class openram_test(unittest.TestCase):
tempspice = "{}.sp".format(a.name) tempspice = "{}.sp".format(a.name)
tempgds = "{}.gds".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 # cannot write gds in netlist_only mode
if not OPTS.netlist_only: if not OPTS.netlist_only:
a.gds_write("{0}{1}".format(OPTS.openram_temp, tempgds)) 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) # shutil.make_archive(zip_file, 'zip', OPTS.openram_temp)
self.fail("LVS mismatch: {}".format(a.name)) self.fail("LVS mismatch: {}".format(a.name))
if lvs_result == 0 and drc_result == 0 and not OPTS.keep_temp:
self.cleanup()
# For debug... # For debug...
# import pdb; pdb.set_trace() # import pdb; pdb.set_trace()
if not OPTS.keep_temp:
self.cleanup()
def run_pex(self, a, output=None): def run_pex(self, a, output=None):
tempspice = "{}.sp".format(a.name) tempspice = "{}.sp".format(a.name)
@ -104,6 +128,7 @@ class openram_test(unittest.TestCase):
def cleanup(self): def cleanup(self):
""" Reset the duplicate checker and cleanup files. """ """ Reset the duplicate checker and cleanup files. """
files = glob.glob(OPTS.openram_temp + '*') files = glob.glob(OPTS.openram_temp + '*')
for f in files: for f in files:
# Only remove the files # Only remove the files

View File

@ -21,6 +21,7 @@ import os
import shutil import shutil
import re import re
import debug import debug
import utils
from globals import OPTS from globals import OPTS
from run_script import run_script 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: if not output_path:
output_path = OPTS.openram_temp output_path = OPTS.openram_temp
if output == None: if not output:
output = cell_name + ".pex.sp" output = cell_name + ".pex.sp"
# check if lvs report has been done # 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", 'pexPexReportFile': cell_name + ".pex.report",
'pexMaskDBFile': cell_name + ".maskdb", 'pexMaskDBFile': cell_name + ".maskdb",
'cmnFDIDEFLayoutPath': cell_name + ".def", '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 # 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.write("*{0}: {1}\n".format(k, pex_runset[k]))
f.close() 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 # Create an auxiliary script to run calibre with the runset
run_file = output_path + "run_pex.sh" run_file = output_path + "run_pex.sh"
f = open(run_file, "w") f = open(run_file, "w")
f.write("#!/bin/sh\n") f.write("#!/bin/sh\n")
cmd = "{0} -gui -pex 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(cmd)
f.write("\n") f.write("\n")
f.close() f.close()

View File

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