Database Development Overview

A targeted approach is used to construct bitstream databases as quickly as possible. The planned flow is for every routing mux or configuration setting we want to determine, to create post-place-and-route designs for all possibilities then run them through bitstream generation and compare the outputs.

NCL Files

NCL files are file format used by Lattice (originating from NeoCAD), which are a textual representation of the internal design database. Although there is little documentation on them, the format is relatively straightforward. There are tools included with Diamond to convert the design database to and from a NCL file (ncd2ncl and ncl2ncd). These are wrapped by the script diamond.sh included in Project Trellis, that allows two possibilities:

  • If given a Verilog file, it will use Diamond for synthesis and PAR, and as well as a bitstream also dump the post-place-and-route design as a NCL file. This way you can inspect how the design maps to an NCL file, and the routing and configuration inside the tile.
  • If given a NCL file, it will skip synthesis and PAR. It will convert the NCL file to a design database, then generate a bitstream from that.

In the planned fuzzing flow, we will first create a Verilog design for what we want to fuzz by hand, and convert it to an NCL file. Then we will manually create a template NCL file containing only the mux/config to be fuzzed. The Python fuzzer script will then subsitute this template file for each fuzz possibility.

A template file for LUT initialisation is shown as an example:

::FROM-WRITER;
design top
{
   device
   {
      architecture sa5p00;
      device LFE5U-25F;
      package CABGA381;
      performance "8";
   }

   comp SLICE_0
      [,,,,A0,B0,D0,C0,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,]
   {
      logical
      {
         cellmodel-name SLICE;
         program "MODE:LOGIC "
                 "K0::H0=${lut_func} "
                 "F0:F ";
         primitive K0 i3_4_lut;
      }
      site R2C2A;
   }

}

The NCL file contains information about the device, components and routing (routing is not included in this example). As this was from a LUT initialisation fuzzer, ${lut_func} will be replaced by a function corresponding to the LUT init bit to be fuzzed (NCL files require an expression for LUT initialisation, rather than a series of bits).

Fuzzers

There are three types of fuzzer in use in Project Trellis. They are routing fuzzers, non-routing fuzzers and meta-fuzzers.

Routing fuzzers use the helper functions in util/fuzz/nonrouting.py. These will generally follow the following algorithm:

  • Use the Tcl API to discover all of the netnames at the target location

  • Use the Tcl API to discover the arcs associated with those netnames

  • Apply filters to remove netnames and/or arcs not applicable to the current fuzzer

    • For example, when fuzzing a CIB_EBR you would ensure to include CIB signals but exclude EBR signals
    • When fuzzing a EBR you would conversely filter out the CIB signals and include EBR signals.
  • Create a reference “empty” bitstream

  • For every arc discovered above:

    • Create a bitstream containing the arc using a NCL file

    • Compare the bitstream against the empty bitstream:

      • If there was a change outside the tile(s) of interest, the arc is ignored
      • If there was a change to any of the tile(s) of interest, add a configurable mux arc to the database of the relevant tile
      • If there was no change at all, add the arc as a fixed connection to the database of the first specified tile

Note that routing fuzzers for special function tiles (such as PIO, EBR, etc) are primarily intended to find fixed connections to CIBs and within special functions rather than significant amounts of configurable routing, but the above algorithm is still used for consistency (and because it is not possible to know a priori whether an arc is configurable or fixed).

Non-routing fuzzers are intended to fuzz configuration such as LUT initialisation, flip-flop settings or IO type. It is possible to fuzz either words or enums. Words are for settings that naturally comprise of one or more bits, such as LUT initialisation. Enums are for settings with multiple textual values.

The algorithm for word settings is to create one bitstream for each word bit with that bit set, and compare to an all-zero bitstream to determine the change for that bit. These are also optionally compared against an empty bitstream with the setting/feature entirely missing to determine a default value, which is not always all-zeros.

The algorithm for enum settings is to create bitstreams with all possible enum values, in each case storing the CRAM of all tiles of interest. These are then compared to determine the set of bits affected by the enum, and in each case the bit values for each possible enum value.

Creating non-routing requires more work than routing fuzzers. The settings of interest, possible values, and how to create them in the Ncl or Verilog input must all be included in the fuzzer script.

Finally meta-fuzzers do not do any fuzzing but perform other necessary manipulations on the database during the fuzzing flow. For example, these may copy config bits from one tile to other tile types which have identical configuration in order to reduce the time needed for fuzzing.