blob: 094c4ee3e709d0f9771775c3e1bc28c7dbdf6ced [file] [log] [blame]
#!/usr/bin/env python3
# Copyright 2020 The Skywater PDK Authors
#
# 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
#
# https://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.
import enum
import io
import json
import os
import pprint
import re
import sys
import textwrap
from shutil import copyfile
from collections import defaultdict, namedtuple
EFS8_RE = re.compile('efs8(hd|hdll|hs|ms|ls|hvl)')
def cleanup_module_name(mod):
"""
>>> cleanup_module_name('efs8hd')
'scs8hd'
>>> cleanup_module_name('blah.123')
'blah'
>>> cleanup_module_name('abc$213')
'abc'
>>> cleanup_module_name('cds_thru')
'feedthru'
>>> cleanup_module_name('cds_thru_cds_alias')
'feedthru_netalias'
>>> cleanup_module_name('BLAH')
'blah'
>>> cleanup_module_name('s8pir_10r_vcells_lvs')
'vcells_lvs'
"""
# Rewrite 'efs8'
mod = EFS8_RE.sub('scs8\\1', mod)
if '$' in mod:
mod = mod[:mod.find('$')]
if '.' in mod:
mod = mod[:mod.find('.')]
if mod == "cds_thru":
mod = "feedthru"
if mod == "cds_thru_cds_alias":
mod = "feedthru_netalias"
if 'cds_' in mod:
mod = mod.replace('cds_', '')
if mod.startswith('s8pir_10r_'):
mod = mod[len('s8pir_10r_'):]
if mod in ('primdev', 'pads'):
return None
return mod.lower()
modules = defaultdict(set)
modules_inc = defaultdict(set)
def add_file_for_module(mod, pn, include=False):
assert mod, ("-->", mod, pn)
assert pn, (mod, "-->", pn)
libname, modname = lib_extract_from_name(mod)
modname = cleanup_module_name(modname)
if include:
print(" Includes", end=" ")
modules_inc[modname].add(pn)
else:
assert \
(not libname) or \
(libname in pn) or \
(libname in pn.replace('efs8', 'scs8')) or \
(libname in pn.replace('s8iom0/', 's8iom0s8/')) or \
"primdev" in pn or \
False, (libname, modname, mod, pn)
print(" Adding ", end=" ")
modules[modname].add(pn)
print(repr(modname), end=" ")
if mod != modname:
print("({!r})".format(mod), end=" ")
print("in", repr(pn))
SCS8_RE = re.compile('^scs8[^_]*(_|$)')
S8_RE = re.compile('^s8[^_]+(_|$)')
WEIRD_LIBS = [
'efs8',
"s8_esd",
's8phirs_10r',
"efab_lib",
"s8_spice_models",
]
def lib_extract_from_name(mod):
"""
>>> lib_extract_from_name('abc')
(None, 'abc')
>>> lib_extract_from_name('a_s8_c')
(None, 'a_s8_c')
>>> lib_extract_from_name('s8_blah_c')
(None, 's8_blah_c')
>>> lib_extract_from_name('scs8hvl_abc')
('scs8hvl', 'abc')
>>> lib_extract_from_name('s8rf_random')
('s8rf', 's8rf_random')
>>> lib_extract_from_name('s8rf2_xcmvpp11p5x11p7_m4shield')
('s8rf2', 's8rf2_xcmvpp11p5x11p7_m4shield')
>>> lib_extract_from_name('efs8_pads')
('efs8', 'pads')
>>> lib_extract_from_name('scs8hd_xor3_4')
('scs8hd', 'xor3_4')
>>> lib_extract_from_name('s8iom0s8_abc')
('s8iom0s8', 'abc')
>>> lib_extract_from_name('s8_esd_Li_40K_2_RevA_south')
('s8_esd', 'Li_40K_2_RevA_south')
>>> lib_extract_from_name('s8_esd_gnd2gnd_120x2_lv_isosub')
('s8_esd', 'gnd2gnd_120x2_lv_isosub')
>>> lib_extract_from_name('s8blerf_rx_au_LNA_ind_shield_m1')
(None, 's8blerf_rx_au_LNA_ind_shield_m1')
>>> # was ('s8blerf', 'rx_au_LNA_ind_shield_m1')
>>> lib_extract_from_name('s8phirs_10r_x')
('s8phirs_10r', 'x')
>>> lib_extract_from_name('efs8hd_a211oi_2')
('scs8hd', 'a211oi_2')
>>> lib_extract_from_name('scs8hs')
('scs8hs', None)
>>> lib_extract_from_name('')
(None, '')
>>> lib_extract_from_name(None)
(None, None)
>>> lib_extract_from_name('s8blref_xind4_01')
(None, 's8blref_xind4_01')
"""
if not mod:
return (None, mod)
mod = EFS8_RE.sub('scs8\\1', mod)
if mod.startswith('scs8'):
m = SCS8_RE.search(mod)
if m:
lib = m.group(0)
if lib.endswith('_'):
return (lib[:-1], mod[len(lib):])
else:
return (lib, None)
if mod.startswith('s8rf2'):
return ('s8rf2', mod)
if mod.startswith('s8rf'):
return ('s8rf', mod)
if mod.startswith('s8') and not mod.startswith('s8p') and not mod.startswith('s8bl'):
m = S8_RE.search(mod)
if m:
lib = m.group(0)
if lib.endswith('_'):
return (lib[:-1], mod[len(lib):])
else:
return (lib, None)
for l in WEIRD_LIBS:
if not mod.startswith(l+'_'):
continue
return (l, mod[len(l)+1:])
return (None, mod)
VERSION_RE = re.compile('[vV]([0-9]+)\.([0-9]+)\.([0-9I])')
def version_extract_from_path(pn):
"""
>>> version_extract_from_path("/home/tansell/github/google/skywater-pdk/s8/V1.2.1/VirtuosoOA/libs/s8rf/s8rf_nhv_W3p0_L0p5_M10_b/extracted/master.tag")
(1, 2, 1)
>>> version_extract_from_path("/home/tansell/github/google/skywater-pdk/s8/v1.2.1/VirtuosoOA/libs/s8rf/s8rf_nhv_W3p0_L0p5_M10_b/extracted/master.tag")
(1, 2, 1)
>>> version_extract_from_path("/home/tansell/github/google/skywater-pdk/scs8hd/V0.0.I/verilog/scs8hd_sdlclkp_2.v")
(0, 0, 'I')
"""
m = VERSION_RE.search(pn)
if not m:
return None
p1 = int(m.group(1))
p2 = int(m.group(2))
p3 = m.group(3)
try:
p3 = int(p3)
except ValueError:
pass
return (p1, p2, p3)
Corner = namedtuple("Corner", "mod volts temps extra")
class CornerFlag(enum.Flag):
t = 'Typical' # all nominal (typical) values
f = 'Fast' # fast, that is, values that make transistors run faster
s = 'Slow' # slow
# "fs" is sometimes called "wp" meaning worst-case power,
# "sf" is sometimes called "ws" meaning worst-case speed
nointpr = 'No internal power'
lv = 'Low voltage'
ccsnoise = 'Composite Current Source Noise'
VOLTS_RE = re.compile('([\-pn+])?([0-9]+[.p][0-9]+)([vV]|lv)')
TEMP_RE = re.compile('([\-pn+])?([0-9]+)[Cc]')
def _sign(c, v):
if c is None:
return v
assert len(c) == 1, c
if c in '+p':
return v
elif c in '-n':
return -v
else:
raise ValueError('Unknown sign value:', repr(c))
def corners_extract_from_filename_lib(fn):
"""
>>> corners_extract_from_filename('s8iom0s8_top_sio_macro_ffss_1.95v_1.65v_-40C.lib')
('s8iom0s8', Corner(mod='top_sio_macro', volts=(1.95, 1.65), temps=(-40.0,), extra=('ffss',)))
>>> corners_extract_from_filename('s8iom0s8_top_pwrdetv2_ssff_1.60v_5.50v_1.65v_-40C.lib')
('s8iom0s8', Corner(mod='top_pwrdetv2', volts=(1.6, 5.5, 1.65), temps=(-40.0,), extra=('ssff',)))
# FIXME: Check with Tim Edwards
>>> corners_extract_from_filename('scs8hvl_tt_3.3v_lowhv_3.3v_lv_1.8v_100C.lib')
('scs8hvl', Corner(mod=None, volts=(3.3, 'lowhv', 3.3, 'lv', 1.8), temps=(100.0,), extra=('tt',)))
>>> corners_extract_from_filename('s8iom0s8_top_amuxsplitv2_ff_ff_1p95v_x_1p65v_100C.lib')
('s8iom0s8', Corner(mod='top_amuxsplitv2', volts=(1.95, 'x', 1.65), temps=(100.0,), extra=('ff', 'ff')))
>>> corners_extract_from_filename('s8iom0s8_top_axresv2_tt_1.80v_3.30v_3.30v_025C.lib')
('s8iom0s8', Corner(mod='top_axresv2', volts=(1.8, 3.3, 3.3), temps=(25.0,), extra=('tt',)))
>>> corners_extract_from_filename('s8iom0s8_top_gpio_ovtv2_ff_ff_1p95v_x_5p50v_n40C_nointpwr.lib')
('s8iom0s8', Corner(mod='top_gpio_ovtv2', volts=(1.95, 'x', 5.5, 'nointpwr'), temps=(-40.0,), extra=('ff', 'ff')))
>>> corners_extract_from_filename('scs8hs_ss_1.60v_-40C_ccsnoise.lib')
('scs8hs', Corner(mod=None, volts=(1.6,), temps=(-40.0,), extra=('ss', 'ccsnoise')))
"""
assert fn.endswith('.lib'), fn
base, ext = os.path.splitext(fn)
assert ext == '.lib', (base, ext, fn)
bits = base.split('_')
extras = []
unknown = []
voltages = []
temps = []
libcellspec = []
while len(bits) > 0:
last = bits.pop(-1)
if last == '1p56lv':
# HACK for scs8hdll/V0.1.0/lib/scs8hdll_ff_xx_1p56lv_hv_io_n40c_lrhc_tpl_PVT2VASTA20.lib
voltages.insert(0, 'lv')
voltages.insert(0, _sign(sign, 1.56))
continue
v = VOLTS_RE.match(last)
if v:
sign, value, unit = v.groups()
value = value.replace('p', '.')
value = value.strip('0')
if unit == 'lv':
voltages.insert(0, unit)
voltages.insert(0, _sign(sign, float(value)))
continue
# FIXME: Are these correct!?
if last in ('x', 'lv', 'hv', 'lowhv', 'nointpwr'):
voltages.insert(0, last)
# HACK for scs8hdll/V0.1.0/lib/scs8hdll_ff_xx_1p56lv_hv_io_n40c_lrhc_tpl_PVT2VASTA20.lib
assert VOLTS_RE.match(bits[-1]) or TEMP_RE.match(bits[-1]) or bits[-1] == '1p56lv', (bits[-1], last, bits[:-1])
continue
t = TEMP_RE.match(last)
if t:
sign, value = t.groups()
temps.insert(0, _sign(sign, float(value)))
continue
if last in ['ff', 'ssff', 'ffss', 'ss', 'tt', 'ccsnoise', '5ns', 'pwr', 'lkg']:
extras.insert(0, last)
continue
# unknown = bits+[last,]
# break
libcellspec.insert(0, last)
mergedextras = '_'.join(extras)
libname, modname = lib_extract_from_name('_'.join(libcellspec))
# mergedextras = mergedextras.replace(modname, '')
# mergedextras = mergedextras.replace(libname, '')
extras = [el for el in mergedextras.split('_') if el != '']
return libname, Corner(mod=modname, extra=tuple(extras), volts=tuple(voltages), temps=tuple(temps))
def explain(modname):
"""
a41oi_4
"41" probably means 4-input, with 1 input inverted.
(The only real way to tell what it is is to look at the "function" line in a liberty file.)
the _4 refers to output drive strength. Relative to 1x.
aoi = and-or-invert.
oai = or-and-invert. (i.e., AND gates combined into a NOR gate, or OR gates combined into a NAND gate).
a2111oi_2
- 2111 refers to the AND part of AOI being 2 inputs,
- followed by 111, the three OR inputs.
I don't know if that is a universal standard, or if it really can work in all cases.
a41oi then I guess is (A1&A2&A3&A4) | (B1).
- That it is a two-output function is very unusual.
"b" refers to an inverted input. So a2bb2o_2 is an AND OR, 2 of the inputs are inverted
"b" is used in the flops, too, like "dfbbn".
- "r", "s", "t", "b" for the sets and resets
"""
def corners_extract_from_filename_cor(fn):
"""
>>> corners_extract_from_filename('nshort_rf_base_b.cor')
(None, Corner(mod='nshort', volts=(), temps=(), extra=('b', 'base', 'rf')))
>>> corners_extract_from_filename('nshort_rf_base_b_ff.cor')
(None, Corner(mod='nshort', volts=(), temps=(), extra=('b', 'base', 'ff', 'rf')))
>>> corners_extract_from_filename('nshort_rf_base_b_fs.cor')
(None, Corner(mod='nshort', volts=(), temps=(), extra=('b', 'base', 'fs', 'rf')))
>>> corners_extract_from_filename('nshort_rf_base_b_leak.cor')
(None, Corner(mod='nshort', volts=(), temps=(), extra=('b', 'base', 'leak', 'rf')))
>>> corners_extract_from_filename('nshort_rf_base_b_sf.cor')
(None, Corner(mod='nshort', volts=(), temps=(), extra=('b', 'base', 'rf', 'sf')))
>>> corners_extract_from_filename('nshort_rf_base_b_tt.cor')
(None, Corner(mod='nshort', volts=(), temps=(), extra=('b', 'base', 'rf', 'tt')))
>>> corners_extract_from_filename('nshort_rf_base_b_tt_leak.cor')
(None, Corner(mod='nshort', volts=(), temps=(), extra=('b', 'base', 'leak', 'rf', 'tt')))
>>> corners_extract_from_filename('nshort_rf_base_b_ttcorreln.cor')
(None, Corner(mod='nshort', volts=(), temps=(), extra=('b', 'base', 'rf', 'ttcorreln')))
>>> corners_extract_from_filename('nshort_rf_base_b_ttcorrelp.cor')
(None, Corner(mod='nshort', volts=(), temps=(), extra=('b', 'base', 'rf', 'ttcorrelp')))
>>> corners_extract_from_filename('nshort_rf_base_b_wafer.cor')
(None, Corner(mod='nshort', volts=(), temps=(), extra=('b', 'base', 'rf', 'wafer')))
>>> corners_extract_from_filename('nshort_rf_mm.cor')
(None, Corner(mod='nshort', volts=(), temps=(), extra=('mm', 'rf')))
>>> corners_extract_from_filename('ff_subvtmm.cor')
(None, Corner(mod='subvtmm', volts=(), temps=(), extra=('ff',)))
>>> corners_extract_from_filename('ff_nonfet.cor')
(None, Corner(mod='nonfet', volts=(), temps=(), extra=('ff',)))
>>> corners_extract_from_filename('ff_discrete.cor')
(None, Corner(mod='discrete', volts=(), temps=(), extra=('ff',)))
>>> corners_extract_from_filename('ndiode.cor')
(None, Corner(mod='ndiode', volts=(), temps=(), extra=()))
>>> corners_extract_from_filename('ndiode_h.cor')
(None, Corner(mod='ndiode', volts=(), temps=(), extra=('h',)))
>>> corners_extract_from_filename('ndiode_hvt.cor')
(None, Corner(mod='ndiode', volts=(), temps=(), extra=('hvt',)))
>>> corners_extract_from_filename('ndiode_lvt.cor')
(None, Corner(mod='ndiode', volts=(), temps=(), extra=('lvt',)))
>>> corners_extract_from_filename('ndiode_native.cor')
(None, Corner(mod='ndiode', volts=(), temps=(), extra=('native',)))
>>> corners_extract_from_filename('nvhv_subcircuit.cor')
(None, Corner(mod='nvhv', volts=(), temps=(), extra=('subcircuit',)))
>>> corners_extract_from_filename('ss_discrete.cor')
(None, Corner(mod='discrete', volts=(), temps=(), extra=('ss',)))
"""
assert fn.endswith('.cor'), fn
base, ext = os.path.splitext(fn)
assert ext == '.cor', (base, ext, fn)
bits = base.split('_')
types = [
'f', # Fast
's', # Slow
't', # Typical
'ff', # Fast Fast
'fs', # Fast Slow
'sf', # Slow Fast
'ss', # Slow Slow
'tt', # Typical Typical
'leak',
'ttcorreln', # Typical Typical + correlated + n?
'ttcorrelp', # Typical Typical + correlated + n?
'b',
'base', # Should this be `base_b`?
'wafer',
#'nonfet',
#'discrete',
#'subvtmm',
'mm',
'hvt', # High Voltage?
'lvt', # Low Voltage?
'h',
'native',
'subcircuit',
'rf',
]
other = []
extra = []
while len(bits) > 0:
b = bits.pop(-1)
if b in types:
extra.insert(0, b)
else:
other.insert(0, b)
extra.sort()
return None, Corner(mod="_".join(other), extra=tuple(extra), volts=tuple(), temps=tuple())
def corners_extract_from_filename(fn):
if fn.endswith('.lib'):
return corners_extract_from_filename_lib(fn)
elif fn.endswith('.cor'):
return corners_extract_from_filename_cor(fn)
else:
raise ValueError("Don't know how to process"+fn)
#nlowvt_w1p65_l0p25_m4
def _libnames_fixup(libnames):
def _remove(s, o=None):
if s not in libnames:
return
if o and o not in libnames:
return
libnames.remove(s)
_remove(None)
# Always remove...
_remove('efab_lib')
_remove('scs8lpa')
_remove('s8-generic')
_remove('s8gds')
_remove('s8io_m0s8')
_remove('scs8')
_remove('techLEF')
# Remove if better name exists...
_remove('efs8', 'efs8_pads')
_remove('s8-generic', 's8x')
_remove('s8io', 's8iom0s8')
_remove('s8iom0', 's8iom0s8')
_remove('s8iom0s8', 's8_esd')
_remove('s8iom0s8', 's8rf')
_remove('s8iom0s8', 's8rf2')
# FIXME: check this
_remove('s8blerf', 'primdev')
_remove('s8blref', 'primdev')
_remove('primdev', 's8rf')
_remove('primdev', 's8rf2')
to_remove = set()
for l in libnames:
if l.endswith('_dv'):
to_remove.add(l)
for l in libnames:
if not l.startswith('scs8_'):
continue
oname = 'scs8'+l[4:]
if oname in libnames:
to_remove.add(l)
for l in to_remove:
libnames.remove(l)
PATHS = [
"models",
#'liberty',
'cdl',
'gds',
'hspice',
'lef',
'mag',
'maglef',
'spi',
'spice',
'sue',
'techLEF',
'verilog',
]
def mod_extract_from_path(pn):
"""
>>> mod_extract_from_path('/home/tansell/github/google/skywater-pdk/s8/V1.0.0/VirtuosoOA/libs/s8phirs_10r/L1_T/symbolic/layout.oa')
('s8phirs_10r', 'l1_t')
>>> mod_extract_from_path('/home/tansell/github/google/skywater-pdk/s8/V1.0.0/MODELS/SPECTRE/s8x/Models/sonos_ffeol.cor')
('s8x', 'sonos_ffeol')
>>> mod_extract_from_path('/home/tansell/github/google/skywater-pdk/s8/V1.0.0/VirtuosoOA/libs/s8rf/nshort_W1p65_L0p15_M4/data.dm')
('s8rf', 'nshort_w1p65_l0p15_m4')
>>> mod_extract_from_path('/home/tansell/github/google/skywater-pdk/s8/V1.0.0/MODELS/SPECTRE/s8phirs_10r/examples/bjt_netlist.scs')
(None, None)
>>> mod_extract_from_path('/home/tansell/github/google/skywater-pdk/s8/V1.0.0/VirtuosoOA/libs/display.drf')
(None, None)
>>> mod_extract_from_path('/home/tansell/github/google/skywater-pdk/s8/V1.0.0/VirtuosoOA/libs/tech/zfilter/verilog/symbol.oa')
('???', 'zfilter')
>>> mod_extract_from_path('/home/tansell/github/google/skywater-pdk/s8/V1.0.0/VirtuosoOA/libs/technology_library/CAPMM4/symbolic/master.tag')
('???', 'capmm4')
>>> mod_extract_from_path('/home/tansell/github/google/skywater-pdk/s8iom0s8/V0.0.0/lib/s8iom0s8_top_sio_macro_ffss_1.95v_1.65v_-40C.lib')
('s8iom0s8', 'top_sio_macro')
>>> mod_extract_from_path('/home/tansell/github/google/skywater-pdk/s8iom0s8/V0.0.0/lib/s8iom0s8_top_xres4v2_ff_ss_1p95v_x_1p95v_100C.lib')
('s8iom0s8', 'top_xres4v2')
>>> mod_extract_from_path('/home/tansell/github/google/skywater-pdk/s8iom0s8/V0.0.0/oa/s8_esd/s8_esd_Li_40K_2_RevA_south/layout/master.tag')
('s8_esd', 'li_40k_2_reva_south')
>>> mod_extract_from_path('/home/tansell/github/google/skywater-pdk/s8iom0s8/V0.0.0/oa/s8_esd/s8_esd_gnd2gnd_120x2_lv_isosub/symbol/symbol.oa')
('s8_esd', 'gnd2gnd_120x2_lv_isosub')
>>> mod_extract_from_path('/home/tansell/github/google/skywater-pdk/s8iom0s8/V0.0.0/oa/s8iom0s8/s8iom0s8_amx_xor/schematic/data.dm')
('s8iom0s8', 'amx_xor')
>>> mod_extract_from_path('/home/tansell/github/google/skywater-pdk/s8iom0s8/V0.0.0/oa/s8iom0s8_dv/s8rf_xcmvpp8p6x7p9_m3shield/layout/layout.oa')
('s8rf', 's8rf_xcmvpp8p6x7p9_m3shield')
>>> mod_extract_from_path('/home/tansell/projects/efabless/tech/SW/EFS8A/libs.ref/efs8_pads/mag/efs8_pads.mag')
('efs8_pads', None)
>>> mod_extract_from_path('/home/tansell/projects/efabless/tech/SW/EFS8A/libs.ref/efs8_pads/mag/analog200ohm_pad.mag')
('efs8_pads', 'analog200ohm_pad')
>>> mod_extract_from_path('/home/tansell/projects/efabless/tech/SW/EFS8A/libs.ref/primdev/mag/s8blerf_rx_au_LNA_ind_shield_m1.mag')
('primdev', 's8blerf_rx_au_lna_ind_shield_m1')
>>> # Was ('primdev', 'rx_au_lna_ind_shield_m1')
>>> mod_extract_from_path('/home/tansell/projects/efabless/tech/SW/EFS8A/libs.ref/primdev/mag/s8blref_xind4_01.mag')
('primdev', 's8blref_xind4_01')
>>> # Was ('primdev', 'xind4_01')
>>> mod_extract_from_path('/home/tansell/gob/tims-openpdk-repo/s8/libraries/s8_osu130/sue/MUX2X1.sue')
('s8_osu130', 'mux2x1')
>>> mod_extract_from_path('/home/tansell/gob/tims-openpdk-repo/s8/libraries/efs8hd/lef/efs8hd_a211oi_2.lef')
('scs8hd', 'a211oi_2')
>>> mod_extract_from_path('/home/tansell/gob/ef2/s8spice/home/uzmn/s8/cypress_models/spice/correl3.cor')
('s8spice', 'correl3')
>>> mod_extract_from_path('/home/tansell/github/google/skywater-pdk/s8iom0s8/V0.2.0/verilog/s8iom0s8_top_ground_hvc_wpad.v')
('s8iom0s8', 'top_ground_hvc_wpad')
>>> mod_extract_from_path('/home/tansell/github/google/skywater-pdk/s8/V1.0.0/VirtuosoOA/libs/s8phirs_10r/bkeep_p/verilog/verilog.v')
('s8phirs_10r', 'bkeep_p')
>>> mod_extract_from_path('/home/tansell/gob/openpdk2/s8/custom/scs8hd/cdl/scs8hd_clkdlybuf4s18_2.cdl')
('scs8hd', 'clkdlybuf4s18_2')
>>> mod_extract_from_path('/home/tansell/gob/tims-openpdk-repo/s8/libraries/efs8hd/hspice/efs8hd_xor3_2.spi')
('scs8hd', 'xor3_2')
>>> mod_extract_from_path('/home/tansell/github/google/skywater-pdk/s8/V1.0.0/VirtuosoOA/SKILL/001-59001/s8/s8x/info/s8-generic.il')
('s8x', None)
>>> mod_extract_from_path('/home/tansell/gob/openpdk2/s8/custom/techLEF/scs8hd.lef')
('scs8hd', None)
>>> mod_extract_from_path('/home/tansell/gob/tims-openpdk-repo/s8/libraries/efs8hd/hspice/efab_lib_a2111oi_2.spi')
('scs8hd', 'a2111oi_2')
>>> mod_extract_from_path('/home/tansell/projects/efabless/tech/SW/EFS8A/libs.tech/models/fnpass.pm3')
(None, 'fnpass')
>>> mod_extract_from_path('/home/tansell/github/google/skywater-pdk/s8/V1.0.1/VirtuosoOA/libs/s8rf2/s8rf2_xcmvpp11p5x11p7_m3_lim5shield/extracted/spice.sp')
('s8rf2', 's8rf2_xcmvpp11p5x11p7_m3_lim5shield')
>>> mod_extract_from_path('/home/tansell/github/google/skywater-pdk/scs8hd/V0.0.I/hspice/scs8hd_clkdlybuf4s50_1.sp')
('scs8hd', 'clkdlybuf4s50_1')
>>> mod_extract_from_path('/home/tansell/gob/openpdk2/s8/skywater/s8_spice_models/pshort_rf_base_b_wafer.cor')
('s8_spice_models', 'pshort_rf_base_b_wafer')
>>> mod_extract_from_path('/home/tansell/github/google/skywater-pdk/s8/V1.0.0/VirtuosoOA/libs/s8phirs_10r/CAP2MM5/symbolic/thumbnail_128x128.png')
('s8phirs_10r', 'cap2mm5')
>>> mod_extract_from_path('/home/jjatczak/Dokumenty/SkyWater/skywater-src-nda/s8iom0s8/V0.2.1/verilog/s8iom0s8_top_tp1.v')
('s8iom0s8', 'top_tp1')
>>> mod_extract_from_path('/home/jjatczak/Dokumenty/SkyWater/skywater-src-nda/s8iom0s8/V0.0.0/oa/s8_esd/s8_esd_gnd2gnd_120x2_lv/behavioral/verilog.v')
('s8_esd', 'gnd2gnd_120x2_lv')
>>> mod_extract_from_path('/home/jjatczak/Dokumenty/SkyWater/skywater-src-nda/s8/V1.3.0/VirtuosoOA/libs/s8phirs_10r_old/bkeep_p/verilog/verilog.v')
('s8phirs_10r_old', 'bkeep_p')
"""
if 'examples' in pn:
return None, None
pn = EFS8_RE.sub('scs8\\1', pn)
bits = pn.split('/')
fname = bits.pop(-1)
fbase, ext = os.path.splitext(fname)
libnames = set()
modnames = set()
file_lib, file_mod = lib_extract_from_name(fbase)
if file_lib:
libnames.add(file_lib)
# Files with corners...
if ext == '.lib':
libname, corner = corners_extract_from_filename(fname)
libnames.add(libname)
if corner.mod:
modnames.add(corner.mod)
# Look for s8 / scs8 type library names
bitresult = None
for b in bits:
m = SCS8_RE.match(b)
if not m:
continue
lib = m.group(0)
if lib.endswith('_'):
bitresult = lib[:-1]
else:
bitresult = lib
if bitresult is not None and len(libnames) == 1:
core = list(libnames)[0]
if core in bitresult:
libnames.pop()
libnames.add(bitresult)
for b in bits:
m = S8_RE.match(b)
if not m:
continue
lib = m.group(0)
if not lib.startswith('s8p'):
if lib.endswith('_'):
libnames.add(lib[:-1])
else:
libnames.add(lib)
for b in bits:
if len(b) <= 2:
continue
if '_' not in b:
if b.startswith('s8'):
libnames.add(b)
if b.startswith('scs8'):
libnames.add(b)
try:
libdir = bits.index('libraries')
if len(bits) > libdir+1:
libnames.add(bits[libdir+1])
if len(bits) > libdir+2 and bits[libdir+2] in PATHS:
libnames.add(bits[libdir+1])
modnames.add(fbase)
except ValueError:
pass
try:
libdir = bits.index('s8spice')
modnames.add(fbase)
except ValueError:
pass
# OpenAccess / Specter directories
if 'VirtuosoOA' not in bits:
if bits[-1] == 'verilog' and ext == '.v' and not fbase == 'verilog':
modnames.add(fbase)
try:
i = bits.index('custom')
if len(bits) > i+1:
libnames.add(bits[i+1])
modnames.add(fbase)
except ValueError:
pass
try:
i = bits.index('VirtuosoOA')
if bits[i+1] == 'libs':
if len(bits) > i+2:
libname = bits[i+2]
if libname in ('technology_library', 'tech'):
libname = '???'
libnames.add(libname)
if len(bits) > i+3:
modname = bits[i+3]
modnames.add(modname)
except ValueError:
pass
try:
i = bits.index('oa')
if len(bits) > i+1:
libnames.add(bits[i+1])
if len(bits) > i+2:
modnames.add(bits[i+2])
except ValueError:
pass
try:
i = bits.index('SPECTRE')
if len(bits) > i+2:
assert bits[i+2] == "Models", (bits[i+2], bits, pn)
libnames.add(bits[i+1])
modnames.add(fbase)
except ValueError:
pass
# efabless / magic structure
try:
libdir = bits.index('libs.ref')
if len(bits) > libdir+2 and bits[libdir+2] in PATHS:
libnames.add(bits[libdir+1])
modnames.add(fbase)
if len(bits) > libdir+2 and bits[libdir+1] in PATHS:
libnames.add(bits[libdir+2])
modnames.add(fbase)
except ValueError:
pass
try:
libdir = bits.index('libs.tech')
if bits[libdir+1] in PATHS:
modnames.add(fbase)
except ValueError:
pass
try:
libdir = bits.index('s8_spice_models')
libnames.add('s8_spice_models')
modnames.add(fbase)
except ValueError:
pass
modlibnames = list(modnames)
modnames = set()
for modlibname in modlibnames:
libname, modname = lib_extract_from_name(modlibname)
if modname:
modnames.add(cleanup_module_name(modname))
if not modnames and file_mod and "_" in file_mod:
modnames.add(file_mod)
modname = None
if modnames:
assert len(modnames) == 1, (modnames, pn)
modname = modnames.pop()
_libnames_fixup(libnames)
libname = None
if libnames:
assert len(libnames) == 1, (libnames, pn)
libname = libnames.pop()
libname = libname.replace('-rechar', '')
return libname, modname
def lib_extract_from_path(pn):
"""
>>> lib_extract_from_path('/home/tansell/gob/tims-openpdk-repo/s8/libraries/efs8_pads/lef/efs8_pads.lef')
'efs8_pads'
>>> lib_extract_from_path('/home/tansell/github/google/skywater-pdk/s8iom0s8/V0.0.0/lef/s8iom0s8.lef')
's8iom0s8'
>>> lib_extract_from_path('/home/tansell/gob/ef-skywater-s8/EFS8A/libs.ref/lef/primdev/primdev.lef')
'primdev'
>>> lib_extract_from_path('/home/tansell/gob/tims-openpdk-repo/s8/libraries/s8_osu130/lef/s8_osu130.lef')
's8_osu130'
>>> lib_extract_from_path('/home/tansell/github/google/skywater-pdk/s8/V1.0.0/MODELS/SPECTRE/s8x/Models/xcmvpp_ponly.mod')
's8x'
>>> lib_extract_from_path('/home/tansell/github/google/skywater-pdk/s8/V1.0.1/VirtuosoOA/libs/s8rf2/s8rf2_xcmvpp11p5x11p7_m3_lim5shield/extracted/spice.sp')
's8rf2'
>>> lib_extract_from_path('/home/tansell/gob/openpdk2/s8/skywater/s8_spice_models/xcmvpp_all_cu.mod')
's8_spice_models'
>>> lib_extract_from_path('/home/tansell/github/google/skywater-pdk/scs8hs/V0.0.0/lib/scs8hs_ff_1.56v_-40C.lib')
'scs8hs'
>>> lib_extract_from_path('/home/tansell/gob/ef2/matt/scs8hd_tt_1.80v_25C_rechar.lib')
'scs8hd'
>>> lib_extract_from_path('/home/tansell/github/google/skywater-pdk/s8/V1.0.0/VirtuosoOA/libs/s8phirs_10r/pfetextd/spectre/data.dm')
's8phirs_10r'
>>> lib_extract_from_path('/home/tansell/github/google/skywater-pdk/s8iom0s8/V0.0.0/oa/s8io_m0s8/.oalib')
's8iom0s8'
>>> lib_extract_from_path('/home/tansell/github/google/skywater-pdk/s8iom0s8/V0.0.0/lib/s8iom0s8_top_xres4v2_ff_ss_1p95v_x_1p95v_100C.lib')
's8iom0s8'
>>> lib_extract_from_path('/home/tansell/github/google/skywater-pdk/s8iom0s8/V0.0.0/oa/s8_esd/s8_esd_Li_40K_2_RevA_south/layout/master.tag')
's8_esd'
>>> lib_extract_from_path('/home/tansell/github/google/skywater-pdk/s8iom0s8/V0.0.0/oa/s8_esd/s8_esd_gnd2gnd_120x2_lv_isosub/symbol/symbol.oa')
's8_esd'
>>> lib_extract_from_path('/home/tansell/github/google/skywater-pdk/s8iom0s8/V0.0.0/oa/s8iom0s8/s8iom0s8_amx_xor/schematic/data.dm')
's8iom0s8'
>>> lib_extract_from_path('/home/tansell/github/google/skywater-pdk/s8iom0s8/V0.0.0/oa/s8iom0s8_dv/s8rf_xcmvpp8p6x7p9_m3shield/layout/layout.oa')
's8rf'
>>> lib_extract_from_path('/home/tansell/github/google/skywater-pdk/scs8hd/V0.0.1/oa/scs8_hd/.oalib')
'scs8hd'
>>> lib_extract_from_path('/home/tansell/projects/efabless/tech/SW/EFS8A/libs.ref/efs8_pads/mag/efs8_pads.mag')
'efs8_pads'
>>> lib_extract_from_path('/home/tansell/projects/efabless/tech/SW/EFS8A/libs.ref/efs8_pads/mag/analog200ohm_pad.mag')
'efs8_pads'
"""
libname, modname = mod_extract_from_path(pn)
return libname
def global_debug(*args):
pass
def files(filetypes):
"""Return the list of files from the .files cache."""
if len(sys.argv) > 1:
global global_debug
global_debug = print
for i in sys.argv[1:]:
yield i
return
if filetypes == 'spice':
fname = '.files.spice'
err = 'run files-spice.py'
exts = []
else:
assert isinstance(filetypes, (list, tuple)), repr(filestypes)
fname = '.files'
err = 'run files-list.py'
exts = filetypes
assert os.path.exists(fname), "Missing {}, {}".format(fname, err)
files = False
for l in open(fname):
l = l.strip()
if exts:
_, ext = os.path.splitext(l)
if ext not in exts:
continue
files = True
assert os.path.exists(l), "{} file missing, {}".format(l, err)
yield l
assert files, "No files found!? {}".format(filetypes)
COMMENTS = {
'c': (['//'], [('/*','*/')]),
'spice': (['//', '\n*'], [('/*','*/')]),
'shell': (['#'], []),
}
def read_without_cmts(file, comments, debug=None):
""""
>>> s = '''\
... no comment
... end comment // here
... // full line end comment
... /* full line multiline comment */
... before /* line one
... line two
... line three */ after
... before /* inside */ after
... a/*b*/c/*d*/
... /*a*/b/*c*//*d*/
... /*a*//*b*//*c*/d/*e*/
... '''
>>> for l in read_without_cmts(io.StringIO(s), 'c'):
... print(repr(l))
' ... no comment'
'end comment '
'before after'
'before after'
'ac'
'b'
'd'
>>> for l in read_without_cmts(io.StringIO(s), [['/*'], []]):
... print(repr(l))
' ... no comment'
'end comment // here'
'// full line end comment'
'before '
' line two'
' line three */ after'
'before '
'a'
>>> for l in read_without_cmts(io.StringIO(s), [[], [('/','/')]]):
... print(repr(l))
' ... no comment'
'end comment here'
' full line end comment'
'before after'
'before after'
'ac'
'b'
'd'
"""
if isinstance(comments, str):
cmt_lines, cmt_multilines = COMMENTS[comments]
else:
cmt_lines, cmt_multilines = comments
if not debug:
debug = global_debug
if not hasattr(file, 'read'):
file = open(file)
data = file.read()
if cmt_multilines:
re_multiline = []
for start, end in cmt_multilines:
# ..SSxxEE..
re_multiline.append('({}(.*?){})'.format(re.escape(start), re.escape(end)))
data = re.sub('|'.join(re_multiline), '', data, flags=re.DOTALL)
for l in data.splitlines():
# Remove single line comments
# ^..SSxxx$
for cmt_line in cmt_lines:
cmt_start_pos = l.find(cmt_line)
if cmt_start_pos > -1:
debug("cmt", cmt_line, repr(l[cmt_start_pos:]))
l = l[:cmt_start_pos]
if l:
debug(' >', repr(l))
yield l
def read_cmts(file, comments, debug=None, include_delimeters=False):
""""
>>> s = '''\
... no comment
... end comment // here1
... end comment // here2
... // full line end comment1
... // full line end comment2
... /* full line multiline comment */
... before /* line one
... line two
... line three */ after
... before /* inside */ after
... a/*b*/c/*d*/
... /*a*/b/*c*//*d*/
... /*a*//*b*//*c*/d/*e*/
... /* a1
... * a2
... * a3
... * a4
... */
... /* b1
... * b2
... * b3
... * b4
... */
... /* list intro
... * list 1
... * list 2
... */
... '''
>>> for l in read_cmts(io.StringIO(s), 'c', include_delimeters=True):
... print(repr(l))
'// here1\\n// here2\\n'
'// full line end comment1\\n// full line end comment2\\n'
'/* full line multiline comment */'
'/* line one\\n line two\\n line three */'
'/* inside */'
'/*b*/'
'/*d*/'
'/*a*/'
'/*c*/'
'/*d*/'
'/*a*/'
'/*b*/'
'/*c*/'
'/*e*/'
'/* a1\\n * a2\\n * a3\\n * a4\\n */'
'/* b1\\n * b2\\n * b3\\n * b4\\n */'
'/* list intro\\n * list 1\\n * list 2\\n */'
>>> for l in read_cmts(io.StringIO(s), 'c', include_delimeters=False):
... print(repr(l))
'here1\\nhere2\\n'
'full line end comment1\\nfull line end comment2\\n'
'full line multiline comment'
'line one\\nline two\\nline three'
'inside'
'b'
'd'
'a'
'c'
'd'
'a'
'b'
'c'
'e'
'a1\\na2\\na3\\na4\\n'
'b1\\nb2\\nb3\\nb4\\n'
'list intro\\n* list 1\\n* list 2\\n'
"""
if isinstance(comments, str):
cmt_lines, cmt_multilines = COMMENTS[comments]
else:
cmt_lines, cmt_multilines = comments
if not debug:
debug = global_debug
if not hasattr(file, 'read'):
file = open(file)
data = file.read()
cmt_delimiters = []
for cmt_start in cmt_lines:
cmt_delimiters.append((cmt_start, '\n'))
cmt_delimiters.extend(cmt_multilines)
unprocessed = data
in_comment = False
comments_pending = []
comments_pending_type = ''
while unprocessed:
next_start_pos = {}
for cmt_start, cmt_end in cmt_delimiters:
start_pos = unprocessed.find(cmt_start)
if start_pos > -1:
assert start_pos not in next_start_pos, (start_pos, next_start_pos)
next_start_pos[start_pos] = (cmt_start, cmt_end)
debug(' cmt', next_start_pos)
if not next_start_pos:
next_start_pos[len(unprocessed)] = ('', '')
min_start_pos = min(next_start_pos.keys())
cmt_start, cmt_end = next_start_pos[min_start_pos]
non_comment, unprocessed = unprocessed[:min_start_pos], unprocessed[min_start_pos:]
if non_comment:
lastpos = non_comment.rfind('\n')
if lastpos > -1:
lastline = non_comment[lastpos+1:]
else:
lastline = non_comment
comments_current_type = 'X'*len(lastline)+cmt_start
else:
comments_current_type = cmt_start
if comments_pending and (cmt_end != '\n' or comments_current_type != comments_pending_type):
# Flush out any pending comments
assert comments_pending, (comments_pending, comments_pending_type, comments_current_type)
if not include_delimeters:
lines = ''.join(comments_pending).splitlines(True)
# FIXME: This is a hack....
rpos = comments_pending_type.rfind('X')
if len(comments_pending_type) > (rpos+2) and comments_pending_type[rpos+2] == '*':
rpos += 2
prefix = ' '*rpos+'*'
for i in range(1, len(lines)):
if lines[i].startswith(prefix):
lines[i] = lines[i][len(prefix):]
comment = lines[0].lstrip()
comment += textwrap.dedent(''.join(lines[1:]))
comment = comment.strip(' \t')
else:
comment = ''.join(comments_pending)
debug(' cmt', repr(comment))
yield comment
comments_pending.clear()
if non_comment:
for l in non_comment.split('\n'):
debug(' >', repr(l))
if not unprocessed:
break
next_end_pos_a = unprocessed.find(cmt_end)
if next_end_pos_a < 0:
debug(' !!!', 'Missing %r for closing %r' % (cmt_end, cmt_start))
next_end_pos_a = len(unprocessed)
next_end_pos_b = next_end_pos_a+len(cmt_end)
next_start_pos_a = len(cmt_start)
if include_delimeters:
next_start_pos_a = 0
next_end_pos_a += len(cmt_end)
elif cmt_end == '\n':
next_end_pos_a += 1
comment, unprocessed = unprocessed[next_start_pos_a:next_end_pos_a], unprocessed[next_end_pos_b:]
comments_pending.append(comment)
comments_pending_type = comments_current_type
def write_mod_json(name, mod2files, broken):
mod2files = dict(mod2files)
for f in mod2files:
mod2files[f] = tuple(sorted(mod2files[f]))
pprint.pprint(mod2files)
if broken:
print()
print("Broken:")
for fname in broken:
print(fname)
if len(sys.argv) > 1:
return
with open('.files.mod2files.{}.json'.format(name), 'w') as f:
json.dump(mod2files, f, indent=' ', sort_keys=True)
with open('.files.mod2files.{}.broken'.format(name), 'w') as f:
for fname in broken:
f.write(fname)
f.write('\n')
def print_files():
unused = []
errors = []
for pn in files([]):
v = version_extract_from_path(pn)
if not v:
v = (0, 0, '?')
l1 = lib_extract_from_path(pn)
l2, m2 = mod_extract_from_path(pn)
if not l1 and not l2 and not m2:
unused.append(pn)
continue
if l1 and l2 and l1 != l2:
errors.append((l1, l2, m2, pn))
print("%i.%i.%s,%-11s,%-11s,%-40s,%s" % (*v, l1, l2, m2, pn))
def convert_libname(l):
"""
>>> # Library Names table
>>> convert_libname('s8phirs_10r')
'sky130_fd_pr_base'
>>> convert_libname('s8x')
'sky130_fd_pr_base'
>>> convert_libname('s8rf')
'sky130_fd_pr_rf'
>>> convert_libname('s8rf2')
'sky130_fd_pr_rf2'
>>> convert_libname('scs8hd')
'sky130_fd_sc_hd'
>>> convert_libname('scs8hdll')
'sky130_fd_sc_hdll'
>>> convert_libname('scs8hs')
'sky130_fd_sc_hs'
>>> convert_libname('scs8ms')
'sky130_fd_sc_ms'
>>> convert_libname('scs8ls')
'sky130_fd_sc_ls'
>>> convert_libname('scs8lp')
'sky130_fd_sc_lp'
>>> convert_libname('s8iom0s8')
'sky130_fd_io'
>>> convert_libname('efs8_pads')
'sky130_ef_io'
>>> convert_libname('osu130')
'sky130_osu_sc'
>>> convert_libname('s8_esd')
'sky130_fd_io'
>>> convert_libname('s8')
'sky130_fd_pr_base'
>>> convert_libname('s8_spice_models')
'sky130_fd_pr_base'
"""
if l == None:
return ""
if l in ('s8phirs_10r', 's8', 's8x'):
return 'sky130_fd_pr_base'
if l in 's8_spice_models':
return 'sky130_fd_pr_base'
if l in ('s8rf', 's8rf2') :
return l.replace('s8rf', 'sky130_fd_pr_rf')
if l == 's8phirs_10r_old':
return 'sky130_fd_pr_base_old'
if l == 'efs8_pads':
return 'sky130_ef_io'
if l == 'osu130':
return 'sky130_osu_sc'
if l == 's8_osu130':
return 'sky130_osu'
if l == 's8iom0s8':
return 'sky130_fd_io'
if l == 's8_esd':
return 'sky130_fd_io'
if 'scs8' in l:
assert '_' not in l, l
return l.replace('scs8', 'sky130_fd_sc_')
if 's8' in l:
assert '_' not in l, l
return l.replace('s8', 'sky130_fd_')
STRENGTH_RE = re.compile(r'_([0-9]|[1-9][0-9]|lp|lp2|m)$')
def strip_strength(s):
"""
>>> strip_strength('XXXX_0')
'XXXX'
>>> strip_strength('XXXX_12')
'XXXX'
>>> strip_strength('XXXX_1')
'XXXX'
>>> strip_strength('XXXX_lp')
'XXXX'
>>> strip_strength('XXXX_lp2')
'XXXX'
>>> strip_strength('XXXX_m')
'XXXX'
>>> strip_strength('XXXX_ABC')
'XXXX_ABC'
>>> strip_strength('XXXX_12m')
'XXXX_12m'
>>> strip_strength('XXXX_02')
'XXXX_02'
"""
return STRENGTH_RE.sub('',s)
def patch_string(s, replacements):
if s in replacements:
return True, replacements[s]
for from_str, to_str in replacements.items():
new_str = s.replace(from_str, to_str)
if s != new_str:
return True, new_str
return False, s
def cell_directory(cell_fullname):
"""
"""
assert cell_fullname.startswith('sky130'), cell_fullname
assert '__' in cell_fullname, cell_fullname
lib_name, cell_name = cell_fullname.split('__', 1)
cell_name = strip_strength(cell_name)
return cell_name
def get_cell_directory(output_dir, libname, version, cellname):
"""
>>> get_cell_directory('output', 'sky130_fd_io', 'v0.0.1', 'sky130_fd_io__top_filter_narrow')
'output/skywater-pdk/libraries/sky130_fd_io/v0.0.1/cells/top_filter_narrow'
>>> get_cell_directory('output', 'sky130_fd_io', 'v0.0.1', 'sky130_fd_io__top_filter_narrow_1')
'output/skywater-pdk/libraries/sky130_fd_io/v0.0.1/cells/top_filter_narrow'
>>> get_cell_directory('output', 'sky130_fd_io', 'v0.0.1', 'sky130_fd_io__a2bb2oi_1')
'output/skywater-pdk/libraries/sky130_fd_io/v0.0.1/cells/a2bb2oi'
# ./libraries/sky130_fd_sc_lp/V0.0.0/cells/a211oi_lp/sky130_fd_sc_lp__a211oi_lp.gds
>>> get_cell_directory('output', 'sky130_fd_sc_lp', 'v0.0.1', 'sky130_fd_sc_lp__a211oi_lp')
'output/skywater-pdk/libraries/sky130_fd_sc_lp/v0.0.1/cells/a211oi'
# ./libraries/sky130_fd_sc_lp/V0.0.0/cells/a211oi_lp2/sky130_fd_sc_lp__a211oi_lp2.gds
>>> get_cell_directory('output', 'sky130_fd_sc_lp', 'v0.0.1', 'sky130_fd_sc_lp__a211oi_lp2')
'output/skywater-pdk/libraries/sky130_fd_sc_lp/v0.0.1/cells/a211oi'
# ./libraries/sky130_fd_sc_lp/V0.0.0/cells/a211oi_m/sky130_fd_sc_lp__a211oi_m.gds
>>> get_cell_directory('output', 'sky130_fd_sc_lp', 'v0.0.1', 'sky130_fd_sc_lp__a211oi_m')
'output/skywater-pdk/libraries/sky130_fd_sc_lp/v0.0.1/cells/a211oi'
>>>
"""
assert libname.startswith('sky130'), libname
assert version.startswith('v'), version
assert cellname.startswith(libname+'__'), (cellname, libname, (output_dir, libname, version, cellname))
cell_dir = cell_directory(cellname)
return f"{output_dir}/skywater-pdk/libraries/{libname}/{version}/cells/{cell_dir}"
def convert_cellname(old_cellname):
"""
>>> convert_cellname("xxxxx")
'xxxxx'
>>> convert_cellname("XXXXX")
'xxxxx'
>>> convert_cellname("m1m2_pr_cdns")
'm1m2_pr'
>>> convert_cellname("s8blref_xind4_01")
'rf_xind4_01'
>>> convert_cellname("L1M1_PR_CDNS_525975574200")
'l1m1_pr_525975574200'
convert_cellname("sky130_fd_pr_base__nlowvt_rf_base_m4_b_w5_lp18__models_nlowvt_rf_base_b.pm3")
>>> convert_cellname("s8blerf_sh_auVia$$182065196")
'rf_sh_auvia_182065196'
>>> convert_cellname("s8pir_10r_vcells_lvs")
'sky130_vcells_lvs'
>>> convert_cellname('S8BLAH')
'blah'
>>> convert_cellname('s8rf_pshort_W3p0_L0p15_2F')
'rf_pshort_w3p0_l0p15_2f'
"""
old_cellname = re.sub('S8PIR[_-]10R', 'sky130', old_cellname, flags=re.I)
old_cellname = old_cellname.lower()
old_cellname = old_cellname.replace('$$', '_')
old_cellname = old_cellname.replace('s8blerf_', 's8rf_')
old_cellname = old_cellname.replace('s8blref_', 's8rf_')
old_cellname = old_cellname.replace('s8', '')
old_cellname = old_cellname.replace('_cdns', '')
old_cellname = old_cellname.replace('cdns', '')
return old_cellname
def convert_pinname(old_pinname):
"""
>>> convert_pinname('CIN')
'CIN'
>>> convert_pinname('COUT')
'COUT'
>>> convert_pinname('AN')
'A_N'
>>> convert_pinname('QN')
'Q_N'
>>> convert_pinname('vnb')
'VNB'
>>> convert_pinname('vpb')
'VPB'
>>> convert_pinname("vpp_li_out")
'VPP_LI_OUT'
>>> convert_pinname("vpp_li_in")
'VPP_LI_IN'
>>> convert_pinname("vpp_li_in")
'VPP_LI_IN'
>>> convert_pinname('RESETB')
'RESET_B'
>>> convert_pinname('SETB')
'SET_B'
>>> convert_pinname('TEB')
'TE_B'
>>> convert_pinname('SLEEPB')
'SLEEP_B'
>>> convert_pinname('GATEN')
'GATE_N'
>>> convert_pinname('dnw_vpwr')
'DNW_VPWR'
>>> convert_pinname('dnw_vgnd')
'DNW_VGND'
>>> convert_pinname('kapwr')
'KAPWR'
>>> convert_pinname('clkneg')
'CLKNEG'
>>> convert_pinname('random string')
'random string'
>>> convert_pinname('vpp_5x4_gate')
'VPP_5X4_GATE'
>>> convert_pinname('vpwr1')
'VPWR1'
"""
s = old_pinname
if ' ' in s:
return s
if re.search('(^|_)v([a-z_0-9]+)(_|$)', s):
return s.upper()
if 'SETB' in s:
return s.replace('SETB', 'SET_B')
if 'TEB' in s:
return s.replace('TEB', 'TE_B')
if 'SLEEPB' in s:
return s.replace('SLEEPB', 'SLEEP_B')
if 'clk' in s:
return s.upper()
if 'pwr' in s:
return s.upper()
# Extract _N from things like GATEN
# QN -> Q_N
if re.match('^([A-Z]*[^I])[N]$', s):
return s[:-1]+'_'+s[-1]
return s
def extract_version_and_lib_from_path(original_path):
old_lib = lib_extract_from_path(original_path)
if not old_lib or original_path.find('vcells') > 0:
old_lib = None
new_lib = 'sky130_fd_pr_base'
else:
new_lib = convert_libname(old_lib)
assert new_lib, (old_lib, new_lib, original_path)
ver = version_extract_from_path(original_path)
if ver is not None:
ver = "V" + ".".join([str(v) for v in ver])
else:
ver = "V0.0.9"
assert ver is not None, ver
return (old_lib, new_lib, ver)
def convert_version(v):
"""
>>> convert_version('V0.0.0')
'v0.0.0'
>>> convert_version('V1.2.3')
'v0.12.3'
"""
assert v.startswith('V'), v
bits = v[1:].split('.')
assert len(bits) == 3, (bits, v)
ver = [int(x) for x in bits]
new_ver = [0, ver[0]*10+ver[1], ver[2]]
return "v{}.{}.{}".format(*new_ver)
SKIP_FILES = {
# ('.lef', 'sky130_fd_sc_ls', 'v0.1.0'): [
# 'probec_p', 'probe_p'],
}
def skip_copy_file(ext, lib, ver, cellname):
if ext.endswith('.pins'):
ext = ext[:-5]
cellname_checks = SKIP_FILES.get((ext, lib, ver), [])
for check in cellname_checks:
if check in cellname:
return True
return False
def copy_file_to_output(src_path, final_dir, lib, ver, cellname, okay_exists=False):
assert '/' not in cellname, cell
ext = '.'+os.path.basename(src_path).split('.', 1)[-1]
new_ver = convert_version(ver)
cell_dirname = get_cell_directory(final_dir, lib, new_ver, cellname)
final_path = os.path.join(cell_dirname, cellname+ext)
if skip_copy_file(ext, lib, new_ver, cellname):
print(f"WARNING: Told to skip {final_path}!")
return None
# Make the directory we are going to put the file into.
os.makedirs(cell_dirname, exist_ok=True)
# Check the files doesn't already exist
if os.path.exists(final_path):
# Allow a file with exactly the same content.
with open(src_path) as f:
new_contents = f.read()
with open(final_path) as f:
old_contents = f.read()
if new_contents == old_contents:
print(f"WARNING: File {final_path} existed, but contents matched")
return final_path
if okay_exists:
print(f"Skipping as {final_path} exists already!")
return None
assert not os.path.exists(final_path), (src_path, final_path)
print(f"Copying to {final_path}")
copyfile(src_path, final_path)
return final_path
def convert_cell_fullname(old_fullname, new_libname=None):
"""
>>> convert_cell_fullname('s8blref_xind4_01')
'sky130_fd_pr_base__rf_xind4_01'
>>> convert_cell_fullname("condiode")
'sky130_fd_pr_base__condiode'
>>> convert_cell_fullname("nvhv")
'sky130_fd_pr_base__nvhv'
>>> convert_cell_fullname("pvhv")
'sky130_fd_pr_base__pvhv'
>>> convert_cell_fullname("s8rf2_xcmvpp11p5x11p7_lim5shield")
'sky130_fd_pr_rf2__rf2_xcmvpp11p5x11p7_lim5shield'
>>> convert_cell_fullname("s8rf2_xcmvpp11p5x11p7_m3_lim5shield")
'sky130_fd_pr_rf2__rf2_xcmvpp11p5x11p7_m3_lim5shield'
>>> convert_cell_fullname("s8rf2_xcmvpp11p5x11p7_m4shield")
'sky130_fd_pr_rf2__rf2_xcmvpp11p5x11p7_m4shield'
>>> convert_cell_fullname("s8rf2_xcmvpp11p5x11p7_polym4shield")
'sky130_fd_pr_rf2__rf2_xcmvpp11p5x11p7_polym4shield'
>>> convert_cell_fullname("s8rf2_xcmvpp11p5x11p7_polym50p4shield")
'sky130_fd_pr_rf2__rf2_xcmvpp11p5x11p7_polym50p4shield'
>>> convert_cell_fullname("s8rf2_xcmvpp4p4x4p6_m3_lim5shield")
'sky130_fd_pr_rf2__rf2_xcmvpp4p4x4p6_m3_lim5shield'
>>> convert_cell_fullname("s8rf2_xcmvpp6p8x6p1_lim4shield")
'sky130_fd_pr_rf2__rf2_xcmvpp6p8x6p1_lim4shield'
>>> convert_cell_fullname("s8rf2_xcmvpp6p8x6p1_polym4shield")
'sky130_fd_pr_rf2__rf2_xcmvpp6p8x6p1_polym4shield'
>>> convert_cell_fullname("s8rf2_xcmvpp8p6x7p9_m3_lim5shield")
'sky130_fd_pr_rf2__rf2_xcmvpp8p6x7p9_m3_lim5shield'
>>> convert_cell_fullname("s8rf2_xcmvppx4_2xnhvnative10x4")
'sky130_fd_pr_rf2__rf2_xcmvppx4_2xnhvnative10x4'
>>> convert_cell_fullname("s8rf_pnp5x")
'sky130_fd_pr_rf__rf_pnp5x'
>>> convert_cell_fullname("s8rf_xcmvpp11p5x11p7_m3_lishield")
'sky130_fd_pr_rf__rf_xcmvpp11p5x11p7_m3_lishield'
>>> convert_cell_fullname("s8rf_xcmvpp11p5x11p7_m3shield")
'sky130_fd_pr_rf__rf_xcmvpp11p5x11p7_m3shield'
>>> convert_cell_fullname("s8rf_xcmvpp1p8x1p8")
'sky130_fd_pr_rf__rf_xcmvpp1p8x1p8'
>>> convert_cell_fullname("s8rf_xcmvpp1p8x1p8_m3shield")
'sky130_fd_pr_rf__rf_xcmvpp1p8x1p8_m3shield'
>>> convert_cell_fullname("s8rf_xcmvpp2")
'sky130_fd_pr_rf__rf_xcmvpp2'
>>> convert_cell_fullname("s8rf_xcmvpp2_nwell")
'sky130_fd_pr_rf__rf_xcmvpp2_nwell'
>>> convert_cell_fullname("s8rf_xcmvpp4p4x4p6_m3_lishield")
'sky130_fd_pr_rf__rf_xcmvpp4p4x4p6_m3_lishield'
>>> convert_cell_fullname("s8rf_xcmvpp4p4x4p6_m3shield")
'sky130_fd_pr_rf__rf_xcmvpp4p4x4p6_m3shield'
>>> convert_cell_fullname("s8rf_xcmvpp8p6x7p9_m3_lishield")
'sky130_fd_pr_rf__rf_xcmvpp8p6x7p9_m3_lishield'
>>> convert_cell_fullname("s8rf_xcmvpp8p6x7p9_m3shield")
'sky130_fd_pr_rf__rf_xcmvpp8p6x7p9_m3shield'
>>> convert_cell_fullname("xcnwvc")
'sky130_fd_pr_base__xcnwvc'
>>> convert_cell_fullname("xcnwvc2")
'sky130_fd_pr_base__xcnwvc2'
>>> convert_cell_fullname("scs8hd_xor2_1")
'sky130_fd_sc_hd__xor2_1'
>>> convert_cell_fullname("scs8hd_xor2_2")
'sky130_fd_sc_hd__xor2_2'
>>> convert_cell_fullname("scs8hd_xor2_4")
'sky130_fd_sc_hd__xor2_4'
>>> convert_cell_fullname("scs8hd_xor3_1")
'sky130_fd_sc_hd__xor3_1'
>>> convert_cell_fullname("scs8ls_a2111o_1")
'sky130_fd_sc_ls__a2111o_1'
>>> convert_cell_fullname("scs8ls_a2111o_2")
'sky130_fd_sc_ls__a2111o_2'
>>> convert_cell_fullname("scs8ls_a2111o_4")
'sky130_fd_sc_ls__a2111o_4'
>>> convert_cell_fullname("scs8ls_a2111oi_1")
'sky130_fd_sc_ls__a2111oi_1'
>>> convert_cell_fullname("L1M1_PR_CDNS_525975574200")
'sky130_fd_pr_base__l1m1_pr_525975574200'
>>> convert_cell_fullname("s8rf_xcmvpp8p6x7p9_m3shield", "sky130_fd_pr_base")
'sky130_fd_pr_base__rf_xcmvpp8p6x7p9_m3shield'
"""
ext_libname, ext_cellname = lib_extract_from_name(old_fullname)
assert ext_cellname is not None, (ext_cellname, old_fullname)
if ext_libname is not None:
xxx_libname = convert_libname(ext_libname)
if new_libname is not None:
if "_pr_rf" in xxx_libname:
assert "fd_pr" in new_libname
else:
assert xxx_libname == new_libname, (xxx_libname, new_libname, old_fullname, ext_libname)
new_libname = xxx_libname
else:
new_libname = xxx_libname
if not new_libname:
new_libname = 'sky130_fd_pr_base'
new_cellname = convert_cellname(ext_cellname)
new_fullname = new_libname + '__' + new_cellname
return new_fullname
if __name__ == "__main__":
import doctest
doctest.testmod()
for i, f in enumerate(files([])):
if i > 10:
print(' ...')
break
print(f)