Skip to content

Commit 1e34d1e

Browse files
committed
First go at the synapse filter code
1 parent 53e63ae commit 1e34d1e

File tree

1 file changed

+331
-0
lines changed

1 file changed

+331
-0
lines changed
Lines changed: 331 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,331 @@
1+
/*
2+
* Copyright (c) 2026 The University of Manchester
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
/*! \file
18+
* \brief A filter binary that filters incoming spikes based on a bitfield
19+
*/
20+
#include <simulation.h>
21+
#include <data_specification.h>
22+
#include <filter_info.h>
23+
#include <circular_buffer.h>
24+
#include <wfi.h>
25+
#include <population_table/population_table.h>
26+
27+
enum {
28+
29+
// This is the system region
30+
FILTER_REGION_SYSTEM = 0,
31+
32+
// This is the configuration, as detailed below as filter_config_t
33+
FILTER_REGION_CONFIG = 1,
34+
35+
// This is the bitfield region, holding a bitfield for each incoming key
36+
FILTER_REGION_BITFIELDS = 2,
37+
38+
// This is the master population table region
39+
FILTER_REGION_POP_TABLE = 3,
40+
41+
// Provenance data region
42+
FILTER_REGION_PROVENANCE = 4
43+
} filter_regions_e;
44+
45+
typedef struct {
46+
// The mask to extract the application id from the incoming key
47+
uint32_t app_id_mask;
48+
// The shift to extract the application id from the incoming key
49+
uint32_t app_id_shift;
50+
// The minimum application id to accept
51+
uint32_t app_id_min;
52+
// The maximum application id to accept
53+
uint32_t app_id_max;
54+
// The size of input queue to use
55+
uint32_t input_queue_size;
56+
// The number of different targets to send to, round robin
57+
uint32_t n_targets;
58+
// The keys for each of the targets
59+
uint32_t send_keys[];
60+
} filter_config_t;
61+
62+
typedef struct {
63+
//! The number of spikes received from the network
64+
uint32_t n_spikes_received;
65+
//! The number of spikes forwarded on to a core
66+
uint32_t n_spikes_forwarded;
67+
//! The number of spikes dropped due to invalid application ID (should be 0)
68+
uint32_t n_spikes_invalid_app_id;
69+
//! The number of times the spike input queue was full (these are lost)
70+
uint32_t n_times_queue_overflowed;
71+
} filter_provenance_t;
72+
73+
typedef struct {
74+
//! The mask to get the source-core-local neuron ID.
75+
//! This can be computed from master_population_table_entry
76+
//! ~(mask | (core_mask << core_shift))
77+
uint32_t mask: 12;
78+
//! Flag to indicate if the filter is redundant.
79+
//! This can be copied from filter_info_t.all_ones field
80+
uint32_t all_ones: 1;
81+
//! The number of bits of key used for colour (0 if no colour).
82+
//! This can be copied from master_population_table_entry.n_colour_bits
83+
uint32_t n_colour_bits: 3;
84+
//! The mask to apply to the key once shifted to get the core index.
85+
//! This can be copied from master_population_table_entry.core_mask
86+
uint32_t core_mask: 16;
87+
//! The shift to apply to the key to get the core part
88+
//! This can be copied from master_population_table_entry.mask_shift
89+
uint32_t core_shift: 16;
90+
//! The number of neurons per core
91+
//! This can be copied from master_population_table_entry.n_neurons
92+
uint32_t n_neurons: 16;
93+
//! The bit field itself (note bit_field_t is a pointer type)
94+
//! This can be copied from filter_info_t.data
95+
bit_field_t data;
96+
} bit_field_filter_info_t;
97+
98+
static filter_config_t *config;
99+
100+
static circular_buffer input_queue;
101+
102+
static bit_field_filter_info_t *filters;
103+
104+
static filter_provenance_t prov;
105+
106+
static uint32_t next_target = 0;
107+
108+
static volatile bool running = false;
109+
110+
static inline bool check_app_id(uint32_t spike, uint32_t *app_id) {
111+
*app_id = (spike & config->app_id_mask) >> config->app_id_shift;
112+
return (app_id <= config->app_id_max) && (app_id >= config->app_id_min);
113+
}
114+
115+
//! \brief Get the source core index from a spike
116+
//! \param[in] filter: The filter info for the spike
117+
//! \param[in] spike: The spike received
118+
//! \return the source core index in the list of source cores
119+
static inline uint32_t get_core_index(bit_field_filter_info_t filter,
120+
spike_t spike) {
121+
return (spike >> filter.core_shift) & filter.core_mask;
122+
}
123+
124+
//! \brief Get the total number of neurons on cores which come before this core
125+
//! \param[in] filter: The filter info for the spike
126+
//! \param[in] spike: The spike received
127+
//! \return the base neuron number of this core
128+
static inline uint32_t get_core_sum(bit_field_filter_info_t filter,
129+
spike_t spike) {
130+
return get_core_index(filter, spike) * filter.n_neurons;
131+
}
132+
133+
//! \brief Get the neuron id of the neuron on the source core
134+
//! \param[in] filter: the filter info for the spike
135+
//! \param[in] spike: the spike received
136+
//! \return the source neuron id local to the core
137+
static inline uint32_t get_local_neuron_id(bit_field_filter_info_t filter,
138+
spike_t spike) {
139+
return spike & filter.mask;
140+
}
141+
142+
static inline bool accepted(uint32_t app_id, uint32_t spike) {
143+
uint32_t pos = app_id - config->app_id_min;
144+
bit_field_t bit_field = filters[pos].data;
145+
if (bit_field == NULL) {
146+
prov.n_spikes_invalid_app_id += 1;
147+
return false;
148+
}
149+
if (filters[pos].all_ones) {
150+
return true;
151+
}
152+
uint32_t neuron_id = get_core_sum(filters[pos], spike)
153+
+ get_local_neuron_id(filters[pos], spike);
154+
return bit_field_get(bit_field, neuron_id);
155+
}
156+
157+
static inline uint32_t get_key() {
158+
uint32_t target = next_target;
159+
next_target = (next_target + 1);
160+
if (next_target >= config->n_targets) {
161+
next_target = 0;
162+
}
163+
return config->send_keys[target];
164+
}
165+
166+
static inline void process_spike(void) {
167+
uint32_t spike;
168+
circular_buffer_pop(input_queue, &spike);
169+
170+
// Check against the bitfield
171+
uint32_t app_id;
172+
if (!check_app_id(spike, &app_id)) {
173+
// Not in range, drop
174+
prov.n_spikes_invalid_app_id += 1;
175+
continue;
176+
}
177+
178+
// If accepted, forward to the targets
179+
if (accepted(app_id, spike)) {
180+
prov.n_spikes_forwarded += 1;
181+
uint32_t key = get_key();
182+
spin1_send_mc_packet(key, spike, WITH_PAYLOAD);
183+
}
184+
}
185+
186+
void start_callback(void) {
187+
running = true;
188+
while (running) {
189+
// Get the next packet
190+
uint32_t cspr = spin1_irq_disable();
191+
while (running && circular_buffer_size(input_queue) == 0) {
192+
spin1_mode_restore(cspr);
193+
wait_for_interrupt();
194+
cspr = spin1_irq_disable();
195+
}
196+
spin1_mode_restore(cspr);
197+
198+
// If we are still running, process the spike
199+
if (running) {
200+
process_spike();
201+
}
202+
}
203+
}
204+
205+
void exit_callback(void) {
206+
running = false;
207+
}
208+
209+
void store_provenance_data(void *prov_region_addr) {
210+
// Copy across the provenance data
211+
spin1_memcpy(prov_region_addr, &prov, sizeof(filter_provenance_t));
212+
}
213+
214+
void receive_spike_callback(uint key, uint payload) {
215+
// Try to put the spike in the input queue
216+
prov.n_spikes_received += 1;
217+
if (!circular_buffer_push(input_queue, key)) {
218+
// Queue overflowed
219+
prov.n_times_queue_overflowed += 1;
220+
}
221+
}
222+
223+
bool initialise() {
224+
data_specification_metadata_t *ds = data_specification_get_data_address();
225+
if (!data_specification_read_header(ds)) {
226+
log_error("Failed to read data specification header");
227+
return false;
228+
}
229+
230+
// set up the simulation interface
231+
uint32_t n_steps;
232+
bool run_forever;
233+
uint32_t step;
234+
if (!simulation_steps_initialise(
235+
data_specification_get_region(FILTER_REGION_SYSTEM, ds),
236+
APPLICATION_NAME_HASH, &n_steps, &run_forever, &step, 0, -2)) {
237+
return false;
238+
}
239+
simulation_set_provenance_function(
240+
store_provenance_data,
241+
data_specification_get_region(FILTER_REGION_PROVENANCE, ds));
242+
243+
// Read in the filter configuration
244+
filter_config_t *sdram_config = data_specification_get_region(
245+
FILTER_REGION_CONFIG, ds);
246+
uint32_t config_size = sizeof(filter_config_t)
247+
+ sizeof(uint32_t) * sdram_config->n_targets;
248+
config = spin1_malloc(config_size);
249+
if (config == NULL) {
250+
log_error("Failed to allocate %u bytes for filter configuration",
251+
config_size);
252+
return false;
253+
}
254+
spin1_memcpy(config, sdram_config, config_size);
255+
256+
// Set up the input queue
257+
input_queue = circular_buffer_initialize(config->input_queue_size);
258+
if (input_queue == NULL) {
259+
log_error("Failed to create input queue of size %u",
260+
config->input_queue_size);
261+
return false;
262+
}
263+
264+
// Prepare the bitfield filters
265+
uint32_t n_entries = (config->app_id_max - config->app_id_min) + 1;
266+
filters = spin1_malloc(sizeof(bit_field_filter_info_t) * n_entries);
267+
if (filters == NULL) {
268+
log_error("Failed to allocate %u filters", n_entries);
269+
return false;
270+
}
271+
for (uint32_t i = 0; i < n_entries; i++) {
272+
filters[i].data = NULL;
273+
}
274+
275+
// Read in the bit field filters
276+
filter_region_t *bitfield_region = data_specification_get_region(
277+
FILTER_REGION_BITFIELDS, ds);
278+
pop_table_config_t *master_pop_table_region = data_specification_get_region(
279+
FILTER_REGION_POP_TABLE, ds);
280+
filter_info_t *filters_sdram = bitfield_region->filters;
281+
for (uint32_t i = 0; i < bitfield_region->n_filters; i++) {
282+
uint32_t app_id = (filters_sdram[i].key & config->app_id_mask)
283+
>> config->app_id_shift;
284+
if ((app_id < config->app_id_min) || (app_id > config->app_id_max)) {
285+
log_error("Filter key 0x%08x has app id %u outside of range %u-%u",
286+
filters_sdram[i].key, app_id, config->app_id_min,
287+
config->app_id_max);
288+
return false;
289+
}
290+
291+
uint32_t pos = app_id - config->app_id_min;
292+
filters[pos].mask = ~(master_pop_table_region->data[i].mask
293+
| (master_pop_table_region->data[i].core_mask
294+
<< master_pop_table_region->data[i].mask_shift));
295+
filters[pos].all_ones = filters_sdram[i].all_ones;
296+
filters[pos].n_colour_bits =
297+
master_pop_table_region->data[i].n_colour_bits;
298+
filters[pos].core_mask =
299+
master_pop_table_region->data[i].core_mask;
300+
filters[pos].core_shift =
301+
master_pop_table_region->data[i].mask_shift;
302+
filters[pos].n_neurons =
303+
master_pop_table_region->data[i].n_neurons;
304+
305+
uint32_t size = get_bit_field_size(
306+
filters_sdram[i].n_atoms) * sizeof(uint32_t);
307+
filters[pos].data = spin1_malloc(size);
308+
if (filters[pos] == NULL) {
309+
log_error("Failed to allocate bit field of %u atoms for app id %u",
310+
filters_sdram[i].n_atoms, app_id);
311+
return false;
312+
}
313+
spin1_memcpy(filters[pos].data, filters_sdram[i].data, size);
314+
}
315+
316+
return true;
317+
}
318+
319+
//! \brief The entry point for this model.
320+
void c_main(void) {
321+
// initialise the model
322+
if (!initialize()) {
323+
rt_error(RTE_SWERR);
324+
}
325+
326+
simulation_set_exit_function(exit_callback);
327+
simulation_set_start_function(start_callback);
328+
simulation_set_uses_timer(false);
329+
spin1_callback_on(MC_PACKET_RECEIVED, receive_spike_callback, -1);
330+
simulation_run();
331+
}

0 commit comments

Comments
 (0)