Skywater changes.

Default 1 thread and no temp subdirectory.
Add skywater setup/hold golden data
Add CLI option for simulation threads (-m)
Add compatibility mode option and nomodcheck for ngspice to speed up sky130 model loading.
Make subdir when using default /tmp dir.
Pass num_threads so temp subdirs are created.
This commit is contained in:
mrg 2021-03-08 14:40:36 -08:00
parent b6f3fbdd1f
commit 671470f5f2
18 changed files with 91 additions and 36 deletions

View File

@ -19,7 +19,7 @@ jobs:
uses: actions/upload-artifact@v2 uses: actions/upload-artifact@v2
with: with:
name: scn4me_subm Archives name: scn4me_subm Archives
path: ${{ github.workspace }}/scn4me_subm_temp/*/* path: $OPENRAM_HOME/*.zip
freepdk45: freepdk45:
runs-on: self-hosted runs-on: self-hosted
steps: steps:
@ -38,7 +38,7 @@ jobs:
uses: actions/upload-artifact@v2 uses: actions/upload-artifact@v2
with: with:
name: FreePDK45 Archives name: FreePDK45 Archives
path: ${{ github.workspace }}/freepdk45_temp/*/* path: $OPENRAM_HOME/*.zip
# coverage_stats: # coverage_stats:
# if: ${{ always() }} # if: ${{ always() }}
# needs: [scn4me_subm, freepdk45] # needs: [scn4me_subm, freepdk45]

View File

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

@ -360,6 +360,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

@ -43,7 +43,7 @@ def error(str, return_value=0):
if globals.OPTS.debug: if globals.OPTS.debug:
pdb.set_trace() pdb.set_trace()
assert return_value == 0 assert return_value == 0
@ -108,7 +108,20 @@ def info(lev, str):
print_raw("[{0}/{1}]: {2}".format(class_name, print_raw("[{0}/{1}]: {2}".format(class_name,
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

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

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

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

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

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

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

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

View File

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

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

@ -28,10 +28,9 @@ 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):
self.reset() self.reset()
@ -74,10 +73,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 +103,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