Skip to content

FPGA-Research-Manchester/nextpnr-fabulous

Repository files navigation

nextpnr -- a portable FPGA place and route tool


NB: This nextpnr-fabulous branch is unofficial, very proof-of-concept, very experimental, very unoptimised, and is provided with no support whatsoever. Use at your own risk!

It leverages a torc fork with minimal changes (those necessary to support building on later versions of gcc) to target XDL-compatible devices.

Note that torc is licensed under GPLv3 which differs from nextpnr's ISC license, thus please respect the limitations imposed by both licenses.

nextpnr aims to be a vendor neutral, timing driven, FOSS FPGA place and route tool.

Prerequisites

The following packages need to be installed for building nextpnr, independent of the selected architecture:

  • CMake 3.3 or later
  • Modern C++11 compiler (clang-format required for development)
  • Qt5 or later (qt5-default for Ubuntu 16.04)
  • Python 3.5 or later, including development libraries (python3-dev for Ubuntu)
    • on Windows make sure to install same version as supported by vcpkg
  • Boost libraries (libboost-dev libboost-filesystem-dev libboost-thread-dev libboost-program-options-dev libboost-python-dev libboost-dev or libboost-all-dev for Ubuntu)
  • Latest git Yosys is required to synthesise the demo design
  • For building on Windows with MSVC, usage of vcpkg is advised for dependency installation.
    • For 32 bit builds: vcpkg install boost-filesystem boost-program-options boost-thread boost-python qt5-base
    • For 64 bit builds: vcpkg install boost-filesystem:x64-windows boost-program-options:x64-windows boost-thread:x64-windows boost-python:x64-windows qt5-base:x64-windows
  • For building on macOS, brew utility is needed.
    • Install all needed packages brew install cmake python boost boost-python3 qt5
    • Do not forget to add qt5 in path as well echo 'export PATH="/usr/local/opt/qt/bin:$PATH"' >> ~/.bash_profile

Getting started

nextpnr-fabulous

To build the fabulous version of nextpnr, copy the contents of the YosysFiles directory to usr/share/yosys or usr/local/share/yosys (make sure you have installed Yosys correctly). Then build and install nextpnr-fabulous using the following commands:

cmake -DARCH=fabulous -DBUILD_GUI=OFF
sudo make install

Under folder fabulous, template.v is a template top file with all IO ports instantiated - however, due to routing issues with the IO buffer tristate ports, it is not recommended to use the template.v file from scratch - we recommend you remove all IO buffers that you are not using. template.sh is a bash script that will run place and route on the contents of template.v (assuming yosys and nextpnr have been installed), and template.pcf is a constraint file to ensure that the primitive IO buffers instantiated are mapped to the correct tiles on the chip. template.ys contains the commands for yosys to run - modify this and template.sh if you are changing verilog file names. Copy template.sh instead of moving it, as it is not generated by the script. A file called wirePairs.csv will also be generated, which contains arcs to be used for ASIC tool querying for timing information.

You are now ready to synthesize, place and route a circuit onto your architecture. This can be done using the template bash file provided, or by running yosys to produce a netlist json and passing this into nextpnr as can be seen in the template files. If you are implementing sequential logic please note that all flip-flops are globally clocked, so sequential logic is best implemented using the SystemVerilog style always @ ($global_clock) begin. LUTs will detect sequential logic and activate internal flip-flops accordingly, and other components have sequential outputs or parameters that can be applied during primitive instantiation to set their outputs to register mode.

To generate a bitstream, you must also generate a bitstream spec file. This contains mapping data for the bel configuration bits, mask data and architecture metadata, and is what the program uses to generate a bitstream. You must pass this file in along with your FASM file when generating a bitstream. Information on how to generate bitstream specs and bitstreams can be found by running python3 gen.py -help

Enabling/Disabling features

To disable generic mapping of multiplier blocks to DSP blocks, comment out the -map/fabulous/dsp_map.v tag after the techmap command in your .ys file.

To disable mapping of carry chains, comment out the -map/fabulous/arith_map.v tag after the techmap command in your .ys file. Note - carry chain mapping is not yet supported.

I'm adding some new tiles! What do I need to do?

  1. Add techmap files to yosys - for an example, have a look at the files in yosys/share in your yosys directory. If you're having issues, the yosys reddit is a good place to seek out help already given on your issues.

  2. Add the bel information to pack.cc, cells.cc and arch.cc in the nextpnrfiles directory - pack.cc will need an implementation of a packing function for any new bels, and this function must be added to the Arch::pack() function in the same file. arch.cc has a getPortTimingClass method that you will need to add a case for for your bel so that nextpnr can perform timing analysis on your new bel, and otherwise should only need to be edited if you have multiple bels on one tile or a prefix before the bel port names - you will have to edit the getBelPinWire method so that the port names can be mapped to the ports on specific bels. This will likely also require some modifications to the ChipInfo definition at the start of the file - changing the z value of a bel is the easiest way to distinguish it from other bels on the tile so you can access the distinct port names (Look at what I've done for LUT4 bels for an example). cells.cc will require modification of the create_diko_cell function so that it has a case for creating your new bel and adds the necessary ports. If your new cell has both inputs and outputs, you will also need to modify the getCellDelay method in arch.cc to let nextpnr know which ports a path exists between (i.e. return true if the from port is an input and the to port is an output)

  3. If you have some cumbersome bel VHDL file names and you would like to change them or shorten them when creating your bel file, add a case to the genNextpnrModel function in in gen.py - this is found towards the end, where you can see assignment of the cType variable - simply catch this case and assign cType to your new, shorter name. Make sure this doesn't share a name with an existing bel!

  4. If you have a lot of bels on your tile, you may need to have a look at the letters variable in gen.py - this is used to name the bels, but is only populated up to a certain point, so you may have to extend it.

  5. For bitstream generation you will need to add to the BelMap dict found in the genBitStream function. You should add the names of your new bels as keys to the dict, and as values you should add another dictionary that maps the feature name (as will be represented in the FASM output) to the index of the configuration bit that enables it. This index should be relevant only to the config bits of the bel itself, as given in the bel VHDL - sequencing of bel configurations and application of masks is performed at a later stage.

Constraints for the placement of IO/bels

Constraints for your architecture can be put in place using a .pcf file - there will be an example in simple.pcf in the nextpnrfiles directory. To constrain IO, use the set_io cell bel command, where cell is the name of the io cell you wish to constrain in your verilog code and bel is the bel name of the bel you wish to constrain this IO element to. Using this tool the bel name takes the format X0Y0.name - i.e. the coordinates of the bel and the name it is assigned in the bel.txt file - this will normally just be a capital letter indexing the bel alphabetically based on the order they were added to your .csv fabric file: the first bel on tile X1Y2 would be called X1Y2.A, the second X1Y2.B and so on. It is important to note that the cell type that IO can be mapped to corresponds directly to the nature of the pin type i.e. inout pins are packed to W_IO tiles, as the bels on these tiles are IO buffers, and input and output pins are packed to CPU_IO tiles, as these tiles have separate input and output buffers. Constraints will not override this, as yosys is responsible for mapping the verilog constructs to cell types, and nextpnr (which accepts the .pcf file format) has no part in this process.

To constrain a cell to a specific bel, use the COMP command, followed by the cell name and then LOCATE = (site name). Note that this will only work if the cell is being packed to that type of bel anyway.

For information about general I/O constraints in nextpnr, nextpnr/docs/constraints.md may be useful.

Primitive instantiation

As described in more detail in the yosys documentation, the (*keep*) attribute can be used to instantiate a component and clarify that yosys should not try to optimise it away. This is done in the format (* keep *) COMPONENT_TYPE #(PARAMETER = VALUE) COMPONENT_NAME(.PORT_NAME1(WIRE_NAME1), .PORT_NAME2(WIRE_NAME2), ...);. For information about which parameters should have which names, take a look at the genBitstreamSpec method in gen.py - it is important the parameter names match or fasm output will fail.

I want to maintain, edit or adjust this tool! How does it work?

The following describes only the flow of this project's CAD tools, and not the fabric generation itself.

  1. The genFabricObject function takes in the main input CSV file containing a tile layout, descriptions of the wires' tiles and bels and the locations of switch matrix files. It converts this into one unified Fabric object, which carries the information the tool will need for model generation (including a 2D list of Tile objects).

  2. The genNextpnrModel function creates the files needed for the nextpnr parser to place and route onto your specific architecture. This consists of two files, pips.txt and bel.txt. pips.txt contains all switch matrix pips as well as all wires between tiles - these are separated by comments that distinguish between tile-internal pips (switch matrix multiplexer interconnects) and tile-external pips (inter-tile wires).

  3. If bitstream generation is necessary, the genBitstreamSpec function creates the bitstream specification file. This file is created using python's pickle library, and contains a dictionary containing a dict mapping tiles to their types, a dict containing the bitstream layout for the different tiles, a dict containing the frame mask for each tile and some other information about the architecture. This data is explained in more detail in step 6.

  4. Mapping from verilog to cells is performed by yosys. More detail about the yosys process can be found by exploring the files referenced in the template simple.ys file provided, but to briefly summarise, cells_sim.v describes the available cells, cells_map.v describes handles mapping of luts, arith_map.v handles carry chain mapping (not yet working), and dsp_map.v handles mapping of multiplier cells to DSP blocks. These files can be commented out according to what is desired/required for the project at hand. The yosys stage outputs a .json file containing a techmapped netlist.

  5. Placing and routing onto the architecture is handled by nextpnr. Nextpnr takes in the bel.txt and pip.txt files created earlier, and uses these to generate an abstract model of the architecture, which it then uses for placement and routing. Primitive instantiation and constraints are also handled during this stage. At the end of this stage, nextpnr outputs a .fasm file, of which each line represents a feature to enable or a value to set.

  6. Finally, the genBitstream function creates bitstream output. This uses the bitstream spec we generated in step 3. Before generation, the fasm input is processed - this involves use of the fasm library, mainly to break up the LUT init values into individual features, which limits how complicated the generation stage must be. The bitstream generation process is relatively simple thanks to the nature of the spec, and takes place on a feature by feature basis. For each feature, the TileMap dict in the spec is used to determine what kind of tile the relevant tile is. This allows access to the correct dict in TileSpecs, which maps feature names to a dict of bit addresses and their corresponding values. This means that to perform the necessary bitstream changes for a given feature, we can simply use the feature name to access a series of bits that need changing and their required value, and we can iterate through this to perform the necessary changes. This process is performed for each feature, and outputs a list that corresponds to the bitstream, however has not yet been adjusted for the frame masks. The frame mask is applied next in what is again a relatively simple process - the code effectively iterates through the characters of the mask, and where a character is 1 the next bit of the list is concatenated to the current output - if a character is 0 we know we do not care about this bit and so we simply put in a 0.

Links and references

Synthesis, simulation, and logic optimization

FPGA bitstream documentation (and tools) projects

Other FOSS FPGA place and route projects