From cbe6cc5959cf15babd8ea20b77f075e8f615e49e Mon Sep 17 00:00:00 2001 From: Ross Owen Date: Thu, 7 Aug 2025 15:19:37 +0100 Subject: [PATCH 1/5] Add xassert_time --- xassert_time.c | 81 ++++++++++++++++++++++++++++++++++++++++++++++++++ xassert_time.h | 54 +++++++++++++++++++++++++++++++++ 2 files changed, 135 insertions(+) create mode 100644 xassert_time.c create mode 100644 xassert_time.h diff --git a/xassert_time.c b/xassert_time.c new file mode 100644 index 0000000..edf4d75 --- /dev/null +++ b/xassert_time.c @@ -0,0 +1,81 @@ +#include "xassert_time.h" + +// Structure for tracking a timing block +typedef struct { + const char *tag; + unsigned start_time; + unsigned deadline; + const char *file; + int line; +} timing_block_t; + +#if XASSERT_ENABLE_ASSERTIONS0 + +static timing_block_t timing_blocks[MAX_TIMING_BLOCKS]; +static int timing_block_count = 0; + +void timing_assert_init(void) { + timing_block_count = 0; +} + +void timing_start_ex(const char *tag, unsigned max_ticks, const char *file, int line) { + unsigned now = get_time(); + + for (int i = 0; i < timing_block_count; ++i) { + if (timing_blocks[i].tag == tag) { + timing_blocks[i].start_time = now; + timing_blocks[i].deadline = now + max_ticks; + timing_blocks[i].file = file; + timing_blocks[i].line = line; + return; + } + } + + xassert(timing_block_count < MAX_TIMING_BLOCKS && msg("Too many concurrent timing blocks")); + + timing_blocks[timing_block_count++] = (timing_block_t){ + .tag = tag, + .start_time = now, + .deadline = now + max_ticks, + .file = file, + .line = line + }; +} + +void timing_end_ex(const char *tag, const char *file, int line) { + unsigned now = get_time(); + + for (int i = 0; i < timing_block_count; ++i) { + if (timing_blocks[i].tag == tag) { + if (now > timing_blocks[i].deadline) { + printstr("Timing deadline missed for tag: "); + printstr(tag); + printstr(" (Started at "); + printint(timing_blocks[i].start_time); + printstr(", Deadline "); + printint(timing_blocks[i].deadline); + printstr(", Now "); + printint(now); + printstr(")\nStart Location: "); + printstr(timing_blocks[i].file); + printstr(":"); + printint(timing_blocks[i].line); + printstr("\nEnd Location: "); + printstr(file); + printstr(":"); + printint(line); + printstr("\n"); + __builtin_trap(); + } + + // Remove this block by overwriting with the last one + timing_blocks[i] = timing_blocks[--timing_block_count]; + return; + } + } + + fail("timing_end() called for unknown tag (no prior timing_start())"); +} + +#endif // XASSERT_ENABLE_ASSERTIONS0 + diff --git a/xassert_time.h b/xassert_time.h new file mode 100644 index 0000000..9328896 --- /dev/null +++ b/xassert_time.h @@ -0,0 +1,54 @@ +// Copyright 2014-2025 XMOS LIMITED. +// This Software is subject to the terms of the XMOS Public Licence: Version 1. + +#ifndef __xassert_time_h__ +#define __xassert_time_h__ + +#include "xassert.h" + +#ifndef __XC__ +extern "C" { +#endif + +#ifndef MAX_TIMING_BLOCKS +#define MAX_TIMING_BLOCKS 8 +#endif + +#if XASSERT_ENABLE_ASSERTIONS0 + +// Initialize the timing system (optional in most systems) +void timing_assert_init(void); + +// Start a timing block with a deadline +void timing_start_ex(const char *tag, unsigned max_ticks, const char *file, int line); + +// End a timing block and assert that it's within deadline +void timing_end_ex(const char *tag, const char *file, int line); + +// Macros to insert file/line automatically +#define timing_start(tag, max_ticks) timing_start_ex(tag, max_ticks, __FILE__, __LINE__) +#define timing_end(tag) timing_end_ex(tag, __FILE__, __LINE__) + +// TIMED_BLOCK: for wrapping a block of code with a timing assertion +#define TIMED_BLOCK(tag, max_ticks, body) \ + do { \ + timing_start(tag, max_ticks); \ + body \ + timing_end(tag); \ + } while (0) + +#else + +#define timing_start(tag, max_ticks) ((void)0) +#define timing_end(tag) ((void)0) +#define TIMED_BLOCK(tag, max_ticks, body) do { body } while (0) +#define timing_assert_init() ((void)0) + +#endif + +#ifndef __XC__ +} +#endif + +#endif // __xassert_time_h__ + From 722a850c55002688751b5cc34a63d669f935677d Mon Sep 17 00:00:00 2001 From: Ross Owen Date: Fri, 8 Aug 2025 13:25:47 +0100 Subject: [PATCH 2/5] Moving all timing assert code to header --- examples/app_assert/src/fn_assert.c | 3 +- examples/app_timed_block/CMakeLists.txt | 13 ++ examples/app_timed_block/src/fn_assert.xc | 19 ++ examples/app_timed_block/src/fn_no_assert.xc | 19 ++ examples/app_timed_block/src/main.c | 11 + examples/app_timed_block/src/xassert_conf.h | 4 + examples/app_timed_loop/CMakeLists.txt | 13 ++ examples/app_timed_loop/src/fn_assert.xc | 23 ++ examples/app_timed_loop/src/fn_no_assert.xc | 25 +++ examples/app_timed_loop/src/main.c | 11 + lib_xassert/api/xassert.h | 218 ++++++++++++++++++- lib_xassert/src/xassert.xc | 1 + xassert_time.c | 81 ------- xassert_time.h | 54 ----- 14 files changed, 354 insertions(+), 141 deletions(-) create mode 100644 examples/app_timed_block/CMakeLists.txt create mode 100644 examples/app_timed_block/src/fn_assert.xc create mode 100644 examples/app_timed_block/src/fn_no_assert.xc create mode 100644 examples/app_timed_block/src/main.c create mode 100644 examples/app_timed_block/src/xassert_conf.h create mode 100644 examples/app_timed_loop/CMakeLists.txt create mode 100644 examples/app_timed_loop/src/fn_assert.xc create mode 100644 examples/app_timed_loop/src/fn_no_assert.xc create mode 100644 examples/app_timed_loop/src/main.c delete mode 100644 xassert_time.c delete mode 100644 xassert_time.h diff --git a/examples/app_assert/src/fn_assert.c b/examples/app_assert/src/fn_assert.c index 7059e01..ac7f0af 100644 --- a/examples/app_assert/src/fn_assert.c +++ b/examples/app_assert/src/fn_assert.c @@ -4,9 +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 +#include void fn_assert(int x) { 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/lib_xassert/api/xassert.h b/lib_xassert/api/xassert.h index 76d0b05..f358791 100644 --- a/lib_xassert/api/xassert.h +++ b/lib_xassert/api/xassert.h @@ -3,7 +3,9 @@ #ifndef __xassert_h__ #define __xassert_h__ + #ifdef __xassert_conf_h_exists__ +#error #include "xassert_conf.h" #endif @@ -19,6 +21,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 +48,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 +69,8 @@ #endif #if XASSERT_ENABLE_DEBUG0 -# include "print.h" +#include "print.h" +#include #endif #if XASSERT_ENABLE_LINE_NUMBERS @@ -63,12 +78,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 +115,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(", limit = "); printint(limit); \ + xassert_timing_print_line(file, line); __builtin_trap();\ + } while(0) #else # define fail(msg) do { __builtin_trap();} while(0) +# define fail_timing(tag, limit, actual, file, line) do { __builtin_trap();} while(0) #endif - #ifndef UNUSED #ifdef __XC__ #define UNUSED(x) do { x; } while(0); @@ -106,7 +135,6 @@ #endif #endif // UNUSED - inline int xassert_msg(const char msg[]) { UNUSED(msg); return 1; } #ifdef __XC__ @@ -121,4 +149,184 @@ 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_ex(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_ex(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); + } + 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; + return; + } + } + fail("timing_end() called without matching timing_start()"); +} + +static inline void timing_loop_ex(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; + + for (int i = head; i != tail; i = CIRCULAR_INC(i)) + { + // Use pointer equality for tag matching instead of hashing. + // This is safe and faster because only one string literal is used per loop timing site. + if ((timing_blocks[i].tag == tag) && timing_blocks[i].is_loop) + { + unsigned delta = now - timing_blocks[i].start_time; + if (delta > interval) + { + 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); +} + +#define xassert_timing_start(tag, max_ticks) timing_start_ex(tag, XASSERT_TAG_ID(tag), max_ticks, __FILE__, __LINE__) +#define xassert_timing_end(tag) timing_end_ex(tag, XASSERT_TAG_ID(tag), __FILE__, __LINE__) +#define xassert_loop_freq(tag, hz) timing_loop_ex(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_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[]); + diff --git a/xassert_time.c b/xassert_time.c deleted file mode 100644 index edf4d75..0000000 --- a/xassert_time.c +++ /dev/null @@ -1,81 +0,0 @@ -#include "xassert_time.h" - -// Structure for tracking a timing block -typedef struct { - const char *tag; - unsigned start_time; - unsigned deadline; - const char *file; - int line; -} timing_block_t; - -#if XASSERT_ENABLE_ASSERTIONS0 - -static timing_block_t timing_blocks[MAX_TIMING_BLOCKS]; -static int timing_block_count = 0; - -void timing_assert_init(void) { - timing_block_count = 0; -} - -void timing_start_ex(const char *tag, unsigned max_ticks, const char *file, int line) { - unsigned now = get_time(); - - for (int i = 0; i < timing_block_count; ++i) { - if (timing_blocks[i].tag == tag) { - timing_blocks[i].start_time = now; - timing_blocks[i].deadline = now + max_ticks; - timing_blocks[i].file = file; - timing_blocks[i].line = line; - return; - } - } - - xassert(timing_block_count < MAX_TIMING_BLOCKS && msg("Too many concurrent timing blocks")); - - timing_blocks[timing_block_count++] = (timing_block_t){ - .tag = tag, - .start_time = now, - .deadline = now + max_ticks, - .file = file, - .line = line - }; -} - -void timing_end_ex(const char *tag, const char *file, int line) { - unsigned now = get_time(); - - for (int i = 0; i < timing_block_count; ++i) { - if (timing_blocks[i].tag == tag) { - if (now > timing_blocks[i].deadline) { - printstr("Timing deadline missed for tag: "); - printstr(tag); - printstr(" (Started at "); - printint(timing_blocks[i].start_time); - printstr(", Deadline "); - printint(timing_blocks[i].deadline); - printstr(", Now "); - printint(now); - printstr(")\nStart Location: "); - printstr(timing_blocks[i].file); - printstr(":"); - printint(timing_blocks[i].line); - printstr("\nEnd Location: "); - printstr(file); - printstr(":"); - printint(line); - printstr("\n"); - __builtin_trap(); - } - - // Remove this block by overwriting with the last one - timing_blocks[i] = timing_blocks[--timing_block_count]; - return; - } - } - - fail("timing_end() called for unknown tag (no prior timing_start())"); -} - -#endif // XASSERT_ENABLE_ASSERTIONS0 - diff --git a/xassert_time.h b/xassert_time.h deleted file mode 100644 index 9328896..0000000 --- a/xassert_time.h +++ /dev/null @@ -1,54 +0,0 @@ -// Copyright 2014-2025 XMOS LIMITED. -// This Software is subject to the terms of the XMOS Public Licence: Version 1. - -#ifndef __xassert_time_h__ -#define __xassert_time_h__ - -#include "xassert.h" - -#ifndef __XC__ -extern "C" { -#endif - -#ifndef MAX_TIMING_BLOCKS -#define MAX_TIMING_BLOCKS 8 -#endif - -#if XASSERT_ENABLE_ASSERTIONS0 - -// Initialize the timing system (optional in most systems) -void timing_assert_init(void); - -// Start a timing block with a deadline -void timing_start_ex(const char *tag, unsigned max_ticks, const char *file, int line); - -// End a timing block and assert that it's within deadline -void timing_end_ex(const char *tag, const char *file, int line); - -// Macros to insert file/line automatically -#define timing_start(tag, max_ticks) timing_start_ex(tag, max_ticks, __FILE__, __LINE__) -#define timing_end(tag) timing_end_ex(tag, __FILE__, __LINE__) - -// TIMED_BLOCK: for wrapping a block of code with a timing assertion -#define TIMED_BLOCK(tag, max_ticks, body) \ - do { \ - timing_start(tag, max_ticks); \ - body \ - timing_end(tag); \ - } while (0) - -#else - -#define timing_start(tag, max_ticks) ((void)0) -#define timing_end(tag) ((void)0) -#define TIMED_BLOCK(tag, max_ticks, body) do { body } while (0) -#define timing_assert_init() ((void)0) - -#endif - -#ifndef __XC__ -} -#endif - -#endif // __xassert_time_h__ - From 727c5706b86dc5ffa16f01386c3bcf30180148d4 Mon Sep 17 00:00:00 2001 From: Ross Owen Date: Fri, 8 Aug 2025 17:31:25 +0100 Subject: [PATCH 3/5] Add timing loop exception --- .../app_timing_loop_exception/CMakeLists.txt | 13 ++++++ .../src/fn_no_assert.xc | 34 +++++++++++++++ examples/app_timing_loop_exception/src/main.c | 10 +++++ lib_xassert/api/xassert.h | 42 +++++++++++++------ 4 files changed, 87 insertions(+), 12 deletions(-) create mode 100644 examples/app_timing_loop_exception/CMakeLists.txt create mode 100644 examples/app_timing_loop_exception/src/fn_no_assert.xc create mode 100644 examples/app_timing_loop_exception/src/main.c 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 f358791..f7fd2c9 100644 --- a/lib_xassert/api/xassert.h +++ b/lib_xassert/api/xassert.h @@ -118,13 +118,13 @@ # 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(", limit = "); printint(limit); \ + 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, limit, actual, file, line) do { __builtin_trap();} while(0) +# define fail_timing(tag, actual, limit, file, line) do { __builtin_trap();} while(0) #endif #ifndef UNUSED @@ -204,7 +204,7 @@ static int tail = 0; static inline void drop_oldest_entry() { head = CIRCULAR_INC(head); } -static inline void timing_start_ex(const char *tag, unsigned id, unsigned max_ticks, const char *file, int line) +static inline void timing_start_impl(const char *tag, unsigned id, unsigned max_ticks, const char *file, int line) { unsigned now = get_time(); @@ -240,7 +240,7 @@ static inline void timing_start_ex(const char *tag, unsigned id, unsigned max_ti tail = CIRCULAR_INC(tail); } -static inline void timing_end_ex(const char *tag, unsigned id, const char *file, int line) +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)) @@ -262,16 +262,15 @@ static inline void timing_end_ex(const char *tag, unsigned id, const char *file, fail("timing_end() called without matching timing_start()"); } -static inline void timing_loop_ex(const char *tag, unsigned id, unsigned min_freq_hz, const char *file, int line) +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; for (int i = head; i != tail; i = CIRCULAR_INC(i)) { - // Use pointer equality for tag matching instead of hashing. - // This is safe and faster because only one string literal is used per loop timing site. - if ((timing_blocks[i].tag == tag) && timing_blocks[i].is_loop) + // 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) @@ -304,9 +303,27 @@ static inline void timing_loop_ex(const char *tag, unsigned id, unsigned min_fre tail = CIRCULAR_INC(tail); } -#define xassert_timing_start(tag, max_ticks) timing_start_ex(tag, XASSERT_TAG_ID(tag), max_ticks, __FILE__, __LINE__) -#define xassert_timing_end(tag) timing_end_ex(tag, XASSERT_TAG_ID(tag), __FILE__, __LINE__) -#define xassert_loop_freq(tag, hz) timing_loop_ex(tag, XASSERT_TAG_ID(tag), hz, __FILE__, __LINE__) +// 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__) +// Optionally, a macro for exception for API symmetry: +#define xassert_loop_exception(tag) xassert_loop_exception(tag) #define XASSERT_TIMED_BLOCK(tag, max_ticks, body) \ do { \ @@ -320,6 +337,7 @@ static inline void timing_loop_ex(const char *tag, unsigned id, unsigned min_fre #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 From f93667c0af3a5b1cba85c5e4b19714662b97059d Mon Sep 17 00:00:00 2001 From: Ross Owen Date: Fri, 8 Aug 2025 18:19:22 +0100 Subject: [PATCH 4/5] Add some (configurable) wiggle room in the freq case --- examples/app_assert/src/fn_assert.c | 1 - lib_xassert/api/xassert.h | 12 ++++++++++-- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/examples/app_assert/src/fn_assert.c b/examples/app_assert/src/fn_assert.c index ac7f0af..ac04e8b 100644 --- a/examples/app_assert/src/fn_assert.c +++ b/examples/app_assert/src/fn_assert.c @@ -6,7 +6,6 @@ #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) diff --git a/lib_xassert/api/xassert.h b/lib_xassert/api/xassert.h index f7fd2c9..f153556 100644 --- a/lib_xassert/api/xassert.h +++ b/lib_xassert/api/xassert.h @@ -262,10 +262,17 @@ static inline void timing_end_impl(const char *tag, unsigned id, const char *fil fail("timing_end() called without matching timing_start()"); } +#ifndef XASSERT_TIMING_DELTA_ERROR +#define XASSERT_TIMING_DELTA_ERROR (2) +#endif + +extern unsigned counter; + 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; + unsigned interval = (XS1_TIMER_HZ + min_freq_hz - 1) / min_freq_hz; // rounds up for (int i = head; i != tail; i = CIRCULAR_INC(i)) { @@ -273,8 +280,9 @@ static inline void timing_loop_impl(const char *tag, unsigned id, unsigned min_f if ((timing_blocks[i].id == id) && timing_blocks[i].is_loop) { unsigned delta = now - timing_blocks[i].start_time; - if (delta > interval) + if (delta > interval + XASSERT_TIMING_DELTA_ERROR) { + printintln(counter); fail_timing(tag, delta, interval, file, line); } timing_blocks[i].start_time = now; From 787d986cd216ffd6683c1e5e33137ed95d569d1d Mon Sep 17 00:00:00 2001 From: Ross Owen Date: Fri, 8 Aug 2025 18:43:43 +0100 Subject: [PATCH 5/5] Speed up removing timing tags --- lib_xassert/api/xassert.h | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/lib_xassert/api/xassert.h b/lib_xassert/api/xassert.h index f153556..49eb33d 100644 --- a/lib_xassert/api/xassert.h +++ b/lib_xassert/api/xassert.h @@ -5,7 +5,6 @@ #ifdef __xassert_conf_h_exists__ -#error #include "xassert_conf.h" #endif @@ -243,6 +242,7 @@ static inline void timing_start_impl(const char *tag, unsigned id, unsigned max_ 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) @@ -251,14 +251,20 @@ static inline void timing_end_impl(const char *tag, unsigned id, const char *fil { fail_timing(tag, now - timing_blocks[i].start_time, timing_blocks[i].deadline - timing_blocks[i].start_time, file, line); } - for (int j = i; j != tail; j = CIRCULAR_INC(j)) + + int last = (tail == 0) ? MAX_TIMING_BLOCKS - 1 : tail - 1; + if (i != last) { - timing_blocks[j] = timing_blocks[CIRCULAR_INC(j)]; + // swap in last active entry + timing_blocks[i] = timing_blocks[last]; } - tail = (tail == 0) ? MAX_TIMING_BLOCKS - 1 : tail - 1; - return; + + // logically remove the last entry + tail = last; + return ; } } + fail("timing_end() called without matching timing_start()"); } @@ -266,8 +272,6 @@ static inline void timing_end_impl(const char *tag, unsigned id, const char *fil #define XASSERT_TIMING_DELTA_ERROR (2) #endif -extern unsigned counter; - static inline void timing_loop_impl(const char *tag, unsigned id, unsigned min_freq_hz, const char *file, int line) { unsigned now = get_time(); @@ -282,7 +286,6 @@ static inline void timing_loop_impl(const char *tag, unsigned id, unsigned min_f unsigned delta = now - timing_blocks[i].start_time; if (delta > interval + XASSERT_TIMING_DELTA_ERROR) { - printintln(counter); fail_timing(tag, delta, interval, file, line); } timing_blocks[i].start_time = now; @@ -330,8 +333,6 @@ static inline void xassert_loop_exception(const char *tag) #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__) -// Optionally, a macro for exception for API symmetry: -#define xassert_loop_exception(tag) xassert_loop_exception(tag) #define XASSERT_TIMED_BLOCK(tag, max_ticks, body) \ do { \