diff --git a/.github/workflows/openlane_ci.yml b/.github/workflows/openlane_ci.yml
index c567bbaf..9599b421 100644
--- a/.github/workflows/openlane_ci.yml
+++ b/.github/workflows/openlane_ci.yml
@@ -70,7 +70,7 @@ jobs:
else
echo "::set-output name=design_matrix::$(python3 ./.github/test_sets/get_test_matrix.py fastest_test_set)"
fi
- echo "::set-output name=issue_regression_matrix::$(python3 ./run_issue_regressions.py get_matrix)"
+ echo "::set-output name=issue_regression_matrix::$(python3 -m tests get_matrix)"
docker_build_amd64:
name: Docker Build (amd64)
runs-on: ubuntu-20.04
@@ -94,7 +94,7 @@ jobs:
dockerhub_user: ${{ secrets.DOCKERHUB_USER }}
dockerhub_password: ${{ secrets.DOCKERHUB_PASSWORD }}
issue_regression_test:
- name: Regression Test (Issue ${{ matrix.design }})
+ name: Regression Test (Test ${{ matrix.test }})
needs: [docker_build_amd64, pdk_build]
runs-on: ubuntu-20.04
strategy:
@@ -131,7 +131,14 @@ jobs:
run: python3 -m pip install pyyaml
- name: Run Issue Regression Test
- run: cd ${GITHUB_WORKSPACE}/ && make run_issue_regression ISSUE_REGRESSION_DESIGN=${{ matrix.design }}
+ run: cd ${GITHUB_WORKSPACE}/ && make run_issue_regression ISSUE_REGRESSION_DESIGN=${{ matrix.test }}
+
+ - name: Upload Logs
+ if: ${{ always() }}
+ uses: actions/upload-artifact@v2
+ with:
+ name: test_${{ matrix.test }}_logs
+ path: ./test_logs
# Each test has two components: a fast test set and an extended test set.
# The fast test set is run on all PRs, etc. The extended test set runs on schedule.
test:
@@ -169,7 +176,7 @@ jobs:
tar -xf /tmp/sky130A.tar -C $PDK_ROOT/sky130A .
- name: Set PDK Variant
- run: |
+ run: |
echo "PDK=sky130A" >> $GITHUB_ENV
- name: Get Pyyaml
diff --git a/Makefile b/Makefile
index a580c004..f2aa6e53 100644
--- a/Makefile
+++ b/Makefile
@@ -155,12 +155,12 @@ test_design_list:
run_issue_regression:
cd $(OPENLANE_DIR) && \
$(ENV_COMMAND) sh -c "\
- python3 -u run_issue_regressions.py run $(ISSUE_REGRESSION_DESIGN)"
+ python3 -um tests run $(ISSUE_REGRESSION_DESIGN)"
issue_regression_all:
cd $(OPENLANE_DIR) && \
$(ENV_COMMAND) sh -c "\
- python3 -u run_issue_regressions.py run_all"
+ python3 -um tests run_all"
.PHONY: test
test:
diff --git a/README.md b/README.md
index d5a21767..2367cce2 100644
--- a/README.md
+++ b/README.md
@@ -20,9 +20,10 @@ The short version is, to install the OpenLane environment...
> On Windows, install and launch the [Windows Subsystem for Linux](https://docs.microsoft.com/en-us/windows/wsl/install) before doing anything. We recommend and provide instructions for Ubuntu 20.04.
-> On macOS, [get brew](https://brew.sh)
-.
+> On macOS, get [brew](https://brew.sh).
+
1. [Get Docker](https://docs.docker.com/get-docker/) (or a compatible container engine)
+ * On Ubuntu, follow the [Docker post install instructions](https://docs.docker.com/engine/install/linux-postinstall/) after you install Docker.
2. Get Python 3.6 or higher ([macOS](https://formulae.brew.sh/formula/python3) | [Ubuntu](https://packages.ubuntu.com/focal/python3))
* On Ubuntu, you may also need to install venv: `apt-get install python3-venv`, and pip: `apt-get install python3-pip`.
3. Get git ([macOS](https://formulae.brew.sh/formula/git) | [Ubuntu](https://packages.ubuntu.com/focal/git))
diff --git a/configuration/general.tcl b/configuration/general.tcl
index 4786b913..cdc7a437 100755
--- a/configuration/general.tcl
+++ b/configuration/general.tcl
@@ -29,6 +29,7 @@ set ::env(RSZ_USE_OLD_REMOVER) 0
## STA
set ::env(STA_REPORT_POWER) {1}
+set ::env(STA_WRITE_LIB) {1}
## ECO Flow
set ::env(ECO_ENABLE) {0}
diff --git a/docs/source/reference/configuration.md b/docs/source/reference/configuration.md
index 0982ceef..7f06b70c 100644
--- a/docs/source/reference/configuration.md
+++ b/docs/source/reference/configuration.md
@@ -66,6 +66,12 @@ These variables are optional that can be specified in the design configuration f
| `SYNTH_FLAT_TOP` | Specifies whether or not the top level should be flattened during elaboration. 1 = True, 0= False
(Default: `0` )|
| `IO_PCT` | Specifies the percentage of the clock period used in the input/output delays. Ranges from 0 to 1.0.
(Default: `0.2`) |
+### STA
+
+| Variable | Description |
+|-|-|
+| `STA_WRITE_LIB` | Controls whether a timing model is written using OpenROAD OpenSTA after static timing analysis. This is an option as it in its current state, the timing model generation (and the model itself) can be quite buggy.
(Default: `1`) |
+
### Floorplanning
|Variable|Description|
diff --git a/run_issue_regressions.py b/run_issue_regressions.py
deleted file mode 100755
index a19aceb2..00000000
--- a/run_issue_regressions.py
+++ /dev/null
@@ -1,157 +0,0 @@
-#!/usr/bin/env python3
-#
-# Copyright 2022 Arman Avetisyan
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-import os # For checking if file exists
-import json # To serialize the matrix for the CI
-import click # For command line parsing
-import subprocess # For running the flow
-
-# TODO: If command is get designs
-# print(json.dumps({"design": designs}))
-# else if design specified
-#
-
-
-def get_test_cases():
- openlane_dir_relative = os.path.dirname(os.path.relpath(__file__))
- test_dir_relative = os.path.join(openlane_dir_relative, "tests")
- retval = []
- for file in os.listdir(test_dir_relative):
- test_path = os.path.join(test_dir_relative, file)
- if os.path.isdir(test_path):
- retval.append(test_path)
- return retval
-
-
-@click.group()
-def cli():
- pass
-
-
-# Note: Following command is executed outside of OpenRoad, so you can't run the ./flow.tcl
-@click.command("get_matrix")
-def get_matrix():
- print(json.dumps({"design": get_test_cases()}))
-
-
-cli.add_command(get_matrix)
-
-
-@click.command("run")
-@click.argument("test_case")
-def run_test_case_cmd(test_case):
- run_test_case(test_case)
-
-
-cli.add_command(run_test_case_cmd)
-
-
-@click.command("run_all")
-def run_all():
- for test_case in get_test_cases():
- run_test_case(test_case)
- print("Done.")
-
-
-cli.add_command(run_all)
-
-
-def run_test_case(test_case):
- # test_case is the path to design
- # test_case_name is name of design to test
- # It is assumed that every test case is inside designs path
-
- # -------------------------------
- # 0. Calculate names
- # -------------------------------
- result = ""
- test_case_issue_regression_script = test_case + "/issue_regression.py"
- script_exists = os.path.isfile(test_case_issue_regression_script)
- (_, test_case_name) = os.path.split(test_case)
- # print("test_case_name", test_case_name)
- logpath = "./regression_results/issue_regression_" + test_case_name + ".log"
- logpath_check = (
- "./regression_results/issue_regression_" + test_case_name + "_check.log"
- )
- # -------------------------------
- # 1. Run the flow
- # -------------------------------
- try:
- logfile = open(logpath, "w")
- print(f"Running test case: {test_case_name} (log: {logpath})")
- interactive = []
- interactive_file = os.path.join(test_case, "interactive.tcl")
- if os.path.exists(interactive_file):
- interactive = ["-it", "-file", interactive_file]
- result = subprocess.run(
- [
- "./flow.tcl",
- "-design",
- test_case,
- "-tag",
- "issue_regression_run",
- "-run_hooks",
- "-overwrite",
- ]
- + interactive,
- stdout=logfile,
- stderr=subprocess.STDOUT,
- check=True,
- )
- except subprocess.CalledProcessError as err:
- # -------------------------------
- # 2.1 If run was not successful, then run the issue_regression.py which
- # will check if the fail was expected or not and it will also check the logs
- # for errors to match
- # 2.2 If issue_regression.py does not exist then it's enough for this design to pass LVS
- # -------------------------------
- if script_exists:
- result = err
- else:
- print(
- f"./flow.tcl failed and issue_regression.py does not exist, therefore test case {test_case} failed."
- )
- raise err
- # -------------------------------
- # 3. Run the issue_regression.py.
- # -------------------------------
- if script_exists:
- print("Running post-run hook...")
- logfile_check = open(logpath_check, "w")
- try:
- subprocess.run(
- [
- "openroad",
- "-python",
- test_case_issue_regression_script,
- test_case + "/runs/issue_regression_run",
- str(result.returncode),
- ],
- check=True,
- stdout=logfile_check,
- stderr=subprocess.STDOUT,
- )
- except subprocess.CalledProcessError as err:
- # -------------------------------
- # 4. Run the issue_regression.py. If it errors out, log it and then raise an error
- # -------------------------------
- print(f"{test_case_name} failed: see '{logpath_check}'.")
- raise err
- print(f"{test_case_name} completed successfully.")
-
-
-if __name__ == "__main__":
- cli()
diff --git a/scripts/odbpy/defutil.py b/scripts/odbpy/defutil.py
index e98e13cd..3fab3c40 100644
--- a/scripts/odbpy/defutil.py
+++ b/scripts/odbpy/defutil.py
@@ -331,8 +331,8 @@ cli.add_command(relocate_pins_command)
help="Regular expression to match for components to be removed. (Default: '^.+$', matches all strings.)",
)
@click_odb
-def remove_components(rx, instance_name, reader):
- matcher = re.compile(instance_name if rx else f"^{re.escape(instance_name)}$")
+def remove_components(rx_str, reader):
+ matcher = re.compile(rx_str)
instances = reader.block.getInsts()
for instance in instances:
name = instance.getName()
@@ -359,12 +359,12 @@ cli.add_command(remove_components)
help="Adds a further condition to only remove empty nets (i.e. unconnected nets).",
)
@click_odb
-def remove_nets(rx, net_name, empty_only, reader):
- matcher = re.compile(net_name if rx else f"^{re.escape(net_name)}$")
+def remove_nets(rx_str, empty_only, reader):
+ matcher = re.compile(rx_str)
nets = reader.block.getNets()
for net in nets:
name = net.getName()
- name_m = matcher.search(name)
+ name_m = matcher.match(name)
if name_m is not None:
if empty_only and len(net.getITerms()) > 0:
continue
@@ -388,8 +388,8 @@ cli.add_command(remove_nets)
help="Regular expression to match for components to be removed. (Default: '^.+$', matches all strings.)",
)
@click_odb
-def remove_pins(rx, pin_name, reader):
- matcher = re.compile(pin_name if rx else f"^{re.escape(pin_name)}$")
+def remove_pins(rx_str, reader):
+ matcher = re.compile(rx_str)
pins = reader.block.getBTerms()
for pin in pins:
name = pin.getName()
diff --git a/scripts/tcl_commands/all.tcl b/scripts/tcl_commands/all.tcl
index 4fdf0b13..29efd3a3 100755
--- a/scripts/tcl_commands/all.tcl
+++ b/scripts/tcl_commands/all.tcl
@@ -906,9 +906,13 @@ proc save_views {args} {
{-def_path optional}
{-gds_path optional}
{-verilog_path optional}
+ {-nl_path optional}
+ {-pnl_path optional}
{-spice_path optional}
{-sdf_path optional}
+ {-mc_sdf_dir optional}
{-spef_path optional}
+ {-mc_spef_dir optional}
{-sdc_path optional}
{-lib_path optional}
{-save_path optional}
@@ -916,6 +920,18 @@ proc save_views {args} {
set flags {}
parse_key_args "save_views" args arg_values $options flags_map $flags
+
+
+ if { [info exists arg_values(-verilog_path)] } {
+ puts_warn "The argument -verilog_path is ambiguous and deprecated."
+ puts_warn "You may use either -nl_path for unpowered or -pnl_path for powered netlists."
+
+ if { ![info exists arg_values(-pnl_path)] } {
+ puts_warn "Setting -pnl_path to '$arg_values(-verilog_path)'..."
+ set arg_values(-pnl_path) $arg_values(-verilog_path)
+ }
+ }
+
if { [info exists arg_values(-save_path)]\
&& $arg_values(-save_path) != "" } {
set path "[file normalize $arg_values(-save_path)]"
@@ -964,11 +980,24 @@ proc save_views {args} {
file copy -force $arg_values(-gds_path) $destination/$::env(DESIGN_NAME).gds
}
}
- if { [info exists arg_values(-verilog_path)] } {
+
+ if { [info exists arg_values(-nl_path)] } {
set destination $path/verilog/gl
file mkdir $destination
- if { [file exists $arg_values(-verilog_path)] } {
- file copy -force $arg_values(-verilog_path) $destination/$::env(DESIGN_NAME).v
+ if { [file exists $arg_values(-nl_path)] } {
+ set nl_file_path $destination/$::env(DESIGN_NAME).nl.v
+ set f [open $nl_file_path w]
+ puts $f "// This is the unpowered netlist."
+ puts $f [cat $arg_values(-nl_path)]
+ close $f
+ }
+ }
+
+ if { [info exists arg_values(-pnl_path)] } {
+ set destination $path/verilog/gl
+ file mkdir $destination
+ if { [file exists $arg_values(-pnl_path)] } {
+ file copy -force $arg_values(-pnl_path) $destination/$::env(DESIGN_NAME).v
}
}
@@ -988,6 +1017,14 @@ proc save_views {args} {
}
}
+ if { [info exists arg_values(-mc_spef_dir)] } {
+ set destination $path/spef/multicorner
+ if { [file exists $arg_values(-mc_spef_dir)] } {
+ exec rm -rf $destination
+ file copy -force $arg_values(-mc_spef_dir) $destination
+ }
+ }
+
if { [info exists arg_values(-sdf_path)] } {
set destination $path/sdf
file mkdir $destination
@@ -996,6 +1033,16 @@ proc save_views {args} {
}
}
+
+ if { [info exists arg_values(-mc_sdf_dir)] } {
+ set destination $path/sdf/multicorner
+ if { [file exists $arg_values(-mc_sdf_dir)] } {
+ exec rm -rf $destination
+ file copy -force $arg_values(-mc_sdf_dir) $destination
+ }
+ }
+
+
if { [info exists arg_values(-sdc_path)] } {
set destination $path/sdc
file mkdir $destination
@@ -1175,15 +1222,24 @@ proc save_final_views {args} {
# Guaranteed to have default values
lappend arg_list -def_path $::env(CURRENT_DEF)
- lappend arg_list -verilog_path $::env(CURRENT_NETLIST)
+ lappend arg_list -nl_path $::env(CURRENT_NETLIST)
# Not guaranteed to have default values
+ if { [info exists ::env(CURRENT_POWERED_NETLIST)] } {
+ lappend arg_list -pnl_path $::env(CURRENT_POWERED_NETLIST)
+ }
if { [info exists ::env(CURRENT_SPEF)] } {
lappend arg_list -spef_path $::env(CURRENT_SPEF)
}
+ if { [info exists ::env(MC_SPEF_DIR)]} {
+ lappend arg_list -mc_spef_dir $::env(MC_SPEF_DIR)
+ }
if { [info exists ::env(CURRENT_SDF)] } {
lappend arg_list -sdf_path $::env(CURRENT_SDF)
}
+ if { [info exists ::env(MC_SDF_DIR)]} {
+ lappend arg_list -mc_sdf_dir $::env(MC_SDF_DIR)
+ }
if { [info exists ::env(CURRENT_SDC)] } {
lappend arg_list -sdc_path $::env(CURRENT_SDC)
}
@@ -1191,6 +1247,7 @@ proc save_final_views {args} {
lappend arg_list -lib_path $::env(CURRENT_LIB)
}
+
# Add the path if it exists...
if { [info exists arg_values(-save_path) ] } {
lappend arg_list -save_path $arg_values(-save_path)
diff --git a/scripts/tcl_commands/sta.tcl b/scripts/tcl_commands/sta.tcl
index a35e67d4..638063d2 100644
--- a/scripts/tcl_commands/sta.tcl
+++ b/scripts/tcl_commands/sta.tcl
@@ -47,20 +47,26 @@ proc run_sta {args} {
puts_info "Running $corner_prefix Static Timing Analysis$process_corner_postfix (log: [relpath . $log])..."
set ::env(STA_PRE_CTS) $pre_cts
+ set lib_option ""
+ if { $::env(STA_WRITE_LIB) } {
+ set lib_option "lib"
+ }
if {[info exists ::env(CLOCK_PORT)]} {
if { $multi_corner == 1 } {
run_openroad_script $::env(SCRIPTS_DIR)/openroad/sta_multi_corner.tcl \
-indexed_log $log\
- -save "to=$arg_values(-save_to),noindex,lib,sdf"\
+ -save "to=$arg_values(-save_to),noindex,sdf,$lib_option"\
-no_update_current
- unset ::env(SAVE_LIB)
+ if { $::env(STA_WRITE_LIB) } {
+ unset ::env(SAVE_LIB)
+ }
unset ::env(SAVE_SDF)
} else {
run_openroad_script $::env(SCRIPTS_DIR)/openroad/sta.tcl \
-indexed_log $log\
- -save "to=$arg_values(-save_to),noindex,lib,sdf"
+ -save "to=$arg_values(-save_to),noindex,sdf,$lib_option"
}
} else {
puts_warn "CLOCK_PORT is not set. STA will be skipped..."
@@ -89,13 +95,20 @@ proc run_parasitics_sta {args} {
set backup_sdc_variable $::env(CURRENT_SDC)
set ::env(CURRENT_SDC) $::env(RCX_SDC_FILE)
+ set mca_results_dir "$arg_values(-out_directory)/mca"
+ set ::env(MC_SPEF_DIR) "$mca_results_dir/spef"
+ set ::env(MC_SDF_DIR) "$mca_results_dir/sdf"
+
+ exec rm -rf $::env(MC_SPEF_DIR)
+ exec rm -rf $::env(MC_SDF_DIR)
+
foreach {process_corner lef ruleset} {
min MERGED_LEF_MIN RCX_RULES_MIN
max MERGED_LEF_MAX RCX_RULES_MAX
nom MERGED_LEF RCX_RULES
} {
if { [info exists ::env($lef)] } {
- set directory "$arg_values(-out_directory)/process_corner_$process_corner"
+ set directory "$mca_results_dir/process_corner_$process_corner"
file mkdir $directory
run_spef_extraction\
@@ -122,6 +135,13 @@ proc run_parasitics_sta {args} {
set ::env(LAST_TIMING_REPORT_TAG) [index_file $::env(signoff_reports)/rcx_sta]
}
+
+ file mkdir $::env(MC_SPEF_DIR)
+ file copy -force "$directory/$::env(DESIGN_NAME).spef" "$::env(MC_SPEF_DIR)/$::env(DESIGN_NAME).$process_corner.spef"
+
+ set sdf_folder "$::env(MC_SDF_DIR)/$process_corner"
+ file mkdir $sdf_folder
+ file copy -force {*}[glob $directory/$::env(DESIGN_NAME).*.sdf] $sdf_folder
}
}
diff --git a/scripts/utils/deflef_utils.tcl b/scripts/utils/deflef_utils.tcl
index 47a361ae..a9192779 100755
--- a/scripts/utils/deflef_utils.tcl
+++ b/scripts/utils/deflef_utils.tcl
@@ -146,7 +146,7 @@ proc remove_nets {args} {
lappend arg_list -output $arg_values(-output)
if { [info exists arg_values(-rx)] } {
- lappend arg_list --rx $arg_values(-rx)
+ lappend arg_list --match $arg_values(-rx)
}
if { [info exists flags_map(-empty)] } {
@@ -164,10 +164,16 @@ proc remove_empty_nets {args} {
proc remove_components {args} {
set options {
{-input required}
+ {-output optional}
}
set flags {}
parse_key_args "remove_components" args arg_values $options flags_map $flags
+
+ set_if_unset arg_values(-output) $arg_values(-input)
+
+
manipulate_layout $::env(SCRIPTS_DIR)/odbpy/defutil.py remove_components \
+ -output $arg_values(-output) \
-input $arg_values(-input)
}
diff --git a/scripts/utils/utils.tcl b/scripts/utils/utils.tcl
index 254cd764..dc71b930 100755
--- a/scripts/utils/utils.tcl
+++ b/scripts/utils/utils.tcl
@@ -509,7 +509,7 @@ proc run_tcl_script {args} {
}
} elseif { $element == "noindex" } {
set index 0
- } else {
+ } elseif { $element != "" } {
set extension $element
if { $element == "netlist" } {
diff --git a/tests/1007/interactive.tcl b/tests/1007/interactive.tcl
index 8e1a29c3..5f37101f 100644
--- a/tests/1007/interactive.tcl
+++ b/tests/1007/interactive.tcl
@@ -1,6 +1,6 @@
package require openlane;
-prep -design tests/1007
+prep -design $::env(TEST_DIR)
set ::env(CURRENT_ODB) $::env(DESIGN_DIR)/in.odb
diff --git a/tests/1413/.gitignore b/tests/1413/.gitignore
new file mode 100644
index 00000000..9c353fa7
--- /dev/null
+++ b/tests/1413/.gitignore
@@ -0,0 +1,2 @@
+out.def
+out.odb
\ No newline at end of file
diff --git a/tests/1413/config.tcl b/tests/1413/config.tcl
new file mode 100644
index 00000000..967e0ed6
--- /dev/null
+++ b/tests/1413/config.tcl
@@ -0,0 +1 @@
+set ::env(DESIGN_NAME) inverter
\ No newline at end of file
diff --git a/tests/1413/hooks/post_run.py b/tests/1413/hooks/post_run.py
new file mode 100644
index 00000000..722c659f
--- /dev/null
+++ b/tests/1413/hooks/post_run.py
@@ -0,0 +1,26 @@
+# Copyright 2022 Efabless Corporation
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import os
+import odb
+
+db = odb.dbDatabase.create()
+odb.read_db(db, os.getenv("CURRENT_ODB"))
+nets = db.getChip().getBlock().getNets()
+pins = db.getChip().getBlock().getBTerms()
+instances = db.getChip().getBlock().getInsts()
+
+assert [instance.getName() for instance in instances] == []
+assert [pin.getName() for pin in pins] == []
+assert [net.getName() for net in nets] == ["out"]
diff --git a/tests/1413/in.odb b/tests/1413/in.odb
new file mode 120000
index 00000000..6e2b2992
--- /dev/null
+++ b/tests/1413/in.odb
@@ -0,0 +1 @@
+../1007/in.odb
\ No newline at end of file
diff --git a/tests/1413/interactive.tcl b/tests/1413/interactive.tcl
new file mode 100644
index 00000000..dcf5c272
--- /dev/null
+++ b/tests/1413/interactive.tcl
@@ -0,0 +1,18 @@
+package require openlane;
+
+prep -design $::env(TEST_DIR)
+
+set ::env(CURRENT_ODB) $::env(DESIGN_DIR)/in.odb
+
+set save_odb $::env(DESIGN_DIR)/out.odb
+
+# Remove pins first: nets cannot be removed if they are associated with a pin
+remove_components -input $::env(CURRENT_ODB) -output $save_odb
+remove_pins -input $save_odb
+remove_nets -rx {^in$} -input $save_odb
+
+set ::env(CURRENT_ODB) $save_odb
+
+exec $::env(OPENROAD_BIN) -python $::env(DESIGN_DIR)/hooks/post_run.py
+
+puts_info "Done."
\ No newline at end of file
diff --git a/tests/912/issue_regression.py b/tests/912/issue_regression.py
index 157fa566..75971463 100644
--- a/tests/912/issue_regression.py
+++ b/tests/912/issue_regression.py
@@ -11,12 +11,21 @@
# See the License for the specific language governing permissions and
# limitations under the License.
+import os
import sys
+import glob
+glob_str = os.path.join(sys.argv[1], "**", "*apply_def_template.log")
+glob_result = glob.glob(glob_str, recursive=True)
+if len(glob_result) < 1:
+ print("apply_def_template log not found.", file=sys.stderr)
+ exit(os.EX_DATAERR)
-with open(sys.argv[1] + "/openlane.log") as f:
+log = glob_result[0]
+
+with open(log) as f:
content = f.read()
- print(content)
+ print(f"-- {log} --\n{content}\n-- --")
if (
content.find(
"Pin manufacturing_grid_missaligned_pin's coordinate 9861 does not lie on the manufacturing grid."
@@ -28,6 +37,7 @@ with open(sys.argv[1] + "/openlane.log") as f:
)
is not -1
):
- sys.exit(0)
+ print("OK")
else:
- sys.exit("Didn't match the log")
+ print("Expected errors were not found in the log.")
+ exit(os.EX_DATAERR)
diff --git a/tests/__main__.py b/tests/__main__.py
new file mode 100755
index 00000000..dd847708
--- /dev/null
+++ b/tests/__main__.py
@@ -0,0 +1,178 @@
+#!/usr/bin/env python3
+#
+# Copyright 2022 Efabless Corporation
+# Copyright 2022 Arman Avetisyan
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+import os
+import sys
+import json
+import click
+import pathlib
+import subprocess
+
+openlane_root = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
+
+
+def mkdirp(path):
+ return pathlib.Path(path).mkdir(parents=True, exist_ok=True)
+
+
+def get_test_cases():
+ test_dir = os.path.join(openlane_root, "tests")
+ test_cases = []
+ for test in os.listdir(test_dir):
+ test_path = os.path.join(test_dir, test)
+ if test == "__pycache__" or not os.path.isdir(test_path):
+ continue
+ test_cases.append(test)
+ return test_cases
+
+
+@click.group()
+def cli():
+ pass
+
+
+# Note: Following command is executed outside of OpenRoad, so you can't run the ./flow.tcl
+@click.command("get_matrix")
+def get_matrix():
+ print(json.dumps({"test": get_test_cases()}))
+
+
+cli.add_command(get_matrix)
+
+
+@click.command("run")
+@click.argument("test_case")
+def run_test_case_cmd(test_case):
+ run_test_case(test_case)
+
+
+cli.add_command(run_test_case_cmd)
+
+
+@click.command("run_all")
+def run_all():
+ for test_case in get_test_cases():
+ run_test_case(test_case)
+ print("Done.")
+
+
+cli.add_command(run_all)
+
+
+def run_test_case(test_case):
+ # test_case is the path to design
+ # if the path does not exist, openlane_root/tests/ will also be checked
+
+ # -------------------------------
+ # 0. Extract names and paths
+ # -------------------------------
+ result = ""
+ if not os.path.isdir(test_case):
+ original_test_case = test_case
+ test_case = os.path.join(openlane_root, "tests", test_case)
+ if not os.path.isdir(test_case):
+ print(f"Test case {original_test_case} not found.", file=sys.stderr)
+ exit(os.EX_DATAERR)
+
+ test_case = os.path.abspath(test_case)
+
+ test_case_issue_regression_script = os.path.join(test_case, "issue_regression.py")
+ script_exists = os.path.isfile(test_case_issue_regression_script)
+ (_, test_case_name) = os.path.split(test_case)
+
+ log_dir = os.path.join(openlane_root, "test_logs")
+ mkdirp(log_dir)
+ run_log_path = os.path.join(log_dir, f"{test_case_name}.log")
+ check_log_path = os.path.join(log_dir, f"{test_case_name}.check.log")
+
+ # -------------------------------
+ # 1. Run the flow
+ # -------------------------------
+ try:
+ with open(run_log_path, "w") as log:
+ print(
+ f"Running test case: {test_case_name}... (log: '{os.path.relpath(run_log_path, '.')}')"
+ )
+ interactive = []
+ interactive_file = os.path.join(test_case, "interactive.tcl")
+ if os.path.exists(interactive_file):
+ interactive = ["-it", "-file", interactive_file]
+ env = os.environ.copy()
+ env["TEST_DIR"] = test_case
+ result = subprocess.run(
+ [
+ "flow.tcl",
+ "-verbose",
+ "99",
+ "-design",
+ test_case,
+ "-tag",
+ "issue_regression_run",
+ "-run_hooks",
+ "-overwrite",
+ ]
+ + interactive,
+ stdout=log,
+ stderr=log,
+ check=True,
+ env=env,
+ )
+ except subprocess.CalledProcessError as err:
+ # -------------------------------
+ # 2.1 If run was not successful, then run the issue_regression.py which
+ # will check if the fail was expected or not and it will also check the logs
+ # for errors to match
+ # 2.2 If issue_regression.py does not exist then it's enough for this design to pass LVS
+ # -------------------------------
+ if script_exists:
+ result = err
+ else:
+ print(
+ f"flow.tcl failed and issue_regression.py does not exist, therefore test case {test_case} failed."
+ )
+ exit(os.EX_DATAERR)
+ # -------------------------------
+ # 3. Run the issue_regression.py.
+ # -------------------------------
+ if script_exists:
+ try:
+ with open(check_log_path, "w") as log:
+ print(
+ f"Running post-run check... (log: '{os.path.relpath(check_log_path, '.')}')"
+ )
+ subprocess.run(
+ [
+ "openroad",
+ "-python",
+ test_case_issue_regression_script,
+ os.path.join(test_case, "runs", "issue_regression_run"),
+ str(result.returncode),
+ ],
+ check=True,
+ stdout=log,
+ stderr=log,
+ )
+ except subprocess.CalledProcessError:
+ # -------------------------------
+ # 4. Run the issue_regression.py. If it errors out, log it and then raise an error
+ # -------------------------------
+ print("Post-run check has failed.")
+ exit(os.EX_DATAERR)
+ print(f"{test_case_name} completed successfully.")
+
+
+if __name__ == "__main__":
+ cli()