From be19e71d880f891d243090ea5e3a578f851be401 Mon Sep 17 00:00:00 2001 From: Andrew Rowley Date: Thu, 13 Apr 2023 13:43:26 +0100 Subject: [PATCH 1/4] Add a GFE Live Example --- gfe_examples/live_io/Makefile | 25 ++++ gfe_examples/live_io/__init__.py | 15 +++ gfe_examples/live_io/live_io.py | 98 +++++++++++++++ gfe_examples/live_io/live_io_vertex.py | 78 ++++++++++++ gfe_examples/live_io/src/live_io.c | 164 +++++++++++++++++++++++++ 5 files changed, 380 insertions(+) create mode 100644 gfe_examples/live_io/Makefile create mode 100644 gfe_examples/live_io/__init__.py create mode 100644 gfe_examples/live_io/live_io.py create mode 100644 gfe_examples/live_io/live_io_vertex.py create mode 100644 gfe_examples/live_io/src/live_io.c diff --git a/gfe_examples/live_io/Makefile b/gfe_examples/live_io/Makefile new file mode 100644 index 00000000..e74a299a --- /dev/null +++ b/gfe_examples/live_io/Makefile @@ -0,0 +1,25 @@ +# Copyright (c) 2023 The University of Manchester +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# If SPINN_DIRS is not defined, this is an error! +ifndef SPINN_DIRS + $(error SPINN_DIRS is not set. Please define SPINN_DIRS (possibly by running "source setup" in the spinnaker package folder)) +endif + +APP = live_io +SOURCES = live_io.c + +APP_OUTPUT_DIR := $(abspath $(dir $(abspath $(lastword $(MAKEFILE_LIST)))))/ + +include $(SPINN_DIRS)/make/local.mk diff --git a/gfe_examples/live_io/__init__.py b/gfe_examples/live_io/__init__.py new file mode 100644 index 00000000..ee57709e --- /dev/null +++ b/gfe_examples/live_io/__init__.py @@ -0,0 +1,15 @@ +# Copyright (c) 2023 The University of Manchester +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +__author__ = 'Arthur Ceccotti' diff --git a/gfe_examples/live_io/live_io.py b/gfe_examples/live_io/live_io.py new file mode 100644 index 00000000..48f115e5 --- /dev/null +++ b/gfe_examples/live_io/live_io.py @@ -0,0 +1,98 @@ +# Copyright (c) 2023 The University of Manchester +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import os +from time import sleep +from random import randint +from pacman.model.graphs.machine.machine_edge import MachineEdge +from spinn_front_end_common.data.fec_data_view import FecDataView +from spinn_front_end_common.utilities.connections import LiveEventConnection +from spinn_front_end_common.utility_models import ( + LivePacketGatherMachineVertex, ReverseIPTagMulticastSourceMachineVertex) +from spinn_front_end_common.utilities.utility_objs import ( + LivePacketGatherParameters) +import spinnaker_graph_front_end as front_end +from gfe_examples.live_io.live_io_vertex import LiveIOVertex + +n_receivers = 20 +receiver_label = "Receiver" +sender_label = "Sender" +sender_partition = "Send" +n_sender_keys = 32 +sends_per_cycle = 10 +lpg_label = "LPGReceiver" +running = True + + +def start_sending(label, c): + sleep(0.5) + while running: + for _ in range(sends_per_cycle): + key = randint(0, n_sender_keys - 1) + print(f"Sending {key}") + c.send_event(sender_label, key) + sleep(0.1) + + +def end_sim(label, c): + global running + running = False + + +def receive(label, time, keys): + print(f"Received from {label} at time {time}: {keys}") + + +# Make a connection to send and receive data +conn = LiveEventConnection( + live_packet_gather_label=lpg_label, + receive_labels=[receiver_label + f" {x}" for x in range(n_receivers)], + send_labels=[sender_label], local_port=None) +conn.add_start_resume_callback(sender_label, start_sending) +conn.add_pause_stop_callback(sender_label, end_sim) +for x in range(n_receivers): + conn.add_receive_callback(receiver_label + f" {x}", receive) + +front_end.setup( + n_chips_required=1, model_binary_folder=os.path.dirname(__file__)) +front_end.add_socket_address(None, None, conn.local_port) + +# Add a sender +sender = ReverseIPTagMulticastSourceMachineVertex( + n_keys=n_sender_keys, label="Sender", + injection_partition_id=sender_partition) +front_end.add_machine_vertex_instance(sender) + +live_out = LivePacketGatherMachineVertex( + LivePacketGatherParameters(tag=1, port=10000, hostname="localhost"), + label=lpg_label) +front_end.add_machine_vertex_instance(live_out) + + +# Put LiveIOVertex on some cores +for x in range(n_receivers): + vertex = LiveIOVertex( + n_keys=n_sender_keys, send_partition=sender_partition, + label=receiver_label + f" {x}") + front_end.add_machine_vertex_instance(vertex) + front_end.add_machine_edge_instance( + MachineEdge(sender, vertex), sender_partition) + front_end.add_machine_edge_instance( + MachineEdge(vertex, live_out), sender_partition) + live_out.add_incoming_source(vertex, sender_partition) + FecDataView.add_live_output_vertex(vertex, sender_partition) + +front_end.run(10000) + +front_end.stop() diff --git a/gfe_examples/live_io/live_io_vertex.py b/gfe_examples/live_io/live_io_vertex.py new file mode 100644 index 00000000..e8033e00 --- /dev/null +++ b/gfe_examples/live_io/live_io_vertex.py @@ -0,0 +1,78 @@ +# Copyright (c) 2023 The University of Manchester +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from enum import IntEnum +import logging +from spinn_utilities.log import FormatAdapter +from spinn_utilities.overrides import overrides +from pacman.model.graphs.common import Slice +from pacman.model.graphs.machine import MachineVertex +from pacman.model.resources import ConstantSDRAM +from spinn_front_end_common.utilities.constants import ( + SYSTEM_BYTES_REQUIREMENT, BYTES_PER_WORD) +from spinn_front_end_common.abstract_models.impl import ( + MachineDataSpecableVertex) +from spinn_front_end_common.data.fec_data_view import FecDataView +from spinnaker_graph_front_end.utilities import SimulatorVertex + +logger = FormatAdapter(logging.getLogger(__name__)) +N_KEY_DATA_BYTES = 3 * BYTES_PER_WORD + + +class DataRegions(IntEnum): + SYSTEM = 0 + KEY_DATA = 1 + + +class LiveIOVertex( + SimulatorVertex, MachineDataSpecableVertex): + + def __init__(self, n_keys, send_partition="LiveOut", label=None): + super().__init__(label, "live_io.aplx") + self.__n_keys = n_keys + self.__send_partition = send_partition + self._vertex_slice = Slice(0, n_keys - 1) + + @property + @overrides(MachineVertex.sdram_required) + def sdram_required(self): + return ConstantSDRAM( + SYSTEM_BYTES_REQUIREMENT + N_KEY_DATA_BYTES) + + def get_n_keys_for_partition(self, partition_id): + return self.__n_keys + + @overrides(MachineDataSpecableVertex.generate_machine_data_specification) + def generate_machine_data_specification( + self, spec, placement, iptags, reverse_iptags): + # Generate the system data region for simulation .c requirements + self.generate_system_region(spec) + + spec.reserve_memory_region(DataRegions.KEY_DATA, N_KEY_DATA_BYTES) + spec.switch_write_focus(DataRegions.KEY_DATA) + + routing_infos = FecDataView().get_routing_infos() + r_info = routing_infos.get_routing_info_from_pre_vertex( + placement.vertex, self.__send_partition) + if r_info is None: + spec.write_value(0) + spec.write_value(0) + spec.write_value(0) + else: + spec.write_value(1) + spec.write_value(r_info.key) + spec.write_value((~r_info.mask) & 0xFFFFFFFF) + + # End-of-Spec: + spec.end_specification() diff --git a/gfe_examples/live_io/src/live_io.c b/gfe_examples/live_io/src/live_io.c new file mode 100644 index 00000000..6614bec4 --- /dev/null +++ b/gfe_examples/live_io/src/live_io.c @@ -0,0 +1,164 @@ +/* + * Copyright (c) 2023 The University of Manchester + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +//! imports +#include +#include "common-typedefs.h" +#include +#include +#include +#include + +//! control value, which says how many timer ticks to run for before exiting +static uint32_t simulation_ticks = 0; +static uint32_t infinite_run = 0; +static uint32_t time = 0; + +//! int as a bool to represent if this simulation should run forever +static uint32_t infinite_run; + +//! The key to send data with +static uint32_t send_key; + +//! Whether the data should be sent +static uint32_t do_send; + +//! The mask to apply to the key to extract any data before sending +static uint32_t key_mask; + +//! human readable definitions of each region in SDRAM +typedef enum regions_e { + SYSTEM_REGION, + KEY_DATA_REGION, +} regions_e; + +//! values for the priority for each callback +typedef enum callback_priorities { + MC_PACKET = -1, + SDP = 0, + DMA = 1, + TIMER = 2, + USER = 3 +} callback_priorities; + +// ------------------------------------------------------------------- + +static void receive_data_pl(uint key, uint payload) { + log_info("Key=%u Payload=%u", key, payload); + if (do_send) { + uint key_to_send = send_key | (key & key_mask); + spin1_send_mc_packet(key_to_send, payload, 1); + } +} + +static void receive_data(uint key, UNUSED uint unused) { + log_info("Key=%u", key); + if (do_send) { + uint key_to_send = send_key | (key & key_mask); + spin1_send_mc_packet(key_to_send, 0, 0); + } +} + +static void update(uint ticks, uint b) { + use(b); + use(ticks); + + time++; + + // check that the run time hasn't already elapsed and thus needs to be + // killed + if ((infinite_run != TRUE) && (time >= simulation_ticks)) { + + // fall into the pause resume mode of operating + simulation_handle_pause_resume(NULL); + + // switch to state where host is ready to read + simulation_ready_to_read(); + + return; + } +} + +static bool initialize(uint32_t *timer_period) { + log_info("Initialise: started\n"); + + // Get the address this core's DTCM data starts at from SRAM + data_specification_metadata_t *data = data_specification_get_data_address(); + + // Read the header + if (!data_specification_read_header(data)) { + log_error("failed to read the data spec header"); + return false; + } + + // Get the timing details and set up the simulation interface + if (!simulation_initialise( + data_specification_get_region(SYSTEM_REGION, data), + APPLICATION_NAME_HASH, timer_period, &simulation_ticks, + &infinite_run, &time, SDP, DMA)) { + return false; + } + + // Get key data + uint *key_data = data_specification_get_region(KEY_DATA_REGION, data); + do_send = key_data[0]; + send_key = key_data[1]; + key_mask = key_data[2]; + + if (do_send) { + log_info("Re-sending received keys with key 0x%08x and mask 0x%08x", + send_key, key_mask); + } else { + log_info("Not re-sending received keys"); + } + + return true; +} + +/****f* + * + * SUMMARY + * This function is called at application start-up. + * It is used to register event callbacks and begin the simulation. + * + * SYNOPSIS + * int c_main() + * + * SOURCE + */ +void c_main(void) { + + // Load DTCM data + uint32_t timer_period; + + // initialise the model + if (!initialize(&timer_period)) { + rt_error(RTE_SWERR); + } + + // set timer tick value to configured value + spin1_set_timer_tick(timer_period); + + // register callbacks + spin1_callback_on(MCPL_PACKET_RECEIVED, receive_data_pl, MC_PACKET); + spin1_callback_on(MC_PACKET_RECEIVED, receive_data, MC_PACKET); + spin1_callback_on(TIMER_TICK, update, TIMER); + + // Start the time at "-1" so that the first tick will be 0 + time = UINT32_MAX; + + simulation_run(); +} From a3b5e16e70204cf4151b509cef7c359345d12408 Mon Sep 17 00:00:00 2001 From: Andrew Rowley Date: Thu, 13 Apr 2023 13:45:28 +0100 Subject: [PATCH 2/4] Add to correct places --- gfe_examples/Makefile | 2 +- gfe_integration_tests/test_scripts.py | 58 +++++++++++++++------------ 2 files changed, 33 insertions(+), 27 deletions(-) diff --git a/gfe_examples/Makefile b/gfe_examples/Makefile index 085af7f4..0942c084 100644 --- a/gfe_examples/Makefile +++ b/gfe_examples/Makefile @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -BUILD_DIRS = hello_world hello_world_untimed Conways template sync_test +BUILD_DIRS = hello_world hello_world_untimed Conways template sync_test live_io all: $(BUILD_DIRS) for d in $(BUILD_DIRS); do (cd $$d; "$(MAKE)") || exit $$?; done diff --git a/gfe_integration_tests/test_scripts.py b/gfe_integration_tests/test_scripts.py index 3c685472..9d6af358 100644 --- a/gfe_integration_tests/test_scripts.py +++ b/gfe_integration_tests/test_scripts.py @@ -27,47 +27,53 @@ class TestScripts(ScriptChecker): """ # flake8: noqa - def test_gfe_examples_sync_test_sync_test(self): - self.check_script("gfe_examples/sync_test/sync_test.py") + def test_gfe_examples_live_io_live_io(self): + self.check_script("gfe_examples/live_io/live_io.py") - def test_gfe_examples_sync_test_sync_test_vertex(self): - self.check_script("gfe_examples/sync_test/sync_test_vertex.py") + def test_gfe_examples_live_io_live_io_vertex(self): + self.check_script("gfe_examples/live_io/live_io_vertex.py") - def test_gfe_examples_Conways_one_no_graph_example_conways_no_graph(self): - self.check_script("gfe_examples/Conways/one_no_graph_example/conways_no_graph.py") + def test_gfe_examples_template_template_vertex(self): + self.check_script("gfe_examples/template/template_vertex.py") - def test_gfe_examples_Conways_partitioned_example_a_no_vis_no_buffer_conways_basic_cell(self): - self.check_script("gfe_examples/Conways/partitioned_example_a_no_vis_no_buffer/conways_basic_cell.py") + def test_gfe_examples_template_python_template(self): + self.check_script("gfe_examples/template/python_template.py") - def test_gfe_examples_Conways_partitioned_example_a_no_vis_no_buffer_conways_partitioned(self): - self.check_script("gfe_examples/Conways/partitioned_example_a_no_vis_no_buffer/conways_partitioned.py") + def test_gfe_examples_hello_world_untimed_hello_world(self): + self.check_script("gfe_examples/hello_world_untimed/hello_world.py") - def test_gfe_examples_Conways_partitioned_example_b_no_vis_buffer_conways_basic_cell(self): - self.check_script("gfe_examples/Conways/partitioned_example_b_no_vis_buffer/conways_basic_cell.py") + def test_gfe_examples_hello_world_untimed_hello_world_vertex(self): + self.check_script("gfe_examples/hello_world_untimed/hello_world_vertex.py") def test_gfe_examples_Conways_partitioned_example_b_no_vis_buffer_conways_partitioned(self): self.check_script("gfe_examples/Conways/partitioned_example_b_no_vis_buffer/conways_partitioned.py") - def test_gfe_examples_Conways_no_edges_examples_conways_basic_cell(self): - self.check_script("gfe_examples/Conways/no_edges_examples/conways_basic_cell.py") + def test_gfe_examples_Conways_partitioned_example_b_no_vis_buffer_conways_basic_cell(self): + self.check_script("gfe_examples/Conways/partitioned_example_b_no_vis_buffer/conways_basic_cell.py") + + def test_gfe_examples_Conways_partitioned_example_a_no_vis_no_buffer_conways_partitioned(self): + self.check_script("gfe_examples/Conways/partitioned_example_a_no_vis_no_buffer/conways_partitioned.py") + + def test_gfe_examples_Conways_partitioned_example_a_no_vis_no_buffer_conways_basic_cell(self): + self.check_script("gfe_examples/Conways/partitioned_example_a_no_vis_no_buffer/conways_basic_cell.py") + + def test_gfe_examples_Conways_one_no_graph_example_conways_no_graph(self): + self.check_script("gfe_examples/Conways/one_no_graph_example/conways_no_graph.py") def test_gfe_examples_Conways_no_edges_examples_conways_no_edges(self): self.check_script("gfe_examples/Conways/no_edges_examples/conways_no_edges.py") + def test_gfe_examples_Conways_no_edges_examples_conways_basic_cell(self): + self.check_script("gfe_examples/Conways/no_edges_examples/conways_basic_cell.py") + + def test_gfe_examples_sync_test_sync_test_vertex(self): + self.check_script("gfe_examples/sync_test/sync_test_vertex.py") + + def test_gfe_examples_sync_test_sync_test(self): + self.check_script("gfe_examples/sync_test/sync_test.py") + def test_gfe_examples_hello_world_hello_world(self): self.check_script("gfe_examples/hello_world/hello_world.py") def test_gfe_examples_hello_world_hello_world_vertex(self): self.check_script("gfe_examples/hello_world/hello_world_vertex.py") - - def test_gfe_examples_hello_world_untimed_hello_world(self): - self.check_script("gfe_examples/hello_world_untimed/hello_world.py") - - def test_gfe_examples_hello_world_untimed_hello_world_vertex(self): - self.check_script("gfe_examples/hello_world_untimed/hello_world_vertex.py") - - def test_gfe_examples_template_python_template(self): - self.check_script("gfe_examples/template/python_template.py") - - def test_gfe_examples_template_template_vertex(self): - self.check_script("gfe_examples/template/template_vertex.py") From 6751e36e49e6bbb288ecc2619748056542b35d0b Mon Sep 17 00:00:00 2001 From: Andrew Rowley Date: Thu, 13 Apr 2023 13:49:22 +0100 Subject: [PATCH 3/4] Pylint disables --- gfe_examples/live_io/live_io.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/gfe_examples/live_io/live_io.py b/gfe_examples/live_io/live_io.py index 48f115e5..508b3749 100644 --- a/gfe_examples/live_io/live_io.py +++ b/gfe_examples/live_io/live_io.py @@ -36,6 +36,7 @@ def start_sending(label, c): + # pylint: disable=unused-argument sleep(0.5) while running: for _ in range(sends_per_cycle): @@ -46,6 +47,7 @@ def start_sending(label, c): def end_sim(label, c): + # pylint: disable=unused-argument global running running = False From 2d04aa2c65fa18a10e8025d274181efc0434b6d1 Mon Sep 17 00:00:00 2001 From: Andrew Rowley Date: Wed, 12 Jul 2023 07:37:51 +0100 Subject: [PATCH 4/4] Remove incorrect author! --- gfe_examples/live_io/__init__.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/gfe_examples/live_io/__init__.py b/gfe_examples/live_io/__init__.py index ee57709e..91eaa0c5 100644 --- a/gfe_examples/live_io/__init__.py +++ b/gfe_examples/live_io/__init__.py @@ -11,5 +11,3 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. - -__author__ = 'Arthur Ceccotti'