数字信号处理-10-并行FIR滤波器MATLAB与FPGA实现

news2025/7/17 11:40:09

前言

本文介绍了设计滤波器的FPGA实现步骤,并结合杜勇老师的书籍中的并行FIR滤波器部分进行一步步实现硬件设计,对书中的架构做了复现以及解读,并进行了仿真验证。

并行FIR滤波器FPGA实现

FIR滤波器的结构形式时,介绍了直接型、级联型、频率取样型和快速卷积型4种。在FPGA实现时,最常用的是最简单的直接型结构。FPGA实现直接型结构的FIR滤波器,可以采用串行结构、并行结构等不同中的结构设计,上文根据书中提供的架构完成了串行 FIR滤波器的实现,本文沿用上文的基本代码结构,按照并行FIR滤波器的架构完成电路描述。

FIR滤波器需求

设计一个15阶(长度为16)的低通线性相位FIR滤波器,采用窗函数设计,截止频率为500 Hz,采样频率为2 000 Hz;采用FPGA实现并行结构的滤波器,系数的量化位数为12比特,输入数据位宽为12比特,输出数据位宽为29比特,系统时钟为16 kHz。

滤波器系数确定与量化

确定滤波器的结构后,就根据滤波器进行设计代码仿真,这里引用书中的仿真设计,并将滤波器参数系数量化。确定滤波器系数的方法有很多,可以使用MATLAB中丰富的函数实现,或者使用相关滤波器设计的软件工具,定制满足当前需求的窗函数的滤波器系数。具体量化系数确定可参考上文《数字信号处理-09-串行FIR滤波器MATLAB与FPGA实现》中的相关内容,或者参考杜勇老师的书中的内容。

硬件架构

下图为杜勇老师的《数字滤波器的MATLAB与FPGA实现》实现的并行FIR滤波器的结构图。因为FIR滤波器参数对称,所以同时计算相应的对称结构的值,将对称系数的X(n)相加后,可调用8个乘法器,完成对滤波器的乘法运算,所以针对并行滤波器的架构数据的输入速率和时钟可以相同,每一个时钟周期流水输出一个滤波后的信号值。图中的8输入的加法器,可以替换成N/2;这样就得到了一个通用化的并行FIR滤波器结构图。

并行FIR滤波器

并行实现FIR滤波器,虽然浪费了加法器和乘法器的资源,但是提升了整个滤波器实现的性能,当滤波器的系数长度N增大时,数据的吞吐速率不变(暂且不考虑面积增大对性能的影响),但带来的坏处就是会用掉相应倍数的逻辑资源和运算资源,速度和面积本来就是鱼和熊掌的关系,在实际应用中应当做相应的权衡和割舍。

根据架构描述电路

根据杜勇老师书中提供的架构,对电路进行描述,同样沿用了前文的通用化的模板,后期可根据参数输入来适配不同滤波器长度的设计。

实现模块框图

接口描述如下:

接口描述

参数描述如下:

参数描述

代码如下:

`timescale 1ns / 1ps
module Fir_Parallel(

        input clk,//!系统时钟
        input rst,//!复位信号
        input signed [SIGN_IN_WIDTH-1:0] signal_in,//!信号输入
        output signed [SIGN_OUT_WIDTH-1:0] signal_out//!信号输出,信号输出速度和输入速度相同
    );

    //
    parameter  integer SIGN_IN_WIDTH    = 12   ;//!信号输入位宽
    parameter  integer SIGN_OUT_WIDTH	= 29   ;//!信号输出位宽
    parameter  integer FIR_COE_WIDTH	= 12   ;//!滤波器系数位宽
    parameter  integer FIR_COE_NUM	= 16   ;//!滤波器长度
    localparam integer FIR_WIDTH_DIV_2	= FIR_COE_NUM/2 ;

    function [FIR_COE_WIDTH-1:0] coe_data;
    input [FIR_WIDTH_DIV_2-1:0] index;
    begin
        case(index)
        'd0:coe_data='h000;
        'd1:coe_data='hffd;
        'd2:coe_data='h00f;
        'd3:coe_data='h02e;
        'd4:coe_data='hf8b;
        'd5:coe_data='hef9;
        'd6:coe_data='h24e;
        'd7:coe_data='h7ff;
        endcase
    end
    endfunction
    integer i;
    genvar j;
    //!滤波器系数加载
    wire signed [FIR_COE_WIDTH-1:0] coe[FIR_WIDTH_DIV_2-1:0]; 
    generate
        for (j=0; j<FIR_WIDTH_DIV_2; j=j+1)
            assign coe[j] = coe_data(j);
    endgenerate
        
    //!寄存输入信号
    reg [SIGN_IN_WIDTH-1:0] Sign_in_Reg[FIR_COE_NUM-1:0];
    //将数据存入移位寄存器sign_in_Reg中
   
    always @(posedge clk)begin
        if (rst=='b1)begin
            //初始化寄存器值为0
            for (i=0; i<FIR_COE_NUM; i=i+1)
                Sign_in_Reg[i]=12'd0;
        end
        else begin
            for (i=0; i<FIR_COE_NUM-1; i=i+1)
                Sign_in_Reg[i+1] <= Sign_in_Reg[i];
            Sign_in_Reg[0] <= signal_in;
        end
    end
    reg signed [SIGN_IN_WIDTH:0] add_sum[FIR_WIDTH_DIV_2-1:0];
    //为了保证加法运算不溢出,输入输出数据均扩展为SIGN_IN_WIDTH+1比特。
    //对称结构只需要计算FIR_WIDTH_DIV_2次
    //一级流水
    always @(posedge clk) begin
        if (rst=='b1)begin
            for (i=0; i<FIR_WIDTH_DIV_2; i=i+1)
                add_sum[i]<= 'd0;
		end
		else begin
            for (i=0; i<FIR_WIDTH_DIV_2; i=i+1)
                add_sum[i]<= {Sign_in_Reg[i][SIGN_IN_WIDTH-1],Sign_in_Reg[i]} + 
                             {Sign_in_Reg[FIR_COE_NUM-1-i][SIGN_IN_WIDTH-1],Sign_in_Reg[FIR_COE_NUM-1-i]};
        end
    end
    
    (*use_dsp48="yes"*) reg signed [SIGN_IN_WIDTH+FIR_COE_WIDTH:0] mult_out[FIR_WIDTH_DIV_2-1:0];
    always @(posedge clk ) begin
        if (rst=='b1)begin
            for (i=0; i<FIR_WIDTH_DIV_2; i=i+1)
                mult_out[i]<= 'd0;
        end
        else begin
            for (i=0; i<FIR_WIDTH_DIV_2; i=i+1)
                mult_out[i] <= add_sum[i] * coe[i];
        end
    end
   
    assign signal_out = sign_out;
    reg signed [SIGN_OUT_WIDTH-1:0] sum;
    reg signed [SIGN_OUT_WIDTH-1:0] sign_out;

	always @(posedge clk)begin
        if (rst)begin 
				sum <= 'd0; 
				sign_out <= 'd0;
		end
		else begin
            sign_out <= sum;
            // sum = 'd0;
			// for (i=0; i<FIR_WIDTH_DIV_2; i=i+1)
            // sum =   sum + mult_out[i];
            sum <=  mult_out[0] + mult_out[1] + mult_out[2] + mult_out[3] +
                    mult_out[4] + mult_out[5] + mult_out[6] + mult_out[7];
        end
    end
endmodule

代码解读

关于加载滤波器系数的部分,我这里使用了function做了包装,以便于后续修改滤波器长度时,可以通过脚本生成function去增加滤波器系数的长度。

function [FIR_COE_WIDTH-1:0] coe_data;
    input [FIR_WIDTH_DIV_2-1:0] index;
    begin
        case(index)
        'd0:coe_data='h000;
        'd1:coe_data='hffd;
        'd2:coe_data='h00f;
        'd3:coe_data='h02e;
        'd4:coe_data='hf8b;
        'd5:coe_data='hef9;
        'd6:coe_data='h24e;
        'd7:coe_data='h7ff;
        endcase
    end
    endfunction

针对乘法运算,这里没有使用IP,但是为了使得该部分运算使用DSP资源,更好地提升性能,因此该信号的运算使用dsp48资源,所以在信号声明时前面加了(*use_dsp48="yes"*)

关于杜勇老师书中写的信号与系数相乘后的结果针对sum信号使用了阻塞赋值的部分,个人觉得这个在时序逻辑中是不太好的设计,使用的代码如下,虽然会简化乘累加的过程,但是针对实际使用的工程来说,这个是不好的代码风格。

always @(posedge clk)begin
        if (rst=='b1)begin 
				sum = 'd0; 
				sign_out <= 'd0;
		end
		else begin
            sign_out <= sum;
            sum = 'd0;
			for (i=0; i<FIR_WIDTH_DIV_2; i=i+1)
                sum =   sum + mult_out[i];
        end
    end	

所以这里我直接做了展开处理,将8个结果做了加法。

电路架构优化

我认为在随着滤波器规模变大运算的数据位宽增加时,信号与系数相乘后的结果进行累加操作的部分,组合逻辑的延时相对会增加很多,为了进一步提升电路架构的性能,可对该部分进行加法树的平衡,打拍优化加法树结构,应该有可能进一步提升电路架构的性能。

仿真设计

仿真数据设计

为了验证并行设计代码的正确性。这里使用MATLAB脚本产生了一个混频信号,混频的频率为100hz和700hz的叠加,然后将混频信号进行量化处理并导出txt文件以供仿真文件读取。

clc;close all;clear all;
 Fs = 2000; %采样频率
N = 2^10; %采样点数
f1=300; %正弦波1频率
f2=400; %正弦波1频率
t=[0:N-1]/Fs; %时间序列
s1 = sin(2*pi*f1*t) ;
s2 = sin(2*pi*f2*t) ;
s = s1 .* s2;
figure(1);
subplot(1,2,1);
plot(t,s,'r','LineWidth',1.2);
title('时域波形');
axis([0,100/Fs,-3,3]);
set(gca,'LineWidth',1.2);
%转化为位宽12bit数据
s_12bit=s./max(s).*(2.^11 - 1); % DA输入波形,量化到16bit
s_12bit(find(s_12bit<0) ) = s_12bit(find(s_12bit<0) ) + 2^12 - 1;
s_12bit = fix(s_12bit);
s_12bit = dec2hex(s_12bit);
% %生成文件
fid= fopen('sin_data.txt','w+');
%生成十六进制
for i=1:N
    fprintf(fid,'%s',s_12bit(i,:));
    fprintf(fid,'\r\n');
end
fclose(fid);
%% 设计验证
N=16;      %滤波器长度
fs=2000;   %采样频率
fc=500;    %低通滤波器的截止频率
B=12;      %量化位数
%生成各种窗函数
w_kais=blackman(N)';
%采用fir1函数设计FIR滤波器
b_kais=fir1(N-1,fc*2/fs,w_kais);
ss=conv(b_kais,s);
subplot(1,2,2);
plot(t(20:1000),ss(20:1000));
title('滤波后信号');
axis([0,100/Fs,-1,1]);
set(gca,'LineWidth',1.2);

运行仿真后,根据设计的滤波器系数进行仿真,发现可以正常滤波除去高频分量。

滤波仿真效果

仿真激励文件编写

`timescale 1ns / 1ps
module Fir_Parallel_tb;

    // Parameters
    localparam integer SIGN_IN_WIDTH = 12;
    localparam integer SIGN_OUT_WIDTH = 29;
    localparam integer FIR_COE_WIDTH = 12;
    localparam integer FIR_COE_NUM = 16;

    // Ports
    reg clk = 1;
    reg rst = 1;
    reg [SIGN_IN_WIDTH-1:0] signal_in;
    wire [SIGN_OUT_WIDTH-1:0] signal_out;

    Fir_Parallel #(
                       .SIGN_IN_WIDTH(SIGN_IN_WIDTH ),
                       .SIGN_OUT_WIDTH(SIGN_OUT_WIDTH ),
                       .FIR_COE_WIDTH(FIR_COE_WIDTH ),
                       .FIR_COE_NUM (FIR_COE_NUM )
                   )Fir_Parallel_dut (
                       .clk (clk ),
                       .rst (rst ),
                       .signal_in (signal_in ),
                       .signal_out  ( signal_out)
                   );

    reg  [11:0] mem [0:99];
    reg  [9:0] addr ;
    // reg  [11:0]data_out ;
    always #(10*1)
    begin
        if(rst==0)
            addr = addr + 10'd1;
        signal_in  =  mem[addr][11:0];
    end

    always
        #5  clk = ! clk ;

    initial
    begin
        signal_in =0;
        $readmemh("sin_data.txt",mem);
        addr  = 10'd0;
        #10;
        rst   = 0;
    end

endmodule

运行仿真,查看波形可见,滤波效果和仿真结果一致。

仿真波形

延迟分析

该架构的数据输入后,每四个时钟周期后输出一个数据,其中,一个时钟周期用于X(n)的加和,一个时钟周期用于计算信号和滤波器系数相乘的结果,一个时钟周期用于乘法输出后的数据做累加处理,一个时钟用于读取累加后的结果。

延时分析

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/38599.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

23. [Python GUI] PyQt5中的模型与视图框架-抽象视图基类QAbstractItemView

PyQt5的抽象视图基类QAbstractItemView 一、QAbstractItemView的基本概念 QAbstractItemView 类继承自 QAbstractScrollArea&#xff0c;后者又继承自 QFrame&#xff0c;该类是 Qt 所有视图类的基类&#xff0c; Qt 的所有视图都需要子类化该类。注意&#xff1a;该类是抽象…

Linux系统编程(四)——signal信号处理

目录 0x01 信号 0x02 信号相关的函数 一、kill函数 二、alarm()函数 三、setitimer() 四、signal() 0x03 信号集 一、信号集的处理过程 ​编辑 二、关于信号集处理的函数 0x04 内核实现信号捕捉的过程 0x05 SIGCHLD信号 0x01 信号 信号是Linux进程间通信的最古老的…

通过FNN算法进行特征组合的商品推荐详细教程 有代码+数据

案例知识点 推荐系统任务描述:通过用户的历史行为(比如浏览记录、购买记录等等)准确的预测出用户未来的行为;好的推荐系统不仅如此,而且能够拓展用户的视野,帮助他们发现可能感兴趣的却不容易发现的item;同时将埋没在长尾中的好商品推荐给可能感兴趣的用户。CTR表示Clic…

【2021 MCM】 Problem A: Fungi by 2100454

【2021 MCM】 Problem A: Fungi by 2100454 文章目录【2021 MCM】 Problem A: Fungi by 2100454一、题目分析1.1 问题总述1.2 具体任务1.3 需要提交的内容二、论文解读2.1 摘要2.2 目录2.3 简介2.4 假设2.5 缩写和定义2.6 The GAME Model2.6.1 Gause’s Model for Predicting F…

防火墙基本概念

防火墙是一款具有安全防护功能的网络设备&#xff0c;保护一个网络区域避免另一个网络区域的攻击和入侵。 物理防火墙&#xff08;物理设备&#xff09;、软件防火墙&#xff08;Windows自带firewall&#xff09; 其本职工作是隔离网络 基本功能 会话管理内网安全管控入侵…

[附源码]java毕业设计中医药系统论文2022

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

痞子衡嵌入式:MCUXpresso IDE下高度灵活的FreeMarker链接文件模板机制

大家好&#xff0c;我是痞子衡&#xff0c;是正经搞技术的痞子。今天痞子衡给大家分享的是MCUXpresso IDE下高度灵活的FreeMarker链接文件模板机制。 痞子衡之前写过一篇文章 《MCUXpresso IDE下工程链接文件配置管理与自动生成机制》&#xff0c;这篇文章介绍了 MCUXpresso ID…

网页前端知识汇总(三)——网页前端利用二维码插件qrcode生成在线二维码

最近几年二维码的广泛应用&#xff0c;方便了很多行业&#xff0c;如支付宝&#xff0c;微信&#xff0c;小程序扫码之类的&#xff0c;这个在二十年前&#xff0c;想都不敢想这么方便&#xff0c;那时候有书刊编码扫一扫都感觉是高科技了&#xff0c;如今&#xff0c;二维码的…

RNA-seq 详细教程:实验设计(2)

学习目标 了解设置重复对于 RNA-seq 分析的重要性了解生物重复次数、测序深度和鉴定到的差异表达基因之间的关系了解如何设计RNA-seq 实验&#xff0c;以避免批次效应1. 注意事项 了解 RNA 提取和 RNA-seq 文库制备实验过程中的步骤&#xff0c;有助于设计 RNA-seq 实验&#x…

PyTorch学习笔记-常用函数与数据加载

1. PyTorch常用函数 &#xff08;1&#xff09;路径相关的函数 假设我们数据集的目录结构如下&#xff1a; 首先需要 import os&#xff0c;在 os 中常用的路径相关的函数有&#xff1a; os.listdir(path)&#xff1a;将 path 目录下的内容列成一个 list。os.path.join(path1…

cmake入门教程 跨平台项目构建工具cmake介绍

一.初识cmake 在介绍cmake之前&#xff0c;我们先来从工具一个个衍生出来&#xff0c;做过linux c/c编程的时候一般用过gcc指令或者makefile。我们先来介绍下 gcc&#xff08;GNU Compiler Collection&#xff09;将源文件编译&#xff08;Compile&#xff09;成可执行文件或…

若依框架解读(前后端分离版)—— 1.Spring Security相关配置(@Anonymous注解)

有关Spring Security与JWT相关知识可以看我之前写的文章&#xff1a;SpringBoot整合SpringSecurityJWT(三更草堂) 这边需要对RBAC模型有一点了解&#xff0c;比较简单可自行百度。 首先查看Security配置类SecurityConfig&#xff0c;如果我们想要放行自己写的接口是可以在此配置…

数学建模国赛/美赛常见赛题类型及建模方案(纯干货)

目录 一&#xff0c;评价类问题 1&#xff0c;建模步骤如下图所示&#xff1a; 2&#xff0c;主客观评价问题的区别 3&#xff0c;如何选择合适的评价方法 二&#xff0c;预测类赛题 1&#xff0c;预测类赛题的基本解题步骤 2&#xff0c;预测类问题的区别 3&#xff0c;…

什么是Tomcat?如何使用Tomcat?如何部署一个静态页面?

目录 1、Tomcat是什么&#xff1f; 2、下载安装 3、目录结构 4、启动服务器 5、部署静态页面&#xff08;简单举例&#xff09; 1、Tomcat是什么&#xff1f; Tomcat是一个HTTP服务器&#xff0c;Tomcat就是基于Java实现的一个开源免费的HTTP服务器 2、下载安装 下载网…

virtualbox安装openEuler-方案二

下载的讲解在另一篇&#xff1a;VirtualBox安装openEuler 方案一 安装&#xff1a; 1&#xff0c;配置网卡 2&#xff0c;加载光驱设置 选择对应的iso文件即可。 3&#xff0c;启动openEuler 选择第一个即可&#xff0c;第二个选项一般是在生成环境中使用。 经过一段…

std::weak_ptr(分析、仿写)

目录 一、为什么会有weak_ptr? 1、看一个例子 2.weak_ptr 是什么? 3.weak_ ptr 如何使用? 1、如何创建weak_ ptr 实例 2、如何判断weak_ptr指向的对象是否存在 3、weak_ptr的使用 二、仿写std::weak_ptr 1、c参考手册 2、代码 一、为什么会有weak_ptr? 1、看一个例子…

18uec++多人游戏【服务器为两个角色发枪,并能在线开枪】

打开主角类&#xff0c;生成枪的代码逻辑在游戏开始函数里 所以在生成之前&#xff0c;我们需要判断该对象是否在服务器端&#xff08;服务器端视角&#xff09; void ASCharacter::BeginPlay() {Super::BeginPlay();DefaultsFOV CameraComp->FieldOfView;//判断是否在服务…

支付系统 — 支付路由

本文主要介绍下支付中路由系统的主要流程。 支付路由的作用 降低成本&#xff1a;越便宜越好&#xff1b; 提高用户体验&#xff1a;用户支付的越爽越好&#xff1b;越快越好&#xff1b;成功率越高越好。 确保有可用通道&#xff1a;多个选择&#xff0c;确保能完成支付。 …

【JVM】PC程序计数器和PC寄存器

一、JVM体系结构 本文所讲内容主要是 JVM 体系结构图中 运行时数据区 中的 PC寄存器&#xff0c;如下图所示&#xff1a; 二、PC寄存器是什么&#xff1f; 这里引用别人的一句话&#xff1a; 首先这里的PC寄存器并非广义上所指的物理寄存器&#xff0c;或许将其翻译为PC计数…

antd Carousel 重写dot样式

antd的Carousel走马灯组件的dot也就是下面那个滑动的按钮非常的不起眼。 白色背景的时候完全看不到。 但是我们大部分时候又都是白色背景&#xff0c;于是来自己重写一下样式。 在控制台看了一下&#xff0c;应该是这个属性在控制dot的颜色&#xff0c;重写这个属性就可以了。…