diff --git a/.gitignore b/.gitignore index a6f61ddaf..b659baf7a 100644 --- a/.gitignore +++ b/.gitignore @@ -11,3 +11,6 @@ cmake-build-release doc/html doc/latex doc/markdown/platform +*.o +*.bc +*.elf diff --git a/README.md b/README.md index 51ad3ea9e..1ae80114b 100644 --- a/README.md +++ b/README.md @@ -16,8 +16,19 @@ information on reactor-uc see our [docs](https://www.lf-lang.org/reactor-uc/) - Java 17 - Additional requirements depend on the target platform +### Installation & Quick Start + + +Clone the repository and set REACTOR_UC_PATH: +```sh +git clone https://github.com/lf-lang/reactor-uc.git --recursive +cd reactor-uc +export REACTOR_UC_PATH=$(pwd) +``` + + ## Supported Platforms -`reactor-uc` can run on top of Zephyr, RIOT, Raspberry Pi Pico and POSIX-compliant OSes. +`reactor-uc` can run on top of Zephyr, RIOT, Raspberry Pi Pico, Patmos, and POSIX-compliant OSes. ### Native (macOS and Linux) `reactor-uc` can also run natively on a host system based on Linux or macOS. This is very useful for developing and testing applications @@ -87,6 +98,19 @@ cd build make ``` +### Patmos + +To install Patmos, follow instructions in [https://github.com/t-crest/patmos/](https://github.com/t-crest/patmos) readme file. + +To compile and run patmos examples, navigate to their folders inside `exmaples/patmos` folder and run `./build.sh`. + +To compile and run all examples together, run the following lines: + +```shell +cd examples/patmos +./buildAll.sh +``` + ## Contributing ### Code organization @@ -125,6 +149,16 @@ make lf-test This depends on having the `timeout` utility installed. For macOS users run `brew install coreutils`. +Run platform related tests with +```sh +make platform-test +``` + +Run all examples with +```sh +make examples +``` + Compute unit test coverage ```sh make coverage diff --git a/examples/fed-template/buildAll.sh b/examples/fed-template/buildAll.sh index bfe78ea93..3c463274e 100755 --- a/examples/fed-template/buildAll.sh +++ b/examples/fed-template/buildAll.sh @@ -10,8 +10,11 @@ cmake -Bbuild cmake --build build popd -pushd MyFed/dest -./run_lfc.sh -west build -popd - +if ! command -v west &> /dev/null; then + echo "Error: 'west' is not installed." +else + pushd MyFed/dest + ./run_lfc.sh + west build + popd +fi diff --git a/examples/flexpret/buildAll.sh b/examples/flexpret/buildAll.sh index 98299bb5d..f8d98d9df 100755 --- a/examples/flexpret/buildAll.sh +++ b/examples/flexpret/buildAll.sh @@ -1,5 +1,9 @@ set -e -${REACTOR_UC_PATH}/lfc/bin/lfc-dev src/Smoke.lf -cmake -Bbuild -make -C build -bin/fp-smoke \ No newline at end of file +if [ -z "$FP_SDK_PATH" ]; then + echo "Error: FP_SDK_PATH is not defined. Please set it before running this script." +else + ${REACTOR_UC_PATH}/lfc/bin/lfc-dev src/Smoke.lf + cmake -Bbuild + make -C build + bin/fp-smoke +fi \ No newline at end of file diff --git a/examples/patmos/buildAll.sh b/examples/patmos/buildAll.sh new file mode 100755 index 000000000..b1c6b50bd --- /dev/null +++ b/examples/patmos/buildAll.sh @@ -0,0 +1,18 @@ +#!/bin/bash + +set -e + +if ! command -v pasim &> /dev/null; then + echo "Error: pasim command not found. Please ensure it is installed and available in your PATH." +else + # Iterate over each folder and execute the command + for dir in ./*; do + if [ -d $dir ]; then + echo "Entering $dir" + pushd $dir + chmod +x build.sh + ./build.sh + popd + fi + done +fi \ No newline at end of file diff --git a/examples/patmos/hello/Makefile b/examples/patmos/hello/Makefile new file mode 100644 index 000000000..947fb3485 --- /dev/null +++ b/examples/patmos/hello/Makefile @@ -0,0 +1,27 @@ +# Makefile for building a simple Hello World program for the Patmos platform + +# Compiler and tools for PATMOS +CC = patmos-clang + +# Paths +SRC_DIR = $(CURDIR) +BUILD_DIR = $(CURDIR)/build + +# Source files +SOURCES = $(SRC_DIR)/hello.c + +# Compiler flags +CFLAGS = -O2 -Wall -Wextra -Werror + +# Output binary +OUTPUT = $(BUILD_DIR)/hello.elf +all : $(OUTPUT) +# Build rule +$(OUTPUT): $(SOURCES) + mkdir -p $(BUILD_DIR) + $(CC) $(CFLAGS) $(SOURCES) -o $(OUTPUT) + +clean: + rm -rf $(BUILD_DIR) + +.PHONY: all clean diff --git a/examples/patmos/hello/build.sh b/examples/patmos/hello/build.sh new file mode 100755 index 000000000..ff973f880 --- /dev/null +++ b/examples/patmos/hello/build.sh @@ -0,0 +1,4 @@ +#!/bin/bash +rm -rf build +make all +pasim ./build/hello.elf \ No newline at end of file diff --git a/examples/patmos/hello/hello.c b/examples/patmos/hello/hello.c new file mode 100644 index 000000000..a5bafc3f2 --- /dev/null +++ b/examples/patmos/hello/hello.c @@ -0,0 +1,5 @@ +#include +int main () { + printf("Hello Patmos!\n"); + return 0; +} \ No newline at end of file diff --git a/examples/patmos/hello_lf/Makefile b/examples/patmos/hello_lf/Makefile new file mode 100755 index 000000000..f8b7eb1f3 --- /dev/null +++ b/examples/patmos/hello_lf/Makefile @@ -0,0 +1,33 @@ +REACTOR_UC_PATH ?= $(CURDIR)/../../../ +LF_MAIN ?= HelloLF + +# The name of the LF application inside "./src" to build/run/flash etc. +SRC_GEN_PATH ?= ./src-gen/$(LF_MAIN) + +# Execute the LF compiler if build target is "all" +ifeq ($(firstword $(MAKECMDGOALS)),all) + _ := $(shell $(REACTOR_UC_PATH)/lfc/bin/lfc-dev src/$(LF_MAIN).lf) +endif + +# ---- Patmos specific configuration ---- +include $(REACTOR_UC_PATH)/make/patmos/patmos-lfc.mk + +CFLAGS += -DEVENT_QUEUE_SIZE=$(EVENT_QUEUE_SIZE) +CFLAGS += -DREACTION_QUEUE_SIZE=$(REACTION_QUEUE_SIZE) + +# Output directory +BIN_DIR = $(CURDIR)/bin +OUTPUT = $(BIN_DIR)/$(LF_MAIN) + +all: $(OUTPUT) + +# Build rule +$(OUTPUT): $(SOURCES) + mkdir -p $(BIN_DIR) + $(CC) $(SOURCES) $(CFLAGS) -o $(OUTPUT) + +clean: + rm -rf $(BIN_DIR) ./src-gen + +.PHONY: all clean + diff --git a/examples/patmos/hello_lf/build.sh b/examples/patmos/hello_lf/build.sh new file mode 100755 index 000000000..8fef4bd05 --- /dev/null +++ b/examples/patmos/hello_lf/build.sh @@ -0,0 +1,5 @@ +#!/bin/bash + +rm -rf bin src-gen +make all +pasim ./bin/HelloLF \ No newline at end of file diff --git a/examples/patmos/hello_lf/src/HelloLF.lf b/examples/patmos/hello_lf/src/HelloLF.lf new file mode 100644 index 000000000..1d165ba9d --- /dev/null +++ b/examples/patmos/hello_lf/src/HelloLF.lf @@ -0,0 +1,9 @@ +target uC { + platform: Patmos +} + +main reactor { + reaction(startup) {= + printf("Hello LF on PATMOS!\n"); + =} +} \ No newline at end of file diff --git a/examples/patmos/s4noc_fed/Makefile b/examples/patmos/s4noc_fed/Makefile new file mode 100644 index 000000000..46e2e9252 --- /dev/null +++ b/examples/patmos/s4noc_fed/Makefile @@ -0,0 +1,20 @@ +include $(REACTOR_UC_PATH)/make/patmos/patmos.mk + +OUTPUT = s4noc_fed.elf +BIN_DIR = $(CURDIR)/bin + +SOURCES += $(CURDIR)/main.c +SOURCES += $(CURDIR)/sender/build/sender.a +SOURCES += $(CURDIR)/receiver/build/receiver.a + +all: $(OUTPUT) + +$(OUTPUT): $(SOURCES) + @echo "BUILDING $(notdir $@) from $^" + mkdir -p $(BIN_DIR) + $(CC) $(CFLAGS) $(SOURCES) -o $(BIN_DIR)/$(OUTPUT) + +clean: + rm -rf $(BIN_DIR) + +.PHONY: all clean \ No newline at end of file diff --git a/examples/patmos/s4noc_fed/build.sh b/examples/patmos/s4noc_fed/build.sh new file mode 100755 index 000000000..af770409c --- /dev/null +++ b/examples/patmos/s4noc_fed/build.sh @@ -0,0 +1,31 @@ +# !/bin/bash + +LF_MAIN=s4noc_fed +BIN_DIR=bin +CC=patmos-clang + +make clean -C ./sender +make clean -C ./receiver + +make all -C ./sender +make all -C ./receiver + +echo "Linking sender and receiver to create executable" +make clean +make all + +read -n 1 -t 10 -p "Choose action: [e]mulate or [f]pga? (default: e) " action +action=${action:-e} +if [[ "$action" == "e" ]]; then + patemu $BIN_DIR/$LF_MAIN.elf +elif [[ "$action" == "f" ]]; then + if jtagconfig | grep -q "USB-Blaster"; then + mv $BIN_DIR/$LF_MAIN.elf ~/t-crest/patmos/tmp/$LF_MAIN.elf + make -C ~/t-crest/patmos APP=$LF_MAIN config download + else + echo "JTAG not connected. Please connect USB-Blaster." + fi +else + echo "Invalid option. Please choose 'e' for emulate or 'f' for fpga." +fi + diff --git a/examples/patmos/s4noc_fed/main.c b/examples/patmos/s4noc_fed/main.c new file mode 100644 index 000000000..05c07296c --- /dev/null +++ b/examples/patmos/s4noc_fed/main.c @@ -0,0 +1,33 @@ +#include +#include +#include "sender/sender.h" +#include "receiver/receiver.h" + +static void* receiver_thread(void* arg) { + (void)arg; + // printf("Starting receiver federate on core/thread 1\n"); + lf_start_receiver(); + return NULL; +} + +static void* sender_thread(void* arg) { + (void)arg; + printf("Starting sender federate on core/thread 2\n"); + lf_start_sender(); + return NULL; +} + +int main(void) { + pthread_t threads[2]; + printf("Starting S4NOC Federated Example\n"); + pthread_create(&threads[0], NULL, receiver_thread, NULL); + pthread_create(&threads[1], NULL, sender_thread, NULL); + + printf("Threads created for federates.\n"); + + pthread_join(threads[0], NULL); + pthread_join(threads[1], NULL); + + printf("All federates finished.\n"); + return 0; +} diff --git a/examples/patmos/s4noc_fed/receiver/Makefile b/examples/patmos/s4noc_fed/receiver/Makefile new file mode 100644 index 000000000..e585c9fc9 --- /dev/null +++ b/examples/patmos/s4noc_fed/receiver/Makefile @@ -0,0 +1,34 @@ +include $(REACTOR_UC_PATH)/make/patmos/patmos.mk + +FED = receiver + +# Paths +SRC_DIR = $(CURDIR) +BUILD_DIR = $(CURDIR)/build + +# Source files +SOURCES = $(FED).c + +OBJECTS ?= $(SOURCES:.c=.bc) + +# Output binary +OUTPUT = $(FED).a + +all: $(OUTPUT) + +# Build rule +$(OUTPUT): $(OBJECTS) + @echo "BUILDING $(notdir $@) from $^" + mkdir -p $(BUILD_DIR) + llvm-ar rcsv $(BUILD_DIR)/$(OUTPUT) $^; + +%.bc: %.c + @echo "$(notdir $^) COMPILED TO $(notdir $@)" + mkdir -p $(BUILD_DIR) + $(CC) $(CFLAGS) -emit-llvm -c $^ -o $@ + +clean: + rm -rf $(BUILD_DIR) + rm -f $(FED).bc + +.PHONY: all clean \ No newline at end of file diff --git a/examples/patmos/s4noc_fed/receiver/receiver.c b/examples/patmos/s4noc_fed/receiver/receiver.c new file mode 100644 index 000000000..6347052e2 --- /dev/null +++ b/examples/patmos/s4noc_fed/receiver/receiver.c @@ -0,0 +1,175 @@ +#include "reactor-uc/platform/patmos/s4noc_channel.h" +#include "reactor-uc/schedulers/dynamic/scheduler.h" +#include "reactor-uc/reactor-uc.h" +#include "reactor-uc/environments/federated_environment.h" +#include "reactor-uc/startup_coordinator.h" +#include "reactor-uc/clock_synchronization.h" +#include + +#define SENDER_CORE_ID 2 + +typedef struct { + int size; + char msg[512]; +} lf_msg_t; + +lf_ret_t deserialize_msg_t(void *user_struct, const unsigned char *msg_buf, size_t msg_size) { + (void)msg_size; + + lf_msg_t *msg = user_struct; + memcpy(&msg->size, msg_buf, sizeof(msg->size)); + memcpy(msg->msg, msg_buf + sizeof(msg->size), msg->size); + + /* Make sure msg->msg is null-terminated before printing. */ + if (msg->size < (int)sizeof(msg->msg)) { + msg->msg[msg->size] = '\0'; + } else { + msg->msg[sizeof(msg->msg) - 1] = '\0'; + } + + printf("Receiver: deserialize_msg_t: deserialized size=%d, msg='%s'\n", msg->size, msg->msg); + + return LF_OK; +} + +LF_DEFINE_REACTION_STRUCT(Receiver, r, 0) +LF_DEFINE_REACTION_CTOR(Receiver, r, 0, NULL, NULL) +LF_DEFINE_INPUT_STRUCT(Receiver, in, 1, 0, lf_msg_t, 0) +LF_DEFINE_INPUT_CTOR(Receiver, in, 1, 0, lf_msg_t, 0) + +typedef struct { + Reactor super; + LF_REACTION_INSTANCE(Receiver, r); + LF_PORT_INSTANCE(Receiver, in, 1); + int cnt; + LF_REACTOR_BOOKKEEPING_INSTANCES(1, 1, 0); +} Receiver; + +LF_DEFINE_REACTION_BODY(Receiver, r) { + LF_SCOPE_SELF(Receiver); + LF_SCOPE_ENV(); + LF_SCOPE_PORT(Receiver, in); + printf("Receiver: Input triggered @ " PRINTF_TIME " with %s size %d\n", env->get_elapsed_logical_time(env), in->value.msg, + in->value.size); +} + +LF_REACTOR_CTOR_SIGNATURE_WITH_PARAMETERS(Receiver, InputExternalCtorArgs *in_external) { + LF_REACTOR_CTOR_PREAMBLE(); + LF_REACTOR_CTOR(Receiver); + LF_INITIALIZE_REACTION(Receiver, r, NEVER); + LF_INITIALIZE_INPUT(Receiver, in, 1, in_external); + + // Register reaction as an effect of in + LF_PORT_REGISTER_EFFECT(self->in, self->r, 1); +} + +LF_DEFINE_FEDERATED_INPUT_CONNECTION_STRUCT(Receiver, in, lf_msg_t, 5); +LF_DEFINE_FEDERATED_INPUT_CONNECTION_CTOR(Receiver, in, lf_msg_t, 5, MSEC(100), false, 0); + +typedef struct { + FederatedConnectionBundle super; + S4NOCPollChannel channel; + LF_FEDERATED_INPUT_CONNECTION_INSTANCE(Receiver, in); + LF_FEDERATED_CONNECTION_BUNDLE_BOOKKEEPING_INSTANCES(1, 0) +} LF_FEDERATED_CONNECTION_BUNDLE_TYPE(Receiver, Sender); + +LF_FEDERATED_CONNECTION_BUNDLE_CTOR_SIGNATURE(Receiver, Sender) { + LF_FEDERATED_CONNECTION_BUNDLE_CTOR_PREAMBLE(); + S4NOCPollChannel_ctor(&self->channel, SENDER_CORE_ID); + printf("Receiver: initialized channel for core %d\n", SENDER_CORE_ID); + LF_FEDERATED_CONNECTION_BUNDLE_CALL_CTOR(); + LF_INITIALIZE_FEDERATED_INPUT_CONNECTION(Receiver, in, deserialize_msg_t); +} + +typedef struct { + StartupCoordinator super; + StartupEvent events[6]; + bool used[6]; + NeighborState neighbors[1]; +} ReceiverStartupCoordinator; + + + void ReceiverStartupCoordinator_ctor(ReceiverStartupCoordinator *self, Environment *env) { + StartupCoordinator_ctor(&self->super, env, self->neighbors, 1, 1, JOIN_IMMEDIATELY, + sizeof(StartupEvent), (void *)self->events, self->used, 6); + } + + typedef struct { + ClockSynchronization super; + ClockSyncEvent events[2]; + NeighborClock neighbor_clocks[1]; + bool used[2]; + } ReceiverClockSynchronization; + + void ReceiverClockSynchronization_ctor(ReceiverClockSynchronization *self, Environment *env) { + ClockSynchronization_ctor(&self->super, env, self->neighbor_clocks, 1, false, + sizeof(ClockSyncEvent), (void *)self->events, self->used, (2), + CLOCK_SYNC_DEFAULT_PERIOD, CLOCK_SYNC_DEFAULT_MAX_ADJ, CLOCK_SYNC_DEFAULT_KP, + CLOCK_SYNC_DEFAULT_KI); + } + +typedef struct { + Reactor super; + LF_CHILD_REACTOR_INSTANCE(Receiver, receiver, 1); + LF_FEDERATED_CONNECTION_BUNDLE_INSTANCE(Receiver, Sender); + LF_FEDERATE_BOOKKEEPING_INSTANCES(1); + LF_CHILD_INPUT_SOURCES(receiver, in, 1, 1, 0); + LF_DEFINE_STARTUP_COORDINATOR(Receiver); + LF_DEFINE_CLOCK_SYNC(Receiver); +} MainRecv; + +LF_REACTOR_CTOR_SIGNATURE(MainRecv) { + LF_REACTOR_CTOR(MainRecv); + LF_FEDERATE_CTOR_PREAMBLE(); + LF_DEFINE_CHILD_INPUT_ARGS(receiver, in, 1, 1); + LF_INITIALIZE_CHILD_REACTOR_WITH_PARAMETERS(Receiver, receiver, 1, _receiver_in_args[i]); + LF_INITIALIZE_FEDERATED_CONNECTION_BUNDLE(Receiver, Sender); + LF_INITIALIZE_STARTUP_COORDINATOR(Receiver); + LF_INITIALIZE_CLOCK_SYNC(Receiver); + lf_connect_federated_input(&self->Receiver_Sender_bundle.inputs[0]->super, &self->receiver->in[0].super); +} + +#define NumEvents 32 +#define NumSystemEvents 32 +#define NumReactions 32 +#define Timeout SEC(1) +#define KeepAlive true +#define NumBundles 1 +#define DoClockSync false + +static MainRecv main_reactor; +static FederatedEnvironment env; +Environment *_lf_environment = &env.super; +static DynamicScheduler scheduler; +static ArbitraryEvent events[(NumEvents)]; +static EventQueue event_queue; +static ArbitraryEvent system_events[(NumSystemEvents)]; +static EventQueue system_event_queue; +static Reaction *reactions[(NumReactions)][(NumReactions)]; +static int level_size[(NumReactions)]; +static ReactionQueue reaction_queue; + +void lf_exit_receiver(void) { + printf("lf_exit_receiver: cleaning up federated environment\n"); + FederatedEnvironment_free(&env); +} + +void lf_start_receiver() { + EventQueue_ctor(&event_queue, events, (NumEvents)); + EventQueue_ctor(&system_event_queue, system_events, (NumSystemEvents)); + ReactionQueue_ctor(&reaction_queue, (Reaction **)reactions, level_size, (NumReactions)); + DynamicScheduler_ctor(&scheduler, _lf_environment, &event_queue, &system_event_queue, &reaction_queue, (Timeout), + (KeepAlive)); + FederatedEnvironment_ctor( + &env, (Reactor *)&main_reactor, &scheduler.super, false, (FederatedConnectionBundle **)&main_reactor._bundles, + (NumBundles), &main_reactor.startup_coordinator.super, (DoClockSync) ? &main_reactor.clock_sync.super : NULL); + MainRecv_ctor(&main_reactor, NULL, _lf_environment); + env.net_bundles_size = (NumBundles); + env.net_bundles = (FederatedConnectionBundle **)&main_reactor._bundles; + printf("lf_start_receiver: assembling federated environment (bundles=%d)\n", env.net_bundles_size); + _lf_environment->assemble(_lf_environment); + printf("lf_start_receiver: starting federated environment\n"); + _lf_environment->start(_lf_environment); + printf("lf_start_receiver: federated environment started\n"); + lf_exit_receiver(); +} diff --git a/examples/patmos/s4noc_fed/receiver/receiver.h b/examples/patmos/s4noc_fed/receiver/receiver.h new file mode 100644 index 000000000..657a53e3a --- /dev/null +++ b/examples/patmos/s4noc_fed/receiver/receiver.h @@ -0,0 +1 @@ +void lf_start_receiver(void); \ No newline at end of file diff --git a/examples/patmos/s4noc_fed/sender/Makefile b/examples/patmos/s4noc_fed/sender/Makefile new file mode 100644 index 000000000..4867feffc --- /dev/null +++ b/examples/patmos/s4noc_fed/sender/Makefile @@ -0,0 +1,34 @@ +include $(REACTOR_UC_PATH)/make/patmos/patmos.mk + +FED = sender + +# Paths +SRC_DIR = $(CURDIR) +BUILD_DIR = $(CURDIR)/build + +# Source files +SOURCES = $(FED).c + +OBJECTS ?= $(SOURCES:.c=.bc) + +# Output binary +OUTPUT = $(FED).a + +all: $(OUTPUT) + +# Build rule +$(OUTPUT): $(OBJECTS) + @echo "BUILDING $(notdir $@) from $^" + mkdir -p $(BUILD_DIR) + llvm-ar rcsv $(BUILD_DIR)/$(OUTPUT) $(OBJECTS); + +%.bc: %.c + @echo "$(notdir $^) COMPILED TO $(notdir $@)" + mkdir -p $(BUILD_DIR) + $(CC) $(CFLAGS) -emit-llvm -c $^ -o $@ + +clean: + rm -rf $(BUILD_DIR) + rm -f $(FED).bc + +.PHONY: all clean \ No newline at end of file diff --git a/examples/patmos/s4noc_fed/sender/sender.c b/examples/patmos/s4noc_fed/sender/sender.c new file mode 100644 index 000000000..801644a47 --- /dev/null +++ b/examples/patmos/s4noc_fed/sender/sender.c @@ -0,0 +1,158 @@ +#include "reactor-uc/platform/patmos/s4noc_channel.h" +#include "reactor-uc/schedulers/dynamic/scheduler.h" +#include "reactor-uc/reactor-uc.h" +#include "reactor-uc/startup_coordinator.h" +#include "reactor-uc/clock_synchronization.h" +#include "reactor-uc/environments/federated_environment.h" +#include + + #define RECEIVER_CORE_ID 1 + +typedef struct { + int size; + char msg[512]; +} lf_msg_t; + +int serialize_msg_t(const void *user_struct, size_t user_struct_size, unsigned char *msg_buf) { + (void)user_struct_size; + const lf_msg_t *msg = user_struct; + + memcpy(msg_buf, &msg->size, sizeof(msg->size)); + memcpy(msg_buf + sizeof(msg->size), msg->msg, msg->size); + + /* Null-terminate a temporary view for logging (do not modify msg_buf) */ + { + int printable = msg->size; + if (printable > 200) printable = 200; + char tmp[201]; + if (printable > 0) { + memcpy(tmp, msg->msg, printable); + } + tmp[printable] = '\0'; + printf("serialize_msg_t: size=%d, preview='%s'\n", msg->size, tmp); + } + + return sizeof(msg->size) + msg->size; +} + +LF_DEFINE_TIMER_STRUCT(Sender, t, 1, 0) +LF_DEFINE_TIMER_CTOR(Sender, t, 1, 0) +LF_DEFINE_REACTION_STRUCT(Sender, r, 1) +LF_DEFINE_REACTION_CTOR(Sender, r, 0, NULL, NULL) +LF_DEFINE_OUTPUT_STRUCT(Sender, out, 1, lf_msg_t) +LF_DEFINE_OUTPUT_CTOR(Sender, out, 1) + +typedef struct { + Reactor super; + LF_TIMER_INSTANCE(Sender, t); + LF_REACTION_INSTANCE(Sender, r); + LF_PORT_INSTANCE(Sender, out, 1); + LF_REACTOR_BOOKKEEPING_INSTANCES(1, 2, 0); +} Sender; + +LF_DEFINE_REACTION_BODY(Sender, r) { + LF_SCOPE_SELF(Sender); + LF_SCOPE_ENV(); + LF_SCOPE_PORT(Sender, out); + + printf("Sender: Timer triggered @ " PRINTF_TIME "\n", env->get_elapsed_logical_time(env)); + lf_msg_t val; + strcpy(val.msg, "Hello From Sender"); + val.size = sizeof("Hello From Sender"); + printf("Sender reaction: preparing message size=%d, msg='%s'\n", val.size, val.msg); + lf_set(out, val); +} + +LF_REACTOR_CTOR_SIGNATURE_WITH_PARAMETERS(Sender, OutputExternalCtorArgs *out_external) { + LF_REACTOR_CTOR_PREAMBLE(); + LF_REACTOR_CTOR(Sender); + printf("Sender: Initializing reaction and timer...\n"); + LF_INITIALIZE_REACTION(Sender, r, NEVER); + LF_INITIALIZE_TIMER(Sender, t, SEC(1), SEC(1)); // Start at 1s, period 1s + LF_INITIALIZE_OUTPUT(Sender, out, 1, out_external); + + printf("Sender: Registering timer effects and port sources...\n"); + LF_TIMER_REGISTER_EFFECT(self->t, self->r); + LF_PORT_REGISTER_SOURCE(self->out, self->r, 1); +} + +LF_DEFINE_FEDERATED_OUTPUT_CONNECTION_STRUCT(Sender, out, lf_msg_t) +LF_DEFINE_FEDERATED_OUTPUT_CONNECTION_CTOR(Sender, out, lf_msg_t, 0) + +typedef struct { + FederatedConnectionBundle super; + S4NOCPollChannel channel; + LF_FEDERATED_OUTPUT_CONNECTION_INSTANCE(Sender, out); + LF_FEDERATED_CONNECTION_BUNDLE_BOOKKEEPING_INSTANCES(0, 1); +} LF_FEDERATED_CONNECTION_BUNDLE_TYPE(Sender, Receiver); + +LF_FEDERATED_CONNECTION_BUNDLE_CTOR_SIGNATURE(Sender, Receiver) { + LF_FEDERATED_CONNECTION_BUNDLE_CTOR_PREAMBLE(); + S4NOCPollChannel_ctor(&self->channel, RECEIVER_CORE_ID); + printf("Sender: initialized channel for core %d\n", RECEIVER_CORE_ID); + LF_FEDERATED_CONNECTION_BUNDLE_CALL_CTOR(); + LF_INITIALIZE_FEDERATED_OUTPUT_CONNECTION(Sender, out, serialize_msg_t); +} + +LF_DEFINE_STARTUP_COORDINATOR_STRUCT(Sender, 1, 6); +LF_DEFINE_STARTUP_COORDINATOR_CTOR(Sender, 1, 1, 6, JOIN_IMMEDIATELY); + +LF_DEFINE_CLOCK_SYNC_STRUCT(Sender, 1, 2); +LF_DEFINE_CLOCK_SYNC_DEFAULTS_CTOR(Sender, 1, 1, true); + +// Reactor main +typedef struct { + Reactor super; + LF_CHILD_REACTOR_INSTANCE(Sender, sender, 1); + LF_FEDERATED_CONNECTION_BUNDLE_INSTANCE(Sender, Receiver); + LF_FEDERATE_BOOKKEEPING_INSTANCES(1); + LF_CHILD_OUTPUT_CONNECTIONS(sender, out, 1, 1, 1); + LF_CHILD_OUTPUT_EFFECTS(sender, out, 1, 1, 0); + LF_CHILD_OUTPUT_OBSERVERS(sender, out, 1, 1, 0); + LF_DEFINE_STARTUP_COORDINATOR(Sender); + LF_DEFINE_CLOCK_SYNC(Sender); +} MainSender; + +LF_REACTOR_CTOR_SIGNATURE(MainSender) { + LF_REACTOR_CTOR(MainSender); + LF_FEDERATE_CTOR_PREAMBLE(); + LF_DEFINE_CHILD_OUTPUT_ARGS(sender, out, 1, 1); + LF_INITIALIZE_CHILD_REACTOR_WITH_PARAMETERS(Sender, sender, 1, _sender_out_args[i]); + LF_INITIALIZE_FEDERATED_CONNECTION_BUNDLE(Sender, Receiver); + lf_connect_federated_output((Connection *)self->Sender_Receiver_bundle.outputs[0], (Port *)self->sender->out); + LF_INITIALIZE_STARTUP_COORDINATOR(Sender); + LF_INITIALIZE_CLOCK_SYNC(Sender); +} + +static MainSender main_reactor; +static FederatedEnvironment env; +Environment *_lf_environment_sender = &env.super; +// Define queues used by scheduler +LF_DEFINE_EVENT_QUEUE(Main_EventQueue, 1) +LF_DEFINE_EVENT_QUEUE(Main_SystemEventQueue, 11) +LF_DEFINE_REACTION_QUEUE(Main_ReactionQueue, 1) +static DynamicScheduler _scheduler; +static Scheduler* scheduler = &_scheduler.super; + +void lf_exit_sender(void) { + printf("lf_exit_sender: cleaning up federated environment\n"); + FederatedEnvironment_free(&env); +} + +void lf_start_sender(void) { + // Define queues used by scheduler + LF_INITIALIZE_EVENT_QUEUE(Main_EventQueue, 1) + LF_INITIALIZE_EVENT_QUEUE(Main_SystemEventQueue, 11) + LF_INITIALIZE_REACTION_QUEUE(Main_ReactionQueue, 1) + DynamicScheduler_ctor(&_scheduler, _lf_environment_sender, &Main_EventQueue.super, &Main_SystemEventQueue.super, &Main_ReactionQueue.super, FOREVER, false); + FederatedEnvironment_ctor(&env, (Reactor *)&main_reactor, scheduler, false, + (FederatedConnectionBundle **) &main_reactor._bundles, 1, &main_reactor.startup_coordinator.super, + &main_reactor.clock_sync.super); + MainSender_ctor(&main_reactor, NULL, _lf_environment_sender); + printf("lf_start_sender: assembling federated environment (bundles=%d)\n", env.net_bundles_size); + _lf_environment_sender->assemble(_lf_environment_sender); + printf("lf_start_sender: starting federated environment\n"); + _lf_environment_sender->start(_lf_environment_sender); + printf("lf_start_sender: federated environment started\n"); + lf_exit_sender(); +} diff --git a/examples/patmos/s4noc_fed/sender/sender.h b/examples/patmos/s4noc_fed/sender/sender.h new file mode 100644 index 000000000..f956c4ec0 --- /dev/null +++ b/examples/patmos/s4noc_fed/sender/sender.h @@ -0,0 +1 @@ +void lf_start_sender(void); \ No newline at end of file diff --git a/examples/patmos/s4noc_fed_lf/S4NoCFedLF/r1/Makefile b/examples/patmos/s4noc_fed_lf/S4NoCFedLF/r1/Makefile new file mode 100644 index 000000000..3070347ab --- /dev/null +++ b/examples/patmos/s4noc_fed_lf/S4NoCFedLF/r1/Makefile @@ -0,0 +1,30 @@ +LF_MAIN ?= S4NoCFedLF +LF_FED ?= r1 + +include $(REACTOR_UC_PATH)/make/patmos/patmos-lfc.mk + +# ---- Patmos specific configuration ---- +# Output directory +BIN_DIR = $(CURDIR)/bin +OBJ_DIR = $(CURDIR)/obj +OUTPUT = $(BIN_DIR)/$(LF_MAIN).a +# OBJECTS = $(patsubst %.c,$(OBJ_DIR)/%.o,$(SOURCES)) +OBJECTS ?= $(SOURCES:.c=.bc) +FILTER_OUT ?= "" + +all: $(OUTPUT) + +# Build rule +$(OUTPUT): $(OBJECTS) + @echo "BUILDING $(notdir $@) from $^ except $(FILTER_OUT)" + mkdir -p $(BIN_DIR) + llvm-ar rcsv $@ $(filter-out $(FILTER_OUT), $(OBJECTS)); + +%.bc: %.c + @echo "$(notdir $^) COMPILED TO $(notdir $@)" + mkdir -p $(OBJ_DIR) + $(CC) -emit-llvm -c $^ -o $@ $(CFLAGS) + +# Clean rule +clean: + rm -rf $(BIN_DIR) \ No newline at end of file diff --git a/examples/patmos/s4noc_fed_lf/S4NoCFedLF/r1/run_lfc.sh b/examples/patmos/s4noc_fed_lf/S4NoCFedLF/r1/run_lfc.sh new file mode 100755 index 000000000..8af94a3b1 --- /dev/null +++ b/examples/patmos/s4noc_fed_lf/S4NoCFedLF/r1/run_lfc.sh @@ -0,0 +1,5 @@ +#!/usr/bin/env bash + +LF_MAIN=S4NoCFedLF + +$REACTOR_UC_PATH/lfc/bin/lfc-dev ../../src/$LF_MAIN.lf -n -o . \ No newline at end of file diff --git a/examples/patmos/s4noc_fed_lf/S4NoCFedLF/r2/Makefile b/examples/patmos/s4noc_fed_lf/S4NoCFedLF/r2/Makefile new file mode 100644 index 000000000..3716cddda --- /dev/null +++ b/examples/patmos/s4noc_fed_lf/S4NoCFedLF/r2/Makefile @@ -0,0 +1,30 @@ +LF_MAIN ?= S4NoCFedLF +LF_FED ?= r2 + +include $(REACTOR_UC_PATH)/make/patmos/patmos-lfc.mk + +# ---- Patmos specific configuration ---- +# Output directory +BIN_DIR = $(CURDIR)/bin +OBJ_DIR = $(CURDIR)/obj +OUTPUT = $(BIN_DIR)/$(LF_MAIN).a +# OBJECTS = $(patsubst %.c,$(OBJ_DIR)/%.o,$(SOURCES)) +OBJECTS ?= $(SOURCES:.c=.bc) +FILTER_OUT ?= "" + +all: $(OUTPUT) + +# Build rule +$(OUTPUT): $(OBJECTS) + @echo "BUILDING $(notdir $@) from $^ except $(FILTER_OUT)" + mkdir -p $(BIN_DIR) + llvm-ar rcsv $@ $(filter-out $(FILTER_OUT), $(OBJECTS)); + +%.bc: %.c + @echo "$(notdir $^) COMPILED TO $(notdir $@)" + mkdir -p $(OBJ_DIR) + $(CC) -emit-llvm -c $^ -o $@ $(CFLAGS) + +# Clean rule +clean: + rm -rf $(BIN_DIR) \ No newline at end of file diff --git a/examples/patmos/s4noc_fed_lf/S4NoCFedLF/r2/run_lfc.sh b/examples/patmos/s4noc_fed_lf/S4NoCFedLF/r2/run_lfc.sh new file mode 100755 index 000000000..8af94a3b1 --- /dev/null +++ b/examples/patmos/s4noc_fed_lf/S4NoCFedLF/r2/run_lfc.sh @@ -0,0 +1,5 @@ +#!/usr/bin/env bash + +LF_MAIN=S4NoCFedLF + +$REACTOR_UC_PATH/lfc/bin/lfc-dev ../../src/$LF_MAIN.lf -n -o . \ No newline at end of file diff --git a/examples/patmos/s4noc_fed_lf/build.sh b/examples/patmos/s4noc_fed_lf/build.sh new file mode 100755 index 000000000..86a1b741d --- /dev/null +++ b/examples/patmos/s4noc_fed_lf/build.sh @@ -0,0 +1,56 @@ +#!/bin/bash + +LF_MAIN=S4NoCFedLF +BIN_DIR=bin +CC=patmos-clang +# Generate configuration templates +rm -rf $LF_MAIN $BIN_DIR +$REACTOR_UC_PATH/lfc/bin/lfc-dev --gen-fed-templates src/$LF_MAIN.lf + + +# Generate and build r1 sources +pushd ./$LF_MAIN/r1 + ./run_lfc.sh + make all +popd + +N=2 +# Generate and build other sources +for i in $(seq 2 $N); do + pushd ./$LF_MAIN/r$i + REACTOR_PATH=$(pwd)/src-gen/$LF_MAIN/r$i + ./run_lfc.sh + sed -i "s/_lf_environment/_lf_environment_$i/g; s/lf_exit/lf_exit_$i/g; s/lf_start/lf_start_$i/g" $REACTOR_PATH/lf_start.c + sed -i "s/(Federate/(Federate$i/g; s/FederateStartup/Federate${i}Startup/g; s/FederateClock/Federate${i}Clock/g; s/Reactor_${LF_MAIN}/Reactor_${LF_MAIN}_$i/g; s/${LF_MAIN}_r1/${LF_MAIN}_r1_$i/g" $REACTOR_PATH/lf_federate.h $REACTOR_PATH/lf_federate.c + make all OBJECTS="$REACTOR_PATH/lf_federate.bc $REACTOR_PATH/$LF_MAIN/Dst.bc $REACTOR_PATH/lf_start.bc" + popd +done + +mkdir -p $BIN_DIR + +A_FILES="" +for i in $(seq 1 $N); do + A_FILES="$A_FILES ./$LF_MAIN/r$i/bin/$LF_MAIN.a" +done + +chmod +x ./gen_main.sh +./gen_main.sh N=$N + +$CC -O2 -Wall -Wextra main.c $A_FILES -o $BIN_DIR/$LF_MAIN + +rm -rf $REACTOR_UC_PATH/external/nanopb/pb_encode.bc $REACTOR_UC_PATH/external/nanopb/pb_decode.bc $REACTOR_UC_PATH/external/nanopb/pb_common.bc $REACTOR_UC_PATH/external/Unity/src/unity.bc + +read -n 1 -t 10 -p "Choose action: [e]mulate or [f]pga? (default: e) " action +action=${action:-e} +if [[ "$action" == "e" ]]; then + patemu $BIN_DIR/$LF_MAIN +elif [[ "$action" == "f" ]]; then + if jtagconfig | grep -q "USB-Blaster"; then + mv $BIN_DIR/$LF_MAIN ~/t-crest/patmos/tmp/$LF_MAIN.elf + make -C ~/t-crest/patmos APP=$LF_MAIN config download + else + echo "JTAG not connected. Please connect USB-Blaster." + fi +else + echo "Invalid option. Please choose 'e' for emulate or 'f' for fpga." +fi diff --git a/examples/patmos/s4noc_fed_lf/gen_main.sh b/examples/patmos/s4noc_fed_lf/gen_main.sh new file mode 100755 index 000000000..52bff7729 --- /dev/null +++ b/examples/patmos/s4noc_fed_lf/gen_main.sh @@ -0,0 +1,74 @@ +#!/bin/bash + +# Number of federates +N=2 +rm -rf main.c + +cat >> main.c < +#include + +EOF + +# Generate include headers for each federate +for i in $(seq 1 $N); do +cat >> main.c <> main.c <> main.c <> main.c <> main.c <> main.c <> main.c <> main.c < +#include + +#include "S4NoCFedLF/r1/src-gen/S4NoCFedLF/r1/lf_start.h" +#include "S4NoCFedLF/r2/src-gen/S4NoCFedLF/r2/lf_start.h" + +void* f1_thread(void* arg) { + (void)arg; + printf("Starting federate 1 on core/thread 1\n"); + lf_start(); + return NULL; +} + +void* f2_thread(void* arg) { + (void)arg; + printf("Starting federate 2 on core/thread 2\n"); + lf_start_2(); + return NULL; +} + +int main(void) { + pthread_t threads[2]; + printf("Starting S4NOC Federated LF Example\n"); + pthread_create(&threads[0], NULL, f1_thread, NULL); + pthread_create(&threads[1], NULL, f2_thread, NULL); + + printf("Threads created for federates.\n"); + + pthread_join(threads[0], NULL); + pthread_join(threads[1], NULL); + + printf("All federates finished.\n"); + return 0; +} diff --git a/examples/patmos/s4noc_fed_lf/src/S4NoCFedLF.lf b/examples/patmos/s4noc_fed_lf/src/S4NoCFedLF.lf new file mode 100644 index 000000000..82b7c886d --- /dev/null +++ b/examples/patmos/s4noc_fed_lf/src/S4NoCFedLF.lf @@ -0,0 +1,44 @@ +target uC { + platform: PATMOS, + clock-sync: ON, + build-type: debug +} + +reactor Src(id: int = 0) { + output out: int + + reaction(startup) -> out{= + printf("Hello Patmos from Src!\n"); + lf_set(out, self->id); + =} +} + +reactor Dst { + input in: int + state check: bool = false + + reaction(startup) {= + printf("Hello Patmos from Dst!\n"); + =} + + reaction(in) {= + printf("Patmos:Received %d from Src\n", in->value); + validate(in->value == 42); + self->check = true; + env->request_shutdown(env); + =} + + reaction(shutdown) {= + validate(self->check); + =} +} + +federated reactor { + @interface_s4noc(core=2) + r1 = new Src(id=42) + + @interface_s4noc(core=1) + r2 = new Dst() + + r1.out -> r2.in +} diff --git a/examples/pico/buildAll.sh b/examples/pico/buildAll.sh index 1c84511f7..319fe8ad9 100755 --- a/examples/pico/buildAll.sh +++ b/examples/pico/buildAll.sh @@ -1,4 +1,9 @@ #!/bin/env bash set -e -cmake -Bbuild -cmake --build build + +if [ -z "$PICO_SDK_PATH" ]; then + echo "Error: PICO_SDK_PATH is not defined. Please set it before running this script." +else + cmake -Bbuild + cmake --build build +fi diff --git a/examples/riot/blinky/Makefile b/examples/riot/blinky/Makefile deleted file mode 100755 index b36e971dd..000000000 --- a/examples/riot/blinky/Makefile +++ /dev/null @@ -1,21 +0,0 @@ -# name of your application -APPLICATION = lf-test - -# This has to be the absolute path to the RIOT base directory: -RIOTBASE ?= $(CURDIR)/../../../../RIOT - -# If no BOARD is found in the environment, use this default: -BOARD ?= native - -# Comment this out to disable code in RIOT that does safety checking -# which is not needed in a production environment but helps in the -# development process: -DEVELHELP ?= 1 - -# Change this to 0 show compiler invocation lines by default: -QUIET ?= 1 - -# Enable reactor-uc features -# CFLAGS += -DNETWORK_CHANNEL_TCP_POSIX - -include $(CURDIR)/../../../make/riot/riot.mk diff --git a/examples/riot/blinky/build.sh b/examples/riot/blinky/build.sh deleted file mode 100755 index fe9a489ef..000000000 --- a/examples/riot/blinky/build.sh +++ /dev/null @@ -1,2 +0,0 @@ -#!/bin/bash -make all \ No newline at end of file diff --git a/examples/riot/blinky/main.c b/examples/riot/blinky/main.c deleted file mode 100755 index 1ccce8497..000000000 --- a/examples/riot/blinky/main.c +++ /dev/null @@ -1,17 +0,0 @@ -#include "board.h" -#include "reactor-uc/reactor-uc.h" -#include -#include "../../common/timer_source.h" - - -LF_DEFINE_REACTION_BODY(TimerSource, r) { - LF_SCOPE_SELF(TimerSource); - LF_SCOPE_ENV(); - printf("Hello World @ %lld\n", env->get_elapsed_logical_time(env)); - LED0_TOGGLE; -} - -int main() { - lf_start(); - return 0; -} diff --git a/examples/riot/buildAll.sh b/examples/riot/buildAll.sh index 3f0cb33e2..3d75f8f56 100755 --- a/examples/riot/buildAll.sh +++ b/examples/riot/buildAll.sh @@ -6,8 +6,8 @@ set -e for dir in ./*; do if [ -d $dir ]; then echo "Entering $dir" - pushd $dir - ./build.sh - popd + pushd $dir + ./build.sh + popd fi done diff --git a/include/reactor-uc/event.h b/include/reactor-uc/event.h index b734525e2..7437126a8 100644 --- a/include/reactor-uc/event.h +++ b/include/reactor-uc/event.h @@ -7,10 +7,10 @@ #include #define EVENT_INIT(Tag, Trigger, Payload) \ - {.super.type = EVENT, .super.tag = Tag, .intended_tag = Tag, .trigger = Trigger, .super.payload = Payload} + { .super.type = EVENT, .super.tag = Tag, .intended_tag = Tag, .trigger = Trigger, .super.payload = Payload } #define SYSTEM_EVENT_INIT(Tag, Handler, Payload) \ - {.super.type = SYSTEM_EVENT, .super.tag = Tag, .super.payload = Payload, .handler = Handler} + { .super.type = SYSTEM_EVENT, .super.tag = Tag, .super.payload = Payload, .handler = Handler } typedef struct Trigger Trigger; typedef struct SystemEventHandler SystemEventHandler; diff --git a/include/reactor-uc/network_channel.h b/include/reactor-uc/network_channel.h index 8796528bf..d8ce2a2bf 100644 --- a/include/reactor-uc/network_channel.h +++ b/include/reactor-uc/network_channel.h @@ -31,7 +31,8 @@ typedef enum { typedef enum { NETWORK_CHANNEL_TYPE_TCP_IP, NETWORK_CHANNEL_TYPE_COAP_UDP_IP, - NETWORK_CHANNEL_TYPE_UART + NETWORK_CHANNEL_TYPE_UART, + NETWORK_CHANNEL_TYPE_S4NOC } NetworkChannelType; typedef enum { @@ -158,6 +159,12 @@ struct AsyncNetworkChannel { #ifdef NETWORK_CHANNEL_TCP_POSIX #error "NETWORK_POSIX_TCP not supported on FlexPRET" #endif + +#elif defined(PLATFORM_PATMOS) +#ifdef NETWORK_CHANNEL_S4NOC +#include "platform/patmos/s4noc_channel.h" +#endif + #endif #endif // REACTOR_UC_NETWORK_CHANNEL_H diff --git a/include/reactor-uc/platform/patmos/s4noc_channel.h b/include/reactor-uc/platform/patmos/s4noc_channel.h new file mode 100644 index 000000000..d68beb289 --- /dev/null +++ b/include/reactor-uc/platform/patmos/s4noc_channel.h @@ -0,0 +1,41 @@ +#ifndef REACTOR_UC_S4NOC_CHANNEL_H +#define REACTOR_UC_S4NOC_CHANNEL_H + +#include "reactor-uc/network_channel.h" +#include "reactor-uc/environment.h" +#include +typedef struct FederatedConnectionBundle FederatedConnectionBundle; +typedef struct S4NOCPollChannel S4NOCPollChannel; +typedef struct S4NOCGlobalState S4NOCGlobalState; + +#define S4NOC_CHANNEL_BUFFERSIZE 1024 + +#ifndef S4NOC_CORE_COUNT +#define S4NOC_CORE_COUNT 4 +#endif + +struct S4NOCGlobalState { + S4NOCPollChannel *core_channels[S4NOC_CORE_COUNT][S4NOC_CORE_COUNT]; +}; + +extern S4NOCGlobalState s4noc_global_state; + +struct S4NOCPollChannel { + PolledNetworkChannel super; + NetworkChannelState state; + + FederateMessage output; + unsigned char write_buffer[S4NOC_CHANNEL_BUFFERSIZE]; + unsigned char receive_buffer[S4NOC_CHANNEL_BUFFERSIZE]; + unsigned int receive_buffer_index; + unsigned int destination_core; + + FederatedConnectionBundle *federated_connection; + void (*receive_callback)(FederatedConnectionBundle *conn, const FederateMessage *message); + pthread_t worker_thread; + bool send_response; + bool received_response; +}; + +void S4NOCPollChannel_ctor(S4NOCPollChannel *self, unsigned int destination_core); +#endif diff --git a/include/reactor-uc/tag.h b/include/reactor-uc/tag.h index 3e52188b9..fb514912d 100644 --- a/include/reactor-uc/tag.h +++ b/include/reactor-uc/tag.h @@ -40,14 +40,19 @@ .time = NEVER, .microstep = NEVER_MICROSTEP \ } // Need a separate initializer expression to comply with some C compilers -#define NEVER_TAG_INITIALIZER {NEVER, NEVER_MICROSTEP} +#define NEVER_TAG_INITIALIZER \ + { NEVER, NEVER_MICROSTEP } #define FOREVER_TAG \ (tag_t) { \ .time = FOREVER, .microstep = FOREVER_MICROSTEP \ } // Need a separate initializer expression to comply with some C compilers -#define FOREVER_TAG_INITIALIZER {FOREVER, FOREVER_MICROSTEP} -#define ZERO_TAG (tag_t){.time = 0LL, .microstep = 0u} +#define FOREVER_TAG_INITIALIZER \ + { FOREVER, FOREVER_MICROSTEP } +#define ZERO_TAG \ + (tag_t) { \ + .time = 0LL, .microstep = 0u \ + } // Returns true if timeout has elapsed. #define CHECK_TIMEOUT(start, duration) (lf_time_physical() > ((start) + (duration))) diff --git a/lfc/core/src/main/java/org/lflang/target/property/PlatformProperty.java b/lfc/core/src/main/java/org/lflang/target/property/PlatformProperty.java index 82c8038d2..1c3c5188e 100644 --- a/lfc/core/src/main/java/org/lflang/target/property/PlatformProperty.java +++ b/lfc/core/src/main/java/org/lflang/target/property/PlatformProperty.java @@ -121,6 +121,9 @@ public void validate(TargetConfig config, MessageReporter reporter) { case ZEPHYR: validateZephyr(config, reporter); break; + case PATMOS: + validatePatmos(config, reporter); + break; default: break; } @@ -165,6 +168,8 @@ private void validateFlexPRET(TargetConfig config, MessageReporter reporter) { private void validateZephyr(TargetConfig config, MessageReporter reporter) {} + private void validatePatmos(TargetConfig config, MessageReporter reporter) {} + @Override public Element toAstElement(PlatformOptions value) { Element e = LfFactory.eINSTANCE.createElement(); diff --git a/lfc/core/src/main/java/org/lflang/target/property/type/PlatformType.java b/lfc/core/src/main/java/org/lflang/target/property/type/PlatformType.java index 0cc6b2f56..a92456534 100644 --- a/lfc/core/src/main/java/org/lflang/target/property/type/PlatformType.java +++ b/lfc/core/src/main/java/org/lflang/target/property/type/PlatformType.java @@ -21,7 +21,8 @@ public enum Platform { ZEPHYR("Zephyr"), RIOT("RIOT"), FLEXPRET("FlexPRET"), - WINDOWS("Windows"); + WINDOWS("Windows"), + PATMOS("Patmos"); final String cMakeName; diff --git a/lfc/core/src/main/java/org/lflang/validation/AttributeSpec.java b/lfc/core/src/main/java/org/lflang/validation/AttributeSpec.java index 908f0f500..1056ef832 100644 --- a/lfc/core/src/main/java/org/lflang/validation/AttributeSpec.java +++ b/lfc/core/src/main/java/org/lflang/validation/AttributeSpec.java @@ -303,6 +303,9 @@ enum AttrParamType { List.of( new AttrParamSpec("name", AttrParamType.STRING, true), new AttrParamSpec("address", AttrParamType.STRING, true)))); + ATTRIBUTE_SPECS_BY_NAME.put( + "interface_s4noc", + new AttributeSpec(List.of(new AttrParamSpec("core", AttrParamType.INT, false)))); ATTRIBUTE_SPECS_BY_NAME.put( "interface_custom", new AttributeSpec( @@ -327,6 +330,8 @@ enum AttrParamType { ATTRIBUTE_SPECS_BY_NAME.put("platform_riot", new AttributeSpec(null)); // @platform_zephyr ATTRIBUTE_SPECS_BY_NAME.put("platform_zephyr", new AttributeSpec(null)); + // @platform_patmos + ATTRIBUTE_SPECS_BY_NAME.put("platform_patmos", new AttributeSpec(null)); // @platform_native ATTRIBUTE_SPECS_BY_NAME.put("platform_native", new AttributeSpec(null)); // @clock_sync(grandmaster=true, server_port=1042) diff --git a/lfc/core/src/main/kotlin/org/lflang/generator/uc/UcFederatedProjectTemplateGenerator.kt b/lfc/core/src/main/kotlin/org/lflang/generator/uc/UcFederatedProjectTemplateGenerator.kt index 96fec14d7..2804ddc39 100644 --- a/lfc/core/src/main/kotlin/org/lflang/generator/uc/UcFederatedProjectTemplateGenerator.kt +++ b/lfc/core/src/main/kotlin/org/lflang/generator/uc/UcFederatedProjectTemplateGenerator.kt @@ -132,6 +132,44 @@ class UcFederatedTemplateGenerator( FileUtil.writeToFile(cmake, projectRoot.resolve("CMakeLists.txt")) } + private fun generateFilesPatmos() { + val make = + """ + |LF_MAIN ?= ${mainDef.name} + |LF_FED ?= ${federate.name} + | + |include $S(REACTOR_UC_PATH)/make/patmos/patmos-lfc.mk + | + |# ---- Patmos specific configuration ---- + |# Output directory + |BIN_DIR = $(CURDIR)/bin + |OBJ_DIR = $(CURDIR)/obj + |OUTPUT = $(BIN_DIR)/$(LF_MAIN).a + |# OBJECTS = $(patsubst %.c,$(OBJ_DIR)/%.o,$(SOURCES)) + |OBJECTS ?= $(SOURCES:.c=.bc) + |FILTER_OUT ?= "" + | + |all: $(OUTPUT) + | + |# Build rule + |$(OUTPUT): $(OBJECTS) + | @echo "BUILDING $(notdir $@) from $^ except $(FILTER_OUT)" + | mkdir -p $(BIN_DIR) + | llvm-ar rcsv $@ $(filter-out $(FILTER_OUT), $(OBJECTS)); + | + |%.bc: %.c + | @echo "$(notdir $^) COMPILED TO $(notdir $@)" + | mkdir -p $(OBJ_DIR) + | $(CC) -emit-llvm -c $^ -o $@ $(CFLAGS) + | + |# Clean rule + |clean: + | rm -rf $(BIN_DIR) + """ + .trimMargin() + FileUtil.writeToFile(make, projectRoot.resolve("Makefile")) + } + fun generateFiles() { if (Files.exists(projectRoot)) { // Skipping since project template already exists @@ -151,6 +189,7 @@ class UcFederatedTemplateGenerator( PlatformType.Platform.NATIVE -> generateFilesNative() PlatformType.Platform.ZEPHYR -> generateFilesZephyr() PlatformType.Platform.RIOT -> generateFilesRiot() + PlatformType.Platform.PATMOS -> generateFilesPatmos() else -> messageReporter .nowhere() diff --git a/lfc/core/src/main/kotlin/org/lflang/generator/uc/UcMakeGenerator.kt b/lfc/core/src/main/kotlin/org/lflang/generator/uc/UcMakeGenerator.kt index 53e921003..1376e1c8f 100644 --- a/lfc/core/src/main/kotlin/org/lflang/generator/uc/UcMakeGenerator.kt +++ b/lfc/core/src/main/kotlin/org/lflang/generator/uc/UcMakeGenerator.kt @@ -54,6 +54,7 @@ class UcMakeGeneratorFederated( NetworkChannelType.TCP_IP -> "CFLAGS += -DNETWORK_CHANNEL_TCP" NetworkChannelType.COAP_UDP_IP -> "CFLAGS += -DNETWORK_CHANNEL_COAP" NetworkChannelType.UART -> "CFLAGS += -DNETWORK_CHANNEL_UART" + NetworkChannelType.S4NOC -> "CFLAGS += -DNETWORK_CHANNEL_S4NOC" NetworkChannelType.NONE -> "" NetworkChannelType.CUSTOM -> "" } diff --git a/lfc/core/src/main/kotlin/org/lflang/generator/uc/UcNetworkChannel.kt b/lfc/core/src/main/kotlin/org/lflang/generator/uc/UcNetworkChannel.kt index ec6db3c73..64ae6c660 100644 --- a/lfc/core/src/main/kotlin/org/lflang/generator/uc/UcNetworkChannel.kt +++ b/lfc/core/src/main/kotlin/org/lflang/generator/uc/UcNetworkChannel.kt @@ -10,6 +10,7 @@ enum class NetworkChannelType { TCP_IP, CUSTOM, COAP_UDP_IP, + S4NOC, UART, NONE } @@ -23,7 +24,8 @@ object UcNetworkInterfaceFactory { UcCoapUdpIpInterface.fromAttribute(federate, attr) }, Pair(CUSTOM) { federate, attr -> UcCustomInterface.fromAttribute(federate, attr) }, - Pair(UART) { federate, attr -> UcUARTInterface.fromAttribute(federate, attr) }) + Pair(UART) { federate, attr -> UcUARTInterface.fromAttribute(federate, attr) }, + Pair(S4NOC) { federate, attr -> UcS4NocInterface.fromAttribute(federate, attr) }) fun createInterfaces(federate: UcFederate): List { val attrs: List = getInterfaceAttributes(federate.inst) @@ -43,6 +45,7 @@ object UcNetworkInterfaceFactory { "tcp" -> creators.get(TCP_IP)!!.invoke(federate, attr) "uart" -> creators.get(UART)!!.invoke(federate, attr) "coap" -> creators.get(COAP_UDP_IP)!!.invoke(federate, attr) + "s4noc" -> creators.get(S4NOC)!!.invoke(federate, attr) "custom" -> creators.get(CUSTOM)!!.invoke(federate, attr) else -> throw IllegalArgumentException("Unrecognized interface attribute $attr") } @@ -72,6 +75,8 @@ class UcUARTEndpoint( class UcCoapUdpIpEndpoint(val ipAddress: IPAddress, iface: UcCoapUdpIpInterface) : UcNetworkEndpoint(iface) {} +class UcS4NocEndpoint(val core: Int, iface: UcS4NocInterface) : UcNetworkEndpoint(iface) {} + class UcCustomEndpoint(iface: UcCustomInterface) : UcNetworkEndpoint(iface) {} // A federate can have several NetworkInterfaces, which are specified using attributes in the LF @@ -195,6 +200,30 @@ class UcCoapUdpIpInterface(private val ipAddress: IPAddress, name: String? = nul } } +class UcS4NocInterface(val core: Int, name: String? = null) : + UcNetworkInterface(S4NOC, name ?: "s4noc") { + override val includeHeaders: String = "" + override val compileDefs: String = "NETWORK_CHANNEL_S4NOC" + + init { + println("UcS4NocInterface created with core=$core and name=${name ?: "s4noc"}") + } + + fun createEndpoint(): UcS4NocEndpoint { + val ep = UcS4NocEndpoint(core, this) + endpoints.add(ep) + return ep + } + + companion object { + fun fromAttribute(federate: UcFederate, attr: Attribute): UcS4NocInterface { + val core = attr.getParamInt("core") ?: 0 + val name = attr.getParamString("name") + return UcS4NocInterface(core, name) + } + } +} + class UcCustomInterface(name: String, val include: String, val args: String? = null) : UcNetworkInterface(CUSTOM, name) { override val compileDefs = "" @@ -288,7 +317,11 @@ abstract class UcNetworkChannel( val destEp = (destIf as UcCoapUdpIpInterface).createEndpoint() channel = UcCoapUdpIpChannel(srcEp, destEp) } - + S4NOC -> { + val srcEp = (srcIf as UcS4NocInterface).createEndpoint() + val destEp = (destIf as UcS4NocInterface).createEndpoint() + channel = UcS4NocChannel(srcEp, destEp) + } CUSTOM -> { val srcEp = (srcIf as UcCustomInterface).createEndpoint() val destEp = (destIf as UcCustomInterface).createEndpoint() @@ -362,6 +395,22 @@ class UcCoapUdpIpChannel( get() = "CoapUdpIpChannel" } +class UcS4NocChannel( + src: UcS4NocEndpoint, + dest: UcS4NocEndpoint, +) : UcNetworkChannel(S4NOC, src, dest, false) { + private val srcS4Noc = src + private val destS4Noc = dest + + override fun generateChannelCtorSrc() = "S4NOCPollChannel_ctor(&self->channel, ${srcS4Noc.core});" + + override fun generateChannelCtorDest() = + "S4NOCPollChannel_ctor(&self->channel, ${destS4Noc.core});" + + override val codeType: String + get() = "S4NOCPollChannel" +} + class UcCustomChannel( src: UcCustomEndpoint, dest: UcCustomEndpoint, diff --git a/make/patmos/patmos-lfc.mk b/make/patmos/patmos-lfc.mk new file mode 100644 index 000000000..cd788d287 --- /dev/null +++ b/make/patmos/patmos-lfc.mk @@ -0,0 +1,51 @@ +PATMOS_MK_DIR := $(dir $(lastword $(MAKEFILE_LIST))) + +# Check if required environment variables exist +ifndef LF_MAIN + $(error LF_MAIN is not defined. Please define it!) +endif + +# Check if this is a federated program +ifdef LF_FED + $(info Building federated program: $(LF_MAIN) with federation: $(LF_FED)) + # Name of your application + APPLICATION ?= $(LF_MAIN)-$(LF_FED) + + # Path of generated lf c-code + LF_SRC_GEN_PATH ?= $(CURDIR)/src-gen/$(LF_MAIN)/$(LF_FED) +else + $(info Building non-federated program: $(LF_MAIN)) + # Name of your application + APPLICATION ?= $(LF_MAIN) + + # Path of generated lf c-code + LF_SRC_GEN_PATH ?= $(CURDIR)/src-gen/$(LF_MAIN) +endif + +# Only include generated files if build target is not "clean" +# In this case the src-gen folder was deleted +ifeq ($(firstword $(MAKECMDGOALS)),clean) + # Delete src-gen folder if build target is "clean" + _ := $(shell rm -rf $(LF_SRC_GEN_PATH)) +else + + # Include the Makefile of the generated target application + include $(LF_SRC_GEN_PATH)/Makefile + + # Include generated c files + SOURCES += $(patsubst %, $(LF_SRC_GEN_PATH)/%, $(LFC_GEN_SOURCES)) + + # Include generated main file + ifndef LF_FED + SOURCES += $(LF_SRC_GEN_PATH)/${LFC_GEN_MAIN} + endif + + # Include generated h files + CFLAGS += -I$(LF_SRC_GEN_PATH) + + # Add compile definitions produced by the LF compiler + CFLAGS += $(patsubst %, -D%, $(LFC_GEN_COMPILE_DEFS)) + + include $(PATMOS_MK_DIR)/patmos.mk +endif + diff --git a/make/patmos/patmos.mk b/make/patmos/patmos.mk new file mode 100644 index 000000000..e4b646ad5 --- /dev/null +++ b/make/patmos/patmos.mk @@ -0,0 +1,38 @@ + +# This Makefile is used to build the Patmos platform for the Reactor-UC project. +# It includes the necessary paths, sources, and compiler flags for the Patmos architecture. + +# Compiler and tools for PATMOS +CC = patmos-clang + +# Includes +CFLAGS += -I$(REACTOR_UC_PATH)/ +CFLAGS += -I$(REACTOR_UC_PATH)/include +CFLAGS += -I$(REACTOR_UC_PATH)/include/reactor-uc +CFLAGS += -I$(REACTOR_UC_PATH)/include/reactor-uc/platform +CFLAGS += -I$(REACTOR_UC_PATH)/include/reactor-uc/platform/patmos +CFLAGS += -I$(REACTOR_UC_PATH)/external + +CFLAGS += -I$(REACTOR_UC_PATH)/external/nanopb +CFLAGS += -I$(REACTOR_UC_PATH)/external/nanopb/pb + +CFLAGS += -I$(REACTOR_UC_PATH)/external/proto + +CFLAGS += -I$(REACTOR_UC_PATH)/external/Unity/src +CFLAGS += -I$(REACTOR_UC_PATH)/test/unit + +UNITY_DIR = $(REACTOR_UC_PATH)/external/Unity +SOURCES += $(UNITY_DIR)/src/unity.c + +SOURCES += $(wildcard $(REACTOR_UC_PATH)/src/*.c) + +SOURCES += $(wildcard $(REACTOR_UC_PATH)/external/nanopb/*.c) +SOURCES += $(REACTOR_UC_PATH)/external/proto/message.pb.c + +CFLAGS += -O2 +CFLAGS += -Wall -Wextra +CFLAGS += -DPLATFORM_PATMOS +CFLAGS += -DNETWORK_CHANNEL_S4NOC +CFLAGS += -DSCHEDULER_DYNAMIC +CFLAGS += -DFEDERATED +CFLAGS += -DLF_LOG_LEVEL_ALL=LF_LOG_LEVEL_DEBUG \ No newline at end of file diff --git a/src/logging.c b/src/logging.c index 0b13dbc3a..b6eea8296 100644 --- a/src/logging.c +++ b/src/logging.c @@ -4,6 +4,7 @@ #include #include +#include #define ANSI_COLOR_RED "\x1b[31m" #define ANSI_COLOR_GREEN "\x1b[32m" @@ -13,10 +14,16 @@ #define ANSI_COLOR_CYAN "\x1b[36m" #define ANSI_COLOR_RESET "\x1b[0m" +static pthread_mutex_t log_lock = PTHREAD_MUTEX_INITIALIZER; + void log_printf(const char *fmt, ...) { va_list args; va_start(args, fmt); + + pthread_mutex_lock(&log_lock); Platform_vprintf(fmt, args); + pthread_mutex_unlock(&log_lock); + va_end(args); } @@ -40,6 +47,8 @@ void log_message(int level, const char *module, const char *fmt, ...) { break; } + pthread_mutex_lock(&log_lock); + va_list args; va_start(args, fmt); @@ -67,7 +76,11 @@ void log_message(int level, const char *module, const char *fmt, ...) { if (_lf_environment) { timestamp = _lf_environment->platform->get_physical_time(_lf_environment->platform); } +#if defined(PLATFORM_PATMOS) + log_printf("(" PRINTF_TIME ") [%s] [%s] [%d] ", timestamp, level_str, module, get_cpuid()); +#else log_printf("(" PRINTF_TIME ") [%s] [%s] ", timestamp, level_str, module); +#endif #else log_printf("[%s] [%s] ", level_str, module); @@ -83,5 +96,8 @@ void log_message(int level, const char *module, const char *fmt, ...) { #else log_printf("\n"); #endif + + pthread_mutex_unlock(&log_lock); + va_end(args); } diff --git a/src/network_channel.c b/src/network_channel.c index 43c91f93f..f690dd31e 100644 --- a/src/network_channel.c +++ b/src/network_channel.c @@ -31,8 +31,8 @@ #endif #elif defined(PLATFORM_PATMOS) -#ifdef NETWORK_CHANNEL_TCP_POSIX -#error "NETWORK_POSIX_TCP not supported on Patmos" +#ifdef NETWORK_CHANNEL_S4NOC +#include "platform/patmos/s4noc_channel.c" #endif #endif diff --git a/src/platform/patmos/patmos.c b/src/platform/patmos/patmos.c index 0ae35c732..e1ece6440 100644 --- a/src/platform/patmos/patmos.c +++ b/src/platform/patmos/patmos.c @@ -2,6 +2,8 @@ #include #include #include +#include +#include "reactor-uc/logging.h" #include #include @@ -19,32 +21,20 @@ instant_t PlatformPatmos_get_physical_time(Platform *super) { lf_ret_t PlatformPatmos_wait_until_interruptible(Platform *super, instant_t wakeup_time) { PlatformPatmos *self = (PlatformPatmos *)super; - self->async_event = false; - super->leave_critical_section(super); // turing on interrupts instant_t now = super->get_physical_time(super); + LF_DEBUG(PLATFORM, "PlatformPatmos_wait_until_interruptible: now: %llu sleeping until %llu", now, wakeup_time); // Do busy sleep do { now = super->get_physical_time(super); - } while ((now < wakeup_time) && !self->async_event); - - super->enter_critical_section(super); - - if (self->async_event) { - self->async_event = false; - return LF_ERR; - } else { - return LF_OK; - } + } while (now < wakeup_time); interval_t sleep_duration = wakeup_time - super->get_physical_time(super); if (sleep_duration < 0) { return LF_OK; } - super->leave_critical_section(super); - return LF_OK; } @@ -55,6 +45,7 @@ lf_ret_t PlatformPatmos_wait_until(Platform *super, instant_t wakeup_time) { } instant_t now = super->get_physical_time(super); + LF_DEBUG(PLATFORM, "PlatformPatmos_wait_until: now: %llu sleeping until %llu", now, wakeup_time); // Do busy sleep do { @@ -70,11 +61,12 @@ lf_ret_t PlatformPatmos_wait_for(Platform *super, interval_t duration) { instant_t now = super->get_physical_time(super); instant_t wakeup = now + duration; + LF_DEBUG(PLATFORM, "PlatformPatmos_wait_for: now: %llu sleeping for %llu", now, duration); // Do busy sleep do { now = super->get_physical_time(super); - } while ((now < wakeup)); + } while (now < wakeup); return LF_OK; } @@ -97,9 +89,10 @@ void Platform_ctor(Platform *super) { super->get_physical_time = PlatformPatmos_get_physical_time; super->wait_until = PlatformPatmos_wait_until; super->wait_for = PlatformPatmos_wait_for; - super->wait_until_interruptible_locked = PlatformPatmos_wait_until_interruptible; + super->wait_until_interruptible = PlatformPatmos_wait_until_interruptible; super->notify = PlatformPatmos_notify; self->num_nested_critical_sections = 0; + LF_DEBUG(PLATFORM, "PlatformPatmos initialized"); } Platform *Platform_new(void) { @@ -130,5 +123,4 @@ void Mutex_ctor(Mutex *super) { MutexPatmos *self = (MutexPatmos *)super; super->lock = MutexPatmos_lock; super->unlock = MutexPatmos_unlock; - critical_section_init(&self->crit_sec); -} \ No newline at end of file +} diff --git a/src/platform/patmos/s4noc_channel.c b/src/platform/patmos/s4noc_channel.c new file mode 100644 index 000000000..2966b31ac --- /dev/null +++ b/src/platform/patmos/s4noc_channel.c @@ -0,0 +1,306 @@ +#include "reactor-uc/logging.h" +#include "reactor-uc/serialization.h" +#include +#include +#include +#include +#include + +#define S4NOC_CHANNEL_ERR(fmt, ...) LF_ERR(NET, "S4NOCPollChannel: " fmt, ##__VA_ARGS__) +#define S4NOC_CHANNEL_WARN(fmt, ...) LF_WARN(NET, "S4NOCPollChannel: " fmt, ##__VA_ARGS__) +#define S4NOC_CHANNEL_INFO(fmt, ...) LF_INFO(NET, "S4NOCPollChannel: " fmt, ##__VA_ARGS__) +#define S4NOC_CHANNEL_DEBUG(fmt, ...) LF_DEBUG(NET, "S4NOCPollChannel: " fmt, ##__VA_ARGS__) + +#ifndef HANDLE_NEW_CONNECTIONS +#define HANDLE_NEW_CONNECTIONS 0 +#endif + +#if HANDLE_NEW_CONNECTIONS +#define S4NOC_OPEN_MESSAGE_REQUEST \ + { 0xC0, 0x18, 0x11, 0xC0 } +#define S4NOC_OPEN_MESSAGE_RESPONSE \ + { 0xC0, 0xFF, 0x31, 0xC0 } +#endif + +S4NOCGlobalState s4noc_global_state = {0}; + +void S4NOCPollChannel_poll(NetworkChannel *untyped_self); + +static void S4NOCPollChannel_close_connection(NetworkChannel *untyped_self) { + S4NOC_CHANNEL_DEBUG("Close connection"); + S4NOCPollChannel *self = (S4NOCPollChannel *)untyped_self; + self->state = NETWORK_CHANNEL_STATE_CLOSED; +} + +static void S4NOCPollChannel_free(NetworkChannel *untyped_self) { + S4NOC_CHANNEL_DEBUG("Free"); + (void)untyped_self; +} + +static lf_ret_t S4NOCPollChannel_is_connected(NetworkChannel *untyped_self) { + S4NOCPollChannel *self = (S4NOCPollChannel *)untyped_self; +#if HANDLE_NEW_CONNECTIONS + volatile _IODEV int *s4noc_data = (volatile _IODEV int *)(PATMOS_IO_S4NOC + 4); + volatile _IODEV int *s4noc_dest = (volatile _IODEV int *)(PATMOS_IO_S4NOC + 8); + + if (!self->received_response) { + *s4noc_dest = self->destination_core; + uint8_t connect_message[] = S4NOC_OPEN_MESSAGE_REQUEST; + uint32_t w = 0; + memcpy(&w, connect_message, sizeof(w)); + S4NOC_CHANNEL_DEBUG("Open Connection - Sending ping"); + *s4noc_data = (int)w; + } + + // poll if your channel is disconnected + if (self->state != NETWORK_CHANNEL_STATE_CONNECTED) { + S4NOCPollChannel_poll(untyped_self); + S4NOC_CHANNEL_WARN("Channel is not connected (state=%d)", self->state); + } + + S4NOC_CHANNEL_DEBUG("Network is %s", NetworkChannel_state_to_string(self->state)); + return self->state == NETWORK_CHANNEL_STATE_CONNECTED && self->received_response && self->send_response; +#else + S4NOC_CHANNEL_DEBUG("New connection handling disabled; assuming connected."); + return self->state == NETWORK_CHANNEL_STATE_CONNECTED; +#endif +} + +static lf_ret_t S4NOCPollChannel_open_connection(NetworkChannel *untyped_self) { + S4NOC_CHANNEL_DEBUG("Open connection"); + // S4NOCPollChannel *self = (S4NOCPollChannel *)untyped_self; + // self->state = NETWORK_CHANNEL_STATE_CONNECTED; + S4NOCPollChannel_is_connected(untyped_self); + return LF_OK; +} + +void printf_msg(char *header, const FederateMessage *message) { + S4NOC_CHANNEL_INFO("%s: FederateMessage type: %d", header, message->which_message); + + if (message->which_message == FederateMessage_clock_sync_msg_tag) { + S4NOC_CHANNEL_INFO("ClockSyncMessage:"); + // ClockSyncMessage + switch (message->message.clock_sync_msg.which_message) { + case ClockSyncMessage_priority_request_tag: + S4NOC_CHANNEL_INFO("ClockPriorityRequest"); + break; + case ClockSyncMessage_priority_tag: + S4NOC_CHANNEL_INFO("ClockPriority: priority = %" PRIu64, + message->message.clock_sync_msg.message.priority.priority); + break; + case ClockSyncMessage_request_sync_tag: + S4NOC_CHANNEL_INFO("RequestSync: seq = %d", message->message.clock_sync_msg.message.request_sync.sequence_number); + break; + case ClockSyncMessage_sync_response_tag: + S4NOC_CHANNEL_INFO("SyncResponse: seq = %d, time = %" PRIu64, + message->message.clock_sync_msg.message.sync_response.sequence_number, + message->message.clock_sync_msg.message.sync_response.time); + break; + case ClockSyncMessage_delay_request_tag: + S4NOC_CHANNEL_INFO("DelayRequest: seq = %d", + message->message.clock_sync_msg.message.delay_request.sequence_number); + break; + case ClockSyncMessage_delay_response_tag: + S4NOC_CHANNEL_INFO("DelayResponse: seq = %d, time = %" PRIu64, + message->message.clock_sync_msg.message.delay_response.sequence_number, + message->message.clock_sync_msg.message.delay_response.time); + break; + case ClockSyncMessage_priority_broadcast_request_tag: + S4NOC_CHANNEL_INFO("ClockPriorityBroadcastRequest"); + break; + default: + S4NOC_CHANNEL_WARN("Unknown ClockSyncMessage type: %d", message->message.clock_sync_msg.which_message); + break; + } + + } else if (message->which_message == FederateMessage_tagged_message_tag) { + // TaggedMessage + S4NOC_CHANNEL_INFO("TaggedMessage: tag = %" PRIu64 ", conn_id = %d", message->message.tagged_message.tag.time, + message->message.tagged_message.conn_id); + + } else if (message->which_message == FederateMessage_startup_coordination_tag) { + // StartupCoordination + S4NOC_CHANNEL_INFO("StartupCoordination:"); + switch (message->message.startup_coordination.which_message) { + case StartupCoordination_startup_handshake_request_tag: + S4NOC_CHANNEL_INFO("StartupHandshakeRequest"); + break; + case StartupCoordination_startup_handshake_response_tag: + S4NOC_CHANNEL_INFO("StartupHandshakeResponse: state = %d", + message->message.startup_coordination.message.startup_handshake_response.state); + break; + case StartupCoordination_start_time_proposal_tag: + S4NOC_CHANNEL_INFO("StartTimeProposal: time = %" PRIu64 ", step = %" PRIu32, + message->message.startup_coordination.message.start_time_proposal.time, + message->message.startup_coordination.message.start_time_proposal.step); + break; + case StartupCoordination_start_time_response_tag: + S4NOC_CHANNEL_INFO("StartTimeResponse: time = %" PRIu64 ", federation_start_time = %" PRIu64, + message->message.startup_coordination.message.start_time_response.elapsed_logical_time, + message->message.startup_coordination.message.start_time_response.federation_start_time); + break; + case StartupCoordination_start_time_request_tag: + S4NOC_CHANNEL_INFO("StartTimeRequest"); + break; + default: + S4NOC_CHANNEL_WARN("Unknown StartupCoordination type: %d", message->message.startup_coordination.which_message); + break; + } + } else { + S4NOC_CHANNEL_WARN("Unknown FederateMessage type: %d", message->which_message); + } +} + +static lf_ret_t S4NOCPollChannel_send_blocking(NetworkChannel *untyped_self, const FederateMessage *message) { + S4NOCPollChannel *self = (S4NOCPollChannel *)untyped_self; + + volatile _IODEV int *s4noc_data = (volatile _IODEV int *)(PATMOS_IO_S4NOC + 4); + volatile _IODEV int *s4noc_dest = (volatile _IODEV int *)(PATMOS_IO_S4NOC + 8); + S4NOC_CHANNEL_DEBUG("S4NOCPollChannel_send_blocking from core %d to core %d", get_cpuid(), self->destination_core); + if (self->state == NETWORK_CHANNEL_STATE_CONNECTED) { + // Print the FederateMessage type before sending + printf_msg("sending msg type:", message); + int message_size = serialize_to_protobuf(message, self->write_buffer + 4, S4NOC_CHANNEL_BUFFERSIZE - 4); + + uint32_t sz32 = (uint32_t)message_size; + memcpy(self->write_buffer, &sz32, sizeof(sz32)); + // *((int *)self->write_buffer) = message_size; + // S4NOC_CHANNEL_DEBUG("S4NOCPollChannel_send_blocking: message size: ((%d)).", message_size); + int total_size = message_size + 4; + S4NOC_CHANNEL_DEBUG("Total size to send: ((%d))", total_size); + + *s4noc_dest = self->destination_core; + int bytes_send = 0; + while (bytes_send < total_size) { + *s4noc_data = ((int *)self->write_buffer)[bytes_send / 4]; + bytes_send += 4; + } + // S4NOC_CHANNEL_DEBUG("Sent ((%d)) bytes", bytes_send); + return LF_OK; + } else { + S4NOC_CHANNEL_ERR("Cannot send: Channel is not connected"); + return LF_ERR; + } +} + +static void S4NOCPollChannel_register_receive_callback(NetworkChannel *untyped_self, + void (*receive_callback)(FederatedConnectionBundle *conn, + const FederateMessage *msg), + FederatedConnectionBundle *conn) { + S4NOC_CHANNEL_INFO("Register receive callback at %p", receive_callback); + S4NOCPollChannel *self = (S4NOCPollChannel *)untyped_self; + + self->receive_callback = receive_callback; + self->federated_connection = conn; +} + +void S4NOCPollChannel_poll(NetworkChannel *untyped_self) { + S4NOCPollChannel *self = (S4NOCPollChannel *)untyped_self; + volatile _IODEV int *s4noc_status = (volatile _IODEV int *)PATMOS_IO_S4NOC; + volatile _IODEV int *s4noc_data = (volatile _IODEV int *)(PATMOS_IO_S4NOC + 4); + volatile _IODEV int *s4noc_source = (volatile _IODEV int *)(PATMOS_IO_S4NOC + 8); + +// if unconnected, and s4noc data available, respond. +#if HANDLE_NEW_CONNECTIONS + if ((self->state != NETWORK_CHANNEL_STATE_CONNECTED) && (((*s4noc_status) & 0x02) != 0)) { + uint32_t word = (uint32_t)(*s4noc_data); + uint8_t req_bytes[] = S4NOC_OPEN_MESSAGE_REQUEST; + uint8_t resp_bytes[] = S4NOC_OPEN_MESSAGE_RESPONSE; + uint32_t req_word = 0; + uint32_t resp_word = 0; + memcpy(&req_word, req_bytes, sizeof(req_word)); + memcpy(&resp_word, resp_bytes, sizeof(resp_word)); + S4NOC_CHANNEL_DEBUG("Data available on S4NOC while channel is unconnected. word=0x%08x", word); + if (word == req_word) { + S4NOC_CHANNEL_INFO("Responding to S4NOC open message"); + *s4noc_data = (int)resp_word; + // set destination/source appropriately (hardware doc dependent) + *s4noc_source = self->destination_core; + self->send_response = true; + } else if (word == resp_word) { + S4NOC_CHANNEL_INFO("Received S4NOC open message response"); + self->state = NETWORK_CHANNEL_STATE_CONNECTED; + self->received_response = true; + } + + return; + } +#endif + // Check if data is available on the S4NOC interface + if (((*s4noc_status) & 0x02) == 0) { + S4NOC_CHANNEL_INFO("S4NOCPollChannel_poll: No data is available"); // if i remove it platform-test doesn't work + return; + } + + int value = *s4noc_data; + int source = *s4noc_source; + S4NOC_CHANNEL_INFO("S4NOCPollChannel_poll: Received data 0x%08x (%c%c%c%c) from source %d", value, + ((char *)&value)[0], ((char *)&value)[1], ((char *)&value)[2], ((char *)&value)[3], source); + // Get the receive channel for the source core + S4NOCPollChannel *receive_channel = s4noc_global_state.core_channels[source][get_cpuid()]; + + if (receive_channel == NULL) { + S4NOC_CHANNEL_WARN("No receive_channel for source=%d dest=%d - dropping word", source, get_cpuid()); + return; + } + + if (receive_channel->receive_buffer_index + 4 > S4NOC_CHANNEL_BUFFERSIZE) { + S4NOC_CHANNEL_WARN("Receive buffer overflow: dropping message"); + receive_channel->receive_buffer_index = 0; + return; + } + + ((int *)receive_channel->receive_buffer)[receive_channel->receive_buffer_index / 4] = value; + receive_channel->receive_buffer_index += 4; + // S4NOC_CHANNEL_DEBUG("receive_buffer_index ((%d))", receive_channel->receive_buffer_index); + unsigned int expected_message_size = *((int *)receive_channel->receive_buffer); + // S4NOC_CHANNEL_DEBUG("Expected message size: ((%d))", expected_message_size); + if (receive_channel->receive_buffer_index >= expected_message_size + 4) { + int bytes_left = deserialize_from_protobuf(&receive_channel->output, + receive_channel->receive_buffer + 4, // skip the 4-byte size header + expected_message_size // only the message payload + ); + // bytes_left = ((bytes_left / 4)+1) * 4; + S4NOC_CHANNEL_DEBUG("Bytes Left after attempted to deserialize: %d", bytes_left); + + if (bytes_left >= 0) { + size_t remaining = (size_t)bytes_left; + receive_channel->receive_buffer_index = (remaining + 3) & ~3U; // rounded up to the next 4 + + S4NOC_CHANNEL_DEBUG("Message received at core %d from core %d", get_cpuid(), source); + printf_msg("received msg type:", &receive_channel->output); + if (receive_channel->receive_callback != NULL) { + S4NOC_CHANNEL_DEBUG("calling user callback at %p!", receive_channel->receive_callback); + receive_channel->receive_callback(self->federated_connection, &receive_channel->output); + } else { + S4NOC_CHANNEL_WARN("No receive callback registered, dropping message"); + } + } + } +} + +void S4NOCPollChannel_ctor(S4NOCPollChannel *self, unsigned int destination_core) { + assert(self != NULL); + + self->super.super.mode = NETWORK_CHANNEL_MODE_POLLED; + self->super.super.expected_connect_duration = SEC(0); + self->super.super.type = NETWORK_CHANNEL_TYPE_S4NOC; + self->super.super.is_connected = S4NOCPollChannel_is_connected; + self->super.super.open_connection = S4NOCPollChannel_open_connection; + self->super.super.close_connection = S4NOCPollChannel_close_connection; + self->super.super.send_blocking = S4NOCPollChannel_send_blocking; + self->super.super.register_receive_callback = S4NOCPollChannel_register_receive_callback; + self->super.super.free = S4NOCPollChannel_free; + self->super.poll = S4NOCPollChannel_poll; + + // Concrete fields + self->receive_buffer_index = 0; + self->receive_callback = NULL; + self->federated_connection = NULL; + self->state = HANDLE_NEW_CONNECTIONS ? NETWORK_CHANNEL_STATE_UNINITIALIZED : NETWORK_CHANNEL_STATE_CONNECTED; + self->destination_core = destination_core; + memset(self->receive_buffer, 0, S4NOC_CHANNEL_BUFFERSIZE); + memset(self->write_buffer, 0, S4NOC_CHANNEL_BUFFERSIZE); + unsigned int src_core = get_cpuid(); + s4noc_global_state.core_channels[src_core][destination_core] = self; +} diff --git a/src/platform/pico/uart_channel.c b/src/platform/pico/uart_channel.c index f0a77b88c..ab865742c 100644 --- a/src/platform/pico/uart_channel.c +++ b/src/platform/pico/uart_channel.c @@ -9,10 +9,14 @@ #define UART_CHANNEL_INFO(fmt, ...) LF_INFO(NET, "UartPolledChannel: " fmt, ##__VA_ARGS__) #define UART_CHANNEL_DEBUG(fmt, ...) LF_DEBUG(NET, "UartPolledChannel: " fmt, ##__VA_ARGS__) -#define UART_OPEN_MESSAGE_REQUEST {0xC0, 0x18, 0x11, 0xC0, 0xDD} -#define UART_OPEN_MESSAGE_RESPONSE {0xC0, 0xFF, 0x31, 0xC0, 0xDD} -#define UART_MESSAGE_PREFIX {0xAA, 0xAA, 0xAA, 0xAA, 0xAA} -#define UART_MESSAGE_POSTFIX {0xBB, 0xBB, 0xBB, 0xBB, 0xBD} +#define UART_OPEN_MESSAGE_REQUEST \ + { 0xC0, 0x18, 0x11, 0xC0, 0xDD } +#define UART_OPEN_MESSAGE_RESPONSE \ + { 0xC0, 0xFF, 0x31, 0xC0, 0xDD } +#define UART_MESSAGE_PREFIX \ + { 0xAA, 0xAA, 0xAA, 0xAA, 0xAA } +#define UART_MESSAGE_POSTFIX \ + { 0xBB, 0xBB, 0xBB, 0xBB, 0xBD } #define UART_CLOSE_MESSAGE {0x2, 0xF, 0x6, 0xC, 0x2}; #define MINIMUM_MESSAGE_SIZE 10 #define UART_CHANNEL_EXPECTED_CONNECT_DURATION MSEC(2500) diff --git a/src/schedulers/dynamic/scheduler.c b/src/schedulers/dynamic/scheduler.c index fe7831802..dc50ea316 100644 --- a/src/schedulers/dynamic/scheduler.c +++ b/src/schedulers/dynamic/scheduler.c @@ -268,6 +268,7 @@ void Scheduler_run(Scheduler *untyped_self) { } if (env->poll_network_channels) { + LF_DEBUG(SCHED, "Polling network channels"); env->poll_network_channels(env); } diff --git a/src/startup_coordinator.c b/src/startup_coordinator.c index 54e6eaad7..5773d856c 100644 --- a/src/startup_coordinator.c +++ b/src/startup_coordinator.c @@ -204,6 +204,7 @@ static void StartupCoordinator_handle_startup_handshake_response(StartupCoordina bool all_received = true; for (size_t i = 0; i < self->num_neighbours; i++) { if (!self->neighbor_state[i].handshake_response_received) { + LF_DEBUG(FED, "Handshake response not received from neighbor %zu", i); all_received = false; break; } diff --git a/test/platform/patmos/runAll.sh b/test/platform/patmos/runAll.sh new file mode 100755 index 000000000..0c8050bca --- /dev/null +++ b/test/platform/patmos/runAll.sh @@ -0,0 +1,13 @@ +#!/bin/bash + +set -e + +# Iterate over each folder and execute the command +for dir in ./*; do + if [ -d $dir ]; then + echo "Entering $dir" + pushd $dir + ./run.sh + popd + fi +done diff --git a/test/platform/patmos/s4noc_channel_test/Makefile b/test/platform/patmos/s4noc_channel_test/Makefile new file mode 100644 index 000000000..5a5ea6a6e --- /dev/null +++ b/test/platform/patmos/s4noc_channel_test/Makefile @@ -0,0 +1,25 @@ +# Makefile for building a simple Hello World program for the Patmos platform +APP = main + +include $(CURDIR)/../../../../make/patmos/patmos.mk +# Paths +SRC_DIR = $(CURDIR) +BUILD_DIR = $(CURDIR)/build + +# Source files +SOURCES += $(SRC_DIR)/$(APP).c + +CFLAGS += -DHANDLE_NEW_CONNECTIONS=0 +# Output binary +OUTPUT = $(BUILD_DIR)/$(APP).elf +all : $(OUTPUT) + +# Build rule +$(OUTPUT): $(SOURCES) + mkdir -p $(BUILD_DIR) + $(CC) $(CFLAGS) $(SOURCES) -o $(OUTPUT) + +clean: + rm -rf $(BUILD_DIR) + +.PHONY: all clean diff --git a/test/platform/patmos/s4noc_channel_test/main.c b/test/platform/patmos/s4noc_channel_test/main.c new file mode 100644 index 000000000..671dd5791 --- /dev/null +++ b/test/platform/patmos/s4noc_channel_test/main.c @@ -0,0 +1,117 @@ +#include "reactor-uc/reactor-uc.h" +#include "reactor-uc/environments/federated_environment.h" +#include "unity.h" +#include "test_util.h" +#include + +#define MESSAGE_CONTENT "Hello S4NOC" +#define MESSAGE_CONNECTION_ID 42 +#define SOURCE_CORE 1 +#define DESTINATION_CORE 2 +#define MAX_TRIES 20 + +Reactor parent; +FederatedEnvironment fed_env; +Environment *_lf_environment = &fed_env.super; +FederatedConnectionBundle sender_bundle; +FederatedConnectionBundle receiver_bundle; +FederatedConnectionBundle *net_bundles[] = {&sender_bundle, &receiver_bundle}; +StartupCoordinator startup_coordinator; + +S4NOCPollChannel sender_channel; +S4NOCPollChannel receiver_channel; +NetworkChannel *sender = (NetworkChannel *)&sender_channel.super; +NetworkChannel *receiver = (NetworkChannel *)&receiver_channel.super; + +bool receiver_callback_called = false; + +void setUp(void) { + /* init environment */ + FederatedEnvironment_ctor(&fed_env, &parent, NULL, false, net_bundles, 2, &startup_coordinator, NULL); + + /* init channel */ + LF_INFO(NET,"init channel"); + S4NOCPollChannel_ctor(&sender_channel, DESTINATION_CORE); + S4NOCPollChannel_ctor(&receiver_channel, SOURCE_CORE); + + /* init bundles */ + LF_INFO(NET,"init bundles"); + FederatedConnectionBundle_ctor(&sender_bundle, &parent, sender, NULL, NULL, 0, NULL, NULL, 0, 0); + FederatedConnectionBundle_ctor(&receiver_bundle, &parent, receiver, NULL, NULL, 0, NULL, NULL, 0, 0); + + LF_INFO(NET,"init global state"); + s4noc_global_state.core_channels[SOURCE_CORE][DESTINATION_CORE] = &receiver_channel; + s4noc_global_state.core_channels[DESTINATION_CORE][SOURCE_CORE] = &sender_channel; + +} + +void tearDown(void) { + sender->free(sender); + receiver->free(receiver); +} + +void receiver_callback_handler(FederatedConnectionBundle *self, const FederateMessage *_msg) { + (void)self; + const TaggedMessage *msg = &_msg->message.tagged_message; + LF_INFO(NET,"Receiver: Received message with connection number %i and content %s\n", msg->conn_id, + (char *)msg->payload.bytes); + TEST_ASSERT_EQUAL_STRING(MESSAGE_CONTENT, (char *)msg->payload.bytes); + TEST_ASSERT_EQUAL(MESSAGE_CONNECTION_ID, msg->conn_id); + + receiver_callback_called = true; +} + +void send_message(void) { + FederateMessage msg; + memset(&msg, 0, sizeof(msg)); + msg.which_message = FederateMessage_tagged_message_tag; + + TaggedMessage *port_message = &msg.message.tagged_message; + port_message->conn_id = MESSAGE_CONNECTION_ID; + const char *message = MESSAGE_CONTENT; + memset(port_message->payload.bytes, 0, sizeof(port_message->payload.bytes)); + memcpy(port_message->payload.bytes, message, sizeof(MESSAGE_CONTENT)); // NOLINT + port_message->payload.size = sizeof(MESSAGE_CONTENT); + // LF_INFO(NET, "Sender: Sending message with connection number %i and content %s\n", port_message->conn_id, (char *)port_message->payload.bytes); + TEST_ASSERT_OK(sender->send_blocking(sender, &msg)); +} + +void receive_message(void) { + int tries = 0; + do { + LF_DEBUG(NET, "Receiver: Polling for messages, tries: %i\n", tries); + tries++; + ((PolledNetworkChannel *)&receiver_channel.super)->poll((PolledNetworkChannel *)&receiver_channel.super); + } while (receiver_callback_called == false && tries < MAX_TRIES); +} + +void test_sender_send_and_receiver_recv(void) { + TEST_ASSERT_OK(sender->open_connection(sender)); + TEST_ASSERT_OK(receiver->open_connection(receiver)); + receiver->register_receive_callback(receiver, receiver_callback_handler, NULL); + + if(pthread_create(&sender_channel.worker_thread, NULL, send_message, NULL)) { + LF_ERR(NET,"Error creating thread\n"); + return; + } + if(pthread_create(&receiver_channel.worker_thread, NULL, receive_message, NULL)) { + LF_ERR(NET,"Error creating thread\n"); + return; + } + if (pthread_join(sender_channel.worker_thread, NULL)) { + LF_ERR(NET,"Error joining thread\n"); + return; + } + if (pthread_join(receiver_channel.worker_thread, NULL)) { + LF_ERR(NET,"Error joining thread\n"); + return; + } + + TEST_ASSERT_TRUE(receiver_callback_called); +} + +int main(void) { + UNITY_BEGIN(); + RUN_TEST(test_sender_send_and_receiver_recv); + return UNITY_END(); +} diff --git a/test/platform/patmos/s4noc_channel_test/run.sh b/test/platform/patmos/s4noc_channel_test/run.sh new file mode 100755 index 000000000..b9b6a6fed --- /dev/null +++ b/test/platform/patmos/s4noc_channel_test/run.sh @@ -0,0 +1,24 @@ +#!/bin/bash + +LF_MAIN=main +BIN_DIR=./build + +# Make test +make clean +make all + +read -n 1 -t 10 -p "Choose action: [e]mulate or [f]pga? (default: e) " action +action=${action:-e} +if [[ "$action" == "e" ]]; then + patemu $BIN_DIR/$LF_MAIN.elf +elif [[ "$action" == "f" ]]; then + if jtagconfig | grep -q "USB-Blaster"; then + mv $BIN_DIR/$LF_MAIN.elf ~/t-crest/patmos/tmp/$LF_MAIN.elf + make -C ~/t-crest/patmos APP=$LF_MAIN config download + else + echo "JTAG not connected. Please connect USB-Blaster." + fi +else + echo "Invalid option. Please choose 'e' for emulate or 'f' for fpga." +fi +