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