Individální projekty MPOA

Mikroprocesory s architekturou ARM

Uživatelské nástroje

Nástroje pro tento web


2015:cm4-math

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 <arm_math.h>, 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 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.

stm32f4_discovery_board.jpg

Popis programu

Vytvořená funkce pro testování doby výpočtu se nachází v knihovně <benchmark.h>. Č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 <arm_math.h> 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 <benchmark.h> slouzi k otestovani doby vypoctu nekterych funkci z knihovny <arm_math.h>.
*
* 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 <math.h>
#include <stdio.h>
#include <stdlib.h>
#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 <arm_math.h>. 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 <math.h>. 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 <arm_math.h> na jiném procesoru lze vidět například zde: K70 DSP benchmark test.

2015/cm4-math.txt · Poslední úprava: 2016/01/16 15:31 autor: Roman Fiala