Welcome back! In this installment, we’re going to get our development environment set up and generate our first custom hardware design. If you found yourself here without seeing Part I, head on over to Hardware-as-Code Part I and read the introduction to what series is all about. If you haven't gotten the hardware yet, see the link in the Things section.
Software installationFirst, let’s install the required software. We need to install three packages:
- PlatformIO – this is a common embedded-systems development platform that can be used to develop for Arduino and many other boards.
- Upduino HLS – this is a toolchain built for PlatformIO that compiles C/C++ down to hardware.
- Radiant Programmer – this is a tool from the FPGA vendor that is used to download a compiled design into the chip on the board,
PlatformIO can be used from the command line or within the Visual Studio Code environment. The following are instructions for both options.
Option 1: Visual Studio Code extension- Click on the Extensions icon to open the extensions folder
- Enter platformio ide in the search box
- Select and install the PlatformIO IDE extension
To install the Upduino HLS toolchain, we need to use the PlatformIO CLI. To open a CLI window, click the PlatformIO icon (ant) on the left and then select PlatformIO Core CLI under the Miscellaneous heading as shown below.
This will open a new terminal windows where you can enter pio commands. Install the HLS toolchain by entering the following two commands:
Install the Windows C/C++ tools:
> pio platform install windows_x86
Install the Upduino HLS tools (this takes around 10 minutes):
> pio platform install upduino_hls
Option 2: Command-line installationRequires python 3.6+
Download the install script from: https://raw.githubusercontent.com/platformio/platformio-core-installer/master/get-platformio.py
Enter the following commands from a Command Prompt:
Install PlatformIO:
> python get-platformio.py
Add PlatformIO to the PATH:
> set PATH=%USERPROFILE%\.platformio\penv\Scripts;%PATH%
Install the Windows C/C++ tools:
> pio platform install windows_x86
Install the Upduino HLS tools (this takes around 10 minutes):
> pio platform install upduino_hls
Radiant installIf you purchased the target hardware mentioned in part I (https://tinyvision.ai/products/upduino-v3-0?variant=39608867618919), then you will need to install the Radiant Programmer to download hardware designs into the board. If you do not have the board, then you do not need this package and can skip ahead to the project creation section.
The Radiant download page is: https://www.latticesemi.com/LatticeRadiant#windows
You do not need to download the complete Radiant package which is quite large. We just need the programmer, so look for this download option towards the bottom of the download page:
With these tools in hand, let’s create a first project. If you want to save time, simply clone the Hardware-as-Code Examples git repository (https://github.com/sathibault/hac-examples.git) and open the hello-fpga
folder.
If you don’t have git installed, or don’t want to clone the project, these are the detailed steps to create your own project:
- Create a new folder for the project
- Create a
platformio.ini
file in the project folder with the following contents:
[env:fpga]
platform = upduino_hls
- Create a
src
sub-folder in the project folder - Copy the source code from https://raw.githubusercontent.com/sathibault/hac-examples/main/hello-fpga/src/hello.cc into a C++ source file in the
src
sub-folder. - If you are using Visual Studio Code then go ahead and open the project using the File -> Open folder… menu option.
NOTE: If you did not install the Radiant programmer because you don’t have the board, you will need to edit the project’s platform.ini
file to add the line: upload_command = none
. This will prevent the tool chain from complaining about not finding the programmer software.
Let’s walk through the source code of this first example:
Line 3: Include the Core Fusion FPGA programming framework header. Often times, programming a co-processor like an FPGA or GPU requires a lot of boilerplate code. We've designed the Core Fusion framework to minimize the effort necessary to get started, but still provide lower-level APIs for more advanced design.
Line 6-8: The example function that we want to implement as custom hardware on the FPGA.
Line 12: Create an instance of the C++ type that represents the FPGA board. This not only tells the compiler what board to compile for, but provides an interface to communicate with the board.
Line 15: Turn the calc
function into a custom FPGA function. The call to fpga_func
is what indicates to the compiler that we want to implement the calc
function in hardware. It also synthesizes and returns a wrapper function that will communicate with the FPGA to call the hardware version whenever we invoke the wrapper. The first two arguments tell it how to communicate with the FPGA, i.e. via the board's serial input/output.
Line 18: Initialize the board. This must appear after the creation of any hardware functionality; for now that means after the call to fpga_func
.
Line 23: Call the FPGA function! When we call fun
, the wrapper function returned by fpga_func
, it sends the input argument i
to the FPGA function over the board's serial input and then waits for the FPGA function to return the result over the board's serial output.
Line 27: Do necessary cleanup. This terminates any connections to the board and performs some other necessary internal housekeeping when we are done with the board.
Build and run the example (simulation)You may now build this program by clicking the check mark icon (next to house icon) at the bottom of the IDE, or if you are using the command line, enter the command: pio run
So, what exactly did that do anyway? If you look at the output, it appears to have built an executable named program.exe
. That looks like software, not hardware! That’s because the default build rule will build only the software version of your program. All the “hardware-as-code” we write is also valid software and can be run on our computer. In fact, that’s one of the major advantages of this approach, that we can easily verify the functional behavior of our code without building any hardware at all!
Okay, let's run it. From the command prompt enter (if you’re using Visual Studio Code, open a command prompt by clicking the PlatformIO icon (ant) on the left and then selecting Platform Core CLI under Miscellaneous):
> .\program
This should output a bunch of numbers like:
calc(0) = -15
calc(1) = -8
calc(2) = -1
calc(3) = 6
calc(4) = 13
When run without any options, instead of using the FPGA, the software will simply call the original calc
function compiled for the CPU. In this way, we can quickly develop and test our function before generating the hardware implementation.
Inspecting the output, you can see that the calc
function seems to be doing what we'd expect, so we can go ahead and implement this in hardware.
First, plug the board into a USB port on your computer. Generate the hardware implementation and upload it to the FPGA by clicking the right-arrow icon (next to build icon) at the bottom of the IDE, or if you are using the command line, enter the command: pio run --target upload
NOTE: if you don't have the board, you can go through the build process without uploading to a board with the command: pio run --target bitstream
This process will take a couple minutes and you will see a lot of output fly by, but when it completes, you will have a hardware implementation of the calc
function on the FPGA.
The board needs to be reset after programming, so unplug it from the USB port and plug it back in to reset the USB interface.
After re-plugging the board into the USB port, you will need to open the device manager to see what serial port it is using. See the example below:
Under Ports (COM & LPT), you will see a "USB Serial Port" with the name of the COM port it is using next to it. This is the UPduino board (TIP: The COM port number will not change if you always plug the board into the same USB port).
You may now run the program again, this time using the FPGA by providing the appropriate COM port name as an argument (COM5 in my case):
> .\program --dev COM5
calc(0) = -15
calc(1) = -8
calc(2) = -1
calc(3) = 6
calc(4) = 13
You should get the same output as before, but this time it's from the FPGA!
What have we done?We've just created our first special-purpose hardware implementation from a C function. Since it's a standard C function, it is quick and easy to test/debug on the computer using any standard software development tools. It's also super small and ultra efficient! This post is already long, so we won't look into the details just yet, but the generated hardware is basically want you'd expect: 1 multiplier connected to 1 adder.
Next stepsAlthough every function we implement in hardware is standard a C function, not any C function can be translated to hardware. In the next few installments, we'll work through the features that are available to us and how to use them effectively. We'll also get into some details of performance: What are the strengths and weaknesses of the generated hardware? How does it compare with the software/CPU approach?
Continue to Part III: Space vs Time
ConnectPlease follow me to stay up-to-date as I release new installments. There is also a Discord server (public chat platform) for any comments, questions, or discussion you might have at https://discord.gg/3sA7FHayGH
Comments