基于Xilinx vivado FFT ip进行信号频谱测量

本文章使用Xilinx的fft ip完成了经过参数化的任意个信号的基频测量,完整代码以及代码解释在文章中给出。如有错误,希望指出。

	SIGNAL_NUM = 2,   //*需要检测的信号个数
    FFT_LEN    = 8192,//*fft运算采样长度
    FFT_WIDTH = 32,   //*fft ip输出数据宽度,实部和虚部位宽为FFT_WIDTH/2
    SAMPLE_RATE = 50, //*ADC采样率,单位Mhz,比如此时为50Mhz
    ADC_WIDTH = 16,   //*ADC数据位宽
    FFT_CONFIG_WIDTH = 8 //*FFT ip的配置信号位宽(未使用)

ps:只使用了fft ip进行频率测量未测量幅度,为防止错误不对幅度测量进行讲解,且幅度测量因为未使用其中获得的幅度是未经处理的原始数据,直接使用应该会出现问题。该代码无法满足所有场景需求,仅起到抛砖引玉的作用。

1.FFT ip的配置

对于FFT ip核的各个配置的介绍在网上已经有很多,故不再详细解释各个配置定义,直接附上我对这个ip的配置。


因为使用ADC为20M采样率,需要测到kHz级别的精度,所以这里目标时钟速率为20Mhz,采样点数为8192。这里根据自己需求设定即可。

2.FFT ip的使用介绍

FFT ip输入为时域信息,而输出为信号的频域信号,所以需要自己针对频域信号来分析出傅里叶变换后的结果,在FFT ip配置中勾选了XK_INDEX这里后面将配合测试出信号的频率。
对于fft ip直接输出的实部数据和虚部数据是不可以直接进行频率的获取,需要经过一部分处理才可以得到信号的频谱,傅里叶变换的结果是一个复数,包括实部和虚部,信号的频谱是通过计算复数的模得到的。对于信号频谱的计算,按以下公式: abs(fft_P)= sqrt(实部的平方+虚部的平方)
然后根据这个得到的频谱,得到最大值的点,这个点转换后对应的频率,就是信号的频率,比如对于20k的信号,会在x_index指代20k的地方幅度达到最大值,而对于多个信号,比如一个混合信号(20k的正弦波+50k的正弦波),这样就检查这个频谱的两个最大值的位置,就可以得到这两个信号的频率。
得到实部虚部的平方和并使用vivado的cordic ip进行求根获得绝对值:

always @(posedge clk) begin : re2_im2_end
   if(rst) begin
       fft_im_end <= 'd0;
       fft_re_end <= 'd0;
       fft_end    <= 'd0;
   end
   else if(m_axis_data_tvalid) begin
       fft_im_end <= $signed(fft_im[ADC_WIDTH-1:0])*$signed(fft_im[ADC_WIDTH-1:0]);  //*one
       fft_re_end <= $signed(fft_re[ADC_WIDTH-1:0])*$signed(fft_re[ADC_WIDTH-1:0]);
       fft_end    <= fft_im_end + fft_re_end;          //*two
   end
end

cordic_0 u_cordic (
   .aclk                              (clk                       ),// input wire aclk
   .aresetn                           (~rst                      ),// input wire aresetn
   .s_axis_cartesian_tvalid           (cordic_valid_reg[1]       ),// input wire s_axis_cartesian_tvalid
   .s_axis_cartesian_tdata            (fft_end                   ),// input wire [39 : 0] s_axis_cartesian_tdata

   .m_axis_dout_tvalid                (cordic_valid              ),// output wire m_axis_dout_tvalid
   .m_axis_dout_tdata                 (cordic_data               ) // output wire [23 : 0] m_axis_dout_tdata
);

获得检测频率使用的频谱后,传到下面一个自定义的信号处理部分,这部分主要是根据信号的频谱,获取频谱的最大值位置(如果有多个信号就获取多个极值点),之后根据这个位置对应的x_index来计算出信号的频率。

比如对于50Mhz的采样率,8192点的采样深度,这样的话每一个x_index值对应的频率便是50M/8192≈6.1kHz,这样的话FFT运算精度大概就在6kHz
,比如对于X_index值未2的点,该信号的频率就是12kHz。

下面为得到x_index后,根据采样率的参数定义来计算出具体的信号频率部分代码。

always @(posedge clk) begin : result
    if (rst) begin
        for(integer i = 0;i < FFT_SIGNAL_NUM;i++) begin
            post_fft_freq[i] <= 0;
            post_fft_mag[i] <= 0;
            post_fft_index[i] <= 0;
        end
    end
    else if(max_valid) begin
        for(integer i = 0;i < FFT_SIGNAL_NUM;i++) begin
            post_fft_freq[i]  <= SAMPLE_RATE*detect_index[i]*(1000>>($clog2(FFT_SIZE)-10)); 
            //乘1000为M转K单位,因为MHz相比于KHz后面多3个0,所以乘1000转换单位,其中FFT_SIZE为采样深度
            //右移的时候-10是为了左移10bit,左移十位为了表示为低10位代表小数,高位代表整数
            post_fft_mag[i]   <= detect_data[i];
            post_fft_index[i] <= detect_index[i];
        end
    end
end
always @(posedge clk) begin
    if(rst) begin
        for(integer i = 0;i < SIGNAL_NUM;i++) begin
            {fft_int[i],fft_real[i]} <= 0;
            fft_mag[i] <= 0;
        end
    end
    else if(post_fft_valid) begin
        for(integer i = 0;i < SIGNAL_NUM;i++) begin
            {fft_int[i],fft_real[i]} <= post_fft_freq[i];//*计算出信号频率后,高六位为整数部分,后面为小数部分
            fft_mag[i] <= post_fft_mag[i];
        end
    end
end

3.代码仿真

为了便于观察出仿真结果,所以使用了两个Xilinx的dds ip分别输出了20kHz的信号和50kHz的信号,然后将这两个信号的输出混合后作为输入,一次性测量两个信号的频率。参数配置如文章顶部的参数,未作更改。
下图为输入的20kHz和50kHz信号,混合信号为这两个信号之和:
在这里插入图片描述
下图为获得的频谱实部和虚部以及将他们求绝对值后的结果,可以看出求出实部虚部的模之后即为两个尖峰,分别对应两个不同的频率:
在这里插入图片描述
下图为经过处理后获得的信号频率,第一个为48.8kHz,第二个为18.3kHz,由于上面说明过在50M的采样率下,只有6.1kHz的精度,所以只能不会完全准确在这里,但是在当前精度下,精确测量出了50kHz和20kHz的信号。(要想提高精度需要提高采样深度或者降低采样率)
在这里插入图片描述

4.完整代码

对于我认为可能比较难理解的地方在上面已做出解释。

module fft_trans 
#(
    SIGNAL_NUM = 2,
    FFT_LEN    = 8192,
    FFT_WIDTH = 32,
    SAMPLE_RATE = 50,
    ADC_WIDTH = 16,
    FFT_CONFIG_WIDTH = 8
)
(
    input  wire clk,//65m clk
    input  wire rst,

    input  wire                       [ADC_WIDTH-1:0]    adc_data ,
    input  wire                                          adc_valid,
    input  wire                                          adc_last ,

    output reg                  [$clog2(FFT_LEN)-1:0]   fft_index[SIGNAL_NUM-1:0],
    output reg                  [ 6:0]  fft_int  [SIGNAL_NUM - 1:0] ,
    output reg                  [ 9:0]  fft_real [SIGNAL_NUM - 1:0] ,
    output reg                  [16:0]  fft_mag  [SIGNAL_NUM - 1:0] ,//!需要根据cordic ip的变化而配置
    output reg                          fft_valid                  ,
    output wire                         fft_slave_ready            ,
    output wire                             freq_valid             ,
    output wire        [$clog2(FFT_LEN)-1:0]freq_index             ,
    output wire        [  16:0]             freq_data                  
);
// region:************parameter************
localparam FFT_SECTION = FFT_WIDTH/2;
// endregion:parameter

// region:************logic define************
//*fft signal
wire                   [$clog2(FFT_LEN)-1:0]x_index                ;
wire              [FFT_CONFIG_WIDTH-1:0]s_axis_config_tdata  = 'd1 ;
wire                                   s_axis_config_tvalid  = 1'b1;
wire                                    s_axis_config_tready       ;
wire                   [FFT_SECTION-1:0]fft_im,fft_re              ;
wire                                    m_axis_data_tvalid         ;
wire                                    m_axis_data_tlast          ;
//*fft end
reg                    [ADC_WIDTH*2-1:0]fft_im_end,fft_re_end      ;
reg                    [ADC_WIDTH*2:0]  fft_end                    ;
reg                                     cordic_valid_reg [1:0]     ;

reg                                     sync_index_en [17:0]       ;
//*detect signal
wire                                    post_fft_valid             ;
wire                   [16:0]           post_fft_freq  [SIGNAL_NUM - 1:0];
wire                   [16:0]           post_fft_mag   [SIGNAL_NUM - 1:0];
wire            [$clog2(FFT_LEN)-1:0] post_fft_index [SIGNAL_NUM - 1:0]  ;

wire                                    cordic_valid               ;
wire                   [  16:0]         cordic_data                ;
wire                   [$clog2(FFT_LEN)-1:0]x_index_sync               ;
// endregion:logic define
// region:************assign************
assign freq_data = cordic_data;   //*freq_data
assign freq_index = x_index_sync;
assign freq_valid = cordic_valid;
assign fft_index[0] =( post_fft_index[0]>post_fft_index[1]) ? post_fft_index[0] : post_fft_index[1];
assign fft_index[1] = (post_fft_index[1]<post_fft_index[0]) ? post_fft_index[1] : post_fft_index[0];
// endregion:assign
always @(posedge clk) begin : re2_im2_end
    if(rst) begin
        fft_im_end <= 'd0;
        fft_re_end <= 'd0;
        fft_end    <= 'd0;
    end
    else if(m_axis_data_tvalid) begin
        fft_im_end <= $signed(fft_im[ADC_WIDTH-1:0])*$signed(fft_im[ADC_WIDTH-1:0]);  //*one
        fft_re_end <= $signed(fft_re[ADC_WIDTH-1:0])*$signed(fft_re[ADC_WIDTH-1:0]);
        fft_end    <= fft_im_end + fft_re_end;          //*two
    end
end
always @(posedge clk) begin:cordic_valid_generate
    if(rst) begin
        for(integer i = 0;i < 2;i++) begin
            cordic_valid_reg[i] <= 'd0;
        end
    end
    else begin
        cordic_valid_reg[1] <= cordic_valid_reg[0];
        cordic_valid_reg[0] <= m_axis_data_tvalid;
    end
end
always @(posedge clk) begin:sync_index
    if(rst) begin
        for(integer i = 0;i < 18;i++) begin
            sync_index_en[i] <= 'd0;
        end
    end
    else begin
        for(integer i = 0;i < 18;i++) begin
            if (i== 0) begin
                sync_index_en[i] <= m_axis_data_tvalid;
            end
            else begin
                sync_index_en[i] <= sync_index_en[i-1];
            end
        end
    end
end
//*out signals
always @(posedge clk) begin
    if(rst) begin
        for(integer i = 0;i < SIGNAL_NUM;i++) begin
            {fft_int[i],fft_real[i]} <= 0;
            fft_mag[i] <= 0;
        end
    end
    else if(post_fft_valid) begin
        for(integer i = 0;i < SIGNAL_NUM;i++) begin
            {fft_int[i],fft_real[i]} <= post_fft_freq[i];
            fft_mag[i] <= post_fft_mag[i];
        end
    end
end
always @(posedge clk) begin
    if(rst) begin
        fft_valid <= 1'b0;
    end
    else begin
        fft_valid <= post_fft_valid;
    end
end


xfft_0 u_xfft_0(
    .aclk                              (clk                       ),
    .aresetn                           (~rst                      ),
    //*fft配置信号
    .s_axis_config_tdata               (s_axis_config_tdata       ),
    .s_axis_config_tvalid              (s_axis_config_tvalid      ),
    .s_axis_config_tready              (s_axis_config_tready      ),
    //*前级adc传入数据
    .s_axis_data_tdata                 ({{{FFT_WIDTH-ADC_WIDTH}{1'b0}},adc_data}),
    .s_axis_data_tvalid                (adc_valid                 ),
    .s_axis_data_tready                (fft_slave_ready           ),
    .s_axis_data_tlast                 (                  ),
    //*out channel
    .m_axis_data_tdata                 ({fft_im,fft_re}           ),
    .m_axis_data_tuser                 (x_index                   ),
    .m_axis_data_tvalid                (m_axis_data_tvalid        ),
    .m_axis_data_tlast                 (m_axis_data_tlast         ),
    //*status channel
    .m_axis_status_tdata               (                          ),
    .m_axis_status_tvalid              (                          ),
    //*event channel
    .event_frame_started               (                          ),
    .event_tlast_unexpected            (                          ),
    .event_tlast_missing               (                          ),
    .event_data_in_channel_halt        (                          ) 
);
//*17 latency
cordic_0 u_cordic (
    .aclk                              (clk                       ),// input wire aclk
    .aresetn                           (~rst                      ),// input wire aresetn
    .s_axis_cartesian_tvalid           (cordic_valid_reg[1]       ),// input wire s_axis_cartesian_tvalid
    .s_axis_cartesian_tdata            (fft_end                   ),// input wire [39 : 0] s_axis_cartesian_tdata

    .m_axis_dout_tvalid                (cordic_valid              ),// output wire m_axis_dout_tvalid
    .m_axis_dout_tdata                 (cordic_data               ) // output wire [23 : 0] m_axis_dout_tdata
);
fifo_generator_0 u_fifo_generator_0(
    .clk                               (clk                       ),
    .srst                              (rst                       ),
    .din                               (x_index                   ),
    .wr_en                             (m_axis_data_tvalid        ),
    .rd_en                             (sync_index_en[17]         ),//!根据cordic ip的变化而配置
    .dout                              (x_index_sync              ),
    .full                              (                          ),
    .empty                             (                          ) 
);

signal_detect
#(
    .FFT_SIZE                          (FFT_LEN                   ),
    .FFT_SIGNAL_NUM                    (SIGNAL_NUM                ),
    .SAMPLE_RATE                       (SAMPLE_RATE               ) 
)
u_signal_detect(
    .clk                               (clk                       ),
    .rst                               (rst                       ),
    .fft_data                          (cordic_data               ),
    .fft_index                         (x_index_sync              ),
    .fft_valid                         (cordic_valid              ),

    .post_fft_index                    (post_fft_index            ),
    .post_fft_valid                    (post_fft_valid            ),
    .post_fft_freq                     (post_fft_freq             ),
    .post_fft_mag                      (post_fft_mag              ) 
);

endmodule //fft_trans


在例化中除了signal_detect均为Xilinx的 ip,其中cordic_0 实现了求根,xfft_0 为fft ip,fifo_generator_0 为一个异步FIFO用于数据对齐,下面给出signal_detect代码

module signal_detect 
#(
    parameter FFT_SIZE = 4096,
    parameter FFT_SIGNAL_NUM = 1,
    parameter integer  SAMPLE_RATE = 'd100 //100MHz,用来计算频率
)
(
    input  wire  clk,
    input  wire  rst,

    input  wire  [16:0] fft_data,//!输入的cordic数据
    input  wire  [$clog2(FFT_SIZE)-1:0] fft_index,
    input  wire        fft_valid,

    output reg  [$clog2(FFT_SIZE)-1:0] post_fft_index [FFT_SIGNAL_NUM],
    output reg  post_fft_valid,
    output reg  [16:0] post_fft_freq [FFT_SIGNAL_NUM-1:0],     //*高16bit为整数,低10bit为小数
    output reg  [16:0] post_fft_mag  [FFT_SIGNAL_NUM-1:0]      //*低位最低,高位最高
);
localparam DETECT_RANGE = FFT_SIZE/2;

reg [$clog2(FFT_SIZE)-1:0] detect_index [FFT_SIGNAL_NUM-1:0]; 
//wire [$clog2(FFT_SIGNAL_NUM):0] detect_cnt;
reg [16:0] detect_data [FFT_SIGNAL_NUM-1:0];
reg [$clog2(DETECT_RANGE):0] cnt;
reg [4:0] state;
reg max_valid;

//*1 state design
always @(posedge clk) begin
    if (rst) begin
        cnt <= {$clog2(DETECT_RANGE){1'b0}};
        state <= 0;
    end
    else begin
        case (state)
            0:  if (fft_valid && (fft_index == 0)) begin//*one clk delay
                    cnt <= 0;
                    state <= 1;
                end
                else begin
                    state <= 0;
                end
            1:  if (fft_valid && (cnt < DETECT_RANGE)) begin
                    cnt <= cnt + 1;
                    state <= 1;
                end
                else if(cnt == DETECT_RANGE)begin
                    state <= 2;
                    cnt <= 0;
                end
            2:  begin
                state <= 0;
                cnt <= 0;
            end
            default: begin
                state <= 0;
                cnt <= 0;
            end
        endcase
    end
end
//*2 output design
generate if(FFT_SIGNAL_NUM == 1) begin
    always @(posedge clk) begin
    if (rst) begin
            detect_data[0] <= 0;
            detect_index[0] <= 0;
            max_valid <= 0;
    end
    else begin
        case (state)
            0:  begin
                    detect_data[0] <= 0;
                    detect_index[0] <= 0;
                    max_valid <= 0;
                end
            1: begin //*只捕获一个信号时
                    if (cnt == DETECT_RANGE-1) begin
                        max_valid <= 1'b1;
                    end
                    else begin
                        max_valid <= 0;
                    end

                    if (fft_data > detect_data[0]) begin
                        detect_data[0] <= fft_data;
                        detect_index[0] <= cnt+1;
                    end
                    
                end
            2:  begin
                max_valid <= 0;
                detect_data[0] <= 0;
                detect_index[0] <= 0;
            end
            default: begin
                max_valid <= 0;
                detect_data[0] <= 0;
                detect_index[0] <= 0;
            end
        endcase
    end
end
end
else begin //*捕获多个信号时
    always @(posedge clk) begin
    if (rst) begin
        max_valid <= 0;
        for(integer i = 0;i < FFT_SIGNAL_NUM;i++) begin
            detect_data[i] <= 0;
            detect_index[i] <= 0;
        end
    end
    else begin
        case (state)
            0:  begin
                    for(integer i = 0;i < FFT_SIGNAL_NUM;i++) begin
                        detect_data[i] <= 0;
                        detect_index[i] <= 0;
                    end
                    max_valid <= 0;
                end
            1: begin
                    if( fft_data > detect_data[0] && detect_data[0] <= detect_data[1]) begin
                        detect_data[0] <= fft_data;
                        detect_index[0] <= cnt + 'd1;
                    end
                    else if(fft_data > detect_data[1] && detect_data[1] <= detect_data[0]) begin
                        detect_data[1] <= fft_data;
                        detect_index[1] <= cnt + 'd1;
                    end
                    if (cnt == DETECT_RANGE-1) begin
                        max_valid <= 1'b1;
                    end
                    else begin
                        max_valid <= 0;
                    end
                end
            2:  begin
                max_valid <= 0;
                for(integer i = 0;i < FFT_SIGNAL_NUM;i++) begin
                    detect_data[i] <= 0;
                    detect_index[i] <= 0;
                end
            end
            default: begin
                max_valid <= 0;
                for(integer i = 0;i < FFT_SIGNAL_NUM;i++) begin
                    detect_data[i] <= 0;
                    detect_index[i] <= 0;
                end
            end
        endcase
    end
end
end
endgenerate
//*output
always @(posedge clk) begin : result
    if (rst) begin
        for(integer i = 0;i < FFT_SIGNAL_NUM;i++) begin
            post_fft_freq[i] <= 0;
            post_fft_mag[i] <= 0;
            post_fft_index[i] <= 0;
        end
    end
    else if(max_valid) begin
        for(integer i = 0;i < FFT_SIGNAL_NUM;i++) begin
            post_fft_freq[i]  <= SAMPLE_RATE*detect_index[i]*(1000>>($clog2(FFT_SIZE)-10)); //<<10/1024 //*1000为M转K单位
            post_fft_mag[i]   <= detect_data[i];
            post_fft_index[i] <= detect_index[i];
        end
    end
end
always @(posedge clk) begin : valid
    if (rst) begin
        post_fft_valid <= 0;
    end
    else if(max_valid) begin
        post_fft_valid <= 1;
    end
    else begin
        post_fft_valid <= 0;
    end
end

endmodule //signal_detect

如有疑问或者文章中有错误,请在评论区指出💕。

参考链接:利用Vivado的 FFT IP 核估计信号的幅度和频率

  • 22
    点赞
  • 43
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
### 回答1: Vivado FFT IP核是Xilinx公司提供的一种用于高性能快速傅里叶变换(FFT)的可编程逻辑器件,可用于信号处理、通信系统、雷达、音频处理和图像处理等领域。 Vivado FFT IP核的主要功能是实现快速傅里叶变换,这是一种将时域信号转换为频域信号的数学技术。FFT是一种高效的算法,能够在较短的时间内对信号进行频谱分析频率测量Vivado FFT IP核提供了多种FFT算法,包括基于蝶形算法、流水线和并行化等技术,可以根据应用的需求选择适合的算法Vivado FFT IP核提供了多种配置选项,可以实现不同数据宽度、数据精度和时域点数的FFT计算。用户可以通过Vivado设计环境来配置和生成FFT IP核,方便地集成到自己的设计中。IP核的接口支持AXI4-Stream和AXI4-Lite等标准接口,与其他系统组件进行数据交换。 通过使用Vivado FFT IP核,用户可以在FPGA上快速实现高性能的FFT计算,提高系统的性能和效率。IP核的可编程性使得用户可以根据应用需求进行定制,并且可以随着设计的迭代进行功能增强或优化。此外,使用IP核还可以减少设计开发时间和复杂度,提高设计的可重用性。 综上所述,Vivado FFT IP核是一种用于快速傅里叶变换的可编程逻辑器件,具有灵活的配置选项和高性能的计算能力。使用该IP核可以快速实现高性能的FFT计算,提高系统的性能和效率。 ### 回答2: Vivado FFT IP核是Xilinx公司提供的一种用于快速傅里叶变换(FFT)运算的IP核。FFT是一种用于信号频谱分析算法,广泛应用于数字信号处理领域。Vivado FFT IP核通过硬件加速的方式,实现了高效的FFT计算。 Vivado FFT IP核具有以下主要特点: 1. 高性能:Vivado FFT IP核使用专门的FFT硬件来执行计算,速度比软件实现的FFT更快。它能够在很短的时间内完成大规模FFT计算。 2. 可定制性:Vivado FFT IP核提供了许多可定制的选项。用户可以根据具体的需求选择不同的FFT大小、输出数据宽度、输入数据格式等。这样有助于优化设计,提高系统整体性能。 3. 多种接口:Vivado FFT IP核支持多种接口,如AXI4-Stream接口、AXI4-Lite接口等,方便与其他IP核或外部系统进行连接。 4. 低功耗:Vivado FFT IP核经过优化设计,能够在低功耗下运行,节约能源消耗。 Vivado FFT IP核的应用范围广泛。在通信领域中,它可以用于信号解调、频谱分析、通道估计等。在图像处理领域中,它可以用于图像压缩、图像增强等。此外,在雷达、声音处理、金融分析等其他领域也有广泛的应用。 Vivado FFT IP核的使用步骤相对简单。首先,在Vivado设计工具中导入该IP核,在设计中进行配置和参数设置。然后,将IP核与其他系统进行连接,并根据需要编写相应的控制和数据处理逻辑。最后,生成比特流文件(bitstream)并下载到目标设备中进行验证和调试。 总之,Vivado FFT IP核是一种高性能、可定制的FFT计算IP核,广泛应用于信号处理等领域,为设计人员提供了快速可靠的FFT计算解决方案。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Max玮

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值