-
Notifications
You must be signed in to change notification settings - Fork 14
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
perform DRC checks on GDS files submitted to Tiny Tapeout
- Loading branch information
Showing
11 changed files
with
1,214 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
__pycache__ | ||
.pytest_cache |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
]; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
klayout==0.28.12 | ||
pytest==7.4.3 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
Oops, something went wrong.