| #!/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 json |
| import os |
| import pprint |
| import re |
| import shutil |
| import subprocess |
| |
| from collections import defaultdict |
| from pathlib import Path |
| |
| import common |
| from common import \ |
| lib_extract_from_name, \ |
| extract_version_and_lib_from_path, \ |
| copy_file_to_output, \ |
| \ |
| convert_libname, \ |
| convert_cell_fullname, \ |
| convert_pinname |
| |
| from lef_canonical import canonicalize_lef, RE_MACRO, RE_PIN, RE_PIN_TYPE, RE_PIN_DIR |
| |
| |
| RE_COMMENT = re.compile(r'#.*$', re.MULTILINE) |
| |
| |
| def change_names_lef(new_lib, contents, extra=''): |
| |
| output = [] |
| info = {} |
| |
| last_macro_endpos = 0 |
| |
| for macro in RE_MACRO.finditer(contents): |
| between = contents[last_macro_endpos:macro.start(0)] |
| if between.strip(): |
| output.append(('???', between)) |
| last_macro_endpos = macro.end(0) |
| |
| old_macroname = macro.group('cellname') |
| old_lib, old_cellname = lib_extract_from_name(old_macroname) |
| if not old_cellname.startswith(extra): |
| assert extra not in old_cellname or 'probe' in old_cellname, (old_cellname, extra, old_macroname) |
| assert old_lib, (old_macroname, old_lib, old_cellname) |
| old_macroname = old_lib+'_'+extra+'_'+old_cellname |
| |
| new_macroname = convert_cell_fullname(old_macroname, new_lib) |
| assert new_macroname.count('__') == 1, (new_macroname, old_macroname, new_lib) |
| ext_lib, new_cell = new_macroname.split('__', 1) |
| assert new_lib == ext_lib, (new_lib, ext_lib) |
| |
| content = macro.group('content') |
| |
| output_macro = [] |
| output.append((new_macroname, output_macro)) |
| output_macro.append(f'\nMACRO {new_macroname}\n') |
| |
| macro_info = {} |
| assert new_macroname not in info, (new_macroname, info[newmacro_name]) |
| info[new_macroname] = macro_info |
| |
| last_pin_endpos = 0 |
| for pin in RE_PIN.finditer(content): |
| between = content[last_pin_endpos:pin.start(0)] |
| if between.strip(): |
| output_macro.append(between) |
| last_pin_endpos = pin.end(0) |
| |
| old_pinname = pin.group('pinname') |
| new_pinname = convert_pinname(old_pinname, new_macroname) |
| pin_content = pin.group('content') |
| |
| pin_info = {} |
| assert new_pinname not in pin_info, (new_pinname, pin_info[new_pinname]) |
| macro_info[new_pinname] = pin_info |
| |
| pin_dir = RE_PIN_DIR.search(pin_content) |
| if pin_dir: |
| pin_info['direction'] = pin_dir.group(1).strip() |
| |
| pin_type = RE_PIN_TYPE.search(pin_content) |
| if pin_type: |
| pin_info['type'] = pin_type.group(1).strip() |
| if pin_info['type'] in ('POWER', 'GROUND') and 'direction' in pin_info: |
| del pin_info['direction'] |
| |
| output_macro.append(f" PIN {new_pinname}\n") |
| output_macro.append(pin_content) |
| output_macro.append(f" END {new_pinname}\n") |
| |
| output_macro.append(content[last_pin_endpos:]) |
| output_macro.append(f'END {new_macroname}\n') |
| |
| return output, info |
| |
| |
| def filemain(input_path, temp_dir, final_dir, args): |
| input_file = os.path.basename(input_path) |
| if '_' in input_file: |
| input_base, _ = input_file.split('.', 1) |
| libname, extra = input_base.split('_', 1) |
| if extra == 's8p': |
| extra = 'lpflow' |
| print("Extra:", extra) |
| else: |
| extra = '' |
| |
| tmp_lef = os.path.join(temp_dir, input_file) |
| shutil.copyfile(input_path, tmp_lef) |
| |
| with open(tmp_lef) as in_f: |
| contents = in_f.read() |
| |
| contents = RE_COMMENT.sub('', contents) |
| out = contents |
| |
| old_lib, new_lib, ver = extract_version_and_lib_from_path(input_path) |
| output, info = change_names_lef(new_lib, out, extra) |
| |
| header = output.pop(0) |
| assert header[0] == '???', header |
| header = header[1] |
| header = "\n".join(x for x in header.splitlines() if x.strip() and not x.strip().startswith('#')) |
| |
| print("LEFs ----") |
| for m in output: |
| macroname, macrocontent = m |
| assert macroname != '???', m |
| |
| macrocontent = canonicalize_lef(''.join(macrocontent)) |
| |
| tmp_file = os.path.join(temp_dir, macroname+'.lef') |
| assert not os.path.exists(tmp_file), tmp_file |
| with open(tmp_file, 'w') as f: |
| f.write(common.copyright_header['#']) |
| f.write(header) |
| f.write(macrocontent) |
| |
| copy_file_to_output(tmp_file, final_dir, new_lib, ver, macroname) |
| print("---------") |
| print() |
| print("Pins ----") |
| for macroname, macrodata in sorted(info.items()): |
| tmp_file = os.path.join(temp_dir, macroname+'.lef.pins') |
| assert not os.path.exists(tmp_file), tmp_file |
| with open(tmp_file, 'w') as f: |
| for pinname, pinprop in sorted(macrodata.items()): |
| f.write(f"{pinname} ") |
| f.write(" ".join(f"{a}={b}" for a, b in sorted(pinprop.items()))) |
| f.write("\n") |
| copy_file_to_output(tmp_file, final_dir, new_lib, ver, macroname) |
| print("---------") |
| |
| |
| |
| if __name__ == "__main__": |
| import doctest |
| fails, _ = doctest.testmod() |
| if fails>0: |
| sys.exit(1) |
| parser = argparse.ArgumentParser() |
| parser.add_argument( |
| "input", |
| help="The path to the source directory/file", |
| 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) |
| args = parser.parse_args() |
| common.main('lef', filemain, args) |