ecp_vlog: parse LPF files to give better names to ports
diff --git a/tools/ecp_vlog.py b/tools/ecp_vlog.py index d86f6aa..6e87259 100644 --- a/tools/ecp_vlog.py +++ b/tools/ecp_vlog.py
@@ -92,7 +92,7 @@ @property def mod_name(self) -> str: res = f"R{self.y}C{self.x}_{self.name}" - return self.mod_name_map.get(res, res) + return res @property def name(self) -> str: @@ -105,9 +105,11 @@ return self.pin.label def __str__(self) -> str: - res = self.mod_name - if self.pin is not None: - res += "$" + self.pin_name + mod_name = self.mod_name + pin_name = self.pin_name + res = self.mod_name_map.get(mod_name, mod_name) + if pin_name: + res += "$" + pin_name return res @@ -531,7 +533,7 @@ output_pins = natsorted(pin_map_pins - all_input_pins) input_pins = natsorted(pin_map_pins & all_input_pins) for pin in input_pins + output_pins: - strs.append(f" .{pin}({self.pin_map[pin]})") + strs.append(f" .{pin}( {self.pin_map[pin]} )") if strs: print(",\n".join(strs)) @@ -843,7 +845,7 @@ print() -def print_verilog(graph: ConnectionGraph, tiles_by_loc: TilesByLoc) -> None: +def print_verilog(graph: ConnectionGraph, tiles_by_loc: TilesByLoc, top_name: str) -> None: # Extract connected components and their roots & leaves sorted_components: List[Tuple[Component, List[Node], List[Node]]] = [] for component in graph.get_components(): @@ -905,24 +907,24 @@ for mod_type in set(type(mod_def) for mod_def in modules.values()): mod_type.print_definition() - print("module top(") + print(f"module {top_name}(") mod_globals_vars = [" input wire " + str(node) for node in mod_sources & mod_globals] mod_globals_vars += [" output wire " + str(node) for node in set(mod_sinks) & mod_globals] - print(",\n".join(natsorted(mod_globals_vars))) + print(" ,\n".join(natsorted(mod_globals_vars))) print(");") print() # sources are either connected to global inputs # or are outputs from some other node for node in natsorted(mod_sources - mod_globals, key=str): - print(f"wire {node};") + print(f"wire {node} ;") print() # sinks are either fed directly into a BEL, # in which case they are directly substituted, # or they are global outputs for node in natsorted(set(mod_sinks) & mod_globals, key=str): - print(f"assign {node} = {mod_sinks[node]};") + print(f"assign {node} = {mod_sinks[node]} ;") print() for modname in natsorted(modules): @@ -954,6 +956,33 @@ print("endmodule") +def parse_lpf(filename: str) -> Dict[str, str]: + import shlex + + lines = [] + with open(filename, "r") as f: + for row in f: + row = row.split("#", 1)[0].split("//", 1)[0].strip() + if row: + lines.append(row) + + sites: Dict[str, str] = {} + + commands = " ".join(lines).split(";") + for cmd in commands: + cmd = cmd.strip() + if not cmd: + continue + + words = shlex.split(cmd) + if words[0] == "LOCATE": + if len(words) != 5 or words[1] != "COMP" or words[3] != "SITE": + print("ignoring malformed LOCATE in LPF:", cmd, file=sys.stderr) + sites[words[4]] = words[2] + + return sites + + def main(argv: List[str]) -> None: import argparse import json @@ -961,9 +990,14 @@ parser = argparse.ArgumentParser("Convert a .bit file into a .v verilog file for simulation") parser.add_argument("bitfile", help="Input .bit file") - parser.add_argument("--package", help="Physical package (e.g. CABGA256), for renaming I/O-related wires") + parser.add_argument("--package", help="Physical package (e.g. CABGA256), for renaming I/O ports") + parser.add_argument("--lpf", help="Use LOCATE COMP commands from this LPF file to name I/O ports") + parser.add_argument("-n", "--module-name", help="Name for the top-level module (default: top)", default="top") args = parser.parse_args(argv) + if args.lpf and not args.package: + parser.error("Cannot use a LPF file without specifying the chip package") + pytrellis.load_database(database.get_db_root()) print("Loading bitstream...", file=sys.stderr) @@ -975,12 +1009,22 @@ with open(dbfn, "r") as f: iodb = json.load(f) + if args.lpf: + lpf_map = parse_lpf(args.lpf) + else: + lpf_map = {} + # Rename PIO and IOLOGIC BELs based on their connected pins, for readability mod_renames = {} for pin_name, pin_data in iodb["packages"][args.package].items(): - mod_renames["R{row}C{col}_PIO{pio}".format(**pin_data)] = f"{pin_name}_PIO" - mod_renames["R{row}C{col}_IOLOGIC{pio}".format(**pin_data)] = f"{pin_name}_IOLOGIC" + if pin_name in lpf_map: + # escape LPF name in case it has funny characters + pin_name = "\\" + lpf_map[pin_name] + # PIO and IOLOGIC do not share pin names except for IOLDO/IOLTO + mod_renames["R{row}C{col}_PIO{pio}".format(**pin_data)] = f"{pin_name}" + mod_renames["R{row}C{col}_IOLOGIC{pio}".format(**pin_data)] = f"{pin_name}" + # Note: the mod_name_map only affects str(node), not node.mod_name Node.mod_name_map = mod_renames print("Computing routing graph...", file=sys.stderr) @@ -991,7 +1035,7 @@ graph = gen_config_graph(chip, rgraph, tiles_by_loc) print("Generating Verilog...", file=sys.stderr) - print_verilog(graph, tiles_by_loc) + print_verilog(graph, tiles_by_loc, args.module_name) print("Done!", file=sys.stderr)