02: Reading and Writing SDRAM

Most interesting SpiNNaker application kernels require some sort of configuration data, and produce result data, which must be loaded and read back from the machine before and after executing respectively. As a result, a typical host program will:

  • Allocate some memory on any SpiNNaker chips where an application kernel is to be loaded
  • Write configuration data into this memory
  • Load and run the application kernel
  • Read and process the result data written into memory by the kernel

To illustrate this process we’ll make a SpiNNaker application kernel which reads pair of 32-bit integers from memory, adds them together, stores the result back into memory and exits.

Much of the code in this example is unchanged from the previous example so we will only discuss the changes.

The source files used in this tutorial can be downloaded below:

Allocating SDRAM from the host

In our application, as in most real world applications, we’ll use the on-chip SDRAM (shared between all cores on a chip) to load our two integers and store the result. By convention, the host program is responsible for allocating space in SDRAM.

The Rig MachineController class provides an sdram_alloc() method which we’ll use to allocate 12 bytes of SDRAM on a SpiNNaker chip. In this example we’ll allocate some SDRAM on chip (0, 0). The first 8 bytes will contain the two numbers to be summed and will be written by our host program. The last four bytes will be written by the SpiNNaker application kernel and will contain the resulting sum.

sdram_addr = mc.sdram_alloc(12, x=0, y=0, tag=1)

The sdram_alloc() method returns the address of a block of SDRAM on chip (0, 0) which was allocated.

We also need to somehow inform the SpiNNaker application kernel of this address. To do this we can use the ‘tag’ argument to identify the allocated memory block. Later, once the application kernel has been loaded it can use sark_tag_ptr() to discover the address of tagged SDRAM blocks. In most applications, memory used by an application running on core 1 is given tag 1, memory for core 2 is given tag 2 and so on. Since an application kernel can discover the core number it is running on using spin1_get_core_id(), the following line gets a pointer to the SDRAM block allocated for a particular core’s application.

  uint32_t *numbers = sark_tag_ptr(spin1_get_core_id(), 0);

Note

Tags are assigned for a single SpiNNaker chip: tag numbers can be re-used on other chips.

Writing SDRAM from the host

After allocating our block of SDRAM we must populate it with the numbers to be added together. In this example, we pick two random numbers and, using Python’s struct module, pack them into 8 bytes.

num_a = random.getrandbits(30)
num_b = random.getrandbits(30)
data = struct.pack("<II", num_a, num_b)

Note

The ‘<’ prefix must be included in the struct format string to indicate that the data should be arranged in the little-endian order used by SpiNNaker.

The write() method of the MachineController is then used to write this value into the first 8 bytes of the SDRAM block we allocated.

mc.write(sdram_addr, data, x=0, y=0)

Warning

The write() method will attempt to perform any write you specify. Due caution should be used to avoid data corruption or illegal memory accesses.

Running the application kernel

With the SDRAM allocated, tagged and populated with data, we can load our application kernel as in the previous example using load_application().

The application kernel adds together the numbers at the memory address discovered by sark_tag_ptr(), writes the result into memory and exits:

  numbers[2] = numbers[0] + numbers[1];

Note

Although SDRAM can be accessed directly like this, ‘real’ application kernels often use DMA requests to avoid blocking on slow memory access.

Reading and writing SDRAM from the host

After waiting for the application kernel to exit, the host can read the answer back using read() and unpack it using Python’s struct module.

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))

As before, the last step is to send a “stop” signal to SpiNNaker using send_signal(). This signal will automatically free all allocated blocks of SDRAM.

In this tutorial we used some fairly low-level APIs for accessing SpiNNaker’s memory. In the next tutorial we’ll use some of Rig’s higher-level APIs to make the process of accessing SpiNNaker’s memory and cleaning up after an application easier and safer. Continue to part 03.