| #!/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. |
| |
| |
| # Go through `.v` line by line and correct port naming & case |
| # |
| # |
| import argparse |
| import re |
| import os |
| import json |
| from pathlib import Path |
| from collections import defaultdict |
| from common import files, lib_extract_from_path, mod_extract_from_path, version_extract_from_path, convert_libname, strip_strength |
| |
| debug = False |
| |
| debug_print = lambda x: print(x) if debug else 0 |
| indent = " " * 4 |
| |
| sourcetodests = defaultdict(list) |
| desttosource = defaultdict(list) |
| |
| # Regular expressions |
| all_lib_re = "(s|scs)8(hd|hdll|hs|ms|ls|hvl|lp|iom0s8|x|_esd|_rd|_rf2|phirs_10r|phirs_10r_old)?" |
| |
| 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') |
| |
| def remap_path(old_path, input_dir, output_dir): |
| """ |
| >>> remap_path('skywater-src-nda/scs8ms/V0.0.0/verilog/scs8ms_a2bb2oi_2.v', 'skywater-src-nda', 'tmp') |
| 'tmp/skywater-pdk/libraries/sky130_fd_sc_ms/V0.0.0/cells/a2bb2oi/sky130_fd_sc_ms__a2bb2oi_2.v' |
| >>> remap_path('skywater-src-nda/openfpga/Skywater_130/SRC/routing/sb_22_22.v', '../skywater-src-nda', 'tmp') |
| 'tmp/skywater-pdk/libraries/openfpga/Skywater_130/SRC/routing/sb_22_22.v' |
| >>> remap_path('skywater-src-nda/s8iom0s8/V0.2.1/oa/s8iom0s8/s8iom0s8_top_hvclamp_wopad/behavioral_tmax/verilog.v', 'skywater-src-nda', 'tmp') |
| 'tmp/skywater-pdk/libraries/sky130_fd_io/V0.2.1/cells/top_hvclamp_wopad/sky130_fd_io__top_hvclamp_wopad.behavioral_tmax.v' |
| >>> remap_path('skywater-src-nda/s8iom0s8/V0.1.0/oa/s8iom0s8/s8iom0s8_analog_pad/behavioral/verilog.v', 'skywater-src-nda', 'tmp') |
| 'tmp/skywater-pdk/libraries/sky130_fd_io/V0.1.0/cells/analog_pad/sky130_fd_io__analog_pad.behavioral.v' |
| >>> remap_path('../skywater-src-nda/s8/V1.0.1/VirtuosoOA/libs/tech/csw_p/uni_verilog/verilog.v', '../skywater-src-nda', 'tmp') |
| 'tmp/skywater-pdk/libraries/sky130_fd_pr_base/V1.0.1/tech/csw_p/uni_verilog/verilog.v' |
| >>> remap_path('skywater-src-nda/scs8ms/V0.0.0/verilog/scs8ms_a2BB2oi_2.v', 'skywater-src-nda', 'tmp') |
| 'tmp/skywater-pdk/libraries/sky130_fd_sc_ms/V0.0.0/cells/a2bb2oi/sky130_fd_sc_ms__a2bb2oi_2.v' |
| >>> remap_path('/home/fullpath/Documents/skywater-src-nda/scs8hvl/V0.0.0/verilog/scs8hvl_pg_U_DF_P.v', 'skywater-src-nda', 'tmp') |
| 'tmp/skywater-pdk/libraries/sky130_fd_sc_hvl/V0.0.0/cells/pg_u_df_p/sky130_fd_sc_hvl__pg_u_df_p.v' |
| >>> remap_path('../skywater-src-nda/scs8hs/V0.0.0/verilog/U_DF_P_R_NO_SLEEPB_pg.v', 'skywater-src-nda' ,'tmp') |
| 'tmp/skywater-pdk/libraries/sky130_fd_sc_hs/V0.0.0/cells/u_df_p_r_no_sleepb_pg/sky130_fd_sc_hs__u_df_p_r_no_sleepb_pg.v' |
| >>> remap_path('../skywater-src-nda/s8/V1.2.1/VirtuosoOA/libs/tech/csw_p/verilog/verilog.v', 'skywater-src-nda' ,'tmp') |
| 'tmp/skywater-pdk/libraries/sky130_fd_pr_base/V1.2.1/tech/csw_p/verilog/verilog.v' |
| >>> remap_path('../skywater-src-nda/scs8lp/V0.0.0/verilog/scs8lpa_U_DFB_SETDOM.v', 'skywater-src-nda' ,'tmp') |
| 'tmp/skywater-pdk/libraries/sky130_fd_sc_lp/V0.0.0/cells/u_dfb_setdom/sky130_fd_sc_lp__u_dfb_setdom.v' |
| >>> remap_path('../skywater-src-nda/s8/V1.0.0/VirtuosoOA/libs/tech/short/verilog/verilog.v', 'skywater-src-nda' ,'tmp') |
| 'tmp/skywater-pdk/libraries/sky130_fd_pr_base/V1.0.0/tech/short/verilog/verilog.v' |
| >>> remap_path('../skywater-src-nda/scs8hdll/V1.0.0/verilog/scs8hdll.v', 'skywater-src-nda' ,'tmp') |
| 'tmp/skywater-pdk/libraries/sky130_fd_sc_hdll/V1.0.0/cells/sky130_fd_sc_hdll/sky130_fd_sc_hdll.v' |
| |
| """ |
| global debug |
| |
| old_path = old_path.replace('scs8lpa', 'scs8lp') |
| parent_dir = str(input_dir).split("/")[-1] |
| output_dir = str(output_dir) |
| lib, mod = mod_extract_from_path(old_path) |
| new_lib = convert_libname(lib) |
| |
| |
| try: |
| ver = "V" + ".".join([str(v) for v in version_extract_from_path(old_path)]) |
| except TypeError: |
| ver = "" |
| if lib != None and ver != "" and lib != "???": |
| if mod == None: |
| mod = lib |
| rest, f_name= os.path.split(old_path) |
| if mod not in old_path: |
| # Letter case was changed - primitive declaration of definition |
| temp = mod |
| mod = mod.lower() |
| f_name = f_name.lower().replace(temp, mod) |
| if f_name == "verilog.v": |
| clean_mod = strip_strength(mod) |
| _, d_name = os.path.split(rest) |
| old_path = f"{output_dir}/skywater-pdk/libraries/{lib}/{ver}/cells/{clean_mod}/{lib}__{mod}.{d_name}.v" |
| else: |
| if f_name.find(lib) == -1: |
| f_name = lib + "__" + f_name |
| else: |
| #change lib-mod separator from _ to __ |
| if f_name.find('_') != -1: |
| f_name = f_name[:len(lib)] + '__' + f_name[len(lib)+1:] |
| clean_mod = re.sub(r'_([0-9]{1,2}|m|lp|lp2)$', '', mod) |
| old_path = f"{output_dir}/skywater-pdk/libraries/{lib}/{ver}/cells/{clean_mod}/{f_name}" |
| else: |
| index = old_path.find(parent_dir) + len(parent_dir) |
| old_path = output_dir +"/skywater-pdk/libraries" + old_path[index:] |
| old_path = old_path.replace("/s8iom0s8/", "/sky130_fd_io/") |
| old_path = old_path.replace("/s8/", "/sky130_fd_pr_base/") |
| old_path = old_path.replace("/VirtuosoOA/libs", "") |
| old_path = old_path.replace("cds_", "") |
| |
| |
| if lib != None and new_lib != None: |
| new_path = old_path.replace(lib, new_lib) |
| else: |
| new_path = old_path |
| |
| return new_path |
| |
| |
| |
| def correct_negation(portname): |
| """ Replace '\w*?[BN]' with '\w*?_[BN]' which is the default symbolator notation. |
| Skips if portname is shorter then 2 chars |
| |
| >>> correct_negation("IN") |
| 'IN' |
| >>> correct_negation("N") |
| 'N' |
| >>> correct_negation("GOOD_N") |
| 'GOOD_N' |
| >>> correct_negation("SETB") |
| 'SET_B' |
| >>> correct_negation("LSKN") |
| 'LSK_N' |
| >>> correct_negation("VEN") |
| 'VEN' |
| >>> correct_negation("CLKN") |
| 'CLK_N' |
| >>> correct_negation("GATEN") |
| 'GATE_N' |
| |
| """ |
| |
| portname = portname.replace(" ", "") |
| if len(portname) > 1 and portname[0] != 'V' and portname[-2:] not in ("_N", "_B", "IN"): |
| if portname[-1] == 'N': |
| portname = portname[:-1] + "_N" |
| elif portname[-1] == 'B': |
| portname = portname[:-1] + "_B" |
| return portname |
| |
| def portstr_to_correct_port(port_str): |
| """ Takes string describing ports and returns list of tuples: 'type', 'name', 'width', 'old_name'. |
| |
| >>> portstr_to_correct_port("input X") |
| [('input', 'X', '', 'X')] |
| >>> portstr_to_correct_port("input sleep") |
| [('input', 'SLEEP', '', 'sleep')] |
| >>> portstr_to_correct_port("input sleep, x, y") |
| [('input', 'SLEEP', '', 'sleep'), ('input', 'X', '', 'x'), ('input', 'Y', '', 'y')] |
| >>> portstr_to_correct_port("input sthn") |
| [('input', 'STH_N', '', 'sthn')] |
| >>> portstr_to_correct_port("output 5 x") |
| [('output', 'X', '5', 'x')] |
| >>> portstr_to_correct_port("input k,") |
| [('input', 'K', '', 'k')] |
| >>> portstr_to_correct_port("input k;") |
| [('input', 'K', '', 'k')] |
| >>> portstr_to_correct_port(" output Q; ") |
| [('output', 'Q', '', 'Q')] |
| >>> portstr_to_correct_port("input CK") |
| [('input', 'CLK', '', 'CK')] |
| |
| """ |
| |
| output = [] |
| port_str = port_str.strip() |
| port_str = port_str.rstrip(";") |
| if port_str.find(",") != -1: |
| parts = port_str.split(" ") |
| parts = [x.strip() for x in parts if x != ""] |
| type = parts[0] |
| width = "" |
| names = "".join(parts[1:]).split(",") |
| for name in names: |
| if name != '': |
| if name in ('CK','ck'): |
| new_name = 'CLK' |
| else: |
| new_name = correct_negation(name.upper()) |
| output.append((type, new_name, width, name)) |
| else: |
| fields= port_str.lstrip().split(" ") |
| fields = [x for x in fields if x != ""] |
| if len(fields) == 3: |
| type = fields[0] |
| name = fields[2] |
| width = fields[1] |
| else: |
| type = fields[0] |
| name = fields[1] |
| width = "" |
| if name in ('CK','ck'): |
| new_name = 'CLK' |
| else: |
| new_name = correct_negation(name.upper()) |
| output.append((type, new_name, width, name)) |
| return output |
| |
| def remove_all_comments(contents): |
| # remove multiblock '/* */' comments and '//*******...*/' lines |
| contents = re.sub(r"(?s)/+\*.*?\*/", "", contents) |
| # remove comments from line continuation |
| contents = re.sub(r"(?<=[;\)])\s*//.*?[\n]", "\n", contents) |
| contents = re.sub(r"(?s)(?<=[\n ])table\s*","table\n", contents) |
| # remove all commented lines, unless previous line starts with 'table' |
| contents = re.sub(r"(?s)(?<!table[\n])//.*?(?=[\n])", "", contents) |
| return contents |
| |
| def indent_section(contents, match, indent_last = False): |
| match_string = contents[match.start():match.end()] |
| lines = match_string.split("\n") |
| new_string = lines[0] |
| if indent_last: |
| lines_to_indent = lines[1:] |
| else: |
| lines_to_indent = lines[1:-1] |
| for line in lines_to_indent: |
| new_string += "\n" + indent + line |
| if not indent_last: |
| new_string += "\n" + lines[-1] |
| return new_string |
| |
| def indent_and_format_table(contents, match): |
| |
| make_table = lambda line : "".join([x.center(field_width) if x not in (';',':') else x for x in line.strip().split()]) |
| |
| debug_print(match) |
| match_string = contents[match.start():match.end()] |
| lines = match_string.split("\n") |
| out = lines[0] |
| if lines[1].strip()[:2] == '//': |
| # First line is a comment |
| field_width = max([len(x) for x in re.split(r"[: ]", lines[1])]) + 3 |
| out += "\n// " + make_table(lines[1][2:]) |
| else: |
| field_width = 5 |
| out += "\n" + indent + make_table(lines[1]) |
| for line in lines[2:-1]: |
| out += "\n" + indent + make_table(line) |
| out += "\n" + lines[-1] + "\n" |
| return out |
| |
| |
| def basic_indent_fix(contents): |
| regexp_replace = lambda old, new : re.sub(old, new, contents) |
| rev_iter = lambda iter : [x for x in iter][::-1] |
| # remove whitespace around ';' on line ending |
| contents = regexp_replace(r"\s*;[ ]*", ";") |
| # don't allow multiple spaces |
| contents = regexp_replace(r"(?<!\n)[ \t\r\f\v]{2,}", " ") |
| # replace tabs with space |
| contents = regexp_replace(r"[\t]", " ") |
| # # remove all indent |
| contents = regexp_replace(r"(?<=[\n])\s+", "") |
| contents = regexp_replace(r"[ \t]*endmodule", "endmodule") |
| # remove trailing spaces |
| contents = regexp_replace(r"[ \t\v]*(?=$)", "") |
| # remove space before ',' |
| contents = regexp_replace(r"[ ]*,", ",") |
| # don't allow newline after ',' |
| contents = regexp_replace(r", *[\n]", ", ") |
| # don't allow 2 statements in one line |
| contents = regexp_replace(r"; *(?![\n])", ";\n") |
| # don't break strings |
| contents = regexp_replace(r"(?s)\\[\n] *", " ") |
| # remove empty lines |
| contents = regexp_replace(r"(?s)\n[ \t\v]*(?=[\n])", "\n") |
| # begin always in newline |
| contents = regexp_replace(r"(?<![\n])[ ]?begin", "\nbegin") |
| # add newline before initial |
| contents = regexp_replace(r"(?<=[\n])initial[\n]", "\ninitial\n") |
| # add newline before new 'ifdef' |
| contents = regexp_replace(r"(/s)(?<!`else)[\n]`ifdef", "\n\n`ifdef") |
| # add newline after '`endif' |
| contents = regexp_replace(r"`endif(?)", "`endif\n") |
| # add newline before always |
| contents = regexp_replace(r"always @", "\nalways @") |
| # add newline after '^); |
| contents = regexp_replace(r"(?<=[\n])\);", ");\n") |
| # add newline if brackets contain newline |
| contents = regexp_replace(r"(?<=(= |\w{2}))\((?P<guts>.*?[^\);])[\n]", "(\n\g<guts>\n") |
| # add newline if module brackets contain newline |
| contents = regexp_replace(r"module (?P<mod_decl>.+?) \( *(?P<brac>[^\)\n]+)[\n]", "module \g<mod_decl> (\n\g<brac>") |
| # add newline between end and buildin decl |
| contents = regexp_replace(r"(?s)end\s*[\n](?=(wire|`endif|endmodule))", "end\n\n") |
| # all preprocessor directives should start on new line |
| contents = regexp_replace(r"(?<![\n])\s*(?P<directive>`\w*)", "\n\g<directive>") |
| # indent if |
| contents = regexp_replace(r"if\s+\((?P<guts>.*?)\)\s*[\n](?!(beg|if ))", "if (\g<guts>)\n ") |
| # indent else |
| contents = regexp_replace(r"(?<!`)else\s*[\n](?!begin)", "else \n ") |
| table_match = re.finditer(r"(?s)table.*?[\n]endtable", contents) |
| for match in rev_iter(table_match): |
| contents = contents[:match.start()] + indent_and_format_table(contents, match) + contents[match.end():] |
| linecontin_match = re.finditer(r"(?s)(?<=[\n])[^\n]*?\([\n].*?;", contents) |
| for match in rev_iter(linecontin_match): |
| contents = contents[:match.start()] + indent_section(contents, match, True) + contents[match.end():] |
| # indent everything inside specify |
| specify_match = re.finditer(r"(?s)specify\s+.*?[\n]endspecify", contents) |
| for match in rev_iter(specify_match): |
| contents = contents[:match.start()] + "\n" + indent_section(contents, match) + contents[match.end():] |
| contents = indent_beginend(contents) |
| # indent everything inside modules |
| modules_match = re.finditer(r"(?s)(?P<type>(module|primitive))\s+.*?[\n]end(?P=type)", contents) |
| for match in rev_iter(modules_match): |
| contents = contents[:match.start()] + "\n" + indent_section(contents, match) + contents[match.end():] |
| # unindent preprocessor directives and ');' |
| contents = regexp_replace(r"(?<=[\n])\s+(?=[`)])", "") |
| # additional nl before endmodule |
| contents = regexp_replace(r"endmodule", "\nendmodule") |
| return contents |
| |
| def indent_lines(lines, start, end): |
| """ |
| >>> indent_lines(["a","b","c","d","e"], 1, 3) |
| ['a', ' b', ' c', 'd', 'e'] |
| """ |
| for x in range(start,end): |
| lines[x] = indent + lines[x] |
| return lines |
| |
| def indent_beginend(contents): |
| """ |
| >>> indent_beginend("\\n always\\n begin\\nif blabla\\nbegin\\nsthsth\\nend\\nend") |
| '\\n always\\n begin\\n if blabla\\n begin\\n sthsth\\n end\\nend' |
| >>> indent_beginend("\\n always\\n begin : SMTH\\nif blabla\\nbegin\\nsthsth\\nend\\nend") |
| '\\n always\\n begin : SMTH\\n if blabla\\n begin\\n sthsth\\n end\\nend' |
| """ |
| lines = contents.split("\n") |
| matches = [] |
| for ln_no, line in enumerate(lines): |
| stripped = line.split(":")[0].strip() |
| if stripped in ("begin", "end"): |
| matches.append((stripped, ln_no)) |
| for i in reversed(range(len(matches)-1)): |
| if i == len(matches) - 1: |
| continue |
| if matches[i][0] != matches[i+1][0]: |
| lines = indent_lines(lines, matches[i][1]+1, matches[i+1][1]) |
| if i < len(matches)-2: |
| matches = matches[:i] + (matches[i+2:]) |
| else: |
| matches = matches[:i] |
| return "\n".join(lines) |
| |
| |
| def canonicalize_case(line, lib): |
| """ |
| >>> canonicalize_case("primitive scs8ls_pg_u_dfb(Q, S, R, CLK, D);", 'scs8hs') |
| 'primitive scs8ls__pg_u_dfb (Q, S, R, CLK, D);' |
| >>> canonicalize_case("U_DL_P_NO_pg ( buf_Q, D_delayed, gate, notifier, vpwr, vgnd );", 'scs8hs') |
| 'scs8hs__u_dl_p_no_pg ( buf_Q, D_delayed, gate, notifier, vpwr, vgnd );' |
| |
| >>> canonicalize_case("U_MUX_2_1 ( mux_out, D, SCD, SCE );", 'scs8hs') |
| 'scs8hs__u_mux_2_1 ( mux_out, D, SCD, SCE );' |
| |
| >>> canonicalize_case("U_MUX_2_1_INV ( mux_out, D, SCD, SCE );", 'scs8hs') |
| 'scs8hs__u_mux_2_1_inv ( mux_out, D, SCD, SCE );' |
| |
| >>> canonicalize_case("module scs8hs_dlxtn_2 ( Q, D, GATEN, vpwr, vgnd );", 'scs8hs') |
| 'module scs8hs__dlxtn_2 ( Q, D, GATEN, vpwr, vgnd );' |
| |
| >>> canonicalize_case("primitive scs8hd_pg_U_VGND ( UDP_OUT, UDP_IN, VGND );", 'scs8hs') |
| 'primitive scs8hd__pg_u_vgnd ( UDP_OUT, UDP_IN, VGND );' |
| |
| >>> canonicalize_case("primitive U_MUX_2_1 (X, A0, A1, S);", 'scs8hs') |
| 'primitive scs8hs__u_mux_2_1 (X, A0, A1, S);' |
| |
| >>> canonicalize_case("primitive U_DL_P_NO_pg ( buf_Q, D_delayed, gate, notifier, vpwr, vgnd );", 'scs8hs') |
| 'primitive scs8hs__u_dl_p_no_pg ( buf_Q, D_delayed, gate, notifier, vpwr, vgnd );' |
| |
| >>> canonicalize_case("U_DL_P_NO_pg #0.0001 ( buf_Q, D_delayed, gate, notifier, vpwr, vgnd );", 'scs8hs') |
| 'scs8hs__u_dl_p_no_pg #0.0001 ( buf_Q, D_delayed, gate, notifier, vpwr, vgnd );' |
| |
| >>> canonicalize_case("primitive scs8hvl_pg_U_DF_P ( Q, D, CP );", 'scs8hvl') |
| 'primitive scs8hvl__pg_u_df_p ( Q, D, CP );' |
| >>> canonicalize_case(" module short ( Q, D, CP );", 'scs8hvl') |
| ' module scs8hvl__short ( Q, D, CP );' |
| >>> canonicalize_case("module scs8ls_bufinv_16 (\\n Q, D, CP );", 'scs8ls') |
| 'module scs8ls__bufinv_16 (\\n Q, D, CP );' |
| |
| """ |
| # Assert here is space between module name and '(' |
| line = re.sub(r'(?<=\w{3})\(', ' (', line) |
| libname_search = re.search(fr"{all_lib_re}_", line) |
| nonlibmod_search = re.search(r"U_[A-Za-z0-9_]+? *(?=(\(|#))", line) |
| if libname_search is not None: |
| name_end = line.find(" ", libname_search.end()) |
| mod = '_' + line[libname_search.end():name_end] |
| if not mod.islower(): |
| mod = mod.lower() |
| return line[:libname_search.end()] + mod + line[name_end:] |
| elif nonlibmod_search is not None: |
| mod = line[nonlibmod_search.start():nonlibmod_search.end()] |
| if not mod.islower(): |
| return line[:nonlibmod_search.start()] + lib + '__' + mod.lower() + line[nonlibmod_search.end():] |
| else: |
| mod = [x for x in line.split(" ") if x != ""][1] |
| mod_index = line.find(mod) |
| lib_in_name = mod.find('lib') != -1 |
| if not lib_in_name: |
| old_mod = mod |
| mod = lib + '__' + mod |
| line = line.replace(old_mod, mod) |
| if not mod.islower(): |
| return line[:mod_index] + mod.lower() + line[mod_index+len(mod):] |
| return line |
| |
| def rename_modules(contents, lib): |
| new_lib_name = convert_libname(lib) |
| contents = contents.replace('scs8lpa', 'scs8lp') |
| if new_lib_name != None: |
| contents = contents.replace(lib + '__', new_lib_name + '__') |
| # change standard cells naming |
| contents = re.sub(r"scs8(?=[mhl][vdps]__)", "sky130_fd_sc_", contents) |
| return contents, new_lib_name |
| |
| def main(verilog_file, out_file=None): |
| |
| re_port_decl = re.compile(r'(input|output|inout) .*?(;|$)') |
| re_mod_def = re.compile(fr"^\s*(primitive|module)\s+{all_lib_re}_\w+.*?\(") |
| re_mod_def_cap = re.compile("^\s*(primitive|module) U_.*? ?\(") |
| re_mod_def_fd_pr = re.compile("^\s*module\s*\w+.*?\(") |
| re_mod_decl = re.compile(fr"^\s*{all_lib_re}_") |
| re_prim_decl = re.compile(fr"^\s*{all_lib_re}_\w+([A-Z0-9_]+[a-z_]*)?\s+(#[0-9\.]+\s)?\([\w$', ]+\).*?;") |
| re_prim_decl_cap = re.compile(fr"^\s*[A-Z1-9_]+([a-z_]*)?\s+(\#[0-9\.]*?\s+)?\([\w$', ]+\).*?\s?;") |
| |
| with open(verilog_file, 'r') as in_f: |
| contents = in_f.read() |
| file_dir, filename = os.path.split(verilog_file) |
| |
| contents = contents.replace('scs8lpa', 'scs8lp') |
| lib = lib_extract_from_path(verilog_file) |
| out = "" |
| ports_to_change = [] |
| for ln_no, line in enumerate(contents.split("\n")): |
| mod_decl = re_mod_decl.search(line) |
| mod_def = re_mod_def.search(line) |
| mod_def_cap = re_mod_def_cap.search(line) |
| prim_decl_cap = re_prim_decl_cap.search(line) |
| prim_decl = re_prim_decl.search(line) |
| # Only this lib contains cell with no libname in name |
| if verilog_file.find('s8phirs_10r') != -1: |
| mod_def_fd_pr = re_mod_def_fd_pr.search(line) |
| all_regex = [mod_decl, mod_def, mod_def_cap, prim_decl, prim_decl_cap, mod_def_fd_pr] |
| else: |
| all_regex = [mod_decl, mod_def, mod_def_cap, prim_decl, prim_decl_cap] |
| # All letters in mod name should have the same case (all upper or all lower) |
| if any(all_regex): |
| new_line = canonicalize_case(line, lib) |
| if new_line != line: |
| debug_print(f"Line {ln_no+1}: Canonicalized!! Change:\n--\t| {line}\n++\t| {new_line}") |
| debug_print("m\t| mod_decl|mod_def|mod_def_cap|prim_decl|prim_decl_cap") |
| debug_print(f"\t| {mod_decl!=None}{' '*3}|{mod_def!=None}{' '*2}|{mod_def_cap!=None}{' '*8}|{prim_decl!=None}{' '*4}|{prim_decl_cap!=None}") |
| line = new_line |
| #if more then one statement in module/primitive def/decl line - split it |
| if line.count(";") > 1: |
| debug_print(f"Line {ln_no+1}: Line split!! Result:") |
| first_line = line.find(";") + 1 |
| debug_print(f"\t| {line[:first_line]}\n\t| {line[first_line:]}") |
| out += line[:first_line] + "\n" |
| line = line[first_line:] |
| port_decl = re_port_decl.search(line) |
| |
| # All ports names should be uppercase, negation should be consistent with symbolator requirements |
| if port_decl: |
| if line[:2]=="//" or re.search(r'["$%?=\\]', line): |
| debug_print(f"Line {ln_no+1} : Discarded as not being port decl:\n\t| {line}") |
| out += line + "\n" |
| continue |
| line = re.sub(r"(?<!^)\s*//.*", "", line) |
| port_decl_end = min(port_decl.end(), len(line)) |
| if port_decl_end < port_decl.start(): |
| continue |
| sep = line[port_decl_end-1] |
| old_decl = line[port_decl.start():port_decl_end ] |
| ports = portstr_to_correct_port(old_decl) |
| ports_to_change += [(x[1],x[3]) for x in ports if x[1] != ""] |
| if len(ports)==1: |
| port = ports[0] |
| new_decl = f"{port[0]} {port[2]+' ' if port[2]!='' else ''}{port[1]}{sep if sep in (',', ';') else ''}" |
| else: |
| assert ports[0][2]=='' |
| new_decl = f"{ports[0][0]} {', '.join([x[1] for x in ports])}{sep if sep in (',', ';') else ''}" |
| if old_decl == new_decl: |
| pass |
| else: |
| debug_print(f"Line {ln_no+1} :Port decl changed!!") |
| debug_print(f"\t| '{old_decl}' >> '{new_decl}'") |
| line = line[:port_decl.start()] + new_decl + line[port_decl.end():] |
| out += line + "\n" |
| |
| for new, old in ports_to_change: |
| if new==old: |
| continue |
| try: |
| out = re.sub(fr"(?<=\W){old}(?=\W)", new, out) |
| except re.error: |
| print(f"Regexp '(?<=\W){old}(?=\W)' failed on pair: old = {old}, new = {new}") |
| exit(1) |
| if lib is None: |
| lib = "X"*20 |
| out, _ = rename_modules(out, lib) |
| out = remove_all_comments(out) |
| out = basic_indent_fix(out) |
| if lib != '???': |
| out = re.sub(rf"{lib}_(?=[^_])", f"{convert_libname(lib)}__", out) |
| out = out.replace('S8IOM0S8', 'SKY130_FD_IO') |
| out = Copyright_header + out |
| |
| if out_file is not None: |
| dest_dir = os.path.split(out_file)[0] |
| if not os.path.isdir(dest_dir): |
| os.makedirs(dest_dir) |
| with open(out_file, 'w') as out_f: |
| out_f.write(out) |
| else: |
| pass |
| print(">>> Output:\n"+"_"*60+ "\n" + out) |
| |
| 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/file", |
| type=Path) |
| parser.add_argument( |
| "output", |
| help="The path to the output directory", |
| type=Path) |
| parser.add_argument( |
| "sourcetodests", |
| help="output file mapping input to ouput", |
| type=Path) |
| args = parser.parse_args() |
| if not os.path.isdir(args.input): |
| debug = True |
| main(str(args.input)) |
| else: |
| files = sorted(args.input.rglob('*.v')) |
| for f in files: |
| f = str(f) |
| skipped = ['stubs', 'openfpga', 'sram', 's8fmlt', 'osu130'] |
| if any(word in f for word in skipped): |
| continue |
| print(f) |
| out_f = remap_path(f, args.input, args.output)[:-2] + ".full.v" |
| main(f, out_f) |
| sourcetodests[f].append(out_f) |
| desttosource[out_f].append(f) |
| |
| with open(args.sourcetodests, 'w') as srctodst: |
| json.dump(sourcetodests, srctodst, indent=2) |
| with open(args.sourcetodests.parent / Path("reverse-" + args.sourcetodests.name), 'w') as dsttosrc: |
| json.dump(desttosource, dsttosrc, indent=2) |