diff --git a/fuzzers/030-iob/minitest/Makefile b/fuzzers/030-iob/minitest/Makefile index 33a80839..5b08dd78 100644 --- a/fuzzers/030-iob/minitest/Makefile +++ b/fuzzers/030-iob/minitest/Makefile @@ -3,7 +3,6 @@ all: build/env build/roi_roi_io.diff \ build/SLEW/run.ok \ build/DRIVE/run.ok \ build/IOSTANDARD/run.ok \ - clean: rm -rf build @@ -35,3 +34,6 @@ build/DRIVE/run.ok: build/IOSTANDARD/run.ok: PROJECT=IOSTANDARD bash runme_tcl.sh +csv: + PROJECT=$@ bash runme.sh + diff --git a/fuzzers/030-iob/minitest/README.md b/fuzzers/030-iob/minitest/README.md index 736770a2..8dcc2fdb 100644 --- a/fuzzers/030-iob/minitest/README.md +++ b/fuzzers/030-iob/minitest/README.md @@ -1,22 +1,39 @@ # PULLTYPE -PULLTYPE 28 29 30 -NONE X -KEEPER X X +PULLTYPE 38_98 39_97 39_97 +NONE X +KEEPER X X PULLDOWN -PULLUP X X +PULLUP X X # DRIVE -DRIVE A00 A02 A08 A10 B09 B01 -0 FIXME -4 X X X -8 X X X -12 X X X -16 X X X -24 FIXME +Drive strength depends on current IOSTANDARD, e.g. +LVCMOS18 +DRIVE 38_64 38_66 38_72 38_74 39_65 39_73 +4 X X X +8 X X X +12 X X X +16 X X X +24 X X X + +LVCMOS25 +DRIVE 38_64 38_66 38_72 38_74 39_65 39_73 +4 X X X +8 X +12 +16 X X X + +LVCMOS33 +DRIVE 38_64 38_66 38_72 38_74 39_65 39_73 +4 X X X +8 X X X +12 X X X +16 X X X + +The minitest contains a csv target which generates a csv with differences across all LVCMOS and LVTTL standards for all supported DRIVE strengths and both slew rates. # IOSTANDARD diff --git a/fuzzers/030-iob/minitest/compare.py b/fuzzers/030-iob/minitest/compare.py new file mode 100644 index 00000000..9c7a24d0 --- /dev/null +++ b/fuzzers/030-iob/minitest/compare.py @@ -0,0 +1,162 @@ +import sys +import glob +import os +import re +import difflib + + +def get_file_pairs(): + pairs_list = list() + for path1 in glob.glob('*.bits'): + for path2 in glob.glob('*.bits'): + file1 = os.path.basename(path1) + file2 = os.path.basename(path2) + if file1 == file2: + continue + files_pair = [file1, file2] + files_pair.sort() + pairs_list.append(files_pair[0] + ":" + files_pair[1]) + pairs_set = set(pairs_list) + for pair in pairs_set: + file1, file2 = pair.split(":") + yield file1, file2 + + +def extract_parameters_string(basename): + params_str = re.search('^design_(.*).bits$', basename) + return params_str.group(1) + + +def extract_parameters(basename): + iostandard, slew, drive = extract_parameters_string(basename).split('_') + return iostandard, slew, drive + + +def generate_differing_bits(basename1, basename2): + with open(basename1, 'r') as path1: + with open(basename2, 'r') as path2: + diff = difflib.unified_diff( + path1.read().splitlines(), + path2.read().splitlines(), + fromfile='path1', + tofile='path2') + for line in diff: + if line.startswith('---'): + continue + if line.startswith('+++'): + continue + if line.startswith('@'): + continue + if line.startswith('-'): + yield extract_parameters_string(basename1), line.strip('-') + continue + if line.startswith('+'): + yield extract_parameters_string(basename2), line.strip('+') + continue + + +class Database(): + def __init__(self, convert_bits=False): + self.all_bits = set() + self.properties_bits = dict() + self.convert_bits = convert_bits + self.populate() + + def populate(self): + for file1, file2 in get_file_pairs(): + #print(file1 + " vs " + file2) + for property_str, bit in generate_differing_bits(file1, file2): + #print(property_str + " " + bit) + self.update_all_bits(bit) + if property_str in self.properties_bits: + self.properties_bits[property_str].add(bit) + else: + self.properties_bits[property_str] = set() + self.properties_bits[property_str].add(bit) + + def update_all_bits(self, bit): + self.all_bits.add(bit) + + def print_all_bits(self): + print(self.all_bits) + + def get_keys(self): + return self.properties_bits.keys() + + def print_bits(self, key): + if key in self.properties_bits: + print("%s: %s" % (key, self.properties_bits[key])) + else: + print("The specified property is not in the database") + + def convert_bit_format(self, item): + dummy, address, word, bit = item.split("_") + address = int(address[-2:], 16) + bit = int(word) % 4 * 32 + int(bit) + return "{address}_{bit}".format(address=address, bit=bit) + + def convert_header(self, header): + converted_bits = [] + for bit in header: + #print(bit + ":" + self.convert_bit_format(bit)) + converted_bits.append(self.convert_bit_format(bit)) + return converted_bits + + def get_csv_header(self): + header = list(self.all_bits) + header.sort() + self.csv_header = header + if self.convert_bits: + header = self.convert_header(header) + line = "property,v,i,r," + for title in header: + line += title + "," + return line + '\n' + + def extract_rvi_parameters(self, rvi): + iostandard, slew, drive = rvi.split("_") + if iostandard[-2:] == "12": + voltage = 1.2 + elif iostandard[-2:] == "15": + voltage = 1.5 + elif iostandard[-2:] == "18": + voltage = 1.8 + elif iostandard[-2:] == "25": + voltage = 2.5 + else: + voltage = 3.3 + resistance = voltage / (int(drive) * 0.001) + return "%.1f,%s,%.3f" % (voltage, drive, resistance) + + def get_csv_body(self): + lines = "" + keys = list(self.get_keys()) + keys.sort() + for properties_key in keys: + line = properties_key + "," + self.extract_rvi_parameters( + properties_key) + "," + for title in self.csv_header: + if title in self.properties_bits[properties_key]: + line += "X," + else: + line += " ," + line += '\n' + lines += line + return lines + + def write_csv(self, filename): + filename = os.getcwd() + "/" + filename + fp = open(filename, 'w') + fp.write(self.get_csv_header()) + fp.write(self.get_csv_body()) + fp.close() + print("Written results to %s file.\n" % filename) + + +def main(): + database = Database(True) + database.write_csv("differences.csv") + + +if __name__ == '__main__': + main() diff --git a/fuzzers/030-iob/minitest/generate.tcl b/fuzzers/030-iob/minitest/generate.tcl new file mode 100644 index 00000000..a20e2263 --- /dev/null +++ b/fuzzers/030-iob/minitest/generate.tcl @@ -0,0 +1,130 @@ +source "$::env(XRAY_DIR)/utils/utils.tcl" + +proc make_io_pin_sites {} { + # get all possible IOB pins + foreach pad [get_package_pins -filter "IS_GENERAL_PURPOSE == 1"] { + set site [get_sites -of_objects $pad] + if {[llength $site] == 0} { + continue + } + if [string match IOB33* [get_property SITE_TYPE $site]] { + dict append io_pin_sites $site $pad + } + } + return $io_pin_sites +} + +proc load_pin_lines {} { + # IOB_X0Y103 clk input + # IOB_X0Y129 do[0] output + + set fp [open "$::env(SRC_DIR)/params.csv" r] + set pin_lines {} + for {gets $fp line} {$line != ""} {gets $fp line} { + lappend pin_lines [split $line ","] + } + close $fp + return $pin_lines +} + +proc loc_pins {} { + set pin_lines [load_pin_lines] + set io_pin_sites [make_io_pin_sites] + + puts "Looping" + foreach line $pin_lines { + puts "$line" + lassign $line site_str pin_str io cell_str + + # Have: site + # Want: pin for site + set site [get_sites $site_str] + #set pad_bel [get_bels -of_objects $site -filter {TYPE =~ PAD && NAME =~ IOB_*}] + # set port [get_ports -of_objects $site] + set port [get_ports $pin_str] + set tile [get_tiles -of_objects $site] + set pin [dict get $io_pin_sites $site] + set iostandard [get_property IOSTANDARD $port] + + set_property -dict "PACKAGE_PIN $pin IOSTANDARD $iostandard" $port + } +} + +proc set_property_value_on_port {property value port} { + set_property $property $value $port + set got [get_property $property $port] + if {"$got" != "$value"} { + puts "Skipping: wanted $value, got $got" + return 1 + } + return 0 +} + +proc run {} { + create_project -force -part $::env(XRAY_PART) design design + read_verilog top.v + synth_design -top top + + # Mostly doesn't matter since IOB are special, but add anyway + create_pblock roi + add_cells_to_pblock [get_pblocks roi] [get_cells roi] + resize_pblock [get_pblocks roi] -add "$::env(XRAY_ROI)" + + set_property CFGBVS VCCO [current_design] + set_property CONFIG_VOLTAGE 3.3 [current_design] + set_property BITSTREAM.GENERAL.PERFRAMECRC YES [current_design] + set_param tcl.collectionResultDisplayLimit 0 + + loc_pins + place_design + route_design + set pin_lines [load_pin_lines] + # For HR Current Drive + set property_dictionary [dict create \ + LVCMOS12 \ + [dict create DRIVE [list 4 8 12] SLEW [list SLOW FAST]] \ + LVCMOS15 \ + [dict create DRIVE [list 4 8 12 16] SLEW [list SLOW FAST]] \ + LVCMOS18 \ + [dict create DRIVE [list 4 8 12 16 24] SLEW [list SLOW FAST]] \ + LVCMOS25 \ + [dict create DRIVE [list 4 8 12 16] SLEW [list SLOW FAST]] \ + LVCMOS33 \ + [dict create DRIVE [list 4 8 12 16] SLEW [list SLOW FAST]] \ + LVTTL \ + [dict create DRIVE [list 4 8 12 16 24] SLEW [list SLOW FAST]] \ + ] + #HSUL_12 no DRIVE support, only SLEW + #HSTL_I, HSTL_II, HSTL_I_18, HSTL_II_18 no drive support, only SLEW + #SSTL/18/135/ no drive support, only SLEW + + foreach iostandard [dict keys $property_dictionary] { + foreach slew [dict get $property_dictionary $iostandard SLEW] { + foreach drive [dict get $property_dictionary $iostandard DRIVE] { + foreach line $pin_lines { + lassign $line site_str pin_str io cell_str + set port [get_ports $pin_str] + + set_property IOSTANDARD $iostandard $port + + if {$io == "input"} continue + + if {[set_property_value_on_port SLEW $slew $port]} { + continue + } + + if {[set_property_value_on_port DRIVE $drive $port]} { + continue + } + } + if {[catch {write_bitstream -force design_${iostandard}_${slew}_${drive}.bit} issue]} { + puts "WARNING failed to write: $issue" + continue + } + # Only write checkpoints for acceptable bitstreams + write_checkpoint -force design_${iostandard}_${slew}_${drive}.dcp + } + } + } +} +run diff --git a/fuzzers/030-iob/minitest/params.csv b/fuzzers/030-iob/minitest/params.csv new file mode 100644 index 00000000..aa42f191 --- /dev/null +++ b/fuzzers/030-iob/minitest/params.csv @@ -0,0 +1,2 @@ +IOB_X0Y111,di[0],input,di_bufs[0].ibuf +IOB_X0Y107,do[0],output,do_bufs[0].obuf diff --git a/fuzzers/030-iob/minitest/runme.sh b/fuzzers/030-iob/minitest/runme.sh index e552f4e0..06bf1611 100644 --- a/fuzzers/030-iob/minitest/runme.sh +++ b/fuzzers/030-iob/minitest/runme.sh @@ -6,13 +6,14 @@ set -ex # Create build dir export SRC_DIR=$PWD -BUILD_DIR=build/$PROJECT +export BUILD_DIR=build/$PROJECT mkdir -p $BUILD_DIR cd $BUILD_DIR +python3 ${SRC_DIR}/top.py > top.v -export TOP_V=$SRC_DIR/top.v - -${XRAY_VIVADO} -mode batch -source $SRC_DIR/runme.tcl -${XRAY_BITREAD} -F $XRAY_ROI_FRAMES -o design.bits -z -y design.bit -test -z "$(fgrep CRITICAL vivado.log)" - +${XRAY_VIVADO} -mode batch -source $SRC_DIR/generate.tcl +for x in design*.bit; do + ${XRAY_BITREAD} -F $XRAY_ROI_FRAMES -o ${x}s -z -y $x +done +test -z "$(fgrep CRITICAL vivado.log)" && touch run.ok +python3 ${SRC_DIR}/compare.py diff --git a/fuzzers/030-iob/minitest/top.py b/fuzzers/030-iob/minitest/top.py new file mode 100644 index 00000000..e9f8a432 --- /dev/null +++ b/fuzzers/030-iob/minitest/top.py @@ -0,0 +1,150 @@ +''' +Generate a primitive to place at every I/O +Unlike CLB tests, the LFSR for this is inside the ROI, not driving it +''' + +import os +import random +import sys +#random.seed(int(os.getenv("SEED"), 16)) +from prjxray import util +from prjxray import verilog + + +def gen_iobs(): + ''' + IOB33S: main IOB of a diff pair + IOB33M: secondary IOB of a diff pair + IOB33: not a diff pair. Relatively rare (at least in ROI...2 of them?) + Focus on IOB33S to start + ''' + for _tile_name, site_name, site_type in util.get_roi().gen_sites( + #['IOB33', 'IOB33S', 'IOB33M']): + ['IOB33S']): + yield site_name, site_type + + +def write_pins(ports): + pinstr = '' + for site, (name, dir_, cell) in sorted(ports.items(), key=lambda x: x[1]): + # pinstr += 'set_property -dict "PACKAGE_PIN %s IOSTANDARD LVCMOS33" [get_ports %s]' % (packpin, port) + pinstr += '%s,%s,%s,%s\n' % (site, name, dir_, cell) + open('params.csv', 'w').write(pinstr) + + +def run(): + # All possible values + iosites = {} + for site_name, site_type in gen_iobs(): + iosites[site_name] = site_type + + # Assigned in this design + ports = {} + DIN_N = 0 + DOUT_N = 0 + + def remain_sites(): + return set(iosites.keys()) - set(ports.keys()) + + def rand_site(): + '''Get a random, unused site''' + return random.choice(list(remain_sites())) + + def get_site(): + return next(iter(remain_sites())) + + def assign_i(site, name): + nonlocal DIN_N + + assert site not in ports + cell = "di_bufs[%u].ibuf" % DIN_N + DIN_N += 1 + ports[site] = (name, 'input', cell) + + def assign_o(site, name): + nonlocal DOUT_N + + assert site not in ports + cell = "do_bufs[%u].obuf" % DOUT_N + DOUT_N += 1 + ports[site] = (name, 'output', cell) + + # Assign at least one di and one do + assign_i(get_site(), 'di[0]') + assign_o(get_site(), 'do[0]') + # Now assign the rest randomly + #while len(remain_sites()): + # assign_o(rand_site(), 'do[%u]' % DOUT_N) + + #write_pins(ports) + + print( + ''' +`define N_DI %u +`define N_DO %u + +module top(input wire [`N_DI-1:0] di, output wire [`N_DO-1:0] do); + genvar i; + + //Instantiate BUFs so we can LOC them + + wire [`N_DI-1:0] di_buf; + generate + for (i = 0; i < `N_DI; i = i+1) begin:di_bufs + IBUF #( + ) ibuf(.I(di[i]), .O(di_buf[i])); + end + endgenerate + + wire [`N_DO-1:0] do_unbuf; + generate + for (i = 0; i < `N_DO; i = i+1) begin:do_bufs + OBUF #( + ) obuf(.I(do_unbuf[i]), .O(do[i])); + end + endgenerate + + roi roi(.di(di_buf), .do(do_unbuf)); +endmodule + +//Arbitrary terminate into LUTs +module roi(input wire [`N_DI-1:0] di, output wire [`N_DO-1:0] do); + genvar i; + + generate + for (i = 0; i < `N_DI; i = i+1) begin:dis + (* KEEP, DONT_TOUCH *) + LUT6 #( + .INIT(64'h8000_0000_0000_0001) + ) lut ( + .I0(di[i]), + .I1(di[i]), + .I2(di[i]), + .I3(di[i]), + .I4(di[i]), + .I5(di[i]), + .O()); + end + endgenerate + + generate + for (i = 0; i < `N_DO; i = i+1) begin:dos + (* KEEP, DONT_TOUCH *) + LUT6 #( + .INIT(64'h8000_0000_0000_0001) + ) lut ( + .I0(), + .I1(), + .I2(), + .I3(), + .I4(), + .I5(), + .O(do[i])); + end + endgenerate +endmodule + ''' % (DIN_N, DOUT_N)) + + +if __name__ == '__main__': + run()