Skip to content

Instantly share code, notes, and snippets.

@joshbohde joshbohde/testing.md Secret
Last active Oct 5, 2017

Embed
What would you like to do?

Write your first Go package for FGPAs

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 FGPAs
  • 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 run 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 add

import (
    "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 main

import (
    // 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 out code.

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

Create a command to run our kernel

$ 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 main

import (
	"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.1.0-x86_64-linux.zip
- unzip reco-v0.1.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, and 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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.