From 19e99d1c7b29281b1a1f12bf73d9de4ef25c9752 Mon Sep 17 00:00:00 2001 From: mrg Date: Wed, 3 Feb 2021 14:19:11 -0800 Subject: [PATCH] Enable parallel regression testing. --- compiler/characterizer/stimuli.py | 6 +-- compiler/globals.py | 15 +++++++- compiler/options.py | 7 ++-- compiler/tests/regress.py | 61 ++++++++++++++++++++++++++++++- compiler/tests/testutils.py | 3 +- 5 files changed, 81 insertions(+), 11 deletions(-) diff --git a/compiler/characterizer/stimuli.py b/compiler/characterizer/stimuli.py index eed7a0d6..035e9e58 100644 --- a/compiler/characterizer/stimuli.py +++ b/compiler/characterizer/stimuli.py @@ -312,12 +312,12 @@ class stimuli(): cmd = "{0} {1} -c {2}xa.cfg -o {2}xa -mt {3}".format(OPTS.spice_exe, temp_stim, OPTS.openram_temp, - OPTS.num_threads) + OPTS.num_sim_threads) valid_retcode=0 elif OPTS.spice_name == "hspice": # TODO: Should make multithreading parameter a configuration option cmd = "{0} -mt {1} -i {2} -o {3}timing".format(OPTS.spice_exe, - OPTS.num_threads, + OPTS.num_sim_threads, temp_stim, OPTS.openram_temp) valid_retcode=0 @@ -326,7 +326,7 @@ class stimuli(): # Measurements can't be made with a raw file set in ngspice # -r {2}timing.raw ng_cfg = open("{}.spiceinit".format(OPTS.openram_temp), "w") - ng_cfg.write("set num_threads={}\n".format(OPTS.num_threads)) + ng_cfg.write("set num_threads={}\n".format(OPTS.num_sim_threads)) ng_cfg.close() cmd = "{0} -b -o {2}timing.lis {1}".format(OPTS.spice_exe, diff --git a/compiler/globals.py b/compiler/globals.py index 57f1577b..8ea989df 100644 --- a/compiler/globals.py +++ b/compiler/globals.py @@ -18,6 +18,8 @@ import sys import re import copy import importlib +import getpass + VERSION = "1.1.9" NAME = "OpenRAM v{}".format(VERSION) @@ -113,6 +115,10 @@ def parse_args(): if OPTS.tech_name == "s8": OPTS.tech_name = "sky130" + if OPTS.openram_temp: + # If they define the temp directory, we can only use one thread at a time! + OPTS.num_threads = 1 + return (options, args) @@ -133,8 +139,9 @@ def print_banner(): debug.print_raw("|=========" + user_info.center(60) + "=========|") dev_info = "Development help: openram-dev-group@ucsc.edu" debug.print_raw("|=========" + dev_info.center(60) + "=========|") - temp_info = "Temp dir: {}".format(OPTS.openram_temp) - debug.print_raw("|=========" + temp_info.center(60) + "=========|") + if OPTS.openram_temp: + temp_info = "Temp dir: {}".format(OPTS.openram_temp) + debug.print_raw("|=========" + temp_info.center(60) + "=========|") debug.print_raw("|=========" + "See LICENSE for license info".center(60) + "=========|") debug.print_raw("|==============================================================================|") @@ -414,6 +421,10 @@ def setup_paths(): if "__pycache__" not in full_path: sys.path.append("{0}".format(full_path)) + # Use a unique temp directory + if not OPTS.openram_temp: + OPTS.openram_temp = "/tmp/openram_{0}_{1}_temp/".format(getpass.getuser(), + os.getpid()) if not OPTS.openram_temp.endswith('/'): OPTS.openram_temp += "/" debug.info(1, "Temporary files saved in " + OPTS.openram_temp) diff --git a/compiler/options.py b/compiler/options.py index 87083a7e..91bae758 100644 --- a/compiler/options.py +++ b/compiler/options.py @@ -74,9 +74,8 @@ class options(optparse.Values): # If user defined the temporary location in their environment, use it openram_temp = os.path.abspath(os.environ.get("OPENRAM_TMP")) except: - # Else use a unique temporary directory - openram_temp = "/tmp/openram_{0}_{1}_temp/".format(getpass.getuser(), - os.getpid()) + openram_temp = None + # This is the verbosity level to control debug information. 0 is none, 1 # is minimal, etc. verbose_level = 0 @@ -135,6 +134,8 @@ class options(optparse.Values): # Number of threads to use num_threads = 2 + # Number of threads to use in ngspice/hspice + num_sim_threads = 2 # Should we print out the banner at startup print_banner = True diff --git a/compiler/tests/regress.py b/compiler/tests/regress.py index bac5d8e1..bd8ba019 100755 --- a/compiler/tests/regress.py +++ b/compiler/tests/regress.py @@ -12,6 +12,9 @@ import unittest 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() del sys.argv[1:] @@ -39,17 +42,71 @@ all_tests = list(filter(nametest.search, files)) filtered_tests = list(filter(lambda i: i not in skip_tests, all_tests)) filtered_tests.sort() +num_threads = OPTS.num_threads + + +def partition_unit_tests(suite, num_threads): + partitions = [list() for x in range(num_threads)] + for index, test in enumerate(suite): + partitions[index % num_threads].append(test) + return partitions + + +def fork_tests(num_threads): + results = [] + test_partitions = partition_unit_tests(suite, num_threads) + suite._tests[:] = [] + + def do_fork(suite): + + for test_partition in test_partitions: + test_suite = unittest.TestSuite(test_partition) + test_partition[:] = [] + c2pread, c2pwrite = os.pipe() + pid = os.fork() + if pid == 0: + # PID of 0 is a child + try: + # Open a stream to write to the parent + stream = os.fdopen(c2pwrite, 'wb', 0) + os.close(c2pread) + sys.stdin.close() + test_suite_result = AutoTimingTestResultDecorator(TestProtocolClient(stream)) + test_suite.run(test_suite_result) + except: + try: + stream.write(traceback.format_exc()) + finally: + os._exit(1) + os._exit(0) + else: + # PID >0 is the parent + # Collect all of the child streams and append to the results + os.close(c2pwrite) + stream = os.fdopen(c2pread, 'rb', 0) + test = ProtocolTestCase(stream) + results.append(test) + return results + return do_fork + + # import all of the modules filenameToModuleName = lambda f: os.path.splitext(f)[0] moduleNames = map(filenameToModuleName, filtered_tests) modules = map(__import__, moduleNames) + suite = unittest.TestSuite() load = unittest.defaultTestLoader.loadTestsFromModule suite.addTests(map(load, modules)) test_runner = unittest.TextTestRunner(verbosity=2, stream=sys.stderr) -test_result = test_runner.run(suite) - +if num_threads == 1: + final_suite = suite +else: + final_suite = ConcurrentTestSuite(suite, fork_tests(num_threads)) + +test_result = test_runner.run(final_suite) + import verify verify.print_drc_stats() verify.print_lvs_stats() diff --git a/compiler/tests/testutils.py b/compiler/tests/testutils.py index eddee87d..b6468749 100644 --- a/compiler/tests/testutils.py +++ b/compiler/tests/testutils.py @@ -315,7 +315,8 @@ def header(filename, technology): print("|=========" + technology.center(60) + "=========|") print("|=========" + filename.center(60) + "=========|") from globals import OPTS - print("|=========" + OPTS.openram_temp.center(60) + "=========|") + if OPTS.openram_temp: + print("|=========" + OPTS.openram_temp.center(60) + "=========|") print("|==============================================================================|")