Building an R Series FPGA Interface Host Application in C

Updated Jul 27, 2023

Environment

Software

  • LabVIEW FPGA Module

Driver

  • FPGA Interface C API

Programming Language

  • C
  • C++

The C Interface to LabVIEW FPGA, new to LabVIEW FPGA 2009, allows C/C++ applications to interact directly with compiled LabVIEW FPGA VIs. Using LabVIEW FPGA, you can graphically program the FPGA functionality on NI reconfigurable I/O (RIO) devices by placing blocks on a LabVIEW block diagram. Traditionally, a LabVIEW host VI is used to interface with a compiled LabVIEW FPGA VI. With the FPGA Interface C API, you now have the option to build this host application in C/C++.

Functionality for the FPGA Interface C API includes: downloading a VI to a RIO target, performing DMA data transfers, waiting on and acknowledging interrupts, and reading and writing named controls and indicators using C function calls. The following tutorial showcases the new feature by developing a LabVIEW FPGA VI that performs analog input and output on an R Series device. The FPGA Interface C API is used with NI LabWindows/CVI 9.0 to transfer the acquired data from the FPGA to the host PC.

Developing the FPGA VI

Note: This tutorial requires the installation of at least LabVIEW 2009 along with NI-RIO 4.0 and the FPGA Interface C API 2.0 .

To begin, create a new LabVIEW FPGA project. Add any R Series (784xR or 785xR) to the project. Add a FIFO and configure the FIFO type as Target to Host - DMA and the data type as I16. The project should look like Figure 2.

Figure 2. LabVIEW FPGA project with PXI-7833R.

Create a new FPGA VI and design the block diagram as shown in Figure 3. In the first loop, a sine wave is generated and then output on AO0. The rate at which the sine wave is generated is determined by the AO Loop Timer control. In the second loop, a sample from AI0 is acquired and if below the Threshold input, is placed onto the DMA FIFO. The rate at which samples are taken is determined by the AI Loop Timer control.

Physically wire AO0 to AI0 on the R Series card.

Figure 3. FPGA VI – Performs finite analog input at the desired rate.
 

Right click the the FPGA VI in the project explorer and click on Compile to compile your FPGA application. The compilation process will create a bitfile and store it in Your Project folder\FPGA Bitfiles.
 

Generating the C FPGA Interface

Next, generate the C API files needed to build and run a C FPGA interface application. There are three ways to launch the FPGA Interface C API Generator.

  1. From LabVIEW: Right click on the FPGA VI in the Project Explorer and click on Launch C API Generator… as shown in Figure 4.0. If this option is not available, make sure at least  NI-RIO 4.0 is installed along with the FPGA Interface C API 2.0. This will generate the necessary header files needed to interface the host code with the FPGA. Make note of the destination location for later.



               Figure 4.0. Launch C API Generator... Function on Project Explorer
  1. From Start Menu: From the Windows Start » All Programs » National Instruments » FPGA Interface C API Generator. If this option is not available, make sure at least  NI-RIO 4.0 is installed along with the FPGA Interface C API 2.0. This will generate the necessary header files needed to interface the host code with the FPGA. Make note of the destination location for later.
     

  2. From LabWindows/CVI: Open LabWindows/CVI and create a new project. Navigate to Tools » Launch FPGA Interface C API Generator... as shown in Figure 4.1. If this option is not available, make sure at least  NI-RIO 4.0 is installed along with the FPGA Interface C API 2.0. This will generate the necessary header files needed to interface the host code with the FPGA. Make note of the destination location for later.

                      

          Figure 4.1. Launch FPGA Interface C API Generator... LabWindows/CVI Tools Menu Option
 

Developing the Application in LabWindows™/CVI

Open LabWindows™/CVI and create a new user interface file. Design the user interface as shown in Figure 5. Add a graph to display the analog input data, a button labeled Acquire to start the acquisition, two numeric inputs labeled AI Loop Rate (uSec) and AO Loop Rate (uSec) to select the sampling rates, a third numeric input labeled Samples to select the number of samples to acquire, a numeric slide labeled Threshold to set the maximum threshold of the output, and a LED boolean indicator labeled Over Limit? to let the user know if the analog output is over the threshold. 



Figure 5. R Series analog input application user interface.
 

Double-click each user interface control to configure its settings. Be sure to designate callback functions for the Acquire button and the panel. These functions are required to perform actions after user interface events occur. 



Figure 6. LabWindows™/CVI control editor window.
 

When the user interface is complete, on the menu bar select Code » Generate » All Code (Figure 7). LabWindows™/CVI CodeBuilder automatically generates skeleton C code that compiles and runs based on your user-interface(.uir) file. The skeletons for the callback functions designated in the last step are automatically generated as well. 


Figure 7. LabWindows™/CVI code generator.
 

The generated skeleton code will look as following (minus the bold items):

#include <ansi_c.h>
#include <cvirte.h>
#include <userint.h>
#include "CVI_FPGA.h"
#include "NiFpga_fpga.h"

static int panelHandle;

int main (int argc, char *argv[])
{
    if (InitCVIRTE (0, argv, 0) == 0)
        return -1;  

    /* out of memory */
    if ((panelHandle = LoadPanel (0, "CVI_FPGA.uir", PANEL)) < 0)
        return -1;

    DisplayPanel (panelHandle);
    RunUserInterface ();
    DiscardPanel (panelHandle);
    return 0;
}

int CVICALLBACK panelCB (int panel, int event, void *callbackData,
                                          int eventData1, int eventData2)
{
    switch (event)
    {
       case EVENT_GOT_FOCUS:
           break;

       case EVENT_LOST_FOCUS:
           break;

       case EVENT_CLOSE:
           QuitUserInterface(0);
           break;
   }

   return 0;
}

int CVICALLBACK acquireCallback (int panel, int control, int event,
                                                    void *callbackData, int eventData1, int eventData2)
{
    switch (event)
    {
        case EVENT_COMMIT:

               /* INSERT FPGA CODE HERE */

        break;
    }
    return 0;

}

Add the QuitUserInterface(0); code to the EVENT_CLOSE case of the panelCallback. This quits the program when the panel is closed.

Next, add the files generated from the FPGA Interface C API Generator to the project: NiFpga.h, NiFpga.c, and NiFpga_fpga.h as seen in Figure 8. Also add the #include statement for the NiFpga_fpga.h and ansi_c.h files. Your project should look like Figure 9.
 



Figure 8. Adding files to the LabWindows™/CVI FPGA interface project.
 


Figure 9. Project after adding LabWindows™/CVI FPGA and adding include statements.
 

Implementing the FPGA Interface

The files generated and added to the  LabWindows™/CVI project define the functions needed to interact with the compiled LabVIEW FPGA VI. The FPGA functions should be called inside the callback function where it says /* INSERT FPGA CODE HERE */ such that when the Acquire button is clicked, the code will open a session to the FPGA VI and read the data from the DMA FIFO.
 

FPGA Interface Functions

First, initialize the FPGA interface with the NiFpga_Initialize() function. This function must be called before any other NI FPGA functions.

NiFpga_Status status = NiFpga_Initialize();


The NiFpga_IsNotError() function checks if errors have occurred. It is good practice to check for errors after the FPGA interface has been initialized and after you open the session to the FPGA VI.

if (NiFpga_IsNotError(status))


The NiFpga_Open() function opens a session to the FPGA and automatically downloads the bitfile. The bitfile is referenced in the NiFpga_fpga.h header file that was generated earlier and in this case is defined as NiFpga_fpga_Bitfile. "RIO0" is the resource name for the targeted FPGA device. By default, the FPGA VI will automatically run, but in this case the NiFpga_OpenAttribute_NoRun attribute specifies otherwise.

NiFpga_MergeStatus(&status, NiFpga_Open(NiFpga_fpga_Bitfile, NiFpga_fpga_Signature,
   "RIO0", NiFpga_OpenAttribute_NoRun, &session));


Next, write to the FPGA VI Samples,AI Loop Rate (uSec), and AO Loop Rate (uSec), Threshold  controls using the NiFpga_WriteI32(), NiFpga_WriteU32(), and NiFpga_WriteI16 functions (the controls are I32 and U32 representation) to specify the number of samples to acquire, the sample rates, and threshold. These values are set from the user interface before writing to the FPGA VI.

NiFpga_MergeStatus(&status, NiFpga_WriteI32(session, NiFpga_fpga_ControlI32_Samples,   

    numSamples));

NiFpga_MergeStatus(&status, NiFpga_WriteU32(session,
    NiFpga_fpga_ControlU32_AILoopPeriodus, airate));

NiFpga_MergeStatus(&status, NiFpga_WriteU32(session,
    NiFpga_fpga_ControlU32_AOLoopPeriodus, airate));

NiFpga_MergeStatus(&status, NiFpga_WriteI16(session, NiFpga_fpga_ControlI16_Threshold,  
    airate));


The NiFpga_Run() function runs the FPGA VI.

NiFpga_MergeStatus(&status, NiFpga_Run(session, 0));


To read from the DMA FIFO, use the NiFpga_ReadFifoI16() function. The FIFO is referenced in the NiFpga_fpga.h header file, and in this case is defined as NiFpga_fpga_TargetToHostFifoI16_AIFIFO . In this example, the parameter data is provided as the pointer to the location to store the data. The number of points to return from the FIFO read is specified by numSamples.

NiFpga_MergeStatus(&status, NiFpga_ReadFifoI16 (session,
    NiFpga_fpga_TargetToHostFifoI16_AIFIFO, data, numSamples, timeout,
    &r));


To read the Over Limit? indicator, use the NiFpga_ReadBool() function.

NiFpga_MergeStatus(&status, NiFpga_ReadBool(session, NiFpga_fpga_IndicatorBool_OverLimit,

    &overLimit));


When everything is complete, you can call the NiFpga_Close() function to close the session to the FPGA VI and the NiFpga_Finalize() function to close the FPGA interface.
 

NiFpga_MergeStatus(&status, NiFpga_Close(session, 0));

NiFpga_MergeStatus(&status, NiFpga_Finalize());

 

Conclusion

The 2009 LabVIEW FPGA Module or later, along with any RIO off-the-shelf hardware, provides developers a graphical programming environment to develop custom hardware. Previously, methods to interface to RIO hardware were limited to LabVIEW. The FPGA Interface C API opens up the doors for C developers to interface these LabVIEW FPGA applications.

Example Code

The following code demonstrates the use of these FPGA interface functions to open a session to the FPGA VI and read the data from the DMA FIFO. Replace the acquireCallback callback function with the following code:

 

int CVICALLBACK acquireCallback (int panel, int control, int event, void *callbackData,
                                                    int eventData1, int eventData2)
{
    switch (event)
    {
       case EVENT_COMMIT:

            /* disable the acquire button */
            SetCtrlAttribute (panelHandle, PANEL_ACQUIRE, ATTR_DIMMED, 1);

            /* setup the graph */
            SetCtrlAttribute(panelHandle,PANEL_GRAPH,ATTR_XAXIS_GAIN,1.0);
            DeleteGraphPlot(panelHandle, PANEL_GRAPH,-1,VAL_IMMEDIATE_DRAW);

            /* must be called before any other FPGA calls */
            NiFpga_Status status = NiFpga_Initialize();

            /* check for any FPGA error */
            if (NiFpga_IsNotError(status))
            {
                NiFpga_Session session;

                /* opens a session, downloads the bitstream, but does not run the FPGA */
                NiFpga_MergeStatus(&status, NiFpga_Open(NiFpga_fpga_Bitfile,
                                               NiFpga_fpga_Signature, "RIO0",
                                               NiFpga_OpenAttribute_NoRun, &session));

                if (NiFpga_IsNotError(status))
                {
                    /* declare variables for output and input */
                    double numSamples, aorate, airate;
                    uint16_t threshold = 0;
                    uint32_t r, timeout = 10000/* 10 seconds */;
                    NiFpga_Bool overLimit;
                    int16_t *data = NULL;

                    /* get number of samples to read and loop rate from the user interface */
                    GetCtrlVal (panelHandle, PANEL_SAMPLES, &numSamples);
                    GetCtrlVal(panelHandle,PANEL_AI_RATE,&airate);
                    GetCtrlVal (panelHandle, PANEL_AO_RATE, &aorate);

                    /* allocate size for the samples to read */
                    data = (int16_t*) malloc (sizeof (int16_t) * numSamples);

                    /* write the number of samples and loop rate to the FPGA VI */
                    NiFpga_MergeStatus(&status, NiFpga_WriteI32(session,
                                                    NiFpga_fpga_ControlI32_Samples, numSamples));
                    NiFpga_MergeStatus(&status, NiFpga_WriteU32(session,
                                                    NiFpga_fpga_ControlU32_AILoopPeriodus, airate));
                    NiFpga_MergeStatus(&status, NiFpga_WriteU32(session,
                                                    NiFpga_fpga_ControlU32_AOLoopPeriodus, aorate));
                    NiFpga_MergeStatus(&status, NiFpga_WriteI16(session,
                                                    NiFpga_fpga_ControlI16_Threshold, threshold));

                    /* run the FPGA application */
                    NiFpga_MergeStatus(&status, NiFpga_Run(session, 0));

                    /* read the DMA FIFO */
                    NiFpga_MergeStatus(&status, NiFpga_ReadFifoI16 (session,
                                                   NiFpga_fpga_TargetToHostFifoI16_AIFIFO,
                                                   data, numSamples, timeout, &r));

                    /* read the Over Limit? boolean */
                    NiFpga_MergeStatus(&status, NiFpga_ReadBool(session,
                                                    NiFpga_fpga_IndicatorBool_OverLimit, &overLimit));

                    /* plot the data */
                    PlotY(panelHandle, PANEL_GRAPH, (unsigned int*) data, numSamples,
                             AL_UNSIGNED_SHORT_INTEGER, VAL_THIN_LINE, 
                             VAL_EMPTY_SQUARE, VAL_SOLID, 1, VAL_RED);
                    SetCtrlAttribute(panelHandle, PANEL_ACQUIRE, ATTR_DIMMED, 1);
                    ProcessDrawEvents();

                    /* close the session */
                    NiFpga_MergeStatus(&status, NiFpga_Close(session, 0));
                }

                /* must be called after all other calls */
                NiFpga_MergeStatus(&status, NiFpga_Finalize());

            }

            /* check if anything went wrong */
            if (NiFpga_IsError(status))
            {
                char error[32];
                sprintf(error, "Error %d!", status);
                MessagePopup("Error", error);
            }
            break;
   }

   /* enable the acquire button */
   SetCtrlAttribute(panelHandle, PANEL_ACQUIRE, ATTR_DIMMED, 0);
   ProcessDrawEvents();

   return 0;
}

Download the example code below. You can also download NI software from the links below and use it on a free, trial basis.