blob: e40045bae8a0a41ff4244dcfbc6f15de3d26265b [file] [log] [blame]
"""
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)