Skip to content

Commit

Permalink
feat: precheck (#30)
Browse files Browse the repository at this point in the history
perform DRC checks on GDS files submitted to Tiny Tapeout
  • Loading branch information
urish authored Nov 20, 2023
1 parent 6702774 commit 2df21d6
Show file tree
Hide file tree
Showing 11 changed files with 1,214 additions and 0 deletions.
48 changes: 48 additions & 0 deletions .github/workflows/test-precheck.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
name: Precheck Tests

on:
push:
pull_request:
workflow_dispatch:

jobs:
test-precheck:
runs-on: ubuntu-latest
env:
PDK_ROOT: /home/runner/pdk
PDK_VERSION: dd7771c384ed36b91a25e9f8b314355fc26561be

steps:
- name: Check out code
uses: actions/checkout@v4

- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: '3.11'
cache: 'pip'

- name: Install dependencies
working-directory: precheck
run: pip install -r requirements.txt

- name: Install Sky130 PDK
uses: TinyTapeout/volare-action@v1
with:
pdk_name: sky130
pdk_version: ${{ env.PDK_VERSION }}
pdk_root: ${{ env.PDK_ROOT }}

- name: Install Nix
uses: rikhuijzer/cache-install@v1.1.2
with:
key: nix-${{ hashFiles('precheck/default.nix') }}
nix_file: 'precheck/default.nix'

- name: Build Nix packages
working-directory: precheck
run: nix-build

- name: Run tests
working-directory: precheck
run: nix-shell --run "pytest"
2 changes: 2 additions & 0 deletions precheck/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
__pycache__
.pytest_cache
11 changes: 11 additions & 0 deletions precheck/default.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{ pkgs ? import (fetchTarball "https://github.com/NixOS/nixpkgs/archive/8f7a6554c058bfdae00f4fb36f5cd52838f410e7.tar.gz")
{ }
,
}:

pkgs.mkShell {
buildInputs = [
pkgs.klayout
pkgs.magic-vlsi
];
}
42 changes: 42 additions & 0 deletions precheck/klayout_tools.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import xml.etree.ElementTree as ET
from typing import Dict


class LayerInfo:
def __init__(self, name: str, source: str, layer: int, data_type: int):
self.name = name
self.source = source
self.layer = layer
self.data_type = data_type

def __repr__(self):
return f"LayerInfo(name={self.name}, source={self.source}, layer={self.layer}, data_type={self.data_type})"


def parse_lyp_layers(lyp_file: str):
with open(lyp_file) as f:
xml_content = f.read()
root = ET.fromstring(xml_content)

layers_dict: Dict[str, LayerInfo] = {}

for properties in root.findall("properties"):
name = properties.find("name")
source = properties.find("source")
valid = properties.find("valid")

if valid is not None and valid.text == "false":
continue

if name is not None and source is not None:
name_key = name.text.split("-")[0].strip()
layer, data_type = source.text.split("@")[0].split("/")
# Add the 'source' text as the value in the dictionary
layers_dict[name_key] = LayerInfo(
name=name.text,
source=source.text,
layer=int(layer),
data_type=int(data_type),
)

return layers_dict
77 changes: 77 additions & 0 deletions precheck/magic_drc.tcl
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
# SPDX-FileCopyrightText: 2020 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.
# SPDX-License-Identifier: Apache-2.0


set GDS_UT_PATH [lindex $argv 6]
set DESIGN_NAME [lindex $argv 7]
set PDK_PATH [lindex $argv 8]
set DRC_REPORT [lindex $argv 9]
set DRC_MAG [lindex $argv 10]

gds read $GDS_UT_PATH

set fout [open $DRC_REPORT w]
set oscale [cif scale out]
set cell_name $DESIGN_NAME
magic::suspendall
puts stdout "\[INFO\]: Loading $cell_name\n"
flush stdout
load $cell_name
select top cell
expand
drc euclidean on
drc style drc(full)
drc check
set drc_result [drc listall why]


set count 0
puts $fout "$cell_name"
puts $fout "----------------------------------------"
foreach {errtype coordlist} $drc_result {
puts $fout $errtype
puts $fout "----------------------------------------"
foreach coord $coordlist {
set bllx [expr {$oscale * [lindex $coord 0]}]
set blly [expr {$oscale * [lindex $coord 1]}]
set burx [expr {$oscale * [lindex $coord 2]}]
set bury [expr {$oscale * [lindex $coord 3]}]
set coords [format " %.3f %.3f %.3f %.3f" $bllx $blly $burx $bury]
puts $fout "$coords"
set count [expr {$count + 1} ]
}
puts $fout "----------------------------------------"
}

puts $fout "\[INFO\]: COUNT: $count"
puts $fout "\[INFO\]: Should be divided by 3 or 4"

puts $fout ""
close $fout

puts stdout "\[INFO\]: DRC Checking DONE ($DRC_REPORT)"
flush stdout

if {$count > 0} {
puts stderr "\[ERROR\]: $count DRC errors found"
puts stdout "\[INFO\]: Saving mag view with DRC errors($DRC_MAG)"
# WARNING: changes the name of the cell; keep as last step
save $DRC_MAG
puts stdout "\[INFO\]: Saved"
flush stdout
exit -1
} else {
exit 0
}
121 changes: 121 additions & 0 deletions precheck/precheck.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
#!/usr/bin/env python3
import argparse
import logging
import os
import subprocess

import klayout.db as pya
import klayout.rdb as rdb
from klayout_tools import parse_lyp_layers

PDK_ROOT = os.getenv("PDK_ROOT")
PDK_NAME = os.getenv("PDK_NAME") or "sky130A"
LYP_FILE = f"{PDK_ROOT}/{PDK_NAME}/libs.tech/klayout/tech/{PDK_NAME}.lyp"
REPORTS_PATH = os.path.join(os.path.dirname(os.path.realpath(__file__)), "reports")


def magic_drc(gds: str, toplevel: str):
logging.info(f"Running magic DRC on {gds} (module={toplevel})")

magic = subprocess.run(
[
"magic",
"-noconsole",
"-dnull",
"-rcfile",
f"{PDK_ROOT}/{PDK_NAME}/libs.tech/magic/{PDK_NAME}.magicrc",
"magic_drc.tcl",
gds,
toplevel,
PDK_ROOT,
f"{REPORTS_PATH}/magic_drc.txt",
f"{REPORTS_PATH}/magic_drc.mag",
],
)

if magic.returncode != 0:
logging.error("Magic DRC failed")
return False

return True


def klayout_drc(gds: str, check: str):
logging.info(f"Running klayout {check} on {gds}")
report_file = f"{REPORTS_PATH}/drc_{check}.xml"
klayout = subprocess.run(
[
"klayout",
"-b",
"-r",
f"tech-files/{PDK_NAME}_mr.drc",
"-rd",
f"{check}=true",
"-rd",
f"input={gds}",
"-rd",
f"report={report_file}",
],
)
if klayout.returncode != 0:
logging.error(f"Klayout {check} failed")
return False

report = rdb.ReportDatabase().load(report_file)

if report.num_items() > 0:
logging.error(
f"Klayout {check} failed with {report.num_items()} DRC violations"
)
return False

return True


def klayout_checks(gds: str):
layout = pya.Layout()
layout.read(gds)
layers = parse_lyp_layers(LYP_FILE)

logging.info("Running forbidden layer check...")
forbidden_layers = [
"met5.drawing",
"met5.pin",
"met5.label",
]

had_error = False
for layer in forbidden_layers:
layer_info = layers[layer]
logging.info(f"* Checking {layer_info.name}")
layer_index = layout.find_layer(layer_info.layer, layer_info.data_type)
if layer_index is not None:
logging.error(f"Forbidden layer {layer} found in {gds}")
had_error = True

if had_error:
logging.error("Klayout checks failed")
return not had_error


def main():
parser = argparse.ArgumentParser()
parser.add_argument("--gds", required=True)
parser.add_argument("--toplevel", required=True)
args = parser.parse_args()
logging.basicConfig(level=logging.INFO, format="%(levelname)s: %(message)s")
logging.info(f"PDK_ROOT: {PDK_ROOT}")

assert magic_drc(args.gds, args.toplevel)

assert klayout_drc(args.gds, "feol")
assert klayout_drc(args.gds, "beol")
assert klayout_drc(args.gds, "offgrid")

assert klayout_checks(args.gds)

logging.info(f"Precheck passed for {args.gds}! 🎉")


if __name__ == "__main__":
main()
5 changes: 5 additions & 0 deletions precheck/reports/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
magic_drc.mag
magic_drc.txt
drc_feol.xml
drc_beol.xml
drc_offgrid.xml
2 changes: 2 additions & 0 deletions precheck/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
klayout==0.28.12
pytest==7.4.3
1 change: 1 addition & 0 deletions precheck/tech-files/README.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
sky130A_mr.drc from https://github.com/efabless/mpw_precheck revision 4efb33553ee6c2acdb0fa9a6a5f87af099f5c1d5
Loading

0 comments on commit 2df21d6

Please sign in to comment.