| #!/usr/bin/env python3 | |
| """ | |
| A simple script that fixes up post-pnr verilog and SDF files from VPR: | |
| - Removes incorrect constants "1'b0" connected to unconnected cell port, | |
| - Disconnects all unconnected outputs from the "DummyOut" net, | |
| - appends a correct prefix for each occurrence of a binary string in round | |
| brackets. For example "(010101)" is converted into "(6'b010101)". | |
| When the option "--split-ports" is given the script also breaks all references | |
| to wide cell ports into references to individual pins. | |
| One shortcoming of the script is that it may treat a decimal value of 10, 100 | |
| etc. as binary. Fortunately decimal literals haven't been observed to appear | |
| in Verilog files written by VPR. | |
| """ | |
| import argparse | |
| import os | |
| import re | |
| # ============================================================================= | |
| def split_verilog_ports(code): | |
| """ | |
| Splits assignments of individual nets to wide cell ports into assignments | |
| of those nets to 1-bit wide cell ports. Effectively splits cell ports as | |
| well. | |
| """ | |
| def sub_func(match): | |
| port = match.group("port") | |
| conn = match.group("conn").strip().replace("\n", "") | |
| # Get individual signals | |
| signals = [s.strip() for s in conn.split(",")] | |
| # Format new port connections | |
| conn = [] | |
| for i, signal in enumerate(signals): | |
| j = len(signals) - 1 - i | |
| conn.append(".\\{}[{}] ({} )".format(port, j, signal)) | |
| conn = ", ".join(conn) | |
| return conn | |
| code = re.sub( | |
| r"\.(?P<port>\S+)\s*\(\s*{(?P<conn>[^}]+)}\s*\)", | |
| sub_func, | |
| code, | |
| flags=re.DOTALL | |
| ) | |
| return code | |
| def split_sdf_ports(code): | |
| """ | |
| Escapes square brackets in port names given in delay path specifications | |
| which results of indexed multi-bit ports being represented as individual | |
| single-bit ones. | |
| """ | |
| def sub_func(match): | |
| return match.group(0).replace("[", "\\[").replace("]", "\\]") | |
| code = re.sub( | |
| r"\((?P<keyword>SETUP|HOLD|IOPATH)\s+" | |
| r"(?P<port1>(\([^\)]*\))|\S+)\s+(?P<port2>(\([^\)]*\))|\S+)", sub_func, | |
| code | |
| ) | |
| return code | |
| # ============================================================================= | |
| def main(): | |
| # Parse arguments | |
| parser = argparse.ArgumentParser( | |
| description=__doc__, | |
| formatter_class=argparse.RawDescriptionHelpFormatter | |
| ) | |
| parser.add_argument( | |
| "--vlog-in", type=str, default=None, help="Input Verilog file" | |
| ) | |
| parser.add_argument( | |
| "--vlog-out", type=str, default=None, help="Output Verilog file" | |
| ) | |
| parser.add_argument( | |
| "--sdf-in", type=str, default=None, help="Input SDF file" | |
| ) | |
| parser.add_argument( | |
| "--sdf-out", type=str, default=None, help="Output SDF file" | |
| ) | |
| parser.add_argument( | |
| "--split-ports", action="store_true", help="Split multi-bit ports" | |
| ) | |
| args = parser.parse_args() | |
| # Check args | |
| if not args.vlog_in and not args.sdf_in: | |
| print("Please provide at least one of --vlog-in, --sdf-in") | |
| exit(1) | |
| # Process Verilog netlist | |
| if args.vlog_in: | |
| # Read the input verilog file | |
| with open(args.vlog_in, "r") as fp: | |
| code = fp.read() | |
| # Remove connection to 1'b0 from all ports. Do this before fixing up | |
| # binary string prefixes so binary parameters won't be affected | |
| code = re.sub( | |
| r"\.(?P<port>\w+)\s*\(\s*1'b0\s*\)", r".\g<port>(1'bZ)", code | |
| ) | |
| # Remove connections to the "DummyOut" net | |
| code = re.sub( | |
| r"\.(?P<port>\w+)\s*\(\s*DummyOut\s*\)", r".\g<port>()", code | |
| ) | |
| # Fixup multi-bit port connections | |
| def sub_func_1(match): | |
| port = match.group("port") | |
| conn = match.group("conn") | |
| conn = re.sub(r"1'b0", "1'bZ", conn) | |
| conn = re.sub(r"DummyOut", "", conn) | |
| return ".{}({{{}}})".format(port, conn) | |
| code = re.sub( | |
| r"\.(?P<port>\w+)\s*\(\s*{(?P<conn>[^}]*)}\s*\)", sub_func_1, code | |
| ) | |
| # Prepend binary literal prefixes | |
| def sub_func(match): | |
| assert match is not None | |
| value = match.group("val") | |
| # Collapse separators "_" | |
| value = value.replace("_", "") | |
| # Add prefix, format the final string | |
| lnt = len(value) | |
| return "({}'b{})".format(lnt, value) | |
| code = re.sub(r"\(\s*(?P<val>[01_]+)\s*\)", sub_func, code) | |
| # Split ports | |
| if args.split_ports: | |
| code = split_verilog_ports(code) | |
| # Write the output verilog file | |
| fname = args.vlog_out | |
| if not fname: | |
| root, ext = os.path.splitext(args.vlog_in) | |
| fname = "{}.fixed{}".format(root, ext) | |
| with open(fname, "w") as fp: | |
| fp.write(code) | |
| # Process SDf file | |
| if args.sdf_in: | |
| # Read the input SDF file | |
| with open(args.sdf_in, "r") as fp: | |
| code = fp.read() | |
| # Split ports | |
| if args.split_ports: | |
| code = split_sdf_ports(code) | |
| # Write the output SDF file | |
| fname = args.sdf_out | |
| if not fname: | |
| root, ext = os.path.splitext(args.sdf_in) | |
| fname = "{}.fixed{}".format(root, ext) | |
| with open(fname, "w") as fp: | |
| fp.write(code) | |
| # ============================================================================= | |
| if __name__ == "__main__": | |
| main() |