AI嵌入式K210项目(17)-快速傅里叶变换加速器 (FFT)

文章目录

  • 前言
  • 一、什么是傅里叶变换?
  • 二、K210的快速傅里叶变换加速器
  • 实验过程
  • 总结

前言

K210内置了丰富的加速器,包括神经网络处理器 (KPU),AES(高级加密加速器),APU 麦克风阵列语音数据加速计算处理器,现场可编程 IO 阵列 (FPIOA),数字摄像头接口 (DVP),相对于软件可以极大的提高 AES 运算速度,快速傅里叶变换加速器 (FFT),安全散列算法加速器 (SHA256)。
本文介绍内置的快速傅里叶变换加速器 (FFT);

一、什么是傅里叶变换?

傅里叶变换(Fourier Transform)可以将一个在时间(或空间)域内的信号转换成频率域内的信号。其物理意义是将一个信号分解为不同频率的正弦波组成的谱,从而揭示了信号的频率特性。它的物理意义在于将信号从时间域转换到频率域,帮助我们理解信号的频率特性。
物理意义
傅里叶变换可以将一个信号分解为不同频率的正弦波组成的谱,从而揭示信号的频率特性。通过傅里叶变换,我们能够更好地理解声音、图像等信号的组成和特性。例如,在声音处理中,傅里叶变换可以将声音信号转换为频域信号,进而分析不同音调所对应的频率成分。这对于音频质量改善、音乐合成和语音识别等方面有着重要作用。
在这里插入图片描述
在物理学、工程和科学领域,许多信号都可以表示为不同频率的正弦波的叠加。傅里叶变换能够帮助我们理解这些信号的频率特性,并提供有效的信号处理和分析方法。例如,在图像处理中,傅里叶变换可用于图像压缩、边缘检测等处理,为数字图像处理提供重要支持。
在这里插入图片描述
傅里叶变换非常重要,在音视频处理,通讯等领域有着重要的应用,大家可以通过其他方式好好学习一下,

快速傅里叶变换(FFT)即利用计算机计算离散傅里叶变换(DFT)的高效、快速计算方法的统称,快速傅里叶变换在运算速度和适用范围方面具有优势,而傅里叶变换在精度和计算方式方面更优。

二、K210的快速傅里叶变换加速器

K210内置快速傅立叶变换加速器FFT Accelerater。该模块可以支持64 点、128 点、256 点以及512 点的FFT 以及IFFT。在FFT 内部有两块大小为512*32bit 的SRAM,在配置完成后FFT 会向DMA 发送TX 请求,将DMA 送来的送据放到其中的一块SRAM 中去,直到满足当前FFT 运算所需要的数据量并开始FFT 运算,蝶形运算单元从包含有有效数据的SRAM 中读出数据,运算结束后将数据写到另外一块SRAM 中去,下次蝶形运算再从刚写入的SRAM 中读出数据,运算结束后并写入另外一块SRAM,如此反复交替进行直到完成整个FFT 运算。

FFT 加速器是用硬件的方式来实现FFT 的基2 时分运算。

? 支持多种运算长度,即支持64 点、128 点、256 点以及512 点运算

? 支持两种运算模式,即FFT 以及IFFT 运算

? 支持可配的输入数据位宽,即支持32 位及64 位输入

? 支持可配的输入数据排列方式,即支持虚部、实部交替,纯实部以及实部、虚部分离三种数据排

列方式

? 支持DMA 传输

对应的头文件 aes.h

为用户提供以下接口:

? fft_complex_uint16_dma:FFT运算。

实验过程

本实验使用K210自带的FFT加速器和开源软FFT做对比,看一下加速效果。使用的fft开源库,大家可以网上下载,或者到我gitee上下载,这里只贴man.c部分内容了;

#include <math.h>
#include <stdlib.h>
#include <stdio.h>
#include "encoding.h"
#include "dmac.h"
#include "fft.h"
#include "encoding.h"
#include "sysctl.h"
#include "fft_soft.h"

#define FFT_N               512U
#define FFT_FORWARD_SHIFT   0x0U
#define FFT_BACKWARD_SHIFT  0x1ffU
#define PI                  3.14159265358979323846

typedef enum _complex_mode
{
    FFT_HARD = 0,
    FFT_SOFT = 1,
    FFT_COMPLEX_MAX,
} complex_mode_t;

int16_t real[FFT_N];
int16_t imag[FFT_N];
float hard_power[FFT_N];
float soft_power[FFT_N];
float hard_angel[FFT_N];
float soft_angel[FFT_N];
uint64_t fft_out_data[FFT_N / 2];
uint64_t buffer_input[FFT_N];
uint64_t buffer_output[FFT_N];
uint64_t cycle[FFT_COMPLEX_MAX][FFT_DIR_MAX];

uint16_t get_bit1_num(uint32_t data)
{
    uint16_t num;
    for (num = 0; data; num++)
        data &= data - 1;
    return num;
}

int main(void)
{
    int32_t i;
    float tempf1[3];
    fft_data_t *output_data;
    fft_data_t *input_data;
    uint16_t bit1_num = get_bit1_num(FFT_FORWARD_SHIFT);
    complex_hard_t data_hard[FFT_N] = {0};
    complex data_soft[FFT_N] = {0};
    /* 取得一组复数 */
    for (i = 0; i < FFT_N; i++)
    {
        tempf1[0] = 0.3 * cosf(2 * PI * i / FFT_N + PI / 3) * 256;
        tempf1[1] = 0.1 * cosf(16 * 2 * PI * i / FFT_N - PI / 9) * 256;
        tempf1[2] = 0.5 * cosf((19 * 2 * PI * i / FFT_N) + PI / 6) * 256;
        data_hard[i].real = (int16_t)(tempf1[0] + tempf1[1] + tempf1[2] + 10);
        data_hard[i].imag = (int16_t)0;
        data_soft[i].real = data_hard[i].real;
        data_soft[i].imag = data_hard[i].imag;
    }

    /* 复数转化成傅里叶数据结构RIRI */
    for (int i = 0; i < FFT_N / 2; ++i)
    {
        input_data = (fft_data_t *)&buffer_input[i];
        input_data->R1 = data_hard[2 * i].real;
        input_data->I1 = data_hard[2 * i].imag;
        input_data->R2 = data_hard[2 * i + 1].real;
        input_data->I2 = data_hard[2 * i + 1].imag;
    }
    
    /* 硬件处理FFT数据,并记录时间 */
    cycle[FFT_HARD][FFT_DIR_FORWARD] = read_cycle();
    fft_complex_uint16_dma(DMAC_CHANNEL0, DMAC_CHANNEL1, FFT_FORWARD_SHIFT, FFT_DIR_FORWARD, buffer_input, FFT_N, buffer_output);
    cycle[FFT_HARD][FFT_DIR_FORWARD] = read_cycle() - cycle[FFT_HARD][FFT_DIR_FORWARD];

    /* 软件处理FFT数据,并记录时间 */
    cycle[FFT_SOFT][FFT_DIR_FORWARD] = read_cycle();
    fft_soft(data_soft, FFT_N);
    cycle[FFT_SOFT][FFT_DIR_FORWARD] = read_cycle() - cycle[FFT_SOFT][FFT_DIR_FORWARD];
    
    /* 解析计算输出的数据 */
    for (i = 0; i < FFT_N / 2; i++)
    {
        output_data = (fft_data_t*)&buffer_output[i];
        data_hard[2 * i].imag = output_data->I1 ;
        data_hard[2 * i].real = output_data->R1 ;
        data_hard[2 * i + 1].imag = output_data->I2 ;
        data_hard[2 * i + 1].real = output_data->R2 ;
    }

    /* 复数取模 */
    for (i = 0; i < FFT_N; i++)
    {
        hard_power[i] = sqrt(data_hard[i].real * data_hard[i].real + data_hard[i].imag * data_hard[i].imag) * 2;
        soft_power[i] = sqrt(data_soft[i].real * data_soft[i].real + data_soft[i].imag * data_soft[i].imag) * 2;
    }

    /* 打印软件和硬件复数的实部和虚部 */
    printf("
[hard fft real][soft fft real][hard fft imag][soft fft imag]
");
    for (i = 0; i < FFT_N / 2; i++)
        printf("%3d:%7d %7d %7d %7d
",
                i, data_hard[i].real, (int32_t)data_soft[i].real, data_hard[i].imag, (int32_t)data_soft[i].imag);

    printf("
hard power  soft power:
");
    printf("%3d : %f  %f
", 0, hard_power[0] / 2 / FFT_N * (1 << bit1_num), soft_power[0] / 2 / FFT_N);
    for (i = 1; i < FFT_N / 2; i++)
        printf("%3d : %f  %f
", i, hard_power[i] / FFT_N * (1 << bit1_num), soft_power[i] / FFT_N);

    /* 打印相位 */
    printf("
hard phase  soft phase:
");
    for (i = 0; i < FFT_N / 2; i++)
    {
        hard_angel[i] = atan2(data_hard[i].imag, data_hard[i].real);
        soft_angel[i] = atan2(data_soft[i].imag, data_soft[i].real);
        printf("%3d : %f  %f
", i, hard_angel[i] * 180 / PI, soft_angel[i] * 180 / PI);
    }

    /* 快速傅里叶变换逆运算 */
    for (int i = 0; i < FFT_N / 2; ++i)
    {
        input_data = (fft_data_t *)&buffer_input[i];
        input_data->R1 = data_hard[2 * i].real;
        input_data->I1 = data_hard[2 * i].imag;
        input_data->R2 = data_hard[2 * i + 1].real;
        input_data->I2 = data_hard[2 * i + 1].imag;
    }

    /* 硬件和软件快速傅里叶变换运算 */
    cycle[FFT_HARD][FFT_DIR_BACKWARD] = read_cycle();
    fft_complex_uint16_dma(DMAC_CHANNEL0, DMAC_CHANNEL1, FFT_BACKWARD_SHIFT, FFT_DIR_BACKWARD, buffer_input, FFT_N, buffer_output);
    cycle[FFT_HARD][FFT_DIR_BACKWARD] = read_cycle() - cycle[FFT_HARD][FFT_DIR_BACKWARD];
    cycle[FFT_SOFT][FFT_DIR_BACKWARD] = read_cycle();
    ifft_soft(data_soft, FFT_N);
    cycle[FFT_SOFT][FFT_DIR_BACKWARD] = read_cycle() - cycle[FFT_SOFT][FFT_DIR_BACKWARD];
    
    for (i = 0; i < FFT_N / 2; i++)
    {
        output_data = (fft_data_t*)&buffer_output[i];
        data_hard[2 * i].imag = output_data->I1 ;
        data_hard[2 * i].real = output_data->R1 ;
        data_hard[2 * i + 1].imag = output_data->I2 ;
        data_hard[2 * i + 1].real = output_data->R2 ;
    }

    printf("
[hard ifft real][soft ifft real][hard ifft imag][soft ifft imag]
");
    for (i = 0; i < FFT_N / 2; i++)
        printf("%3d:%7d  %7d %7d %7d
",
                i, data_hard[i].real, (int32_t)data_soft[i].real, data_hard[i].imag, (int32_t)data_soft[i].imag);

    printf("[hard fft test] [%d bytes] forward time = %ld us, backward time = %ld us
",
            FFT_N,
            cycle[FFT_HARD][FFT_DIR_FORWARD]/(sysctl_clock_get_freq(SYSCTL_CLOCK_CPU)/1000000),
            cycle[FFT_HARD][FFT_DIR_BACKWARD]/(sysctl_clock_get_freq(SYSCTL_CLOCK_CPU)/1000000));

    printf("[soft fft test] [%d bytes] forward time = %ld us, backward time = %ld us
",
            FFT_N,
            cycle[FFT_SOFT][FFT_DIR_FORWARD]/(sysctl_clock_get_freq(SYSCTL_CLOCK_CPU)/1000000),
            cycle[FFT_SOFT][FFT_DIR_BACKWARD]/(sysctl_clock_get_freq(SYSCTL_CLOCK_CPU)/1000000));
    while (1)
        ;
    return 0;
}

代码完成后,进行编译

cd build

cmake .. -DPROJ=fft -G "MinGW Makefiles"

make

编译完成后,在build文件夹下会生成fft.bin文件。

使用type-C数据线连接电脑与K210开发板,打开kflash,选择对应的设备,再将程序固件烧录到K210开发板上。
在这里插入图片描述
烧录后重启开发板,实验结果如下:
该处使用的url网络请求的数据。


总结

从实验结果来看内置的快速傅里叶变换加速器 (FFT)速度是软FFT的300多倍,加速效果非常明显。