Skip to content

FPGA Development

Ryan Thompson edited this page Jul 9, 2024 · 23 revisions

This page contains information pertaining to digital logic development for the bladeRF's Altera Cyclone IV FPGA. See http://www.nuand.com/fpga.php for available FPGA bitstreams. "Hosted" is the default bitstream for the bladeRF.

Table of Contents

Overview

The Altera Cyclone IV FPGA interfaces with the Cypress FX3 USB3.0 controller (CYUSB3014-BZXC), the Lime Micro LMS6002D RF transceiver, the Si5338 clock generator chip, the VCTCXO DAC. The primary purpose of the default "hosted" image is to shuffle I/Q samples between the FX3 (via GPIF II interface) and the LMS6002D, and configure the LMS6002D via SPI based on commands received from the FX3 via UART. Other purposes include controlling to the Si5338 clock generator chip and VCTCXO DAC, controlling the SPDT RF switches lying in between the LMS6002D and the SMA RX/TX ports, and interfacing with expansion boards.

The VHDL source code is separated into platform-specific code and IP cores. IP is separated into nuand IP, OpenCores IP, and IP created from Altera tools.

The top level bladerf entity (bladerf.vhd) defines the ports which are connected to external pins. Architectures for the bladerf entity are kept in different files for different designs. The hosted_bladerf architecture (bladerf-hosted.vhd) corresponds to the default bladeRF image. Other existing architectures include the ADSB decoder which performs signal processing to decode ADSB signals instead of shuffling IQ samples (more info here).

Architecture

This section will describe the structure of the default FPGA architecture bladerf-hosted.vhd.

The FPGA consists of an Altera Nios II soft processor for command+control and custom logic for sample shuffling+everything else. The Nios II is clocked at 80 MHz. The C code for the Nios II is found in bladeRF/tree/master/hdl/fpga/platforms/common/bladerf/software. The "nios_system" component inside bladerf-hosted.vhd contains everything needed for command+control, which includes the Nios II processor and attached IP such as RAM, I2C, and SPI controller. IQ sample shuffling is kept separate from command+control. Samples are transferred between the FX3 and the FPGA via the FX3's GPIF II interface.

Samples Architecture

The following diagram illustrates the modules that IQ samples flow through in the FPGA. Modules outside of the sample path have been omitted. Signals that do not carry IQ samples have also been omitted for simplicity.

Note: to reduce confusion, signals/ports will be italicized and modules/processes will be monospace code formatted.

At the LMS6002D chip interface, each "IQ sample" consists of one 12-bit signed I sample and one 12-bit signed Q sample. Inside the FPGA, I and Q samples are either sign extended or sign contracted so that the FX3 sees a 16-bit I sample and a 16-bit Q sample. This was done for ease of integration with FX3 firmware.

Transmit side

Samples are sent from the FX3 to the FPGA over the bidirectional fx3_gpif bus to the fx3_gpif module. The register_gpif process inside the hosted_bladerf architecture handles the driving/releasing of the fx3_gpif bus and interfaces with the fx3_gpif module via fx3_gpif_in, fx3_gpif_out, and fx3_gpif_oe. Samples then go to tx_sample_fifo via tx_sample_fifo.wdata. Sample metadata passes through tx_meta_fifo via tx_meta_fifo.wdata when metadata is enabled. A clock domain crossing happens inside these FIFOs. Samples then pass to fifo_reader through tx_sample_fifo.rdata which separates them into the I sample (tx_sample_raw_i) and the Q sample (tx_sample_raw_q). Corrections for DC offset and other imperfections are done in tx_iq_correction. The sign contraction from 16 bits to 12 bits happens just before tx_sample_i and tx_sample_q go into the lms6002d module. This module creates the time-multiplexed lms_tx_data output signal which goes directly to the LMS6002D chip. The I sample is driven during one clock cycle and the Q sample on the next clock cycle.

Receive side

Samples are received from the LMS6002D chip via lms_rx_data. The lms6002d module extracts the time-multiplexed I and Q samples and outputs them to the rx_mux process. Sign extension happens just before the signals pass into the mux. In RX_MUX_NORMAL mode, the mux passes the raw I and Q samples to the output. The RX_MUX_12BIT_COUNTER and RX_MUX_32BIT_COUNTER modes pass either 12-bit or 32-bit counter signals rx_gen_i and rx_gen_q to the output. These modes are useful for debugging, to find out if samples are being lost in the bladeRF's USB connection (especially USB 2.0). If the host PC side receives a stream of sample values that have discontinuities (e.g. 1 2 3 15 16 17), then samples were lost. RX_MUX_DIGITAL_LOOPBACK mode performs loopback by outputting rx_loopback_i and rx_loopback_q, which originate from the rx_loopback_fifo module (not shown). After the RX mux the rest of receive path is essentially the reverse of the transmit path.

Where to place custom code

Typical FPGA applications involve placing some signal processing in the RX and/or TX path. The code can be modified so that all IQ sample processing happens inside the FPGA (e.g. modulation/demodulation, filtering, etc) and the FX3 transmits/receives some sort of data instead of IQ samples. These types of modifications/additions should go in between the iq_correction blocks and the sample/meta fifos. fifo_reader and fifo_writer can be rewritten or removed. See the Examples section for some examples of this.

Nios II Software

Software running on the Nios II soft processor handles all command/control. The software receives packetized requests (originating from host software) from the FX3 chip via UART. It then performs the appropriate configuration/control action. For example, writing registers on the LMS6002D via SPI to set the frequency/gains, setting IQ imbalance correction, setting the sample rate, writing/reading expansion or configuration GPIO, etc. The Nios system communicates with the rest of the FPGA logic via the input/output ports of the nios_system component (instantiated in bladerf-hosted.vhd). For more information, view the readme for the Nios software here.

Development

Getting Started

Building FPGA image from source

Once you have cloned the bladeRF repository to your local machine, instructions for installing the required Quartus II free software and building the FPGA image and Quartus project file can be found in the README of bladeRF/hdl. Quartus Prime Lite Edition version 20.1 is recommended.

Tutorial: Blinking some LEDs

This can be thought of as a "hello world" for the FPGA. We will simply modify the FPGA code to blink some LEDs. Quartus GUI does not need to be open for this tutorial.

The bladeRF board has 3 LEDs accessible from the FPGA: D11, D12, and D13 controlled by bladerf entity output pins led(1), led(2), and led(3) respectively. The default FPGA image uses these LEDs for status indicators. We are going to use them for an LED blinking pattern instead.

Open bladerf-hosted.vhd. CTRL-F for "led(" and you should find these lines that control the LEDs:

led(1) <= led1_blink        when nios_gpio(15) = '0' else not nios_gpio(12);
led(2) <= tx_underflow_led  when nios_gpio(15) = '0' else not nios_gpio(13);
led(3) <= rx_overflow_led   when nios_gpio(15) = '0' else not nios_gpio(14);

Comment out these lines. We are going to instead insert a blink_leds process to control the LEDs. This process will make the LEDs blink one at a time for 0.5 seconds each, in order. Paste this code into the file above the commented-out code:

blink_leds : process (c4_clock)
    variable counter : natural range 0 to 57_600_000 := 57_600_000;
begin
    if (rising_edge(c4_clock)) then
        counter := counter - 1;
        if (counter = 0) then
            counter := 57_600_000;
        elsif (counter < 19_200_000) then
            led(2) <= '0';
            led(1) <= '1';
            led(3) <= '1';
        elsif (counter < 38_400_000) then
            led(2) <= '1';
            led(1) <= '0';
            led(3) <= '1';
        else
            led(2) <= '1';
            led(1) <= '1';
            led(3) <= '0';
        end if;
    end if;
end process;

This process involves a counter which counts clock cycles of the 38.4MHz c4_clock. The LED pattern outputted is based on the value of the counter. The LEDs are active low, as seen in the bladeRF schematic. Also note that the LEDs are placed in the order D12 D11 D13 on the board.

Now lets build the FPGA bitstream. Instructions are the same as in the "Building FPGA image from source" section. Run
[quartus install directory]/nios2eds/nios2_command_shell.sh to get into the appropriate Quartus II environment. From the same terminal, go to the bladeRF/hdl/quartus directory and run build_bladerf.sh like so:

./build_bladerf.sh to print an updated list of supported boards along with their supported revisions and sizes.

./build_bladerf.sh -b <board> -r hosted -s <size>, where <board> and <size> are replaced with your bladeRF board model (bladeRF, bladeRF-micro, etc.) and FPGA size (40, 115, A5, A9, etc.).

This will take ~10 minutes to build. Once finished, cd into the newly created build folder which contains the hostedx<size>.rbf FPGA bitstream. From there you can plug in your bladeRF and use bladeRF-cli to load the FPGA with your bitstream:

bladeRF-cli -l hostedx<size>.rbf

Once loaded, you should see the LEDs blinking on your board.

Adding custom VHDL or Verilog modules, or Altera IP

If you want to instantiate a custom VHDL/IP module in bladerf-hosted.vhd, you will need to also reference the source of that module in bladerf-hosted.qip so that the build script will find it. Follow the steps below:

  1. Write/obtain a VHDL file for your custom module, or generate Altera IP using Quartus.
  2. Instantiate the module in bladerf-hosted.vhd using the same syntax of other instantiated modules
    <instance name>: entity work.<entity name of module>
  3. Open bladerf-hosted.qip and add the relative path to your module.
    For a custom VHDL module, add:
    set_global_assignment -name VHDL_FILE [file normalize [file join $here <relative_path_to_your_VHDL_file>.vhd]]
    For a custom Verilog module, add:
    set_global_assignment -name VERILOG_FILE [file normalize [file join $here <relative_path_to_your_Verilog_file>.v]]
    For Altera-generated IP, add:
    set_global_assignment -name QIP_FILE [file normalize [file join $here <relative_path_to_your_IP_file>.qip]]
  4. Run the build script to build the FPGA bitstream.
Adding these custom modules also be done in the Quartus GUI (project file will be located at hdl/quartus/work/bladeRF.qpf after initially running the build script). Be sure that the bladerf-hosted.qip file is updated if you want to build script to work.
Note: If you are generating Altera IP cores using a free version of Quartus (i.e. Quartus II Web Edition) without an IP license, you may not be able to generate a .rbf bitstream file to load the bladeRF FPGA with. Instead you will be required to use Altera's OpenCore Plus evaluation feature to load the FPGA. This means that a time-limited .sof file will be generated (placed in hdl/quartus/work/output_files) and must be loaded on to the FPGA using the USB Blaster cable connected to the FPGA's JTAG header (see "Debugging" section). This can be done in Quartus. You can either keep the cable attached after you program the FPGA, and the IP core will work without a time limit, or you can detach the cable and the IP core will work for a limited time (ex: For the FFT core, the time limit is 1 hour).

Adding your own platform revision

Working on bladerf-hosted.vhd is not a good idea if you plan to implement your own signal processing for the bladeRF, as you might want to go back to the original functionality without hassle. For this reason, the bladeRF codebase uses Quartus revisions to allow different FPGA design implementations to coexist.

To add your own revision to the bladeRF project, perform the following steps:

  • In hdl/fpga/platforms/bladerf/vhdl/, create a new vhdl file for your top level architecture. It is possible to copy bladerf-hosted.vhd to bladerf-revision_name.vhd as a start.
  • In hdl/fpga/platforms/bladerf/, create a new QIP file containing the necessary IP files, including your top level design file. Again, you can copy bladerf-hosted.qip to bladerf-revision_name.qip and just change the top level VHDL design file name to fit.
  • In hdl/quartus/bladerf.tcl, where all the revisions are created, add your revision by adding a line such as "make_revision revision_name" to make your revision known to the Quartus project.
  • In hdl/quartus/build_bladerf.sh, you need to add your revision to the list of allowed revisions. This can be accomplished by finding where the script checks if a correct revision name was passed (it's right after command line options are parsed) and adding your revision as an exception there. Don't forget to change the help message in usage() to list your revision as well.
At this point, you should be able to build your revision using the build_bladerf.sh script, passing it your revision name as a parameter.

Nios Software Development

Code for the Nios II processor is found in bladeRF/hdl/fpga/platforms/common/bladerf/software. The Quartus II installation includes an edition of Eclipse IDE which can be used to build, load, and debug the Nios program independently. View the README for instructions on getting set up with Eclipse. An Altera/Terasic USB Blaster cable is required for manually loading/debugging the code (See the "Debugging" section).

Debugging

FPGA designs can be debugged using an Altera/Terasic USB Blaster cable which connects to the bladeRF's FPGA JTAG header (J38 on the board). The Terasic cable is recommended since it is significantly cheaper than the cable sold by Altera and works the same. Designs can be debugged in the following ways:

  • Probe internal signals of the FPGA logic using Altera's SignalTap II internal logic analyzer (located in the Tools menu in Quartus). SignalTap .stp files can also be passed into the build_bladerf.sh script using the '-a' flag and the '-f' flag.
  • Step through your Nios program line-by-line using the debugger built into Eclipse. Instructions for doing this are here.
NOTE: If you are using the USB blaster cable to load the FPGA with a .sof configuration file in Quartus, you must first initialize the FPGA with the default bladeRF .rbf bitstream using bladeRF-cli or libbladeRF. Otherwise, the .sof bitstream will not work after being loaded onto the FPGA (not sure why this happens).

Tips

  • If you previously compiled an FPGA bitstream (.rbf/.sof file) in Quartus, but have edited code for the Nios program and want to regenerate the bitstream, you do not need to perform the full 10-15 minute Quartus recompilation. Follow these steps to quickly regenerate the bitstream with your updated Nios program:
    1. In Eclipse, rebuild the program by selecting Project > Build All.
    2. Right click on the bladeRF_nios project and select Make Targets > Build.
    3. Select mem_init_generate and click Build. This will update the memory initialization file nios_system_ram.hex.
    4. In Quartus, select Processing > "Update Memory Initialization File" and wait for it to complete.
    5. In the compilation view, double click "Assembler (Generate programming files)" to regenerate the bitstream. The new bitstream file will be located in quartus/work/output_files. From here you can load the FPGA as usual.
  • When using SignalTap, be sure that the clock you are using corresponds to the clock domain of the signals you are probing.

Examples

Related Resources

Clone this wiki locally