333 lines
8.8 KiB
Python
Executable File
333 lines
8.8 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
|
|
from __future__ import print_function
|
|
|
|
|
|
import csv
|
|
import os
|
|
import re
|
|
import textwrap
|
|
|
|
from tabulate import tabulate
|
|
import enum
|
|
from typing import List, Tuple
|
|
from dataclasses import dataclass, field, asdict
|
|
|
|
|
|
def filesafe_template(s):
|
|
s = s.replace('(', '')
|
|
s = s.replace(')', '')
|
|
s = s.replace(' ', '_')
|
|
s = s.replace('/', '')
|
|
s = s.replace('.-', '_dotdash')
|
|
return s
|
|
|
|
|
|
class RuleFlags(enum.Enum):
|
|
"""
|
|
|
|
Note: some rules contain correction factors to compensate possible mask
|
|
defect and unpredicted process biases)
|
|
"""
|
|
|
|
P = u'Rule applies to periphery only (outside :drc_tag:`areaid.ce`). A corresponding core rule may or may not exist.'
|
|
NE = u'Rule not checked for esd_nwell_tap. There are no corresponding rule for esd_nwell_tap.'
|
|
NC = u'Rule not checked by DRC. It should be used as a guideline only.'
|
|
TC = u'Rule not checked for cell name "*_tech_CD_top*"'
|
|
A = u'Rule documents a functionality implemented in CL algorithms and may not be checked by DRC.'
|
|
AD = u'Rule documents a functionality implemented in CL algorithms and checked by DRC.'
|
|
DE = u'Rule not checked for source of Drain Extended device'
|
|
LVS = u'Rule handled by LVS'
|
|
F = u'Rule intended for Frame only, not checked inside Die'
|
|
DNF = u'Drawn Not equal Final. The drawn rule does not reflect the final dimension on silicon. See table J for details.'
|
|
RC = u'Recommended rule at the chip level, required rule at the IP level.'
|
|
RR = u'Recommended rule at any IP level'
|
|
AL = u'Rules applicable only to Al BE flows'
|
|
CU = u'Rules applicable only to Cu BE flows'
|
|
IR = u'IR drop check compering Al database and slotted Cu database for the same product (2 gds files) must be clean'
|
|
|
|
EXEMPT = u'Rule is an exception?'
|
|
|
|
|
|
@dataclass
|
|
class Rule:
|
|
name: str = ''
|
|
description: str = ''
|
|
flags: Tuple[RuleFlags] = field(default_factory=tuple)
|
|
value: str = ''
|
|
|
|
|
|
@dataclass
|
|
class RuleTable:
|
|
fname: str = ''
|
|
order: int = -1
|
|
template: str = ''
|
|
name: str = ''
|
|
description: str = ''
|
|
image: str = ''
|
|
rules: List[Rule] = field(default_factory=list, repr=False)
|
|
enabled: bool = True
|
|
notes: str = ''
|
|
|
|
@property
|
|
def csv_fname(self):
|
|
return 'p{:03d}-{}.csv'.format(self.order, self.fname)
|
|
|
|
|
|
data = [[]]
|
|
for l in open('periphery.csv'):
|
|
if '.-)' in l:
|
|
data.append([])
|
|
|
|
only_comma = True
|
|
for i in l:
|
|
if i not in ' ,\n':
|
|
only_comma = False
|
|
break
|
|
if only_comma:
|
|
continue
|
|
data[-1].append(l)
|
|
|
|
no_pics = [
|
|
'p024-hvtr_dotdash.svg',
|
|
'p033-licon_dotdash.svg',
|
|
'p042-via3_dotdash.svg',
|
|
'p043-nsm_dotdash.svg',
|
|
'p044-m5_dotdash.svg',
|
|
'p044-via4_dotdash.svg',
|
|
'p045-pad_dotdash.svg',
|
|
'p045-rdl_dotdash.svg',
|
|
'p053-hv_dotdash_dotdash.svg',
|
|
'p055-uhvi_dotdash_dotdash.svg',
|
|
'p055-ulvt-_dotdash.svg',
|
|
'p055-vhvi_dotdash_dotdash.svg',
|
|
]
|
|
|
|
image_files = {}
|
|
image_location = {}
|
|
image_re = re.compile('p([0-9][0-9][0-9])-([^.]*)\.svg')
|
|
for i in sorted(os.listdir('.')+no_pics):
|
|
if not i.endswith('.svg'):
|
|
continue
|
|
page, name = image_re.match(i).groups()
|
|
page = int(page, 10)
|
|
if name in image_files:
|
|
assert page-1 == image_location[name], (name, i, image_files[name], page, image_location[name])
|
|
if i not in no_pics:
|
|
image_files[name] = i
|
|
image_location[name] = page
|
|
|
|
|
|
rule_tables = []
|
|
for d in data[1:]:
|
|
rows = list(csv.reader(d))
|
|
|
|
rule_template, rule_name, a, process = rows.pop(0)
|
|
rule_template = rule_template.strip()
|
|
rule_name = rule_name.strip()
|
|
a = a.strip()
|
|
process = process.strip()
|
|
assert a == '', (d[0], a)
|
|
assert process in ('sky130', ''), process
|
|
|
|
image_name = filesafe_template(rule_template)
|
|
|
|
if 'High Voltage' in rule_name and not 'hv' in rule_template:
|
|
rule_template = rule_template.replace('(', '(hv')
|
|
|
|
# Extract the function line (if it exists)
|
|
func = 'Function: Defines '+rule_name+' (FIXME)'
|
|
if rows[0][1].startswith('Function:'):
|
|
a, func, b, c = rows.pop(0)
|
|
assert a == '', a
|
|
assert b == '', b
|
|
assert c == '', c
|
|
|
|
# Strip off the notes
|
|
notes = []
|
|
for i, r in enumerate(rows):
|
|
if r[0] not in ('Note', ''):
|
|
break
|
|
if r[1] == '':
|
|
assert r[2] == '', (r[2], rule_name, i, r)
|
|
assert r[3] == 'NA', (r[3], rule_name, i, r)
|
|
continue
|
|
assert r[1] != '', (r[1], rule_name, i, r)
|
|
assert r[2] == '', (r[2], rule_name, i, r)
|
|
assert r[3] == '', (r[3], rule_name, i, r)
|
|
notes.append(r[1])
|
|
rows = rows[i:]
|
|
|
|
# Strip off the flags table
|
|
for i, r in enumerate(rows):
|
|
if r[0] == 'Use' and r[1] == 'Explanation':
|
|
break
|
|
rows = rows[:i]
|
|
|
|
# Join together description which span multiple rows.
|
|
continued_index = []
|
|
for i, row in enumerate(rows):
|
|
if row[0] == '':
|
|
continued_index.append(i)
|
|
for i in reversed(continued_index):
|
|
previous_row = rows[i-1]
|
|
l = rows[i]
|
|
a, extra, b, c = rows.pop(i)
|
|
assert a.strip() == '', (a, l)
|
|
assert b.strip() in ('', previous_row[2]), (b, l, previous_row)
|
|
assert c.strip() in ('', 'NA', 'N/A', previous_row[3]), (c, l, previous_row)
|
|
if extra.strip() == '':
|
|
continue
|
|
previous_row[1] += '\n'+extra
|
|
|
|
# Calculate the actual rule name.
|
|
pr = None
|
|
values = []
|
|
for r in rows:
|
|
values.append(r[-1])
|
|
|
|
if r[0].startswith('.'):
|
|
r[0] = rule_template.replace('.-.-', r[0])
|
|
elif r[0].strip() == '':
|
|
r[0] = pr[0]
|
|
else:
|
|
r[0] = rule_template.replace('.-', '.'+r[0], 1)
|
|
pr = r
|
|
|
|
rt = RuleTable()
|
|
rt.template = rule_template
|
|
rt.fname = filesafe_template(rule_template)
|
|
rt.name = rule_name
|
|
rt.description = func
|
|
|
|
if notes:
|
|
rt.notes = "\n\n".join(notes)
|
|
|
|
if rt.fname in image_files:
|
|
rt.image = image_files[rt.fname]
|
|
if rt.fname in image_location:
|
|
rt.order = image_location[rt.fname]
|
|
|
|
# Check for all the rules having Not Applicable values
|
|
if len(values) == len([v for v in values if v in ('NA', 'N/A')]):
|
|
rt.enabled = False
|
|
|
|
for r in rows:
|
|
assert len(r) == 4, r
|
|
|
|
if r[0] == 'Use' and r[1] == 'Explanation':
|
|
break
|
|
|
|
rc = Rule()
|
|
rc.name = r[0]
|
|
rc.description = r[1].strip()
|
|
flags = [getattr(RuleFlags, f.upper().replace(',', '')) for f in r[2].strip().split()]
|
|
if r[3] == 'NC':
|
|
flags.append(RuleFlags.NC)
|
|
r[3] = ''
|
|
rc.flags = tuple(flags)
|
|
rc.value = r[3]
|
|
rt.rules.append(rc)
|
|
|
|
if rule_tables:
|
|
assert rt.order >= rule_tables[-1].order, "{} >= {}\n{}\n{}\n".format(rt.order, rule_tables[-1].order, rt, rule_tables[-1])
|
|
rule_tables.append(rt)
|
|
continue
|
|
|
|
|
|
PERIPHERY_RULES_FILE = os.path.join('..', 'periphery-rules.rst')
|
|
|
|
rst = open(PERIPHERY_RULES_FILE, 'w')
|
|
|
|
rst.write("""\
|
|
.. Do **not** modify this file it is generated from the periphery.csv file
|
|
found in the periphery directory using the
|
|
./periphery/periphery-split-csv.py script. Instead run `make
|
|
rules/periphery-rules.rst` in the ./docs directory.
|
|
|
|
.. list-table::
|
|
:header-rows: 1
|
|
:stub-columns: 1
|
|
:width: 100%
|
|
:widths: 10 75
|
|
|
|
* - Use
|
|
- Explanation
|
|
""")
|
|
for e in RuleFlags:
|
|
rst.write("""\
|
|
* -
|
|
.. _{e.name}:
|
|
|
|
:drc_flag:`{e.name}`
|
|
|
|
- {e.value}
|
|
""".format(e=e))
|
|
|
|
|
|
for rt in rule_tables:
|
|
|
|
rst.write("""\
|
|
|
|
:drc_rule:`{rt.template}`
|
|
{hd}
|
|
|
|
""".format(rt=rt, hd='-'*(len(rt.template)+len(':drc_rule:``'))))
|
|
|
|
if rt.notes:
|
|
rst.write("""\
|
|
.. note::
|
|
|
|
{}
|
|
|
|
|
|
""".format(textwrap.indent(rt.notes, prefix=' ')))
|
|
|
|
headers = ('Name', 'Description', 'Flags', 'Value')
|
|
headers_fmt = (':drc_rule:`Name`', 'Description', ':drc_flag:`Flags`', 'Value')
|
|
|
|
rst.write("""\
|
|
.. list-table:: {rt.description}
|
|
:header-rows: 1
|
|
:stub-columns: 1
|
|
:width: 100%
|
|
:widths: 10 75 5 10
|
|
|
|
* - {h}
|
|
""".format(rt=rt,h='\n - '.join(headers_fmt)))
|
|
|
|
|
|
for r in rt.rules:
|
|
f = ' '.join(':drc_flag:`{}`'.format(f.name) for f in r.flags)
|
|
d = textwrap.indent(r.description, prefix=' ').strip()
|
|
rst.write("""\
|
|
* - :drc_rule:`{r.name}`
|
|
- {d}
|
|
- {f}
|
|
- {r.value}
|
|
""".format(r=r, d=d, f=f))
|
|
|
|
rst.write('\n\n')
|
|
|
|
with open(rt.csv_fname, 'w', newline='') as f:
|
|
w = csv.DictWriter(f, headers)
|
|
w.writeheader()
|
|
for r in rt.rules:
|
|
d = {f: getattr(r, f.lower()) for f in headers}
|
|
d['Flags'] = ' '.join(f.name for f in d['Flags'])
|
|
w.writerow(d)
|
|
|
|
if rt.image:
|
|
rst.write("""\
|
|
.. figure:: {image_file}
|
|
:width: 100%
|
|
:align: center
|
|
|
|
|
|
""".format(image_file=os.path.join('periphery', rt.image)))
|
|
|
|
rst.close()
|
|
|
|
with open(PERIPHERY_RULES_FILE) as f:
|
|
print(f.read())
|