| #!/usr/bin/python |
| |
| import argparse |
| import re |
| import os |
| import sys |
| from common import lib_extract_from_path, convert_libname |
| from pathlib import Path |
| |
| debug = False |
| |
| debug_print = lambda x: print(x) if debug else 0 |
| |
| def main(in_file, out_file=None): |
| """ Strip all elements unparsable by Yosys while keeping as much content as possible. |
| All modules are renamed basing on the lib name. |
| It does not strip comments. Returns whole file contents as string.""" |
| |
| unwanted_blocks = [ |
| # tasks |
| ("task\s\w*\s*(\(\w*\)\s*)?;\s*(//\s[\w\(\). ]*)?[\n].*?endtask[\n]"), |
| # initial blocks |
| ("(?<=[\n])(?P<indent> *)initial[ \n](?P=indent)begin.*?[\n](?P=indent)end[\n]"), |
| # always blocks |
| ("(?P<indent> *)always\s*@\s*\([a-zA-Z0-9*_\n ]*\)(\s*//.*)?\s*[\n](?P=indent)*begin.*?[\n](?P=indent)end[\n]"), |
| # tables |
| ("\s*table((?!table).)*[\n]\s*endtable")] |
| unwanted_expresions = [ |
| "[$]readmemh", |
| "disable ", |
| "event ", |
| "ifnone", |
| "pulldown[( ]", |
| "pullup[01]? ", |
| "real ", |
| "realtime "] |
| unwanted_directives = [ |
| "delay_mode_path", |
| "disable_portfaults", |
| "enable_portfaults", |
| "timescale .*", |
| "nosuppress_faults", |
| "suppress_faults"] |
| |
| with open(in_file, 'r') as in_f: |
| contents = in_f.read() |
| |
| lib = lib_extract_from_path(in_file) |
| |
| debug_print(">>> Debug:\n"+"_"*60) |
| |
| # remove strength specification |
| contents = re.sub(r"\s+\(\s?(pull|supply|strong|highz|large|weak|medium|small)[01],\s?[a-z01]+\s?\)", " ", contents) |
| # remove delay specification, both lonely and in module init |
| contents = re.sub(r"\s*#([0-9.]+|\([a-z_, ]*\))(\s*;)?", "", contents) |
| contents = re.sub(r" #\([a-z0-9_, ]*?\)", "", contents) |
| # remove $display statements |
| contents = re.sub(r"[$]display\s?\(.*?\);", "", contents) |
| # user-defined primitives not supported - changing to modules |
| contents = re.sub(r"primitive", "module", contents) |
| debug_print(contents) |
| # remove alias cells |
| contents = re.sub(r"(?s)module [a-z_]*?alias.*?endmodule", "", contents) |
| |
| for block in unwanted_blocks: |
| contents = re.sub(rf"(?s){block}", "", contents) |
| for keyword in unwanted_expresions: |
| contents = re.sub(rf"[\n]\s*{keyword}.*;", "", contents) |
| for directive in unwanted_directives: |
| contents = re.sub(rf"[\n]\s*`{directive}", "", contents) |
| |
| contents = re.sub(r"for\s?\(.*;.*;.*\)", "for(j = 0; j< 0; j = j + 1)", contents) |
| # remove events |
| contents = re.sub(r"event_(warning|errflag|error)_\w+;", "", contents) |
| # remove lines beggining with '->' |
| contents = re.sub(r"[\n][\W]*[-][>]\s*([a-z0-9_]*\s?;.*|\n)", "" , contents) |
| # negative indexes in part-select adressing unsupported! |
| contents = re.sub(r" -: ", " : -", contents) |
| contents = re.sub(r"\([a-z]+\s+=>\s+\(.*;", "", contents) |
| # make sure no arg is uninitialized, repeated to deal with 2 and more empty args next to each other |
| contents = re.sub(r",\s*,", ", 0,", contents) |
| contents = re.sub(r",\s*,", ", 0,", contents) |
| # change floating to unknown |
| contents = re.sub(r"1'bz", "1'bx", contents) |
| |
| # Add missing TOK_ID for modules created from primitives |
| lines = contents.split("\n") |
| output = "" |
| elements_count = 1 |
| # Module definition with upper |
| re_mod_def_cap = re.compile(fr"^\s*module\s*{lib}_\w+.*?;") |
| re_mod_decl = re.compile(r"^\s*sky130_(fd|ef)_[ips][rco](_(base|rf|rf2|esd|hd|hdll|hs|ms|ls|lp))?_[A-Za-z_]*?\s\(") |
| # Initialization of former user-defined primitives... |
| re_prim_decl = re.compile(fr"^\s*{lib}_\w+([A-Z0-9_]+[a-z_]*)?\s+\([\w$', ]+\).*?;") |
| # ...from which some have no 'lib' in name, but begin with 'u_' |
| re_prim_decl_cap = re.compile(fr"^\s*u_[a-z1-9_]+?\s+\([\w$', ]+\).*?\s?;") |
| # Standard element initialization |
| re_std_elem = re.compile(r"^\s*(tran(if)?|pmos|nmos|cmos)[01]?\s?\([a-zA-Z0-9',_|\[\] ]*\)\s?;") |
| # One line initializing two modules |
| re_double_init = re.compile(r"^[^(]*\([^()]*\)\s*,\s*\([^:]*\)\s*;") |
| |
| for ln_no, line in enumerate(lines): |
| if re.search(r"^\s*\(\s?(posedge |negedge )?[A-Z_a-z]*?\s*=>\s*\(.*?:.*?\)\s*\)\s*=\s*.*$", line): |
| continue |
| second = re_double_init.search(line) |
| if second: |
| debug_print(f"Line {ln_no}:Detected multiple mod decl in one line!!") |
| debug_print(f"\t| {line}") |
| first_paren_end = line[:-5].rfind(")") |
| second_paren_begin = line.rfind("(") |
| name = line.split("(")[0].strip() |
| line = line[:first_paren_end + 1] + f";\n {name} {name}__{str(elements_count)}" + line[second_paren_begin:] |
| elements_count += 1 |
| std_elem= re_std_elem.search(line) |
| prim_decl_cap = re_prim_decl_cap.search(line) |
| prim_decl = re_prim_decl.search(line) |
| mod_decl = re_mod_decl.search(line) |
| mod_def_cap = re_mod_def_cap.search(line) |
| if std_elem or prim_decl or prim_decl_cap or mod_decl : |
| index = line.find("(") |
| # Add TOK_ID created from module name and unique number |
| name = line.split("(")[0].strip() |
| debug_print(f"Line {ln_no} : Found module decl and added name!!") |
| debug_print("\t| std_elem|prim_decl|prim_decl_cap|mod_decl|") |
| debug_print(f"\t| {std_elem is not None}\t|{prim_decl is not None}\t|{prim_decl_cap is not None}\t|{mod_decl is not None}\n|") |
| debug_print(f"--\t| {line}") |
| line = line[:index] + f" {name}__{str(elements_count)} " + line[index:] |
| debug_print(f"++\t| {line}") |
| elements_count += 1 |
| # missing edge identifier - assuming positive |
| missing_edge = re.search(r"\(\s?[A-Z]+\s?=>\s?\(", line) |
| if missing_edge: |
| line = line[:missing_edge.start()+1] + "posedge " + line[missing_edge.start() + 1:] |
| output += line + "\n" |
| |
| |
| # rename standard cells if any |
| output = re.sub(r"scs8(?=[mhl][dps]_)", "sky130_fd_sc_", output) |
| # remove multiple empty lines |
| output = re.sub(r"[\n] *[\n]", "\n", output) |
| |
| if out_file is not None: |
| with open(out_file, 'w') as out_f: |
| out_f.write(output) |
| else: |
| print(">>> Output:") |
| print_cont(output) |
| |
| def print_cont(content): |
| print("_"*60) |
| for ln_no, line in enumerate(content.split("\n")): |
| print(f"{ln_no}\t|{line}") |
| |
| |
| if __name__ == "__main__": |
| |
| import doctest |
| fails, _ = doctest.testmod() |
| if fails>0: |
| exit(1) |
| parser = argparse.ArgumentParser() |
| parser.add_argument( |
| "input", |
| help="The path to the source directory/file. If file is passed it will run in debug mode with no output file" + |
| "If dir is passed it will find '.full.v' files generate '.simple.v' in the same directory", |
| type=Path) |
| args = parser.parse_args() |
| if not os.path.isdir(args.input): |
| debug = True |
| main(str(args.input)) |
| else: |
| files = args.input.rglob('*.full.v') |
| for f in files: |
| f = str(f) |
| print(f) |
| main(f, f[:-7] + ".simple.v") |
| |