1 /*
2 * Copyright (c) 2011-2014 Wind River Systems, Inc.
3 * Copyright (c) 2020 Stephanos Ioannidis <root@stephanos.io>
4 *
5 * SPDX-License-Identifier: Apache-2.0
6 */
7
8 /*
9 * @file
10 * @brief pi computation portion of FPU sharing test
11 *
12 * @ingroup kernel_fpsharing_tests
13 *
14 * This module is used for the FPU sharing test, and supplements the basic
15 * load/store test by incorporating two additional threads that utilize the
16 * floating point unit.
17 *
18 * Testing utilizes a pair of tasks that independently compute pi. The lower
19 * priority task is regularly preempted by the higher priority task, thereby
20 * testing whether floating point context information is properly preserved.
21 *
22 * The following formula is used to compute pi:
23 *
24 * pi = 4 * (1 - 1/3 + 1/5 - 1/7 + 1/9 - ... )
25 *
26 * This series converges to pi very slowly. For example, performing 50,000
27 * iterations results in an accuracy of 3 decimal places.
28 *
29 * A reference value of pi is computed once at the start of the test. All
30 * subsequent computations must produce the same value, otherwise an error
31 * has occurred.
32 */
33
34 #include <zephyr/ztest.h>
35
36 #include "float_context.h"
37 #include "test_common.h"
38
39 #ifdef CONFIG_CPU_HAS_FPU_DOUBLE_PRECISION
40 #define FP_TYPE double
41 #define FP_CONSTANT(x) x
42 #else
43 #define FP_TYPE float
44 #define FP_CONSTANT(x) x##f
45 #endif
46
47 /*
48 * PI_NUM_ITERATIONS: This macro is defined in the project's Makefile and
49 * is configurable from the command line.
50 */
51 static FP_TYPE reference_pi = FP_CONSTANT(0.0);
52
53 /*
54 * Test counters are "volatile" because GCC wasn't properly updating
55 * calc_pi_low_count properly when calculate_pi_low() contained a "return"
56 * in its error handling logic -- the value was incremented in a register,
57 * but never written back to memory. (Seems to be a compiler bug!)
58 */
59 static volatile unsigned int calc_pi_low_count;
60 static volatile unsigned int calc_pi_high_count;
61
62 /* Indicates that the load/store test exited */
63 static volatile bool test_exited;
64
65 /* Semaphore for signaling end of test */
66 static K_SEM_DEFINE(test_exit_sem, 0, 1);
67
68 /**
69 * @brief Entry point for the low priority pi compute task
70 *
71 * @ingroup kernel_fpsharing_tests
72 */
calculate_pi_low(void)73 static void calculate_pi_low(void)
74 {
75 volatile FP_TYPE pi; /* volatile to avoid optimizing out of loop */
76 FP_TYPE divisor = FP_CONSTANT(3.0);
77 FP_TYPE sign = FP_CONSTANT(-1.0);
78 unsigned int ix;
79
80 /* Loop until the test finishes, or an error is detected. */
81 for (calc_pi_low_count = 0; !test_exited; calc_pi_low_count++) {
82
83 sign = FP_CONSTANT(-1.0);
84 pi = FP_CONSTANT(1.0);
85 divisor = FP_CONSTANT(3.0);
86
87 for (ix = 0; ix < PI_NUM_ITERATIONS; ix++) {
88 pi += sign / divisor;
89 divisor += FP_CONSTANT(2.0);
90 sign *= FP_CONSTANT(-1.0);
91 }
92
93 pi *= FP_CONSTANT(4.0);
94
95 if (reference_pi == FP_CONSTANT(0.0)) {
96 reference_pi = pi;
97 } else if (reference_pi != pi) {
98 printf("Computed pi %1.6f, reference pi %1.6f\n",
99 (double)pi, (double)reference_pi);
100 }
101
102 zassert_equal(reference_pi, pi,
103 "pi computation error");
104 }
105 }
106
107 /**
108 * @brief Entry point for the high priority pi compute task
109 *
110 * @ingroup kernel_fpsharing_tests
111 */
calculate_pi_high(void)112 static void calculate_pi_high(void)
113 {
114 volatile FP_TYPE pi; /* volatile to avoid optimizing out of loop */
115 FP_TYPE divisor = FP_CONSTANT(3.0);
116 FP_TYPE sign = FP_CONSTANT(-1.0);
117 unsigned int ix;
118
119 /* Run the test until the specified maximum test count is reached */
120 for (calc_pi_high_count = 0;
121 calc_pi_high_count <= MAX_TESTS;
122 calc_pi_high_count++) {
123
124 sign = FP_CONSTANT(-1.0);
125 pi = FP_CONSTANT(1.0);
126 divisor = FP_CONSTANT(3.0);
127
128 for (ix = 0; ix < PI_NUM_ITERATIONS; ix++) {
129 pi += sign / divisor;
130 divisor += FP_CONSTANT(2.0);
131 sign *= FP_CONSTANT(-1.0);
132 }
133
134 /*
135 * Relinquish the processor for the remainder of the current
136 * system clock tick, so that lower priority threads get a
137 * chance to run.
138 *
139 * This exercises the ability of the kernel to restore the
140 * FPU state of a low priority thread _and_ the ability of the
141 * kernel to provide a "clean" FPU state to this thread
142 * once the sleep ends.
143 */
144 k_sleep(K_MSEC(10));
145
146 pi *= FP_CONSTANT(4.0);
147
148 if (reference_pi == FP_CONSTANT(0.0)) {
149 reference_pi = pi;
150 } else if (reference_pi != pi) {
151 printf("Computed pi %1.6f, reference pi %1.6f\n",
152 (double)pi, (double)reference_pi);
153 }
154
155 zassert_equal(reference_pi, pi,
156 "pi computation error");
157
158 /* Periodically issue progress report */
159 if ((calc_pi_high_count % 100) == 50) {
160 printf("Pi calculation OK after %u (high) +"
161 " %u (low) tests (computed %1.6lf)\n",
162 calc_pi_high_count, calc_pi_low_count, (double)pi);
163 }
164 }
165
166 /* Signal end of test */
167 test_exited = true;
168 k_sem_give(&test_exit_sem);
169 }
170
171 K_THREAD_DEFINE(pi_low, THREAD_STACK_SIZE, calculate_pi_low, NULL, NULL, NULL,
172 THREAD_LOW_PRIORITY, THREAD_FP_FLAGS, K_TICKS_FOREVER);
173
174 K_THREAD_DEFINE(pi_high, THREAD_STACK_SIZE, calculate_pi_high, NULL, NULL, NULL,
175 THREAD_HIGH_PRIORITY, THREAD_FP_FLAGS, K_TICKS_FOREVER);
176
ZTEST(fpu_sharing_generic,test_pi)177 ZTEST(fpu_sharing_generic, test_pi)
178 {
179 /* Initialise test states */
180 test_exited = false;
181 k_sem_reset(&test_exit_sem);
182
183 /* Start test threads */
184 k_thread_start(pi_low);
185 k_thread_start(pi_high);
186
187 /* Wait for test threads to exit */
188 k_sem_take(&test_exit_sem, K_FOREVER);
189 }
190