mirror of
https://github.com/RIOT-OS/RIOT.git
synced 2025-01-18 12:52:44 +01:00
2006937ee6
This test is intended to collect statistics about the runtime delays in the periph_timer implementation. This tool is mainly intended to detect problems in the low level driver implementation which can be difficult to detect from higher level systems such as xtimer.
751 lines
24 KiB
C
751 lines
24 KiB
C
/*
|
|
* Copyright (C) 2018 Eistec AB
|
|
*
|
|
* This file is subject to the terms and conditions of the GNU Lesser
|
|
* General Public License v2.1. See the file LICENSE in the top level
|
|
* directory for more details.
|
|
*/
|
|
|
|
/**
|
|
* @ingroup tests
|
|
* @{
|
|
*
|
|
* @file
|
|
* @brief Another peripheral timer test application
|
|
*
|
|
* @author Joakim Nohlgård <joakim.nohlgard@eistec.se>
|
|
*
|
|
* @}
|
|
*/
|
|
|
|
#include <stddef.h>
|
|
#include <stdint.h>
|
|
#include <string.h>
|
|
|
|
#include "bitarithm.h"
|
|
#include "fmt.h"
|
|
#include "mutex.h"
|
|
#include "random.h"
|
|
#include "div.h"
|
|
#include "matstat.h"
|
|
#include "thread.h"
|
|
#ifdef MODULE_PERIPH_RTT
|
|
#include "periph/rtt.h"
|
|
#endif
|
|
#if TEST_XTIMER
|
|
#include "xtimer.h"
|
|
#endif
|
|
|
|
#include "board.h"
|
|
#include "cpu.h"
|
|
#include "periph/timer.h"
|
|
|
|
#include "print_results.h"
|
|
#include "spin_random.h"
|
|
#include "bench_timers_config.h"
|
|
|
|
#ifndef TEST_TRACE
|
|
#define TEST_TRACE 0
|
|
#endif
|
|
|
|
/*
|
|
* All different variations will be mixed to provide the most varied input
|
|
* vector possible for the benchmark. A more varied input should yield a more
|
|
* correct estimate of the mean error and variance. Random CPU processing delays
|
|
* will be inserted between each step to avoid phase locking the benchmark to
|
|
* unobservable timer internals.
|
|
*/
|
|
#if TEST_XTIMER
|
|
enum test_variants {
|
|
TEST_XTIMER_SET = 0,
|
|
TEST_PARALLEL = 1,
|
|
TEST_XTIMER_SET_ABSOLUTE = 2,
|
|
TEST_XTIMER_PERIODIC_WAKEUP = 4,
|
|
TEST_XTIMER_SPIN = 6,
|
|
TEST_VARIANT_NUMOF = 8,
|
|
};
|
|
#else /* TEST_XTIMER */
|
|
/*
|
|
* Results will be grouped by function, rescheduling yes/no, start/stop.
|
|
* functions: timer_set, timer_set_absolute
|
|
* reschedule: yes/no, when yes: first set one target time, before that time has
|
|
* passed, set the real target time
|
|
* start/stop: if stop: call timer_stop before setting the target time, then call timer_start
|
|
*/
|
|
enum test_variants {
|
|
TEST_RESCHEDULE = 1,
|
|
TEST_STOPPED = 2,
|
|
TEST_ABSOLUTE = 4,
|
|
TEST_VARIANT_NUMOF = 8,
|
|
};
|
|
#endif /* else TEST_XTIMER */
|
|
|
|
/* Benchmark processing overhead, results will be compensated for this to make
|
|
* the results easier to understand */
|
|
static int32_t overhead_target;
|
|
static int32_t overhead_read;
|
|
|
|
/* Seed for initializing the random module */
|
|
static uint32_t seed = 123;
|
|
|
|
/* Mutex used for signalling between main thread and ISR callback */
|
|
static mutex_t mtx_cb = MUTEX_INIT_LOCKED;
|
|
|
|
/* Test state element */
|
|
typedef struct {
|
|
matstat_state_t *ref_state; /* timer_set error statistics state */
|
|
matstat_state_t *int_state; /* timer_read error statistics state */
|
|
unsigned int target_ref; /* Target time in reference timer */
|
|
unsigned int target_tut; /* Target time in timer under test */
|
|
} test_ctx_t;
|
|
|
|
static test_ctx_t test_context;
|
|
|
|
#if DETAILED_STATS
|
|
#if LOG2_STATS
|
|
/* Group test values by 2-logarithm to reduce memory requirements */
|
|
static matstat_state_t ref_states[TEST_VARIANT_NUMOF * TEST_LOG2NUM];
|
|
#else
|
|
/* State vector, first half will contain state for timer_set tests, second half
|
|
* will contain state for timer_set_absolute */
|
|
static matstat_state_t ref_states[TEST_VARIANT_NUMOF * TEST_NUM];
|
|
#endif
|
|
#else
|
|
/* Only keep stats per function variation */
|
|
static matstat_state_t ref_states[TEST_VARIANT_NUMOF];
|
|
#endif
|
|
|
|
/* timer_read error statistics states */
|
|
static matstat_state_t int_states[TEST_VARIANT_NUMOF];
|
|
|
|
/* Limits for the mean and variance, to compare the results against expectation */
|
|
static stat_limits_t ref_limits;
|
|
static stat_limits_t int_limits;
|
|
|
|
#if TEST_XTIMER
|
|
static const result_presentation_t presentation = {
|
|
.groups = (const result_group_t[1]) {
|
|
{
|
|
.label = "xtimer",
|
|
.sub_labels = (const char *[]){
|
|
[TEST_XTIMER_SET] = "_xt_set",
|
|
[TEST_XTIMER_SET | TEST_PARALLEL] = "_xt_set race",
|
|
[TEST_XTIMER_SET_ABSOLUTE] = "_xt_set_abs",
|
|
[TEST_XTIMER_SET_ABSOLUTE | TEST_PARALLEL] = "_xt_set_abs race",
|
|
[TEST_XTIMER_PERIODIC_WAKEUP] = "_xt_periodic",
|
|
[TEST_XTIMER_PERIODIC_WAKEUP | TEST_PARALLEL] = "_xt_periodic race",
|
|
[TEST_XTIMER_SPIN] = "_xt_spin",
|
|
[TEST_XTIMER_SPIN | TEST_PARALLEL] = "_xt_spin race",
|
|
},
|
|
.num_sub_labels = TEST_VARIANT_NUMOF,
|
|
},
|
|
},
|
|
.num_groups = 1,
|
|
.ref_limits = &ref_limits,
|
|
.int_limits = &int_limits,
|
|
.offsets = (const unsigned[]) {
|
|
[TEST_XTIMER_SET] = TEST_MIN_REL,
|
|
[TEST_XTIMER_SET | TEST_PARALLEL] = TEST_MIN_REL,
|
|
[TEST_XTIMER_SET_ABSOLUTE] = TEST_MIN,
|
|
[TEST_XTIMER_SET_ABSOLUTE | TEST_PARALLEL] = TEST_MIN,
|
|
[TEST_XTIMER_PERIODIC_WAKEUP] = TEST_MIN_REL,
|
|
[TEST_XTIMER_PERIODIC_WAKEUP | TEST_PARALLEL] = TEST_MIN_REL,
|
|
[TEST_XTIMER_SPIN] = TEST_MIN_REL,
|
|
[TEST_XTIMER_SPIN | TEST_PARALLEL] = TEST_MIN_REL,
|
|
},
|
|
};
|
|
#else /* TEST_XTIMER */
|
|
/* Number of variant groups, used when printing results */
|
|
#define TEST_VARIANT_GROUPS 2
|
|
|
|
static const result_presentation_t presentation = {
|
|
.groups = (const result_group_t[TEST_VARIANT_GROUPS]) {
|
|
{
|
|
.label = "timer_set",
|
|
.sub_labels = (const char *[]){
|
|
[0] = "running",
|
|
[TEST_RESCHEDULE] = "resched",
|
|
[TEST_STOPPED] = "stopped",
|
|
[TEST_STOPPED | TEST_RESCHEDULE] = "stopped, resched",
|
|
},
|
|
.num_sub_labels = (TEST_VARIANT_NUMOF / TEST_VARIANT_GROUPS),
|
|
},
|
|
{
|
|
.label = "timer_set_absolute",
|
|
.sub_labels = (const char *[]){
|
|
[0] = "running",
|
|
[TEST_RESCHEDULE] = "resched",
|
|
[TEST_STOPPED] = "stopped",
|
|
[TEST_STOPPED | TEST_RESCHEDULE] = "stopped, resched",
|
|
},
|
|
.num_sub_labels = (TEST_VARIANT_NUMOF / TEST_VARIANT_GROUPS),
|
|
},
|
|
},
|
|
.num_groups = TEST_VARIANT_GROUPS,
|
|
.ref_limits = &ref_limits,
|
|
.int_limits = &int_limits,
|
|
.offsets = (const unsigned[]) {
|
|
[0] = TEST_MIN_REL,
|
|
[TEST_RESCHEDULE] = TEST_MIN_REL,
|
|
[TEST_STOPPED] = TEST_MIN_REL,
|
|
[TEST_STOPPED | TEST_RESCHEDULE] = TEST_MIN_REL,
|
|
[TEST_ABSOLUTE] = TEST_MIN,
|
|
[TEST_ABSOLUTE | TEST_RESCHEDULE] = TEST_MIN,
|
|
[TEST_ABSOLUTE | TEST_STOPPED] = TEST_MIN,
|
|
[TEST_ABSOLUTE | TEST_STOPPED | TEST_RESCHEDULE] = TEST_MIN,
|
|
},
|
|
};
|
|
#endif /* else TEST_XTIMER */
|
|
|
|
#ifdef MODULE_PERIPH_RTT
|
|
static uint32_t rtt_begin;
|
|
#endif
|
|
|
|
static unsigned int ref_begin;
|
|
static unsigned int tut_begin;
|
|
|
|
/**
|
|
* @brief Calculate the limits for mean and variance for this test
|
|
*/
|
|
static void set_limits(void)
|
|
{
|
|
ref_limits.mean_low = -(TEST_UNEXPECTED_MEAN);
|
|
ref_limits.mean_high = (TEST_UNEXPECTED_MEAN);
|
|
ref_limits.variance_low = 0;
|
|
ref_limits.variance_high = (TEST_UNEXPECTED_STDDEV) * (TEST_UNEXPECTED_STDDEV);
|
|
|
|
int_limits.mean_low = -(TEST_UNEXPECTED_MEAN);
|
|
int_limits.mean_high = TEST_UNEXPECTED_MEAN;
|
|
int_limits.variance_low = 0;
|
|
int_limits.variance_high = (TEST_UNEXPECTED_STDDEV) * (TEST_UNEXPECTED_STDDEV);
|
|
|
|
/* The quantization errors should be uniformly distributed within +/- 0.5
|
|
* test timer ticks of the reference time */
|
|
/* The formula for the variance of a rectangle distribution on [a, b] is
|
|
* Var = (b - a)^2 / 12 (taken directly from a statistics textbook)
|
|
* Using (b - a)^2 / 12 == (10b - 10a) * ((10b + 1) - (10a + 1)) / 1200
|
|
* gives a smaller truncation error when using integer operations for
|
|
* converting the ticks */
|
|
uint32_t conversion_variance = ((TIM_TEST_TO_REF(10) - TIM_TEST_TO_REF(0)) *
|
|
(TIM_TEST_TO_REF(11) - TIM_TEST_TO_REF(1))) / 1200;
|
|
if (TIM_REF_FREQ > TIM_TEST_FREQ) {
|
|
ref_limits.variance_low = ((TIM_TEST_TO_REF(10) - TIM_TEST_TO_REF(0) - 10 * (TEST_UNEXPECTED_STDDEV)) *
|
|
(TIM_TEST_TO_REF(11) - TIM_TEST_TO_REF(1) - 10 * (TEST_UNEXPECTED_STDDEV))) / 1200;
|
|
ref_limits.variance_high = ((TIM_TEST_TO_REF(10) - TIM_TEST_TO_REF(0) + 10 * (TEST_UNEXPECTED_STDDEV)) *
|
|
(TIM_TEST_TO_REF(11) - TIM_TEST_TO_REF(1) + 10 * (TEST_UNEXPECTED_STDDEV))) / 1200;
|
|
/* The limits of the mean should account for the conversion error as well */
|
|
/* rounded towards positive infinity */
|
|
int32_t mean_error = (TIM_TEST_TO_REF(128) - TIM_TEST_TO_REF(0) + 127) / 128;
|
|
ref_limits.mean_high += mean_error;
|
|
}
|
|
|
|
print_str("Expected error variance due to truncation in tick conversion: ");
|
|
print_u32_dec(conversion_variance);
|
|
print("\n", 1);
|
|
}
|
|
|
|
/* Callback for the timeout */
|
|
static void cb(void *arg)
|
|
{
|
|
unsigned int now_tut = READ_TUT();
|
|
unsigned int now_ref = timer_read(TIM_REF_DEV);
|
|
if (arg == NULL) {
|
|
print_str("cb: Warning! arg = NULL\n");
|
|
return;
|
|
}
|
|
test_ctx_t *ctx = arg;
|
|
if (ctx->ref_state == NULL) {
|
|
print_str("cb: Warning! ref_state = NULL\n");
|
|
return;
|
|
}
|
|
if (ctx->int_state == NULL) {
|
|
print_str("cb: Warning! int_state = NULL\n");
|
|
return;
|
|
}
|
|
/* Update running stats */
|
|
/* When setting a timer with a timeout of X ticks, we expect the
|
|
* duration between the set and the callback, dT, to be at least
|
|
* X * time_per_tick.
|
|
* In order to ensure that dT <= X * time_per_tick, the timer read value
|
|
* will actually have incremented (X + 1) times during that period,
|
|
* because the set can occur asynchrously anywhere between timer
|
|
* increments. Therefore, in this test, we consider (X + 1) to be the
|
|
* expected timer_read value at the point the callback is called.
|
|
*/
|
|
|
|
/* Check that reference timer did not overflow during the test */
|
|
if ((now_ref + 0x4000u) >= ctx->target_ref) {
|
|
int32_t diff = now_ref - ctx->target_ref - 1 - overhead_target;
|
|
matstat_add(ctx->ref_state, diff);
|
|
}
|
|
/* Update timer_read statistics only when timer_read has not overflowed
|
|
* since the timer was set */
|
|
if ((now_tut + 0x4000u) >= ctx->target_tut) {
|
|
int32_t diff = now_tut - ctx->target_tut - 1 - overhead_read;
|
|
matstat_add(ctx->int_state, diff);
|
|
}
|
|
|
|
mutex_unlock(&mtx_cb);
|
|
}
|
|
|
|
/* Wrapper for periph_timer callbacks */
|
|
static void cb_timer_periph(void *arg, int chan)
|
|
{
|
|
(void)chan;
|
|
cb(arg);
|
|
}
|
|
|
|
/**
|
|
* @brief Select the proper state for the given test number depending on the
|
|
* compile time configuration
|
|
*
|
|
* Depends on DETAILED_STATS, LOG2_STATS
|
|
*/
|
|
static void assign_state_ptr(test_ctx_t *ctx, unsigned int variant, uint32_t interval)
|
|
{
|
|
ctx->int_state = &int_states[variant];
|
|
if (DETAILED_STATS) {
|
|
if (LOG2_STATS) {
|
|
unsigned int log2num = bitarithm_msb(interval);
|
|
|
|
ctx->ref_state = &ref_states[variant * TEST_LOG2NUM + log2num];
|
|
}
|
|
else {
|
|
ctx->ref_state = &ref_states[variant * TEST_NUM + interval];
|
|
}
|
|
}
|
|
else {
|
|
ctx->ref_state = &ref_states[variant];
|
|
}
|
|
}
|
|
|
|
static uint32_t derive_interval(uint32_t num)
|
|
{
|
|
uint32_t interval;
|
|
if ((DETAILED_STATS) && (LOG2_STATS)) {
|
|
/* Use a logarithmic method to generate geometric variates in order to
|
|
* populate the result table evenly across all buckets */
|
|
|
|
/* Static exponent mask, picking the mask as tightly as possible reduces the
|
|
* probability of discarded values, which reduces the computing overhead
|
|
* between test iterations */
|
|
|
|
static uint32_t exp_mask = 0;
|
|
if (exp_mask == 0) {
|
|
/* non-constant initializer */
|
|
exp_mask = (2 << bitarithm_msb(TEST_LOG2NUM)) - 1;
|
|
print_str("exp_mask = ");
|
|
print_u32_hex(exp_mask);
|
|
print("\n", 1);
|
|
print_str("max interval = ");
|
|
print_u32_dec((2 << exp_mask) - 1);
|
|
print("\n", 1);
|
|
}
|
|
|
|
/* Pick an exponent based on the top bits of the number */
|
|
/* exponent will be a number in the interval [0, log2(TEST_NUM) + 1] */
|
|
unsigned int exponent = ((num >> (32 - 8)) & exp_mask);
|
|
if (exponent == 0) {
|
|
/* Special handling to avoid the situation where we never see a zero */
|
|
/* We could also have used an extra right shift in the else case,
|
|
* but the state grouping also groups 0 and 1 in the same bucket, which means that they are twice as likely */
|
|
interval = bitarithm_bits_set(num) & 1;
|
|
}
|
|
else {
|
|
interval = (1 << exponent);
|
|
interval |= (num & (interval - 1));
|
|
}
|
|
}
|
|
else {
|
|
static const uint32_t mask = (1 << TEST_LOG2NUM) - 1;
|
|
interval = num & mask;
|
|
}
|
|
return interval;
|
|
}
|
|
|
|
#if TEST_XTIMER
|
|
static void nop(void *arg)
|
|
{
|
|
(void)arg;
|
|
}
|
|
|
|
static void run_test(test_ctx_t *ctx, uint32_t interval, unsigned int variant)
|
|
{
|
|
interval += TEST_MIN;
|
|
unsigned int interval_ref = TIM_TEST_TO_REF(interval);
|
|
xtimer_t xt = {
|
|
.target = 0,
|
|
.long_target = 0,
|
|
.callback = cb,
|
|
.arg = ctx,
|
|
};
|
|
xtimer_t xt_parallel = {
|
|
.target = 0,
|
|
.long_target = 0,
|
|
.callback = nop,
|
|
.arg = NULL,
|
|
};
|
|
if (TEST_TRACE) {
|
|
switch (variant & ~TEST_PARALLEL) {
|
|
case TEST_XTIMER_SET:
|
|
print_str("rel ");
|
|
break;
|
|
case TEST_XTIMER_SET_ABSOLUTE:
|
|
print_str("abs ");
|
|
break;
|
|
case TEST_XTIMER_PERIODIC_WAKEUP:
|
|
print_str("per ");
|
|
break;
|
|
case TEST_XTIMER_SPIN:
|
|
print_str("spn ");
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
if (variant & TEST_PARALLEL) {
|
|
print_str("- ");
|
|
}
|
|
else {
|
|
print_str("= ");
|
|
}
|
|
print_u32_dec(interval);
|
|
print("\n", 1);
|
|
}
|
|
|
|
spin_random_delay();
|
|
if (variant & TEST_PARALLEL) {
|
|
_xtimer_set(&xt_parallel, interval);
|
|
//~ interval += XTIMER_BACKOFF;
|
|
spin_random_delay();
|
|
}
|
|
ctx->target_ref = timer_read(TIM_REF_DEV) + interval_ref;
|
|
uint32_t now = READ_TUT();
|
|
ctx->target_tut = now + interval;
|
|
switch (variant & ~TEST_PARALLEL) {
|
|
case TEST_XTIMER_SET:
|
|
_xtimer_set(&xt, interval);
|
|
break;
|
|
case TEST_XTIMER_SET_ABSOLUTE:
|
|
now = READ_TUT();
|
|
ctx->target_tut = now + interval;
|
|
_xtimer_set_absolute(&xt, ctx->target_tut);
|
|
break;
|
|
case TEST_XTIMER_PERIODIC_WAKEUP:
|
|
_xtimer_periodic_wakeup(&now, interval);
|
|
/* xtimer_periodic_wakeup sleeps the thread, no automatic callback */
|
|
cb(xt.arg);
|
|
break;
|
|
case TEST_XTIMER_SPIN:
|
|
_xtimer_spin(interval);
|
|
/* xtimer_spin sleeps the thread, no automatic callback */
|
|
cb(xt.arg);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
mutex_lock(&mtx_cb);
|
|
xtimer_remove(&xt_parallel);
|
|
xtimer_remove(&xt);
|
|
}
|
|
#else /* TEST_XTIMER */
|
|
static void run_test(test_ctx_t *ctx, uint32_t interval, unsigned int variant)
|
|
{
|
|
if (variant & TEST_ABSOLUTE) {
|
|
interval += TEST_MIN;
|
|
}
|
|
else {
|
|
interval += TEST_MIN_REL;
|
|
}
|
|
unsigned int interval_ref = TIM_TEST_TO_REF(interval);
|
|
|
|
if (TEST_TRACE) {
|
|
if (variant & TEST_ABSOLUTE) {
|
|
print_str("A");
|
|
}
|
|
else {
|
|
print_str("_");
|
|
}
|
|
if (variant & TEST_RESCHEDULE) {
|
|
print_str("R");
|
|
}
|
|
else {
|
|
print_str("_");
|
|
}
|
|
if (variant & TEST_STOPPED) {
|
|
print_str("S ");
|
|
}
|
|
else {
|
|
print_str("_ ");
|
|
}
|
|
print_u32_dec(interval);
|
|
print_str(" ");
|
|
print_u64_hex(READ_TUT());
|
|
print("\n", 1);
|
|
}
|
|
|
|
spin_random_delay();
|
|
if (variant & TEST_RESCHEDULE) {
|
|
timer_set(TIM_TEST_DEV, TIM_TEST_CHAN, interval + RESCHEDULE_MARGIN);
|
|
spin_random_delay();
|
|
}
|
|
if (variant & TEST_STOPPED) {
|
|
timer_stop(TIM_TEST_DEV);
|
|
spin_random_delay();
|
|
}
|
|
ctx->target_ref = timer_read(TIM_REF_DEV) + interval_ref;
|
|
ctx->target_tut = READ_TUT() + interval;
|
|
if (variant & TEST_ABSOLUTE) {
|
|
timer_set_absolute(TIM_TEST_DEV, TIM_TEST_CHAN, ctx->target_tut);
|
|
}
|
|
else {
|
|
timer_set(TIM_TEST_DEV, TIM_TEST_CHAN, interval);
|
|
}
|
|
if (variant & TEST_STOPPED) {
|
|
spin_random_delay();
|
|
/* do not update ctx->target_tut, because TUT should have been stopped
|
|
* and not incremented during spin_random_delay */
|
|
ctx->target_ref = timer_read(TIM_REF_DEV) + interval_ref;
|
|
timer_start(TIM_TEST_DEV);
|
|
}
|
|
mutex_lock(&mtx_cb);
|
|
}
|
|
#endif /* TEST_XTIMER */
|
|
|
|
static int test_timer(void)
|
|
{
|
|
uint32_t time_last = timer_read(TIM_REF_DEV);
|
|
uint32_t time_elapsed = 0;
|
|
do {
|
|
uint32_t num = random_uint32();
|
|
|
|
unsigned int variant = (num >> (32 - 3));
|
|
if (variant >= TEST_VARIANT_NUMOF) {
|
|
continue;
|
|
}
|
|
uint32_t interval = derive_interval(num);
|
|
if (interval >= TEST_NUM) {
|
|
/* Discard values outside our test range */
|
|
continue;
|
|
}
|
|
assign_state_ptr(&test_context, variant, interval);
|
|
run_test(&test_context, interval, variant);
|
|
uint32_t now = timer_read(TIM_REF_DEV);
|
|
if (now >= time_last) {
|
|
/* Account for reference timer possibly overflowing before 30 seconds have passed */
|
|
time_elapsed += now - time_last;
|
|
}
|
|
time_last = now;
|
|
//~ print_str("e: ");
|
|
//~ print_u32_dec(time_elapsed);
|
|
//~ print_str("\n");
|
|
} while(time_elapsed < TEST_PRINT_INTERVAL_TICKS);
|
|
|
|
uint32_t ref_now = timer_read(TIM_REF_DEV);
|
|
uint32_t tut_now = READ_TUT();
|
|
#ifdef MODULE_PERIPH_RTT
|
|
uint32_t rtt_now = rtt_get_counter();
|
|
#endif
|
|
print_str("Elapsed time:\n");
|
|
print_str(" Reference: ");
|
|
print_u32_dec((ref_now - ref_begin) / TIM_REF_FREQ);
|
|
print_str("\n");
|
|
print_str(" Timer under test: ");
|
|
print_u32_dec((tut_now - tut_begin) / TIM_TEST_FREQ);
|
|
print_str("\n");
|
|
#ifdef MODULE_PERIPH_RTT
|
|
print_str(" Wall clock (RTT): ");
|
|
print_u32_dec((rtt_now - rtt_begin) / RTT_FREQUENCY);
|
|
print_str("\n");
|
|
#endif
|
|
print_results(&presentation, &ref_states[0], &int_states[0]);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void estimate_cpu_overhead(void)
|
|
{
|
|
/* Try to estimate the amount of CPU overhead between test start to test
|
|
* finish to get a better reading */
|
|
print_str("Estimating benchmark overhead...\n");
|
|
uint32_t interval = 0;
|
|
overhead_target = 0;
|
|
overhead_read = 0;
|
|
test_ctx_t context;
|
|
test_ctx_t *ctx = &context;
|
|
matstat_state_t ref_state = MATSTAT_STATE_INIT;
|
|
matstat_state_t int_state = MATSTAT_STATE_INIT;
|
|
ctx->ref_state = &ref_state;
|
|
ctx->int_state = &int_state;
|
|
for (unsigned int k = 0; k < ESTIMATE_CPU_ITERATIONS; ++k) {
|
|
unsigned int interval_ref = TIM_TEST_TO_REF(interval);
|
|
spin_random_delay();
|
|
ctx->target_tut = READ_TUT() + interval - 1;
|
|
ctx->target_ref = timer_read(TIM_REF_DEV) + interval_ref - 1;
|
|
/* call yield to simulate a context switch to isr and back */
|
|
thread_yield_higher();
|
|
cb_timer_periph(ctx, TIM_TEST_CHAN);
|
|
mutex_lock(&mtx_cb);
|
|
}
|
|
overhead_target = matstat_mean(&ref_state);
|
|
overhead_read = matstat_mean(&int_state);
|
|
print_str("overhead_target = ");
|
|
print_s32_dec(overhead_target);
|
|
print_str(" (s2 = ");
|
|
uint32_t var = matstat_variance(&ref_state);
|
|
print_u32_dec(var);
|
|
print_str(")\n");
|
|
if (var > 2) {
|
|
print_str("Warning: Variance in CPU estimation is too high\n");
|
|
#ifdef CPU_NATIVE
|
|
print_str("This is expected on native when other processes are running\n");
|
|
#endif
|
|
}
|
|
print_str("overhead_read = ");
|
|
print_s32_dec(overhead_read);
|
|
print_str(" (s2 = ");
|
|
var = matstat_variance(&int_state);
|
|
print_u32_dec(var);
|
|
print_str(")\n");
|
|
if (var > 2) {
|
|
print_str("Warning: Variance in CPU estimation is too high\n");
|
|
#ifdef CPU_NATIVE
|
|
print_str("This is expected on native when other processes are running\n");
|
|
#endif
|
|
}
|
|
}
|
|
|
|
int main(void)
|
|
{
|
|
print_str("\nStatistical benchmark for timers\n");
|
|
for (unsigned int k = 0; k < (sizeof(ref_states) / sizeof(ref_states[0])); ++k) {
|
|
matstat_clear(&ref_states[k]);
|
|
}
|
|
for (unsigned int k = 0; k < (sizeof(int_states) / sizeof(int_states[0])); ++k) {
|
|
matstat_clear(&int_states[k]);
|
|
}
|
|
/* print test overview */
|
|
print_str("Running timer test with seed ");
|
|
print_u32_dec(seed);
|
|
print_str(" using ");
|
|
#if MODULE_PRNG_MERSENNE
|
|
print_str("Mersenne Twister PRNG.\n");
|
|
#elif MODULE_PRNG_MINSTD
|
|
print_str("Park & Miller Minimal Standard PRNG.\n");
|
|
#elif MODULE_PRNG_MUSL_LCG
|
|
print_str("Musl C PRNG.\n");
|
|
#elif MODULE_PRNG_TINYMT32
|
|
print_str("Tiny Mersenne Twister PRNG.\n");
|
|
#elif MODULE_PRNG_XORSHIFT
|
|
print_str("XOR Shift PRNG.\n");
|
|
#else
|
|
print_str("unknown PRNG.\n");
|
|
#endif
|
|
|
|
print_str("TEST_MIN = ");
|
|
print_u32_dec(TEST_MIN);
|
|
print("\n", 1);
|
|
print_str("TEST_MAX = ");
|
|
print_u32_dec(TEST_MAX);
|
|
print("\n", 1);
|
|
print_str("TEST_MIN_REL = ");
|
|
print_u32_dec(TEST_MIN_REL);
|
|
print("\n", 1);
|
|
print_str("TEST_MAX_REL = ");
|
|
print_u32_dec(TEST_MIN_REL + TEST_NUM - 1);
|
|
print("\n", 1);
|
|
print_str("TEST_NUM = ");
|
|
print_u32_dec(TEST_NUM);
|
|
print("\n", 1);
|
|
print_str("log2(TEST_NUM - 1) = ");
|
|
unsigned log2test = bitarithm_msb(TEST_NUM - 1);
|
|
print_u32_dec(log2test);
|
|
print("\n", 1);
|
|
print_str("state vector elements per variant = ");
|
|
print_u32_dec(sizeof(ref_states) / sizeof(ref_states[0]) / TEST_VARIANT_NUMOF);
|
|
print("\n", 1);
|
|
print_str("number of variants = ");
|
|
print_u32_dec(TEST_VARIANT_NUMOF);
|
|
print("\n", 1);
|
|
print_str("sizeof(state) = ");
|
|
print_u32_dec(sizeof(ref_states[0]));
|
|
print_str(" bytes\n");
|
|
print_str("state vector total memory usage = ");
|
|
print_u32_dec(sizeof(ref_states));
|
|
print_str(" bytes\n");
|
|
assert(log2test < TEST_LOG2NUM);
|
|
print_str("TIM_TEST_DEV = ");
|
|
print_u32_dec(TIM_TEST_DEV);
|
|
print_str(", TIM_TEST_FREQ = ");
|
|
print_u32_dec(TIM_TEST_FREQ);
|
|
print_str(", TIM_TEST_CHAN = ");
|
|
print_u32_dec(TIM_TEST_CHAN);
|
|
print("\n", 1);
|
|
print_str("TIM_REF_DEV = ");
|
|
print_u32_dec(TIM_REF_DEV);
|
|
print_str(", TIM_REF_FREQ = ");
|
|
print_u32_dec(TIM_REF_FREQ);
|
|
print("\n", 1);
|
|
#ifdef TIM_TEST_TO_REF_SHIFT
|
|
print_str("TIM_TEST_TO_REF_SHIFT = ");
|
|
print_u32_dec(TIM_TEST_TO_REF_SHIFT);
|
|
print("\n", 1);
|
|
#endif
|
|
print_str("USE_REFERENCE = ");
|
|
print_u32_dec(USE_REFERENCE);
|
|
print("\n", 1);
|
|
print_str("TEST_PRINT_INTERVAL_TICKS = ");
|
|
print_u32_dec(TEST_PRINT_INTERVAL_TICKS);
|
|
print("\n", 1);
|
|
|
|
if (TEST_MAX > 512) { /* Arbitrarily chosen limit */
|
|
print_str("Warning: Using long intervals for testing makes the result "
|
|
"more likely to be affected by clock drift between the "
|
|
"reference timer and the timer under test. This can be "
|
|
"detected as a skewness in the mean values between different "
|
|
"intervals in the results table.\n");
|
|
if (LOG2_STATS) {
|
|
print_str("The variance of the larger intervals may also be greater "
|
|
"than expected if there is significant clock drift across "
|
|
"the bucketed time frame\n");
|
|
}
|
|
|
|
}
|
|
int res = timer_init(TIM_REF_DEV, TIM_REF_FREQ, cb_timer_periph, NULL);
|
|
if (res < 0) {
|
|
print_str("Error ");
|
|
print_s32_dec(res);
|
|
print_str(" intializing reference timer\n");
|
|
return res;
|
|
}
|
|
random_init(seed);
|
|
|
|
#if !(TEST_XTIMER)
|
|
res = timer_init(TIM_TEST_DEV, TIM_TEST_FREQ, cb_timer_periph, &test_context);
|
|
if (res < 0) {
|
|
print_str("Error ");
|
|
print_s32_dec(res);
|
|
print_str(" intializing timer under test\n");
|
|
return res;
|
|
}
|
|
#endif
|
|
|
|
set_limits();
|
|
|
|
print_str("Calibrating spin delay...\n");
|
|
uint32_t spin_max = spin_random_calibrate(TIM_TEST_DEV, SPIN_MAX_TARGET);
|
|
print_str("spin_max = ");
|
|
print_u32_dec(spin_max);
|
|
print("\n", 1);
|
|
estimate_cpu_overhead();
|
|
#ifdef MODULE_PERIPH_RTT
|
|
rtt_begin = rtt_get_counter();
|
|
#endif
|
|
ref_begin = timer_read(TIM_REF_DEV);
|
|
tut_begin = READ_TUT();
|
|
while(1) {
|
|
test_timer();
|
|
}
|
|
|
|
return 0;
|
|
}
|