blob: c8539d6354aa49fa7f78633378c0efa7a9c7ae1b [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 argparse
import csv
import difflib
import io
import json
import multiprocessing
import os
import pprint
import re
import sys
import time
from collections import defaultdict, OrderedDict
from pathlib import Path
import common
from common import lib_extract_from_path, version_extract_from_path, lib_extract_from_name, extract_version_and_lib_from_path, copy_file_to_output
from common import convert_libname, convert_cellname, convert_cell_fullname, convert_pinname
debug = False
debug_print = lambda x: print(x) if debug else 0
Copyright_header = (
'* Copyright 2020 The SkyWater PDK Authors\n'
'*\n'
'* Licensed under the Apache License, Version 2.0 (the "License");\n'
'* you may not use this file except in compliance with the License.\n'
'* You may obtain a copy of the License at\n'
'*\n'
'* https://www.apache.org/licenses/LICENSE-2.0\n'
'*\n'
'* Unless required by applicable law or agreed to in writing, software\n'
'* distributed under the License is distributed on an "AS IS" BASIS,\n'
'* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n'
'* See the License for the specific language governing permissions and\n'
'* limitations under the License.\n'
'\n')
EXPORT_FILES = {
'lib.spice': 'lib.spice',
'cor': 'corner.spice',
'mod': 'model.spice',
'pm3': 'pm3.spice',
'sp': 'spice',
'cor.bins.csv': 'bins.csv',
'all': 'spice',
'hed': 'spice',
'sp': 'spice',
}
CORNERS = ['ff', 'fs', 'sf', 'ss', 'tt', 'leak', 'wafer']
# # FF - Fast, Fast Corner
# /ff.cor
# /ff_cell.cor
# /ff_discrete.cor
# /ff_nonfet.cor
# /ff_rf.cor
# /ff_subvtmm.cor
# # FS - Fast, Slow Corner
# /fs.cor
# /fs_cell.cor
# /fs_discrete.cor
# /fs_nonfet.cor
# /fs_rf.cor
# /fs_subvtmm.cor
# # SF - Slow, Fast Corner
# /sf.cor
# /sf_cell.cor
# /sf_discrete.cor
# /sf_nonfet.cor
# /sf_rf.cor
# /sf_subvtmm.cor
# # SS - Slow Slow Corner
# /ss.cor
# /ss_cell.cor
# /ss_discrete.cor
# /ss_nonfet.cor
# /ss_rf.cor
# /ss_subvtmm.cor
# # TT - Typical, Typical
# /tt.cor
# /tt_cell.cor
# /tt_discrete.cor
# /tt_nonfet.cor
# /tt_rf.cor
# /tt_subvtmm.cor
# /tt_leak.cor
# /tt_leak_discrete.cor
# /tt_leak_rf.cor
# # Leak
# /leak.cor
# /leak_discrete.cor
# /leak_nonfet.cor
# /leak_cell.cor
# /leak_rf.cor
# # Wafer
# /wafer.cor
# /wafer_nonfet.cor
# /wafer_cell.cor
# /wafer_rf.cor
# corners/ff/XXXX
# ff
# ff cell
# ff rf
#
# fs
# fs cell
# fs rf
#
# leak
# leak cell
# leak rf
#
# sf
# sf cell
# sf rf
#
# ss
# ss cell
# ss rf
#
# tt
# tt cell
# tt rf
#
# wafer
# wafer cell
# wafer rf
RE_RC_MATCH = re.compile('(?P<r>.)r(?P<c>.)c(.*)')
# (.)r(.)c
RC_NAMES = {
'h': 'high',
't': 'typical',
'l': 'low',
}
CORNER_NAMES = {
'f': 'fast',
's': 'slow',
't': 'typical',
}
SONOS_PERIODS = {
'bol': 'begin_of_life',
'eol': 'end_of_life',
}
SONOS_PERIOD_TYPES = {
'w': 'Worst',
't': 'Typical',
}
SONOS_CORNERS = {
'bol': 'Begin of life',
'eol': 'End of life',
'tbol': 'Typical begin of life',
'wbol': 'Worst begin of life',
'teol': 'Typical end of life',
'weol': 'Worst end of life',
'tt': 'Typical, Typical',
'ff': 'Fast, Fast',
'ss': 'Slow, Slow',
}
RE_SONOS_MATCH = re.compile('.*sonos(?P<see1>_see)?_(?P<corner>(ff)|(tt)|(ss))?(?P<period_type>[tw])?(?P<period>(bol)|(eol))(?P<ep>(_e)|(_p))?(?P<see2>_see)?(?P<mm>_mm)?')
RE_INCLUDE = re.compile('^\\.include ["\']?(?P<f>[^"\']*)["\']?$', flags=re.M)
def corner_file(c, old_cell, ext):
corner_type = old_cell[len(c):].strip('_')
if not corner_type:
return "models/corners/{}.spice".format(c)
if corner_type == 'cell':
corner_type = 'specialized_cells'
if ext == 'cor':
newfile = "models/corners/{}/{}.spice".format(c, corner_type)
elif ext == 'cor.bins.csv':
newfile = "models/corners/{}/{}.bin.csv".format(c, corner_type)
else:
assert False, ext
return newfile
FILE_MAPPING = {
'sky130.lib.spice': "models/sky130.lib.spice",
'correl1.cor': "models/correl1.spice",
'correl2.cor': "models/correl2.spice",
'correl3.cor': "models/correl3.spice",
'correl4.cor': "models/correl4.spice",
'custom.cor': "models/custom.spice",
'model.hed': "models/head.spice",
'models.all': "models/all.spice",
'r+c_mrp1monte.mod': "models/r+c.mrp1monte.spice",
'invariant.cor': "models/parameters/invariant.spice",
'monte.cor': "models/parameters/montecarlo.spice",
'critical_params.cor': "models/parameters/critical.spice",
'fpar.cor': "models/parameters/fast.spice",
'fpar70.cor': "models/parameters/fast_70p.spice",
'tpar.cor': "models/parameters/typical.spice",
'spar.cor': "models/parameters/slow.spice",
'spar70.cor': "models/parameters/slow_70p.spice",
'lod_params.cor': "models/parameters/lod.spice",
}
def get_cell_and_path(input_file):
"""
>>> get_cell_and_path('/ssd/gob/foss-eda-tools/temp-spice/s8/V1.0.0/step1-converted/s8b/r+c_top.mod')
('sky130_fd_pr__model__r+c', 'sky130_fd_pr__model__r+c.model.spice')
>>> get_cell_and_path('/ssd/gob/foss-eda-tools/temp-spice/s8/V1.0.0/step1-converted/s8b/xcmvpp_ponly.mod')
('sky130_fd_pr__model__cap_vpp_only_p', 'sky130_fd_pr__model__cap_vpp_only_p.model.spice')
>>> get_cell_and_path('/ssd/gob/foss-eda-tools/temp-spice/s8/V1.0.0/step1-converted/s8b/xcmvpp_moscap.mod')
('sky130_fd_pr__model__cap_vpp_only_mos', 'sky130_fd_pr__model__cap_vpp_only_mos.model.spice')
>>> get_cell_and_path('/ssd/gob/foss-eda-tools/temp-spice/s8/V1.0.0/step1-converted/s8b/sky130.lib.spice')
('common', 'models/sky130.lib.spice')
>>> get_cell_and_path('/ssd/gob/foss-eda-tools/temp-spice/s8/V1.0.0/step1-converted/s8a/critical_params.cor')
('common', 'models/parameters/critical.spice')
>>> get_cell_and_path('/ssd/gob/foss-eda-tools/temp-spice/s8/V1.0.0/step1-converted/s8a/ff.cor')
('common', 'models/corners/ff.spice')
>>> get_cell_and_path('/ssd/gob/foss-eda-tools/temp-spice/s8/V1.0.0/step1-converted/s8a/ff_discrete.cor')
('common', 'models/corners/ff/discrete.spice')
>>> get_cell_and_path('/ssd/gob/foss-eda-tools/temp-spice/s8/V1.0.0/step1-converted/s8a/fpar70.cor')
('common', 'models/parameters/fast_70p.spice')
>>> get_cell_and_path('/ssd/gob/foss-eda-tools/temp-spice/s8/V1.0.0/step1-converted/s8a/hrhc.cor')
('common', 'models/r+c/res_high__cap_high.spice')
>>> get_cell_and_path('/ssd/gob/foss-eda-tools/temp-spice/s8/V1.0.0/step1-converted/s8a/nhv_rf_base_b.pm3')
('sky130_fd_pr__rf_nfet_g5v0d10v5_b', 'sky130_fd_pr__rf_nfet_g5v0d10v5_b.pm3.spice')
>>> get_cell_and_path('/ssd/gob/foss-eda-tools/temp-spice/s8/V1.0.0/step1-converted/s8a/nhv_rf_base_b_sf.cor.bins.csv')
('sky130_fd_pr__rf_nfet_g5v0d10v5_b', 'sky130_fd_pr__rf_nfet_g5v0d10v5_b.bins.csv')
>>> get_cell_and_path('/ssd/gob/foss-eda-tools/temp-spice/s8/V1.0.0/step1-converted/s8a/nlowvt_rf_base_b_ss.cor')
('sky130_fd_pr__rf_nfet_01v8_lvt_b__ss', 'sky130_fd_pr__rf_nfet_01v8_lvt_b__ss.corner.spice')
>>> get_cell_and_path('/ssd/gob/foss-eda-tools/temp-spice/s8/V1.0.0/step1-converted/s8a/sonos_wbol.cor')
('common', 'models/sonos/begin_of_life/worst.spice')
>>> get_cell_and_path('/ssd/gob/foss-eda-tools/temp-spice/s8/V1.0.0/step1-converted/s8a/sonos_ffeol.cor')
('common', 'models/sonos/end_of_life/ff.spice')
>>> get_cell_and_path('/ssd/gob/foss-eda-tools/temp-spice/s8/V1.0.0/step1-converted/s8a/models.all')
('common', 'models/all.spice')
>>> get_cell_and_path('/ssd/gob/foss-eda-tools/temp-spice/s8/V1.0.0/step1-converted/s8b/nlvtpass_ff.cor')
('sky130_fd_pr__special_nfet_pass_lvt__ff', 'sky130_fd_pr__special_nfet_pass_lvt__ff.corner.spice')
>>> get_cell_and_path('/ssd/gob/foss-eda-tools/temp-spice/s8/V1.0.0/step1-converted/s8b/nlvtpass_ff.cor.bins.csv')
('sky130_fd_pr__special_nfet_pass_lvt', 'sky130_fd_pr__special_nfet_pass_lvt.bins.csv')
>>> get_cell_and_path('/ssd/gob/foss-eda-tools/temp-spice/s8/V1.0.0/step1-converted/s8b/nlvtpass_ff_discrete.cor')
('sky130_fd_pr__special_nfet_pass_lvt__ff_discrete', 'sky130_fd_pr__special_nfet_pass_lvt__ff_discrete.corner.spice')
>>> get_cell_and_path('/ssd/gob/foss-eda-tools/temp-spice/s8/V1.0.0/step1-converted/s8b/nlvtpass_ff_discrete.cor.bins.csv')
('sky130_fd_pr__special_nfet_pass_lvt', 'sky130_fd_pr__special_nfet_pass_lvt.bins.csv')
>>> get_cell_and_path('/ssd/gob/foss-eda-tools/temp-spice/s8/V1.0.0/step3/s8b/spice_rewrite/sky130_fd_b/V1.0.0/__ssd__gob__foss-eda-tools__temp-spice__s8__V1_0_0__step1-converted__s8b__sonos_bol_e.cor')
('common', 'models/sonos_e/begin_of_life.spice')
>>> get_cell_and_path('__ssd__gob__foss-eda-tools__temp-spice__s8__V1_0_0__step2-split__s8b__sonos_bol_e.cor.bins.csv')
('common', 'models/sonos_e/begin_of_life.bins.csv')
>>> get_cell_and_path('__ssd__gob__foss-eda-tools__temp-spice__s8__V1_0_0__step2-split__s8b__xcmvpp2_phv5x4.mod')
('sky130_fd_pr__cap_vpp_04p4x04p6_m1m2_noshield_o1phv', 'sky130_fd_pr__cap_vpp_04p4x04p6_m1m2_noshield_o1phv.model.spice')
>>> get_cell_and_path('ff_cell.cor')
('common', 'models/corners/ff/specialized_cells.spice')
>>> get_cell_and_path('__home__tansell__tempdir__spice__s8__V1_0_0__step2-split__s8b__dnwdiode_psub.mod')
('sky130_fd_pr__model__parasitic__diode_ps2dn', 'sky130_fd_pr__model__parasitic__diode_ps2dn.model.spice')
>>> get_cell_and_path('__home__tansell__tempdir__spice__s8__V1_0_0__step2-split__s8b__nor2_p.mod')
('sky130_fd_pr__model__typical__nor2', 'sky130_fd_pr__model__typical__nor2.model.spice')
>>> get_cell_and_path('/home/tansell/tempdir/spice/s8/V1.0.0/step3/s8b/spice_rewrite/sky130_fd_b/V1.0.0/__home__tansell__tempdir__spice__s8__V1_0_0__step2-split__s8b__xcmvpp.mod')
('sky130_fd_pr__cap_vpp_08p6x07p8_l1m1m2_noshield_o1', 'sky130_fd_pr__cap_vpp_08p6x07p8_l1m1m2_noshield_o1.model.spice')
>>> get_cell_and_path('/home/tansell/tempdir/spice/s8/V1.0.0/step3/s8b/spice_rewrite/sky130_fd_b/V1.0.0/__home__tansell__tempdir__spice__s8__V1_0_0__step2-split__s8b__xcmvpp_top.mod')
('sky130_fd_pr__model__cap_vpp', 'sky130_fd_pr__model__cap_vpp.model.spice')
>>> get_cell_and_path('/home/tansell/tempdir/spice/s8/V1.0.0/step3/s8b/spice_rewrite/sky130_fd_b/V1.0.0/dnwdiode_pw_top.mod')
('sky130_fd_pr__model__parasitic__diodes_pw2dn', 'sky130_fd_pr__model__parasitic__diodes_pw2dn.model.spice')
>>> get_cell_and_path('/home/tansell/tempdir/spice/s8/V1.0.0/step3/s8b/spice_rewrite/sky130_fd_b/V1.0.0/dnwdiode_pw_no_rs.mod')
('sky130_fd_pr__model__parasitic__diode_pw2dn_noresistor', 'sky130_fd_pr__model__parasitic__diode_pw2dn_noresistor.model.spice')
>>> sonos_names = set()
>>> s = get_cell_and_path('step1-converted__s8b__sonos_bol_e.cor')
>>> assert s not in sonos_names, (s, sonos_names)
>>> sonos_names.add(s)
>>> s = get_cell_and_path('step1-converted__s8b__sonos_bol_e_mm.cor')
>>> assert s not in sonos_names, (s, sonos_names)
>>> sonos_names.add(s)
>>> s = get_cell_and_path('step1-converted__s8b__sonos_bol_p.cor')
>>> assert s not in sonos_names, (s, sonos_names)
>>> sonos_names.add(s)
>>> s = get_cell_and_path('step1-converted__s8b__sonos_bol_p_mm.cor')
>>> assert s not in sonos_names, (s, sonos_names)
>>> sonos_names.add(s)
>>> s = get_cell_and_path('step1-converted__s8b__sonos_ffeol_e.cor')
>>> assert s not in sonos_names, (s, sonos_names)
>>> sonos_names.add(s)
>>> s = get_cell_and_path('step1-converted__s8b__sonos_ffeol_p.cor')
>>> assert s not in sonos_names, (s, sonos_names)
>>> sonos_names.add(s)
>>> s = get_cell_and_path('step1-converted__s8b__sonos_ffteol_e.cor')
>>> assert s not in sonos_names, (s, sonos_names)
>>> sonos_names.add(s)
>>> s = get_cell_and_path('step1-converted__s8b__sonos_ffteol_p.cor')
>>> assert s not in sonos_names, (s, sonos_names)
>>> sonos_names.add(s)
>>> s = get_cell_and_path('step1-converted__s8b__sonos_see_bol_e.cor')
>>> assert s not in sonos_names, (s, sonos_names)
>>> sonos_names.add(s)
>>> s = get_cell_and_path('step1-converted__s8b__sonos_see_bol_p.cor')
>>> assert s not in sonos_names, (s, sonos_names)
>>> sonos_names.add(s)
>>> s = get_cell_and_path('step1-converted__s8b__sonos_see_eol_e.cor')
>>> assert s not in sonos_names, (s, sonos_names)
>>> sonos_names.add(s)
>>> s = get_cell_and_path('step1-converted__s8b__sonos_see_eol_p.cor')
>>> assert s not in sonos_names, (s, sonos_names)
>>> sonos_names.add(s)
>>> s = get_cell_and_path('step1-converted__s8b__sonos_see_ffeol_e.cor')
>>> assert s not in sonos_names, (s, sonos_names)
>>> sonos_names.add(s)
>>> s = get_cell_and_path('step1-converted__s8b__sonos_see_ffeol_p.cor')
>>> assert s not in sonos_names, (s, sonos_names)
>>> sonos_names.add(s)
>>> s = get_cell_and_path('step1-converted__s8b__sonos_see_ffteol_e.cor')
>>> assert s not in sonos_names, (s, sonos_names)
>>> sonos_names.add(s)
>>> s = get_cell_and_path('step1-converted__s8b__sonos_see_ffteol_p.cor')
>>> assert s not in sonos_names, (s, sonos_names)
>>> sonos_names.add(s)
>>> s = get_cell_and_path('step1-converted__s8b__sonos_see_tbol_e.cor')
>>> assert s not in sonos_names, (s, sonos_names)
>>> sonos_names.add(s)
>>> s = get_cell_and_path('step1-converted__s8b__sonos_see_tbol_p.cor')
>>> assert s not in sonos_names, (s, sonos_names)
>>> sonos_names.add(s)
>>> s = get_cell_and_path('step1-converted__s8b__sonos_see_teol_e.cor')
>>> assert s not in sonos_names, (s, sonos_names)
>>> sonos_names.add(s)
>>> s = get_cell_and_path('step1-converted__s8b__sonos_see_teol_p.cor')
>>> assert s not in sonos_names, (s, sonos_names)
>>> sonos_names.add(s)
>>> s = get_cell_and_path('step1-converted__s8b__sonos_see_wbol_e.cor')
>>> assert s not in sonos_names, (s, sonos_names)
>>> sonos_names.add(s)
>>> s = get_cell_and_path('step1-converted__s8b__sonos_see_wbol_p.cor')
>>> assert s not in sonos_names, (s, sonos_names)
>>> sonos_names.add(s)
>>> s = get_cell_and_path('step1-converted__s8b__sonos_sseol_e.cor')
>>> assert s not in sonos_names, (s, sonos_names)
>>> sonos_names.add(s)
>>> s = get_cell_and_path('step1-converted__s8b__sonos_sseol_p.cor')
>>> assert s not in sonos_names, (s, sonos_names)
>>> sonos_names.add(s)
>>> s = get_cell_and_path('step1-converted__s8b__sonos_ssteol_e.cor')
>>> assert s not in sonos_names, (s, sonos_names)
>>> sonos_names.add(s)
>>> s = get_cell_and_path('step1-converted__s8b__sonos_ssteol_p.cor')
>>> assert s not in sonos_names, (s, sonos_names)
>>> sonos_names.add(s)
>>> s = get_cell_and_path('step1-converted__s8b__sonos_tbol_e.cor')
>>> assert s not in sonos_names, (s, sonos_names)
>>> sonos_names.add(s)
>>> s = get_cell_and_path('step1-converted__s8b__sonos_tbol_p.cor')
>>> assert s not in sonos_names, (s, sonos_names)
>>> sonos_names.add(s)
>>> s = get_cell_and_path('step1-converted__s8b__sonos_tteol_e.cor')
>>> assert s not in sonos_names, (s, sonos_names)
>>> sonos_names.add(s)
>>> s = get_cell_and_path('step1-converted__s8b__sonos_tteol_p.cor')
>>> assert s not in sonos_names, (s, sonos_names)
>>> sonos_names.add(s)
>>> s = get_cell_and_path('step1-converted__s8b__sonos_ttteol_e.cor')
>>> assert s not in sonos_names, (s, sonos_names)
>>> sonos_names.add(s)
>>> s = get_cell_and_path('step1-converted__s8b__sonos_ttteol_p.cor')
>>> assert s not in sonos_names, (s, sonos_names)
>>> sonos_names.add(s)
>>> s = get_cell_and_path('step1-converted__s8b__sonos_wbol_e.cor')
>>> assert s not in sonos_names, (s, sonos_names)
>>> sonos_names.add(s)
>>> s = get_cell_and_path('step1-converted__s8b__sonos_wbol_p.cor')
>>> assert s not in sonos_names, (s, sonos_names)
>>> sonos_names.add(s)
>>> pprint.pprint(list(sorted(sonos_names)))
[('common', 'models/sonos_e/begin_of_life.spice'),
('common', 'models/sonos_e/begin_of_life/mm.spice'),
('common', 'models/sonos_e/begin_of_life/typical.spice'),
('common', 'models/sonos_e/begin_of_life/worst.spice'),
('common', 'models/sonos_e/end_of_life/ff.spice'),
('common', 'models/sonos_e/end_of_life/ss.spice'),
('common', 'models/sonos_e/end_of_life/tt.spice'),
('common', 'models/sonos_e/end_of_life/typical/ff.spice'),
('common', 'models/sonos_e/end_of_life/typical/ss.spice'),
('common', 'models/sonos_e/end_of_life/typical/tt.spice'),
('common', 'models/sonos_p/begin_of_life.spice'),
('common', 'models/sonos_p/begin_of_life/mm.spice'),
('common', 'models/sonos_p/begin_of_life/typical.spice'),
('common', 'models/sonos_p/begin_of_life/worst.spice'),
('common', 'models/sonos_p/end_of_life/ff.spice'),
('common', 'models/sonos_p/end_of_life/ss.spice'),
('common', 'models/sonos_p/end_of_life/tt.spice'),
('common', 'models/sonos_p/end_of_life/typical/ff.spice'),
('common', 'models/sonos_p/end_of_life/typical/ss.spice'),
('common', 'models/sonos_p/end_of_life/typical/tt.spice'),
('common', 'models/sonos_see_e/begin_of_life.spice'),
('common', 'models/sonos_see_e/begin_of_life/typical.spice'),
('common', 'models/sonos_see_e/begin_of_life/worst.spice'),
('common', 'models/sonos_see_e/end_of_life.spice'),
('common', 'models/sonos_see_e/end_of_life/ff.spice'),
('common', 'models/sonos_see_e/end_of_life/typical.spice'),
('common', 'models/sonos_see_e/end_of_life/typical/ff.spice'),
('common', 'models/sonos_see_p/begin_of_life.spice'),
('common', 'models/sonos_see_p/begin_of_life/typical.spice'),
('common', 'models/sonos_see_p/begin_of_life/worst.spice'),
('common', 'models/sonos_see_p/end_of_life.spice'),
('common', 'models/sonos_see_p/end_of_life/ff.spice'),
('common', 'models/sonos_see_p/end_of_life/typical.spice'),
('common', 'models/sonos_see_p/end_of_life/typical/ff.spice')]
"""
if '.' not in input_file:
return None, None
filename = os.path.split(input_file)[-1]
old_cell, ext = filename.split('.', 1)
if ext not in EXPORT_FILES:
return None, None
if '__' in old_cell:
old_cell = old_cell.rsplit('__', 1)[1]
newext = EXPORT_FILES[ext]
new_cellname = None
m = RE_RC_MATCH.search(old_cell)
if m:
newext = 'spice'
new_cellname = 'common'
res = RC_NAMES[m.group('r')]
cap = RC_NAMES[m.group('c')]
n = ''
if m.group(3):
n = '__'+m.group(3)
new_filename = "models/r+c/res_{}__cap_{}{}.spice".format(res, cap, n)
elif filename in FILE_MAPPING:
new_cellname = 'common'
new_filename = FILE_MAPPING[filename]
elif 'sonos' in filename:
if newext == 'corner.spice':
newext = 'spice'
new_cellname = 'common'
m = RE_SONOS_MATCH.match(old_cell)
assert m, old_cell
path = ['models', 'sonos']
see1 = m.group('see1')
see2 = m.group('see2')
if see1 or see2:
path[-1] += '_see'
ep = m.group('ep')
if ep:
path[-1] += ep
period = m.group('period')
mm = m.group('mm')
if not period:
assert not mm
else:
path.append(SONOS_PERIODS[period].lower())
period_type = m.group('period_type')
if period_type:
path.append(SONOS_PERIOD_TYPES[period_type].lower())
corner = m.group('corner')
if mm:
assert mm[0] == '_', m.group()
mm = mm[1:]
if corner:
corner = corner+'_'+mm
else:
corner = mm
if corner:
path.append(corner)
path[-1] += '.'+newext
new_filename = '/'.join(path)
else:
for c in CORNERS:
if not old_cell.startswith(c):
continue
new_cellname = 'common'
new_filename = corner_file(c, old_cell, ext)
break
if not new_cellname:
new_cellname = common.convert_cell_fullname(old_cell)
if ext == "cor.bins.csv":
new_cellname = new_cellname.rsplit('__', 1)[0]
new_filename = "{}.{}".format(new_cellname, newext)
else:
new_filename = "{}.{}".format(new_cellname, newext)
assert new_cellname == 'common' or new_cellname.startswith('sky130_fd_pr__'), (new_cellname, new_filename, input_file)
assert new_filename
return new_cellname, new_filename
def safe_input_filename(input_file):
"""
>>> safe_input_filename("abc/123")
'abc__123'
>>> safe_input_filename("/ssd/gob/foss-eda-tools/temp-spice/s8/V1.0.0/step1-converted__s8b__sonos_bol_e.cor")
'__ssd__gob__foss-eda-tools__temp-spice__s8__V1_0_0__step1-converted__s8b__sonos_bol_e.cor'
>>> safe_input_filename("/ssd/gob/foss-eda-tools/temp-spice/s8/v0.10.3/step1-converted__s8b__sonos_bol_e.cor")
'__ssd__gob__foss-eda-tools__temp-spice__s8__v0_10_3__step1-converted__s8b__sonos_bol_e.cor'
"""
input_file = input_file.replace('/', '__')
return re.sub('([vV][0-9]+)\\.([0-9]+)\\.([0-9]+)', '\\1_\\2_\\3', input_file)
def filemain(input_file, temp_dir, final_dir, ver):
assert os.path.exists(input_file), input_file
assert os.path.isfile(input_file), input_file
input_dir = os.path.dirname(input_file)
new_lib = "sky130_fd_pr"
while not os.path.exists(temp_dir):
try:
os.makedirs(temp_dir)
except Exception as e:
print(e)
filename = os.path.split(input_file)[-1]
assert '.' in filename, filename
old_cell, ext = filename.split('.', 1)
if ext not in EXPORT_FILES:
print("Skipping", input_file)
return 0
new_cellname, new_filename = get_cell_and_path(input_file)
new_ext = new_filename.split('.', 1)[-1]
new_dirname = os.path.dirname(new_filename)
our_final_path = common.get_final_path(final_dir, new_lib, ver, new_cellname, new_ext, filename=new_filename)
our_final_dir = os.path.dirname(our_final_path)
print(input_file, old_cell)
print(" -->", our_final_path)
print(flush=True)
# Rewrite file contents
with open(input_file, 'r') as in_f:
contents = in_f.read()
def rewrite_inc(m):
old_incpath = m.group('f')
assert old_incpath, m
old_fpath = os.path.join(input_dir, old_incpath)
assert old_fpath
inc_new_cellname, inc_new_filename = get_cell_and_path(old_fpath)
assert inc_new_cellname, (old_fpath, inc_new_cellname, inc_new_filename)
assert inc_new_filename, (old_fpath, inc_new_cellname, inc_new_filename)
inc_new_ext = inc_new_filename.split('.', 1)[-1]
assert inc_new_ext, (inc_new_cellname, inc_new_filename)
inc_final_path = common.get_final_path(final_dir, new_lib, ver, inc_new_cellname, inc_new_ext, filename=inc_new_filename)
inc_final_relpath = os.path.relpath(inc_final_path, our_final_dir)
return '.include "{}"'.format(inc_final_relpath)
contents = RE_INCLUDE.sub(rewrite_inc, contents)
contents = test_rewrite_string(contents, print_time=True).splitlines()
print("-"*75)
print("\n".join(contents[:100]))
if len(contents) > 100:
print("...")
print("-"*75)
contents = '\n'.join(contents)
if contents[-1] != '\n':
contents += '\n'
tmp_file = os.path.join(temp_dir, safe_input_filename(input_file))
tmp_filedir = os.path.dirname(tmp_file)
if not os.path.exists(tmp_filedir):
os.makedirs(tmp_filedir)
assert not os.path.exists(tmp_file), tmp_file
with open(tmp_file, 'w') as tmp_f:
if new_ext.endswith('.spice'):
tmp_f.write(Copyright_header)
tmp_f.write(contents)
print("Wrote:", tmp_file)
used_final_path = copy_file_to_output(tmp_file, final_dir, new_lib, ver, new_cellname, filename=new_filename)
assert used_final_path == our_final_path, (used_final_path, our_final_path)
return 0
REWRITES_new = []
REWRITES_old = []
REWRITES_MAPPING = {}
with open('rewrites.csv', newline='') as f:
c = csv.DictReader(f)
regex_middle = []
def rewrite(m):
b = m.group('before')
if not b:
b = ''
a = m.group('after')
if not a:
a = ''
elif a == '_':
a = "__"
f = m.group('from').lower()
return b+REWRITES_MAPPING[f]+a
BOUNDRY_START = '((?<![/])\\b)'
BOUNDRY_END = '(\\b(?![/]))'
for r in c:
f = re.escape(r['from'])
t = r['to']
if f.lower().startswith('x'):
REWRITES_old.append((re.compile(('^(\\s*)'+f ), flags=re.I|re.M), '\\1X'+t))
REWRITES_old.append((re.compile((BOUNDRY_START+f+BOUNDRY_END), flags=re.I), t))
REWRITES_old.append((re.compile((BOUNDRY_START+f+'_' ), flags=re.I), t+'__'))
if f != 'inv':
REWRITES_old.append((re.compile(('\\b([MXQ])'+f ), flags=re.I), '\\1'+t))
else:
REWRITES_old.append((re.compile(('^([MXQ])'+f ), flags=re.I|re.M), '\\1'+t))
REWRITES_MAPPING[f] = t
regex_middle.append('('+f+')')
regex_middle = '|'.join(regex_middle)
REWRITES_new.append((
re.compile(
BOUNDRY_START+('(?P<before>)(?P<from>(XXX))(?P<after>(_*)|'+BOUNDRY_END+')').replace('XXX', regex_middle),
flags=re.I,
),
rewrite,
))
REWRITES_new.append((
re.compile(
BOUNDRY_START+'(?P<before>[MXQ])?(?P<from>(XXX))(?P<after>)'.replace('XXX', regex_middle),
flags=re.I,
),
rewrite,
))
RE_MODEL_DONT_TOUCH = re.compile('^\\s*.model\\s+(?P<name>\\S+)\\s+(?P<type>\\S+)', flags=re.MULTILINE)
MODEL_DONT_TOUCH_TO = {
'npn': 'N_DONT_TOUCH_P_DONT_TOUCH_N',
'pnp': 'P_DONT_TOUCH_N_DONT_TOUCH_P',
}
MODEL_DONT_TOUCH_FROM = dict((v, k)for k, v in MODEL_DONT_TOUCH_TO.items())
def test_rewrite_string(s, print_time=False):
"""
>>> test_rewrite_string('''
... Xextdntran BLAH
... ''')
'\\nXsky130_fd_pr__model__nfet_extendeddrain BLAH\\n'
>>> REWRITES_MAPPING['s8rf_pshort_W5p0_L0p15_M2_b'.lower()]
'sky130_fd_pr__rf_pfet_01v8_bM02W5p00L0p15'
>>> print(test_rewrite_string('''
... .subckt s8rf_pshort_W5p0_L0p15_M2_b Bulk Drain Gate Source
... X1 Drain Gate Source Bulk psrf_5p15m2_b
... .ends s8rf_pshort_W5p0_L0p15_M2_b
... '''.strip()))
.subckt sky130_fd_pr__rf_pfet_01v8_bM02W5p00L0p15 Bulk Drain Gate Source
X1 Drain Gate Source Bulk sky130_fd_pr__rf_pfet_01v8_bM02W5p00L0p15
.ends sky130_fd_pr__rf_pfet_01v8_bM02W5p00L0p15
>>> print(test_rewrite_string('''
... .param mult = 1 area = 1.0
... Qnpnpar1x1 c b e s sky130_fd_pr__npn_05v5_W1p00L1p00__model
... .model sky130_fd_pr__npn_05v5_W1p00L1p00__model npn level = 1.0
... '''.strip()))
.param mult = 1 area = 1.0
Qsky130_fd_pr__npn_05v5_W1p00L1p00 c b e s sky130_fd_pr__npn_05v5_W1p00L1p00__model
.model sky130_fd_pr__npn_05v5_W1p00L1p00__model npn level = 1.0
>>> print(test_rewrite_string('''
... *
... * nlowvt, Bin 003, W = 1.0, L = 0.15
... *
... + nlowvt_kt1_diff_3 = 0.0
... + nlowvt_nfactor_diff_3 = 1.713
... + nlowvt_keta_diff_3 = 0.0
... + nlowvt_rdsw_diff_3 = 0.0
... + nlowvt_vth0_diff_3 = 0.050867
... '''.strip()))
*
* sky130_fd_pr__nfet_01v8_lvt, Bin 003, W = 1.0, L = 0.15
*
+ sky130_fd_pr__nfet_01v8_lvt__kt1_diff_3 = 0.0
+ sky130_fd_pr__nfet_01v8_lvt__nfactor_diff_3 = 1.713
+ sky130_fd_pr__nfet_01v8_lvt__keta_diff_3 = 0.0
+ sky130_fd_pr__nfet_01v8_lvt__rdsw_diff_3 = 0.0
+ sky130_fd_pr__nfet_01v8_lvt__vth0_diff_3 = 0.050867
>>> print(test_rewrite_string('''
... * SKY130 Spice File.
... .include "../cells/nfet_01v8/sky130_fd_pr__nfet_01v8__tt_correln.corner.spice"
... .include "../cells/nfet_01v8/sky130_fd_pr__nfet_01v8__mismatch.corner.spice"
... '''.strip()))
* SKY130 Spice File.
.include "../cells/nfet_01v8/sky130_fd_pr__nfet_01v8__tt_correln.corner.spice"
.include "../cells/nfet_01v8/sky130_fd_pr__nfet_01v8__mismatch.corner.spice"
>>> print(test_rewrite_string('''
... + minv = 0
... '''.strip()))
+ minv = 0
>>> print(test_rewrite_string('''
... Minv sadfasdfasdfs sadfad 123 dafslkj
... '''.strip()))
Msky130_fd_pr__model__typical__inv sadfasdfasdfs sadfad 123 dafslkj
>>> print(test_rewrite_string('''
... xhrpoly_0p35 (ra r1) resbody r=rbody*(1+abs(v(r0,r1))*vc1_body+pow(abs(v(r0,r1)),2)*vc2_body+pow(abs(v(r0,r1)),3)*vc3_body)
... .SUBCKT xuhrpoly_1p41 POS NEG SUB
... inline subckt xuhrpoly_5p73 (r0 r1 b)
... xuhrpoly_5p73 (r0 r1 b) xuhrpoly_base w=w l=l mult=mult
... subckt xuhrpoly_base (r1 r2 b)
... '''.strip()))
Xsky130_fd_pr__res_high_po_0p35 (ra r1) resbody r=rbody*(1+abs(v(r0,r1))*vc1_body+pow(abs(v(r0,r1)),2)*vc2_body+pow(abs(v(r0,r1)),3)*vc3_body)
.SUBCKT sky130_fd_pr__res_xhigh_po_1p41 POS NEG SUB
inline subckt sky130_fd_pr__res_xhigh_po_5p73 (r0 r1 b)
Xsky130_fd_pr__res_xhigh_po_5p73 (r0 r1 b) sky130_fd_pr__res_xhigh_po__base w=w l=l mult=mult
subckt sky130_fd_pr__res_xhigh_po__base (r1 r2 b)
>>> print(test_rewrite_string('''
... .model npn_1x1_2p0_hv_base npn level = 1
... '''.strip()))
.model sky130_fd_pr__npn_11v0_W1p00L1p00__base npn level = 1
>>> print(test_rewrite_string('''
... .model pnppar_polyhv pnp level = 1
... '''.strip()))
.model sky130_fd_pr__pnp_05v5_W0p68L0p68__polyhv pnp level = 1
"""
def to_donttouch(m):
assert m.group('type')
t = m.group('type').lower()
if t not in MODEL_DONT_TOUCH_TO:
return m.group(0)
return ".model {} {}".format(m.group('name'), MODEL_DONT_TOUCH_TO[t])
def from_donttouch(m):
assert m.group('type')
t = m.group('type')
if t not in MODEL_DONT_TOUCH_FROM:
return m.group(0)
return ".model {} {}".format(m.group('name'), MODEL_DONT_TOUCH_FROM[t])
s = RE_MODEL_DONT_TOUCH.sub(to_donttouch, s)
s = rewrite_string_c(s)
s = RE_MODEL_DONT_TOUCH.sub(from_donttouch, s)
return s
t0 = time.time()
a = rewrite_string_a(s)
ta = time.time()-t0
t0 = time.time()
b = rewrite_string_b(s)
tb = time.time()-t0
t0 = time.time()
c = rewrite_string_c(s)
tc = time.time()-t0
t0 = time.time()
d = rewrite_string_d(s)
td = time.time()-t0
if print_time:
print('\n@ Time taken, a:%0.2f b:%0.2f c:%0.2f d:%0.2f @\n' % (ta, tb, tc, td))
if a == b and a == c and a == d:
a = a.replace('N_P_N', 'npn')
a = a.replace('P_N_P', 'pnp')
return a
a = repr(a)
b = repr(b)
c = repr(c)
d = repr(d)
lmax = max(len(a), len(b), len(c), len(d))
lmin = min(len(a), len(b), len(c), len(d))
for i in range(0, lmax):
if i >= lmin:
break
if a[i] != b[i]:
break
if a[i] != c[i]:
break
if a[i] != d[i]:
break
error = ['Not matching!']
error.append(repr(s))
error.append(a)
error.append(b)
error.append(c)
error.append(d)
error.append(' '*i+'^')
assert False, "\n".join(error)
def rewrite_string_a(s):
for a, b in REWRITES_new:
s = a.sub(b, s)
return s
def rewrite_string_b(s):
s = s.splitlines(True)
for i, l in enumerate(s):
if ".include" in l:
continue
l = rewrite_string_a(l)
s[i] = l
return "".join(s)
def rewrite_string_c(s):
for a, b in REWRITES_old:
s = a.sub(b, s)
return s
def rewrite_string_d(s):
s = s.splitlines(True)
for i, l in enumerate(s):
if ".include" in l:
continue
l = rewrite_string_c(l)
s[i] = l
return "".join(s)
def main(args, infile):
if os.path.isdir(infile):
all_input_files = sorted(infile.rglob('*'))
if args.single:
pool = multiprocessing.Pool(processes=1)
else:
pool = multiprocessing.Pool()
results = []
for f in all_input_files:
p = os.path.abspath(f)
if os.path.isfile(p):
results.append((p, pool.apply_async(main, (args, p))))
else:
print(p, "is dir")
s = 0
for p, r in results:
try:
s += r.get()
except Exception as e:
print(p, r)
raise
return s
else:
path = os.path.abspath(infile)
ver = version_extract_from_path(path)
if ver is None:
ver = 'XXXX'
else:
ver = "v{}.{}.{}".format(*ver)
old_lib, new_lib, ver = extract_version_and_lib_from_path(path)
tempdir = os.path.join(args.temp, 'spice_rewrite', new_lib, ver)
print()
print()
print("Processing", path, "in", tempdir)
print('-'*75)
filemain(path, tempdir, str(args.output), ver)
print('-'*75)
return 0
if __name__ == "__main__":
import doctest
fails, _ = doctest.testmod()
if fails != 0:
exit(1)
else:
print("Tests Passed")
parser = argparse.ArgumentParser()
parser.add_argument(
"input",
help="The path to the source directory",
type=Path)
parser.add_argument(
"output",
help="The path to the output directory",
type=Path)
parser.add_argument(
"temp",
help="The path to the temp directory",
type=Path)
parser.add_argument(
"--single",
help="The path to the temp directory",
default=False,
action="store_true")
args = parser.parse_args()
sys.exit(main(args, args.input))