From 0aa5dc57041f618a2ddabae19619ca22cfb64cda Mon Sep 17 00:00:00 2001 From: Jonathan Heathcote Date: Mon, 2 Nov 2015 14:29:01 +0000 Subject: [PATCH] Add heat diffusion example application. --- heat_diffusion/Makefile | 195 +++++++++++ heat_diffusion/README.rst | 62 ++++ heat_diffusion/heat.aplx | Bin 0 -> 13784 bytes heat_diffusion/heat.c | 247 ++++++++++++++ heat_diffusion/heat_diffusion_gui.png | Bin 0 -> 31359 bytes heat_diffusion/run.py | 453 ++++++++++++++++++++++++++ index.rst | 1 + 7 files changed, 958 insertions(+) create mode 100644 heat_diffusion/Makefile create mode 100644 heat_diffusion/README.rst create mode 100644 heat_diffusion/heat.aplx create mode 100644 heat_diffusion/heat.c create mode 100644 heat_diffusion/heat_diffusion_gui.png create mode 100644 heat_diffusion/run.py 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 0000000000000000000000000000000000000000..8568e11206ae37c8974dc889b1642726958245bf GIT binary patch literal 13784 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 DOJRh} literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..da460cd5de5531cdbe47ada06e7aa3b6f46943b5 GIT binary patch literal 31359 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 i{RKu?F*x`KBv!w%*zcA62;fHPC-N^