草庐IT

数字信号处理-11-FPGA FFT IP应用实例

Vuko-wxh 2023-07-21 原文

前言

本文根据FFT相关原理进行设计构建工程,仿造前文的工程构建的混频功能的工程,设计工程显示该混频信号的功率谱,然后进行仿真分析。

FFT仿真与分析

本文不再针对FFT的原理进行过多赘述,提供一份简单的matlab仿真代码。根据仿真简述下FFT的相关使用注意事项。

clc;clear all;
fs=50e6;%采样率
N=1024;%采样点数
t=[0:N-1]/fs; %时间序列
f1=3e6;%频点1 3MHZ
f2=4e6;%频点2 4MHZ
s1=sin(2*pi*f1*t);%信号1
s2=sin(2*pi*f2*t);%信号2
mixsign=s1.*s2;%混频
fftsign=fft(mixsign);%求fft
fftabs=abs(fftsign);%取模运算
plot(fftabs);

代码设计,模拟生成了两个不同频率的信号3MHz和4MHz,模拟采样了1024,将两个信号进行混频后则产生了7MHz和1MHz的信号。然后通过FFT函数,取模运算,求得FFT的幅度谱,然后进行显示输出。

频率分辨率

频率分辨率是FFT的一个重要的参数,横坐标每一个单位的频率精度等于 fs/N,N 是 FFT 的点数。即求出该仿真情况下的频率分辨率如下:
50 M H z 1024   = 48828.125 H z \frac {50MHz}{1024}\ = 48828.125Hz 102450MHz =48828.125Hz
将仿真输出的图片放大,并标注坐标,可见,第一个峰值的横坐标为21,第二个峰值的横坐标为144,计算可知,第一个峰值对应的频率为1.0254MHz,第二个峰值对应的频率为1.0254MHz,7.0313MHz。

可见实际FFT出来后的结果,和仿真设置的相差了一点,但是基本上是在设置的附近,这是因为频率分辨率不够,48828.125Hz的分辨率不能恰好对应到设置的1MHz和7MHz。

如果想恰好得到1MHz和7MHz的FFT的处理结果,或者想进一步减小误差,则需要进行相干采样,频率分辨率恰好是所求的频率的倍数。

关于频谱泄露

频率分辨率欠佳后,就会造成频谱泄露。

当信号X(t)的频率f0是fs/N的整数倍时,这说明在处理长度NT内有信号的K个整周期。这时由X(t)构成的以NT为周期的周期性信号是连续的。当信号X(t)的频率f0不是fs/N的整数倍时,则在NT的处理长度内,就不是恰好为信号周期的整数倍,有X(t)以NT为周期进行周期延拓所得到的周期性信号就出现了不连续点,造成了频谱分量从其正常频谱扩展开来,就这样形成了频谱泄露现象。

整周期截断,不会造成频谱泄露;非整周期截断,必然造成频谱泄露。

前面提到的相干采样,正式因为进行了整数周期的截断才使得频谱不进行泄露,并且FFT后的信号尖峰也恰好能对应我们设置的预期的频率。

使用FFT IP进行工程设计

可以将DDS应用实例的工程进行复制备份,然后添加相关 IP,进行工程适配。

实现功率谱逻辑

FFT 的原理是可以通过实部和虚部的数据恢复出周期信号的相位和幅值; 假如 a 是实部数据, b 是虚部数据, a+bj 是复数;对应的模运算是=sqrt(a2+b2),FFT处理后取模运算中的开更号在FPGA中实现比较麻烦,可以利用自带的cordic IP去处理,这里可以简化一下求FFT处理后的功率谱,也即(a2+b2)。

因此在调用FFT函数后,将输出的数据的实部虚部进行平方再相加即可得到FFT处理后的功率谱。

添加FFT IP

在配置界面可配置FFT的通道个数,傅里叶变换的长度,该结构的时钟,以及采用的算法架构。改变通道个数为N后,对应的数据位宽会变成一个通道的N倍。这里设置 IP 核 1024 点 FFT, 采样率 50MHz, 选择基 2 突发结构。

数据格式选择定点数类型,放缩设置为块浮点模式, 输出 FFT 结果选择顺序输出。

在侧边栏可以看到IP的接口状态,以及具体实现架构的相关细节,从实现细节界面可看到,在CONFIG接口处的数据位,有一个FWD_INV的配置参数,该参数是配置正变换还是反变换,因为 FFT 的计算正变换和反变换可以用一套算法实现。 这里 FWD_INV=1 为正变换, 为 0 是反变换。

这里看到 CHAN_0_XN_IM_0(31:16)是复数的虚部数据,并且使用的是 fix16_15 定点数, 意思是最高位为符号位, 小数部分有15位。CHAN_0_XN_RE_0(15:0)是复数的实部数据;这里的FFT混频信号只提供了实部的信号,因此,在信号连接时,只需要把低 16 位赋值为乘法器输出值, 而高 16 位赋值为 0即可。

从侧边栏还可以对FFT进行延时分析,从图中可知,该架构的FFT变化需要146.820us才能完成。

添加DDS IP

添加DDS IP,配置输出两路信号分别为3MHz和4MHz。匹配FFT的IP采样频率的50MHz,修改SFDR为45。

配置完成基本信息配置下一页,基本保持默认即可,这里只想查看波形,所以相位输出就关闭。

DDS的IP核多通道之间是分时复用的,所以在细节实现配置界面最好使能通道ID以供进行区别单个通道的信号波形。其余可以保持默认。

配置输出频率为3MHz和4MHz。其余保持默认,点击OK,完成配置。

修改乘法器IP

将乘法器适配当前的数据位宽,并保存设置。该乘法器用于实现混频乘法。

调用第二个乘法器,配置输入位宽为16位,输出为32位,有符号类型。该乘法器用于实现FFT处理后的功率谱逻辑。

编写.V文件

根据上面的逻辑结构,例化IP、编写代码依次实现DDS的信号产生、混频、FFT处理、以及功率谱运算逻辑。

`timescale 1ns / 1ps
module top(
    input clk
    );

    wire          m_axis_data_tvalid_ch3;
    wire [7 : 0]  m_axis_data_tdata_ch3;
    wire [0 : 0]  m_axis_data_tuser_ch3;

    //多通道测试
    dds_compiler_1 multi_ch_dds(
    .aclk(clk),                                // input wire aclk
    .m_axis_data_tvalid(m_axis_data_tvalid_ch3),    // output wire m_axis_data_tvalid
    .m_axis_data_tdata(m_axis_data_tdata_ch3),      // output wire [7 : 0] m_axis_data_tdata
    .m_axis_data_tuser(m_axis_data_tuser_ch3)
    );

    reg [7 : 0] data3MHz;
    reg [7 : 0] data4MHz;
    always @(posedge clk) begin
        case(m_axis_data_tuser_ch3)
            0:data3MHz<=m_axis_data_tdata_ch3;
            1:data4MHz<=m_axis_data_tdata_ch3;
        endcase
     end

    //混频测试
    wire [15 : 0]  mixer_singal;
    mult_gen_0 mult_mixer (
        .CLK(clk),  // input wire CLK
        .A(data3MHz),      // input wire [7 : 0] A
        .B(data4MHz),      // input wire [7 : 0] B
        .P(mixer_singal)      // output wire [15 : 0] P
    );

    reg div_clk=0;
    always @(posedge clk ) begin
        div_clk<=!div_clk;
    end

    wire mixer_singal_tready;
    wire [31 : 0] after_fft_data;
    wire [7 : 0] m_axis_data_tuser;
    wire m_axis_data_tvalid;

    xfft_0 uut_fft(
    .aclk(div_clk),                                                // input wire aclk
    .s_axis_config_tdata('d1),                  // input wire [7 : 0] s_axis_config_tdata
    .s_axis_config_tvalid(1),                // input wire s_axis_config_tvalid
    .s_axis_config_tready(),                // output wire s_axis_config_tready
    .s_axis_data_tdata({16'd0,mixer_singal}),                      // input wire [31 : 0] s_axis_data_tdata
    .s_axis_data_tvalid(1),                    // input wire s_axis_data_tvalid
    .s_axis_data_tready(mixer_singal_tready),                    // output wire s_axis_data_tready
    .s_axis_data_tlast(0),                      // input wire s_axis_data_tlast
    .m_axis_data_tdata(after_fft_data),                      // output wire [31 : 0] m_axis_data_tdata
    .m_axis_data_tuser(m_axis_data_tuser),                      // output wire [7 : 0] m_axis_data_tuser
    .m_axis_data_tvalid(m_axis_data_tvalid),                    // output wire m_axis_data_tvalid
    .m_axis_data_tready(1),                    // input wire m_axis_data_tready
    .m_axis_data_tlast(),                      // output wire m_axis_data_tlast
    .m_axis_status_tdata(),                  // output wire [7 : 0] m_axis_status_tdata
    .m_axis_status_tvalid(),                // output wire m_axis_status_tvalid
    .m_axis_status_tready(1),                // input wire m_axis_status_tready
    .event_frame_started(),                  // output wire event_frame_started
    .event_tlast_unexpected(),            // output wire event_tlast_unexpected
    .event_tlast_missing(),                  // output wire event_tlast_missing
    .event_status_channel_halt(),      // output wire event_status_channel_halt
    .event_data_in_channel_halt(),    // output wire event_data_in_channel_halt
    .event_data_out_channel_halt()  // output wire event_data_out_channel_halt
    );
    
    wire [31 : 0] fft_re_2;
    mult_gen_1 mult_re (
        .CLK(div_clk),  // input wire CLK
        .A(after_fft_data[15:0]),      // input wire [15 : 0] A
        .B(after_fft_data[15:0]),      // input wire [15 : 0] B
        .P(fft_re_2)      // output wire [31 : 0] P
    );

    wire [31 : 0] fft_im_2;
    mult_gen_1 mult_im (
        .CLK(div_clk),  // input wire CLK
        .A(after_fft_data[31:15]),      // input wire [15 : 0] A
        .B(after_fft_data[31:15]),      // input wire [15 : 0] B
        .P(fft_im_2)      // output wire [31 : 0] P
    );

    wire [32 : 0] sum = fft_re_2 + fft_im_2;

endmodule

运行仿真测试

这里仿真只需要给一个时钟源即可,编写仿真代码实现100MHz的时钟。运行仿真将相关信号添加到波形窗口中,观察信号。这里的sum为做完FFT处理后,实现功率谱逻辑的信号。将波形转换成模拟形式后可看出波形和matlab的代码仿真类似,然后确定下横坐标是否为21和144,即对应信号频率是否为1MHz和7MHz。

从下图可看出,从FFT处理输出的第一个信号到输出第一个峰值花费了420ns,FFT的配置频率和工作频率是50MHz,也就是周期就是20ns,恰好对应了第21个点,和仿真结果一致。

第二个峰值距离FFT输出第一个数据的时间花费了2880ns,换算下来也就是144个时钟周期,和仿真结果一致。

有关数字信号处理-11-FPGA FFT IP应用实例的更多相关文章

  1. ruby - 将差异补丁应用于字符串/文件 - 2

    对于具有离线功能的智能手机应用程序,我正在为Xml文件创建单向文本同步。我希望我的服务器将增量/差异(例如GNU差异补丁)发送到目标设备。这是计划:Time=0Server:hasversion_1ofXmlfile(~800kiB)Client:hasversion_1ofXmlfile(~800kiB)Time=1Server:hasversion_1andversion_2ofXmlfile(each~800kiB)computesdeltaoftheseversions(=patch)(~10kiB)sendspatchtoClient(~10kiBtransferred)Cl

  2. ruby - 如何指定 Rack 处理程序 - 2

    Rackup通过Rack的默认处理程序成功运行任何Rack应用程序。例如:classRackAppdefcall(environment)['200',{'Content-Type'=>'text/html'},["Helloworld"]]endendrunRackApp.new但是当最后一行更改为使用Rack的内置CGI处理程序时,rackup给出“NoMethodErrorat/undefinedmethod`call'fornil:NilClass”:Rack::Handler::CGI.runRackApp.newRack的其他内置处理程序也提出了同样的反对意见。例如Rack

  3. ruby-on-rails - Rails 应用程序之间的通信 - 2

    我构建了两个需要相互通信和发送文件的Rails应用程序。例如,一个Rails应用程序会发送请求以查看其他应用程序数据库中的表。然后另一个应用程序将呈现该表的json并将其发回。我还希望一个应用程序将存储在其公共(public)目录中的文本文件发送到另一个应用程序的公共(public)目录。我从来没有做过这样的事情,所以我什至不知道从哪里开始。任何帮助,将不胜感激。谢谢! 最佳答案 无论Rails是什么,几乎所有Web应用程序都有您的要求,大多数现代Web应用程序都需要相互通信。但是有一个小小的理解需要你坚持下去,网站不应直接访问彼此

  4. ruby - 无法运行 Rails 2.x 应用程序 - 2

    我尝试运行2.x应用程序。我使用rvm并为此应用程序设置其他版本的ruby​​:$rvmuseree-1.8.7-head我尝试运行服务器,然后出现很多错误:$script/serverNOTE:Gem.source_indexisdeprecated,useSpecification.Itwillberemovedonorafter2011-11-01.Gem.source_indexcalledfrom/Users/serg/rails_projects_terminal/work_proj/spohelp/config/../vendor/rails/railties/lib/r

  5. ruby-on-rails - 如何使用 instance_variable_set 正确设置实例变量? - 2

    我正在查看instance_variable_set的文档并看到给出的示例代码是这样做的:obj.instance_variable_set(:@instnc_var,"valuefortheinstancevariable")然后允许您在类的任何实例方法中以@instnc_var的形式访问该变量。我想知道为什么在@instnc_var之前需要一个冒号:。冒号有什么作用? 最佳答案 我的第一直觉是告诉你不要使用instance_variable_set除非你真的知道你用它做什么。它本质上是一种元编程工具或绕过实例变量可见性的黑客攻击

  6. ruby 正则表达式 - 如何替换字符串中匹配项的第 n 个实例 - 2

    在我的应用程序中,我需要能够找到所有数字子字符串,然后扫描每个子字符串,找到第一个匹配范围(例如5到15之间)的子字符串,并将该实例替换为另一个字符串“X”。我的测试字符串s="1foo100bar10gee1"我的初始模式是1个或多个数字的任何字符串,例如,re=Regexp.new(/\d+/)matches=s.scan(re)给出["1","100","10","1"]如果我想用“X”替换第N个匹配项,并且只替换第N个匹配项,我该怎么做?例如,如果我想替换第三个匹配项“10”(匹配项[2]),我不能只说s[matches[2]]="X"因为它做了两次替换“1fooX0barXg

  7. ruby-on-rails - Rails 应用程序中的 Rails : How are you using application_controller. rb 是新手吗? - 2

    刚入门rails,开始慢慢理解。有人可以解释或给我一些关于在application_controller中编码的好处或时间和原因的想法吗?有哪些用例。您如何为Rails应用程序使用应用程序Controller?我不想在那里放太多代码,因为据我了解,每个请求都会调用此Controller。这是真的? 最佳答案 ApplicationController实际上是您应用程序中的每个其他Controller都将从中继承的类(尽管这不是强制性的)。我同意不要用太多代码弄乱它并保持干净整洁的态度,尽管在某些情况下ApplicationContr

  8. ruby-on-rails - 如何在我的 Rails 应用程序 View 中打印 ruby​​ 变量的内容? - 2

    我是一个Rails初学者,但我想从我的RailsView(html.haml文件)中查看Ruby变量的内容。我试图在ruby​​中打印出变量(认为它会在终端中出现),但没有得到任何结果。有什么建议吗?我知道Rails调试器,但更喜欢使用inspect来打印我的变量。 最佳答案 您可以在View中使用puts方法将信息输出到服务器控制台。您应该能够在View中的任何位置使用Haml执行以下操作:-puts@my_variable.inspect 关于ruby-on-rails-如何在我的R

  9. ruby-on-rails - Rails - 从另一个模型中创建一个模型的实例 - 2

    我有一个正在构建的应用程序,我需要一个模型来创建另一个模型的实例。我希望每辆车都有4个轮胎。汽车模型classCar轮胎模型classTire但是,在make_tires内部有一个错误,如果我为Tire尝试它,则没有用于创建或新建的activerecord方法。当我检查轮胎时,它没有这些方法。我该如何补救?错误是这样的:未定义的方法'create'forActiveRecord::AttributeMethods::Serialization::Tire::Module我测试了两个环境:测试和开发,它们都因相同的错误而失败。 最佳答案

  10. ruby-on-rails - RSpec:避免使用允许接收的任何实例 - 2

    我正在处理旧代码的一部分。beforedoallow_any_instance_of(SportRateManager).toreceive(:create).and_return(true)endRubocop错误如下:Avoidstubbingusing'allow_any_instance_of'我读到了RuboCop::RSpec:AnyInstance我试着像下面那样改变它。由此beforedoallow_any_instance_of(SportRateManager).toreceive(:create).and_return(true)end对此:let(:sport_

随机推荐