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)