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