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()