diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 00000000..6a7cd73b --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,55 @@ +name: ci +on: [push] +jobs: + scn4me_subm: + runs-on: self-hosted + steps: + - name: Check out repository + uses: actions/checkout@v1 + - name: SCMOS test + run: | + . /home/github-runner/setup-paths.sh + export OPENRAM_HOME="`pwd`/compiler" + export OPENRAM_TECH="`pwd`/technology:/software/PDKs/skywater-tech" + export OPENRAM_TMP="`pwd`/scn4me_subm" + python3-coverage run -p $OPENRAM_HOME/tests/regress.py -j 48 -t scn4m_subm + - name: Archive + if: ${{ failure() }} + uses: actions/upload-artifact@v2 + with: + name: scn4me_subm Archives + path: $OPENRAM_TMP/ + freepdk45: + runs-on: self-hosted + steps: + - name: Check out repository + uses: actions/checkout@v1 + - name: FreePDK45 test + run: | + . /home/github-runner/setup-paths.sh + export OPENRAM_HOME="`pwd`/compiler" + export OPENRAM_TECH="`pwd`/technology:/software/PDKs/skywater-tech" + export OPENRAM_TMP="`pwd`/freepdk45" + python3-coverage run -p $OPENRAM_HOME/tests/regress.py -j 48 -t freepdk45 + - name: Archive + if: ${{ failure() }} + uses: actions/upload-artifact@v2 + with: + name: FreePDK45 Archives + path: $OPENRAM_TMP/ + coverage: + if: ${{ always() }} + needs: [scn4me_subm, freepdk45] + runs-on: self-hosted + steps: + - name: Coverage stats + run: | + python3-coverage combine + python3-coverage report + python3-coverage html -d coverage_html + - name: Archive coverage + uses: actions/upload-artifact@v2 + with: + name: code-coverage-report + path: coverage_html/ + diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml deleted file mode 100644 index 27431cb2..00000000 --- a/.gitlab-ci.yml +++ /dev/null @@ -1,48 +0,0 @@ -before_script: - - . /home/gitlab-runner/setup-paths.sh - - export OPENRAM_HOME="`pwd`/compiler" - - export OPENRAM_TECH="`pwd`/technology:/home/PDKs/skywater-tech" - -stages: - - test - - coverage - -freepdk45: - stage: test - script: - - coverage run -p $OPENRAM_HOME/tests/regress.py -t freepdk45 - artifacts: - paths: - - .coverage.* - expire_in: 1 week - -scn4m_subm: - stage: test - script: - - coverage run -p $OPENRAM_HOME/tests/regress.py -t scn4m_subm - artifacts: - paths: - - .coverage.* - expire_in: 1 week - -# s8: -# stage: test -# script: -# - coverage run -p $OPENRAM_HOME/tests/regress.py -t s8 -# artifacts: -# paths: -# - .coverage.* -# expire_in: 1 week - -coverage: - stage: coverage - script: - - coverage combine - - coverage report - - coverage html -d coverage_html - artifacts: - paths: - - coverage_html - expire_in: 1 week - coverage: '/TOTAL.+ ([0-9]{1,3}%)/' - diff --git a/README.md b/README.md index dd7055e0..da68361b 100644 --- a/README.md +++ b/README.md @@ -4,13 +4,9 @@ [![License: BSD 3-clause](./images/license_badge.svg)](./LICENSE) Master: -[![Pipeline Status](https://scone.soe.ucsc.edu:8888/mrg/OpenRAM/badges/master/pipeline.svg)](https://github.com/VLSIDA/OpenRAM/commits/master) -![Coverage](https://scone.soe.ucsc.edu:8888/mrg/OpenRAM/badges/master/coverage.svg) [![Download](./images/download-stable-blue.svg)](https://github.com/VLSIDA/OpenRAM/archive/master.zip) Dev: -[![Pipeline Status](https://scone.soe.ucsc.edu:8888/mrg/OpenRAM/badges/dev/pipeline.svg)](https://github.com/VLSIDA/OpenRAM/commits/dev) -![Coverage](https://scone.soe.ucsc.edu:8888/mrg/OpenRAM/badges/dev/coverage.svg) [![Download](./images/download-unstable-blue.svg)](https://github.com/VLSIDA/OpenRAM/archive/dev.zip) An open-source static random access memory (SRAM) compiler. diff --git a/compiler/characterizer/__init__.py b/compiler/characterizer/__init__.py index 040658f0..0d9fbe83 100644 --- a/compiler/characterizer/__init__.py +++ b/compiler/characterizer/__init__.py @@ -7,8 +7,7 @@ # import os import debug -import globals -from globals import OPTS,find_exe,get_tool +from globals import OPTS, find_exe, get_tool from .lib import * from .delay import * from .elmore import * @@ -21,7 +20,7 @@ from .model_check import * from .analytical_util import * from .regression_model import * -debug.info(1,"Initializing characterizer...") +debug.info(1, "Initializing characterizer...") OPTS.spice_exe = "" if not OPTS.analytical_delay: @@ -30,17 +29,17 @@ if not OPTS.analytical_delay: if OPTS.spice_name != "": OPTS.spice_exe=find_exe(OPTS.spice_name) if OPTS.spice_exe=="" or OPTS.spice_exe==None: - debug.error("{0} not found. Unable to perform characterization.".format(OPTS.spice_name),1) + debug.error("{0} not found. Unable to perform characterization.".format(OPTS.spice_name), 1) else: - (OPTS.spice_name,OPTS.spice_exe) = get_tool("spice",["hspice", "ngspice", "ngspice.exe", "xa"]) + (OPTS.spice_name, OPTS.spice_exe) = get_tool("spice", ["ngspice", "ngspice.exe", "hspice", "xa"]) # set the input dir for spice files if using ngspice if OPTS.spice_name == "ngspice": os.environ["NGSPICE_INPUT_DIR"] = "{0}".format(OPTS.openram_temp) if OPTS.spice_exe == "": - debug.error("No recognizable spice version found. Unable to perform characterization.",1) + debug.error("No recognizable spice version found. Unable to perform characterization.", 1) else: - debug.info(1,"Analytical model enabled.") + debug.info(1, "Analytical model enabled.") 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/example_configs/riscv-freepdk45-8kbyte.py b/compiler/example_configs/riscv_freepdk45_8kbyte.py similarity index 100% rename from compiler/example_configs/riscv-freepdk45-8kbyte.py rename to compiler/example_configs/riscv_freepdk45_8kbyte.py diff --git a/compiler/example_configs/riscv-scn4m_subm-16kbyte-1rw1r.py b/compiler/example_configs/riscv_scn4m_subm_16kbyte_1rw1r.py similarity index 100% rename from compiler/example_configs/riscv-scn4m_subm-16kbyte-1rw1r.py rename to compiler/example_configs/riscv_scn4m_subm_16kbyte_1rw1r.py diff --git a/compiler/example_configs/riscv-scn4m_subm-1kbyte-1rw1r.py b/compiler/example_configs/riscv_scn4m_subm_1kbyte_1rw1r.py similarity index 100% rename from compiler/example_configs/riscv-scn4m_subm-1kbyte-1rw1r.py rename to compiler/example_configs/riscv_scn4m_subm_1kbyte_1rw1r.py diff --git a/compiler/example_configs/riscv-scn4m_subm-2kbyte-1rw1r.py b/compiler/example_configs/riscv_scn4m_subm_2skbyte_1rw1r.py similarity index 100% rename from compiler/example_configs/riscv-scn4m_subm-2kbyte-1rw1r.py rename to compiler/example_configs/riscv_scn4m_subm_2skbyte_1rw1r.py diff --git a/compiler/example_configs/riscv-scn4m_subm-32kbyte.py b/compiler/example_configs/riscv_scn4m_subm_32kbyte_1rw1r.py similarity index 100% rename from compiler/example_configs/riscv-scn4m_subm-32kbyte.py rename to compiler/example_configs/riscv_scn4m_subm_32kbyte_1rw1r.py diff --git a/compiler/example_configs/riscv-scn4m_subm-4kbyte-1rw1r.py b/compiler/example_configs/riscv_scn4m_subm_4kbyte_1rw1r.py similarity index 100% rename from compiler/example_configs/riscv-scn4m_subm-4kbyte-1rw1r.py rename to compiler/example_configs/riscv_scn4m_subm_4kbyte_1rw1r.py diff --git a/compiler/example_configs/riscv-scn4m_subm-8kbyte-1rw1r.py b/compiler/example_configs/riscv_scn4m_subm_8kbyte_1rw1r.py similarity index 100% rename from compiler/example_configs/riscv-scn4m_subm-8kbyte-1rw1r.py rename to compiler/example_configs/riscv_scn4m_subm_8kbyte_1rw1r.py diff --git a/compiler/example_configs/riscv-sky130-1kbyte-1rw.py b/compiler/example_configs/riscv_sky130_1kbyte_1rw.py similarity index 100% rename from compiler/example_configs/riscv-sky130-1kbyte-1rw.py rename to compiler/example_configs/riscv_sky130_1kbyte_1rw.py diff --git a/compiler/example_configs/riscv-sky130-1kbyte-1rw1r.py b/compiler/example_configs/riscv_sky130_1kbyte_1rw1r.py similarity index 90% rename from compiler/example_configs/riscv-sky130-1kbyte-1rw1r.py rename to compiler/example_configs/riscv_sky130_1kbyte_1rw1r.py index 20463a99..d0b47857 100644 --- a/compiler/example_configs/riscv-sky130-1kbyte-1rw1r.py +++ b/compiler/example_configs/riscv_sky130_1kbyte_1rw1r.py @@ -2,7 +2,7 @@ word_size = 32 num_words = 256 write_size = 8 -local_array_size = 16 +#local_array_size = 16 num_rw_ports = 1 num_r_ports = 1 @@ -11,9 +11,9 @@ num_w_ports = 0 tech_name = "sky130" nominal_corner_only = True -route_supplies = False +#route_supplies = False check_lvsdrc = True -perimeter_pins = False +#perimeter_pins = False #netlist_only = True #analytical_delay = False output_name = "sram_{0}rw{1}r{2}w_{3}_{4}_{5}".format(num_rw_ports, diff --git a/compiler/example_configs/riscv-sky130-2kbyte-1rw.py b/compiler/example_configs/riscv_sky130_2kbyte_1rw.py similarity index 100% rename from compiler/example_configs/riscv-sky130-2kbyte-1rw.py rename to compiler/example_configs/riscv_sky130_2kbyte_1rw.py diff --git a/compiler/example_configs/riscv-sky130-2kbyte-1rw1r.py b/compiler/example_configs/riscv_sky130_2kbyte_1rw1r.py similarity index 100% rename from compiler/example_configs/riscv-sky130-2kbyte-1rw1r.py rename to compiler/example_configs/riscv_sky130_2kbyte_1rw1r.py diff --git a/compiler/example_configs/riscv-sky130-4kbyte-1rw.py b/compiler/example_configs/riscv_sky130_4kbyte_1rw.py similarity index 100% rename from compiler/example_configs/riscv-sky130-4kbyte-1rw.py rename to compiler/example_configs/riscv_sky130_4kbyte_1rw.py diff --git a/compiler/example_configs/riscv-sky130-4kbyte-1rw1r.py b/compiler/example_configs/riscv_sky130_4kbyte_1rw1r.py similarity index 100% rename from compiler/example_configs/riscv-sky130-4kbyte-1rw1r.py rename to compiler/example_configs/riscv_sky130_4kbyte_1rw1r.py diff --git a/compiler/globals.py b/compiler/globals.py index 57f1577b..8a4cff3d 100644 --- a/compiler/globals.py +++ b/compiler/globals.py @@ -18,6 +18,9 @@ import sys import re import copy import importlib +import getpass +import subprocess + VERSION = "1.1.9" NAME = "OpenRAM v{}".format(VERSION) @@ -133,8 +136,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("|==============================================================================|") @@ -154,6 +158,17 @@ def check_versions(): # or, this could be done in each module (e.g. verify, characterizer, etc.) global OPTS + def cmd_exists(cmd): + return subprocess.call("type " + cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) == 0 + + if cmd_exists("coverage"): + OPTS.coverage_exe = "coverage run -p " + elif cmd_exists("python3-coverage"): + OPTS.coverage_exe = "python3-coverage run -p " + else: + OPTS.coverage_exe = "" + debug.warning("Failed to find coverage installation. This can be installed with pip3 install coverage") + try: import coverage OPTS.coverage = 1 @@ -406,7 +421,7 @@ def setup_paths(): # Add all of the subdirs to the python path # These subdirs are modules and don't need # to be added: characterizer, verify - subdirlist = [ item for item in os.listdir(OPENRAM_HOME) if os.path.isdir(os.path.join(OPENRAM_HOME, item)) ] + subdirlist = [item for item in os.listdir(OPENRAM_HOME) if os.path.isdir(os.path.join(OPENRAM_HOME, item))] for subdir in subdirlist: full_path = "{0}/{1}".format(OPENRAM_HOME, subdir) debug.check(os.path.isdir(full_path), @@ -414,6 +429,10 @@ 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()) + 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..4c04cdb0 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 = "/tmp" + # 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/30_openram_back_end_test.py b/compiler/tests/30_openram_back_end_test.py index 7a2cc012..af8773a2 100755 --- a/compiler/tests/30_openram_back_end_test.py +++ b/compiler/tests/30_openram_back_end_test.py @@ -46,12 +46,7 @@ class openram_back_end_test(openram_test): if OPTS.spice_name: options += " -s {}".format(OPTS.spice_name) - # 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 = "coverage run -p {0}/openram.py ".format(OPENRAM_HOME) + exe_name = "{0}{1}/openram.py ".format(OPTS.coverage_exe, OPENRAM_HOME) config_name = "{0}/tests/configs/config_back_end.py".format(OPENRAM_HOME) cmd = "{0} -o {1} -p {2} {3} {4} 2>&1 > {5}/output.log".format(exe_name, out_file, diff --git a/compiler/tests/30_openram_front_end_test.py b/compiler/tests/30_openram_front_end_test.py index d5089bbc..b80c6d7d 100755 --- a/compiler/tests/30_openram_front_end_test.py +++ b/compiler/tests/30_openram_front_end_test.py @@ -46,12 +46,7 @@ class openram_front_end_test(openram_test): if OPTS.spice_name: options += " -s {}".format(OPTS.spice_name) - # 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 = "coverage run -p {0}/openram.py ".format(OPENRAM_HOME) + exe_name = "{0}{1}/openram.py ".format(OPTS.coverage_exe, OPENRAM_HOME) config_name = "{0}/tests/configs/config_front_end.py".format(OPENRAM_HOME) cmd = "{0} -n -o {1} -p {2} {3} {4} 2>&1 > {5}/output.log".format(exe_name, out_file, diff --git a/compiler/tests/regress.py b/compiler/tests/regress.py index bac5d8e1..878182a5 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,20 +42,74 @@ 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 EBADF: + 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) - -import verify -verify.print_drc_stats() -verify.print_lvs_stats() -verify.print_pex_stats() +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() +# verify.print_pex_stats() sys.exit(not test_result.wasSuccessful()) 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("|==============================================================================|") diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 00000000..2a8879b9 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,6 @@ +scikit-learn>=0.22.2 +coverage>=4.5.2 +scipy>=1.3.3 +numpy>=1.17.4 +python-subunit>=1.4.0 +unittest2>=1.1.0