众所周知,Matlab 中的 Filter Designer 可以直接生成 FIR 滤波器的 verilog 代码,可以方便地生成指定阶数、指定滤波器参数的高通、低通、带通滤波器,生成的 verilog 代码也可以指定输入输出信号的类型和位宽。然而其生成的代码实在算不上美观,复用性也很差,要实现不同滤波特性的切换就要生成多个滤波器的代码。
出于以上考虑,自己设计实现了 FIR 滤波器的通用 verilog 代码,其滤波器参数通过接口输入,从而可以通过输入不同的参数获得相应的滤波结果。verilog 代码如下:
/*
* file : FIR_filter.v
* author : 今朝无言
* date : 2023-07-03
* version : v1.0
* description : FIR 滤波器
*/
module FIR_filter(
input clk,
input rst_n,
input [16*N-1:0] filter_params,
input signed [15:0] data_in,
output reg signed [15:0] data_out
);
parameter N = 32; //滤波器参数个数
parameter div_N = 16; //sum结果除 2^div_N,作为 filter 的输出
//FIR 滤波器参数
reg signed [15:0] b[0:N-1];
integer m;
always @(*) begin
for(m=0; m=0; i=i-1) begin
shift_reg[i] <= 16'd0;
end
end
else begin
for(i=N-1; i>0; i=i-1) begin
shift_reg[i] <= shift_reg[i-1];
end
shift_reg[0] <= data_in;
end
end
reg signed [31:0] multi[0:N-1];
integer j;
always @(*) begin
for(j=0; j
当滤波器阶数较高时,滤波器参数如何给出无疑是个麻烦事,因此又编写了 matlab 代码,可以一键生成所需的 .v 文件以实现参数的配置:
%-----------FIR滤波器参数(生成.v)-----------------
clc,clear,close all
fs=1e6;
N=20;
Wn=0.1;
b = fir1(N, Wn); % 默认Hamming窗
freqz(b,1,512)
%% pramas
B=floor(b*65536);
B=B';
%% test
t=0:1/fs:1e-3;
s=(mod(t,1e-4)<5e-5)*1.0;
%s_filt=filter(B,1,s)/65536;
for i=1:size(s,2)-N-1
s_filt(i)=s(i:i+N)*double(B)/65536;
end
figure
subplot(2,1,1)
plot(t,s)
subplot(2,1,2)
plot(t(1:end-N-1),s_filt)
%% 生成.v
filename='FIR_params';
fid=fopen(['./v/',filename,'.v'],'w');
fprintf(fid,['/* ','\n',...
' * file\t\t\t: ',filename,'.v','\n',...
' * author\t\t: 今朝无言','\n',...
' * date\t\t\t: 2023-07-04','\n',...
' * version\t\t: v1.0','\n',...
' * description\t: FIR 滤波器','\n',...
' */','\n']);
fprintf(fid,['module ',filename,'(','\n',...
'output\t[',num2str(size(B,1)*16-1),':0]\tparams\n',...
');\n\n']);
for i=1:size(B,1)
if(B(i)>=0)
hex=dec2hex(B(i),4);
else
hex=dec2hex(65536+B(i),4);
end
fprintf(fid,['assign\t','params[',...
num2str(i*16-1),':',num2str((i-1)*16),...
']\t= 16','''','h',hex,';\n']);
end
fprintf(fid,'\nendmodule\n');
fclose(fid);
testbench与测试结果如下
`timescale 1ns/100ps
module FIR_filter_tb();
reg clk_100M = 1'b1;
always #5 begin
clk_100M <= ~clk_100M;
end
localparam N = 20; //FIR滤波器阶数
wire [16*(N+1)-1:0] filter_params;
FIR_params_0d1 FIR_params_inst(
.params (filter_params)
);
reg [15:0] data_in;
wire signed [15:0] data_out;
FIR_filter #(.N(N+1))
FIR_filter_inst2(
.clk (clk_100M),
.filter_params (filter_params), //滤波器参数
.data_in (data_in),
.data_out (data_out)
);
reg [7:0] cnt = 8'd0;
always @(posedge clk_100M) begin
cnt <= cnt + 1'b1;
if(cnt<100) begin
data_in <= -10000;
end
else if(cnt<200) begin
data_in <= 10000;
end
else begin
data_in <= 0;
end
end
initial begin
#10000;
$stop;
end
endmodule
使用以上 FIR 滤波器代码,还可以实现许多其他滤波功能,比如常用的 90 度相移,可以使用 Hilbert 变换实现,Hilbert 滤波器参数的 matlab 生成代码如下
%-----------------Hilbert----------------------
clc,clear,close all
%% Hilbert
N=200;
% method 1 这种直接通过 h(n) 表达式生成的更为精确,推荐
n=(1:floor((N-1)/2));
b1=(1-(-1).^n)./(pi.*n);
if mod(N,2)==0
b1=[0,b1,0,-b1(end:-1:1)]';
else
b1=[0,b1,-b1(end:-1:1)]';
end
% method 2 构造 Hilbert 的频域特性,经 IFFT 获得
H=[-1j*ones(1,floor((N+1)/2)),1j*ones(1,floor(N/2))];
b2=ifft(H);
b2=real(b2)';
b=b1;
freqz(b,1,100)
%% Filter
fs=1e3;
t=0:1/fs:1;
s=5*sin(2*pi*10*t);
% f >= fs/N 时,可以由很好的90度移相
s2=filter(b,1,s);
figure
hold on
plot(t,s,'r-')
plot(t,s2,'b--')
hold off
%% 量化
B=floor(b*32768);
s3=filter(B,1,s)/32768;
figure
hold on
plot(t,s,'r-')
plot(t,s3,'b--')
hold off
%% 生成params.v
filename='Hilbert_params';
fid=fopen(['./v/',filename,'.v'],'w');
fprintf(fid,['/* ','\n',...
' * file\t\t\t: ',filename,'.v','\n',...
' * author\t\t: 今朝无言','\n',...
' * date\t\t\t: 2023-08-04','\n',...
' * version\t\t: v1.0','\n',...
' * description\t: FIR滤波器参数(Hilbert)',...
' N=',num2str(N),'\n',...
' */','\n']);
fprintf(fid,['module ',filename,'(','\n',...
'output\t[',num2str(size(B,1)*16-1),':0]\tparams\n',...
');\n\n']);
for i=1:size(B,1)
if(B(i)>=0)
hex=dec2hex(B(i),4);
else
hex=dec2hex(65536+B(i),4);
end
fprintf(fid,['assign\t','params[',...
num2str(i*16-1),':',num2str((i-1)*16),...
']\t= 16','''','h',hex,';\n']);
end
fprintf(fid,'\nendmodule\n');
fclose(fid);
仿真结果如下