Skip to content

Commit

Permalink
lint
Browse files Browse the repository at this point in the history
  • Loading branch information
htfab committed Apr 30, 2024
1 parent 0e2f6b8 commit 26aa641
Show file tree
Hide file tree
Showing 3 changed files with 191 additions and 129 deletions.
191 changes: 111 additions & 80 deletions precheck/pin_check.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import gdstk
import logging
import re

import gdstk


class PrecheckFailure(Exception):
pass
Expand All @@ -10,37 +11,36 @@ class PrecheckFailure(Exception):
def parsefp3(value: str):
# parse fixed-point numbers with 3 digits after the decimal point
# e.g. '20.470' to 20470
ip, fp = value.split('.')
mul = ip + fp[:3].rjust(3, '0')
ip, fp = value.split(".")
mul = ip + fp[:3].rjust(3, "0")
return int(mul)


def pin_check(gds: str, lef: str, template_def: str, toplevel: str):

logging.info("Running pin check...")

# parse pins from template def

diearea_re = re.compile(r'DIEAREA \( (\S+) (\S+) \) \( (\S+) (\S+) \) ;')
pins_re = re.compile(r'PINS (\d+) ;')
pin_re = re.compile(r' *- (\S+) \+ NET (\S+) \+ DIRECTION (\S+) \+ USE (\S+)')
layer_re = re.compile(r' *\+ LAYER (\S+) \( (\S+) (\S+) \) \( (\S+) (\S+) \)')
placed_re = re.compile(r' *\+ PLACED \( (\S+) (\S+) \) (\S+) ;')
diearea_re = re.compile(r"DIEAREA \( (\S+) (\S+) \) \( (\S+) (\S+) \) ;")
pins_re = re.compile(r"PINS (\d+) ;")
pin_re = re.compile(r" *- (\S+) \+ NET (\S+) \+ DIRECTION (\S+) \+ USE (\S+)")
layer_re = re.compile(r" *\+ LAYER (\S+) \( (\S+) (\S+) \) \( (\S+) (\S+) \)")
placed_re = re.compile(r" *\+ PLACED \( (\S+) (\S+) \) (\S+) ;")

def_pins = {}
die_width = 0
die_height = 0

with open(template_def) as f:
for line in f:
if line.startswith('DIEAREA '):
if line.startswith("DIEAREA "):
match = diearea_re.match(line)
lx, by, rx, ty = map(int, match.groups())
if (lx, by) != (0, 0):
raise PrecheckFailure('Wrong die origin in template DEF')
raise PrecheckFailure("Wrong die origin in template DEF")
die_width = rx
die_height = ty
elif line.startswith('PINS '):
elif line.startswith("PINS "):
match = pins_re.match(line)
pin_count = int(match.group(1))
break
Expand All @@ -50,41 +50,42 @@ def pin_check(gds: str, lef: str, template_def: str, toplevel: str):
match = pin_re.match(line)
pin_name, net_name, direction, use = match.groups()
if pin_name != net_name:
raise PrecheckFailure('Inconsistent pin name and net name in template DEF')
raise PrecheckFailure(
"Inconsistent pin name and net name in template DEF"
)

line = next(f)
if not line.strip().startswith('+ PORT'):
raise PrecheckFailure('Unexpected token in template DEF')
if not line.strip().startswith("+ PORT"):
raise PrecheckFailure("Unexpected token in template DEF")

line = next(f)
match = layer_re.match(line)
layer, lx, by, rx, ty = match.groups()
lx, by, rx, ty = map(int, (lx, by, rx, ty))

line = next(f)
match = placed_re.match(line)
ox, oy, direction = match.groups()
ox, oy = map(int, (ox, oy))

if pin_name in def_pins:
raise PrecheckFailure('Duplicate pin in template DEF')
raise PrecheckFailure("Duplicate pin in template DEF")

def_pins[pin_name] = (layer, ox+lx, oy+by, ox+rx, oy+ty)
def_pins[pin_name] = (layer, ox + lx, oy + by, ox + rx, oy + ty)

line = next(f)
if not line.startswith('END PINS'):
raise PrecheckFailure('Unexpected token in template DEF')
if not line.startswith("END PINS"):
raise PrecheckFailure("Unexpected token in template DEF")

# parse pins from user lef

origin_re = re.compile(r'ORIGIN (\S+) (\S+) ;')
size_re = re.compile(r'SIZE (\S+) BY (\S+) ;')
layer_re = re.compile(r'LAYER (\S+) ;')
rect_re = re.compile(r'RECT (\S+) (\S+) (\S+) (\S+) ;')
origin_re = re.compile(r"ORIGIN (\S+) (\S+) ;")
size_re = re.compile(r"SIZE (\S+) BY (\S+) ;")
layer_re = re.compile(r"LAYER (\S+) ;")
rect_re = re.compile(r"RECT (\S+) (\S+) (\S+) (\S+) ;")

macro_seen = False
macro_active = False
pins_expected = set(def_pins).union({'VPWR', 'VGND'})
pins_expected = set(def_pins).union({"VPWR", "VGND"})
pins_seen = set()
current_pin = None
pin_rects = 0
Expand All @@ -94,138 +95,168 @@ def pin_check(gds: str, lef: str, template_def: str, toplevel: str):
with open(lef) as f:
for line in f:
line = line.strip()
if line.startswith('MACRO '):
if line == 'MACRO ' + toplevel:
macro_seen = True
if line.startswith("MACRO "):
if line == "MACRO " + toplevel:
macro_active = True
else:
macro_active = False
elif macro_active:
if line.startswith('ORIGIN '):
if line.startswith("ORIGIN "):
match = origin_re.match(line)
lx, by = map(parsefp3, match.groups())
if lx != 0 or by != 0:
raise PrecheckFailure('Wrong die origin in LEF')
elif line.startswith('SIZE '):
raise PrecheckFailure("Wrong die origin in LEF")
elif line.startswith("SIZE "):
match = size_re.match(line)
rx, ty = map(parsefp3, match.groups())
if (rx, ty) != (die_width, die_height):
raise PrecheckFailure(f'Inconsistent die area between LEF and template DEF: ({rx}, {ry}) != ({die_width}, {die_height})')
elif line.startswith('PIN '):
raise PrecheckFailure(
f"Inconsistent die area between LEF and template DEF: ({rx}, {ty}) != ({die_width}, {die_height})"
)
elif line.startswith("PIN "):
if current_pin is not None:
raise PrecheckFailure('Unexpected token in LEF')
current_pin = line.removeprefix('PIN ')
raise PrecheckFailure("Unexpected token in LEF")
current_pin = line.removeprefix("PIN ")
pins_seen.add(current_pin)
if current_pin not in pins_expected:
logging.error(f'Unexpected pin {current_pin} in {lef}')
logging.error(f"Unexpected pin {current_pin} in {lef}")
lef_errors += 1
pin_rects = 0
elif line == 'PORT':
elif line == "PORT":
if current_pin is None:
raise PrecheckFailure('Unexpected token in LEF')
raise PrecheckFailure("Unexpected token in LEF")
line = next(f).strip()
while line.startswith('LAYER '):
while line.startswith("LAYER "):
match = layer_re.match(line)
layer = match.group(1)
line = next(f).strip()
while line.startswith('RECT '):
while line.startswith("RECT "):
match = rect_re.match(line)
lx, by, rx, ty = map(parsefp3, match.groups())
pin_rects += 1
lef_ports[current_pin] = (layer, lx, by, rx, ty)
line = next(f).strip()
if line != 'END':
raise PrecheckFailure('Unexpected token in LEF')
elif current_pin is not None and line.startswith('END ' + current_pin):
if line != "END":
raise PrecheckFailure("Unexpected token in LEF")
elif current_pin is not None and line.startswith("END " + current_pin):
if pin_rects < 1:
logging.error(f'No ports for pin {current_pin} in {lef}')
logging.error(f"No ports for pin {current_pin} in {lef}")
lef_errors += 1
elif pin_rects > 1:
logging.error(f'Too many rectangles for pin {current_pin} in {lef}')
logging.error(
f"Too many rectangles for pin {current_pin} in {lef}"
)
lef_errors += 1
current_pin = None

for current_pin in def_pins:
if current_pin not in lef_ports:
logging.error(f'Pin {current_pin} not found in {lef}')
logging.error(f"Pin {current_pin} not found in {lef}")
lef_errors += 1
else:
lef_layer, *lef_rect = lef_ports[current_pin]
def_layer, *def_rect = def_pins[current_pin]
if lef_layer != def_layer:
logging.error(f'Port {current_pin} on layer {lef_layer} in {lef} but on layer {def_layer} in {template_def}')
logging.error(
f"Port {current_pin} on layer {lef_layer} in {lef} but on layer {def_layer} in {template_def}"
)
lef_errors += 1
elif lef_rect != def_rect:
logging.error(f'Port {current_pin} has different dimensions in {lef} and {template_def}')
logging.error(
f"Port {current_pin} has different dimensions in {lef} and {template_def}"
)
lef_errors += 1

pin_widths = {}
for current_pin in ('VPWR', 'VGND'):
if current_pin not in lef_ports:
logging.error(f'Pin {current_pin} not found in {lef}')
for current_pin in ("VPWR", "VGND"):
if current_pin not in lef_ports:
logging.error(f"Pin {current_pin} not found in {lef}")
lef_errors += 1
else:
layer, lx, by, rx, ty = lef_ports[current_pin]
width, height = rx-lx, ty-by
width, height = rx - lx, ty - by
pin_widths[current_pin] = width
if layer != 'met4':
logging.error(f'Port {current_pin} has wrong layer in {lef}: {layer} != met4')
if layer != "met4":
logging.error(
f"Port {current_pin} has wrong layer in {lef}: {layer} != met4"
)
lef_errors += 1
if width < 1200:
logging.error(f'Port {current_pin} has too small width in {lef}: {width/1000} < 1.2 um')
logging.error(
f"Port {current_pin} has too small width in {lef}: {width/1000} < 1.2 um"
)
lef_errors += 1
if width > 2000:
logging.error(f'Port {current_pin} has too large width in {lef}: {width/1000} > 2.0 um')
logging.error(
f"Port {current_pin} has too large width in {lef}: {width/1000} > 2.0 um"
)
lef_errors += 1
if height < die_height * 0.95:
logging.error(f'Port {current_pin} has too small height in {lef}: {height/1000} < {die_height*0.95/1000} um')
logging.error(
f"Port {current_pin} has too small height in {lef}: {height/1000} < {die_height*0.95/1000} um"
)
lef_errors += 1
if lx < 0 or rx > die_width or by < 0 or ty > die_height:
logging.error(f'Port {current_pin} not entirely within project area in {lef}')
logging.error(
f"Port {current_pin} not entirely within project area in {lef}"
)
lef_errors += 1

if 'VPWR' in pin_widths and 'VGND' in pin_widths and pin_widths['VPWR'] != pin_widths['VGND']:
vpwr_width = pin_widths['VPWR']
vgnd_width = pin_widths['VGND']
logging.error(f'VPWR and VGND have different widths in {lef}: {vpwr_width/1000} != {vgnd_width/1000} um')
if (
"VPWR" in pin_widths
and "VGND" in pin_widths
and pin_widths["VPWR"] != pin_widths["VGND"]
):
vpwr_width = pin_widths["VPWR"]
vgnd_width = pin_widths["VGND"]
logging.error(
f"VPWR and VGND have different widths in {lef}: {vpwr_width/1000} != {vgnd_width/1000} um"
)
lef_errors += 1

# check gds for the ports being present

lib = gdstk.read_gds(gds)
top = [cell for cell in lib.top_level() if cell.name == toplevel]
if not top:
raise PrecheckFailure('Wrong cell at GDS top-level')
raise PrecheckFailure("Wrong cell at GDS top-level")
top = top[0]

gds_layers = {'met1.pin': (68, 16), 'met2.pin': (69, 16), 'met3.pin': (70, 16), 'met4.pin': (71, 16)}
gds_layers = {
"met1.pin": (68, 16),
"met2.pin": (69, 16),
"met3.pin": (70, 16),
"met4.pin": (71, 16),
}

gds_errors = 0
for current_pin, (layer, lx, by, rx, ty) in sorted(lef_ports.items()):
assert layer + '.pin' in gds_layers, 'Unexpected port layer in LEF'
pin_layer = gds_layers[layer + '.pin']
assert layer + ".pin" in gds_layers, "Unexpected port layer in LEF"
pin_layer = gds_layers[layer + ".pin"]

pin_ok = False
for poly in top.polygons:
poly_layer = (poly.layer, poly.datatype)
if poly.contain_all(
((lx+1)/1000, (by+1)/1000),
((rx-1)/1000, (by+1)/1000),
((lx+1)/1000, (ty-1)/1000),
((rx-1)/1000, (ty-1)/1000)):
if poly_layer == pin_layer:
pin_ok = True
((lx + 1) / 1000, (by + 1) / 1000),
((rx - 1) / 1000, (by + 1) / 1000),
((lx + 1) / 1000, (ty - 1) / 1000),
((rx - 1) / 1000, (ty - 1) / 1000),
):
if poly_layer == pin_layer:
pin_ok = True

if not pin_ok:
logging.error(f'Port {current_pin} missing from layer {layer}.pin in {gds}')
logging.error(f"Port {current_pin} missing from layer {layer}.pin in {gds}")
gds_errors += 1

if lef_errors > 0 or gds_errors > 0:
err_list = []
if lef_errors > 0:
err_list.append(f'{lef_errors} LEF error' + ('s' if lef_errors > 1 else ''))
err_list.append(f"{lef_errors} LEF error" + ("s" if lef_errors > 1 else ""))
if gds_errors > 0:
err_list.append(f'{gds_errors} GDS error' + ('s' if gds_errors > 1 else ''))
err_desc = ' and '.join(err_list)
raise PrecheckFailure(f'Some ports are missing or have wrong dimensions, see {err_desc} above')

err_list.append(f"{gds_errors} GDS error" + ("s" if gds_errors > 1 else ""))
err_desc = " and ".join(err_list)
raise PrecheckFailure(
f"Some ports are missing or have wrong dimensions, see {err_desc} above"
)
15 changes: 6 additions & 9 deletions precheck/precheck.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,12 @@
import time
import traceback
import xml.etree.ElementTree as ET
import yaml

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

from pin_check import pin_check

PDK_ROOT = os.getenv("PDK_ROOT")
Expand Down Expand Up @@ -131,8 +130,6 @@ def klayout_checks(gds: str):
)




def main():
parser = argparse.ArgumentParser()
parser.add_argument("--gds", required=True)
Expand All @@ -151,19 +148,19 @@ def main():
if args.lef:
lef = args.lef
else:
lef = os.path.splitext(args.gds)[0] + '.lef'
lef = os.path.splitext(args.gds)[0] + ".lef"

if args.template_def:
template_def = args.template_def
else:
yaml_file = f"{os.path.dirname(args.gds)}/info.yaml"
yaml_data = yaml.safe_load(open(yaml_file))
tiles = yaml_data.get('project', {}).get('tiles', '1x1')
is_analog = yaml_data.get('pinout', {}).get('analog_pins', 0) > 0
tiles = yaml_data.get("project", {}).get("tiles", "1x1")
is_analog = yaml_data.get("pinout", {}).get("analog_pins", 0) > 0
if is_analog:
template_def = "../def/analog/tt_block_{tiles}_pg_ana.def"
template_def = f"../def/analog/tt_block_{tiles}_pg_ana.def"
else:
template_def = "../def/tt_block_{tiles}_pg.def"
template_def = f"../def/tt_block_{tiles}_pg.def"

checks = [
["Magic DRC", lambda: magic_drc(args.gds, top_module)],
Expand Down
Loading

0 comments on commit 26aa641

Please sign in to comment.