blob: 213bcb3d4ea1adccc0ede5b9fc32b3dc723a3b37 [file] [log] [blame] [edit]
#/usr/bin/python
import os
import re
import simplejson as json
import subprocess
import argparse
from pathlib import Path
from common import lib_extract_from_path
debug = False
debug_print = lambda x: print(x) if debug else 0
def call_with_retcode(cmd):
""" Runs subprocess and returns return code. If return code != 0 prints stderr output"""
p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
stdout, stderr = p.communicate()
stderr = stderr.decode("utf-8")
retcode = p.wait()
if retcode != 0:
print("")
print("Command '{}' failed with error:".format(" ".join(cmd)))
print(stderr)
print("")
return retcode
return 0
def draw_module(blackbox_file, out_dir, libname):
"""Draw module diagram using symbolator. Operates on verilog blackbox files"""
cmd = ["symbolator", "-t", "--title", "-i", blackbox_file, "-f", "png", "-o", out_dir]
if libname !="":
cmd+= [ "--libname", libname]
return call_with_retcode(cmd)
def correct_negation(portname):
""" Replace '\w*?N' with '\w*?_N' which is the default symbolator notation.
Skips if portname is shorter then 2 chars, or if port belongs to power section
>>> correct_negation("2IN")
'2IN'
>>> correct_negation("2GOOD_N")
'2GOOD_N'
>>> correct_negation("4SETB")
'4SET_B'
>>> correct_negation("3LSKN")
'3LSK_N'
>>> correct_negation("5VEN")
'5VEN'
>>> correct_negation("3CLKN")
'3CLK_N'
>>> correct_negation("4GATEN")
'4GATE_N'
"""
portname = portname.replace(" ", "")
clue = int(portname[0])
if len(portname) > 2 and portname[-2:] not in ("_N", "_B", "IN"):
if clue in (1,2,3,4) and portname[-1] == 'N':
portname = portname[:-1] + "_N"
elif clue == 4 and portname[-1] == 'B':
portname = portname[:-1] + "_B"
elif clue == 5 and portname[1]!='V' and portname[-1] == 'B':
portname = portname[:-1] + "_B"
return portname
def portstr_to_tuple(port_str):
""" Takes string describing port and returns tuple: 'many', 'type', 'name', 'width'.
If string defines many ports (like "input A, B, C;") 'many' will be True and name is a list of all names"""
many_ports = False
debug_print(port_str)
if port_str.find(",") != -1:
many_ports = True
parts = port_str.split(" ")
parts = [x for x in parts if x != ""]
type = parts[0]
width = ""
names = "".join(parts[1:]).split(",")
names = [ x.replace(" ","") for x in names ]
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 many_ports:
names = [normalize_and_add_order_clue(name) for name in names]
return many_ports, type, names, width
else:
name = normalize_and_add_order_clue(name)
return many_ports, type, name, width
def normalize_and_add_order_clue(portname):
""" Change all chars to uppercase, correct_negation and add order clue for sorting
>>> normalize_and_add_order_clue("an")
'1A_N'
>>> normalize_and_add_order_clue("x")
'1X'
>>> normalize_and_add_order_clue("clk")
'3CLK'
>>> normalize_and_add_order_clue("in")
'2IN'
>>> normalize_and_add_order_clue("sthset")
'4STHSET'
>>> normalize_and_add_order_clue("vsth")
'5VSTH'
>>> normalize_and_add_order_clue("sleepsth")
'5SLEEPSTH'
>>> normalize_and_add_order_clue("kapwr")
'5KAPWR'
>>> normalize_and_add_order_clue("lowlvpwra")
'5LOWLVPWRA'
>>> normalize_and_add_order_clue("R")
'4R'
>>> normalize_and_add_order_clue("S")
'4S'
>>> normalize_and_add_order_clue("G")
'3G'
>>> normalize_and_add_order_clue("sce")
'3SCE'
"""
portname = portname.upper()
re_pwr_or_sleep = re.compile(r"^(SLEEP|\w*?PWR)")
# Add order clue:
if re.search(r"(GATE|CLOCK|CLK)", portname) or portname in ('SCE','G'):
portname = "3" + portname
elif re.search(r"(SET|RESET)", portname) or portname in ('R','S'):
portname = "4" + portname
elif re.search(r"^[A-Z][0-9]?[N]?$", portname) and portname != 'IN':
portname = "1" + portname
elif portname[0] == "V" or re_pwr_or_sleep.search(portname):
portname = "5" + portname
else:
portname = "2" + portname
portname = correct_negation(portname)
return portname
def correct_port_decl(contents):
""" Given the module declaration, changes the ports declaration to match symbolator requirements.
All ports are sorted in order:
(1) single letter (with index) ports should be first,
(2) other should go under in the same section,
(3) clock/gate ports should be the secoind section,
(4) set/reset ports should be the third section,
(5) power ports should be the last section,
And divided into sections.
All portnames change to uppercaase.
Negation notation change from 'name[NB]' to '<name>_[NB]'.
"""
global debug
ports = []
sections = {1: "//# {{}}",
2: "//# {{}}",
3: "//# {{clocks|}}",
4: "//# {{control|}}",
5: "//# {{power|}}"}
# remove comments
contents = re.sub(r"[/]{2}.*", "", contents)
# remove empty lines
contents = re.sub(r"[\n][\n]", "\n", contents)
# find module declaration header
inputs_decl_pos = re.search(r"(?s)module\s+\w*?\s*\(.*?\s*;", contents)
inputs_decl = contents[inputs_decl_pos.start(): inputs_decl_pos.end()]
debug_print( "_______CONTENTS_____\n" + contents)
debug_print( "______MOD_HEADER____\n" + inputs_decl)
if not re.search("(input |output |inout )", inputs_decl):
# not ansi declaration - ports definition in module body
module_header = inputs_decl
inputs_decl_pos = re.search(r"(?s)(?<=\);).*?(?=endmodule)", contents)
inputs_decl = contents[inputs_decl_pos.start(): inputs_decl_pos.end()]
# don't allow single declaration in multiple lines
inputs_decl = re.sub(r",\s*[\n]", ",", inputs_decl)
lines = inputs_decl.replace(";", "").split("\n")[1:-1]
decl_end = ";\nendmodule"
line_end = ";\n"
else:
# ansi declaration - header contains port type definitions
lines = inputs_decl[:-2].split("\n")[1:]
lines = [line.lstrip() for line in lines]
module_header = inputs_decl[:inputs_decl.find("(") + 1]
decl_end = "\n);"
line_end = ",\n"
debug_print("_____HEADER_____\n" + module_header)
debug_print("______DECL______\n" + inputs_decl)
debug_print("______END_______\n" + decl_end[2:])
debug_print("________________\n")
lines = [re.sub("([ ]?^[ ,]|[ ,][ ]?$)", "", line) for line in lines if line != ","]
debug_print(lines)
for line in lines:
many, type, n, width = portstr_to_tuple(line)
if many:
for name in n:
ports.append((type, name, width))
else:
ports.append((type, n, width))
ports = sort_ports(ports)
new_decl = ""
prev_clue = 0
debug_print("______OUT______\n")
for type, name, width in ports:
clue = int(name[0])
name = name[1:]
if clue != prev_clue:
if clue > 2:
new_decl += " " + sections[clue] + "\n"
prev_clue = clue
debug_print((type, name, width))
new_decl += " " + type + " " + width + name + line_end
new_decl = new_decl[:-2] + decl_end
contents = contents[:inputs_decl_pos.start()] + module_header + "\n" + new_decl + contents[inputs_decl_pos.end():]
debug_print(contents)
return contents
def sort_ports(port_list):
"""
>>> sort_ports([(0,"1A_N"),(1,"1B"),(2,"1C")])
[(0, '1A_N'), (1, '1B'), (2, '1C')]
"""
port_list = sorted(port_list, key = lambda x: x[1].replace("_N", ""))
return port_list
def correct_all_modules(contents):
out = ""
for module in re.finditer(r"(?s)\(\* blackbox \*\)[\n]module.*?endmodule", contents):
debug_print(module)
out += correct_port_decl(contents[module.start(): module.end()]) + "\n"
return out
def main(input_file):
with open(input_file, 'r') as in_f:
contents = in_f.read()
contents = correct_all_modules(contents)
file_dir, filename = os.path.split(input_file)
lib = lib_extract_from_path(input_file)
if lib not in ('???', None):
contents = contents.replace(f"{lib}_", "")
else:
lib = ""
temp_file = "tmp_" + filename
with open(temp_file, 'w') as out_f:
out_f.write(contents)
if draw_module(temp_file, file_dir, lib) == 0:
pass
debug_print("Passed")
else:
print("Failed")
os.remove(temp_file)
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 '.blackbox.v' files and generate '.png' in the same directory",
type=Path)
args = parser.parse_args()
if not os.path.isdir(args.input):
debug = True
main(args.input)
else:
files = args.input.rglob('*.blackbox.v')
for f in files:
f = str(f)
print(f)
main(f)