/* Correction du TP 7 (partie 2, FreeRTOS) -- execution en flash externe.
 *
 * Configuration CubeMX : FREERTOS CMSIS_V2 ; SYS Timebase Source = TIM6 ;
 * UART5 et printf comme au TP 5 de la partie 1 ; LED_1 en sortie ; tache
 * benchTask (StartBench), osPriorityNormal, pile 512 ; CORTEX_M7 : MPU NOT
 * USED, ICache desactive (etapes 1-2) puis active (etape 3), DCache
 * desactive ; QUADSPI NON active.
 * Etape 2 : STM32H750VBTX_FLASH.ld, FLASH ORIGIN = 0x90000000, LENGTH = 8M.
 * Ajouter #include <stdio.h> (USER CODE Includes).
 *
 * Ordres de grandeur attendus (a confirmer sur carte, non mesures ici) :
 * flash interne et flash externe avec I-cache tres proches pour crc32 seule ;
 * flash externe sans I-cache, un a deux ordres de grandeur plus lent.
 */

/* ===== USER CODE BEGIN PD =================================================== */
#define BENCH_LEN   4096u
#define BIG_CODE    1        /* etape 3 : 20 copies de crc32 */
/* USER CODE END PD */

/* ===== USER CODE BEGIN PV =================================================== */
static uint8_t g_data[BENCH_LEN];
/* USER CODE END PV */

/* ===== USER CODE BEGIN 0 ==================================================== */
int __io_putchar(int ch)
{
    uint8_t c = (uint8_t)ch;

    HAL_UART_Transmit(&huart5, &c, 1u, 10u);
    return ch;
}

/* noinline : garder une fonction distincte, mesurable et visible au .map */
__attribute__((noinline)) static uint32_t crc32(const uint8_t *p, uint32_t n)
{
    uint32_t crc = 0xFFFFFFFFu;

    while (n--)
    {
        crc ^= *p++;
        for (int k = 0; k < 8; k++)
        {
            crc = (crc >> 1) ^ (0xEDB88320u & (0u - (crc & 1u)));
        }
    }
    return ~crc;
}

#if BIG_CODE
/* Etape 3 : du code volumineux. Chaque copie est deroulee (8 tours de la
 * boucle interne ecrits a la main) et differe par sa
 * constante, pour que le compilateur ne les fusionne pas. 16 octets par
 * tour, ecrits a la main : environ 1,5 Kio par copie en -O2 (verifier au
 * .map), soit pres de 30 Kio pour les 20, presque le double du cache. */
#define STEP(c, poly)  c = ((c) >> 1) ^ ((poly) & (0u - ((c) & 1u)))
#define BYTE(c, b, poly) do { c ^= (b); STEP(c, poly); STEP(c, poly); STEP(c, poly); \
    STEP(c, poly); STEP(c, poly); STEP(c, poly); STEP(c, poly); STEP(c, poly); } while (0)
#define CRC_COPY(name, poly)                                                   \
    __attribute__((noinline)) static uint32_t name(const uint8_t *p, uint32_t n) \
    {                                                                           \
        uint32_t c = 0xFFFFFFFFu;                                               \
        for (; n >= 16u; n -= 16u, p += 16)                                     \
        {                                                                       \
            BYTE(c, p[0], poly); BYTE(c, p[1], poly); BYTE(c, p[2], poly); BYTE(c, p[3], poly);\
            BYTE(c, p[4], poly); BYTE(c, p[5], poly); BYTE(c, p[6], poly); BYTE(c, p[7], poly);\
            BYTE(c, p[8], poly); BYTE(c, p[9], poly); BYTE(c, p[10], poly); BYTE(c, p[11], poly);\
            BYTE(c, p[12], poly); BYTE(c, p[13], poly); BYTE(c, p[14], poly); BYTE(c, p[15], poly);\
        }                                                                       \
        return ~c;                                                              \
    }

CRC_COPY(crc_a, 0xEDB88320u) CRC_COPY(crc_b, 0xEDB88321u) CRC_COPY(crc_c, 0xEDB88322u)
CRC_COPY(crc_d, 0xEDB88323u) CRC_COPY(crc_e, 0xEDB88324u) CRC_COPY(crc_f, 0xEDB88325u)
CRC_COPY(crc_g, 0xEDB88326u) CRC_COPY(crc_h, 0xEDB88327u) CRC_COPY(crc_i, 0xEDB88328u)
CRC_COPY(crc_j, 0xEDB88329u) CRC_COPY(crc_k, 0xEDB8832Au) CRC_COPY(crc_l, 0xEDB8832Bu)
CRC_COPY(crc_m, 0xEDB8832Cu) CRC_COPY(crc_n, 0xEDB8832Du) CRC_COPY(crc_o, 0xEDB8832Eu)
CRC_COPY(crc_p, 0xEDB8832Fu) CRC_COPY(crc_q, 0xEDB88330u) CRC_COPY(crc_r, 0xEDB88331u)
CRC_COPY(crc_s, 0xEDB88332u) CRC_COPY(crc_t, 0xEDB88333u)

typedef uint32_t (*crc_fn)(const uint8_t *, uint32_t);
static const crc_fn g_copies[20] = {
    crc_a, crc_b, crc_c, crc_d, crc_e, crc_f, crc_g, crc_h, crc_i, crc_j,
    crc_k, crc_l, crc_m, crc_n, crc_o, crc_p, crc_q, crc_r, crc_s, crc_t,
};
#endif
/* USER CODE END 0 */

/* ===== USER CODE BEGIN 1 (tout debut de main) ===============================
    __enable_irq();          // le bootloader a masque les interruptions
   USER CODE END 1 */

void StartBench(void *argument)
{
    (void)argument;

    CoreDebug->DEMCR |= CoreDebug_DEMCR_TRCENA_Msk;
    DWT->CYCCNT = 0u;
    DWT->CTRL  |= DWT_CTRL_CYCCNTENA_Msk;
    for (uint32_t i = 0u; i < BENCH_LEN; i++) { g_data[i] = (uint8_t)(i * 7u); }

    for (;;)
    {
        uint32_t t0  = DWT->CYCCNT;
        uint32_t crc = crc32(g_data, BENCH_LEN);
        uint32_t cy  = DWT->CYCCNT - t0;

        printf("VTOR %08lx  crc32 %08lx : %lu cycles, %lu cycles/octet\r\n",
               (unsigned long)SCB->VTOR, (unsigned long)crc,
               (unsigned long)cy, (unsigned long)(cy / BENCH_LEN));
#if BIG_CODE
        {
            uint32_t acc = 0u;

            t0 = DWT->CYCCNT;
            for (uint32_t k = 0u; k < 16u; k++)        /* 16 x 20 x 256 = 80 Kio */
            {
                for (uint32_t f = 0u; f < 20u; f++)
                {
                    acc ^= g_copies[f](&g_data[f * 128u], 256u);
                }
            }
            cy = DWT->CYCCNT - t0;
            printf("20 copies (%08lx) : %lu cycles/octet\r\n",
                   (unsigned long)acc, (unsigned long)(cy / (16u * 20u * 256u)));
        }
#endif
        HAL_GPIO_TogglePin(LED_1_GPIO_Port, LED_1_Pin);
        osDelay(1000);
    }
}

/* Reponses attendues aux questions :
 *  1. De l'ordre d'une dizaine de cycles par octet en -O2 (8 tours de 3 ou
 *     4 instructions), plusieurs fois plus en -O0. Les comparaisons n'ont de
 *     sens qu'a reglage egal.
 *  2. VTOR = 0x90000000 ; crc32 a une adresse en 0x9000xxxx (impaire dans un
 *     pointeur de fonction : bit Thumb).
 *  3. Beaucoup plus lent sans cache : chaque groupe d'instructions est lu
 *     sur un fil a 16 MHz, avec la commande et l'adresse a chaque lecture.
 *  4. Avec FreeRTOS, le demarrage de l'ordonnanceur demasque les
 *     interruptions : les taches tournent. Mais avant osKernelStart, le tic
 *     de la HAL (TIM6) ne compte pas : un HAL_Delay ne se termine jamais, et
 *     les delais d'attente de la HAL (HAL_GetTick) ne s'ecoulent pas. Sans
 *     FreeRTOS, le programme se bloquerait au premier HAL_Delay.
 *  5. Presque plus : la boucle de crc32 fait quelques dizaines d'octets, lue
 *     une fois en flash externe puis servie par le cache a chaque tour.
 *  6. Les copies se chassent mutuellement du cache : chaque appel recharge
 *     son code depuis la QSPI, et le cout par octet remonte nettement.
 *  7. Oui, plus lente : le cache est vide au premier passage (defauts dits
 *     obligatoires), puis le code y reste.
 */
