blob: 904b346a68aca1662d2dbf5acd1fdf6a9e5d4d0f [file] [log] [blame]
#!/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")