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