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