mirror of
https://github.com/The-OpenROAD-Project/OpenLane.git
synced 2026-05-29 00:23:55 +08:00
Add Issue-Specific CI (#902)
This adds an issue-specific CI that can be used to test specific regressions rather than running entire flows. Also, a partial solution for #892.
This commit is contained in:
53
.github/workflows/openlane_ci.yml
vendored
53
.github/workflows/openlane_ci.yml
vendored
@@ -18,6 +18,7 @@ jobs:
|
||||
runs-on: ubuntu-20.04
|
||||
outputs:
|
||||
matrix: ${{ steps.set-matrix.outputs.matrix }}
|
||||
issue_regression_matrix: ${{ steps.set-matrix.outputs.issue_regression_matrix }}
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
@@ -96,7 +97,57 @@ jobs:
|
||||
else
|
||||
echo "::set-output name=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)"
|
||||
|
||||
issue_regression_test:
|
||||
needs: docker_build
|
||||
runs-on: ubuntu-20.04
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix: ${{ fromJSON(needs.docker_build.outputs.issue_regression_matrix) }}
|
||||
name: Test Issue Regression ${{ matrix.design }}
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
# EXPORT BLOCK
|
||||
- name: Export Repo URL
|
||||
run: echo "REPO_URL=git://github.com/${{ github.repository }}.git" >> $GITHUB_ENV
|
||||
|
||||
- name: Export PDK ROOT
|
||||
run: echo "PDK_ROOT=/usr/local/pdk" >> $GITHUB_ENV
|
||||
|
||||
- name: Export Branch Name
|
||||
run: echo "BRANCH_NAME=${GITHUB_REF##*/}" >> $GITHUB_ENV
|
||||
|
||||
- name: Export Temp Image Name
|
||||
run: echo "OPENLANE_IMAGE_NAME=openlane:intermediate" >> $GITHUB_ENV
|
||||
# END EXPORT BLOCK
|
||||
|
||||
- name: Download Docker Image
|
||||
uses: actions/download-artifact@v2
|
||||
with:
|
||||
name: docker-image
|
||||
path: /tmp
|
||||
|
||||
- name: Import Docker Image
|
||||
run: docker load --input /tmp/image.tar
|
||||
|
||||
- name: Download PDK Tarball
|
||||
uses: actions/download-artifact@v2
|
||||
with:
|
||||
name: pdk-tarball
|
||||
path: /tmp
|
||||
|
||||
- name: Unpack PDK Tarball
|
||||
run: |
|
||||
sudo mkdir -p ${{ env.PDK_ROOT }}/sky130A
|
||||
sudo chown -R $USER:$USER ${{ env.PDK_ROOT }}
|
||||
tar -xf /tmp/sky130A.tar -C $PDK_ROOT/sky130A .
|
||||
|
||||
- name: Get Pyyaml
|
||||
run: python3 -m pip install pyyaml
|
||||
|
||||
- name: Run Issue Regression Test
|
||||
run: cd ${GITHUB_WORKSPACE}/ && make run_issue_regression ISSUE_REGRESSION_DESIGN=${{ matrix.design }}
|
||||
# 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:
|
||||
@@ -159,7 +210,7 @@ jobs:
|
||||
|
||||
cleanup_and_deploy:
|
||||
name: Cleanup (and Possibly Deployment)
|
||||
needs: test
|
||||
needs: [test, issue_regression_test]
|
||||
if: always()
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
|
||||
10
Makefile
10
Makefile
@@ -119,6 +119,16 @@ test_design_list:
|
||||
--benchmark $(BENCHMARK)\
|
||||
$(DESIGN_LIST)\
|
||||
"
|
||||
# -u is needed, as the python buffers the stdout, so no output is generated
|
||||
run_issue_regression:
|
||||
cd $(OPENLANE_DIR) && \
|
||||
$(ENV_COMMAND) sh -c "\
|
||||
python3 -u run_issue_regressions.py run $(ISSUE_REGRESSION_DESIGN)"
|
||||
|
||||
issue_regression_all:
|
||||
cd $(OPENLANE_DIR) && \
|
||||
$(ENV_COMMAND) sh -c "\
|
||||
python3 -u run_issue_regressions.py run_all"
|
||||
|
||||
.PHONY: test
|
||||
test:
|
||||
|
||||
@@ -321,6 +321,8 @@ python3 run_designs.py --tag test --threads 3 spm xtea md5 aes256
|
||||
|
||||
For more information on how to run this script, refer to this [file][21]
|
||||
|
||||
OpenLane also has flow for issue regression testing. Refer to this [document][38].
|
||||
|
||||
For more information on design configurations, how to update them, and the need for an exploration for each design, refer to this [file](./designs/README.md)
|
||||
|
||||
# Hardening Macros
|
||||
@@ -406,3 +408,5 @@ To check the original author list of OpenLane, check [this][33].
|
||||
[35]: https://github.com/KLayout/klayout
|
||||
[36]: https://github.com/cuhk-eda/cu-gr
|
||||
[37]: https://github.com/The-OpenROAD-Project/OpenROAD/tree/master/src/rcx
|
||||
[38]: ./docs/source/issue_regression_tests.md
|
||||
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
VERSION 5.8 ;
|
||||
DIVIDERCHAR "/" ;
|
||||
BUSBITCHARS "[]" ;
|
||||
DESIGN def_test ;
|
||||
UNITS DISTANCE MICRONS 1000 ;
|
||||
DIEAREA ( 0 0 ) ( 400000 400000 ) ;
|
||||
PINS 2 ;
|
||||
- in + NET in + DIRECTION INPUT + USE SIGNAL
|
||||
+ PORT
|
||||
+ LAYER met2 ( -140 -2000 ) ( 140 2000 )
|
||||
+ PLACED ( 1000 350000 ) N ;
|
||||
- out + NET out + DIRECTION OUTPUT + USE SIGNAL
|
||||
+ PORT
|
||||
+ LAYER met2 ( -140 -2000 ) ( 140 2000 )
|
||||
+ PLACED ( 400 2000 ) N ;
|
||||
END PINS
|
||||
NETS 2 ;
|
||||
- in ( PIN in ) ( _0_ A ) + USE SIGNAL ;
|
||||
- out ( PIN out ) ( _0_ Y ) + USE SIGNAL ;
|
||||
END NETS
|
||||
END DESIGN
|
||||
33
designs/issue_892_def_test/def_test.def
Normal file
33
designs/issue_892_def_test/def_test.def
Normal file
@@ -0,0 +1,33 @@
|
||||
VERSION 5.8 ;
|
||||
DIVIDERCHAR "/" ;
|
||||
BUSBITCHARS "[]" ;
|
||||
DESIGN def_test ;
|
||||
UNITS DISTANCE MICRONS 1000 ;
|
||||
DIEAREA ( 0 0 ) ( 400000 400000 ) ;
|
||||
PINS 4 ;
|
||||
- in + NET in + DIRECTION INPUT + USE SIGNAL
|
||||
+ PORT
|
||||
+ LAYER met2 ( -140 -2000 ) ( 140 2000 )
|
||||
+ PLACED ( 1000 350000 ) N ;
|
||||
- out + NET out + DIRECTION OUTPUT + USE SIGNAL
|
||||
+ PORT
|
||||
+ LAYER met2 ( -140 -2000 ) ( 140 2000 )
|
||||
+ PLACED ( 400 2000 ) N ;
|
||||
- tied_to_zero + NET zero_ + DIRECTION OUTPUT + USE SIGNAL
|
||||
+ PORT
|
||||
+ LAYER met2 ( -140 -2000 ) ( 140 2000 )
|
||||
+ PLACED ( 4000 2000 ) N ;
|
||||
- manufacturing_grid_missaligned_pin + NET manufacturing_grid_missaligned_pin + DIRECTION OUTPUT + USE SIGNAL
|
||||
+ PORT
|
||||
+ LAYER met2 ( -140 -2000 ) ( 140 2000 )
|
||||
+ PLACED ( 10000 2000 ) N ;
|
||||
- VPWR + NET VPWR + DIRECTION INOUT + USE POWER
|
||||
+ PORT
|
||||
+ LAYER met2 ( -140 -2000 ) ( 140 2000 )
|
||||
+ PLACED ( 20000 2000 ) N ;
|
||||
- VGND + NET VGND + DIRECTION INOUT + USE GROUND
|
||||
+ PORT
|
||||
+ LAYER met2 ( -140 -2000 ) ( 140 2000 )
|
||||
+ PLACED ( 25000 2000 ) N ;
|
||||
END PINS
|
||||
END DESIGN
|
||||
@@ -9,9 +9,6 @@ import odb
|
||||
import os
|
||||
import sys
|
||||
|
||||
print(sys.argv)
|
||||
print(os.environ)
|
||||
|
||||
|
||||
def extract_pins(db, def_file):
|
||||
odb.read_lef(db, os.environ["TECH_LEF"])
|
||||
@@ -24,19 +21,28 @@ def extract_pins(db, def_file):
|
||||
|
||||
result_data = {}
|
||||
for net in nets:
|
||||
name = net.getName()
|
||||
print("Net: " + name)
|
||||
net_name = net.getName()
|
||||
# BTerms = PINS, if it has a pin we need to keep the net
|
||||
bterms = net.getBTerms()
|
||||
if len(bterms) > 0:
|
||||
for port in bterms:
|
||||
if (port.getSigType() == "POWER") or (port.getSigType() == "GROUND"):
|
||||
# Ignore power pins
|
||||
continue
|
||||
name = port.getName()
|
||||
print("Port: " + name)
|
||||
|
||||
bbox = port.getBBox()
|
||||
result_data[name] = bbox
|
||||
print("ll: " + " ".join(str(e) for e in bbox.ll()))
|
||||
print("ur: " + " ".join(str(e) for e in bbox.ur()))
|
||||
print(
|
||||
"Net:",
|
||||
net_name,
|
||||
"Port:",
|
||||
name,
|
||||
"ll: ",
|
||||
" ".join(str(e) for e in bbox.ll()),
|
||||
"ur: ",
|
||||
" ".join(str(e) for e in bbox.ur()),
|
||||
)
|
||||
return result_data
|
||||
|
||||
|
||||
@@ -67,4 +73,20 @@ for k, v in ref_data.items():
|
||||
v.ur() == result_data[k].ur()
|
||||
), f"For pin {k} upper right rectangle point {result_data[k].ur()} does not match {v.ur()}"
|
||||
|
||||
assert (
|
||||
result_db.getTech().getManufacturingGrid()
|
||||
== ref_db.getTech().getManufacturingGrid()
|
||||
)
|
||||
assert (
|
||||
result_db.getTech().getDbUnitsPerMicron() == ref_db.getTech().getDbUnitsPerMicron()
|
||||
)
|
||||
assert (
|
||||
result_db.getChip().getBlock().getDieArea().ur()
|
||||
== ref_db.getChip().getBlock().getDieArea().ur()
|
||||
)
|
||||
assert (
|
||||
result_db.getChip().getBlock().getDieArea().ll()
|
||||
== ref_db.getChip().getBlock().getDieArea().ll()
|
||||
)
|
||||
|
||||
sys.exit(0)
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2020 Matt Venn
|
||||
// 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.
|
||||
@@ -15,7 +15,12 @@
|
||||
`default_nettype none
|
||||
module def_test (
|
||||
input wire in,
|
||||
output out );
|
||||
output out,
|
||||
output tied_to_zero,
|
||||
output manufacturing_grid_missaligned_pin
|
||||
);
|
||||
// We tie this to one, so if def's pin is tied to zero, it will LVS error
|
||||
assign tied_to_zero = 1;
|
||||
|
||||
assign out = !in;
|
||||
|
||||
1
designs/issue_912_def_test_missaligned/config.tcl
Symbolic link
1
designs/issue_912_def_test_missaligned/config.tcl
Symbolic link
@@ -0,0 +1 @@
|
||||
../issue_892_def_test/config.tcl
|
||||
33
designs/issue_912_def_test_missaligned/def_test.def
Normal file
33
designs/issue_912_def_test_missaligned/def_test.def
Normal file
@@ -0,0 +1,33 @@
|
||||
VERSION 5.8 ;
|
||||
DIVIDERCHAR "/" ;
|
||||
BUSBITCHARS "[]" ;
|
||||
DESIGN def_test ;
|
||||
UNITS DISTANCE MICRONS 1000 ;
|
||||
DIEAREA ( 0 0 ) ( 400000 400000 ) ;
|
||||
PINS 4 ;
|
||||
- in + NET in + DIRECTION INPUT + USE SIGNAL
|
||||
+ PORT
|
||||
+ LAYER met2 ( -140 -2000 ) ( 140 2000 )
|
||||
+ PLACED ( 1000 350000 ) N ;
|
||||
- out + NET out + DIRECTION OUTPUT + USE SIGNAL
|
||||
+ PORT
|
||||
+ LAYER met2 ( -140 -2000 ) ( 140 2000 )
|
||||
+ PLACED ( 400 2000 ) N ;
|
||||
- tied_to_zero + NET zero_ + DIRECTION OUTPUT + USE SIGNAL
|
||||
+ PORT
|
||||
+ LAYER met2 ( -140 -2000 ) ( 140 2000 )
|
||||
+ PLACED ( 4000 2000 ) N ;
|
||||
- manufacturing_grid_missaligned_pin + NET manufacturing_grid_missaligned_pin + DIRECTION OUTPUT + USE SIGNAL
|
||||
+ PORT
|
||||
+ LAYER met2 ( -140 -2000 ) ( 140 2000 )
|
||||
+ PLACED ( 10001 2000 ) N ;
|
||||
- VPWR + NET VPWR + DIRECTION INOUT + USE POWER
|
||||
+ PORT
|
||||
+ LAYER met2 ( -140 -2000 ) ( 140 2000 )
|
||||
+ PLACED ( 20000 2000 ) N ;
|
||||
- VGND + NET VGND + DIRECTION INOUT + USE GROUND
|
||||
+ PORT
|
||||
+ LAYER met2 ( -140 -2000 ) ( 140 2000 )
|
||||
+ PLACED ( 25000 2000 ) N ;
|
||||
END PINS
|
||||
END DESIGN
|
||||
32
designs/issue_912_def_test_missaligned/issue_regression.py
Normal file
32
designs/issue_912_def_test_missaligned/issue_regression.py
Normal file
@@ -0,0 +1,32 @@
|
||||
# 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 sys
|
||||
|
||||
|
||||
with open(sys.argv[1] + "/openlane.log") as f:
|
||||
content = f.read()
|
||||
if (
|
||||
content.find(
|
||||
"Pin coordinate 9861 for pin manufacturing_grid_missaligned_pin does not match the manufacturing grid"
|
||||
)
|
||||
is not -1
|
||||
) and (
|
||||
content.find(
|
||||
"Pin coordinate 10141 for pin manufacturing_grid_missaligned_pin does not match the manufacturing grid"
|
||||
)
|
||||
is not -1
|
||||
):
|
||||
sys.exit(0)
|
||||
else:
|
||||
sys.exit("Didn't match the log")
|
||||
1
designs/issue_912_def_test_missaligned/src
Symbolic link
1
designs/issue_912_def_test_missaligned/src
Symbolic link
@@ -0,0 +1 @@
|
||||
../issue_892_def_test/src
|
||||
@@ -1,5 +1,8 @@
|
||||
# Issue regression tests
|
||||
Issue regression tests are used to test issues that happened in the past, that might return. Regression testing also allows to extend the options/configs that is not covered currently.
|
||||
Issue regression tests are used to test issues that happened in the past, that might return by changes introduced in the future. Regression testing also allows to extend the options/configs that is not covered currently. To run issue regression flow execute `make issue_regression_all` outside of docker image.
|
||||
|
||||
# Issue regression tests flow
|
||||
If -run_hooks is specified, after a successful flow run hooks/post_run.py script is run. Script may contain any checks required. Multiple issues can be convered by single script. As a reference def_test design can be used. It's a simple inverter, with pins locations specified in DEF. Script just checks for pins to be in locations in tempalte DEF.
|
||||
|
||||
Another type of checks is intentionally broken test cases which test the OpenLane's ability to warn user about possible issues. Example issue is: `issue_912_def_test_missaligned`. Entry point is `run_issue_regressions.py` which will run all designs matching `designs/issue_*` pattern. After the run was compeleted with or without errors or fails, then for that design `issue_regression.py` is ran in OpenROAD Python environment. If flow failed and `issue_regression.py` does not exist, then issue regression failed. As `-run_hooks` will run only after successful flow, but the `issue_regression.py` will be ran in both failed and successful case. OpenLane users may want to use `-run_hooks`, while issue_regression is designed to be used only by regression flow.
|
||||
|
||||
If `-run_hooks` is specified, after a successful flow run `hooks/post_run.py` script is run. Script may contain any checks required. Multiple issues can be covered by single script. As a reference `issue_892_def_test` can be used.
|
||||
|
||||
@@ -34,7 +34,7 @@ The script can be used in two ways
|
||||
python3 run_designs.py --threads 4 spm xtea PPU APU
|
||||
```
|
||||
|
||||
You can run the defualt test set consisting of all designs under [./designs](../designs/) through running the following command along with any of the flags:
|
||||
You can run the default test set consisting of all designs under [./designs](../designs/) through running the following command along with any of the flags:
|
||||
|
||||
```bash
|
||||
python3 run_design.py --defaultTestSet
|
||||
|
||||
@@ -32,7 +32,7 @@ import scripts.utils.utils as utils
|
||||
@click.option("-c", "--config_tag", default="config", help="Configuration file")
|
||||
@click.option("-r", "--regression", default=None, help="Regression file")
|
||||
@click.option("-t", "--tag", default="regression", help="Tag for the log file")
|
||||
@click.option("-j", "--threads", help="Number of designs in parallel")
|
||||
@click.option("-j", "--threads", default=1, help="Number of designs in parallel")
|
||||
@click.option(
|
||||
"-p",
|
||||
"--configuration_parameters",
|
||||
|
||||
156
run_issue_regressions.py
Normal file
156
run_issue_regressions.py
Normal file
@@ -0,0 +1,156 @@
|
||||
# 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 glob # For finding all issue_regression designs
|
||||
import subprocess # For running the flow
|
||||
import os # For checking if file exists
|
||||
import click # For command line parsing
|
||||
import json
|
||||
|
||||
|
||||
test_cases = glob.glob("./designs/issue_*")
|
||||
|
||||
# TODO: If command is get designs
|
||||
# print(json.dumps({"design": designs}))
|
||||
# else if design specified
|
||||
#
|
||||
|
||||
|
||||
@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": 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 test_cases:
|
||||
run_test_case(test_case)
|
||||
print("Issue regression flow completed without errors")
|
||||
|
||||
|
||||
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("Running test case:", test_case_name, "Logfile:", logpath)
|
||||
result = subprocess.run(
|
||||
[
|
||||
"./flow.tcl",
|
||||
"-design",
|
||||
test_case,
|
||||
"-tag",
|
||||
"issue_regression_run",
|
||||
"-run_hooks",
|
||||
"-overwrite",
|
||||
],
|
||||
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
|
||||
print(
|
||||
"./flow.tcl failed. This might be expected, as issue_regression.py may expect this"
|
||||
)
|
||||
else:
|
||||
print(
|
||||
"./flow.tcl failed and issue_regression.py does not exist, therefore test case",
|
||||
test_case,
|
||||
"failed. Logfile:",
|
||||
logpath,
|
||||
)
|
||||
raise err
|
||||
# -------------------------------
|
||||
# 3. Run the issue_regression.py.
|
||||
# -------------------------------
|
||||
if script_exists:
|
||||
print("Running", test_case_issue_regression_script, "Logfile:", logpath_check)
|
||||
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("Issue regression check failed, check log:", logpath_check)
|
||||
raise err
|
||||
print("Completed run successfully:", test_case)
|
||||
else:
|
||||
print(
|
||||
"For design",
|
||||
test_case,
|
||||
"no regression script",
|
||||
test_case_issue_regression_script,
|
||||
"has been found",
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
cli()
|
||||
@@ -13,95 +13,37 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import os
|
||||
import re
|
||||
import click
|
||||
import shutil
|
||||
import subprocess
|
||||
import defutil
|
||||
|
||||
|
||||
@click.command()
|
||||
@click.option("-t", "--def-template", "templatedef", required=True, help="Template DEF")
|
||||
@click.option("-l", "--lef", "lef", required=True, help="LEF file")
|
||||
@click.option("-lg", "--log", "logfile", required=True, help="Log output file")
|
||||
@click.argument("userdef")
|
||||
def cli(templatedef, userdef):
|
||||
def cli(templatedef, userdef, lef, logfile):
|
||||
userDEF = userdef
|
||||
templateDEF = templatedef
|
||||
scriptsDir = os.path.dirname(__file__)
|
||||
|
||||
def remove_power_pins(DEF):
|
||||
templateDEFOpener = open(DEF, "r")
|
||||
if templateDEFOpener.mode == "r":
|
||||
templateDEFSections = templateDEFOpener.read().split("PINS")
|
||||
templateDEFOpener.close()
|
||||
PINS = templateDEFSections[1].split("- ")
|
||||
OUT_PINS = [" ;"]
|
||||
cnt = 0
|
||||
for pin in PINS[1:]:
|
||||
if pin.find("USE GROUND") + pin.find("USE POWER") == -2:
|
||||
cnt += 1
|
||||
OUT_PINS.append(pin)
|
||||
OUT_PINS[0] = " " + str(cnt) + OUT_PINS[0] + PINS[0].split(";")[1]
|
||||
OUT_PINS[-1] = OUT_PINS[-1].replace("END ", "")
|
||||
OUT_PINS[-1] = OUT_PINS[-1] + "END "
|
||||
templateDEFSections[1] = "- ".join(OUT_PINS)
|
||||
templateDEFOpener = open(DEF, "w")
|
||||
templateDEFOpener.write("PINS".join(templateDEFSections))
|
||||
templateDEFOpener.close()
|
||||
# Removed section to remove the power/ground pins as defutil:replace_pins implements this
|
||||
|
||||
newTemplateDEF = f"{userDEF}.template.tmp"
|
||||
shutil.copy(templateDEF, newTemplateDEF)
|
||||
templateDEF = newTemplateDEF
|
||||
|
||||
templateDEF = f"{userDEF}.template.tmp"
|
||||
remove_power_pins(templateDEF)
|
||||
|
||||
subprocess.check_output(
|
||||
[
|
||||
"openroad",
|
||||
"-python",
|
||||
f"{scriptsDir}/defutil.py",
|
||||
"replace_pins",
|
||||
"--output",
|
||||
userDEF,
|
||||
"--input-lef",
|
||||
"/dev/null",
|
||||
userDEF,
|
||||
templateDEF,
|
||||
],
|
||||
stderr=subprocess.PIPE,
|
||||
defutil.replace_pins(
|
||||
input_lef=lef,
|
||||
logpath=logfile,
|
||||
template_def=templateDEF,
|
||||
source_def=userDEF,
|
||||
output_def=f"{userDEF}.replace_pins.tmp",
|
||||
)
|
||||
|
||||
# read template Def
|
||||
templateDEFOpener = open(templateDEF, "r")
|
||||
if templateDEFOpener.mode == "r":
|
||||
templateDEFContent = templateDEFOpener.read()
|
||||
templateDEFOpener.close()
|
||||
|
||||
# read user Def
|
||||
userDEFOpener = open(userDEF, "r")
|
||||
if userDEFOpener.mode == "r":
|
||||
userDEFContent = userDEFOpener.read()
|
||||
userDEFOpener.close()
|
||||
|
||||
def copyStringWithWord(word, f_rom, t_o):
|
||||
pattern = re.compile(r"\b%s\b\s*\([^)]*\)\s*\([^)]*\)" % word)
|
||||
instances = re.findall(pattern, f_rom)
|
||||
if len(instances) == 1:
|
||||
str_from = instances[0]
|
||||
tmp = re.sub(pattern, str_from, t_o)
|
||||
return tmp
|
||||
return None
|
||||
|
||||
# Copy DIEAREA
|
||||
word = "DIEAREA"
|
||||
userDEFContent = copyStringWithWord(word, templateDEFContent, userDEFContent)
|
||||
|
||||
if userDEFContent is not None:
|
||||
userDEFOpener = open(userDEF, "w")
|
||||
userDEFOpener.write(userDEFContent)
|
||||
userDEFOpener.close()
|
||||
else:
|
||||
raise Exception("DIEAREA not found in DEF")
|
||||
# Call defutil to move die area
|
||||
defutil.move_diearea(
|
||||
template_def=templateDEF,
|
||||
output_def=f"{userDEF}.replace_pins.tmp",
|
||||
input_lef=lef,
|
||||
)
|
||||
shutil.copy(f"{userDEF}.replace_pins.tmp", userDEF)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
# Copyright 2021 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.
|
||||
@@ -17,6 +18,10 @@ import click
|
||||
|
||||
import odb
|
||||
|
||||
import os # For checks of file existance
|
||||
import shutil # For copy
|
||||
import sys # For stderr
|
||||
|
||||
|
||||
@click.group()
|
||||
def cli():
|
||||
@@ -26,12 +31,15 @@ def cli():
|
||||
class OdbReader(object):
|
||||
def __init__(self, lef_in, def_in):
|
||||
self.db = odb.dbDatabase.create()
|
||||
|
||||
self.lef = odb.read_lef(self.db, lef_in)
|
||||
self.sites = self.lef.getSites()
|
||||
|
||||
self.df = odb.read_def(self.db, def_in)
|
||||
self.block = self.db.getChip().getBlock()
|
||||
self.rows = self.block.getRows()
|
||||
self.dbunits = self.block.getDefUnits()
|
||||
self.nets = self.block.getNets()
|
||||
|
||||
|
||||
@click.command("extract_core_dims")
|
||||
@@ -152,21 +160,347 @@ def merge_pins(output, input_lef, def_one, def_two):
|
||||
f.write(merge_item_section("PINS", def_one_str, def_two_str))
|
||||
|
||||
|
||||
@click.command("replace_pins")
|
||||
@click.option("-o", "--output", default="./out.def")
|
||||
@click.command("move_diearea")
|
||||
@click.option("-l", "--input-lef", required=True, help="Merged LEF file")
|
||||
@click.argument("def_one")
|
||||
@click.argument("def_two")
|
||||
def replace_pins(output, input_lef, def_one, def_two):
|
||||
# TODO: Rewrite in OpenDB if possible
|
||||
def_one_str = open(def_one).read()
|
||||
def_two_str = open(def_two).read()
|
||||
|
||||
with open(output, "w") as f:
|
||||
f.write(merge_item_section("PINS", def_one_str, def_two_str, replace_two=True))
|
||||
@click.option(
|
||||
"-o",
|
||||
"--output-def",
|
||||
required=True,
|
||||
help="Output DEF. File should exist. Die area will be applied to this DEF",
|
||||
)
|
||||
@click.option("-i", "--template-def", required=True, help="Input DEF")
|
||||
def move_diearea_command(input_lef, output_def, template_def):
|
||||
"""
|
||||
Move die area from input def to output def
|
||||
"""
|
||||
move_diearea(input_lef, output_def, template_def)
|
||||
|
||||
|
||||
cli.add_command(replace_pins)
|
||||
def move_diearea(input_lef, output_def, template_def):
|
||||
if not os.path.isfile(template_def):
|
||||
print("LEF file ", input_lef, " not found")
|
||||
raise FileNotFoundError
|
||||
if not os.path.isfile(template_def):
|
||||
print("Input DEF file ", template_def, " not found")
|
||||
raise FileNotFoundError
|
||||
if not os.path.isfile(output_def):
|
||||
print("Output DEF file ", output_def, " not found")
|
||||
raise FileNotFoundError
|
||||
|
||||
source_db = odb.dbDatabase.create()
|
||||
destination_db = odb.dbDatabase.create()
|
||||
odb.read_lef(source_db, input_lef)
|
||||
odb.read_lef(destination_db, input_lef)
|
||||
|
||||
odb.read_def(source_db, template_def)
|
||||
odb.read_def(destination_db, output_def)
|
||||
|
||||
assert (
|
||||
source_db.getTech().getManufacturingGrid()
|
||||
== destination_db.getTech().getManufacturingGrid()
|
||||
)
|
||||
assert (
|
||||
source_db.getTech().getDbUnitsPerMicron()
|
||||
== destination_db.getTech().getDbUnitsPerMicron()
|
||||
)
|
||||
|
||||
diearea = source_db.getChip().getBlock().getDieArea()
|
||||
output_block = destination_db.getChip().getBlock()
|
||||
output_block.setDieArea(diearea)
|
||||
# print("Applied die area: ", destination_db.getChip().getBlock().getDieArea().ur(), destination_db.getChip().getBlock().getDieArea().ll(), file=sys.stderr)
|
||||
|
||||
assert odb.write_def(output_block, output_def) == 1
|
||||
|
||||
|
||||
def check_pin_grid(
|
||||
manufacturing_grid, dbu_per_microns, pin_name, pin_coordinate, logfile
|
||||
):
|
||||
if (pin_coordinate % manufacturing_grid) != 0:
|
||||
print(
|
||||
"[ERROR]: Pin coordinate",
|
||||
pin_coordinate,
|
||||
"for pin",
|
||||
pin_name,
|
||||
"does not match the manufacturing grid",
|
||||
file=sys.stderr,
|
||||
)
|
||||
print(
|
||||
"[ERROR]: Pin coordinate",
|
||||
pin_coordinate,
|
||||
"for pin",
|
||||
pin_name,
|
||||
"does not match the manufacturing grid",
|
||||
file=logfile,
|
||||
) # IDK how to do this
|
||||
return True
|
||||
|
||||
|
||||
# openroad -python scripts/defutil.py replace_pins -o output.def -l designs/def_test/runs/RUN_2022.01.30_12.32.26/tmp/merged.lef designs/def_test/runs/RUN_2022.01.30_12.32.26/tmp/floorplan/4-io.def designs/def_test/def_test.def
|
||||
|
||||
|
||||
@click.command("replace_pins")
|
||||
@click.option("-o", "--output-def", default="./out.def", help="Destination DEF path")
|
||||
@click.option("-l", "--input-lef", required=True, help="Merged LEF file")
|
||||
@click.option("-lg", "--log", "logpath", help="Log output file")
|
||||
@click.argument("source_def")
|
||||
@click.argument("template_def")
|
||||
def replace_pins_command(output_def, input_lef, logpath, source_def, template_def):
|
||||
"""
|
||||
Copies source_def to output, then if same pin exists in template def and first def then, it's written to output def
|
||||
|
||||
Example to run:
|
||||
openroad -python scripts/defutil.py replace_pins -o output.def -l designs/def_test/runs/RUN_2022.01.30_12.32.26/tmp/merged.lef designs/def_test/runs/RUN_2022.01.30_12.32.26/tmp/floorplan/4-io.def designs/def_test/def_test.def --log defutil.log
|
||||
Note: Assumes that all pins are on metal layers and via pins dont exist.
|
||||
Note: It assumes that all pins are rectangles, not polygons.
|
||||
Note: This tool assumes no power pins exist in template def.
|
||||
Note: It should leave pins in source_def as-is if no pin in template def is found.
|
||||
Note: It assumes only one port with the same name exist.
|
||||
Note: It assumes that pin names matches the net names in template DEF.
|
||||
"""
|
||||
replace_pins(output_def, input_lef, logpath, source_def, template_def)
|
||||
|
||||
|
||||
cli.add_command(replace_pins_command)
|
||||
|
||||
# Note: If you decide to change any parameters, also change replace_pins_command's
|
||||
def replace_pins(output_def, input_lef, logpath, source_def, template_def):
|
||||
# --------------------------------
|
||||
# 0. Sanity check: Check for all defs and lefs to exist
|
||||
# I removed the output def to NOT exist check, as it was making testing harder
|
||||
# --------------------------------
|
||||
|
||||
if not os.path.isfile(input_lef):
|
||||
print("LEF file ", input_lef, " not found")
|
||||
raise FileNotFoundError
|
||||
if not os.path.isfile(source_def):
|
||||
print("First DEF file ", source_def, " not found")
|
||||
raise FileNotFoundError
|
||||
if not os.path.isfile(template_def):
|
||||
print("Template DEF file ", template_def, " not found")
|
||||
raise FileNotFoundError
|
||||
if logpath is None:
|
||||
logfile = sys.stdout
|
||||
else:
|
||||
logfile = open(logpath, "w+")
|
||||
|
||||
# --------------------------------
|
||||
# 1. Copy the one def to second
|
||||
# --------------------------------
|
||||
print(
|
||||
"[defutil.py:replace_pins] Creating output DEF based on first DEF", file=logfile
|
||||
)
|
||||
shutil.copy(source_def, output_def)
|
||||
|
||||
# --------------------------------
|
||||
# 2. Find list of all bterms in first def
|
||||
# --------------------------------
|
||||
source_db = odb.dbDatabase.create()
|
||||
odb.read_lef(source_db, input_lef)
|
||||
odb.read_def(source_db, source_def)
|
||||
source_bterms = source_db.getChip().getBlock().getBTerms()
|
||||
|
||||
manufacturing_grid = source_db.getTech().getManufacturingGrid()
|
||||
dbu_per_microns = source_db.getTech().getDbUnitsPerMicron()
|
||||
|
||||
print(
|
||||
"Using manufacturing grid:",
|
||||
manufacturing_grid,
|
||||
"Using dbu per mircons: ",
|
||||
dbu_per_microns,
|
||||
file=logfile,
|
||||
)
|
||||
|
||||
all_bterm_names = set()
|
||||
|
||||
for source_bterm in source_bterms:
|
||||
source_name = source_bterm.getName()
|
||||
# TODO: Check for pin name matches net name
|
||||
# print("Bterm", source_name, "is declared as", source_bterm.getSigType())
|
||||
|
||||
# --------------------------------
|
||||
# 3. Check no bterms should be marked as power, because it is assumed that caller already removed them
|
||||
# --------------------------------
|
||||
if (source_bterm.getSigType() == "POWER") or (
|
||||
source_bterm.getSigType() == "GROUND"
|
||||
):
|
||||
print(
|
||||
"Bterm",
|
||||
source_name,
|
||||
"is declared as",
|
||||
source_bterm.getSigType(),
|
||||
"ignoring it",
|
||||
file=logfile,
|
||||
)
|
||||
continue
|
||||
all_bterm_names.add(source_name)
|
||||
|
||||
print(
|
||||
"[defutil.py:replace_pins] Found",
|
||||
len(all_bterm_names),
|
||||
"block terminals in first def",
|
||||
file=logfile,
|
||||
)
|
||||
|
||||
# --------------------------------
|
||||
# 4. Read the template def
|
||||
# --------------------------------
|
||||
template_db = odb.dbDatabase.create()
|
||||
odb.read_lef(template_db, input_lef)
|
||||
odb.read_def(template_db, template_def)
|
||||
template_bterms = template_db.getChip().getBlock().getBTerms()
|
||||
|
||||
assert (
|
||||
source_db.getTech().getManufacturingGrid()
|
||||
== template_db.getTech().getManufacturingGrid()
|
||||
)
|
||||
assert (
|
||||
source_db.getTech().getDbUnitsPerMicron()
|
||||
== template_db.getTech().getDbUnitsPerMicron()
|
||||
)
|
||||
# --------------------------------
|
||||
# 5. Create a dict with net -> pin location. Check for only one pin location to exist, overwise return an error
|
||||
# --------------------------------
|
||||
template_bterm_locations = dict()
|
||||
|
||||
for template_bterm in template_bterms:
|
||||
template_name = template_bterm.getName()
|
||||
template_pins = template_bterm.getBPins()
|
||||
|
||||
# TODO: Check for pin name matches net name
|
||||
for template_pin in template_pins:
|
||||
boxes = template_pin.getBoxes()
|
||||
|
||||
for box in boxes:
|
||||
layer = box.getTechLayer().getName()
|
||||
if template_name not in template_bterm_locations:
|
||||
template_bterm_locations[template_name] = []
|
||||
template_bterm_locations[template_name].append(
|
||||
(
|
||||
layer,
|
||||
box.xMin(),
|
||||
box.yMin(),
|
||||
box.xMax(),
|
||||
box.yMax(),
|
||||
)
|
||||
)
|
||||
|
||||
print(
|
||||
"[defutil.py:replace_pins] Found template_bterms: ",
|
||||
len(template_bterm_locations),
|
||||
file=logfile,
|
||||
)
|
||||
|
||||
for template_bterm_name in template_bterm_locations:
|
||||
print(
|
||||
template_bterm_name,
|
||||
": ",
|
||||
template_bterm_locations[template_bterm_name],
|
||||
file=logfile,
|
||||
)
|
||||
|
||||
# --------------------------------
|
||||
# 6. Modify the pins in out def, according to dict
|
||||
# --------------------------------
|
||||
output_db = odb.dbDatabase.create()
|
||||
odb.read_lef(output_db, input_lef)
|
||||
odb.read_def(output_db, output_def)
|
||||
output_tech = output_db.getTech()
|
||||
output_block = output_db.getChip().getBlock()
|
||||
output_bterms = output_block.getBTerms()
|
||||
grid_errors = False
|
||||
for output_bterm in output_bterms:
|
||||
name = output_bterm.getName()
|
||||
output_bpins = output_bterm.getBPins()
|
||||
|
||||
if name in template_bterm_locations and name in all_bterm_names:
|
||||
for output_bpin in output_bpins:
|
||||
odb.dbBPin.destroy(output_bpin)
|
||||
|
||||
for template_bterm_location_tuple in template_bterm_locations[name]:
|
||||
layer = output_tech.findLayer(template_bterm_location_tuple[0])
|
||||
|
||||
# --------------------------------
|
||||
# 6.2 Create new pin
|
||||
# --------------------------------
|
||||
|
||||
output_new_bpin = odb.dbBPin.create(output_bterm)
|
||||
|
||||
print(
|
||||
"For:",
|
||||
name,
|
||||
"Wrote on layer:",
|
||||
layer.getName(),
|
||||
"coordinates: ",
|
||||
template_bterm_location_tuple[1],
|
||||
template_bterm_location_tuple[2],
|
||||
template_bterm_location_tuple[3],
|
||||
template_bterm_location_tuple[4],
|
||||
file=logfile,
|
||||
)
|
||||
grid_errors = (
|
||||
check_pin_grid(
|
||||
manufacturing_grid,
|
||||
dbu_per_microns,
|
||||
name,
|
||||
template_bterm_location_tuple[1],
|
||||
logfile,
|
||||
)
|
||||
or grid_errors
|
||||
)
|
||||
grid_errors = (
|
||||
check_pin_grid(
|
||||
manufacturing_grid,
|
||||
dbu_per_microns,
|
||||
name,
|
||||
template_bterm_location_tuple[2],
|
||||
logfile,
|
||||
)
|
||||
or grid_errors
|
||||
)
|
||||
grid_errors = (
|
||||
check_pin_grid(
|
||||
manufacturing_grid,
|
||||
dbu_per_microns,
|
||||
name,
|
||||
template_bterm_location_tuple[3],
|
||||
logfile,
|
||||
)
|
||||
or grid_errors
|
||||
)
|
||||
grid_errors = (
|
||||
check_pin_grid(
|
||||
manufacturing_grid,
|
||||
dbu_per_microns,
|
||||
name,
|
||||
template_bterm_location_tuple[4],
|
||||
logfile,
|
||||
)
|
||||
or grid_errors
|
||||
)
|
||||
odb.dbBox.create(
|
||||
output_new_bpin,
|
||||
layer,
|
||||
template_bterm_location_tuple[1],
|
||||
template_bterm_location_tuple[2],
|
||||
template_bterm_location_tuple[3],
|
||||
template_bterm_location_tuple[4],
|
||||
)
|
||||
output_new_bpin.setPlacementStatus("PLACED")
|
||||
else:
|
||||
print(
|
||||
"[defutil.py:replace_pins] Not found",
|
||||
name,
|
||||
"in template def, but found in output def. Leaving as-is",
|
||||
file=logfile,
|
||||
)
|
||||
|
||||
if grid_errors:
|
||||
sys.exit("[ERROR]: Grid errors happened. Check log for grid errors.")
|
||||
# --------------------------------
|
||||
# 7. Write back the output def
|
||||
# --------------------------------
|
||||
print("[defutil.py:replace_pins] Writing output def to: ", output_def, file=logfile)
|
||||
assert odb.write_def(output_block, output_def) == 1
|
||||
|
||||
|
||||
@click.command("remove_components")
|
||||
|
||||
@@ -252,13 +252,16 @@ proc chip_floorplan {args} {
|
||||
|
||||
proc apply_def_template {args} {
|
||||
if { [info exists ::env(FP_DEF_TEMPLATE)] } {
|
||||
puts_info "Applying DEF template..."
|
||||
set log [index_file $::env(floorplan_logs)/apply_def_template.log]
|
||||
set def [index_file $::env(floorplan_tmpfiles)/apply_def_template.def]
|
||||
puts_info "Applying DEF template. See log: $log"
|
||||
try_catch $::env(OPENROAD_BIN) -python $::env(SCRIPTS_DIR)/apply_def_template.py\
|
||||
--lef $::env(MERGED_LEF) \
|
||||
--def-template $::env(FP_DEF_TEMPLATE)\
|
||||
--log $log \
|
||||
$::env(CURRENT_DEF)
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
proc run_power_grid_generation {args} {
|
||||
|
||||
Reference in New Issue
Block a user