diff --git a/minitests/iostandard/dump_iobs.tcl b/minitests/iostandard/dump_iobs.tcl new file mode 100644 index 00000000..05767626 --- /dev/null +++ b/minitests/iostandard/dump_iobs.tcl @@ -0,0 +1,31 @@ +proc dump_iobs {file_name} { + + set fp [open $file_name w] + puts $fp "tile,site_name,site_type,clock_region,bank,pkg_pin,is_bonded,is_clock,is_global_clock,is_vref,pin_func" + + foreach tile [get_tiles *IOB33*] { + foreach site [get_sites -of_objects $tile] { + set site_type [get_property SITE_TYPE $site] + set clock_region [get_property CLOCK_REGION $site] + set is_bonded [get_property IS_BONDED $site] + + set pin [get_package_pins -of_objects $site] + set bank [get_property BANK $pin] + set pkg_pin [get_property NAME $pin] + set is_clock [get_property IS_CLK_CAPABLE $pin] + set is_global_clock [get_property IS_GLOBAL_CLK $pin] + set is_vref [get_property IS_VREF $pin] + set pin_func [get_property PIN_FUNC $pin] + + puts $fp "$tile,$site,$site_type,$clock_region,$bank,$pkg_pin,$is_bonded,$is_clock,$is_global_clock,$is_vref,$pin_func" + } + } + + close $fp +} + +create_project -force -in_memory -name dump_iobs -part $::env(VIVADO_PART) +set_property design_mode PinPlanning [current_fileset] +open_io_design -name io_1 + +dump_iobs "iobs-$::env(VIVADO_PART).csv" diff --git a/minitests/iostandard/features/Makefile b/minitests/iostandard/features/Makefile new file mode 100644 index 00000000..35e393b1 --- /dev/null +++ b/minitests/iostandard/features/Makefile @@ -0,0 +1,76 @@ +PART?=xc7a50tfgg484-1 +VIVADO_PART?=$(PART) + +BIT2FASM_ARGS= --part "$(XRAY_DIR)/database/$(XRAY_DATABASE)/$(PART)" --verbose + +DESIGN_FILES=$(wildcard design*.v) +CSV_FILES=$(subst .v,.csv,$(DESIGN_FILES)) +FASM_FILES=$(subst .v,.fasm,$(DESIGN_FILES)) + +.PHONY: all clean designs analysis +.PRECIOUS: %.bit %.fasm %.csv + +all: analysis + +clean: + @rm -rf design_*.v + @rm -rf design_*.tcl + @rm -rf design_*.json + @rm -rf design_*.csv + @rm -rf design_*.fasm + @rm -rf design_*.bit + @rm -rf design_*.dcp + @rm -rf designs.ok + @rm -rf *.log + @rm -rf *.jou + @rm -rf *.xml + @for f in build-*; \ + do \ + rm -rf $$f; \ + done + @rm -rf .Xil + @rm -rf snippets + @rm -rf features.csv + @rm -rf results.json + @rm -rf unknown_bits.jl + @rm -rf iobs-$(VIVADO_PART).csv + +iobs-$(VIVADO_PART).csv: ../dump_iobs.tcl + env VIVADO_PART=$(VIVADO_PART) $(XRAY_VIVADO) -mode batch -source ../dump_iobs.tcl -nojournal -log dump_iobs.log + +designs.ok: iobs-$(VIVADO_PART).csv generate.py + env VIVADO_PART=$(VIVADO_PART) python3 ./generate.py + touch designs.ok + +designs: designs.ok + +%.bit: %.v designs.ok ../syn+par.tcl + mkdir -p build-$(basename $@) + cd build-$(basename $@) && env PROJECT_NAME=$(basename $@) VIVADO_PART=${VIVADO_PART} $(XRAY_VIVADO) -mode batch -source ../../syn+par.tcl -nojournal -log ../$@.log + rm -rf *.backup.log + +%.fasm: %.bit + $(XRAY_BIT2FASM) $(BIT2FASM_ARGS) $< > $@ + @sort -u -o $@ $@ + +%.csv: %.fasm + python3 ./analyze.py --fasm $< --design $(basename $@).json -o $@ + +analysis: features.csv unknown_bits.jl + +features.csv: $(CSV_FILES) designs.ok + @head -n 1 $(word 1,$(CSV_FILES)) >features.csv + @rm -rf features.csv.tmp + @for f in $(CSV_FILES); \ + do \ + tail -n +2 $$f >>features.csv.tmp; \ + done + @sort features.csv.tmp >>features.csv + @rm -rf features.csv.tmp + +unknown_bits.jl: $(FASM_FILES) designs.ok + @cat $(FASM_FILES) | grep unknown_bit | cut -d: -f 2 | sort -u >unknown_bits.jl + +snippets: features.csv + mkdir -p snippets + cd snippets && python3 ../snippets.py ../$< diff --git a/minitests/iostandard/features/README.md b/minitests/iostandard/features/README.md new file mode 100644 index 00000000..71b30bce --- /dev/null +++ b/minitests/iostandard/features/README.md @@ -0,0 +1,41 @@ +# IOSTANDARD feature correlation minitest + +This test checks which fasm features present in the db are set for given IO settings (IOSTANDARD + DRIVE + SLEW) and for which IOB type (IBUF, OBUF, IOBUF, IBUFDS, OBUFDS, IOBUFDS). It also checks what features are set on unused IOBs of a given bank in which active IOBs are instantiated. To avoid conflicts, a single iosettings combination is used within an IO bank. + +## Running the minitest + +1. Dump IOBs and generate verilog designs + +` +make designs +` + +2. (can skip this and go to 3) Synthesize designs, decode bitstreams to fasm files, correlate with database features + +` +make analysis +` + +3. Generate verilog and XML snippets to be used in cell definition, techmap and architecture definition +` +make snippets +` + +## How it works + +At first IOB information is dumped from Vivado. Relevant IOB attributes are: IO bank, is it a Vref input, is the IOB bonded. + +Next a set of design is generated. Each design contains two IBUFs, two OBUFs and one IOBUF (if IOSTANDARD allows it) per bank. + +These designs are synthesized and their bitstreams are disassembled to fasms. + +Each fasm is compared agains the prjxray db to see what features are activated for given IO settings. Results of that checks are stored in CSV files which are then concatenated to one named "features.csv". Unknown bits are reported and stored in the "unknown_bits.jl" file. + +The "features.csv" consists of one row per IO settings and one column per fasm feature. Each "cell" contains a string identifying for which types of IOB a given feature was active: + - "I" input + - "O" output + - "T" inout + - "U" active in unused IOBs of the same bank + +Finally during code snippet generation the "feature.csv" file is read back by a python script and code snippets for cell definition, cell techmap and XML architecture definition are generated. The goal is to automatically generate mappings between parameters of Vivado's IOB cells to VPR IOB cells. + diff --git a/minitests/iostandard/features/analyze.py b/minitests/iostandard/features/analyze.py new file mode 100644 index 00000000..c18c0464 --- /dev/null +++ b/minitests/iostandard/features/analyze.py @@ -0,0 +1,184 @@ +""" +This script performs the analysis of disassembled bitstream and design information. +It correlates presence/absence of particular fasm features in the bitstream +with presence/absence of a particular IOB type. It also checks for features +that are set for unused IOBs that are in the same bank as the used ones. + +The list of available fasm features is read from the prjxray database. The +analysis result is written to a CSV file. +""" +import os +import argparse +import json +import random + +from collections import defaultdict + +from prjxray import util +import fasm + +# ============================================================================= + + +def load_iob_segbits(): + """ + Loads IOB segbits from the database to get fasm feature list. + This function assumes: + - LIOB33/RIOB33 have the same features. + - IOB_Y0 and IOB_Y1 have the same features (at least those of interest). + """ + features = [] + + fname = os.path.join(util.get_db_root(), "segbits_liob33.db") + with open(fname, "r") as fp: + for line in fp: + + line = line.strip() + if len(line) == 0: + continue + + # Parse the line + fields = line.split(maxsplit=1) + feature = fields[0] + bits = fields[1] + + # Parse the feature + parts = feature.split(".", maxsplit=3) + if len(parts) >= 3 and parts[1] == "IOB_Y0": + features.append(".".join(parts[2:])) + + return features + + +def correlate_features(features, tile, site, set_features): + """ + Correlate each feature with the fasm disassembly for given tile/site. + + Given a set of all possible fasm features (for an IOB) in the first + argument, checks whether they are set or cleared for the given tile+site in + a design. The parameter set_features contains fasm dissassembly of the + design. + + Returns a list of tuples with feature names and whether they are set or not. + """ + result = [] + + for feature in features: + full_feature = "{}.{}.{}".format(tile, site, feature) + if full_feature in set_features: + result.append(( + feature, + True, + )) + else: + result.append(( + feature, + False, + )) + + return result + + +def run(): + """ + Main. + """ + + # Parse arguments + parser = argparse.ArgumentParser() + parser.add_argument( + "--design", type=str, required=True, help="Design JSON file") + parser.add_argument( + "--fasm", type=str, required=True, help="Decoded fasm file") + parser.add_argument( + "-o", type=str, default="results.csv", help="Output CSV file") + parser.add_argument("-j", type=str, default=None, help="Output JSON file") + + args = parser.parse_args() + + # Load IOB features + features = load_iob_segbits() + + # Load the design data + with open(args.design, "r") as fp: + design = json.load(fp) + + # Load disassembled fasm + fasm_tuples = fasm.parse_fasm_filename(args.fasm) + set_features = fasm.fasm_tuple_to_string(fasm_tuples).split("\n") + + # Correlate features for given IOB types + results = [] + for region in design: + result = dict(region["iosettings"]) + + for l in ["input", "output", "inout", "unused_sites"]: + + # TODO: Check if this is true eg. for all unused sites, not just + # one random site. + tile, site = random.choice(region[l]).split(".") + matches = correlate_features(features, tile, site, set_features) + + result[l] = matches + + results.append(result) + + # Save results + if args.j: + with open(args.j, "w") as fp: + json.dump(results, fp, indent=2, sort_keys=True) + + # Save results to CSV + with open(args.o, "w") as fp: + csv_data = defaultdict(lambda: {}) + + # Collect data + for result in results: + iostandard = result["iostandard"] + drive = result["drive"] + slew = result["slew"] + + if drive is None: + drive = "_FIXED" + + iosettings = "{}.I{}.{}".format(iostandard, drive, slew) + + is_diff = "DIFF" in iostandard + + for feature in sorted(features): + I = [f[1] for f in result["input"] if f[0] == feature and f[1]] + O = [ + f[1] for f in result["output"] if f[0] == feature and f[1] + ] + T = [f[1] for f in result["inout"] if f[0] == feature and f[1]] + U = [ + f[1] + for f in result["unused_sites"] + if f[0] == feature and f[1] + ] + + s = "".join( + [ + "I" if len(I) > 0 else "", + "O" if len(O) > 0 else "", + "T" if len(T) > 0 else "", + "U" if len(U) > 0 else "", + ]) + + csv_data[iosettings][feature] = s + + # Write header + line = ["iosettings"] + sorted(features) + fp.write(",".join(line) + "\n") + + # Write data + for iosettings in sorted(csv_data.keys()): + data = csv_data[iosettings] + line = [iosettings + ] + [data[feature] for feature in sorted(features)] + + fp.write(",".join(line) + "\n") + + +if __name__ == "__main__": + run() diff --git a/minitests/iostandard/features/generate.py b/minitests/iostandard/features/generate.py new file mode 100644 index 00000000..33d87101 --- /dev/null +++ b/minitests/iostandard/features/generate.py @@ -0,0 +1,483 @@ +import os +import random +import json +import csv +from collections import defaultdict + +try: + random.seed(int(os.getenv("SEED"), 16)) +except TypeError: + pass + +# ============================================================================= + + +def load_iob_sites(file_name): + """ + Loads IOB site dump from the given CSV file. + """ + + # Load the data + with open(file_name, "r") as fp: + data = [row for row in csv.DictReader(fp)] + + # Index IOB site data by clock regions + iob_sites = defaultdict(lambda: []) + for site_data in data: + iob_sites[site_data["clock_region"]].append(site_data) + + print(data) + return iob_sites + + +# ============================================================================= + +IOBUF_NOT_ALLOWED = [ + 'HSTL_I', + 'HSTL_I_18', + 'SSTL18_I', +] + +DIFF_MAP = { + 'SSTL135': 'DIFF_SSTL135', +} + + +def gen_iosettings(): + """ + A generator function which yields all possible IO settings combintions. + """ + + IOSTANDARDS = ( + 'LVCMOS12', + 'LVCMOS15', + 'LVCMOS18', + 'LVCMOS25', + 'LVCMOS33', + 'LVTTL', + 'SSTL135', + + # Those are available but not currently fuzzed. + # 'SSTL135_R', + # 'SSTL15', + # 'SSTL15_R', + # 'SSTL18_I', + # 'SSTL18_II', + # 'HSTL_I', + # 'HSTL_I_18', + # 'HSTL_II', + # 'HSTL_II_18', + # 'HSUL_12', + # 'MOBILE_DDR', + ) + + DRIVES = defaultdict(lambda: [None]) + DRIVES.update( + { + "LVTTL": [4, 8, 12, 16, 24], + "LVCMOS12": [4, 8, 12], + "LVCMOS15": [4, 8, 12, 16], + "LVCMOS18": [4, 8, 12, 16, 24], + "LVCMOS25": [4, 8, 12, 16], + "LVCMOS33": [4, 8, 12, 16], + }) + + SLEWS = ("SLOW", "FAST") + + for iostandard in IOSTANDARDS: + + # Single ended + for drive in DRIVES[iostandard]: + for slew in SLEWS: + yield {"iostandard": iostandard, "drive": drive, "slew": slew} + + # Differential + if iostandard in DIFF_MAP: + for drive in DRIVES[iostandard]: + for slew in SLEWS: + yield { + "iostandard": DIFF_MAP[iostandard], + "drive": drive, + "slew": slew + } + + +# ============================================================================= + + +def run(): + """ + Main. + """ + + # Load IOB data + iob_sites = load_iob_sites("iobs-{}.csv".format(os.getenv("VIVADO_PART"))) + + # Generate IOB site to package pin map and *M site to *S site map. + site_to_pkg_pin = {} + master_to_slave = {} + + for region, sites in iob_sites.items(): + tiles = defaultdict(lambda: {}) + + for site in sites: + site_to_pkg_pin[site["site_name"]] = site["pkg_pin"] + if site["site_type"] == "IOB33M": + tiles[site["tile"]]["M"] = site + if site["site_type"] == "IOB33S": + tiles[site["tile"]]["S"] = site + + for sites in tiles.values(): + master_to_slave[sites["M"]["site_name"]] = sites["S"]["site_name"] + + # Generate designs + iosettings_gen = gen_iosettings() + design_index = 0 + while True: + + num_inp = 0 + num_out = 0 + num_ino = 0 + + # Generate clock regions + region_data = [] + for region in sorted(list(iob_sites.keys())): + + # Get IO settings + try: + iosettings = next(iosettings_gen) + except StopIteration: + break + + # Get sites + sites = [ + ( + site["site_name"], + site["site_type"], + ) + for site in iob_sites[region] + if site["is_bonded"] and not int(site["is_vref"]) and "SING" + not in site["tile"] and not "PUDC_B" in site["pin_func"] + ] + if not len(sites): + continue + + # Differential / single ended + if "DIFF" in iosettings["iostandard"]: + + # Select 5 random sites (IBUFDS, IBUFDS, OBUFDS, OBUFDS, IOBUFDS) + site_names = [s[0] for s in sites if s[1] == "IOB33M"] + used_sites = random.sample(site_names, 5) + unused_sites = list(set(site_names) - set(used_sites)) + + num_inp += 4 + num_out += 4 + num_ino += 2 + + else: + # Select 5 random sites (IBUF, IBUF, OBUF, OBUF, IOBUF) + site_names = [s[0] for s in sites] + used_sites = random.sample(site_names, 5) + unused_sites = list(set(site_names) - set(used_sites)) + + num_inp += 2 + num_out += 2 + num_ino += 1 + + # Store data + region_data.append( + { + "region": region, + "iosettings": iosettings, + "unused_sites": unused_sites, + "input": used_sites[0:2], + "output": used_sites[2:4], + "inout": used_sites[4:5], + }) + print(region, iosettings) + + # No more + if len(region_data) == 0: + break + + print("----") + + # Generate the design + verilog = """ +module top ( + input wire [{num_inp}:0] inp, + inout wire [{num_ino}:0] ino, + output wire [{num_out}:0] out +); +""".format(num_inp=num_inp - 1, num_ino=num_ino - 1, num_out=num_out - 1) + + tcl = "" + + inp_idx = 0 + out_idx = 0 + ino_idx = 0 + + for i, data in enumerate(region_data): + + is_diff = "DIFF" in data["iosettings"]["iostandard"] + use_ino = data["iosettings"]["iostandard"] not in IOBUF_NOT_ALLOWED + + iostandard = data["iosettings"]["iostandard"] + drive = data["iosettings"]["drive"] + slew = data["iosettings"]["slew"] + + ibuf_param_str = ".IOSTANDARD(\"{}\")".format(iostandard) + obuf_param_str = str(ibuf_param_str) + + if drive is not None: + obuf_param_str += ", .DRIVE({})".format(drive) + if slew is not None: + obuf_param_str += ", .SLEW(\"{}\")".format(slew) + + keys = { + "region": data["region"], + "ibuf_0_loc": data["input"][0], + "ibuf_1_loc": data["input"][1], + "obuf_0_loc": data["output"][0], + "obuf_1_loc": data["output"][1], + "iobuf_loc": data["inout"][0], + "inp_0_p": inp_idx, + "inp_0_n": inp_idx + 2, + "inp_1_p": inp_idx + 1, + "inp_1_n": inp_idx + 3, + "out_0_p": out_idx, + "out_0_n": out_idx + 2, + "out_1_p": out_idx + 1, + "out_1_n": out_idx + 3, + "ino_p": ino_idx, + "ino_n": ino_idx + 1, + "ibuf_param_str": ibuf_param_str, + "obuf_param_str": obuf_param_str, + } + + if is_diff: + inp_idx += 4 + out_idx += 4 + ino_idx += 2 + else: + inp_idx += 2 + out_idx += 2 + ino_idx += 1 + + # Single ended + if not is_diff: + + tcl += "set_property PACKAGE_PIN {} [get_ports inp[{}]]\n".format( + site_to_pkg_pin[keys["ibuf_0_loc"]], keys["inp_0_p"]) + tcl += "set_property PACKAGE_PIN {} [get_ports inp[{}]]\n".format( + site_to_pkg_pin[keys["ibuf_1_loc"]], keys["inp_1_p"]) + tcl += "set_property PACKAGE_PIN {} [get_ports out[{}]]\n".format( + site_to_pkg_pin[keys["obuf_0_loc"]], keys["inp_0_p"]) + tcl += "set_property PACKAGE_PIN {} [get_ports out[{}]]\n".format( + site_to_pkg_pin[keys["obuf_1_loc"]], keys["inp_1_p"]) + tcl += "set_property PACKAGE_PIN {} [get_ports ino[{}]]\n".format( + site_to_pkg_pin[keys["iobuf_loc"]], keys["ino_p"]) + + verilog += """ + + // {region} + wire inp_0_{region}; + wire inp_1_{region}; + wire out_0_{region}; + wire out_1_{region}; + + wire ino_i_{region}; + wire ino_o_{region}; + wire ino_t_{region}; +""".format(**keys) + + verilog += """ + (* KEEP, DONT_TOUCH *) + IBUF # ({ibuf_param_str}) ibuf_0_{region} ( + .I(inp[{inp_0_p}]), + .O(inp_0_{region}) + ); + + (* KEEP, DONT_TOUCH *) + IBUF # ({ibuf_param_str}) ibuf_1_{region} ( + .I(inp[{inp_1_p}]), + .O(inp_1_{region}) + ); + + (* KEEP, DONT_TOUCH *) + OBUF # ({obuf_param_str}) obuf_0_{region} ( + .I(out_0_{region}), + .O(out[{out_0_p}]) + ); + + (* KEEP, DONT_TOUCH *) + OBUF # ({obuf_param_str}) obuf_1_{region} ( + .I(out_1_{region}), + .O(out[{out_1_p}]) + ); +""".format(**keys) + + if use_ino: + verilog += """ + + (* KEEP, DONT_TOUCH *) + IOBUF # ({obuf_param_str}) iobuf_{region} ( + .I(ino_i_{region}), + .O(ino_o_{region}), + .T(ino_t_{region}), + .IO(ino[{ino_p}]) + ); + + assign out_0_{region} = inp_0_{region}; + assign out_1_{region} = ino_o_{region}; + assign ino_i_{region} = inp_0_{region}; + assign ino_t_{region} = inp_1_{region}; +""".format(**keys) + + else: + verilog += """ + + assign out_0_{region} = inp_0_{region}; + assign out_1_{region} = inp_1_{region}; + assign ino[{ino}] = 1'b0; +""".format(**keys) + + # Differential + else: + + tcl += "set_property PACKAGE_PIN {} [get_ports inp[{}]]\n".format( + site_to_pkg_pin[keys["ibuf_0_loc"]], keys["inp_0_p"]) + tcl += "set_property PACKAGE_PIN {} [get_ports inp[{}]]\n".format( + site_to_pkg_pin[master_to_slave[keys["ibuf_0_loc"]]], + keys["inp_0_n"]) + tcl += "set_property PACKAGE_PIN {} [get_ports inp[{}]]\n".format( + site_to_pkg_pin[keys["ibuf_1_loc"]], keys["inp_1_p"]) + tcl += "set_property PACKAGE_PIN {} [get_ports inp[{}]]\n".format( + site_to_pkg_pin[master_to_slave[keys["ibuf_1_loc"]]], + keys["inp_1_n"]) + tcl += "set_property PACKAGE_PIN {} [get_ports out[{}]]\n".format( + site_to_pkg_pin[keys["obuf_0_loc"]], keys["inp_0_p"]) + tcl += "set_property PACKAGE_PIN {} [get_ports out[{}]]\n".format( + site_to_pkg_pin[master_to_slave[keys["obuf_0_loc"]]], + keys["inp_0_n"]) + tcl += "set_property PACKAGE_PIN {} [get_ports out[{}]]\n".format( + site_to_pkg_pin[keys["obuf_1_loc"]], keys["inp_1_p"]) + tcl += "set_property PACKAGE_PIN {} [get_ports out[{}]]\n".format( + site_to_pkg_pin[master_to_slave[keys["obuf_1_loc"]]], + keys["inp_1_n"]) + tcl += "set_property PACKAGE_PIN {} [get_ports ino[{}]]\n".format( + site_to_pkg_pin[keys["iobuf_loc"]], keys["ino_p"]) + tcl += "set_property PACKAGE_PIN {} [get_ports ino[{}]]\n".format( + site_to_pkg_pin[master_to_slave[keys["iobuf_loc"]]], + keys["ino_n"]) + + verilog += """ + + // {region} + wire inp_0_{region}; + wire inp_1_{region}; + wire out_0_{region}; + wire out_1_{region}; + + wire ino_i_{region}; + wire ino_o_{region}; + wire ino_t_{region}; +""".format(**keys) + + verilog += """ + (* KEEP, DONT_TOUCH *) + IBUFDS # ({ibuf_param_str}) ibufds_0_{region} ( + .I(inp[{inp_0_p}]), + .IB(inp[{inp_0_n}]), + .O(inp_0_{region}) + ); + + (* KEEP, DONT_TOUCH *) + IBUFDS # ({ibuf_param_str}) ibufds_1_{region} ( + .I(inp[{inp_1_p}]), + .IB(inp[{inp_1_n}]), + .O(inp_1_{region}) + ); + + (* KEEP, DONT_TOUCH *) + OBUFDS # ({obuf_param_str}) obufds_0_{region} ( + .I(out_0_{region}), + .O(out[{out_0_p}]), + .OB(out[{out_0_n}]) + ); + + (* KEEP, DONT_TOUCH *) + OBUFDS # ({obuf_param_str}) obufds_1_{region} ( + .I(out_1_{region}), + .O(out[{out_1_p}]), + .OB(out[{out_1_n}]) + ); +""".format(**keys) + + if use_ino: + verilog += """ + + (* KEEP, DONT_TOUCH *) + IOBUFDS # ({obuf_param_str}) iobufds_{region} ( + .I(ino_i_{region}), + .O(ino_o_{region}), + .T(ino_t_{region}), + .IO(ino[{ino_p}]), + .IOB(ino[{ino_n}]) + ); + + assign out_0_{region} = inp_0_{region}; + assign out_1_{region} = ino_o_{region}; + assign ino_i_{region} = inp_0_{region}; + assign ino_t_{region} = inp_1_{region}; +""".format(**keys) + + else: + verilog += """ + + assign out_0_{region} = inp_0_{region}; + assign out_1_{region} = inp_1_{region}; + assign ino[{ino_p}] = 1'b0; + assign ino[{ino_n}] = 1'b0; +""".format(**keys) + + verilog += "endmodule" + + # Write verilog + fname = "design_{:03d}.v".format(design_index) + with open(fname, "w") as fp: + fp.write(verilog) + + # Write TCL + fname = "design_{:03d}.tcl".format(design_index) + with open(fname, "w") as fp: + fp.write(tcl) + + # Write JSON + fname = "design_{:03d}.json".format(design_index) + + # Convert Vivado site names to fasm-style TILE.SITE names. + for data in region_data: + type_to_loc = { + "IOB33": "IOB_Y0", + "IOB33M": "IOB_Y0", + "IOB33S": "IOB_Y1" + } + site_to_loc = { + s["site_name"]: "{}.{}".format( + s["tile"], type_to_loc[s["site_type"]]) + for s in iob_sites[data["region"]] + } + + for l in ["input", "output", "inout", "unused_sites"]: + data[l] = [site_to_loc[s] for s in data[l]] + + # Write design settings to JSON + with open(fname, "w") as fp: + json.dump(region_data, fp, sort_keys=True, indent=1) + + design_index += 1 + + +if __name__ == "__main__": + run() diff --git a/minitests/iostandard/features/snippets.py b/minitests/iostandard/features/snippets.py new file mode 100644 index 00000000..be8e5ea1 --- /dev/null +++ b/minitests/iostandard/features/snippets.py @@ -0,0 +1,212 @@ +""" +This script generates code snippets for cell definition, technology mapping and +VPR architecture definition. It reads correlation of IOB fasm features with +IO standard settings such as IOSTANDARD, DRIVE and SLEW and generates: + +- Verilog model parameter definitions that match fasm features. +- Xilinx's IOB parameter translation to those features. +- Assignment of model parameters to the actual fasm features for VPR arch def. + +Generated code snippets need to be manually copied to relevant places in the +target code / arch. def. +""" +import os +import argparse +import csv + +from collections import namedtuple + +IOSettings = namedtuple("IOSettings", "iostandard, drive, slew, is_diff") + +# ============================================================================= + + +def load_feature_data(fname): + """ + Load feature vs. IO settings correlation data from a CSV file. + """ + + # Load the data from CSV + feature_data = {} + with open(fname, "r") as fp: + for line in csv.DictReader(fp): + + # Get IOSettings + ios_parts = line["iosettings"].split(".") + ios = IOSettings( + ios_parts[0], + int(ios_parts[1][1:]) if ios_parts[1][1:] != "_FIXED" else + None, ios_parts[2], "DIFF_" in ios_parts[0]) + + # Feature activity + feature_data[ios] = {k: line[k] for k in list(line.keys())[1:]} + + return feature_data + + +def filter_feature_data(feature_data): + """ + Filters fasm features and leaves only those related to IO settings. + """ + + for data in feature_data.values(): + for feature in dict(data): + + if "DISABLE" in feature: + del data[feature] + if "PULLTYPE" in feature: + del data[feature] + if "IN_TERM" in feature: + del data[feature] + if "LOW_PWR" in feature: + del data[feature] + + return feature_data + + +def get_relevant_parameters(feature_data, iob_type): + """ + Gets features relevant to a specific IOB type and assign them verilog + parameter names. + """ + + # Get all features and make parameter names + all_features = set( + [f for data in feature_data.values() for f in data.keys()]) + parameters = [] + + # Check if a feature is relevant to particular IOB type + is_diff = "DIFF_" in iob_type + + for feature in all_features: + for iosettings in feature_data: + if iosettings.is_diff == is_diff: + base_iob_type = iob_type.replace("DIFF_", "") + if base_iob_type in feature_data[iosettings][feature]: + parameters.append(( + feature, + feature.replace(".", "_"), + )) + break + + return parameters + + +# ============================================================================= + + +def generate_parameter_defs(parameters): + """ + Generates verilog parameter definitions for the IOB cell model. + """ + verilog = [] + + for feature, parameter in sorted(parameters): + verilog.append("parameter [0:0] {} = 1'b0;".format(parameter)) + + return "\n".join(verilog) + + +def generate_parameter_map(parameters): + """ + Generates mapping of parameters for fasm features for architecture + definition. + """ + xml = [] + + xml.append("") + for feature, parameter in sorted(parameters): + xml.append(" {} = {}".format(feature, parameter)) + xml.append("") + + return "\n".join(xml) + + +def generate_parameter_assignments(parameters, feature_data, iob_type): + """ + Generates parameter assignmets to be used inside the techmap. For + each IOB cell parameter a set of conditions is generated based on + feature activity observation. + """ + verilog = [] + + is_diff = "DIFF_" in iob_type + base_iob_type = iob_type.replace("DIFF_", "") + + for feature, parameter in sorted(parameters): + condition = [] + for iosettings in feature_data: + + if iosettings.is_diff != is_diff: + continue + + # Feature is set + if base_iob_type in feature_data[iosettings][feature]: + cond = "IOSTANDARD == \"{}\"".format( + iosettings.iostandard.upper()) + + if base_iob_type in ["O", "T"]: + if iosettings.drive is not None and "DRIVE" in feature: + cond += " && DRIVE == {}".format(iosettings.drive) + if "SLEW" in feature: + cond += " && SLEW == \"{}\"".format(iosettings.slew) + + condition.append(cond) + + condition = sorted(list(set(condition))) + + condition_str = " || \n".join([" ({})".format(c) for c in condition]) + verilog.append(".{}(\n{}\n)".format(parameter, condition_str)) + + return ",\n".join(verilog) + + +# ============================================================================= + + +def run(): + """ + Main. + """ + + # Parse arguments + parser = argparse.ArgumentParser() + parser.add_argument("features", type=str, help="Input CSV file") + + args = parser.parse_args() + + # Load iosettings vs. feature map + feature_data = load_feature_data(args.features) + # Reject non-iostandard related features + feature_data = filter_feature_data(feature_data) + + # Make parameters + parameters = {} + for iob_type in ["I", "O", "T", "DIFF_I", "DIFF_O", "DIFF_T"]: + parameters[iob_type] = get_relevant_parameters(feature_data, iob_type) + + # Parameter definition + with open("cells_sim.v", "w") as fp: + for iob_type, params in parameters.items(): + fp.write("// {} {}\n\n".format(iob_type, "=" * 70)) + verilog = generate_parameter_defs(params) + fp.write(verilog + "\n\n") + + # Parameter assignments + with open("cells_map.v", "w") as fp: + for iob_type, params in parameters.items(): + fp.write("// {} {}\n\n".format(iob_type, "=" * 70)) + verilog = generate_parameter_assignments( + params, feature_data, iob_type) + fp.write(verilog + "\n\n") + + # Parameter assignments + with open("arch.xml", "w") as fp: + for iob_type, params in parameters.items(): + fp.write("\n\n".format(iob_type)) + xml = generate_parameter_map(params) + fp.write(xml + "\n\n") + + +if __name__ == "__main__": + run() diff --git a/minitests/iostandard/syn+par.tcl b/minitests/iostandard/syn+par.tcl new file mode 100644 index 00000000..e75a63c8 --- /dev/null +++ b/minitests/iostandard/syn+par.tcl @@ -0,0 +1,15 @@ +create_project -force -name $env(PROJECT_NAME) -part $env(VIVADO_PART) + +set_property SEVERITY {Warning} [get_drc_checks NSTD-1] +set_property SEVERITY {Warning} [get_drc_checks UCIO-1] + +read_verilog ../$env(PROJECT_NAME).v +synth_design -top top + +source ../$env(PROJECT_NAME).tcl + +place_design +route_design + +write_checkpoint -force ../$env(PROJECT_NAME).dcp +write_bitstream -force ../$env(PROJECT_NAME).bit