| #!/usr/bin/env python3 | |
| """ | |
| This script provides a temporary solution for the problem of inout top level | |
| port representation of the BLIF format. | |
| The script loads the design from a JSON file generated by Yosys. Then it splits | |
| all inout ports along with their nets and connections to cell ports into two. | |
| Suffixes are automatically added to distinguish between the input and the | |
| output part. | |
| The JSON design format used by Yosys is documented there: | |
| - http://www.clifford.at/yosys/cmd_write_json.html | |
| - http://www.clifford.at/yosys/cmd_read_json.html | |
| For example in the given design (in verilog): | |
| module top( | |
| input A, | |
| output B, | |
| inout C | |
| ); | |
| IOBUF iobuf ( | |
| .I(A), | |
| .O(B), | |
| .IO_$inp(C), | |
| .IO_$out(C) | |
| ); | |
| endmodule | |
| the resulting design would be: | |
| module top( | |
| input A, | |
| output B, | |
| input C_$inp, | |
| output C_$out | |
| ); | |
| IOBUF iobuf ( | |
| .I(A), | |
| .O(B), | |
| .IO_$inp(C_$inp), | |
| .IO_$out(C_$out) | |
| ); | |
| endmodule | |
| """ | |
| import argparse | |
| import os | |
| import simplejson as json | |
| # ============================================================================= | |
| def find_top_module(design): | |
| """ | |
| Looks for the top-level module in the design. Returns its name. Throws | |
| an exception if none was found. | |
| """ | |
| for name, module in design["modules"].items(): | |
| attrs = module["attributes"] | |
| if "top" in attrs and int(attrs["top"]) == 1: | |
| return name | |
| raise RuntimeError("No top-level module found in the design!") | |
| def get_nets(bits): | |
| """ | |
| Returns a set of numbers corresponding to net indices effectively skipping | |
| connections to consts ("0", "1", "x"). | |
| >>> get_nets([0, 1, 2, "0", "1", "x", 3, 4, 5]) | |
| {0, 1, 2, 3, 4, 5} | |
| """ | |
| return set([n for n in bits if isinstance(n, int)]) | |
| def get_free_net(nets): | |
| """ | |
| Given a set of used net indices, returns a new, free index. | |
| >>> get_free_net({0, 1, 2, 4, 5, 6}) | |
| 3 | |
| >>> get_free_net({0, 1, 2, 3, 4, 5, 6}) | |
| 7 | |
| """ | |
| sorted_nets = sorted(list(nets)) | |
| # Find a gap in the sequence | |
| for i in range(len(nets) - 1): | |
| n0 = sorted_nets[i] | |
| n1 = sorted_nets[i + 1] | |
| if n1 != (n0 + 1): | |
| return n0 + 1 | |
| # No gap was found, return max + 1. | |
| return sorted_nets[-1] + 1 | |
| # ============================================================================= | |
| def find_and_split_inout_ports_and_nets(design, module_name): | |
| """ | |
| This function takes a given module from the design and splits all of its | |
| inout ports into pairs of inputs and outputs. Newly created ports are | |
| given suffixed. For example an inout port named "A" is going to be replaced | |
| by a pair consisting of "A_$inp" and "A_$out" ports. | |
| The function also looks for "netnames" that correspond to inout ports being | |
| split. These ones are removed and replaced with new ones related to newly | |
| added input and output ports. | |
| If any other "netname" mentions a net index connected to a former inout | |
| port, then the index is removed from the "netname" (replaced by "x"). If | |
| there are only "x" left in the "netname", then it is removed. | |
| The function returns port name map and net index map. The port map is a | |
| list of pairs (input name, input/output name). There are two entries per | |
| an inout. The net map is a dict indexed by indices of nets associated with | |
| inout ports. Each item contains a dict like {"i": int, "o": int} with | |
| indices of the inout net split products. | |
| """ | |
| # Get the module | |
| module = design["modules"][module_name] | |
| # Find indices of all used nets | |
| nets = set() | |
| for port in module["ports"].values(): | |
| nets |= get_nets(port["bits"]) | |
| for netname in module["netnames"].values(): | |
| nets |= get_nets(netname["bits"]) | |
| for cell in module["cells"].values(): | |
| for connection in cell["connections"].values(): | |
| nets |= get_nets(connection) | |
| # Get all inout ports | |
| inouts = { | |
| k: v | |
| for k, v in module["ports"].items() | |
| if v["direction"] == "inout" | |
| } | |
| # Split ports | |
| new_ports = {} | |
| net_map = {} | |
| port_map = [] | |
| for name, port in inouts.items(): | |
| # Remove the inout port from the module | |
| del module["ports"][name] | |
| nets -= get_nets(port["bits"]) | |
| # Make an input and output port | |
| for dir in ["input", "output"]: | |
| new_name = name + "_$" + dir[:3] | |
| new_port = {"direction": dir, "bits": []} | |
| print("Mapping port '{}' to '{}'".format(name, new_name)) | |
| for n in port["bits"]: | |
| if isinstance(n, int): | |
| mapped_n = get_free_net(nets) | |
| print("Mapping net {} to {} ({})".format(n, mapped_n, dir)) | |
| if n not in net_map: | |
| net_map[n] = {} | |
| net_map[n][dir[0]] = mapped_n | |
| nets.add(mapped_n) | |
| new_port["bits"].append(mapped_n) | |
| else: | |
| new_port["bits"].append(n) | |
| port_map.append(( | |
| name, | |
| new_name, | |
| )) | |
| new_ports[new_name] = new_port | |
| # Add inputs and outputs | |
| module["ports"].update(new_ports) | |
| # ..................................................... | |
| netnames = module["netnames"] | |
| # Remove netnames related to inout ports | |
| for name, net in list(module["netnames"].items()): | |
| if name in inouts: | |
| print("Removing netname '{}'".format(name)) | |
| del netnames[name] | |
| # Remove remapped nets | |
| for name, net in list(netnames.items()): | |
| # Remove "bits" used by the net that were re-mapped. | |
| if len(set(net["bits"]) & set(net_map.keys())): | |
| # Remove | |
| net["bits"] = ["x" if b in net_map else b for b in net["bits"]] | |
| # If there is nothing left, remove the whole net. | |
| if all([b == "x" for b in net["bits"]]): | |
| print("Removing netname '{}'".format(name)) | |
| del netnames[name] | |
| # Add netnames related to new input and output ports | |
| for name, port in new_ports.items(): | |
| netnames[name] = { | |
| "hide_name": 0, | |
| "bits": port["bits"], | |
| "attributes": {} | |
| } | |
| return port_map, net_map | |
| def remap_connections(design, module_name, net_map): | |
| """ | |
| This function remaps cell connections that mention inout ports being split. | |
| The function loops over all cells and their ports. If a port contains | |
| a connection to an inout net then the connection is remapped according to | |
| the given net_map. Only ports which names ends on "_$inp" and "_$out" are | |
| affected. | |
| """ | |
| module = design["modules"][module_name] | |
| cells = module["cells"] | |
| # Process cells | |
| for name, cell in cells.items(): | |
| if "port_directions" not in cell: | |
| continue | |
| port_directions = cell["port_directions"] | |
| connections = cell["connections"] | |
| # Process cell connections | |
| for port_name, port_nets in list(connections.items()): | |
| # Skip if no net of this connection were remapped | |
| if len(set(net_map.keys()) & set(port_nets)) == 0: | |
| continue | |
| # Remove connections to the output net from input port and vice | |
| # versa. | |
| for dir in ["input", "output"]: | |
| if port_directions[port_name] == dir and \ | |
| port_name.endswith("$" + dir[:3]): | |
| for i, n in enumerate(port_nets): | |
| if n in net_map: | |
| mapped_n = net_map[n][dir[0]] | |
| port_nets[i] = mapped_n | |
| print( | |
| "Mapping connection {}.{}[{}] from {} to {}". | |
| format(name, port_name, i, n, mapped_n) | |
| ) | |
| # ============================================================================= | |
| def main(): | |
| """ | |
| The main. | |
| """ | |
| # Parse args | |
| parser = argparse.ArgumentParser( | |
| description=__doc__, | |
| formatter_class=argparse.RawDescriptionHelpFormatter | |
| ) | |
| parser.add_argument("-i", required=True, type=str, help="Input JSON") | |
| parser.add_argument("-o", default=None, type=str, help="Output JSON") | |
| args = parser.parse_args() | |
| # Output name | |
| if args.o is None: | |
| args.o = os.path.splitext(args.i)[0] + "_out.json" | |
| # Read the design | |
| with open(args.i, "r") as fp: | |
| design = json.load(fp) | |
| # Find the top module | |
| top_name = find_top_module(design) | |
| # Find and split inouot ports | |
| port_map, net_map = find_and_split_inout_ports_and_nets(design, top_name) | |
| # Remap cell connections | |
| remap_connections(design, top_name, net_map) | |
| # Write the design | |
| with open(args.o, "w") as fp: | |
| json.dump(design, fp, sort_keys=True, indent=2) | |
| if __name__ == "__main__": | |
| main() |