diff --git a/examples/app_assert/src/fn_assert.c b/examples/app_assert/src/fn_assert.c index 7059e01..ac04e8b 100644 --- a/examples/app_assert/src/fn_assert.c +++ b/examples/app_assert/src/fn_assert.c @@ -4,10 +4,10 @@ #define XASSERT_UNIT FN_ASSERT #define XASSERT_ENABLE_ASSERTIONS_FN_ASSERT 1 /* Enable assertions*/ #define XASSERT_ENABLE_DEBUG_FN_ASSERT 1 /* Enable printing debug message when asserting */ +#define XASSERT_ENABLE_LINE_NUMBERS 1 /* Enable line numbers in assert messages */ #include - void fn_assert(int x) { assert(x > 5 && msg("assert from fn_assert()")); diff --git a/examples/app_timed_block/CMakeLists.txt b/examples/app_timed_block/CMakeLists.txt new file mode 100644 index 0000000..0345359 --- /dev/null +++ b/examples/app_timed_block/CMakeLists.txt @@ -0,0 +1,13 @@ +cmake_minimum_required(VERSION 3.21) +include($ENV{XMOS_CMAKE_PATH}/xcommon.cmake) +project(app_assert) + +set(APP_HW_TARGET XK-EVK-XU316) + +include(${CMAKE_CURRENT_LIST_DIR}/../deps.cmake) + +set(APP_COMPILER_FLAGS -DXASSERT_ENABLE_ASSERTIONS=0 -DXASSERT_ENABLE_LINE_NUMBERS=1) # Override from source files + +set(XMOS_SANDBOX_DIR ${CMAKE_CURRENT_LIST_DIR}/../../../) + +XMOS_REGISTER_APP() diff --git a/examples/app_timed_block/src/fn_assert.xc b/examples/app_timed_block/src/fn_assert.xc new file mode 100644 index 0000000..fae45d6 --- /dev/null +++ b/examples/app_timed_block/src/fn_assert.xc @@ -0,0 +1,19 @@ +// Copyright 2024-2025 XMOS LIMITED. +// This Software is subject to the terms of the XMOS Public Licence: Version 1. + +#define XASSERT_UNIT FN_ASSERT +#define XASSERT_ENABLE_TIMING_ASSERTIONS_FN_ASSERT 1 /* Enable timing assertions*/ +#define XASSERT_ENABLE_DEBUG_FN_ASSERT 1 /* Enable printing debug message when asserting */ + +#include +#include + +void fn_assert() +{ + XASSERT_TIMED_BLOCK("test", 1000, + timer t; + unsigned time; + t :> time; + t when timerafter(time + 5000) :> void; + ); +} diff --git a/examples/app_timed_block/src/fn_no_assert.xc b/examples/app_timed_block/src/fn_no_assert.xc new file mode 100644 index 0000000..e3ba2e3 --- /dev/null +++ b/examples/app_timed_block/src/fn_no_assert.xc @@ -0,0 +1,19 @@ +// Copyright 2024-2025 XMOS LIMITED. +// This Software is subject to the terms of the XMOS Public Licence: Version 1. + +#define XASSERT_UNIT FN_ASSERT +#define XASSERT_ENABLE_ASSERTIONS_FN_ASSERT 0 /* Disable assertions*/ +#define XASSERT_ENABLE_DEBUG_FN_ASSERT 1 /* Enable printing debug message when asserting */ + +#include +#include + +void fn_no_assert() +{ + XASSERT_TIMED_BLOCK("test", 1000, + timer t; + unsigned time; + t :> time; + t when timerafter(time + 50) :> void; + ); +} diff --git a/examples/app_timed_block/src/main.c b/examples/app_timed_block/src/main.c new file mode 100644 index 0000000..b030014 --- /dev/null +++ b/examples/app_timed_block/src/main.c @@ -0,0 +1,11 @@ +// Copyright 2024-2025 XMOS LIMITED. +// This Software is subject to the terms of the XMOS Public Licence: Version 1. + +extern void fn_no_assert(void); +extern void fn_assert(void); + +int main() { + fn_no_assert(); // This shouldn't assert + fn_assert(); // This asserts + return 0; +} diff --git a/examples/app_timed_block/src/xassert_conf.h b/examples/app_timed_block/src/xassert_conf.h new file mode 100644 index 0000000..9e4b461 --- /dev/null +++ b/examples/app_timed_block/src/xassert_conf.h @@ -0,0 +1,4 @@ +// Copyright 2025 XMOS LIMITED. +// This Software is subject to the terms of the XMOS Public Licence: Version 1. + +#define XASSERT_ENABLE_LINE_NUMBERS 1 diff --git a/examples/app_timed_loop/CMakeLists.txt b/examples/app_timed_loop/CMakeLists.txt new file mode 100644 index 0000000..d72f66c --- /dev/null +++ b/examples/app_timed_loop/CMakeLists.txt @@ -0,0 +1,13 @@ +cmake_minimum_required(VERSION 3.21) +include($ENV{XMOS_CMAKE_PATH}/xcommon.cmake) +project(app_assert) + +set(APP_HW_TARGET XK-EVK-XU316) + +include(${CMAKE_CURRENT_LIST_DIR}/../deps.cmake) + +set(APP_COMPILER_FLAGS -DXASSERT_ENABLE_ASSERTIONS=0) # Override from source files + +set(XMOS_SANDBOX_DIR ${CMAKE_CURRENT_LIST_DIR}/../../../) + +XMOS_REGISTER_APP() diff --git a/examples/app_timed_loop/src/fn_assert.xc b/examples/app_timed_loop/src/fn_assert.xc new file mode 100644 index 0000000..79531f8 --- /dev/null +++ b/examples/app_timed_loop/src/fn_assert.xc @@ -0,0 +1,23 @@ +// Copyright 2024-2025 XMOS LIMITED. +// This Software is subject to the terms of the XMOS Public Licence: Version 1. + +#define XASSERT_UNIT FN_ASSERT +#define XASSERT_ENABLE_TIMING_ASSERTIONS_FN_ASSERT 1 /* Enable timing assertions*/ +#define XASSERT_ENABLE_DEBUG_FN_ASSERT 1 /* Enable printing debug message when asserting */ +#define XASSERT_ENABLE_LINE_NUMBERS 1 /* Enable line numbers in assertions */ +#include +#include + +void fn_assert() +{ + timer t; + unsigned time; + t :> time; + + for(int i = 0; i< 5; i++) + { + time += (i * 5000); + t when timerafter(time) :> void; + xassert_loop_freq("test loop", 10000); + } +} diff --git a/examples/app_timed_loop/src/fn_no_assert.xc b/examples/app_timed_loop/src/fn_no_assert.xc new file mode 100644 index 0000000..2afaa20 --- /dev/null +++ b/examples/app_timed_loop/src/fn_no_assert.xc @@ -0,0 +1,25 @@ +// Copyright 2024-2025 XMOS LIMITED. +// This Software is subject to the terms of the XMOS Public Licence: Version 1. + +#define XASSERT_UNIT FN_ASSERT +#define XASSERT_ENABLE_ASSERTIONS_FN_ASSERT 0 /* Enable assertions*/ +#define XASSERT_ENABLE_DEBUG_FN_ASSERT 1 /* Enable printing debug message when asserting */ + +#include +#include +#include + +void fn_no_assert() +{ + timer t; + unsigned time; + t :> time; + + + for(int i = 0; i< 5; i++) + { + time += (i * 5000); + t when timerafter(time) :> void; + xassert_loop_freq("test loop", 10000); + } +} diff --git a/examples/app_timed_loop/src/main.c b/examples/app_timed_loop/src/main.c new file mode 100644 index 0000000..b030014 --- /dev/null +++ b/examples/app_timed_loop/src/main.c @@ -0,0 +1,11 @@ +// Copyright 2024-2025 XMOS LIMITED. +// This Software is subject to the terms of the XMOS Public Licence: Version 1. + +extern void fn_no_assert(void); +extern void fn_assert(void); + +int main() { + fn_no_assert(); // This shouldn't assert + fn_assert(); // This asserts + return 0; +} diff --git a/examples/app_timing_loop_exception/CMakeLists.txt b/examples/app_timing_loop_exception/CMakeLists.txt new file mode 100644 index 0000000..d72f66c --- /dev/null +++ b/examples/app_timing_loop_exception/CMakeLists.txt @@ -0,0 +1,13 @@ +cmake_minimum_required(VERSION 3.21) +include($ENV{XMOS_CMAKE_PATH}/xcommon.cmake) +project(app_assert) + +set(APP_HW_TARGET XK-EVK-XU316) + +include(${CMAKE_CURRENT_LIST_DIR}/../deps.cmake) + +set(APP_COMPILER_FLAGS -DXASSERT_ENABLE_ASSERTIONS=0) # Override from source files + +set(XMOS_SANDBOX_DIR ${CMAKE_CURRENT_LIST_DIR}/../../../) + +XMOS_REGISTER_APP() diff --git a/examples/app_timing_loop_exception/src/fn_no_assert.xc b/examples/app_timing_loop_exception/src/fn_no_assert.xc new file mode 100644 index 0000000..4a65ea2 --- /dev/null +++ b/examples/app_timing_loop_exception/src/fn_no_assert.xc @@ -0,0 +1,34 @@ +// Copyright 2024-2025 XMOS LIMITED. +// This Software is subject to the terms of the XMOS Public Licence: Version 1. + +#define XASSERT_UNIT FN_ASSERT +#define XASSERT_ENABLE_TIMING_ASSERTIONS_FN_ASSERT 1 /* Enable timing assertions*/ +#define XASSERT_ENABLE_DEBUG_FN_ASSERT 1 /* Enable printing debug message when asserting */ +#define XASSERT_ENABLE_LINE_NUMBERS 1 /* Enable line numbers in assertions */ +#include +#include + +void fn_assert() +{ + timer t; + unsigned time; + t :> time; + + for(int i = 0; i< 5; i++) + { + int delay = 1000; + + xassert_loop_freq("test loop", 10000); + + if(i == 3) + { + delay *= 50; + + /* Due to this added exception the assertion will not fail */ + xassert_loop_exception("test loop"); + } + + time += delay; + t when timerafter(time) :> void; + } +} diff --git a/examples/app_timing_loop_exception/src/main.c b/examples/app_timing_loop_exception/src/main.c new file mode 100644 index 0000000..dd18e6a --- /dev/null +++ b/examples/app_timing_loop_exception/src/main.c @@ -0,0 +1,10 @@ +// Copyright 2024-2025 XMOS LIMITED. +// This Software is subject to the terms of the XMOS Public Licence: Version 1. + +extern void fn_no_assert(void); +extern void fn_assert(void); + +int main() { + fn_no_assert(); // This shouldn't assert + return 0; +} diff --git a/lib_xassert/api/xassert.h b/lib_xassert/api/xassert.h index 76d0b05..49eb33d 100644 --- a/lib_xassert/api/xassert.h +++ b/lib_xassert/api/xassert.h @@ -3,6 +3,7 @@ #ifndef __xassert_h__ #define __xassert_h__ + #ifdef __xassert_conf_h_exists__ #include "xassert_conf.h" #endif @@ -19,6 +20,10 @@ #define XASSERT_ENABLE_ASSERTIONS 1 #endif +#ifndef XASSERT_ENABLE_TIMING_ASSERTIONS +#define XASSERT_ENABLE_TIMING_ASSERTIONS 1 +#endif + #ifndef XASSERT_ENABLE_DEBUG #define XASSERT_ENABLE_DEBUG 0 #endif @@ -42,6 +47,14 @@ # define XASSERT_ENABLE_ASSERTIONS0 XASSERT_ENABLE_ASSERTIONS #endif +#if XASSERT_JOIN(XASSERT_ENABLE_TIMING_ASSERTIONS_,XASSERT_UNIT) +# define XASSERT_ENABLE_TIMING_ASSERTIONS0 1 +#endif + +#if XASSERT_JOIN(XASSERT_DISABLE_TIMING_ASSERTIONS_,XASSERT_UNIT) +# define XASSERT_ENABLE_TIMING_ASSERTIONS0 0 +#endif + #if XASSERT_JOIN(XASSERT_ENABLE_DEBUG_,XASSERT_UNIT) # define XASSERT_ENABLE_DEBUG0 1 #endif @@ -55,7 +68,8 @@ #endif #if XASSERT_ENABLE_DEBUG0 -# include "print.h" +#include "print.h" +#include #endif #if XASSERT_ENABLE_LINE_NUMBERS @@ -63,12 +77,19 @@ printint(__LINE__); \ printstr(")\n"); \ } while(0) + +#define xassert_timing_print_line(file, line) do { printf(" (%s:", file); \ + fflush(stdout); \ + printint(line); \ + printstr(")\n"); \ + } while(0) + #else # define xassert_print_line do { printstr("\n"); } while(0) +# define xassert_timing_print_line(file, line) do { printstr("\n"); } while(0) #endif - -#if XASSERT_ENABLE_ASSERTIONS0 +#if XASSERT_ENABLE_ASSERTIONS0 || XASSERT_ENABLE_TIMING_ASSERTIONS0 # if XASSERT_ENABLE_DEBUG0 # define xassert(e) do { if (!(e)) {\ printstr(#e); xassert_print_line; \ @@ -93,11 +114,18 @@ #if XASSERT_ENABLE_DEBUG0 # define fail(msg) do { printstr(msg); xassert_print_line; __builtin_trap();} while(0) +# define fail_timing(tag, actual, limit, file, line) do { printstr("Timing failed for: "); \ + printf("%s", (const char *) tag); \ + fflush(stdout); \ + printstr("\nΔt = "); printint(actual); printstr(" ticks ("); printint((actual) * 10); printstr(" ns), "); \ + printstr("limit = "); printint(limit); printstr(" ticks ("); printint((limit) * 10); printstr(" ns) "); \ + xassert_timing_print_line(file, line); __builtin_trap();\ + } while(0) #else # define fail(msg) do { __builtin_trap();} while(0) +# define fail_timing(tag, actual, limit, file, line) do { __builtin_trap();} while(0) #endif - #ifndef UNUSED #ifdef __XC__ #define UNUSED(x) do { x; } while(0); @@ -106,7 +134,6 @@ #endif #endif // UNUSED - inline int xassert_msg(const char msg[]) { UNUSED(msg); return 1; } #ifdef __XC__ @@ -121,4 +148,212 @@ inline int xassert_msg(const char msg[]) { UNUSED(msg); return 1; } #define assert(...) xassert(__VA_ARGS__) #endif +#ifndef __STDC__ +extern "C" { +#endif + +#ifndef MAX_TIMING_BLOCKS +#define MAX_TIMING_BLOCKS 8 +#endif + +#if XASSERT_ENABLE_TIMING_ASSERTIONS0 + +#ifdef __XC__ +# define UNSAFE unsafe +#endif + +// DJB2 hash for string to int ID +static inline unsigned xassert_hash(const char *str) +{ + unsigned hash = 5381; + char c; + while ((c = *str++)) { + hash = ((hash << 5) + hash) + c; // hash * 33 + c + } + return hash; +} + +#define XASSERT_TAG_ID(tag) (xassert_hash(tag)) + +typedef struct { + const char * UNSAFE tag; + unsigned start_time; + unsigned deadline; + const char *file; + int line; + int is_loop; + unsigned loop_interval_ticks; + unsigned id; +} timing_block_t; + +static inline unsigned get_time(void) +{ + unsigned time; + asm volatile("gettime %0" : "=r"(time)); + return time; +} + +static timing_block_t timing_blocks[MAX_TIMING_BLOCKS]; +static int head = 0; +static int tail = 0; + +#define CIRCULAR_INC(x) (((x) + 1) % MAX_TIMING_BLOCKS) +#define CIRCULAR_FULL (CIRCULAR_INC(tail) == head) +#define CIRCULAR_EMPTY (head == tail) + +static inline void drop_oldest_entry() { head = CIRCULAR_INC(head); } + +static inline void timing_start_impl(const char *tag, unsigned id, unsigned max_ticks, const char *file, int line) +{ + unsigned now = get_time(); + + for (int i = head; i != tail; i = CIRCULAR_INC(i)) + { + if (timing_blocks[i].id == id && !timing_blocks[i].is_loop) + { + timing_blocks[i].start_time = now; + timing_blocks[i].deadline = now + max_ticks; + timing_blocks[i].file = file; + timing_blocks[i].line = line; + return; + } + } + + if (CIRCULAR_FULL) + { +#if XASSERT_ENABLE_DEBUG0 + printstr("WARNING: Timing buffer full. Dropping oldest entry.\n"); +#endif + drop_oldest_entry(); + } + + timing_blocks[tail].tag = tag; + timing_blocks[tail].start_time = now; + timing_blocks[tail].deadline = now + max_ticks; + timing_blocks[tail].file = file; + timing_blocks[tail].line = line; + timing_blocks[tail].is_loop = 0; + timing_blocks[tail].loop_interval_ticks = 0; + timing_blocks[tail].id = id; + + tail = CIRCULAR_INC(tail); +} + +static inline void timing_end_impl(const char *tag, unsigned id, const char *file, int line) +{ + unsigned now = get_time(); + + for (int i = head; i != tail; i = CIRCULAR_INC(i)) + { + if ((timing_blocks[i].id == id) && !timing_blocks[i].is_loop) + { + if (now > timing_blocks[i].deadline) + { + fail_timing(tag, now - timing_blocks[i].start_time, timing_blocks[i].deadline - timing_blocks[i].start_time, file, line); + } + + int last = (tail == 0) ? MAX_TIMING_BLOCKS - 1 : tail - 1; + if (i != last) + { + // swap in last active entry + timing_blocks[i] = timing_blocks[last]; + } + + // logically remove the last entry + tail = last; + return ; + } + } + + fail("timing_end() called without matching timing_start()"); +} + +#ifndef XASSERT_TIMING_DELTA_ERROR +#define XASSERT_TIMING_DELTA_ERROR (2) +#endif + +static inline void timing_loop_impl(const char *tag, unsigned id, unsigned min_freq_hz, const char *file, int line) +{ + unsigned now = get_time(); + //unsigned interval = XS1_TIMER_HZ / min_freq_hz; + unsigned interval = (XS1_TIMER_HZ + min_freq_hz - 1) / min_freq_hz; // rounds up + + for (int i = head; i != tail; i = CIRCULAR_INC(i)) + { + // Now use id (hash) for matching to support multiple literals! + if ((timing_blocks[i].id == id) && timing_blocks[i].is_loop) + { + unsigned delta = now - timing_blocks[i].start_time; + if (delta > interval + XASSERT_TIMING_DELTA_ERROR) + { + fail_timing(tag, delta, interval, file, line); + } + timing_blocks[i].start_time = now; + return; + } + } + + if (CIRCULAR_FULL) + { + /* TODO maybe we should fail here instead? */ +#if XASSERT_ENABLE_DEBUG0 + printstr("WARNING: Timing buffer full. Dropping oldest entry.\n"); +#endif + drop_oldest_entry(); + } + + timing_blocks[tail].tag = tag; + timing_blocks[tail].start_time = now; + timing_blocks[tail].deadline = 0; + timing_blocks[tail].loop_interval_ticks = interval; + timing_blocks[tail].file = file; + timing_blocks[tail].line = line; + timing_blocks[tail].is_loop = 1; + timing_blocks[tail].id = id; + + tail = CIRCULAR_INC(tail); +} + +// Remove a loop timing entry for tag/id (used in exceptional cases) +static inline void xassert_loop_exception(const char *tag) +{ + unsigned id = XASSERT_TAG_ID(tag); + for (int i = head; i != tail; i = CIRCULAR_INC(i)) + { + if ((timing_blocks[i].id == id) && timing_blocks[i].is_loop) + { + for (int j = i; j != tail; j = CIRCULAR_INC(j)) + timing_blocks[j] = timing_blocks[CIRCULAR_INC(j)]; + tail = (tail == 0) ? MAX_TIMING_BLOCKS - 1 : tail - 1; + break; + } + } +} + +#define xassert_timing_start(tag, max_ticks) timing_start_impl(tag, XASSERT_TAG_ID(tag), max_ticks, __FILE__, __LINE__) +#define xassert_timing_end(tag) timing_end_impl(tag, XASSERT_TAG_ID(tag), __FILE__, __LINE__) +#define xassert_loop_freq(tag, hz) timing_loop_impl(tag, XASSERT_TAG_ID(tag), hz, __FILE__, __LINE__) + +#define XASSERT_TIMED_BLOCK(tag, max_ticks, body) \ + do { \ + xassert_timing_start(tag, max_ticks); \ + body \ + xassert_timing_end(tag); \ + } while (0) + +#else + +#define xassert_timing_start(tag, max_ticks) +#define xassert_timing_end(tag) +#define xassert_loop_freq(tag, hz) +#define xassert_loop_exception(tag) +#define XASSERT_TIMED_BLOCK(tag, max_ticks, body) body + +#endif + +#ifndef __STDC__ +} +#endif + #endif // __xassert_h__ + diff --git a/lib_xassert/src/xassert.xc b/lib_xassert/src/xassert.xc index 6e436a6..6656d53 100644 --- a/lib_xassert/src/xassert.xc +++ b/lib_xassert/src/xassert.xc @@ -3,3 +3,4 @@ #include "xassert.h" extern inline int xassert_msg(const char msg[]); +