| #/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) |
| |