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:
Arman Avetisyan
2022-02-28 15:56:36 +04:00
committed by GitHub
parent 8fea3c1cb7
commit de223b4c53
20 changed files with 735 additions and 126 deletions

View File

@@ -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:

View File

@@ -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:

View File

@@ -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

View File

@@ -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

View 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

View File

@@ -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)

View File

@@ -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;

View File

@@ -0,0 +1 @@
../issue_892_def_test/config.tcl

View 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

View 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")

View File

@@ -0,0 +1 @@
../issue_892_def_test/src

View File

@@ -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.

View File

@@ -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

View File

@@ -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
View 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()

View File

@@ -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__":

View File

@@ -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")

View File

@@ -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} {