diff --git a/fuzzers/076-ps7/Makefile b/fuzzers/076-ps7/Makefile
new file mode 100644
index 00000000..f58aae52
--- /dev/null
+++ b/fuzzers/076-ps7/Makefile
@@ -0,0 +1,30 @@
+N := 1
+SPECIMENS := $(addprefix build/specimen_,$(shell seq -f '%03.0f' $(N)))
+SPECIMENS_OK := $(addsuffix /OK,$(SPECIMENS))
+
+database: $(SPECIMENS_OK)
+
+pushdb: build/ps7_ports.json
+ mkdir -p ${XRAY_FAMILY_DIR}
+ cp build/ps7*.json ${XRAY_FAMILY_DIR}/
+
+$(SPECIMENS_OK):
+ bash generate.sh $(subst /OK,,$@)
+ touch $@
+
+build/ps7_pins.csv: $(SPECIMENS_OK)
+ cp build/specimen_001/ps7_pins.csv build/
+
+build/ps7_ports.json: build/ps7_pins.csv
+ python3 make_ports.py $< $@
+
+run:
+ $(MAKE) clean
+ $(MAKE) database
+ $(MAKE) pushdb
+ touch run.ok
+
+clean:
+ rm -rf build run.ok
+
+.PHONY: database pushdb run clean
diff --git a/fuzzers/076-ps7/README.md b/fuzzers/076-ps7/README.md
new file mode 100644
index 00000000..571b84dc
--- /dev/null
+++ b/fuzzers/076-ps7/README.md
@@ -0,0 +1,3 @@
+# PS7 verilog cell definition extractor
+
+Extracts all pins of the PS7 bel from Vivado, groups them into ports, writes them to a JSON file.
diff --git a/fuzzers/076-ps7/generate.sh b/fuzzers/076-ps7/generate.sh
new file mode 100644
index 00000000..e6b0e2c6
--- /dev/null
+++ b/fuzzers/076-ps7/generate.sh
@@ -0,0 +1,6 @@
+#!/bin/bash -x
+
+source ${XRAY_GENHEADER}
+
+${XRAY_VIVADO} -mode batch -source $FUZDIR/generate.tcl
+
diff --git a/minitests/ps7/xtra/dump_ps7.tcl b/fuzzers/076-ps7/generate.tcl
similarity index 84%
rename from minitests/ps7/xtra/dump_ps7.tcl
rename to fuzzers/076-ps7/generate.tcl
index 10586c8c..f8a55556 100644
--- a/minitests/ps7/xtra/dump_ps7.tcl
+++ b/fuzzers/076-ps7/generate.tcl
@@ -1,8 +1,8 @@
-create_project -force -in_memory -name design -part xc7z020clg400-1
+create_project -force -name design -part $::env(XRAY_PART)
set_property design_mode PinPlanning [current_fileset]
open_io_design -name io_1
-set fp [open ps7.csv w]
+set fp [open ps7_pins.csv w]
puts $fp "name,is_input,is_output,is_bidir"
set pins [get_bel_pins -of_objects [get_bels -of_objects [get_sites PS7* -of_objects [get_tiles PSS*]]]]
diff --git a/fuzzers/076-ps7/make_ports.py b/fuzzers/076-ps7/make_ports.py
new file mode 100644
index 00000000..1f4f169c
--- /dev/null
+++ b/fuzzers/076-ps7/make_ports.py
@@ -0,0 +1,131 @@
+#!/usr/bin/env python3
+"""
+This script loads the PS7 pin dump from Vivado and groups pins into ports. Also
+assigns each port a class that indicates its function. The classes are:
+
+- "normal": A port that connects to the PL (FPGA)
+- "test": A port used for testing, not accessible from the PL.
+- "debug": A debug port, not accessible.
+- "mio": The "mio" ports go directly to die pads, not relevant for routing.
+
+Ports are then written to a JSON file.
+"""
+import argparse
+import csv
+import json
+import re
+
+from collections import defaultdict
+
+# =============================================================================
+
+
+def main():
+
+ BUS_REGEX = re.compile("(.*[A-Z_])([0-9]+)$")
+
+ # Parse arguments
+ parser = argparse.ArgumentParser(
+ description=__doc__,
+ formatter_class=argparse.RawDescriptionHelpFormatter)
+
+ parser.add_argument("csv", type=str, help="PS7 pin dump file")
+ parser.add_argument(
+ "json",
+ type=str,
+ help="Output JSON file with PS7 pins grouped into ports")
+
+ args = parser.parse_args()
+
+ # Load pin dump
+ with open(args.csv, "r") as fp:
+ pin_dump = list(csv.DictReader(fp))
+
+ # Group pins into ports
+ ports = defaultdict(lambda :{
+ "direction": None,
+ "min": None,
+ "max": None,
+ "width": 0
+ })
+
+ for pin in list(pin_dump):
+
+ # Get port name and signal index
+ match = BUS_REGEX.match(pin["name"])
+ if match:
+ name = match.group(1)
+ idx = int(match.group(2))
+ else:
+ name = pin["name"]
+ idx = 0
+
+ # Get direction
+ is_input = int(pin["is_input"])
+ is_output = int(pin["is_output"])
+ is_bidir = int(pin["is_bidir"])
+
+ if is_input and not is_output and not is_bidir:
+ direction = "input"
+ elif not is_input and is_output and not is_bidir:
+ direction = "output"
+ elif not is_input and not is_output and is_bidir:
+ direction = "inout"
+ else:
+ assert False, pin
+
+ # Add to port
+ port = ports[name]
+
+ if port["direction"] is None:
+ port["direction"] = direction
+ else:
+ assert port["direction"] == direction
+
+ if port["min"] is None:
+ port["min"] = idx
+ else:
+ port["min"] = min(port["min"], idx)
+
+ if port["max"] is None:
+ port["max"] = idx
+ else:
+ port["max"] = max(port["max"], idx)
+
+ port["width"] = port["max"] - port["min"] + 1
+
+ # Sort ports by their purpose
+ for name, port in ports.items():
+
+ # A test pin (unconnected)
+ if name.startswith("TEST"):
+ cls = "test"
+
+ # A debug pin (unconnected)
+ elif name.startswith("DEBUG"):
+ cls = "debug"
+
+ # A MIO/DDR pin.
+ elif name.startswith("MIO") or name.startswith("DDR") and \
+ name != "DDRARB":
+ cls = "mio"
+
+ # PS7 clock/reset
+ elif name in ["PSCLK", "PSPORB", "PSSRSTB"]:
+ cls = "mio"
+
+ # "Normal" pin
+ else:
+ cls = "normal"
+
+ port["class"] = cls
+
+ # Write pin ports to a JSON file
+ with open(args.json, "w") as fp:
+ json.dump(ports, fp, indent=1, sort_keys=True)
+
+
+# =============================================================================
+
+if __name__ == "__main__":
+ main()
diff --git a/fuzzers/Makefile b/fuzzers/Makefile
index b58659b9..cedde639 100644
--- a/fuzzers/Makefile
+++ b/fuzzers/Makefile
@@ -120,6 +120,9 @@ $(eval $(call fuzzer,072-ordered_wires,))
$(eval $(call fuzzer,073-get_counts,))
$(eval $(call fuzzer,074-dump_all,005-tilegrid 072-ordered_wires))
$(eval $(call fuzzer,075-pins,))
+ifeq ($(XRAY_DATABASE),zynq7)
+$(eval $(call fuzzer,076-ps7,))
+endif
endif
endif
$(eval $(call fuzzer,100-dsp-mskpat,005-tilegrid))
diff --git a/minitests/ps7/xtra/Makefile b/minitests/ps7/xtra/Makefile
deleted file mode 100644
index b9d99d3a..00000000
--- a/minitests/ps7/xtra/Makefile
+++ /dev/null
@@ -1,20 +0,0 @@
-.PHONY: all clean
-
-all: xtra.ok
-
-clean:
- rm -rf xtra.ok
- rm -rf ps7_sim.v
- rm -rf ps7_map.v
- rm -rf ps7_pins.json
- rm -rf ps7.csv
- rm -rf *.xml
- rm -rf *.log
- rm -rf .Xil
-
-ps7.csv: dump_ps7.tcl
- $(XRAY_VIVADO) -mode batch -source dump_ps7.tcl -nojournal -log $(basename $@).log
-
-xtra.ok: ps7.csv make_cell.py
- python3 make_cell.py $<
- touch xtra.ok
diff --git a/minitests/ps7/xtra/README.md b/minitests/ps7/xtra/README.md
deleted file mode 100644
index 285bc14b..00000000
--- a/minitests/ps7/xtra/README.md
+++ /dev/null
@@ -1,3 +0,0 @@
-# PS7 verilog cell definition extractor
-
-Extracts all pins of the PS7 bel from Vivado, groups them into buses, removes those that are not connected (TEST*, DEBUG*) and creates the VPR counterpart for it. It also generates model XML, pb_type XML and techmap which handles unconnected ports correctly.
diff --git a/minitests/ps7/xtra/make_cell.py b/minitests/ps7/xtra/make_cell.py
deleted file mode 100644
index 307f2ef4..00000000
--- a/minitests/ps7/xtra/make_cell.py
+++ /dev/null
@@ -1,319 +0,0 @@
-#!/usr/bin/env python3
-import argparse
-import csv
-import json
-import re
-
-from collections import defaultdict
-
-# =============================================================================
-
-
-def main():
-
- BUS_REGEX = re.compile("(.*[A-Z_])([0-9]+)$")
-
- # Parse arguments
- parser = argparse.ArgumentParser()
- parser.add_argument("csv", type=str, help="PS7 pin dump file")
- args = parser.parse_args()
-
- # Load pin dump
- with open(args.csv, "r") as fp:
- pin_dump = list(csv.DictReader(fp))
-
- # Group pins into buses
- buses = defaultdict(lambda :{
- "direction": None,
- "min": None,
- "max": None,
- "width": 0
- })
-
- for pin in list(pin_dump):
-
- # Get bus name and signal index
- match = BUS_REGEX.match(pin["name"])
- if match:
- name = match.group(1)
- idx = int(match.group(2))
- else:
- name = pin["name"]
- idx = 0
-
- # Get direction
- is_input = int(pin["is_input"])
- is_output = int(pin["is_output"])
- is_bidir = int(pin["is_bidir"])
-
- if is_input and not is_output and not is_bidir:
- direction = "input"
- elif not is_input and is_output and not is_bidir:
- direction = "output"
- elif not is_input and not is_output and is_bidir:
- direction = "inout"
- else:
- assert False, pin
-
- # Add to bus
- bus = buses[name]
-
- if bus["direction"] is None:
- bus["direction"] = direction
- else:
- assert bus["direction"] == direction
-
- if bus["min"] is None:
- bus["min"] = idx
- else:
- bus["min"] = min(bus["min"], idx)
-
- if bus["max"] is None:
- bus["max"] = idx
- else:
- bus["max"] = max(bus["max"], idx)
-
- bus["width"] = bus["max"] - bus["min"] + 1
-
- # Sort buses by their purpose
- for name, bus in buses.items():
-
- # A test pin (unconnected)
- if name.startswith("TEST"):
- cls = "test"
-
- # A debug pin (unconnected)
- elif name.startswith("DEBUG"):
- cls = "debug"
-
- # A MIO/DDR pin.
- elif name.startswith("MIO") or name.startswith("DDR") and \
- name != "DDRARB":
- cls = "mio"
-
- # PS7 clock/reset
- elif name in ["PSCLK", "PSPORB", "PSSRSTB"]:
- cls = "mio"
-
- # "Normal" pin
- else:
- cls = "normal"
-
- bus["class"] = cls
-
- # .....................................................
- # Generate JSON with PS7 pins grouped by direction
-
- ps7_pins = {"input": [], "output": [], "inout": []}
- for name in sorted(buses.keys()):
- bus = buses[name]
-
- # Skip not relevant pins
- if bus["class"] not in ["normal", "mio"]:
- continue
-
- if bus["width"] > 1:
- for i in range(bus["min"], bus["max"]+1):
- pin_name = "{}{}".format(name, i)
- ps7_pins[bus["direction"]].append(pin_name)
- else:
- ps7_pins[bus["direction"]].append(name)
-
- with open("ps7_pins.json", "w") as fp:
- json.dump(ps7_pins, fp, sort_keys=True, indent=2)
-
- # .....................................................
- # Generate XML model
- pb_name = "PS7"
- blif_model = "PS7_VPR"
-
- model_xml = """
-
-""".format(blif_model)
-
- # Inputs
- model_xml += """
-"""
- for name in sorted(buses.keys()):
- bus = buses[name]
-
- # Skip not relevant pins
- if bus["class"] not in ["normal"]:
- continue
-
- if bus["direction"] != "input":
- continue
-
- model_xml += " \n".format(name)
-
- # Outputs
- model_xml += """
-
-"""
- for name in sorted(buses.keys()):
- bus = buses[name]
-
- # Skip not relevant pins
- if bus["class"] not in ["normal"]:
- continue
-
- if bus["direction"] != "output":
- continue
-
- model_xml += " \n".format(name)
-
- model_xml += """
-"""
-
- model_xml += """
-"""
-
- with open("ps7.model.xml", "w") as fp:
- fp.write(model_xml)
-
- # .....................................................
- # Generate XML pb_type
- pb_xml = """
-""".format(pb_name, blif_model)
-
- for name in sorted(buses.keys()):
- bus = buses[name]
-
- # Skip not relevant pins
- if bus["class"] not in ["normal"]:
- continue
-
- pb_xml += " <{} name=\"{}\" num_pins=\"{}\"/>\n".format(
- bus["direction"].ljust(6), name, bus["width"])
-
- pb_xml += """
-"""
-
- with open("ps7.pb_type.xml", "w") as fp:
- fp.write(pb_xml)
-
- # .....................................................
- # Prepare Verilog module definition for the PS7_VPR
- port_defs = []
- for name in sorted(buses.keys()):
- bus = buses[name]
-
- # Skip not relevant pins (eg. MIO and DDR)
- if bus["class"] not in ["normal"]:
- continue
-
- # Generate port definition
- if bus["width"] > 1:
- port_str = " {} [{:>2d}:{:>2d}] {}".format(
- bus["direction"].ljust(6), bus["max"], bus["min"], name)
- else:
- port_str = " {} {}".format(
- bus["direction"].ljust(6), name)
-
- port_defs.append(port_str)
-
- verilog = """(* blackbox *)
-module PS7_VPR (
-{}
-);
-
-endmodule
-""".format(",\n".join(port_defs))
-
- with open("ps7_sim.v", "w") as fp:
- fp.write(verilog)
-
- # .....................................................
- # Prepare techmap that maps PS7 to PS7_VPR and handles
- # unconnected inputs (ties them to GND)
- port_defs = []
- port_conns = []
- param_defs = []
- wire_defs = []
- for name in sorted(buses.keys()):
- bus = buses[name]
-
- # Skip not relevant pins
- if bus["class"] not in ["normal", "mio"]:
- continue
-
- # Generate port definition
- if bus["width"] > 1:
- port_str = " {} [{:>2d}:{:>2d}] {}".format(
- bus["direction"].ljust(6), bus["max"], bus["min"], name)
- else:
- port_str = " {} {}".format(
- bus["direction"].ljust(6), name)
-
- port_defs.append(port_str)
-
- # MIO and DDR pins are not mapped as they are dummy
- if bus["class"] == "mio":
- continue
-
- # This is an input port, needs to be tied to GND if unconnected
- if bus["direction"] == "input":
-
- # Techmap parameter definition
- param_defs.append(
- " parameter _TECHMAP_CONSTMSK_{}_ = 0;".format(name.upper()))
- param_defs.append(
- " parameter _TECHMAP_CONSTVAL_{}_ = 0;".format(name.upper()))
-
- # Wire definition using generate statement. Necessary for detection
- # of unconnected ports.
- wire_defs.append(
- """
- generate if((_TECHMAP_CONSTMSK_{name_upr}_ == {N}'d0) && (_TECHMAP_CONSTVAL_{name_upr}_ == {N}'d0))
- wire [{M}:0] {name_lwr} = {N}'d0;
- else
- wire [{M}:0] {name_lwr} = {name};
- endgenerate""".format(
- name=name,
- name_upr=name.upper(),
- name_lwr=name.lower(),
- N=bus["width"],
- M=bus["width"] - 1))
-
- # Connection to the "generated" wire.
- port_conns.append(
- " .{name:<25}({name_lwr})".format(
- name=name, name_lwr=name.lower()))
-
- # An output port
- else:
-
- # Direct connection
- port_conns.append(" .{name:<25}({name})".format(name=name))
-
- # Format the final verilog.
- verilog = """module PS7 (
-{port_defs}
-);
-
- // Techmap specific parameters.
-{param_defs}
-
- // Detect all unconnected inputs and tie them to 0.
-{wire_defs}
-
- // Replacement cell.
- PS7_VPR _TECHMAP_REPLACE_ (
-{port_conns}
- );
-
-endmodule
-""".format(
- port_defs=",\n".join(port_defs),
- param_defs="\n".join(param_defs),
- wire_defs="\n".join(wire_defs),
- port_conns=",\n".join(port_conns))
-
- with open("ps7_map.v", "w") as fp:
- fp.write(verilog)
-
-
-# =============================================================================
-
-if __name__ == "__main__":
- main()