| """ | |
| This module provides a flexible, generic interconnect fuzzer that can be adapted | |
| to a number of different cases using filter predicates and other options. | |
| The modus operandi is to first enumerate the nets at the location of interest | |
| using the ispTcl API, then find arcs on those nets again with Tcl. | |
| Then, bitstreams for each arc possibility are created and analysed using PyTrellis. | |
| These are then compared to determine the mux enable bits for that arc. | |
| """ | |
| import threading | |
| import re | |
| import isptcl | |
| import fuzzloops | |
| import pytrellis | |
| import nets | |
| import tiles | |
| def fuzz_interconnect(config, | |
| location, | |
| netname_predicate=lambda x, nets: True, | |
| arc_predicate=lambda x, nets: True, | |
| fc_predicate=lambda x, nets: True, | |
| netname_filter_union=False, | |
| enable_span1_fix=False, | |
| func_cib=False, | |
| fc_prefix="", | |
| nonlocal_prefix="", | |
| bias=0): | |
| """ | |
| The fully-automatic interconnect fuzzer function. This performs the fuzzing and updates the database with the | |
| results. It is expected that PyTrellis has already been initialised with the database prior to this function being | |
| called. | |
| :param config: FuzzConfig instance containing target device and tile(s) of interest | |
| :param location: The grid location to analyse as a (row, col) tuple. This is the grid location passed to the Tcl | |
| API, which corresponds to the location of the site of interest, which for non-CIBs is not necessarily the same as | |
| the tile location but the location at which the item of interest appears in the floorplan. | |
| :param netname_predicate: a predicate function which should return True if a netname is of interest, given | |
| the netname and the set of all nets | |
| :param arc_predicate: a predicate function which should return True if an arc, given the arc as a (source, sink) | |
| tuple and the set of all netnames, is of interest | |
| :param fc_predicate: a predicate function which should return True if a fixed connection should be included | |
| :param netname_filter_union: if True, arcs will be included if either net passes netname_predicate, if False both | |
| nets much pass the predicate. | |
| :param enable_span1_fix: if True, include span1 wires that are excluded due to a Tcl API bug | |
| :param func_cib: if True, we are fuzzing a special function to CIB interconnect, enable optimisations for this | |
| :param fc_prefix: add a prefix to non-global fixed connections for device-specific fuzzers | |
| :param nonlocal_prefix: add a prefix to non-global and non-neighbour wires for device-specific fuzzers | |
| :param bias: Apply offset correction for n-based column numbering, n > 0. Used used by Lattice | |
| on certain families. | |
| """ | |
| netdata = isptcl.get_wires_at_position(config.ncd_prf, location) | |
| netnames = [x[0] for x in netdata] | |
| if enable_span1_fix: | |
| extra_netnames = [] | |
| for net in netnames: | |
| m = re.match("R(\d+)C(\d+)_V01N(\d{4})", net) | |
| if m: | |
| row = int(m.group(1)) | |
| col = int(m.group(2)) | |
| idn = m.group(3) | |
| if row == location[0] + 1 and col == location[1]: | |
| fixednet = "R{}C{}_V01N{}".format(location[0] - 1, col, idn) | |
| print("added {}".format(fixednet)) | |
| extra_netnames.append(fixednet) | |
| netnames = extra_netnames + netnames | |
| if func_cib and not netname_filter_union: | |
| netnames = list(filter(lambda x: netname_predicate(x, netnames), netnames)) | |
| fuzz_interconnect_with_netnames(config, netnames, netname_predicate, arc_predicate, fc_predicate, func_cib, | |
| netname_filter_union, False, fc_prefix, nonlocal_prefix, bias) | |
| def fuzz_interconnect_with_netnames( | |
| config, | |
| netnames, | |
| netname_predicate=lambda x, nets: True, | |
| arc_predicate=lambda x, nets: True, | |
| fc_predicate=lambda x, nets: True, | |
| bidir=False, | |
| netname_filter_union=False, | |
| full_mux_style=False, | |
| fc_prefix="", | |
| nonlocal_prefix="", | |
| bias=0): | |
| """ | |
| Fuzz interconnect given a list of netnames to analyse. Arcs associated these netnames will be found using the Tcl | |
| API and bits identified as described above. | |
| :param config: FuzzConfig instance containing target device and tile(s) of interest | |
| :param netnames: A list of netnames in Lattice (un-normalised) format to analyse | |
| :param netname_predicate: a predicate function which should return True if a netname is of interest, given | |
| the netname and the set of all nets | |
| :param arc_predicate: a predicate function which should return True if an arc, given the arc as a (source, sink) | |
| tuple and the set of all netnames, is of interest | |
| :param bidir: if True, arcs driven by as well as driving the given netnames will be considered during analysis | |
| :param netname_filter_union: if True, arcs will be included if either net passes netname_predicate, if False both | |
| nets much pass the predicate. | |
| :param full_mux_style: if True, is a full mux, and all 0s is considered a valid config bit possibility | |
| :param fc_prefix: add a prefix to non-global fixed connections for device-specific fuzzers | |
| :param bias: Apply offset correction for n-based column numbering, n > 0. Used used by Lattice | |
| on certain families. | |
| """ | |
| net_arcs = isptcl.get_arcs_on_wires(config.ncd_prf, netnames, not bidir) | |
| baseline_bitf = config.build_design(config.ncl, {}, "base_") | |
| baseline_chip = pytrellis.Bitstream.read_bit(baseline_bitf).deserialise_chip() | |
| max_row = baseline_chip.get_max_row() | |
| max_col = baseline_chip.get_max_col() | |
| def normalise_arc_in_tile(tile, arc): | |
| return tuple(nets.normalise_name((max_row, max_col), tile, x, bias) for x in arc) | |
| def add_nonlocal_prefix(wire): | |
| if wire.startswith("G_"): | |
| return wire | |
| m = re.match(r'([NS]\d+)?([EW]\d+)?_.*', wire) | |
| if m: | |
| for g in m.groups(): | |
| if g is not None and int(g[1:]) >= 3: | |
| return nonlocal_prefix + wire | |
| return wire | |
| def per_netname(net): | |
| # Get a unique prefix from the thread ID | |
| prefix = "thread{}_".format(threading.get_ident()) | |
| assoc_arcs = net_arcs[net] | |
| # Obtain the set of databases | |
| tile_dbs = {tile: pytrellis.get_tile_bitdata( | |
| pytrellis.TileLocator(config.family, config.device, tiles.type_from_fullname(tile))) for tile in | |
| config.tiles} | |
| # First filter using netname predicate | |
| if netname_filter_union: | |
| assoc_arcs = filter(lambda x: netname_predicate(x[0], netnames) and netname_predicate(x[1], netnames), | |
| assoc_arcs) | |
| else: | |
| assoc_arcs = filter(lambda x: netname_predicate(x[0], netnames) or netname_predicate(x[1], netnames), | |
| assoc_arcs) | |
| # Then filter using the arc predicate | |
| fuzz_arcs = list(filter(lambda x: arc_predicate(x, netnames), assoc_arcs)) | |
| # Ful fullmux mode only | |
| changed_bits = set() | |
| arc_tiles = {} | |
| tiles_changed = set() | |
| for arc in fuzz_arcs: | |
| # Route statement containing arc for NCL file | |
| arc_route = "route\n\t\t\t" + arc[0] + "." + arc[1] + ";" | |
| # Build a bitstream and load it using libtrellis | |
| arc_bitf = config.build_design(config.ncl, {"route": arc_route}, prefix) | |
| arc_chip = pytrellis.Bitstream.read_bit(arc_bitf).deserialise_chip() | |
| # Compare the bitstream with the arc to the baseline bitstream | |
| diff = arc_chip - baseline_chip | |
| if (not full_mux_style) or len(fuzz_arcs) == 1: | |
| if len(diff) == 0: | |
| # No difference means fixed interconnect | |
| # We consider this to be in the first tile if multiple tiles are being analysed | |
| if fc_predicate(arc, netnames): | |
| norm_arc = normalise_arc_in_tile(config.tiles[0], arc) | |
| fc = pytrellis.FixedConnection() | |
| norm_arc = [fc_prefix + _ if not _.startswith("G_") else _ for _ in norm_arc] | |
| norm_arc = [add_nonlocal_prefix(_) for _ in norm_arc] | |
| fc.source, fc.sink = norm_arc | |
| tile_dbs[config.tiles[0]].add_fixed_conn(fc) | |
| else: | |
| for tile in config.tiles: | |
| if tile in diff: | |
| # Configurable interconnect in <tile> | |
| norm_arc = normalise_arc_in_tile(tile, arc) | |
| norm_arc = [add_nonlocal_prefix(_) for _ in norm_arc] | |
| ad = pytrellis.ArcData() | |
| ad.source, ad.sink = norm_arc | |
| ad.bits = pytrellis.BitGroup(diff[tile]) | |
| tile_dbs[tile].add_mux_arc(ad) | |
| else: | |
| arc_tiles[arc] = {} | |
| for tile in config.tiles: | |
| if tile in diff: | |
| tiles_changed.add(tile) | |
| for bit in diff[tile]: | |
| changed_bits.add((tile, bit.frame, bit.bit)) | |
| arc_tiles[arc][tile] = arc_chip.tiles[tile] | |
| if full_mux_style and len(fuzz_arcs) > 1: | |
| for tile in tiles_changed: | |
| for arc in arc_tiles: | |
| bg = pytrellis.BitGroup() | |
| for (btile, bframe, bbit) in changed_bits: | |
| if btile == tile: | |
| state = arc_tiles[arc][tile].cram.bit(bframe, bbit) | |
| cb = pytrellis.ConfigBit() | |
| cb.frame = bframe | |
| cb.bit = bbit | |
| cb.inv = (state == 0) | |
| bg.bits.add(cb) | |
| ad = pytrellis.ArcData() | |
| ad.source, ad.sink = normalise_arc_in_tile(tile, arc) | |
| ad.bits = bg | |
| tile_dbs[tile].add_mux_arc(ad) | |
| # Flush database to disk | |
| for tile, db in tile_dbs.items(): | |
| db.save() | |
| fuzzloops.parallel_foreach(netnames, per_netname) |