Creating a GitHub Action to run DRC checks on cells.
This GitHub Action uses Magic to run the SKY130 design rules pulled from
OpenPDKs on all the cells in a standard library.
See the `README.rst` for instructions on how to use.
Signed-off-by: Tim 'mithro' Ansell <tansell@google.com>
Signed-off-by: Mohamed Gaber <mohamed.gaber@efabless.com>
diff --git a/.github/workflows/build-docker-image-run-drc-for-cell-gds-using-magic.yml b/.github/workflows/build-docker-image-run-drc-for-cell-gds-using-magic.yml
new file mode 100644
index 0000000..c1d7b9c
--- /dev/null
+++ b/.github/workflows/build-docker-image-run-drc-for-cell-gds-using-magic.yml
@@ -0,0 +1,52 @@
+# Copyright 2021 SkyWater PDK Authors
+#
+# 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
+#
+# https://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.
+#
+# SPDX-License-Identifier: Apache 2.0
+
+name: Build Docker Image for Run DRC for cell GDS (using Magic) Action
+
+on:
+ workflow_dispatch:
+ push:
+
+jobs:
+ prebuild-magic-gds-drc:
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@v2
+
+ - name: Set up Docker Buildx
+ uses: docker/setup-buildx-action@v1
+
+ - name: Set Action Name
+ run: echo "ACTION_NAME=run-drc-for-cell-gds-using-magic" >> $GITHUB_ENV
+
+ - name: Login to GitHub Container Registry
+ uses: docker/login-action@v1
+ with:
+ registry: ghcr.io
+ username: ${{ github.repository_owner }}
+ password: ${{ github.token }}
+
+ - name: Build and push
+ uses: docker/build-push-action@v2
+ with:
+ context: ${{ env.ACTION_NAME }}
+ file: ${{ env.ACTION_NAME }}/Dockerfile
+ push: true
+ tags: ghcr.io/${{ github.repository }}-${{ env.ACTION_NAME }}:latest
+
+ - name: Image digest
+ run: echo ${{ steps.docker_build.outputs.digest }}
diff --git a/.gitignore b/.gitignore
index a81c8ee..b530fea 100644
--- a/.gitignore
+++ b/.gitignore
@@ -136,3 +136,6 @@
# Cython debug symbols
cython_debug/
+
+# IDEs
+.vscode/
diff --git a/AUTHORS b/AUTHORS
index ea70201..de14df6 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -7,7 +7,7 @@
# Companies
Google LLC
-efabless corporation
+Efabless Corporation
The American University in Cairo
# Individuals
diff --git a/environment.yml b/environment.yml
index 2a7b5da..b740766 100644
--- a/environment.yml
+++ b/environment.yml
@@ -23,3 +23,4 @@
# Packages installed from PyPI
- pip:
- -r file:requirements.txt
+ - -r file:run-drc-for-cell-gds-using-magic/requirements.txt
diff --git a/pytest.ini b/pytest.ini
new file mode 100644
index 0000000..4735410
--- /dev/null
+++ b/pytest.ini
@@ -0,0 +1,2 @@
+[pytest]
+addopts = --doctest-modules --ignore-glob=env --ignore-glob=third_party
diff --git a/requirements.txt b/requirements.txt
index 58d13b0..5dc7f8b 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,4 +1,6 @@
+# Python code testing + linting
flake8
+pytest
# rst_include tool as GitHub doesn't support `.. include::` when rendering
# previews.
diff --git a/run-drc-for-cell-gds-using-magic/Dockerfile b/run-drc-for-cell-gds-using-magic/Dockerfile
new file mode 100644
index 0000000..ab321e8
--- /dev/null
+++ b/run-drc-for-cell-gds-using-magic/Dockerfile
@@ -0,0 +1,70 @@
+# Copyright 2021 SkyWater PDK Authors
+#
+# 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
+#
+# https://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.
+#
+# SPDX-License-Identifier: Apache-2.0
+
+FROM ubuntu:20.04
+
+ENV DEBIAN_FRONTEND noninteractive
+
+RUN apt-get update
+RUN apt-get install -y build-essential git curl python3 python3-pip tcl-dev tk-dev csh libcairo2-dev
+
+WORKDIR /build
+
+# Setup Magic
+ENV MAGIC_TAG 8.3.160
+RUN \
+ curl -L https://github.com/RTimothyEdwards/magic/archive/refs/tags/${MAGIC_TAG}.tar.gz -o /build/magic.tar.gz \
+ && sha256sum /build/magic.tar.gz \
+ && mkdir /build/magic \
+ && tar -xzC /build/magic --strip-components=1 -f /build/magic.tar.gz \
+ && cd /build/magic \
+ && ./configure \
+ && make -j$(nproc) \
+ && make install \
+ && rm -rf /build/magic*
+
+# Setup Python Dependencies
+WORKDIR /build
+COPY ./requirements.txt /build/requirements.txt
+RUN \
+ cat /build/requirements.txt \
+ && python3 -m pip install -r /build/requirements.txt --progress-bar off \
+ && rm -rf ~/.cache/pip
+
+# OpenPDKs
+## Tag must exist on both https://github.com/efabless/open_pdk_techfiles
+## and https://github.com/RTimothyEdwards/open_pdks
+ENV OPEN_PDKS_TAG 1.0.159
+
+## Download run_standard_drc
+RUN \
+ curl -L https://raw.githubusercontent.com/RTimothyEdwards/open_pdks/${OPEN_PDKS_TAG}/sky130/custom/scripts/run_standard_drc.py -o /usr/bin/run_standard_drc.py \
+ && sha256sum /usr/bin/run_standard_drc.py
+
+## Download Precompiled OpenPDKs Magic Tech Files
+ENV PDK_ROOT /share/pdk/sky130A
+RUN \
+ curl -L https://github.com/efabless/open_pdk_techfiles/releases/download/${OPEN_PDKS_TAG}/sky130A_tech_magic.tar.xz -o /build/sky130A_tech_magic.tar.xz \
+ && sha256sum /build/sky130A_tech_magic.tar.xz \
+ && mkdir -p ${PDK_ROOT}/libs.tech/magic \
+ && tar -xC ${PDK_ROOT}/libs.tech/magic -f /build/sky130A_tech_magic.tar.xz \
+ && rm -rf /build/*
+
+# Copy Entry Point
+COPY ./run_all_drc.py /usr/bin/run_all_drc.py
+RUN chmod +x /usr/bin/run_all_drc.py
+
+ENTRYPOINT ["/usr/bin/run_all_drc.py"]
diff --git a/run-drc-for-cell-gds-using-magic/Makefile b/run-drc-for-cell-gds-using-magic/Makefile
new file mode 100644
index 0000000..fb408e8
--- /dev/null
+++ b/run-drc-for-cell-gds-using-magic/Makefile
@@ -0,0 +1,24 @@
+# Copyright 2021 SkyWater PDK Authors
+#
+# 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
+#
+# https://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.
+#
+# SPDX-License-Identifier: Apache-2.0
+
+TOP_DIR := $(realpath $(dir $(lastword $(MAKEFILE_LIST)))/..)
+
+README.rst: README.src.rst $(TOP_DIR)/docs/*.rst $(TOP_DIR)/Makefile
+ make -C $(TOP_DIR) run-drc-for-cell-gds-using-magic/README.rst
+
+# Redirect everything to the top directory by default.
+%:
+ make -C $(TOP_DIR) $@
diff --git a/run-drc-for-cell-gds-using-magic/README.rst b/run-drc-for-cell-gds-using-magic/README.rst
new file mode 100644
index 0000000..dd4d8e9
--- /dev/null
+++ b/run-drc-for-cell-gds-using-magic/README.rst
@@ -0,0 +1,145 @@
+``skywater-pdk-actions`` - ``run-drc-for-cell-gds-using-magic``
+=============================================================
+
+This GitHub action runs Design Rule Checks on all GDS files inside the /cells
+directory.
+
+Usage
+=====
+
+Add this to any push, PR or manual dispatch workflow:
+
+.. code:: yml
+
+ steps:
+ - uses: actions/checkout@v2
+
+ - name: Run Magic DRC
+ uses: docker://ghcr.io/google/skywater-pdk-actions-run-drc-for-cell-gds-using-magic:latest
+ with:
+ args: --acceptable-errors-file /dev/null --match-directories . --known-bad ''
+
+Check the Python file for more documentation on arguments.
+
+How to Contribute
+=================
+
+We'd love to accept your patches and contributions to this project.
+There are just a few small guidelines you need to follow.
+
+Contributor License Agreement
+-----------------------------
+
+Contributions to this project must be accompanied by a Contributor
+License Agreement. You (or your employer) retain the copyright to your
+contribution; this simply gives us permission to use and redistribute
+your contributions as part of the project. Head over to
+https://cla.developers.google.com/ to see your current agreements on
+file or to sign a new one.
+
+You generally only need to submit a CLA once, so if you've already
+submitted one (even if it was for a different project), you probably
+don't need to do it again.
+
+Code reviews
+------------
+
+All submissions, including submissions by project members, require
+review. We use GitHub pull requests for this purpose. Consult `GitHub
+Help <https://help.github.com/articles/about-pull-requests/>`__ for more
+information on using pull requests.
+
+Community Guidelines
+--------------------
+
+This project follows `Google's Open Source Community
+Guidelines <https://opensource.google/conduct/>`__.
+
+At Google, we recognize and celebrate the creativity and collaboration
+of open source contributors and the diversity of skills, experiences,
+cultures, and opinions they bring to the projects and communities they
+participate in.
+
+Every one of Google's open source projects and communities are inclusive
+environments, based on treating all individuals respectfully, regardless
+of gender identity and expression, sexual orientation, disabilities,
+neurodiversity, physical appearance, body size, ethnicity, nationality,
+race, age, religion, or similar personal characteristic.
+
+We value diverse opinions, but we value respectful behavior more.
+
+Respectful behavior includes:
+
+- Being considerate, kind, constructive, and helpful.
+- Not engaging in demeaning, discriminatory, harassing, hateful,
+ sexualized, or physically threatening behavior, speech, and imagery.
+- Not engaging in unwanted physical contact.
+
+Some Google open source projects
+`may adopt <https://opensource.google/docs/releasing/preparing/#conduct>`__
+an explicit project code of conduct, which may have additional detailed
+expectations for participants. Most of those projects will use our
+`modified Contributor Covenant <https://opensource.google/docs/releasing/template/CODE_OF_CONDUCT/>`__.
+
+Resolve peacefully
+~~~~~~~~~~~~~~~~~~
+
+We do not believe that all conflict is necessarily bad; healthy debate
+and disagreement often yields positive results. However, it is never
+okay to be disrespectful.
+
+If you see someone behaving disrespectfully, you are encouraged to
+address the behavior directly with those involved. Many issues can be
+resolved quickly and easily, and this gives people more control over the
+outcome of their dispute. If you are unable to resolve the matter for
+any reason, or if the behavior is threatening or harassing, report it.
+We are dedicated to providing an environment where participants feel
+welcome and safe.
+
+Reporting problems
+~~~~~~~~~~~~~~~~~~
+
+Some Google open source projects may adopt a project-specific code of
+conduct. In those cases, a Google employee will be identified as the
+Project Steward, who will receive and handle reports of code of conduct
+violations. In the event that a project hasn’t identified a Project
+Steward, you can report problems by emailing opensource@google.com.
+
+We will investigate every complaint, but you may not receive a direct
+response. We will use our discretion in determining when and how to
+follow up on reported incidents, which may range from not taking action
+to permanent expulsion from the project and project-sponsored spaces. We
+will notify the accused of the report and provide them an opportunity to
+discuss it before any action is taken. The identity of the reporter will
+be omitted from the details of the report supplied to the accused. In
+potentially harmful situations, such as ongoing harassment or threats to
+anyone's safety, we may take action without notice.
+
+*This document was adapted from the*
+`IndieWeb Code of Conduct <https://indieweb.org/code-of-conduct>`_
+*and can also be found at* <https://opensource.google/conduct/>.
+
+License
+=======
+
+The SkyWater Open Source PDK GitHub actions are released under the
+`Apache 2.0 license <https://github.com/google/skywater-pdk/blob/master/LICENSE>`_.
+
+The copyright details (which should also be found at the top of every file) are;
+
+::
+
+ Copyright 2021 SkyWater PDK Authors
+
+ 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.
+
diff --git a/run-drc-for-cell-gds-using-magic/README.src.rst b/run-drc-for-cell-gds-using-magic/README.src.rst
new file mode 100644
index 0000000..50057f9
--- /dev/null
+++ b/run-drc-for-cell-gds-using-magic/README.src.rst
@@ -0,0 +1,26 @@
+``skywater-pdk-actions`` - ``run-drc-for-cell-gds-using-magic``
+=============================================================
+
+This GitHub action runs Design Rule Checks on all GDS files inside the /cells
+directory.
+
+Usage
+=====
+
+Add this to any push, PR or manual dispatch workflow:
+
+.. code:: yml
+
+ steps:
+ - uses: actions/checkout@v2
+
+ - name: Run Magic DRC
+ uses: docker://ghcr.io/google/skywater-pdk-actions-run-drc-for-cell-gds-using-magic:latest
+ with:
+ args: --acceptable-errors-file /dev/null --match-directories . --known-bad ''
+
+Check the Python file for more documentation on arguments.
+
+.. include:: ../docs/contributing.rst
+
+.. include:: ../docs/license.rst
diff --git a/run-drc-for-cell-gds-using-magic/requirements.txt b/run-drc-for-cell-gds-using-magic/requirements.txt
new file mode 100644
index 0000000..b98f660
--- /dev/null
+++ b/run-drc-for-cell-gds-using-magic/requirements.txt
@@ -0,0 +1 @@
+click
\ No newline at end of file
diff --git a/run-drc-for-cell-gds-using-magic/run_all_drc.py b/run-drc-for-cell-gds-using-magic/run_all_drc.py
new file mode 100644
index 0000000..5adcbcd
--- /dev/null
+++ b/run-drc-for-cell-gds-using-magic/run_all_drc.py
@@ -0,0 +1,231 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+#
+# Copyright 2020 SkyWater PDK Authors
+#
+# 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
+#
+# https://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.
+#
+# SPDX-License-Identifier: Apache-2.0
+
+"""
+run_all_drc.py --- A script that will run run_standard_drc for all .gds files
+under the cells/ folder.
+
+Must be run from repository root.
+
+Usage: python3 run_all_drc.py --help
+
+Results:
+
+ Prints a report to standard output.
+"""
+
+import os
+import re
+import subprocess
+import traceback
+
+from concurrent import futures
+from typing import List, Tuple
+
+import click
+
+acceptable_errors = []
+
+SCRIPT_DIR = os.path.realpath(os.path.dirname(__file__))
+STANDARD_DRC_SCRIPT = os.path.join(SCRIPT_DIR, "run_standard_drc.py")
+PDK_SUBSET = os.getenv("PDK_ROOT") or os.path.join(SCRIPT_DIR, "sky130A")
+
+DRCError = Tuple[str, List[str]]
+
+
+PARSE_DRC_REPORT_EXAMPLE = """
+This first set of lines is the 'header':
+DRC errors for a cell that doesn't exist
+It's skipped over by this function.
+--------------------------------------------
+
+This is an acceptable error.
+These lines are details for the acceptable error.
+There are usually a couple of lines.
+
+This is an unacceptable error.
+These lines are details for the unacceptable error.
+There are usually a couple of lines.
+
+This is another unacceptable error.
+It has less lines of detail.
+"""
+
+
+def parse_drc_report(
+ report: str, acceptable_errors: List[str]) -> List[DRCError]:
+ """
+ Takes a magic report in the format as seen in PARSE_DRC_REPORT_EXAMPLE
+ above, and returns all errors as a list of tuples, where the first element
+ of the tuple is the name of the error and the other lines are the details.
+
+ >>> from pprint import pprint as p
+ >>> p(parse_drc_report(
+ ... PARSE_DRC_REPORT_EXAMPLE.strip(),
+ ... ["This is an acceptable error."]))
+ [('This is an unacceptable error.',
+ ['These lines are details for the unacceptable error.',
+ 'There are usually a couple of lines.']),
+ ('This is another unacceptable error.', ['It has less lines of detail.'])]
+ """
+ components = [x.split("\n") for x in report.split("\n\n")]
+ errors = []
+
+ header = components.pop(0) # noqa: F841
+
+ for error in components:
+ error_name = error[0]
+ if error_name in acceptable_errors:
+ continue
+ errors.append((error[0], error[1:]))
+
+ return errors
+
+
+def drc_gds(path: str) -> Tuple[str, List[DRCError]]:
+ """
+ Takes a GDS path. Returns the name of the cell and returns a list of
+ DRC errors.
+ """
+ cell_name = os.path.basename(path)[:-4]
+
+ env = os.environ.copy()
+ env["PDKPATH"] = PDK_SUBSET
+
+ res = subprocess.run([
+ "python3",
+ STANDARD_DRC_SCRIPT,
+ path
+ ], env=env, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+
+ report_path = path[:-4] + "_drc.txt"
+ try:
+ report = open(report_path).read()
+
+ if os.getenv("ACTIONS_STEP_DEBUG") or False:
+ print("::group::%s" % report_path)
+ print(report)
+ print("::endgroup::")
+
+ return cell_name, parse_drc_report(report, acceptable_errors)
+ except FileNotFoundError:
+ return cell_name, [
+ (
+ "Magic did not produce a report.",
+ [res.stdout.decode("utf8"), res.stderr.decode("utf8")]
+ )
+ ]
+
+
+@click.command()
+@click.option(
+ "-a",
+ "--acceptable-errors-file",
+ default="/dev/null",
+ help="A file containing a list of newline-delimited acceptable DRC errors."
+ " Default: No file will be read and all errors deemed unacceptable."
+)
+@click.option(
+ "-m",
+ "--match-directories",
+ default=".",
+ help="A regex that will match subdirectories under cells/."
+ " Default: . (matches everything.)"
+)
+@click.option(
+ "-b",
+ "--known-bad",
+ default="",
+ help="A comma,delimited list of cells that are known bad and"
+ " thus do not cause a non-zero exit upon failure."
+ " Default: empty string (None of them.)"
+)
+def run_all_drc(acceptable_errors_file, match_directories, known_bad):
+ print("Testing cells in directories matching /%s/…" % match_directories)
+
+ global acceptable_errors
+ acceptable_errors_str = open(acceptable_errors_file).read()
+ acceptable_errors = acceptable_errors_str.split("\n")
+
+ known_bad_list = known_bad.split(",")
+
+ nproc = os.cpu_count()
+ with futures.ThreadPoolExecutor(max_workers=nproc) as executor:
+ future_list = []
+
+ cells_dir = "./cells"
+ cells = os.listdir(cells_dir)
+
+ for cell in cells:
+ if not re.match(match_directories, cell):
+ print("Skipping directory %s…" % cell)
+ continue
+
+ cell_dir = os.path.join(cells_dir, cell)
+
+ gds_list = list(
+ filter(lambda x: x.endswith(".gds"), os.listdir(cell_dir))
+ )
+
+ for gds_name in gds_list:
+ gds_path = os.path.join(cell_dir, gds_name)
+
+ future_list.append(executor.submit(drc_gds, gds_path))
+
+ successes = 0
+ total = 0
+ exit_code = 0
+ for future in future_list:
+ total += 1
+ cell_name, errors = future.result()
+
+ symbol = "❌"
+ message = "ERROR"
+ if len(errors) == 0:
+ successes += 1
+ # This tick is rendered black on all major platforms except for
+ # Microsoft.
+ symbol = "✔\ufe0f"
+ message = "CLEAN"
+ print("%-64s %s %s" % (cell_name, symbol, message))
+
+ if len(errors) != 0:
+ if cell_name not in known_bad_list:
+ exit_code = 65
+ for error in errors:
+ print("* %s" % error[0])
+ for line in error[1]:
+ print(" %s" % line)
+
+ success_rate = (successes / total * 100)
+ print("%i/%i successes (%0.1f%%)" % (successes, total, success_rate))
+
+ exit(exit_code)
+
+
+def main():
+ try:
+ run_all_drc()
+ except Exception:
+ print("An unhandled exception has occurred.", traceback.format_exc())
+ exit(69)
+
+
+if __name__ == '__main__':
+ main()