| #!/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)) |