diff --git a/heat_diffusion/Makefile b/heat_diffusion/Makefile new file mode 100644 index 0000000..57f0c0c --- /dev/null +++ b/heat_diffusion/Makefile @@ -0,0 +1,195 @@ +##------------------------------------------------------------------------------ +## +## Makefile Makefile for a simple SpiNNaker application +## +## Copyright (C) The University of Manchester - 2013 +## +## Author Steve Temple, APT Group, School of Computer Science +## +## Email temples@cs.man.ac.uk +## +##------------------------------------------------------------------------------ + +# Makefile for a simple SpiNNaker application. This will compile +# a single C source file into an APLX file which can be loaded onto +# SpiNNaker. It will link with either a 'bare' SARK library or a +# combined SARK/API library. + +# The options below can be overridden from the command line or via +# environment variables. For example, to compile and link "my_example.c" +# with the ARM tools and generate ARM (as opposed to Thumb) code +# +# make APP=my_example GNU=0 THUMB=0 + +# Name of app (derived from C source - eg sark.c) + +APP := sark + +# Configuration options + +# Set to 1 for GNU tools, 0 for ARM + +GNU := 1 + +# Set to 1 if using SARK/API (0 for SARK) + +API := 1 + +# Set to 1 to make Thumb code (0 for ARM) + +THUMB := 1 + +# Prefix for GNU tool binaries + +GP := arm-none-eabi + +# Set to 1 if making a library (advanced!) + +LIB := 0 + +# If SPINN_DIRS is defined, use that to find include and lib directories +# otherwise look two levels up + +ifdef SPINN_DIRS + LIB_DIR := $(SPINN_DIRS)/lib + INC_DIR := $(SPINN_DIRS)/include +else + LIB_DIR := ../../lib + INC_DIR := ../../include +endif + +#------------------------------------------------------------------------------- + +# Set up the various compile/link options for GNU and ARM tools + +# GNU tool setup + +ifeq ($(GNU),1) + AS := $(GP)-as --defsym GNU=1 -mthumb-interwork -march=armv5te + + CA := $(GP)-gcc -c -Os -mthumb-interwork -march=armv5te -std=gnu99 \ + -I $(INC_DIR) + + CT := $(CA) -mthumb -DTHUMB + +ifeq ($(LIB),1) + CFLAGS += -fdata-sections -ffunction-sections +endif + +ifeq ($(API),1) +# LIBRARY := -L$(LIB_DIR) -lspin1_api + LIBRARY := $(LIB_DIR)/libspin1_api.a +else +# LIBRARY := -L$(LIB_DIR) -lsark + LIBRARY := $(LIB_DIR)/libsark.a +endif + + SCRIPT := $(LIB_DIR)/sark.lnk + + LD := $(GP)-gcc -T$(SCRIPT) -Wl,-e,cpu_reset -Wl,--gc-sections -Wl,--use-blx + + AR := $(GP)-ar -rcs + OC := $(GP)-objcopy + OD := $(GP)-objdump -dxt > $(APP).txt + +# ARM tool setup + +else + AS := armasm --keep --cpu=5te --apcs /interwork + + CA := armcc -c --c99 --cpu=5te --apcs /interwork --min_array_alignment=4 \ + -I $(INC_DIR) + + CT := $(CA) --thumb -DTHUMB + +ifeq ($(LIB),1) + CFLAGS += --split_sections +endif + +ifeq ($(API),1) + LIBRARY := $(LIB_DIR)/spin1_api.a +else + LIBRARY := $(LIB_DIR)/sark.a +endif + + SCRIPT := $(LIB_DIR)/sark.sct + + LD := armlink --scatter=$(SCRIPT) --remove --entry cpu_reset + + AR := armar -rcs + OC := fromelf + OD := fromelf -cds --output $(APP).txt + +endif + +ifeq ($(THUMB),1) + CC := $(CT) +else + CC := $(CA) +endif + +CAT := \cat +RM := \rm -f +LS := \ls -l + +#------------------------------------------------------------------------------- + +# Build the application + +# List of objects making up the application. If there are other files +# in the application, add their object file names to this variable. + +OBJECTS := $(APP).o + + +# Primary target is an APLX file - built from the ELF + +# 1) Create a binary file which is the concatenation of RO and RW sections +# 2) Make an APLX header from the ELF file with "mkaplx" and concatenate +# that with the binary to make the APLX file +# 3) Remove temporary files and "ls" the APLX file + +$(APP).aplx: $(APP).elf +ifeq ($(GNU),1) + $(OC) -O binary -j RO_DATA -j .ARM.exidx $(APP).elf RO_DATA.bin + $(OC) -O binary -j RW_DATA $(APP).elf RW_DATA.bin + mkbin RO_DATA.bin RW_DATA.bin > $(APP).bin +else + $(OC) --bin --output $(APP).bin $(APP).elf +endif + mkaplx $(APP).elf | $(CAT) - $(APP).bin > $(APP).aplx + $(RM) $(APP).bin RO_DATA.bin RW_DATA.bin + $(LS) $(APP).aplx + + +# Build the ELF file + +# 1) Make a "sark_build.c" file containing app. name and build time +# with "mkbuild" and compile it +# 2) Link application object(s), build file and library to make the ELF +# 3) Tidy up temporaries and create a list file + +$(APP).elf: $(OBJECTS) $(SCRIPT) $(LIBRARY) + mkbuild $(APP) > sark_build.c + $(CC) sark_build.c + $(LD) $(LFLAGS) $(OBJECTS) sark_build.o $(LIBRARY) -o $(APP).elf + $(RM) sark_build.c sark_build.o + $(OD) $(APP).elf + + +# Build the main object file. If there are other files in the +# application, place their build dependencies below this one. + +$(APP).o: $(APP).c $(INC_DIR)/spinnaker.h $(INC_DIR)/sark.h \ + $(INC_DIR)/spin1_api.h + $(CC) $(CFLAGS) $(APP).c + + +# Tidy and cleaning dependencies + +tidy: + $(RM) $(OBJECTS) $(APP).elf $(APP).txt +clean: + $(RM) $(OBJECTS) $(APP).elf $(APP).txt $(APP).aplx + +#------------------------------------------------------------------------------- diff --git a/heat_diffusion/README.rst b/heat_diffusion/README.rst new file mode 100644 index 0000000..d8c6adb --- /dev/null +++ b/heat_diffusion/README.rst @@ -0,0 +1,62 @@ +Heat Diffusion Model +==================== + +This application uses SpiNNaker to model heat diffusion in a 2D surface and +demonstrates how data can be extracted from SpiNNaker and plotted +interactively. + +Each SpiNNaker core models a single square fragment of the 2D surface. Every +1ms, cores multicast their current temperature to their immediate neighbours in +2D space and update their own temperatures according to the reported +temperatures of the cores around them. Every 64 ms, every chip reports the +current temperature of all cores on its chip back to the host in an SDP packet. +The application also listens for SDP packets which allow the host to set the +temperature of the periphery of the simulation. + +The host application is responsible for loading the SpiNNaker application, +generating appropriate multicast routes, plotting the temperatures received +from the machine and allowing the user to set the temperature of the boarders +of the machine. + +`Source code on GitHub +`_ + +Usage +----- + +You'll need to install some Python dependencies before you start:: + + $ pip install rig numpy matplotlib + +Then make sure your board has been booted, e.g.:: + + $ rig-boot spinnaker-board-hostname --spin5 + +You can then start the example using:: + + $ python run.py spinnaker-board-hostname + +Or if you want to specify your own constant of diffusivity:: + + $ python run.py spinnaker-board-hostname 0.5 + +Once the application has been loaded, a GUI should launch in which looks like +the screenshot below (taken using a single SpiNN-5 board). Click on the sliders +to adjust the temperature around the periphery of the model. Closing the window +will stop the application runnning. + +.. image:: heat_diffusion_gui.png + :alt: A screenshot of the heat diffusion GUI. + + +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 simply compile ``heat.c`` as +usual:: + + make APP=heat + diff --git a/heat_diffusion/heat.aplx b/heat_diffusion/heat.aplx new file mode 100644 index 0000000..8568e11 Binary files /dev/null and b/heat_diffusion/heat.aplx differ diff --git a/heat_diffusion/heat.c b/heat_diffusion/heat.c new file mode 100644 index 0000000..f2e5b7a --- /dev/null +++ b/heat_diffusion/heat.c @@ -0,0 +1,247 @@ +/** + * Heat diffusion model SpiNNaker application. + */ + +#include +#include + +#include "spin1_api.h" + +#define DEBUG + +// XXX: Will be included as part of next version of SCAMP/SARK +// Get a pointer to a tagged allocation. If the "app_id" parameter is zero +// uses the core's app_id. +void *sark_tag_ptr (uint tag, uint app_id) +{ + if (app_id == 0) + app_id = sark_vec->app_id; + + return (void *) sv->alloc_tag[(app_id << 8) + tag]; +} + + +//////////////////////////////////////////////////////////////////////////////// +// Heat diffusion model variables/constants. +//////////////////////////////////////////////////////////////////////////////// + +// The number of neighbouring cells to this cell +#define NUM_NEIGHBOURS 4 + +// The routing key to use when multicasting this cell's temperature +uint32_t temperature_key; + +// The routing keys used to indicate the temperature of the four neighbouring +// cells. +uint32_t neighbour_keys[NUM_NEIGHBOURS]; + +// The current temperature of this cell as an s15.16 fixed point +// number. +volatile int32_t temperature; + +// The last known temperature of the neighbours of this cell as s15.16 fixed +// point numbers. +volatile int32_t neighbour_temperatures[NUM_NEIGHBOURS]; + +// The constant of thermal diffusivity as an s15.16 fixed point number. +volatile int32_t alpha; + +//////////////////////////////////////////////////////////////////////////////// +// Application variables/constants +//////////////////////////////////////////////////////////////////////////////// + +// The core number of this chip +uint32_t core_id; + +// The shared memory block where all cores report their most recent +// temperatures. +volatile uint32_t *reported_temperatures; + +// The length of the reported_temperatures array or zero if this core is not +// responsible for reporting temperature. +uint32_t num_reported_temperatures; + +// The index of this core's slot in the reported_temperatures array. +uint32_t reported_temperature_slot; + +// The number of msec over which all temperature reports are sent to the host +// via SDP +#define REPORT_PERIOD 64 + +// The phase (0-(REPORT_PERIOD-1)) in msec at which this chip will report back +// to the host. +uint32_t report_phase; + +// The temperature reporting message +sdp_msg_t report_msg; + +//////////////////////////////////////////////////////////////////////////////// +// Implementation +//////////////////////////////////////////////////////////////////////////////// + +/** + * Compute temperature change and multicast it to our immediate neighbours. + */ +void update_temperature(void) +{ + // Compute temperature change (note we also scale due to fixed-point) + int32_t mean_neighbour_difference = 0; + for (int i = 0; i < NUM_NEIGHBOURS; i++) + { + mean_neighbour_difference += neighbour_temperatures[i] - temperature; + } + mean_neighbour_difference /= NUM_NEIGHBOURS; + temperature += (int32_t)((((int64_t)mean_neighbour_difference) * ((int64_t)alpha)) >> 16); + + // Transmit the new temperature to neighbours + spin1_send_mc_packet(temperature_key, temperature, WITH_PAYLOAD); +} + +/** + * Report the current temperature back to the host. If not the reporting core, + * this just means placing the current temperature in shared memory. + */ +void report_temperature(uint32_t time) +{ + // Update the current temperature in shared memory + reported_temperatures[reported_temperature_slot] = temperature; + + // Send the temperature back to the host + if (num_reported_temperatures && ((time % REPORT_PERIOD) == report_phase)) + { + // Send reports back to the host via the nearest Ethernet chip using IPTag + // 1 + report_msg.tag = 1; + report_msg.dest_port = PORT_ETH; + report_msg.dest_addr = sv->eth_addr; + + // Indicate the packet's origin as this chip/core + report_msg.flags = 0x07; + report_msg.srce_port = spin1_get_core_id(); + report_msg.srce_addr = spin1_get_chip_id(); + + // Append the latest temperature info to the message + int len = num_reported_temperatures * sizeof(reported_temperatures[0]); + spin1_memcpy(report_msg.data, (void *)reported_temperatures, len); + report_msg.length = sizeof (sdp_hdr_t) + sizeof (cmd_hdr_t) + len; + + // and send it with a 100ms timeout + spin1_send_sdp_msg(&report_msg, 100); + } +} + +/** + * Timer tick callback. + */ +void on_timer_tick(uint time, uint arg2) +{ + update_temperature(); + report_temperature(time); +} + + +/** + * MC packet arrived callback. + */ +void on_mc_packet(uint key, uint payload) +{ + // Handle neighbour temperature reports. + for (int i = 0; i < NUM_NEIGHBOURS; i++) + { + if (key == neighbour_keys[i]) + { + neighbour_temperatures[i] = payload; + } + } +} + +/** + * An SDP packet arrived from host, these simply trigger the sending of an MC + * packet with the key and payload specified in the SDP packet. + */ +void on_sdp_from_host(uint mailbox, uint port) +{ + sdp_msg_t *msg = (sdp_msg_t *)mailbox; + if (msg->cmd_rc == 0) // Send MC packet command + { + #ifdef DEBUG + io_printf(IO_BUF, + "Host requested MC packet with key %08x and payload %08x\n", + msg->arg1, + msg->arg2); + #endif + spin1_send_mc_packet(msg->arg1, msg->arg2, WITH_PAYLOAD); + } + spin1_msg_free(msg); +} + + +void c_main(void) +{ + core_id = spin1_get_core_id(); + + // SDRAM tag 0 contains the shared temperature reporting memory + reported_temperatures = sark_tag_ptr(0xFF, 0); + + // SDRAM tag core_id contains the configuration options for this core. + struct { + // 0 if no the reporting core, an integer giving the number of temperatures + // to record otherwise. + uint32_t num_reported_temperatures; + + // The constant of thermal diffusivity + uint32_t alpha; + + // The routing key to use for this node + uint32_t temperature_key; + + // The routing keys used by the immediate neighbours of this node + uint32_t neighbour_keys[NUM_NEIGHBOURS]; + } *config_data = sark_tag_ptr(core_id, 0); + + // Copy provided config parameters + num_reported_temperatures = config_data->num_reported_temperatures; + alpha = config_data->alpha; + temperature_key = config_data->temperature_key; + for (int i = 0; i < NUM_NEIGHBOURS; i++) + { + neighbour_keys[i] = config_data->neighbour_keys[i]; + } + + // Each core gets a slot in the reported temperatures array + reported_temperature_slot = core_id - 1; + + // The reporting phase of this chip will be simply its index in its 8x8 + // segment of the machine. + uint32_t chip_id = spin1_get_chip_id(); + report_phase = ( (((chip_id >> 8) & 0x7) << 3) + | (((chip_id >> 0) & 0x7) << 0)); + + // Initialise temperatures + temperature = 0; + for (int i = 0; i < NUM_NEIGHBOURS; i++) + { + neighbour_temperatures[i] = 0; + } + + #ifdef DEBUG + io_printf(IO_BUF, "reported_temperatures: %08x\n", reported_temperatures); + io_printf(IO_BUF, "reported_temperature_slot: %d\n", reported_temperature_slot); + io_printf(IO_BUF, "num_reported_temperatures: %d\n", num_reported_temperatures); + io_printf(IO_BUF, "alpha: %08x\n", alpha); + io_printf(IO_BUF, "temperature_key: %08x\n", temperature_key); + for (int i = 0; i < NUM_NEIGHBOURS; i++) + { + io_printf(IO_BUF, "neighbour_keys[%d]: %08x\n", i, neighbour_keys[i]); + } + #endif + + // Setup callbacks + spin1_set_timer_tick(1000); // 1 ms + spin1_callback_on(MCPL_PACKET_RECEIVED, on_mc_packet, -1); + spin1_callback_on(TIMER_TICK, on_timer_tick, 0); + spin1_callback_on(SDP_PACKET_RX, on_sdp_from_host, 0); + + // Go! + spin1_start(SYNC_WAIT); +} diff --git a/heat_diffusion/heat_diffusion_gui.png b/heat_diffusion/heat_diffusion_gui.png new file mode 100644 index 0000000..da460cd Binary files /dev/null and b/heat_diffusion/heat_diffusion_gui.png differ diff --git a/heat_diffusion/run.py b/heat_diffusion/run.py new file mode 100644 index 0000000..c1a932a --- /dev/null +++ b/heat_diffusion/run.py @@ -0,0 +1,453 @@ +#!/usr/bin/env python + +""" +A SpiNNaker-based heat diffusion demo application. + +After booting your SpiNNaker machine:: + + $ python run.py hostname + +To specify an alternative constant of diffusivity:: + + $ python run.py hostname 0.5 +""" + +from collections import namedtuple + +import socket + +import struct + +import threading + +from functools import partial + +from six import itervalues, iteritems + +import numpy as np + +import matplotlib.pyplot as plt +from matplotlib.widgets import Slider + +from rig.type_casts import float_to_fix, NumpyFixToFloatConverter +from rig.geometry import spinn5_eth_coords +from rig.machine_control import MachineController +from rig.machine_control.packets import SCPPacket +from rig.machine_control.consts import SCP_PORT +from rig.machine import Cores +from rig.netlist import Net +from rig.place_and_route import route +from rig.place_and_route.utils import build_routing_tables + + +# Conversion to signed 15.16 fixed-point +float_to_s_15_16 = float_to_fix(signed=True, n_bits=32, n_frac=16) + +# Conversion from a Numpy array of signed 15.16 fixed-point numbers to a Numpy +# array of floating point equivalents. +np_s_15_16_to_np_float = NumpyFixToFloatConverter(16) + +# The UDP port to use for receiving temperature reports from the machine. +REPORT_PORT = 50007 + +# The (dx, dy) to each immediate neighbour of a cell. +NEIGHBOUR_DIRECTIONS = { + (1, 0): "East", + (-1, 0): "West", + (0, 1): "North", + (0, -1): "South", +} + +Cell = namedtuple("Cell", "x,y,chip,core") +"""An object which represents a Cell. + +Simply contains the coordinates of the cell in the heat map and the chip/core +it is simulated by. +""" + +def init_cells(machine): + """Create a Cell object for each cell in the simulation. + + Returns + ------- + (cells, chip_cell_lookup) + cells is a dictionary {(x, y): cell, ...} giving the Cell object at + each coordinate in the heatmap. + + chip_cell_lookup is a dictionary {chip: [cell, ...], ...} giving the + cells on each core of a SpiNNaker chip (identified as an (x, y) tuple). + Element 0 in the list of cells corresponds with core 1 in a SpiNNaker + machine. + """ + cells = {} + chip_cell_lookup = {} + for chip in machine: + chip_cell_lookup[chip] = [] + # Each chip will hosts a 4x4 set of cells, if possible. If 16 + # application cores are unavailable, just make as many as possible. + for core in range(min(16, machine[chip][Cores] - 1)): + x = (chip[0] * 4) + (core % 4) + y = (chip[1] * 4) + (core // 4) + cell = Cell(x, y, chip, core + 1) + + cells[(x, y)] = cell + chip_cell_lookup[chip].append(cell) + + return cells, chip_cell_lookup + + +def cell_neighbours(cell): + """Iterate over the coordinates of a cell's neighbours.""" + for dx, dy in NEIGHBOUR_DIRECTIONS: + yield (cell.x + dx, cell.y + dy) + + +def create_nets(cells): + """Create nets between the cells over which temperature updates will be + sent. + + Returns + ------- + cell_nets : {cell: net, ...} + For each cell, gives the net used to multicast its temperature to its + neighbours. + edge_nets : {edge: net, ...} + For each edge direction in NEIGHBOUR_DIRECTIONS, gives the net which + connects from the cell (0, 0) to any disconnected edges in that + direction. This net is used for setting the edge temperatures. + cell_neighbour_net_lookup : {(x, y): {(dx, dy): net, ...}, ...} + For each cell, gives the net connecting to each of its neighbours used + to receive temperature updates. + """ + cell_nets = {} + + # Connect each cell to its neighbours + for cell in itervalues(cells): + cell_nets[cell] = Net(cell, [cells[n] + for n in cell_neighbours(cell) + if n in cells]) + + # For cells with no neighbour available in each of the directions, we + # connect them to (0, 0) which can then be used to allow the host to set + # border temperatures. + edge_nets = { + (dx, dy): Net(cells[(0, 0)], + [c for (x, y), c in iteritems(cells) + if (x + dx, y + dy) not in cells]) + for (dx, dy) in NEIGHBOUR_DIRECTIONS + } + + # A lookup {(x, y): {(dx, dy): net}, ...} giving the nets connected to each + # neighbour of a cell + cell_neighbour_net_lookup = { + (x, y): { + (dx, dy): (cell_nets[cells[(x + dx, y + dy)]] + if (x + dx, y + dy) in cells + else edge_nets[(dx, dy)]) + for (dx, dy) in NEIGHBOUR_DIRECTIONS + } for (x, y) in cells + } + + return cell_nets, edge_nets, cell_neighbour_net_lookup + + +def generate_routing_tables(cells, cell_nets, edge_nets, machine): + """Generate routing tables for the application. + + Returns + ------- + net_keys, routing_tables + `net_keys` is a dict {net: (key, mask), ...} giving the unique key and + mask given to each net. + + `routing_tables` gives the automatically generated routing tables in + the standard structure used by rig. + """ + # Assign each net a unique routing key sequentially + nets = list(itervalues(cell_nets)) + list(itervalues(edge_nets)) + net_keys = {n: (i, 0xFFFFFFFF) for i, n in enumerate(nets)} + + # Perform manual placement of each cell + vertices_resources = {c: {Cores: 1} for c in itervalues(cells)} + placements = {c: c.chip for c in itervalues(cells)} + allocations = {c: {Cores: slice(c.core, c.core + 1)} + for c in itervalues(cells)} + + # Since placement/allocation has been done manually already there's no need + # for a ReserveResourceConstraint to reserve a core for the monitor (which + # only affects placement and allocation) + constraints = [] + + # Perform automatic routing + routes = route(vertices_resources, nets, machine, constraints, + placements, allocations) + routing_tables = build_routing_tables(routes, net_keys) + + return net_keys, routing_tables + + +def setup_iptags(mc, machine, addr, port): + """Set up an IP tag on each Ethernet connected chip to point packets back + to this machine. + """ + # Configure the IP tag on all Ethernet connected chips + for x, y in spinn5_eth_coords(machine.width, machine.height): + mc.iptag_set(1, addr, port, x, y) + + +def load_sdram(mc, chip_cell_lookup, alpha, + net_keys, cell_nets, cell_neighbour_net_lookup): + """Allocate and initialise all shared memory and configuration data.""" + for chip, chip_cells in iteritems(chip_cell_lookup): + # Allocate space for the shared memory for communicating cell + # temperatures on chip + mc.sdram_alloc(4 * len(chip_cells), + x=chip[0], y=chip[1], tag=0xFF, + clear=True) + + # Generate and write configuration data for each cell + for num, cell in enumerate(chip_cells): + sdram = mc.sdram_alloc_as_filelike( + 4 * (3 + len(NEIGHBOUR_DIRECTIONS)), + x=chip[0], y=chip[1], tag=cell.core) + sdram.write(struct.pack( + "