There are 3 sections that we will focus on, the first section is the “Host VIs”, which are VIs that run on the host computer that has the FPGA plugged in to it, or in some case is connected to the FPGA over the network.
Section 1 – Host VIs
Vi that runs this example, this is what you should open and run. Make sure that you set the FPGA Target to run in “Simulation Mode” first.
Section 2 – FPGA Vis
This is the top-level FPGA vi, it starts several loops, each of which handles a specific task:
(Host to FPGA) reading the data from a host-to-target DMA FIFO
(Data to RAM) writing and reading data to/from SRAM
(FPGA to Host) sending the results back to the host via a target-to-host DMA FIFO.
RadioData to SRAM states.ctl
This defines the states for our “State Machine”, there are 3 states:
read from SRAM states.ctl
State machine states for the memory read loop, there are only 2 states:
Just a re-usable latch vi, instead of implementing a latch every time you need one, you can use this one instead.
Section 3 – FPGA Resources
FPGA Scoped Ram is represented by the icon:
A FIFO is represented by the icon:
The definition of the FPGA SRAM
HT stands for “Host-to-Target” and is the FIFO used for transferring data from the Host to the FPGA
The FIFO which receives command from the Host Computer and transfers them to the FPGA for execution.
TH stands for “Target-to-Host” and is the FIFO used for transferring data from the FPGA back to the Host.
The host VI (Host-Top-Level.vi) contains some initialization code that opens a reference to the FPGA vi and runs it:
It then starts an event loop and waits for one of 3 actions that get triggered from the buttons on the front panel:
Write Memory – Send array of U64’s to the FPGA for loading in to FPGA SRAM
Read Memory – Send a read command to the FPGA via a Host-to-Target DMA FIFO requested the memory starting at a specific address and a size for the number of elements to return to the host. Then it listens on the target-to-host FIFO for the requested data.
Exit – Exit event. Closes the FPGA reference and terminates the application.
Finally, after the event loop terminates, there is some code to close the FPGA reference:
The top level FPGA vi contains four loops.
DMA Streaming – Host to Target
DMA Streaming – Target to Host
Data to SRAM
Data from SRAM
First Loop – DMA Streaming Host to Target
This loop listens on the “HT-Data” FIFO for new data coming in from the Host Computer. Any data it receives it sends to the VI-Scoped FIFO “HostData-to-SRAM”. This is a very simple loop and is intended to connect the functionality of this VI with the host. This loop can easily be replaced with another loop whose source of data is another Vi running on the same FPGA, or even to a Test Harness. You can have a Test Harness that runs on the Host or the FPGA, and you want this Test Harness to exercise the functionality of this VI. Additionally, this Vi can now be used in larger components and all you have to do is modify or replace this loop, which serves as a single entry point to this Vi.
Second Loop – DMA Streaming Target to Host
This loop does the opposite of the first loop, it takes the results of any operation carried out by this Vi and sends it back up to the host. Again, you can replace this loop with another loop and use it in a Test Harness or bigger component.
Third Loop – Data to SRAM
This loop contains a state machine with three states:
The idle state is pictured above and it keeps checking the VI-scoped HostData-to-SRAM FIFO for available data, once it detects that data is available on that FIFO it switches to the “write” state.
The Write state reads one element from the VI-Scoped FIFO HostData-to-SRAM and writes that element into the next memory address. The next memory address is tracked by a wire and is incremented only each time new data is written to SRAM.
The final state is the reset state. The loop enters this state whenever it detects that there is no more data to read from the host. This also resets the write address to 0. Note that in most cases data comes from the host in bursts, so you will not enter this state in most cases until everything has been read. But what about the case where the data is sent in multiple bursts? Well… that is an improvement that will make this example a bit more complex. See a future article for how to deal with this, but in a nutshell, you not only send the data from the Host, you precede it with a count for the number of elements being sent and enter the reset state once that count number of elements has been read.
Fourth and Final Loop – Data from SRAM
Above is a VI snippet of the idle state. The idle state listens for commands coming in on the HT-Commands FIFO. When a command is read, a read address counter – i – is set to 0, and the start address and number of elements to read is loaded into the 3 wires pictured above. Then the loop enters the read state.
The read state reads memory from SRAM address starting at “Start Address + i”, saves the results into a Feedback node, increments i and writes the data back to the top “Target-to-Host” loop via the VI-scoped FIFO “SRAM-to-HostData”. After the specified number of elements have been read, the loop enters the idle state.
Please post any questions in the form of comments.
Putting it to use So how do we use the Static RAM capabilities of LabVIEW FPGA? Here is a bigger and better example. St...
I spent quite a lot of time reverse engineering a PCI device made by National Instruments. It was a fun learning experience and a great way to learn how to write device drivers for a PCI device on linux. There are many ways to go about reverse engineering a device, and as John McAfee says, most of reverse engineering is just social engineering. Which means that you have to get in to the mindset of a device driver developer.
Part of getting in to that mindset is reading manuals written by the same manufacturer (if available) for that device, similar devices in the same family, reading manuals, looking at sample code from similar products made by competitors, and finally, reading as much as you can about the chips and technology in use by the device. Example? If you want to reverse engineer the DMA portion of a device, just start reading about DMA in general.
As for PCI versus PCI Express, there is no concern here because there is virtually no difference between PCI and PCIe devices as far as your device driver development is concerned, so everything you read here will easily transfer to PCIe devices.
To start, you need to have some background info on what PCI is and how a PCI device is detected and used by your operating system. I used Linux and the mainline kernel now has a great tool called mmiotrace that has been merged into the mainline kernel starting with a later version of I think 2.6. So if you have anything starting with 3 you should be good. If not, just google mmiotrace and your kernel version to make sure. This tool also should allow you to avoid needing to purchase an expensive PCI Logic Analyzer, which nowadays can be acquired for less than $5,000 (USD).
Here is a quick list of some good resources to check out:
Linux Device Drivers (3rd edition), by Jonathan Corbet, Alessandro Rubini, and Greg Kroah-Hartman – open source book which I love and purchased anyway. A bit outdated, and the 4th edition is coming out in late 2017.
mmiotrace – mmiotrace traces all reads and writes to memory mapped I/O by setting the page where each address resides to “not present” forcing a page fault whenever access occurs to that page. Then it saves the address and value, marks the page present and restarts the memory access.
So, when you turn on your computer, the system BIOS will enumerate all PCI/PCIe devices by sending a bunch of commands and identifying all devices, bridges, and devices behind bridges. (Yes, you can have multiple bridges behind each other.) After the BIOS posts and while the operating system is booting up, the kernel looks at each PCI device and its associated Manufacturer ID and Device ID. You can look up some codes on pcidatabase.com. One particular manufacturer id is Intel, whose manufacturer ID is 0x8086. Does that number sound familiar? Anyway, I thought that was pretty cool… The kernel then takes these two numbers and tries to find a kernel module that listens for those devices and loads the module with that device.
Your kernel module can then access the device configuration space. You can either read this region manually, or you can use helper functions to get more information about your device and what resources it uses/has available to you. These resources fit into 3 categories:
In the case of the National Instruments PCI-7813 device, I have 3 types of resources that I want to use, one is reading from an indicator and writing to a control, which appears to be an I/O type of access, reading and writing to/from FIFOs, which appears to be a Memory Region type of access, and the raising of Interrupts from the FPGA which then get asserted by the host computer, which is likely an Interrupt type of resource.
Here is a birds-eye view of what I did:
Install manufacturers linux device drivers
Figure out a way to unload and reload those drivers at runtime
Turn on mmiotrace, load manufacturer device drivers, run each sample program
Most simple program that loads an FPGA vi and does absolutely nothing
More advanced programs:
Write to a control and read a value
Write to a FIFO
Write to a FIFO and read an indicator which counts how many values were read
Write to a FIFO and read from a FIFO
Trigger an interrupt assert by either writing to a FIFO or a control
For each sample program I varied the control being written to, the indicator being read from by adding more to my LabVIEW project repeatedly until I started noticing a pattern.
So, for now take a look at the links (above and below) and please submit any questions in the form of comments below, while you wait for my next article.
I found this somewhere online and I saved a script for me to run this. I forget where I found it, but it is very useful to me. So let’s say you have a process, namely a kernel process such as a device driver and you want to reboot your system and don’t have physical access to your machine, you can force a system reboot with the following script.
Please note that any files will not be flushed to the hard disk, even if you click “save” or write the file out to disk, there is a good chance that these changes will not make it, however, this is better than nothing, so make the best of it!
Here is a quick overview of how to use Static RAM with the LabVIEW FPGA Module:
You first have to declare a piece of Static RAM, to do this you have 2 options:
Option 1 – Declare an FPGA global Static RAM
Right-click on the FPGA target, and select “New->Memory”
The default options are fine, I chose to rename my RAM to “My_SRAM”, and to set the number of cycles of read latency to 1. (The default option was 2)
Here is what the project looks like after adding FPGA global Static RAM:
Option 2 – Declare FPGA vi-scoped Static RAM
From an FPGA vi, right click and select “Data Storage & Transfer->VI-Defined Memory Configuration”
And you will see the following on your Block Diagram:
Double-click on it and you will see the same options as before. Rename it and set the # of cycles of read latency to 1.
After you have added some Static RAM to your project, you will be able to either Read from the memory, or write to it. Additionally, you should only read or write to it from one place each, and to do both operations from inside a Single-Cycle Timed Loop. (Note: Yes, you can do it from outside these locations, but I do not recommend that a newbie do so.)
The VI to use for both reading and writing can be accessed from the “Data Storage & Transfer” palette. It is called “Memory Method Node”. You right-click on it and select between “Read” and “Write”. You also have to wire in a reference to the SRAM. The reference can be from a Constant, where you can pick the FPGA global SRAM from a combox, or it can be a wire from the VI-defined SRAM.
Now the Write method contains 2 input terminals, one for the Address, and one for the data. Memory addresses, are indexed starting with 0, and the data type wired in should match the data type defined in the memory configuration. See here, for what is probably the most simple LabVIEW FPGA SRAM example in the world:
note: The above is also a VI snippet, which means you can drag and drop it directly into a VI and it will produce the appropriate code for you!
If you use the LabVIEW FPGA module to create a bitfile that can run on a piece of National Instruments hardware
Now that I have already demonstrated how to use LabVIEW for Windows to communicate with the NI PXIe-6592R board, I will demonstrate how to do the same thing using C++ on Windows with the free Microsoft Visual Studio 2015 Community edition. But first things first, we have to download the FPGA C interface API and Visual Studio 2015 Community Edition.
To download the installer for Microsoft Visual Studio 2015 Community Edition go here:
In this video, I use LabVIEW for Windows to call the FPGA and transfer the data in by using a Host-to-Target DMA FIFO, and a Target-to-Host DMA FIFO to get the result back to the LabVIEW for Windows application.
Part 1 showed how to create a LabVIEW FPGA project, add an FPGA target, create a VI and run it in simulation mode. Now I will go over how to call this FPGA VI from the host computer. This will come in handy when you have a larger program running on the FPGA. For the purposes of this example we will use a simple adder.
The LabVIEW FPGA module is a great tool if you want to learn how to program FPGAs. For the most basic project, you can create a simple “Adder” that on each clock cycle reads 2 input values from a control, and writes the sum to an indicator. There is a video at the bottom of this post if you don’t want to read through everything.
Step 1 – Start LabVIEW 2015
Self-explanatory, but you should see the following splash screen:
Followed by the following screen asking you to create a project or to open a project:
Step 2 – Create a Project
Click on the “Create Project” button, and the following screen will appear, select “Blank Project”
and you end up with something like this:
Save the project file now, (create a new directory somewhere and save the project file in there)
Right click on “My Computer” that is visible from inside the LabVIEW project window, select “New->Targets and Devices…”
Now you should see two options at the top, if you have the hardware plugged in and already set up, you can use the top option, if you have the hardware plugged in somewhere else and are only doing development on the current machine, select the bottom option “New target or device”, and the window below will show you all devices that you have the drivers installed for. For this example, I am selecting the PXIe-6592R
Now your project will look like this:
Any vi that you create under the “FPGA Target” node, has to be written using LabVIEW FPGA, and will run on the FPGA, anything under the “My Computer” node will run on your computer. Now you can place multiple vi’s under the FPGA Target node, but for each “Build Specification” that you create, you can only have one top-level vi. Each build specification will end up creating 1 bitfile. If your vi references or uses other vi’s, those dependencies will automatically be included in your bitfile. This way you can have one top-level vi that uses other vi’s in the same project. This comes handy when you have a complex application that you want to have wrapper test vis around.
Step 4 – Create an FPGA VI
Right-click on the “FPGA Target” and select “New->Vi”
Now save this vi, and you have a blank FPGA project! Now that’s good, what about the Adder part?
Step 5 – Create a Single-Cycle Timed Loop
Now you can place a bunch of controls, indicators and logic inside the FPGA, but if you do not put it inside of a loop, that code will execute only once. We want to be able to keep using the Adder that is inside the FPGA over and over again, so we will place the logic inside of a loop. The default speed of a LabVIEW FPGA loop is 40 MHz. You can increase the speed, but I recommend you start creating your application at 40MHz, and as your performance requirements increase to also increase the loop speed. Also note that after a certain speed, you will have to make a choice between how much you do on each clock cycle and how much logic you put in there.
So right-click on the background of the Block Diagram and select the following:
Drag your mouse and make sure the loop takes up a good amount of space:
Now create the logic using regular LabVIEW controls and indicators. (I left the gory details out for this step, as the intended audience is users with some LabVIEW experience, so if the details would have helped you, please leave a comment and I will add them here.)
On the block diagram, wire them up as so:
Step 6 – Run in Simulation Mode
Right click on the FPGA Target, and select “Simulation (Simulated I/O)”:
And click “Run” on the Front Panel and the FPGA is now running in simulation mode!
Change the values in the controls, and watch the value in the indicator change. Note that simulation mode runs a lot slower than when running on the actual FPGA target.
Watch the following video for a quick demonstration on how to create a simple LabVIEW FPGA project with a single-cycle timed loop that has 2 controls and 1 indicator that displays the sum of the values written to the controls. The video also includes a demonstration for compiling the vi to a bitstream and for running it on live FPGA hardware.