Write your first Go package for FPGAs

Josh Bohde
The Recon
Published in
4 min readOct 9, 2017

Reconfigure.io allows you to build and run your Go code on FPGAs in the cloud. In 15 minutes, you can have a reusable package written in Go, that you can deploy to FPGAs.

Things you’ll need

What we’ll do

  • Create a local Go package
  • Test it with our local Go development environment
  • Create an example kernel to use on FPGAs
  • Test it all with reco check
  • Create a command to test our kernel
  • Setup Travis CI to test it on commit

Conventions

Whenever <your github username> is used in a file, you'll need to replace that with your Github user name in your editor.

Create a package

To get started, in a shell, set the GH_USER variable to your Github username.

$ export GH_USER=<your github user name>

Next, we’re going to create a folder for our package.

$ mkdir -p $GOPATH/src/github.com/$GH_USER/add

Create $GOPATH/src/github.com/$GH_USER/add/add.go, with the following:

// Package add adds two numbers
package add
// Add adds two numbers
func Add(a uint32, b uint32) uint32 {
return a + b
}

Now you can build your package with Go like so:

go build github.com/$GH_USER/add

Test your package

Create $GOPATH/src/github.com/$GH_USER/add/add_test.go, with the following:

package addimport (
"testing"
)
func TestAdd(t *testing.T){
result := Add(2, 3)
if result != 5 {
t.Errorf("Expected 5, got %d", result)
}
}

Now we can run those tests using go test

go test github.com/$GH_USER/add

Create an example kernel

$ mkdir -p $GOPATH/src/github.com/$GH_USER/add/kernels/add

Create $GOPATH/src/github.com/$GH_USER/add/kernels/add/main.go with the following:

package mainimport (
// Import the entire framework (including bundled verilog)
_ "sdaccel"
aximemory "axi/memory"
axiprotocol "axi/protocol"
"github.com/<github user name>/add"
)
func Top(
a uint32,
b uint32,
addr uintptr,
// The second set of arguments will be the ports for interacting with 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)
// Calculate the value
val := add.Add(a, b)
// Write it back to the pointer the host requests
aximemory.WriteUInt32(
memWriteAddr, memWriteData, memWriteResp, false, addr, val)
}

This gives us a kernel that depends on our package. Next, we’ll need to vendor our package in order for our kernel to find it. Normally, we’d use other tools for this, but in this case, we can do it by hand.

$ mkdir -p $GOPATH/src/github.com/$GH_USER/add/kernels/add/vendor/github.com/$GH_USER/add
$ cp $GOPATH/src/github.com/$GH_USER/add/add.go $GOPATH/src/github.com/$GH_USER/add/kernels/add/vendor/github.com/$GH_USER/add/add.go

Now we can use reco to check our code.

$ reco -s $GOPATH/src/github.com/$GH_USER/add/kernels/add check

Create a command to run our kernel

First, we’ll create the directory for a new command, called test-add.

$ mkdir -p $GOPATH/src/github.com/$GH_USER/add/kernels/add/cmd/test-add/

Create $GOPATH/src/github.com/$GH_USER/add/kernels/add/cmd/test-add/main.go with the following:

package mainimport (
"encoding/binary"
"fmt"
"os"
"xcl"
)
func main() {
// Allocate a world for interacting with kernels
world := xcl.NewWorld()
defer world.Release()
// Import the 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 a buffer on the FPGA to store the return value of our 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 output buffer
krnl.SetMemoryArg(2, buff)
// Run the kernel with the supplied arguments
krnl.Run(1, 1, 1)
// Decode that byte slice into the uint32 we're expecting
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)
// Exit with an error if the value is not correct
if ret != 3 {
os.Exit(1)
}
}

Now we can simulate this using reco after we create a new project.

$ reco projects create add
$ cd $GOPATH/src/github.com/$GH_USER/add/kernels/add
$ reco projects set add
$ reco test run test-add
2017-10-05 14:37:43| preparing simulation .
2017-10-05 14:37:43| done
2017-10-05 14:37:43| archiving
2017-10-05 14:37:43| done
2017-10-05 14:37:43| uploading ..
2017-10-05 14:37:44| done
2017-10-05 14:37:44| running simulation
2017-10-05 14:37:44|
2017-10-05 14:37:44| you can run "reco simulation log 7767026d-9366-4a7e-b6c7-632c83844e5b" to manually stream logs
2017-10-05 14:37:44| getting simulation details
2017-10-05 14:37:44| status: QUEUED
2017-10-05 14:37:44| this may take several minutes
2017-10-05 14:37:44| waiting for simulation to start ...
$ cd -

Setup Travis CI

Create $GOPATH/src/github.com/$GH_USER/add/.travis.yml with the following:

language: go
go_import_path: github.com/<github user name>/add
go:
- 1.9
install:
- curl -LO https://s3.amazonaws.com/reconfigure.io/reco/releases/reco-v0.2.0-x86_64-linux.zip
- unzip reco-v0.2.0-x86_64-linux.zip
- sudo mv reco /usr/local/bin
script:
- go test github.com/<github user name>/add
- mkdir -p kernels/add/vendor/github.com/<github user name>/add
- cp add.go kernels/add/vendor/github.com/<github user name>/add/add.go
- cd kernels/add && reco check

Now make a new repo on Github named “add”, initialized with a README and an open source license.

$ cd $GOPATH/src/github.com/$GH_USER/add
$ git init
$ git remote add origin git@github.com:$GH_USER/add.git
$ git fetch
$ git checkout -t origin/master

In your browser, visit Travis CI, click on your name, go to Accounts, and enable <github user name>/add

Now we’ll add everything and push

$ git add .travis.yml add.go add_test.go kernels/add/cmd/test-add/main.go kernels/add/main.go
$ git commit -m "Import example program"
$ git push

If you visit your Travis CI dashboard, you should see your project on the left hand side. It should start building shortly, if it hasn’t already. If you’ve done everything correctly, the build should be green, and you’ve published your first usable library for Reconfigure.io.

--

--