Coding Style Guide

This style guide sets out some best practices for writing programs to be used with Reconfigure.io.

Template

We provide a stripped down version of our project code to help you get started creating your own projects. You can find the template here: examples/template. For more information on using the template, see Tutorial 4 – Structure and Communication.

Code Organization

Splitting code between a CPU and FPGA usually involves a separation that’s different to what you would expect when using just a CPU. A CPU is flexible and good at sequential things, whereas the FPGA is good for static things that lend themselves to parallelism. The best separation will depend on you application, but dividing based upon the relative strengths of CPUs and FPGAs is generally a great place to start. For instance, for data processing applications, a natural separation would be to do data preprocessing and postprocessing on the CPU, while having the FPGA do a calculation intensive loop.

The FPGA

Break out small functions

When using graph generation to inspect performance, it’s generally better to break out small functions. This allows you to better reason about their parallel aspects, and then embed them into a larger program. For example, if (a * b) + c is in an inner loop of your application, breaking it out into the below function will help you see its performance in isolation.

For example:

1
2
3
func MultiplyAndAdd(a uint, b uint, c uint) uint {
   return (a * b) + c
}

Use goroutines for small scale parallelism

The compiler performs dependency analysis, and will parallelize statements that don’t have a strict dependency on each other. In some cases, it will enforce an unnecessary sequential dependency. When you find these using the graph generation, you can use goroutines and channels to force parallelism.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
// If this for some reason sequentializes the calls to Foo & Bar
func Baz(a int, b int) int {
   return Foo(a) + Bar(a)
}

// You can force it to run them in parallel using goroutines
// Here's a rewritten version of the function to force
// the functions to be evaluated in parallel
func Baz(a int, b int) int {
   tmp1 := make(chan int)
   go func(){
     tmp1 <- Foo(a)
   }()

   tmp2 := make(chan int)
   go func(){
     tmp2 <- Bar(b)
   }()

   return (<-tmp1) + (<-tmp2)
}

Unroll loops

For small, often used inner loops, it’s best to unroll them to ensure parallel processing. In the below example, the loop version will take an order of magnitude longer to run than the unrolled version.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
func Add4Loop(a [4]int) int {
   sum := 0
   for i := 0; i < 4; i++ {
       sum += a[i]
   }
   return sum
}

func Add4Unrolled(a [4]int) int {
   return (a[0] + a[1]) + (a[2] + a[3])
}