From 671470f5f2d249547f4ce80c54b93dc8ed6104ca Mon Sep 17 00:00:00 2001 From: mrg Date: Mon, 8 Mar 2021 14:40:36 -0800 Subject: [PATCH] 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. --- .github/workflows/ci.yml | 4 +-- compiler/characterizer/charutils.py | 2 ++ compiler/characterizer/stimuli.py | 2 ++ compiler/debug.py | 15 +++++++++- ...1r.py => riscv_scn4m_subm_2kbyte_1rw1r.py} | 0 compiler/globals.py | 29 ++++++++++++++++--- compiler/modules/bank.py | 3 ++ compiler/modules/hierarchical_predecode.py | 19 +++++++----- compiler/modules/hierarchical_predecode2x4.py | 4 +-- compiler/modules/hierarchical_predecode3x8.py | 4 +-- .../modules/hierarchical_predecode4x16.py | 4 +-- compiler/options.py | 2 +- compiler/tests/21_hspice_setuphold_test.py | 10 ++++--- compiler/tests/21_ngspice_setuphold_test.py | 10 ++++--- compiler/tests/30_openram_back_end_test.py | 3 ++ compiler/tests/30_openram_front_end_test.py | 3 ++ compiler/tests/regress.py | 3 +- compiler/tests/testutils.py | 10 +++---- 18 files changed, 91 insertions(+), 36 deletions(-) rename compiler/example_configs/{riscv_scn4m_subm_2skbyte_1rw1r.py => riscv_scn4m_subm_2kbyte_1rw1r.py} (100%) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 262e65ca..d50b93d2 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -19,7 +19,7 @@ jobs: uses: actions/upload-artifact@v2 with: name: scn4me_subm Archives - path: ${{ github.workspace }}/scn4me_subm_temp/*/* + path: $OPENRAM_HOME/*.zip freepdk45: runs-on: self-hosted steps: @@ -38,7 +38,7 @@ jobs: uses: actions/upload-artifact@v2 with: name: FreePDK45 Archives - path: ${{ github.workspace }}/freepdk45_temp/*/* + path: $OPENRAM_HOME/*.zip # coverage_stats: # if: ${{ always() }} # needs: [scn4me_subm, freepdk45] diff --git a/compiler/characterizer/charutils.py b/compiler/characterizer/charutils.py index b618f0db..8f33c279 100644 --- a/compiler/characterizer/charutils.py +++ b/compiler/characterizer/charutils.py @@ -31,6 +31,8 @@ def parse_spice_list(filename, key): f = open(full_filename, "r") except IOError: debug.error("Unable to open spice output file: {0}".format(full_filename),1) + debug.archive() + contents = f.read() f.close() # val = re.search(r"{0}\s*=\s*(-?\d+.?\d*\S*)\s+.*".format(key), contents) diff --git a/compiler/characterizer/stimuli.py b/compiler/characterizer/stimuli.py index 4eee9e5c..06b6058d 100644 --- a/compiler/characterizer/stimuli.py +++ b/compiler/characterizer/stimuli.py @@ -360,6 +360,8 @@ class stimuli(): # -r {2}timing.raw ng_cfg = open("{}.spiceinit".format(OPTS.openram_temp), "w") ng_cfg.write("set num_threads={}\n".format(OPTS.num_sim_threads)) + ng_cfg.write("set ngbehavior=hsa\n") + ng_cfg.write("set ng_nomodcheck\n") ng_cfg.close() cmd = "{0} -b -o {2}timing.lis {1}".format(OPTS.spice_exe, diff --git a/compiler/debug.py b/compiler/debug.py index f14b1224..f8a4807d 100644 --- a/compiler/debug.py +++ b/compiler/debug.py @@ -43,7 +43,7 @@ def error(str, return_value=0): if globals.OPTS.debug: pdb.set_trace() - + assert return_value == 0 @@ -108,7 +108,20 @@ def info(lev, str): print_raw("[{0}/{1}]: {2}".format(class_name, frm[0].f_code.co_name, str)) + +def archive(): + from globals import OPTS + try: + OPENRAM_HOME = os.path.abspath(os.environ.get("OPENRAM_HOME")) + except: + error("$OPENRAM_HOME is not properly defined.", 1) + import shutil + zip_file = "{0}/{1}_{2}".format(OPENRAM_HOME, "fail_", os.getpid()) + info(0, "Archiving failed files to {}.zip".format(zip_file)) + shutil.make_archive(zip_file, 'zip', OPTS.openram_temp) + + def bp(): """ An empty function so you can set soft breakpoints in pdb. diff --git a/compiler/example_configs/riscv_scn4m_subm_2skbyte_1rw1r.py b/compiler/example_configs/riscv_scn4m_subm_2kbyte_1rw1r.py similarity index 100% rename from compiler/example_configs/riscv_scn4m_subm_2skbyte_1rw1r.py rename to compiler/example_configs/riscv_scn4m_subm_2kbyte_1rw1r.py diff --git a/compiler/globals.py b/compiler/globals.py index b977e8e3..1a4bd5c5 100644 --- a/compiler/globals.py +++ b/compiler/globals.py @@ -61,8 +61,13 @@ def parse_args(): optparse.make_option("-j", "--threads", action="store", type="int", - help="Specify the number of threads (default: 2)", + help="Specify the number of threads (default: 1)", dest="num_threads"), + optparse.make_option("-m", "--sim_threads", + action="store", + type="int", + help="Specify the number of spice simulation threads (default: 2)", + dest="num_sim_threads"), optparse.make_option("-v", "--verbose", action="count", @@ -381,6 +386,10 @@ def purge_temp(): """ Remove the temp directory. """ debug.info(1, "Purging temp directory: {}".format(OPTS.openram_temp)) + #import inspect + #s = inspect.stack() + #print("Purge {0} in dir {1}".format(s[3].filename, OPTS.openram_temp)) + # This annoyingly means you have to re-cd into # the directory each debug iteration # shutil.rmtree(OPTS.openram_temp, ignore_errors=True) @@ -429,9 +438,15 @@ def setup_paths(): if "__pycache__" not in full_path: sys.path.append("{0}".format(full_path)) - # Use a unique temp subdirectory - OPTS.openram_temp += "/openram_{0}_{1}_temp/".format(getpass.getuser(), - os.getpid()) + # Use a unique temp subdirectory if multithreaded + if OPTS.num_threads > 1 or OPTS.openram_temp == "/tmp": + + # Make a unique subdir + tempdir = "/openram_{0}_{1}_temp".format(getpass.getuser(), + os.getpid()) + # Only add the unique subdir one time + if tempdir not in OPTS.openram_temp: + OPTS.openram_temp += tempdir if not OPTS.openram_temp.endswith('/'): OPTS.openram_temp += "/" @@ -470,6 +485,12 @@ def init_paths(): except OSError as e: if e.errno == 17: # errno.EEXIST os.chmod(OPTS.openram_temp, 0o750) + #import inspect + #s = inspect.stack() + #from pprint import pprint + #pprint(s) + #print("Test {0} in dir {1}".format(s[2].filename, OPTS.openram_temp)) + # Don't delete the output dir, it may have other files! # make the directory if it doesn't exist diff --git a/compiler/modules/bank.py b/compiler/modules/bank.py index 7f7fb0d4..488439fd 100644 --- a/compiler/modules/bank.py +++ b/compiler/modules/bank.py @@ -526,13 +526,16 @@ class bank(design.design): height=self.dff.height) elif self.col_addr_size == 2: self.column_decoder = factory.create(module_type="hierarchical_predecode2x4", + column_decoder=True, height=self.dff.height) elif self.col_addr_size == 3: self.column_decoder = factory.create(module_type="hierarchical_predecode3x8", + column_decoder=True, height=self.dff.height) elif self.col_addr_size == 4: self.column_decoder = factory.create(module_type="hierarchical_predecode4x16", + column_decoder=True, height=self.dff.height) else: # No error checking before? diff --git a/compiler/modules/hierarchical_predecode.py b/compiler/modules/hierarchical_predecode.py index bd192694..c2ed5949 100644 --- a/compiler/modules/hierarchical_predecode.py +++ b/compiler/modules/hierarchical_predecode.py @@ -18,19 +18,17 @@ class hierarchical_predecode(design.design): """ Pre 2x4 and 3x8 and TBD 4x16 decoder shared code. """ - def __init__(self, name, input_number, height=None): + def __init__(self, name, input_number, column_decoder=False, height=None): self.number_of_inputs = input_number b = factory.create(module_type=OPTS.bitcell) if not height: self.cell_height = b.height - self.column_decoder = False else: self.cell_height = height - # If we are pitch matched to the bitcell, it's a predecoder - # otherwise it's a column decoder (out of pgates) - self.column_decoder = (height != b.height) + + self.column_decoder = column_decoder self.number_of_outputs = int(math.pow(2, self.number_of_inputs)) super().__init__(name) @@ -87,8 +85,15 @@ class hierarchical_predecode(design.design): self.bus_layer = layer_props.hierarchical_predecode.bus_layer self.bus_directions = layer_props.hierarchical_predecode.bus_directions - self.bus_pitch = getattr(self, self.bus_layer + "_pitch") - self.bus_space = layer_props.hierarchical_predecode.bus_space_factor * getattr(self, self.bus_layer + "_space") + + if self.column_decoder: + # Column decoders may be routed on M2/M3 if there's a write mask + self.bus_pitch = self.m3_pitch + self.bus_space = self.m3_space + else: + self.bus_pitch = getattr(self, self.bus_layer + "_pitch") + self.bus_space = getattr(self, self.bus_layer + "_space") + self.bus_space = layer_props.hierarchical_predecode.bus_space_factor * self.bus_space self.input_layer = layer_props.hierarchical_predecode.input_layer self.output_layer = layer_props.hierarchical_predecode.output_layer self.output_layer_pitch = getattr(self, self.output_layer + "_pitch") diff --git a/compiler/modules/hierarchical_predecode2x4.py b/compiler/modules/hierarchical_predecode2x4.py index c212a3c6..941a0756 100644 --- a/compiler/modules/hierarchical_predecode2x4.py +++ b/compiler/modules/hierarchical_predecode2x4.py @@ -13,8 +13,8 @@ class hierarchical_predecode2x4(hierarchical_predecode): """ Pre 2x4 decoder used in hierarchical_decoder. """ - def __init__(self, name, height=None): - super().__init__( name, 2, height) + def __init__(self, name, column_decoder=False, height=None): + super().__init__(name, 2, column_decoder, height) self.create_netlist() if not OPTS.netlist_only: diff --git a/compiler/modules/hierarchical_predecode3x8.py b/compiler/modules/hierarchical_predecode3x8.py index 61c25094..ef70a282 100644 --- a/compiler/modules/hierarchical_predecode3x8.py +++ b/compiler/modules/hierarchical_predecode3x8.py @@ -13,8 +13,8 @@ class hierarchical_predecode3x8(hierarchical_predecode): """ Pre 3x8 decoder used in hierarchical_decoder. """ - def __init__(self, name, height=None): - super().__init__(name, 3, height) + def __init__(self, name, column_decoder=False, height=None): + super().__init__(name, 3, column_decoder, height) self.create_netlist() if not OPTS.netlist_only: diff --git a/compiler/modules/hierarchical_predecode4x16.py b/compiler/modules/hierarchical_predecode4x16.py index fe1426d8..64eef96d 100644 --- a/compiler/modules/hierarchical_predecode4x16.py +++ b/compiler/modules/hierarchical_predecode4x16.py @@ -13,8 +13,8 @@ class hierarchical_predecode4x16(hierarchical_predecode): """ Pre 4x16 decoder used in hierarchical_decoder. """ - def __init__(self, name, height=None): - super().__init__(name, 4, height) + def __init__(self, name, column_decoder=False, height=None): + super().__init__(name, 4, column_decoder, height) self.create_netlist() if not OPTS.netlist_only: diff --git a/compiler/options.py b/compiler/options.py index e3a9a76e..551c3950 100644 --- a/compiler/options.py +++ b/compiler/options.py @@ -133,7 +133,7 @@ class options(optparse.Values): magic_exe = None # Number of threads to use - num_threads = 2 + num_threads = 1 # Number of threads to use in ngspice/hspice num_sim_threads = 2 diff --git a/compiler/tests/21_hspice_setuphold_test.py b/compiler/tests/21_hspice_setuphold_test.py index e97a8aca..634b2982 100755 --- a/compiler/tests/21_hspice_setuphold_test.py +++ b/compiler/tests/21_hspice_setuphold_test.py @@ -12,8 +12,7 @@ import sys, os sys.path.append(os.getenv("OPENRAM_HOME")) import globals from globals import OPTS -from sram_factory import factory -import debug + class timing_setup_test(openram_test): @@ -29,14 +28,12 @@ class timing_setup_test(openram_test): import characterizer reload(characterizer) from characterizer import setup_hold - import sram import tech slews = [tech.spice["rise_time"]*2] corner = (OPTS.process_corners[0], OPTS.supply_voltages[0], OPTS.temperatures[0]) sh = setup_hold(corner) data = sh.analyze(slews,slews) - #print data if OPTS.tech_name == "freepdk45": golden_data = {'hold_times_HL': [-0.0158691], 'hold_times_LH': [-0.0158691], @@ -47,6 +44,11 @@ class timing_setup_test(openram_test): 'hold_times_LH': [-0.11718749999999999], 'setup_times_HL': [0.16357419999999998], 'setup_times_LH': [0.1757812]} + elif OPTS.tech_name == "sky130": + golden_data = {'hold_times_HL': [-0.05615234], + 'hold_times_LH': [-0.03173828], + 'setup_times_HL': [0.078125], + 'setup_times_LH': [0.1025391]} else: self.assertTrue(False) # other techs fail diff --git a/compiler/tests/21_ngspice_setuphold_test.py b/compiler/tests/21_ngspice_setuphold_test.py index da6d07ff..dab02e7d 100755 --- a/compiler/tests/21_ngspice_setuphold_test.py +++ b/compiler/tests/21_ngspice_setuphold_test.py @@ -12,8 +12,7 @@ import sys, os sys.path.append(os.getenv("OPENRAM_HOME")) import globals from globals import OPTS -from sram_factory import factory -import debug + class timing_setup_test(openram_test): @@ -29,14 +28,12 @@ class timing_setup_test(openram_test): import characterizer reload(characterizer) from characterizer import setup_hold - import sram import tech slews = [tech.spice["rise_time"]*2] corner = (OPTS.process_corners[0], OPTS.supply_voltages[0], OPTS.temperatures[0]) sh = setup_hold(corner) data = sh.analyze(slews,slews) - #print data if OPTS.tech_name == "freepdk45": golden_data = {'hold_times_HL': [-0.01586914], 'hold_times_LH': [-0.01586914], @@ -47,6 +44,11 @@ class timing_setup_test(openram_test): 'hold_times_LH': [-0.1293945], 'setup_times_HL': [0.1757812], 'setup_times_LH': [0.1879883]} + elif OPTS.tech_name == "sky130": + golden_data = {'hold_times_HL': [-0.05615234], + 'hold_times_LH': [-0.03173828], + 'setup_times_HL': [0.078125], + 'setup_times_LH': [0.1025391]} else: self.assertTrue(False) # other techs fail diff --git a/compiler/tests/30_openram_back_end_test.py b/compiler/tests/30_openram_back_end_test.py index c2060656..74232db0 100755 --- a/compiler/tests/30_openram_back_end_test.py +++ b/compiler/tests/30_openram_back_end_test.py @@ -49,6 +49,9 @@ class openram_back_end_test(openram_test): if OPTS.tech_name: options += " -t {}".format(OPTS.tech_name) + if OPTS.num_threads: + options += " -j {}".format(OPTS.num_threads) + # Always perform code coverage if OPTS.coverage == 0: debug.warning("Failed to find coverage installation. This can be installed with pip3 install coverage") diff --git a/compiler/tests/30_openram_front_end_test.py b/compiler/tests/30_openram_front_end_test.py index 1c190840..9c134152 100755 --- a/compiler/tests/30_openram_front_end_test.py +++ b/compiler/tests/30_openram_front_end_test.py @@ -49,6 +49,9 @@ class openram_front_end_test(openram_test): if OPTS.tech_name: options += " -t {}".format(OPTS.tech_name) + if OPTS.num_threads: + options += " -j {}".format(OPTS.num_threads) + # Always perform code coverage if OPTS.coverage == 0: debug.warning("Failed to find coverage installation. This can be installed with pip3 install coverage") diff --git a/compiler/tests/regress.py b/compiler/tests/regress.py index 878182a5..ea7e1991 100755 --- a/compiler/tests/regress.py +++ b/compiler/tests/regress.py @@ -13,7 +13,6 @@ import sys, os sys.path.append(os.getenv("OPENRAM_HOME")) import globals from subunit import ProtocolTestCase, TestProtocolClient -from subunit.test_results import AutoTimingTestResultDecorator from testtools import ConcurrentTestSuite (OPTS, args) = globals.parse_args() @@ -71,7 +70,7 @@ def fork_tests(num_threads): stream = os.fdopen(c2pwrite, 'wb', 0) os.close(c2pread) sys.stdin.close() - test_suite_result = AutoTimingTestResultDecorator(TestProtocolClient(stream)) + test_suite_result = TestProtocolClient(stream) test_suite.run(test_suite_result) except EBADF: try: diff --git a/compiler/tests/testutils.py b/compiler/tests/testutils.py index b6468749..4d8bf573 100644 --- a/compiler/tests/testutils.py +++ b/compiler/tests/testutils.py @@ -28,10 +28,9 @@ class openram_test(unittest.TestCase): result=verify.run_drc(w.name, tempgds, None) if result != 0: self.fail("DRC failed: {}".format(w.name)) - - if not OPTS.keep_temp: + elif not OPTS.keep_temp: self.cleanup() - + def local_check(self, a, final_verification=False): self.reset() @@ -74,10 +73,10 @@ class openram_test(unittest.TestCase): # shutil.make_archive(zip_file, 'zip', OPTS.openram_temp) self.fail("LVS mismatch: {}".format(a.name)) + if lvs_result == 0 and drc_result == 0 and not OPTS.keep_temp: + self.cleanup() # For debug... # import pdb; pdb.set_trace() - if not OPTS.keep_temp: - self.cleanup() def run_pex(self, a, output=None): tempspice = "{}.sp".format(a.name) @@ -104,6 +103,7 @@ class openram_test(unittest.TestCase): def cleanup(self): """ Reset the duplicate checker and cleanup files. """ + files = glob.glob(OPTS.openram_temp + '*') for f in files: # Only remove the files