====== Matematické funkce s jádrem Cortex-M4 ====== ===== Zadání projektu ===== Proveďte srovnání časové a paměťové náročnosti matematických výpočtů goniometrických funkcí, vektorového násobení a konvoluce na jádru Cortex-M4. Využijte funkce dostupné v [[https://www.keil.com/pack/doc/CMSIS/DSP/html/index.html|]], použijte fixed-point a float aritmetiky, vyzkoušejte soft a hard FPU koprocesor. ===== Použitý hardware ===== V tomto projektu byla použita vývojová deska [[http://www.st.com/web/catalog/tools/FM116/SC959/SS1532/PF252419?sc=internet/evalboard/product/252419.jsp|STM32F4 DISCOVERY]], která je osazena mikrokontrolérem STM32F407VGT6. Zmíněný mikrokontrolér obsahuje jádro ARM Cortex-M4 včetně FPU koprocesoru. Kromě vývojové desky není pro tento projekt potřeba žádný další hardware. {{ 2015:cm4-math:stm32f4_discovery_board.jpg }} ===== Popis programu ===== Vytvořená funkce pro testování doby výpočtu se nachází v knihovně ''''. Čas je měřen pomocí 32-bitového časovače TIM 2. Přetečení časovače není ošetřeno, protože všechny výpočty proběhnou o dost dříve, než přeteče samotný časovač. Časovač přeteče přibližně každých 50 sekund. Výsledky program zobrazí na standartní výstup. V tomto projektu bylo použito přesměrování standartního výstupu do debuggeru pomocí semihostingu. Pro funkci knihovny '''' je potřeba přidat do projektu soubor knihovny pro příslušný procesor. V našem případě je to soubor **libarm_cortexM4lf_math.a** pro výpočty s pomocí FPU koprocesoru. Soubor **libarm_cortexM4l_math.a** slouží k výpočtům bez využití FPU koprocesoru. Program se však pro výpočty bez využití FPU koprocesoru nepodařilo zprovoznit. ==== Zdrojový kód ==== Níže je zobrazen soubor **benchmark.c** vytvořené knihovny. /* Knihovna slouzi k otestovani doby vypoctu nekterych funkci z knihovny . * * Funkce PrintTime zobrazi na standartnim vystupu cas v us vypocitany z rozdilu t1 a t2. * TrigonometricFunctionsTest provede mereni casu vypoctu goniometrickych funkci. * VectorMultiplicationTest provede mereni casu vypoctu funkci na vektorove nasobeni. * ConvolutionTest provede mereni casu vypoctu funkci konvoluce. */ #include "stm32f4xx_hal.h" #include #include #include #include "arm_math.h" #define MEAS_ERR 0.096 //chyba vznikla pri mereni casu casovacem v us TIM_HandleTypeDef htim2; void PrintTime(uint32_t t1, uint32_t t2) { float32_t time_us = (t2 - t1) * 1000000.0 / (SystemCoreClock / 2) - MEAS_ERR; printf("doba = %.2f us\n", time_us); printf(" \n"); } void TrigonometricFunctionsTest(void) { //promenne k mereni casu uint32_t t1; uint32_t t2; //promenne k vypoctum float32_t fx = 0.222; float32_t fy; q31_t qqx = 536870911; q31_t qqy; q15_t qx = 8191; q15_t qy; //cos float t1 = __HAL_TIM_GetCounter(&htim2); fy = arm_cos_f32(fx); t2 = __HAL_TIM_GetCounter(&htim2); printf("test: arm_cos_f32\n"); PrintTime(t1, t2); //sin float t1 = __HAL_TIM_GetCounter(&htim2); fy = arm_sin_f32(fx); t2 = __HAL_TIM_GetCounter(&htim2); printf("test: arm_sin_f32\n"); PrintTime(t1, t2); //cos q31 t1 = __HAL_TIM_GetCounter(&htim2); qqy = arm_cos_q31(qqx); t2 = __HAL_TIM_GetCounter(&htim2); printf("test: arm_cos_q31\n"); PrintTime(t1, t2); //sin q31 t1 = __HAL_TIM_GetCounter(&htim2); qqy = arm_sin_q31(qqx); t2 = __HAL_TIM_GetCounter(&htim2); printf("test: arm_sin_q31\n"); PrintTime(t1, t2); //cos q15 t1 = __HAL_TIM_GetCounter(&htim2); qy = arm_cos_q15(qx); t2 = __HAL_TIM_GetCounter(&htim2); printf("test: arm_cos_q15\n"); PrintTime(t1, t2); //sin q15 t1 = __HAL_TIM_GetCounter(&htim2); qy = arm_sin_q15(qx); t2 = __HAL_TIM_GetCounter(&htim2); printf("test: arm_sin_q15\n"); PrintTime(t1, t2); //cos float math.h t1 = __HAL_TIM_GetCounter(&htim2); fy = cos(fx); t2 = __HAL_TIM_GetCounter(&htim2); printf("test: cos\n"); PrintTime(t1, t2); //sin float math.h t1 = __HAL_TIM_GetCounter(&htim2); fy = sin(fx); t2 = __HAL_TIM_GetCounter(&htim2); printf("test: sin\n"); PrintTime(t1, t2); //pouziti nepouzitych promennych qy++; qqy++; fy++; } void VectorMultiplicationTest(void) { //promenne k mereni casu uint32_t t1; uint32_t t2; //promenne k vypoctum float32_t fvx[1024]; float32_t fvy[1024]; float32_t fvz[1024]; q31_t qqqvx[1024]; q31_t qqqvy[1024]; q31_t qqqvz[1024]; q15_t qqvx[1024]; q15_t qqvy[1024]; q15_t qqvz[1024]; q7_t qvx[1024]; q7_t qvy[1024]; q7_t qvz[1024]; //naplneni nejakymi daty k vypoctu for(uint16_t i = 0; i < 1024; i++) { fvx[i] = i; fvy[i] = PI; qqqvx[i] = i; qqqvy[i] = 123456789; qqvx[i] = i; qqvy[i] = 1234; qvx[i] = i % 50; qvy[i] = 12; } //vektorove nasobeni float 1024, 256, 64, 8 prvku uint32_t blockSize[] = {1024, 256, 64, 8}; for(uint16_t i = 0; i < 4; i++) { t1 = __HAL_TIM_GetCounter(&htim2); arm_mult_f32(fvx, fvy, fvz, blockSize[i]); t2 = __HAL_TIM_GetCounter(&htim2); printf("test: arm_mult_f32, delka vektoru = %lu\n", (unsigned long)blockSize[i]); PrintTime(t1, t2); } //vektorove nasobeni q31 1024, 256, 64, 8 prvku for(uint16_t i = 0; i < 4; i++) { t1 = __HAL_TIM_GetCounter(&htim2); arm_mult_q31(qqqvx, qqqvy, qqqvz, blockSize[i]); t2 = __HAL_TIM_GetCounter(&htim2); printf("test: arm_mult_q31, delka vektoru = %lu\n", (unsigned long)blockSize[i]); PrintTime(t1, t2); } //vektorove nasobeni q15 1024, 256, 64, 8 prvku for(uint16_t i = 0; i < 4; i++) { t1 = __HAL_TIM_GetCounter(&htim2); arm_mult_q15(qqvx, qqvy, qqvz, blockSize[i]); t2 = __HAL_TIM_GetCounter(&htim2); printf("test: arm_mult_q15, delka vektoru = %lu\n", (unsigned long)blockSize[i]); PrintTime(t1, t2); } //vektorove nasobeni q7 1024, 256, 64, 8 prvku for(uint16_t i = 0; i < 4; i++) { t1 = __HAL_TIM_GetCounter(&htim2); arm_mult_q7(qvx, qvy, qvz, blockSize[i]); t2 = __HAL_TIM_GetCounter(&htim2); printf("test: arm_mult_q7, delka vektoru = %lu\n", (unsigned long)blockSize[i]); PrintTime(t1, t2); } } void ConvolutionTest(void) { //promenne k mereni casu uint32_t t1; uint32_t t2; //promenne k vypoctum float32_t *fvx; float32_t *fvy; float32_t *fvz; q31_t *qqqvx; q31_t *qqqvy; q31_t *qqqvz; q15_t *qqvx; q15_t *qqvy; q15_t *qqvz; q7_t *qvx; q7_t *qvy; q7_t *qvz; //konvoluce float 1024, 256, 64, 8 prvku fvx = (float32_t*) malloc(1024 * sizeof(float32_t)); fvy = (float32_t*) malloc(1024 * sizeof(float32_t)); fvz = (float32_t*) malloc(2047 * sizeof(float32_t)); srand(22); //naplneni nahodnymi daty k vypoctu for(uint16_t i = 0; i < 1024; i++) { fvx[i] = rand() - 100.1234; fvy[i] = rand() - 200.5678; } uint32_t blockSize[] = {1024, 256, 64, 8}; for(uint16_t i = 0; i < 4; i++) { t1 = __HAL_TIM_GetCounter(&htim2); arm_conv_f32(fvx, blockSize[i], fvy, blockSize[i], fvz); t2 = __HAL_TIM_GetCounter(&htim2); printf("test: arm_conv_f32, delka vektoru = %lu\n", (unsigned long)blockSize[i]); PrintTime(t1, t2); } free(fvx); free(fvy); free(fvz); //konvoluce q31 1024, 256, 64, 8 prvku qqqvx = (q31_t*) malloc(1024 * sizeof(q31_t)); qqqvy = (q31_t*) malloc(1024 * sizeof(q31_t)); qqqvz = (q31_t*) malloc(2047 * sizeof(q31_t)); //naplneni nahodnymi daty k vypoctu for(uint16_t i = 0; i < 1024; i++) { qqqvx[i] = rand(); qqqvy[i] = rand(); } for(uint16_t i = 0; i < 4; i++) { t1 = __HAL_TIM_GetCounter(&htim2); arm_conv_q31(qqqvx, blockSize[i], qqqvy, blockSize[i], qqqvz); t2 = __HAL_TIM_GetCounter(&htim2); printf("test: arm_conv_q31, delka vektoru = %lu\n", (unsigned long)blockSize[i]); PrintTime(t1, t2); } free(qqqvx); free(qqqvy); free(qqqvz); //konvoluce q15 1024, 256, 64, 8 prvku qqvx = (q15_t*) malloc(1024 * sizeof(q15_t)); qqvy = (q15_t*) malloc(1024 * sizeof(q15_t)); qqvz = (q15_t*) malloc(2047 * sizeof(q15_t)); //naplneni nahodnymi daty k vypoctu for(uint16_t i = 0; i < 1024; i++) { qqvx[i] = rand() % 500; qqvy[i] = rand() % 500; } for(uint16_t i = 0; i < 4; i++) { t1 = __HAL_TIM_GetCounter(&htim2); arm_conv_q15(qqvx, blockSize[i], qqvy, blockSize[i], qqvz); t2 = __HAL_TIM_GetCounter(&htim2); printf("test: arm_conv_q15, delka vektoru = %lu\n", (unsigned long)blockSize[i]); PrintTime(t1, t2); } free(qqvx); free(qqvy); free(qqvz); //konvoluce q7 1024, 256, 64, 8 prvku qvx = (q7_t*) malloc(1024 * sizeof(q7_t)); qvy = (q7_t*) malloc(1024 * sizeof(q7_t)); qvz = (q7_t*) malloc(2047 * sizeof(q7_t)); //naplneni nahodnymi daty k vypoctu for(uint16_t i = 0; i < 1024; i++) { qvx[i] = rand() % 20; qvy[i] = rand() % 20; } for(uint16_t i = 0; i < 4; i++) { t1 = __HAL_TIM_GetCounter(&htim2); arm_conv_q7(qvx, blockSize[i], qvy, blockSize[i], qvz); t2 = __HAL_TIM_GetCounter(&htim2); printf("test: arm_conv_q7, delka vektoru = %lu\n", (unsigned long)blockSize[i]); PrintTime(t1, t2); } free(qvx); free(qvy); free(qvz); } ===== Výsledky ===== V této části se nachází výsledky měření doby výpočtu požadovaných funkcí z knihovny ''''. Měření bylo provedeno pouze s hard FPU koprocesorem. Pro soft FPU koprocesor se nepodařilo program rozběhnout. Pro porovnání byly změřeny i funkce ''cos'' a ''sin'' z knihovny ''''. Frekvence procesoru byla nastavena na 168 MHz. Optimalizace kompilátoru nebyly použité. ==== Goniometrické funkce ==== ^ funkce ^ doba výpočtu ^ | arm_cos_f32 | 0.45 us | | arm_sin_f32 | 0.45 us | | arm_cos_q31 | 0.33 us | | arm_sin_q31 | 0.33 us | | arm_cos_q15 | 0.38 us | | arm_sin_q15 | 0.33 us | | cos | 10.21 us | | sin | 8.59 us | ==== Vektorové násobení ==== ^ počet prvků vstupních vektorů ^ arm_mult_f32 ^ arm_mult_q31 ^ arm_mult_q15 ^ arm_mult_q7 ^ | 1024 | 44.64 us | 85.90 us | 50.90 us | 63.02 us | | 256 | 11.50 us | 21.90 us | 13.19 us | 16.17 us | | 64 | 3.21 us | 5.90 us | 3.76 us | 4.45 us | | 8 | 0.81 us | 1.24 us | 1.00 us | 1.05 us | ==== Konvoluce ==== ^ počet prvků vstupních vektorů ^ arm_conv_f32 ^ arm_conv_q31 ^ arm_conv_q15 ^ arm_conv_q7 ^ | 1024 | 39310.40 us | 33176.83 us | 159415.36 us | 43162.62 us | | 256 | 2510.31 us | 2148.12 us | 9997.67 us | 2742.38 us | | 64 | 170.95 us | 153.62 us | 633.33 us | 183.38 us | | 8 | 5.62 us | 6.19 us | 11.50 us | 5.78 us | ===== Závěr ===== Zadání bylo splněno pouze z části, protože se mi nepodařilo vytvořit program nevyužívající FPU koprocesor a také není zjištěna paměťová náročnost funkcí. Zkoušel jsem přidat do projektu soubor **libarm_cortexM4l_math.a**, vypnout v nastavení kompilátoru FPU a odstranit z #defines __FPU_USED. Program však nešel zkompilovat. Pro zjištění paměťové náročnosti jsem zkoušel alokovat pole o maximální velikosti co šlo (musel jsem postupně v cyklu zkoušet postupně alokovat více a více místa), poté bylo pole naplněno stejnými konstantami, následně byla paměť uvolněna. Po provedení výpočtů bylo pole opět alokováno a zjištěno, kolik se v poli nachází původních konstant. Toto řešení mi ovšem ukazovalo využitou paměť chybně. Zjištěné doby výpočtů u funkce ''arm_conv_q15'' jsou oproti ostatním časům podezřele velké. Výsledky podobného měření doby výpočtu funkcí z knihovny '''' na jiném procesoru lze vidět například zde: [[https://community.freescale.com/thread/327833|K70 DSP benchmark test]].