diff --git a/Makefile b/Makefile index b589275757e2eaecf30215a1f3b37660176636d6..06becf234277f8e97b311740b0656e0ca573cdac 100644 --- a/Makefile +++ b/Makefile @@ -125,7 +125,8 @@ CSRC = $(ALLCSRC) \ $(SRCDIR)/crc32.c \ $(SRCDIR)/main.c \ $(SRCDIR)/serial.c \ - $(SRCDIR)/usb.c + $(SRCDIR)/usb.c \ + $(SRCDIR)/tests.c # C++ sources that can be compiled in ARM or THUMB mode depending on the global # setting. @@ -155,7 +156,7 @@ CPPWARN = -Wall -Wextra -Wundef # # List all user C define here, like -D_DEBUG=1 -UDEFS = -DHAL_USE_COMMUNITY=FALSE +UDEFS = -DHAL_USE_COMMUNITY=FALSE -DCHPRINTF_USE_FLOAT=TRUE # Define ASM defines here UADEFS = diff --git a/src/linalg.h b/src/linalg.h new file mode 100644 index 0000000000000000000000000000000000000000..edcdfb340680665895581833f37f952b2d49eb82 --- /dev/null +++ b/src/linalg.h @@ -0,0 +1,137 @@ +// This file is part of the execution-time evaluation for the qronos observer abstractions. +// Copyright (C) 2022-2023 Tim Rheinfels <tim.rheinfels@fau.de> +// See https://gitlab.cs.fau.de/qronos-state-abstractions/execution-time +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <https://www.gnu.org/licenses/>. + +/// +/// @file linalg.h +/// +/// @brief Provides linear algebra routines for fix-sized objects +/// +/// @author Tim Rheinfels <tim.rheinfels@fau.de> +/// + +#ifndef LINALG_H +#define LINALG_H + +#include <math.h> +#include <stdlib.h> +#include <string.h> + +/// +/// @brief Adds the matrices @p A, @p B and @p C of size @p N by @p M using independent scalar factors @p a, @p b and @p c as +/// @f[ C \leftarrow a \cdot A + b \cdot B + c \cdot C @f] +/// +/// @param[in] A First input matrix +/// @param[in] B Second input matrix +/// @param[in,out] C Third input matrix before call, result afterwards +/// @param[in] a Scalar for weighting @p A +/// @param[in] b Scalar for weighting @p B +/// @param[in] c Scalar for weighting @p C +/// @param[in] N Number of rows for all three matrices +/// @param[in] M Number of columns for all three matrices +/// +#define LINALG_MA(A, B, C, a, b, c, N, M) { \ + for(size_t i = 0u; i < (N); ++i) { \ + for(size_t j = 0u; j < (M); ++j) { \ + (C)[(M)*i+j] = (a) * (A)[(M)*i+j] + (b) * (B)[(M)*i+j] + (c) * (C)[(M)*i+j]; \ + } \ + } \ + } + +/// +/// @brief Performs a matrix-valued multiply-accumulate operation for the matrices @p A, @p B and @p C +/// of sizes @p N by @p K, @p K by @p M and @p N by @p M using independent scalar factors @p a and @p b as +/// @f[ C \leftarrow a \cdot A B + b \cdot C @f] +/// +/// @param[in] A First input matrix +/// @param[in] B Second input matrix +/// @param[in,out] C Third input matrix before call, result afterwards +/// @param[in] a Scalar for weighting the product of @p A and @p B +/// @param[in] b Scalar for weighting @p C +/// @param[in] N Number of rows for the result matrix @p C +/// @param[in] K Shared dimension between @p A (colmns) and @p B (rows) +/// @param[in] M Number of columns for the result matrix @p C +/// +#define LINALG_MM(A, B, C, a, b, N, K, M) { \ + for(size_t i = 0u; i < (N); ++i) { \ + for(size_t j = 0u; j < (M); ++j) { \ + float accu = 0.0f; \ + for(size_t k = 0u; k < (K); ++k) { \ + accu += (A)[(K)*i+k] * (B)[(M)*k+j]; \ + } \ + (C)[(M)*i+j] = (a) * accu + (b) * (C)[(M)*i+j]; \ + } \ + } \ + } + +/// +/// @brief Performs a matrix-valued multiply-accumulate operation for the matrices @p A, @p B and @p C +/// of sizes @p N by @p K, @p K by @p M and @p N by @p M where @p B is stored transposed +/// using independent scalar factors @p a and @p b as +/// @f[ C \leftarrow a \cdot A B^T + b \cdot C @f] +/// +/// @param[in] A First input matrix +/// @param[in] B Second input matrix (transposed in memory) +/// @param[in,out] C Third input matrix before call, result afterwards +/// @param[in] a Scalar for weighting the product of @p A and @p B +/// @param[in] b Scalar for weighting @p C +/// @param[in] N Number of rows for the result matrix @p C +/// @param[in] K Shared dimension between @p A (colmns) and @p B (rows) +/// @param[in] M Number of columns for the result matrix @p C +/// +#define LINALG_MM_T(A, B, C, a, b, N, K, M) { \ + for(size_t i = 0u; i < (N); ++i) { \ + for(size_t j = 0u; j < (M); ++j) { \ + float accu = 0.0f; \ + for(size_t k = 0u; k < (K); ++k) { \ + accu += (A)[(K)*i+k] * (B)[(K)*j+k]; \ + } \ + (C)[(M)*i+j] = (a) * accu + (b) * (C)[(M)*i+j]; \ + } \ + } \ + } + +/// +/// @brief Performs a matrix-vector multiply-accumulate operation for the matrix @p A and vectors @p B and @p C +/// of sizes @p N by @p K and @p K using independent scalar factors @p a and @p b as +/// @f[ C \leftarrow a \cdot A B + b \cdot C @f] +/// +/// @param[in] A First input matrix +/// @param[in] B First input vector +/// @param[in,out] C Second input vector before call, result afterwards +/// @param[in] a Scalar for weighting the product of @p A and @p B +/// @param[in] b Scalar for weighting @p C +/// @param[in] N Number of rows for the input matrix @p A +/// @param[in] K Shared dimension between @p A (columns) and B (rows) +/// +#define LINALG_MV(A, B, C, a, b, N, K) LINALG_MM(A, B, C, a, b, N, K, 1u) + +/// +/// @brief Compute the trace @p T of an @p N by @p N matrix @p A as +/// @f[ T \leftarrow \text{tr } A @f] +/// +/// @param[in] A Square matrix to compute trace for +/// @param[out] T Single precision float to store result in +/// @param[in] N Number of rows and columns of @p A +/// +#define LINALG_TRACE(A, T, N) { \ + T = 0.0f; \ + for(size_t i = 0u; i < (N); ++i) { \ + T += (A)[(N)*i+i]; \ + } \ + } + +#endif // LINALG_H diff --git a/src/main.c b/src/main.c index b68eb00b61e7bdc02e4d55619f212ebc9d39ace5..49b0181a5946dbf4c63067444db02ba07841463e 100644 --- a/src/main.c +++ b/src/main.c @@ -28,6 +28,12 @@ #include <ch.h> #include <hal.h> + +#include <crc32.h> +#include <linalg.h> +#include <serial.h> +#include <tests.h> + /// /// @brief Program's entry point /// @@ -42,6 +48,16 @@ int main(void) // Fix random seeds srand(0u); + // Initialise drivers + crc32_init(); + serial_init(); + + // Run unit tests + if(!tests_run()) + { + while(1); + } + // We're done -> Wait forever while(true) { diff --git a/src/tests.c b/src/tests.c new file mode 100644 index 0000000000000000000000000000000000000000..508c082e06a492223d6b571e651ec3c1aa8d6719 --- /dev/null +++ b/src/tests.c @@ -0,0 +1,331 @@ +// This file is part of the execution-time evaluation for the qronos observer abstractions. +// Copyright (C) 2022-2023 Tim Rheinfels <tim.rheinfels@fau.de> +// See https://gitlab.cs.fau.de/qronos-state-abstractions/execution-time +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <https://www.gnu.org/licenses/>. + +/// +/// @file tests.c +/// +/// @brief Provides the implementation of the unit tests +/// +/// @author Tim Rheinfels <tim.rheinfels@fau.de> +/// + +#include <tests.h> + +#include <string.h> + +#include <linalg.h> +#include <serial.h> + +/// +/// @brief Runs the test called @p named by calling @p func and assessing its results +/// +/// @param[in] name Name of the test +/// @param[in] func Function returning true upon success and false when the tests failed +/// +/// @returns Either +/// - true if the test succeeded or +/// - false otherwise +/// +static bool run(const char *name, bool (*func) (void)) +{ + serial_printf("Running Test %s\n", name); + bool success = func(); + if(success) + { + serial_printf(" --> SUCCESS\n"); + } + else + { + serial_printf(" --> FAILURE\n"); + } + return success; +} + +/// +/// @brief Checks that @ref LINALG_MA returns the expected result for one fixed example and that the operation obeys its memory constraints +/// +/// @returns Either +/// - true if @p LINALG_MA works as expected or +/// - false otherwise +/// +static bool linalg_ma(void) +{ + static const float A[6u] = {1, 2, 3, 4, 5, 6}; + static const float B[6u] = {7, 8, 9, 10, 11, 12}; + static const float C_ref[6u] = {23, 27, 31, 35, 39, 43}; + + static uint8_t mem[32u + sizeof(C_ref) + 32u]; + memset(mem, 0xA5u, 32u); + memset(mem + 32u + sizeof(C_ref), 0xA5u, 32u); + + float *C = (float *) (mem + 32u); + + for(size_t i = 0u; i < 2u*3u; ++i) + { + C[i] = i; + } + + LINALG_MA(A, B, C, 2.0f, 3.0f, -1.0f, 2u, 3u); + + bool success = true; + for(size_t i = 0u; i < 2u; ++i) + { + for(size_t j = 0u; j < 3u; ++j) + { + if(C[3u*i+j] != C_ref[3u*i+j]) + { + serial_printf(" Error in matrix C[%d, %d]: Expected %f, Got: %f\n", i, j, C_ref[3u*i+j], C[3u*i+j]); + success = false; + } + } + } + + for(size_t i = 0u; i < 32u; ++i) + { + if(mem[i] != 0xA5u) + { + serial_printf(" Memory error %d bytes before result: Expected %u, Got: %u\n", (31u - i), 0xA5u, mem[i]); + success = false; + } + } + + for(size_t i = 0u; i < 32u; ++i) + { + if(mem[32u + sizeof(C_ref) + i] != 0xA5u) + { + serial_printf(" Memory error %d bytes after result: Expected %u, Got: %u\n", i, 0xA5u, mem[i]); + success = false; + } + } + + return success; +} + +/// +/// @brief Checks that @ref LINALG_MM returns the expected result for one fixed example and that the operation obeys its memory constraints +/// +/// @returns Either +/// - true if @p LINALG_MM works as expected or +/// - false otherwise +/// +static bool linalg_mm(void) +{ + static const float A[6u] = {1, 2, 3, 4, 5, 6}; + static const float B[12u] = {7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18}; + static const float C_ref[8u] = {444, 479, 514, 549, 1034, 1123, 1212, 1301}; + + static uint8_t mem[32u + sizeof(C_ref) + 32u]; + memset(mem, 0xA5u, 32u); + memset(mem + 32u + sizeof(C_ref), 0xA5u, 32u); + + float *C = (float *) (mem + 32u); + + for(size_t i = 0u; i < 2u*4u; ++i) + { + C[i] = i; + } + + LINALG_MM(A, B, C, 6.0f, -1.0f, 2u, 3u, 4u); + + bool success = true; + for(size_t i = 0u; i < 2u; ++i) + { + for(size_t j = 0u; j < 4u; ++j) + { + if(C[4u*i+j] != C_ref[4u*i+j]) + { + serial_printf(" Error in matrix C[%d, %d]: Expected %f, Got: %f\n", i, j, C_ref[4u*i+j], C[4u*i+j]); + success = false; + } + } + } + + for(size_t i = 0u; i < 32u; ++i) + { + if(mem[i] != 0xA5u) + { + serial_printf(" Memory error %d bytes before result: Expected %u, Got: %u\n", (31u - i), 0xA5u, mem[i]); + success = false; + } + } + + for(size_t i = 0u; i < 32u; ++i) + { + if(mem[32u + sizeof(C_ref) + i] != 0xA5u) + { + serial_printf(" Memory error %d bytes after result: Expected %u, Got: %u\n", i, 0xA5u, mem[i]); + success = false; + } + } + + return success; +} + +/// +/// @brief Checks that @ref LINALG_MM_T returns the expected result for one fixed example and that the operation obeys its memory constraints +/// +/// @returns Either +/// - true if @p LINALG_MM_T works as expected or +/// - false otherwise +/// +static bool linalg_mm_t(void) +{ + static const float A[6u] = {1, 2, 3, 4, 5, 6}; + static const float B[12u] = {7, 11, 15, 8, 12, 16, 9, 13, 17, 10, 14, 18}; + static const float C_ref[8u] = {444, 479, 514, 549, 1034, 1123, 1212, 1301}; + + static uint8_t mem[32u + sizeof(C_ref) + 32u]; + memset(mem, 0xA5u, 32u); + memset(mem + 32u + sizeof(C_ref), 0xA5u, 32u); + + float *C = (float *) (mem + 32u); + + for(size_t i = 0u; i < 2u*4u; ++i) + { + C[i] = i; + } + + LINALG_MM_T(A, B, C, 6.0f, -1.0f, 2u, 3u, 4u); + + bool success = true; + for(size_t i = 0u; i < 2u; ++i) + { + for(size_t j = 0u; j < 4u; ++j) + { + if(C[4u*i+j] != C_ref[4u*i+j]) + { + serial_printf(" Error in matrix C[%d, %d]: Expected %f, Got: %f\n", i, j, C_ref[4u*i+j], C[4u*i+j]); + success = false; + } + } + } + + for(size_t i = 0u; i < 32u; ++i) + { + if(mem[i] != 0xA5u) + { + serial_printf(" Memory error %d bytes before result: Expected %u, Got: %u\n", (31u - i), 0xA5u, mem[i]); + success = false; + } + } + + for(size_t i = 0u; i < 32u; ++i) + { + if(mem[32u + sizeof(C_ref) + i] != 0xA5u) + { + serial_printf(" Memory error %d bytes after result: Expected %u, Got: %u\n", i, 0xA5u, mem[i]); + success = false; + } + } + + return success; +} + +/// +/// @brief Checks that @ref LINALG_MV returns the expected result for one fixed example and that the operation obeys its memory constraints +/// +/// @returns Either +/// - true if @p LINALG_MV works as expected or +/// - false otherwise +/// +static bool linalg_mv(void) +{ + static const float A[6u] = {1, 2, 3, 4, 5, 6}; + static const float B[12u] = {7, 8, 9}; + static const float C_ref[8u] = {300, 731}; + + static uint8_t mem[32u + sizeof(C_ref) + 32u]; + memset(mem, 0xA5u, 32u); + memset(mem + 32u + sizeof(C_ref), 0xA5u, 32u); + + float *C = (float *) (mem + 32u); + + for(size_t i = 0u; i < 2u*4u; ++i) + { + C[i] = i; + } + + LINALG_MV(A, B, C, 6.0f, -1.0f, 2u, 3u); + + bool success = true; + for(size_t i = 0u; i < 2u; ++i) + { + if(C[i] != C_ref[i]) + { + serial_printf(" Error in vector C[%d]: Expected %f, Got: %f\n", i, C_ref[i], C[i]); + success = false; + } + } + + for(size_t i = 0u; i < 32u; ++i) + { + if(mem[i] != 0xA5u) + { + serial_printf(" Memory error %d bytes before result: Expected %u, Got: %u\n", (31u - i), 0xA5u, mem[i]); + success = false; + } + } + + for(size_t i = 0u; i < 32u; ++i) + { + if(mem[32u + sizeof(C_ref) + i] != 0xA5u) + { + serial_printf(" Memory error %d bytes after result: Expected %u, Got: %u\n", i, 0xA5u, mem[i]); + success = false; + } + } + + return success; +} + +/// +/// @brief Checks that @ref LINALG_TRACE returns the expected result for one fixed example +/// +/// @returns Either +/// - true if @p LINALG_TRACE works as expected or +/// - false otherwise +/// +static bool linalg_trace(void) +{ + static const float A[9u] = {1, 2, 3, 4, 5, 6, 7, 8, 9}; + static const float trace_ref = 15; + + float trace = -1.0f; + + LINALG_TRACE(A, trace, 3u); + + if(trace != trace_ref) + { + serial_printf(" Error in trace: Expected %f, Got: %f\n", trace_ref, trace); + return false; + } + + return true; +} + +bool tests_run(void) +{ + bool success = true; + + success &= run("linalg_ma", &linalg_ma); + success &= run("linalg_mm", &linalg_mm); + success &= run("linalg_mm_t", &linalg_mm_t); + success &= run("linalg_mv", &linalg_mv); + success &= run("linalg_trace", &linalg_trace); + + return success; +} diff --git a/src/tests.h b/src/tests.h new file mode 100644 index 0000000000000000000000000000000000000000..22aa2521d6d7859247d97bd89288afb2cbbe4a98 --- /dev/null +++ b/src/tests.h @@ -0,0 +1,40 @@ +// This file is part of the execution-time evaluation for the qronos observer abstractions. +// Copyright (C) 2022-2023 Tim Rheinfels <tim.rheinfels@fau.de> +// See https://gitlab.cs.fau.de/qronos-state-abstractions/execution-time +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <https://www.gnu.org/licenses/>. + +/// +/// @file tests.h +/// +/// @brief Provides the interface for the unit tests +/// +/// @author Tim Rheinfels <tim.rheinfels@fau.de> +/// + +#ifndef TESTS_H +#define TESTS_H + +#include <ch.h> + +/// +/// @brief Runs all unit tests +/// +/// @returns Either +/// - true if all test cases succeeded or +/// - false otherwise +/// +bool tests_run(void); + +#endif // TESTS_H