Rig Example Programs¶
This project aims to build up a series of usage examples for the Rig library. This is currently a work-in-progress.
Tutorial series:
Example 01: Hello World¶
In this classic example we make a SpiNNaker application which simply prints “Hello, world!” on one core and then exits.
SpiNNaker application¶
We start by writing the SpiNNaker
application itself which consists of a single call to io_printf
in
hello.c
.
#include "sark.h"
void c_main(void)
{
io_printf(IO_BUF, "Hello, world!\n");
}
This call writes our famous message to the “IO buffer”,
an area of system memory which we can later read back from the host. This is
then compiled using the usual SpiNNaker-tools makefile to produce
hello.aplx
:
$ cd /path/to/spinnaker_tools
$ source ./setup
$ cd -
$ make APP=hello
Note
For those who don’t have the ARM cross-compiler installed a precompiled binary is included with this example.
Host-side application¶
Now that we have our compiled binary we must boot our SpiNNaker machine, load our application onto a core and then read back the IO buffer. We could do this using the ybug command included with the low-level software tools but since we’re building up towards a real-world application, we’ll use Rig. Rig provides a higher-level Python interface to SpiNNaker machines which is easily used as part of a larger program, unlike ybug which is designed for interactive use as a debugger.
Before we start we must install the Rig library. The easiest way to do this is via PyPI using pip:
$ pip install rig
The hello.py
contains a Python program which uses Rig to boot a SpiNNaker
machine, load our application and then print the result. We’ll go through this
line by line below.
All control of a SpiNNaker machine is achieved via a Rig
MachineController
which we import like so:
from rig.machine_control import MachineController
We create an instance with the hostname/IP address set to our SpiNNaker board, taken from the command-line (to avoid having to hard-code things in our program).
import sys
mc = MachineController(sys.argv[1])
Next to boot the machine we use the
boot()
method, taking the width
and height of the machine to boot from the next two command line arguments. For
a SpiNN-2 or SpiNN-3 board these dimensions are 2 and 2. For a SpiNN-5 board
the dimensions are 8 and 8.
mc.boot(int(sys.argv[2]), int(sys.argv[3]))
Next we’ll load our application using the
load_application()
method.
This method loads our application onto core 1 of chip (0, 0), checks it was
loaded successfully and then starts the program executing before returning.
Note that this method can load an application onto many cores at once, hence
the slightly unusual syntax.
mc.load_application("hello.aplx", {(0, 0): {1}})
When a SpiNNaker application’s c_main
function returns, the application
goes into the exit
state. By using
wait_for_cores_to_reach_state()
we can wait for our hello world application to finish executing.
mc.wait_for_cores_to_reach_state("exit", 1)
After our application has exited we can fetch and print out the contents of the
IO buffer for the core we ran our application on to see what it printed using
the get_iobuf()
method. (By
convention Rig uses the name p
– for processor – when identifying cores.)
print(mc.get_iobuf(x=0, y=0, p=1))
As a final step we must send the “stop” signal to SpiNNaker which frees up any resources allocated during the runing of our application. In this case that just means the memory allocated for the IO buffer.
mc.send_signal("stop")
We can finally run our script like so (for a SpiNN-5 board):
$ python hello.py my-spinn-5-board 8 8
Hello, world!
Note that this script can take a little time while the boot is carried out. If your board is already booted, you can comment out the boot line and the script should run almost instantaneously.
Note
Instead of incorporating the boot process into your scripts you can use the
rig-boot
command line utility to boot your machine first like so:
$ rig-boot my-spinn-5-board --spin5
For help see:
$ rig-boot --help
In later examples we’ll use this instead of including the call to
boot()
in our scripts.
Example 02: Reading and Writing SDRAM¶
Most interesting SpiNNaker applications require some sort of configuration data to be loaded onto the machine or produce result data which must be read back from the machine. Typically this is done by allocating and writing to or reading from the shared SDRAM on each SpiNNaker chip. In this example we’ll write a simple SpiNNaker application which, using a single core, adds two numbers loaded in SDRAM and writes the answer back to SDRAM.
Much of the code in this example is unchanged from the previous example so we will only discuss the changes.
Allocating SDRAM¶
When both the host and SpiNNaker need to access the same block of SDRAM, such
as when loading configuration data or reading back results, it is common for
the host to allocate the SDRAM. In this example we’ll allocate some SDRAM on
chip (0, 0). We’ll allocate a total of 12 bytes: 4 bytes (32 bits) each for the
two values we want to be added and another 4 bytes for the result using
sdram_alloc()
:
sdram_addr = mc.sdram_alloc(12, x=0, y=0, tag=1)
The sdram_alloc()
method simply
returns the address of a block of SDRAM on chip 0, 0 which was allocated.
We also need to somehow inform the SpiNNaker application of this address. To do
this we can use the ‘tag’ using the argument to give an identifier to the
allocated memory block. The SpiNNaker application then uses the
sark_tag_ptr
function to look up the address of an SDRAM block with a
specified tag.
uint32_t *numbers = sark_tag_ptr(spin1_get_core_id(), 0);
A tag is a user-defined identifier which must be unique to the SpiNNaker chip and application. By convention the SDRAM allocated for applications running on core 1 are given tag 1, those on core 2 given tag 2 and so on. This means the same application binary can be loaded onto multiple cores which can simply look up their core number to discover their unique SDRAM allocation’s address.
Reading and writing to SDRAM¶
We pick two random numbers to add together and write them to the SDRAM we just
allocated. Note that we must pack our values into bytes using Python’s
struct
module. Since SpiNNaker is little-endian we must be careful
to use the ‘<’ format string.
num_a = random.getrandbits(30)
num_b = random.getrandbits(30)
data = struct.pack("<II", num_a, num_b)
Next we simply write the bytes to the allocated block of SDRAM using
write()
.
mc.write(sdram_addr, data, x=0, y=0)
After we’ve allocated and writen our config data to SDRAM we can load our application as usual. On the C side of our application, the SDRAM can be accessed like any other memory.
numbers[2] = numbers[0] + numbers[1];
Warning
Though SpiNNaker’s SDRAM can be accessed just like normal memory within a SpiNNaker application, this comes with a significant performance penalty; ‘real’ applications should use DMA to access SDRAM.
Once the application has exited, the host can read the answer back using
read()
.
result_data = mc.read(sdram_addr + 8, 4, x=0, y=0)
result, = struct.unpack("<I", result_data)
print("{} + {} = {}".format(num_a, num_b, result))
Finally, as in our last example we must send the stop
signal using
send_signal()
to free up all
SpiNNaker resources. This is important since until the ‘stop’ signal is sent
the SDRAM and tag number will remain allocated and may not be reallocated.
mc.send_signal("stop")
Example 03: Reading and Writing SDRAM - Improved¶
This example is functionally identical to the previous version but changes some of the host-side code to use some of Rig’s more advanced features which help make the program more robust and concise. The SpiNNaker application remains unchanged.
Reliably stopping applications¶
Now that we’re starting to allocate machine resources and write more complex
programs it is important to be sure that the stop
signal is sent to the
machine at the end of our application’s execution, especially if our host-side
application crashes and exits prematurely. To aid with this, the
MachineController
class provides an
application()
context manager
which will send the stop
signal however the block is exited. In our
example, we just put the main body of our application logic in a with
block:
with mc.application():
File-like memory access¶
When working with SDRAM it can be easy to accidentally access memory outside
the range of an allocated buffer. To provide safer and more conveninent SDRAM
access the
sdram_alloc_as_filelike()
method produces a file-like
MemoryIO
object. This
object can then be used just like a conventional file using
read()
,
write()
and
seek()
methods. All
writes and reads to the file will be bounded to the allocated block of SDRAM
preventing accidental corruption of memory. Additionally, users of an
allocation need not know anything about the chip or address of the allocation
and in fact may be oblivious to the fact that they’re using anything other than
a normal file.
sdram = mc.sdram_alloc_as_filelike(12, x=0, y=0, tag=1)
sdram.write(data)
result_data = sdram.read(4)
Like files, reads and writes occur immediately after the previous read and
write and seek()
must
be used to cause a read/write to occur at a different location. Note that in
this case since the result value is written immediately after the two input
values we do no need to seek before reading.
More complex example programs:
Simple Digital Circuit Simulator¶
This example application demonstrates how a simple yet functional gate-level logic circuit simulation SpiNNaker application can be built using Rig.
In this application, each individual gate is modelled by an application core in
SpiNNaker. Connections between the gates are modelled by transmitting multicast
packets. Each core simply recomputes and transmits its value every millisecond
based on the most recently received input values. Some applications may also be
used to playback predefined stimulus signals or record the state of signals as
the simulation progresses. A simple Rig-based program (circuit_sim
) runs
on the host taking care of mapping a description of a circuit into applications
and routes, loading and configuring the machine and retrieving results.
Though this example is very simple (and makes incredibly wasteful use of SpiNNaker’s resources), its operation is analogous to the more complex task of building neural simulation software for SpiNNaker. Just as in many neural modelling applications we translate a high-level description of a problem into a graph of application cores connected by routes, map this onto a SpiNNaker machine and handle basic configuration and result retrieval.
Usage¶
You’ll need to install some Python dependencies before you start:
$ pip install rig numpy matplotlib bitarray
Then make sure your board has been booted, e.g.:
$ rig-boot spinnaker-board-hostname --spin5
Two example circuits are provided which can be executed using one of:
$ python example_xor.py spinnaker-board-hostname
$ python example_flipflop.py spinnaker-board-hostname
These scripts describe (at a high level) a circuit and then run a simulation on
an attached SpiNNaker board and then plot the results using matplotlib. The
example_xor.py
script describes an XOR gate implemented using
sum-of-products and produces a plot which looks like the following where the
top two waveforms are the inputs to the XOR and the bottom waveform is the
output.

The circuit_sim.py
file contains the full Rig-based program which maps the
high level circuit description onto a SpiNNaker board and runs the model. This
module has a set of complementary tests which demonstrate how to test host-side
applications without the need for a SpiNNaker board to be present. The test
suite’s dependencies can be installed and the tests executed using:
$ pip install pytest mock
$ py.test test_circuit_sim.py
The spinnaker_applications
directory contains the C sources and compiled
binaries for the SpiNNaker applications which implement the SpiNNaker-side of
the simulation. See the subsection below for details of how to recompile these
applications.
SpiNNaker Binary Compilation¶
Precompiled binaries are included in the repository so you don’t need to compile the binaries in order to play with the program as it stands. If you want to compile the SpiNNaker binaries, after sourcing the setup script in the official spinnaker low-level software release, execute the following quick-hack snippet:
cd spinnaker_applications
for f in *.c; do
make APP=${f%.c} clean; make APP=${f%.c};
done