josuah.net | panoramix-labs.fr

cv | links | quotes | mail

Interacting with FPGA hardware

One aspect of FPGAs boards is very little documented even for open-source modules: the usage of the hardware blocks of the FPGA: the primitives.

Someone contributed offered these helpful koans on IRC, starting by recalling how synthesis works.

Help from lkcl

Original text: https://libre-soc.org/irclog-f4pga/%23f4pga.2022-05-12.log.html

The way it works is that to compile something to a bitstream you need two things:

  1. Some HDL
  2. A constraints file

The constraints file is what tells the nextpnr-* to map individual NETLIST entries (pins) onto actual IO pads.

A differential clock is a clock line that, rather than having one single pin that goes on and off, there are a pair of clock lines that, simultaneously, switch to the opposite direction. I.e. they are "differential". The convention to distinguish differential pair pins is, one ends with "P" the other ends with "N" So ClockP and ClockN. P for Positive, N for Negative.

Back to internal blocks. Yosys does not know about constraints (the IO pads), it only knows about NETLISTs and the things that those nets connect to. But you do have to have some sort of representation of e.g. the IBUFDS, as a black-box module, with the inputs and outputs, so that when the use of that module is passed over to nextpnr-* (or VerilogToRouting), the PnR tool knows what to do with it.

Here is a declaration - a model - of IBUFDS: https://electronics.stackexchange.com/questions/93373/how-to-route-a-lvds-clock-from-fpga-input-to-output

Now you can (after probably translating that to verilog) use an IBUFDS / OBUFDS as if it was some sort of "black box". You do not have to write the contents of that black box named IBUFDS / OBUFDS: just use it (as an external module).

YoSys is intelligent enough to recognise this and will not complain (or, it shouldn't). It will simply pass through the black box - and all its netlists - IBUFDS.I, IBUFDS.IB, IBUFDS.O - to nextpnr-* or VerilogToRouting:

Oh, you wanted an IBUFDS, and you're compiling for ICE40? Sure, I recognise those, let me just wire that up and P&R it for you."

So it is actually extremely straightforward.

The only tricky bit, as you are finding out, is that none of this direct-interfacing with FPGA blocks is really properly documented, you're pretty much just expected to "work it out".

Sometimes not even the FPGA designers properly provide documentation. One good example is the Lattice ECP5 JTAG block, which allows you to tap into the JTAG port and insert your own commands so that you can interact with openocd via the ECP5's JTAG port.

Someone had to actually reverse-engineer the ECP5's JTAG block but, your first priority is to find the "model" of the ICE40 IBUFDS. The one I found above is for Xilinx FPGAs despite searching on gooogle "ICE40 IBUFDS".

Sources where you should definitely find a model file (IBUFDS.v and/or IBUFDS.vhdl) will be in the Lattice (proprietary) SDK. Beyond that, you are into playing the "hunt the unknown reverse-engineered thing on the internet" game.

My 2 cents

One way to go is first searching through the vendor documentation what feature we would need. For instance I/O multiplexing, math acceleration, built-in clock, analog features...

That often is documented through the company datasheet. For the example of an iCE40: SB_IO

Then the name of the block will be hopefully documented here, and now we know what to search. It gets possible to search for the name of the block over the synthesis tool (like YoSys) library: Here, there might be Verilog code with "Blackbox" modules to call as regular Verilog modules.

For instance SB_IO for iCE40's I/O control or SB_HFOSC for the internal oscillator. For YoSys, it might be somewhere like /usr/local/share/yosys.

This would give a module interface definition to use in the code.