blob: 9e632d4dcb5ba87610ae088dfb0d1b54603d05b4 [file] [log] [blame]
#!/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 = ''
unit: 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', encoding='utf8'):
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
should_strip_flags = False
for i, r in enumerate(rows):
if r[0] == 'Use' and r[1] == 'Explanation':
should_strip_flags = True
break
if should_strip_flags:
rows = rows[:i]
# Remove rows with Section in the first column
rows = [r for r in rows if not r[0].startswith('Section ')]
# 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) == 5, 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]
rc.unit = r[4].strip()
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', encoding='utf8')
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
: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', 'Unit')
headers_fmt = (':drc_rule:`Name`', 'Description', ':drc_flag:`Flags`', 'Value', 'Unit')
rst.write("""\
.. list-table:: {rt.description}
:header-rows: 1
:stub-columns: 1
:widths: 9 73 6 6 6
* - {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)
if '\\n- ' in r.description: # bullet list description
r.description = r.description.replace('\\n- ','\n - ')
elif '\\n' in r.description: # multi line description
r.description = '\n'.join( [ '| '+l for l in r.description.split('\\n') ] )
else:
r.description = r.description.lstrip(' -') # one item bullet list to text
d = textwrap.indent(r.description, prefix=' ').strip()
rst.write("""\
* - :drc_rule:`{r.name}`
- {d}
- {f}
- {r.value}
- {r.unit}
""".format(r=r, d=d, f=f))
rst.write('\n\n')
with open(rt.csv_fname, 'w', newline='', encoding='utf8') as f:
w = csv.DictWriter(f, headers, lineterminator='\n')
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, encoding='utf8') as f:
print(f.read())