Tutorial 2 – Filling in the Gaps

Make sure you’re up to date.

Run reco version to check your installation. Our current version is v0.6.0. If you need to update run reco update or see our install/update instructions.

We are going to look at pretty much the simplest calculation possible – adding two numbers together. First, we will look at the problem, discuss how to design the program, and have a go at filling in the gaps in some incompete code. The last tutorial was all about workflow, so now we’re taking the first step towards writing and debugging your own programs.

What we will do

  • Talk through a possible solution for a simple addition problem
  • Fork our tutorial materials repo
  • Have a go at completing an incomplete code example
  • Test your complete code in your Go environment
  • Check the code for compatibility with the Reconfigure.io compiler
  • Simulate running the code on an FPGA
  • Compare your code with ours

This tutorial assumes you have already run through our first tutorial: Tutorial 1 – Setup and workflow.

What’s the problem?

We want the FPGA to take two integers, 1 and 2, add them together and send the result back to us. As you saw in our first example, there’s a host CPU which works with the FPGA, with communication happening across a control interface. So, the first thing we need to do is decide what each element needs to do, and when. Then we can write some Go code to tell the host CPU how to communicate with the FPGA, as well as some Go code to program the FPGA to carry out the required tasks.

Let’s break this process down. There are just two operands involved so the host can pass them straight to the FPGA along with an address at which to store the result. Then, the FPGA can add the numbers together and write the result back. The host can read the result and print it for us to see. A flow diagram could look like this:

_images/AdditionDiagram.png

Addition flow diagram

Fork our tutorials repository

We’re now going to start using our tutorial materials repo, which contains an incomplete example for you to work on. So, as we’re going to be making changes to the code, let’s fork the repo. You’ll find it here.

First, click the fork button towards the top right of the screen.

_images/fork_button.png

You will be asked to authorize the fork being placed into your account. Then, using the instructions for your operating system below, clone your fork to your local machine:

Linux/MacOSX

From a terminal create an environment variable for your github username (substitute <username> for your github username):

export GITHUB_USERNAME=<username>

Then copy and paste the following:

git clone https://github.com/$GITHUB_USERNAME/tutorials.git $GOPATH/src/github.com/$GITHUB_USERNAME/tutorials
cd $GOPATH/src/github.com/$GITHUB_USERNAME/tutorials
git remote add upstream git://github.com/ReconfigureIO/tutorials.git
git fetch upstream
git checkout v0.3.5

Windows 10

From a Powershell terminal create an environment variable for your github username (substitute <username> for your github username):

$env:GithubUsername="<username>"

Then copy and paste the following:

git clone https://github.com/$env:GithubUsername/tutorials.git $Env:GOPATH/src/github.com/$env:GithubUsername/tutorials
cd $Env:GOPATH/src/github.com/$env:GithubUsername/tutorials
git remote add upstream git://github.com/ReconfigureIO/tutorials.git
git fetch upstream
git checkout v0.1.0

Filling in the gaps

Now navigate to your-github-username/tutorials/addition-gaps/cmd/test-addition/main.go to look at the incomplete code for the host CPU. You will notice some of the code is missing. Using the information given in the comments, along with the flowchart above, you can have a go at filling in the missing sections.

First, as we’re going to be editing existing code, let’s make a new branch to work on, call it fill-gaps:

git checkout -b fill-gaps

Here’s what needs completing:

  • Pass operands and results pointer to the FPGA (lines 28, 30 and 32)
  • Print the result from the FPGA (line 48)
  • Create an if statement to exit if the result from the FPGA does not equal 3 (lines 51-53)

Once you have completed this, move on to the incomplete code for the FPGA, located at your-github-username/examples/addition-gaps/main.go, and complete the following sections:

  • Specify the operands and result pointer (lines 24-26)
  • Perform the addition (line 40)

Once you’ve made your changes you can stage and commit them to your fill-gaps branch:

git add main.go && cmd/test-addition/main.go
git commit -m "code completed"
git push origin fill-gaps

Test your code

Now you can test your program for syntax and semantic errors within your Go environment. We’ve included a test file – main_test.go which will check that the function Add at the top of the FPGA code does what’s it’s supposed to. So, let’s test that first. Make sure you’re in your-github-username/tutorials/addition-gaps and run go test. All being well you should see something like:

$ go test
PASS
ok    github.com/your-github-username/tutorials/addition-gaps 0.007s

If there are any errors in your code they will be flagged up here for you to fix. A pass tells us that your code is compatible with the Go compiler, and the ADD function does what we’re expecting.

Next navigate to your-github-username/tutorials/addition-gaps/cmd/test-addition and run go test, and hopefully you’ll see:

$ go test
PASS
ok    github.com/your-github-username/tutorials/addition-gaps/cmd/test-addition       0.007s

If not, you will be able to see where any errors are located. A pass here tells us that your CPU code is compatible with the Go compiler.

Check and then simulate your code

Now the code is complete and we know it conforms to the Go language, let’s check your FPGA code is compatible with the Reconfigure.io compiler. Make sure you are back in tutorials/addition-gaps and run reco check. Any syntax errors will be flagged up here. All being well you should see:

$ reco check
$GOPATH/github.com/your-github-username/tutorials/addition-gaps/main.go checked successfully

Next, once you have dealt with any errors, use our hardware simulator to test how your code will run on the FPGA. First, create a project to work within and set it to be active:

reco project create addition
reco project set addition

Now you can start a simulation by running reco sim run test-addition:

$ reco sim run test-addition
preparing simulation
done
archiving
done
uploading
done
running simulation

status: QUEUED
Waiting for Batch job to start
status: STARTED
Beginning log stream for simulation 74c620cf-8fe0-4500-8a6f-fac0fa03edc2
...
3

Getting in the queue

Simulation should normally only take around 5 minutes but could be up to 30 minutes depending on what else is in the queue.

For more detailed descriptions of any error messages you might receive here, you can take a look at our troubleshooting section: Compiler Error messages.

See how we did it

Now you can take a look at our full example to see if there are any differences between our code and yours, you can find it in the examples repo you cloned in the previous tutorial. It’s always a good idea to check you have the most up-to-date version of our examples, so, first, open a terminal and navigate to $GOPATH/src/github.com/Reconfigureio/examples and run:

git describe --tags

If you have a version other than v0.4.10, please run

git fetch
git checkout v0.4.10

Here’s the host code with the missing sections highlighted:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
  package main

  import (
    "encoding/binary"
    "fmt"
    "github.com/ReconfigureIO/sdaccel/xcl"
    "os"
  )

  func main() {
    // Allocate a world for interacting with the FPGA
    world := xcl.NewWorld()
    defer world.Release()

    // Import the compiled code that will be loaded onto the FPGA (referred to here as a kernel)
    // Right now these two idenitifers are hard coded as an output from the build process
    krnl := world.Import("kernel_test").GetKernel("reconfigure_io_sdaccel_builder_stub_0_1")
    defer krnl.Release()

    // Allocate space in shared memory for the FPGA to store the result of the computation
    // The output is a uint32, so we need 4 bytes to store it
    buff := world.Malloc(xcl.WriteOnly, 4)
    defer buff.Free()

    // Pass the arguments to the kernel

    // Set the first operand to 1
    krnl.SetArg(0, 1)
    // Set the second operand to 2
    krnl.SetArg(1, 2)
    // Set the pointer to the result address in shared memory
    krnl.SetMemoryArg(2, buff)

    // Run the FPGA with the supplied arguments. This is the same for all projects.
    // The arguments ``(1, 1, 1)`` relate to x, y, z co-ordinates and correspond to our current
    // underlying technology.
    krnl.Run(1, 1, 1)

    // Create a variable for the result from the FPGA and read the result into it.
    // We have also set an error condition to tell us if the read fails.
    var ret uint32
    err := binary.Read(buff.Reader(), binary.LittleEndian, &ret)
    if err != nil {
      fmt.Println("binary.Read failed:", err)
    }

    // Print the value we got from the FPGA
    fmt.Printf("%d\n", ret)

    // Check the result is correct and if not, return an error
    if ret != 3 {
      os.Exit(1)
    }
  }

And here’s the FPGA code:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
 package main

 import (
 //  Import the entire framework for interracting with SDAccel from Go (including bundled verilog)
 _ "github.com/ReconfigureIO/sdaccel"

 // Use the new AXI protocol package for interracting with memory
 aximemory "github.com/ReconfigureIO/sdaccel/axi/memory"
 axiprotocol "github.com/ReconfigureIO/sdaccel/axi/protocol"
 )

 // function to add two uint32s
 func Add(a uint32, b uint32) uint32 {
 return a + b
 }

 func Top(
 // The first set of arguments to this function can be any number
 // of Go primitive types and can be provided via `SetArg` on the host.

 // For this example, we have 3 arguments: two operands to add
 // together and an address in shared memory where the FPGA will
 // store the output.
 a uint32,
 b uint32,
 addr uintptr,

 // Set up channels for interacting with the shared memory
 memReadAddr chan<- axiprotocol.Addr,
 memReadData <-chan axiprotocol.ReadData,

 memWriteAddr chan<- axiprotocol.Addr,
 memWriteData chan<- axiprotocol.WriteData,
 memWriteResp <-chan axiprotocol.WriteResp) {

 // Since we're not reading anything from memory, disable those reads
 go axiprotocol.ReadDisable(memReadAddr, memReadData)

 // Add the two input integers together
 val := Add(a, b)

 // Write the result of the addition to the shared memory address provided by the host
 aximemory.WriteUInt32(
   memWriteAddr, memWriteData, memWriteResp, false, addr, val)
 }

What’s next?

Now you’ve had a go at writing some code for yourself, let’s move on to Tutorial 3 – Structure and Communication to look in more detail at how we share data between the host CPU and FPGA, and we’ll build on a project template to create another simple program.