verilog实现FIR滤波系数生成(阶数,FIR滤波器类型及窗函数可调)
在以往采用 FPGA 实现的 FIR 滤波功能,滤波器系数是通过 matlab 计算生成,然后作为固定参数导入到 verilog 程序中,这尽管简单,但灵活性不足。在某些需求下(例如捕获任意给定台站信号)需要随时修改滤波器的中心频率、带宽等信息,这要么通过上位机计算系数后更新到 FPGA 端(但并非所有设备都具备配套的上位机),要么直接在 FPGA 端计算并更新滤波器系数。本文对后者进行实现。
计算 FIR 滤波器系数,主要包括两个方面的计算:窗函数计算,滤波器系数计算。
窗函数生成
几种常用窗函数
首先给出几种常用的窗函数的表达式,这里不对窗函数细节进行讨论:
- 矩形窗
w ( n ) = 1.0 , n = 0 , 1 , . . . , N − 1 w(n) = 1.0,\ n=0,1,...,N-1 w(n)=1.0, n=0,1,...,N−1
- 三角窗
w ( n ) = 1 − ∣ 1 − 2 n N − 1 ∣ , n = 0 , 1 , . . . , N − 1 w(n)=1 - |1 - \frac{2n}{N - 1}|,\ n=0,1,...,N-1 w(n)=1−∣1−N−12n∣, n=0,1,...,N−1
- 图基窗 Tukey
w ( n ) = { 0.5 − 0.5 cos ( n π k + 1 ) , 0 ≤ n ≤ k 1.0 , k < n ≤ N − k − 2 0.5 − 0.5 cos ( π ( N − n − 1 ) k + 1 ) , N − k − 2 < n ≤ N − 1 , where k = N − 2 10 w(n)= \left\{ \begin{aligned} 0.5 - 0.5\cos(\frac{n\pi}{k + 1}),&\ 0\le n\le k\\ 1.0,&\ k<n\le N-k-2\\ 0.5 - 0.5\cos(\frac{\pi(N - n - 1)}{k + 1}),&\ N-k-2<n\le N-1\ \end{aligned} \right. ,\ \text{where}\ k=\frac{N-2}{10} w(n)=⎩ ⎨ ⎧0.5−0.5cos(k+1nπ),1.0,0.5−0.5cos(k+1π(N−n−1)), 0≤n≤k k<n≤N−k−2 N−k−2<n≤N−1 , where k=10N−2
- 汉宁窗 Hann
w ( n ) = 0.5 × [ 1.0 − cos ( 2 π n N − 1 ) ] , n = 0 , 1 , . . . , N − 1 w(n)=0.5 \times [1.0 - \cos(\frac{2\pi n}{N - 1})],\ n=0,1,...,N-1 w(n)=0.5×[1.0−cos(N−12πn)], n=0,1,...,N−1
- 汉明窗 Hamming
w ( n ) = 0.54 − 0.46 cos ( 2 π n N − 1 ) , n = 0 , 1 , . . . , N − 1 w(n)=0.54 - 0.46\cos(\frac{2\pi n}{N - 1}),\ n=0,1,...,N-1 w(n)=0.54−0.46cos(N−12πn), n=0,1,...,N−1
- 布莱克曼窗 Blackman
w ( n ) = 0.42 − 0.5 cos ( 2 π n N − 1 ) + 0.08 cos ( 4 π n N − 1 ) , n = 0 , 1 , . . . , N − 1 w(n)=0.42 - 0.5\cos(\frac{2\pi n}{N - 1}) + 0.08\cos(\frac{4\pi n}{N-1}),\ n=0,1,...,N-1 w(n)=0.42−0.5cos(N−12πn)+0.08cos(N−14πn), n=0,1,...,N−1
verilog 实现
可以观察到,Tukey、Hann、Hamming 和 Blackman 窗都用到了余弦函数,这可以用正余弦查找表实现,下面代码中会用到这一模块,可参考我这篇博文;窗函数生成器代码如下
/* * file : FIR_windows_generator.v* author : 今朝无言* lab : WHU-EIS-LMSWE* date : 2024-09-26* version : v1.0* description : 生成指定阶数、指定类型的窗函数*/
`default_nettype none
module FIR_windows_generator(
input wire clk,
input wire rst_n,input wire en, //上升沿触发窗口计算
input wire [3:0] win_type, //窗口类型,1:矩形窗,2:图基窗Tukey,3:三角窗,4:汉宁窗Hann,5:海明窗Hamming,6:布莱克曼窗Blackman,(7:凯塞窗kaiser, 暂未实现)
input wire [15:0] n, //窗口长度
input wire [15:0] i, //窗口索引值,0 ~ n-1
//input wire signed [15:0] beta, //kaiser窗的参数beta,win_type=7时需要这个参数,其他情况可任意给值output wire busy, //指示模块是否计算完成
output wire signed [15:0] win
);reg signed [15:0] win_buf = 16'sd256; //8-8有符号定点数
reg signed [15:0] win_buf_d0 = 16'sd256;
reg busy_buf = 1'b0;assign win = win_buf_d0;
assign busy = busy_buf;localparam S_IDLE = 4'h1;
localparam S_CAL = 4'h2;
localparam S_END = 4'h4;reg [3:0] state = S_IDLE;
reg [3:0] next_state;always @(posedge clk) beginif(~rst_n) beginstate <= S_IDLE;endelse beginstate <= next_state;end
endalways @(*) begincase(state)S_IDLE: beginif(en_pe) beginnext_state <= S_CAL;endelse beginnext_state <= S_IDLE;endendS_CAL: begincase(win_type)4'd1: begin //矩形窗next_state <= S_END;end4'd2: begin //图基窗if(cnt >= 4'd4) beginnext_state <= S_END;endelse beginnext_state <= S_CAL;endend4'd3: begin //三角窗if(cnt >= 4'd1) beginnext_state <= S_END;endelse beginnext_state <= S_CAL;endend4'd4: begin //汉宁窗if(cnt >= 4'd3) beginnext_state <= S_END;endelse beginnext_state <= S_CAL;endend4'd5: begin //海明窗if(cnt >= 4'd3) beginnext_state <= S_END;endelse beginnext_state <= S_CAL;endend4'd6: begin //布莱克曼窗if(cnt >= 4'd5) beginnext_state <= S_END;endelse beginnext_state <= S_CAL;endend// 4'd7: begin //凯塞窗 这个涉及到循环逼近bessel函数和sqrt计算,FPGA比较麻烦,就先不实现这个窗口类型了// next_state <= S_END;// enddefault: beginnext_state <= S_END;endendcaseendS_END: beginnext_state <= S_IDLE;enddefault: beginnext_state <= S_IDLE;endendcase
end//en 边沿检测
wire en_pe;
detect_sig_edge detect_sig_edge_inst(.clk (clk), //工作时钟.sig (en), //待检测信号.sig_pe (en_pe), //信号上升沿.sig_ne (), //下降沿.sig_de () //双边沿
);//cnt 控制读取cosin结果,以计算窗口值
reg [3:0] cnt = 4'd0;
always @(posedge clk) begincase(state)S_IDLE: begincnt <= 4'd0;endS_CAL: begincnt <= cnt + 1'b1;enddefault: begincnt <= 4'd0;endendcase
end//win_buf
reg signed [31:0] multi_tmp = 32'sd0;
always @(posedge clk) beginif(~rst_n) beginwin_buf <= 16'sd256; //1.0endelse case(state)S_CAL: begincase(win_type)4'd1: begin //矩形窗win_buf <= 16'sd256;end4'd2: begin //图基窗if(cnt == 4'd4) beginif(i <= k) beginwin_buf <= (16'sd256 - (cos_val_s >>> 7)) >>> 1;endelse if(i > n - k - 4'd2) beginwin_buf <= (16'sd256 - (cos_val_s >>> 7)) >>> 1;endelse beginwin_buf <= 16'sd256;endendelse beginwin_buf <= win_buf;endend4'd3: begin //三角窗if(cnt == 4'd0) beginmulti_tmp <= 16'sd512 * i / (n - 1'b1);endelse if(cnt == 4'd1) beginwin_buf <= 16'sd256 - abs(16'sd256 - multi_tmp[15:0]);endelse beginwin_buf <= win_buf;endend4'd4: begin //汉宁窗if(cnt == 4'd3) beginwin_buf <= 16'sd128 - (cos_val_s >>> 8); //0.5 * (1.0 - cos(2 * i * pi / (n - 1)));endelse beginwin_buf <= win_buf;endend4'd5: begin //海明窗if(cnt == 4'd3) beginwin_buf <= 16'sd138 - ((16'sd118 * (cos_val_s >>> 7)) >>> 8); //0.54 - 0.46 * cos(2 * i * pi / (n - 1));endelse beginwin_buf <= win_buf;endend4'd6: begin //布莱克曼窗if(cnt == 4'd3) beginwin_buf <= 16'sd108 - (cos_val_s >>> 8);endelse if(cnt == 4'd5) beginwin_buf <= win_buf + ((16'sd82 * (cos_val_s >>> 7)) >>> 10);endelse beginwin_buf <= win_buf;endend// 4'd7: begin //凯塞窗// win_buf <= 16'sd0;// enddefault: beginwin_buf <= 16'sd256;endendcaseenddefault: beginwin_buf <= win_buf;endendcase
end//busy_buf
always @(*) begincase(state)S_IDLE: beginbusy_buf <= 1'b0;enddefault: beginbusy_buf <= 1'b1;endendcase
end//cos_phase
reg [15:0] k = 16'd0;
always @(posedge clk) begincase(state)S_CAL: begincase(win_type)4'd1: begin //矩形窗cos_phase <= 16'd0;end4'd2: begin //图基窗if(cnt == 4'd0) begink <= (n - 2'd2) / 4'd10;cos_phase <= cos_phase;endelse if(cnt == 4'd1) begink <= k;if(i <= k) begincos_phase <= i * (16'd32768 / (k + 1'b1)) + 16'd16384;endelse if(i > n - k - 2'd2) begincos_phase <= (n - i - 1'b1) * (16'd32768 / (k + 1'b1)) + 16'd16384;endelse begincos_phase <= 16'd0;endendelse begincos_phase <= cos_phase;k <= k;endend4'd3: begin //三角窗cos_phase <= 16'd0;end4'd4: begin //汉宁窗cos_phase <= 2'd2 * i * (16'd32768 / (n - 1'b1)) + 16'd16384;end4'd5: begin //海明窗cos_phase <= 2'd2 * i * (16'd32768 / (n - 1'b1)) + 16'd16384;end4'd6: begin //布莱克曼窗if(cnt == 4'd0) begincos_phase <= 2'd2 * i * (16'd32768 / (n - 1'b1)) + 16'd16384;endelse if(cnt == 4'd2) begincos_phase <= 4'd4 * i * (16'd32768 / (n - 1'b1)) + 16'd16384;endelse begincos_phase <= cos_phase;endend// 4'd7: begin //凯塞窗// cos_phase <= 16'd0;// enddefault: begincos_phase <= 16'd0;endendcaseenddefault: begincos_phase <= cos_phase;endendcase
end//sin_rom
reg [15:0] cos_phase = 16'd0;
wire [15:0] cos_out;
sin_gen sin_gen_inst(.clk (clk),.phase (cos_phase), //相位,0~65535对应[0~2pi).sin_out (cos_out) //0~65535
);wire signed [15:0] cos_val_s;
assign cos_val_s = {~cos_out[15], cos_out[14:0]};//win_buf_d0
always @(posedge clk) begincase(state)S_END: beginwin_buf_d0 <= win_buf;enddefault: beginwin_buf_d0 <= win_buf_d0;endendcase
end//------------------func------------------------------
function signed [15:0] abs(input signed [15:0] a);beginabs = (a >= 16'sd0)? a : -a;end
endfunctionendmodule
测试
testbench 如下
`timescale 1ns/100psmodule FIR_windows_generate_tb();reg clk_100M = 1'b1;
always #5 beginclk_100M <= ~clk_100M;
endreg rst_n = 1'b1;reg en; //上升沿触发窗口计算
reg [3:0] win_type; //窗口类型,1:矩形窗,2:图基窗,3:三角窗,4:汉宁窗,5:海明窗,6:布莱克曼窗
reg [15:0] n; //滤波器阶数
reg [15:0] i; //窗口索引值,0 ~ n-1wire busy;
wire signed [15:0] win;FIR_windows_generator FIR_windows_generator_inst(.clk (clk_100M),.rst_n (rst_n),.en (en), //上升沿触发窗口计算.win_type (win_type), //窗口类型,1:矩形窗,2:图基窗,3:三角窗,4:汉宁窗,5:海明窗,6:布莱克曼窗.n (n), //滤波器阶数.i (i), //窗口索引值,0 ~ n-1.busy (busy), //指示模块是否计算完成.win (win)
);//进行一组FIR_win的计算
task cal_win;input [3:0] WIN_TYPE;input [15:0] N;integer k;beginn = N;win_type = WIN_TYPE;#10;for (k = 0; k < N; k = k + 1'b1) begini = k;en = 1'b1;wait(busy);#10;en = 1'b0;wait(~busy);#10;endend
endtaskinitial beginrst_n <= 1'b0;en <= 1'b0;win_type <= 1'b1;n <= 16'd16;i <= 16'd0;#100;rst_n <= 1'b1;#100;cal_win(1, 64); //矩形窗#100;cal_win(3, 64); //三角窗#100;cal_win(2, 64); //图基窗#100;cal_win(4, 64); //汉宁窗#100;cal_win(5, 64); //海明窗#100;cal_win(6, 64); //布莱克曼窗#200;$stop;
endendmodule
仿真结果如下
滤波器系数计算
FIR 滤波器冲激响应
这里给出理想低通 FIR 滤波器,理想高通 FIR 滤波器、理想带通 FIR 滤波器、理想带阻 FIR 滤波器的冲激响应函数表达式:
- 低通
h L P ( n ) = sin ( 2 π f c f s s ) π s , where s = ∣ n − N 2 ∣ , n = 0 , 1 , . . . , N h_{LP}(n)=\frac{\sin(\frac{2\pi f_{c}}{f_s}s)}{\pi s},\ \text{where}\ s=|n-\frac{N}{2}|,\ n=0,1,...,N hLP(n)=πssin(fs2πfcs), where s=∣n−2N∣, n=0,1,...,N
其中 f s f_s fs 为采样率, f c f_c fc 为截止频率。
- 高通
h H P ( n ) = sin ( π s ) − sin ( 2 π f c f s s ) π s , where s = ∣ n − N 2 ∣ , n = 0 , 1 , . . . , N h_{HP}(n)=\frac{\sin(\pi s)-\sin(\frac{2\pi f_{c}}{f_s}s)}{\pi s},\ \text{where}\ s=|n-\frac{N}{2}|,\ n=0,1,...,N hHP(n)=πssin(πs)−sin(fs2πfcs), where s=∣n−2N∣, n=0,1,...,N
- 带通
h B P ( n ) = sin ( 2 π f c 2 f s s ) − sin ( 2 π f c 1 f s s ) π s , where s = ∣ n − N 2 ∣ , n = 0 , 1 , . . . , N h_{BP}(n)=\frac{\sin(\frac{2\pi f_{c2}}{f_s}s)-\sin(\frac{2\pi f_{c1}}{f_s}s)}{\pi s},\ \text{where}\ s=|n-\frac{N}{2}|,\ n=0,1,...,N hBP(n)=πssin(fs2πfc2s)−sin(fs2πfc1s), where s=∣n−2N∣, n=0,1,...,N
其中 f c 1 f_{c1} fc1 为下截止频率, f c 2 f_{c2} fc2 为上截止频率。
- 带阻
h B S ( n ) = sin ( 2 π f c 1 f s s ) + sin ( π s ) − sin ( 2 π f c 2 f s s ) π s , where s = ∣ n − N 2 ∣ , n = 0 , 1 , . . . , N h_{BS}(n)=\frac{\sin(\frac{2\pi f_{c1}}{f_s}s)+\sin(\pi s)-\sin(\frac{2\pi f_{c2}}{f_s}s)}{\pi s},\ \text{where}\ s=|n-\frac{N}{2}|,\ n=0,1,...,N hBS(n)=πssin(fs2πfc1s)+sin(πs)−sin(fs2πfc2s), where s=∣n−2N∣, n=0,1,...,N
在实际设计中,FIR 滤波的数据要加窗以将无限冲激的 sinc 函数截断为有限长(即窗函数法 FIR 滤波器设计),因此将以上冲激响应与窗函数相乘即可。在计算以上冲激函数时,当阶数 N 为偶数时,则会在 n = N / 2 n=N/2 n=N/2 时出现除零的问题,此时利用洛必达法则进行计算即可。
verilog 实现
/* * file : FIR_firwin_generator.v* author : 今朝无言* lab : WHU-EIS-LMSWE* date : 2024-09-26* version : v1.0* description : 生成指定阶数、指定类型的FIR滤波窗口*/
`default_nettype none
module FIR_firwin_generator(
input wire clk,
input wire rst_n,input wire en, //上升沿触发窗口计算
input wire [1:0] band_type, //滤波器类型,0:低通LP,1:高通HP,2:带通BP,3:带阻BS
input wire signed [15:0] fs, //采样率,注意fln,fhn均应小于fs/2
input wire signed [15:0] fln, //滤波器下频点,LP,HP,BP,BS均会用到
input wire signed [15:0] fhn, //滤波器上频点,BP,BS用到
input wire [3:0] win_type, //窗函数类型,1:矩形窗,2:图基窗Tukey,3:三角窗,4:汉宁窗Hann,5:海明窗Hamming,6:布莱克曼窗Blackman
input wire signed [15:0] n, //滤波器阶数 注意,HP/BS的阶数应为偶数,奇数阶的系数不可靠
input wire signed [15:0] i, //0~n,共n+1个值output wire busy, //指示模块是否计算完成
output wire signed [15:0] firwin
);reg signed [31:0] firwin_buf = 32'sd256;
reg signed [15:0] firwin_buf_d0 = 16'sd256; //8-8有符号定点数
reg busy_buf = 1'b0;assign firwin = firwin_buf_d0;
assign busy = busy_buf;localparam S_IDLE = 4'h1;
localparam S_CAL = 4'h2;
localparam S_END = 4'h4;reg [3:0] state = S_IDLE;
reg [3:0] next_state;always @(posedge clk) beginif(~rst_n) beginstate <= S_IDLE;endelse beginstate <= next_state;end
endalways @(*) begincase(state)S_IDLE: beginif(en_pe) beginnext_state <= S_CAL;endelse beginnext_state <= S_IDLE;endendS_CAL: beginif(cnt >= 4'd12) begin //最迟在cnt=7可以读取窗函数值并计算firwin,随后本模块可计算firwinnext_state <= S_END;endelse beginnext_state <= S_CAL;endendS_END: beginnext_state <= S_IDLE;enddefault: beginnext_state <= S_IDLE;endendcase
end//firwin_buf
always @(posedge clk) begincase(state)S_CAL: begincase(band_type)2'd0: begin //LPif(cnt == 4'd3) beginif((~n[0]) && (i_buf == n/4'sd2)) begin //偶数阶滤波器,计算最中间的滤波器系数 即洛必达求=0时的值firwin_buf <= (({fln, 16'b0} / fs) * 16'sd804) >>> 8; //3.1415 = 804/256endelse beginfirwin_buf <= sin_val_s / s_mlti2 * 4'sd2;endendelse if(cnt == 4'd7) beginfirwin_buf <= (firwin_buf * win) >>> 16;endelse beginfirwin_buf <= firwin_buf;endend2'd1: begin //HPif(cnt == 4'd3) beginif((~n[0]) && (i_buf == n/4'sd2)) begin //偶数阶滤波器,计算最中间的滤波器系数 即洛必达求=0时的值firwin_buf <= ((32'sd32768 - {fln, 16'b0} / fs) * 16'sd804) >>> 8;endelse beginfirwin_buf <= sin_val_s;endendelse if(cnt == 4'd7) beginif((~n[0]) && (i_buf == n/4'sd2)) beginfirwin_buf <= firwin_buf;endelse beginfirwin_buf <= (firwin_buf - sin_val_s) / s_mlti2 * 4'sd2;endendelse if(cnt == 4'd8) beginfirwin_buf <= (firwin_buf * win) >>> 16;endelse beginfirwin_buf <= firwin_buf;endend2'd2: begin //BPif(cnt == 4'd3) beginif((~n[0]) && (i_buf == n/4'sd2)) begin //偶数阶滤波器,计算最中间的滤波器系数 即洛必达求=0时的值firwin_buf <= ((({fhn, 16'b0} - {fln, 16'b0}) / fs) * 16'sd804) >>> 8;endelse beginfirwin_buf <= sin_val_s;endendelse if(cnt == 4'd7) beginif((~n[0]) && (i_buf == n/4'sd2)) beginfirwin_buf <= firwin_buf;endelse beginfirwin_buf <= (firwin_buf - sin_val_s) / s_mlti2 * 4'sd2;endendelse if(cnt == 4'd8) beginfirwin_buf <= (firwin_buf * win) >>> 16;endelse beginfirwin_buf <= firwin_buf;endend2'd3: begin //BSif(cnt == 4'd3) beginif((~n[0]) && (i_buf == n/4'sd2)) begin //偶数阶滤波器,计算最中间的滤波器系数 即洛必达求=0时的值firwin_buf <= (({fln, 16'b0} / fs + 32'sd32768 - {fhn, 16'b0} / fs) * 16'sd804) >>> 8;endelse beginfirwin_buf <= sin_val_s;endendelse if(cnt == 4'd7) beginif((~n[0]) && (i_buf == n/4'sd2)) beginfirwin_buf <= firwin_buf;endelse beginfirwin_buf <= firwin_buf + sin_val_s;endendelse if(cnt == 4'd11) beginif((~n[0]) && (i_buf == n/4'sd2)) beginfirwin_buf <= firwin_buf;endelse beginfirwin_buf <= (firwin_buf - sin_val_s) / s_mlti2 * 4'sd2;endendelse if(cnt == 4'd12) beginfirwin_buf <= (firwin_buf * win) >>> 16;endelse beginfirwin_buf <= firwin_buf;endenddefault: beginfirwin_buf <= firwin_buf;endendcaseenddefault: beginfirwin_buf <= firwin_buf;endendcase
end//i_buf
reg signed [15:0] i_buf;
always @(posedge clk) beginif(i > (n >>> 1)) begini_buf <= n - i; //滤波器是对称的,这里处理后利用i_buf计算滤波器系数endelse begini_buf <= i;end
end//sin_phase
reg [31:0] multi_tmp;
always @(posedge clk) begincase(state)S_CAL: beginsin_phase <= multi_tmp[15:0];enddefault: beginsin_phase <= sin_phase;endendcase
endreg signed [15:0] s_mlti2;
always @(*) begins_mlti2 <= n - i_buf * 4'sd2;
endalways @(*) begincase(state)S_CAL: begincase(band_type)2'd0: begin //LPmulti_tmp <= (({fln, 16'b0} / fs) * s_mlti2) >> 1;end2'd1: begin //HPif(cnt <= 4'd3) beginmulti_tmp <= {s_mlti2, 14'b0};endelse beginmulti_tmp <= (({fln, 16'b0} / fs) * s_mlti2) >> 1;endend2'd2: begin //BPif(cnt <= 4'd3) beginmulti_tmp <= (({fhn, 16'b0} / fs) * s_mlti2) >> 1;endelse beginmulti_tmp <= (({fln, 16'b0} / fs) * s_mlti2) >> 1;endend2'd3: begin //BSif(cnt <= 4'd3) beginmulti_tmp <= (({fln, 16'b0} / fs) * s_mlti2) >> 1;endelse if(cnt <= 4'd7) beginmulti_tmp <= {s_mlti2, 14'b0};endelse beginmulti_tmp <= (({fhn, 16'b0} / fs) * s_mlti2) >> 1;endenddefault: beginmulti_tmp <= 32'd0;endendcaseenddefault: beginmulti_tmp <= 32'd0;endendcase
end//窗函数
wire signed [15:0] win;
FIR_windows_generator FIR_windows_generator_inst(.clk (clk),.rst_n (rst_n),.en (en), //上升沿触发窗口计算.win_type (win_type), //窗口类型,1:矩形窗,2:图基窗Tukey,3:三角窗,4:汉宁窗Hann,5:海明窗Hamming,6:布莱克曼窗Blackman.n (n + 1'b1), //窗口长度,=滤波器阶数+1.i (i), //窗口索引值.busy (), //指示模块是否计算完成.win (win)
);//en 边沿检测
wire en_pe;
detect_sig_edge detect_sig_edge_inst(.clk (clk), //工作时钟.sig (en), //待检测信号.sig_pe (en_pe), //信号上升沿.sig_ne (), //下降沿.sig_de () //双边沿
);//cnt 控制计算流程
reg [3:0] cnt = 4'd0;
always @(posedge clk) begincase(state)S_IDLE: begincnt <= 4'd0;endS_CAL: begincnt <= cnt + 1'b1;enddefault: begincnt <= 4'd0;endendcase
end//busy_buf
always @(*) begincase(state)S_IDLE: beginbusy_buf <= 1'b0;enddefault: beginbusy_buf <= 1'b1;endendcase
end//sin_rom
reg [15:0] sin_phase = 16'd0;
wire [15:0] sin_out;
sin_gen sin_gen_inst(.clk (clk),.phase (sin_phase), //相位,0~65535对应[0~2pi).sin_out (sin_out) //0~65535
);wire signed [15:0] sin_val_s;
assign sin_val_s = {~sin_out[15], sin_out[14:0]};//firwin_buf_d0
always @(posedge clk) begincase(state)S_END: beginfirwin_buf_d0 <= firwin_buf[15:0];enddefault: beginfirwin_buf_d0 <= firwin_buf_d0;endendcase
endendmodule
测试
testbench 如下
`timescale 1ns/100psmodule FIR_firwin_generate_tb();reg clk_100M = 1'b1;
always #5 beginclk_100M <= ~clk_100M;
endreg rst_n = 1'b1;reg en; //上升沿触发窗口计算reg [1:0] band_type; //滤波器类型,0:低通LP,1:高通HP,2:带通BP,3:带阻BS
reg [15:0] fs; //采样率,注意fln,fhn均应小于fs/2
reg [15:0] fln; //滤波器下频点,LP,HP,BP,BS均会用到
reg [15:0] fhn; //滤波器上频点,BP,BS用到
reg [3:0] win_type; //窗口类型,1:矩形窗,2:图基窗,3:三角窗,4:汉宁窗,5:海明窗,6:布莱克曼窗
reg [15:0] n; //滤波器阶数
reg [15:0] i; //窗口索引值,0 ~ nwire busy;
wire signed [15:0] firwin;FIR_firwin_generator FIR_firwin_generator_inst(.clk (clk_100M),.rst_n (rst_n),.en (en), //上升沿触发窗口计算.band_type (band_type), //滤波器类型,0:低通LP,1:高通HP,2:带通BP,3:带阻BS.fs (fs), //采样率,注意fln,fhn均应小于fs/2.fln (fln), //滤波器下频点,LP,HP,BP,BS均会用到.fhn (fhn), //滤波器上频点,BP,BS用到.win_type (win_type), //窗函数类型,1:矩形窗,2:图基窗Tukey,3:三角窗,4:汉宁窗Hann,5:海明窗Hamming,6:布莱克曼窗Blackman.n (n), //滤波器阶数.i (i), //0~n,共n+1个值.busy (busy),.firwin (firwin)
);//进行一组FIR_win的计算
task cal_firwin;input [1:0] BAND_TYPE;input [15:0] Fs;input [15:0] Fln;input [15:0] Fhn;input [3:0] WIN_TYPE;input [15:0] N;integer k;beginband_type = BAND_TYPE;fs = Fs;fln = Fln;fhn = Fhn;win_type = WIN_TYPE;n = N;#10;for (k = 0; k <= N; k = k + 1'b1) begini = k;en = 1'b1;wait(busy);#10;en = 1'b0;wait(~busy);#10;endend
endtaskinitial beginrst_n <= 1'b0;en <= 1'b0;band_type <= 2'd0;fs <= 16'd100;fln <= 16'd10;fhn <= 16'd60;win_type <= 4'd1;n <= 16'd64;i <= 16'd0;#100;rst_n <= 1'b1;#100;//可与matlab函数fir1(fir_N, Wn, band_type, win)的结果比对 注意其中的Wn是 [fln/f_ny, fhn/f_ny],其中f_ny=fs/2cal_firwin(2'd0, 16'd1000, 16'd50, 16'd100, 4'd1, 16'd64); //LP,矩形窗#200;cal_firwin(2'd0, 16'd1000, 16'd50, 16'd100, 4'd5, 16'd64); //LP,海明窗#200;cal_firwin(2'd0, 16'd1000, 16'd50, 16'd100, 4'd6, 16'd64); //LP,布莱克曼窗#200;cal_firwin(2'd0, 16'd1000, 16'd50, 16'd100, 4'd6, 16'd63); //LP,布莱克曼窗#200;cal_firwin(2'd1, 16'd1000, 16'd50, 16'd100, 4'd1, 16'd64); //HP,矩形窗#200;cal_firwin(2'd1, 16'd1000, 16'd50, 16'd100, 4'd1, 16'd63); //HP,矩形窗 HP的阶数应为偶数,否则系数不可靠,matlab fir1也是只能生成偶数阶的HP#200;cal_firwin(2'd2, 16'd1000, 16'd50, 16'd100, 4'd1, 16'd64); //BP,矩形窗#200;cal_firwin(2'd2, 16'd1000, 16'd50, 16'd100, 4'd1, 16'd63); //BP,矩形窗#200;cal_firwin(2'd2, 16'd1000, 16'd50, 16'd100, 4'd5, 16'd63); //BP,海明窗#200;cal_firwin(2'd3, 16'd1000, 16'd50, 16'd100, 4'd1, 16'd64); //BS,矩形窗#200;cal_firwin(2'd3, 16'd1000, 16'd50, 16'd100, 4'd1, 16'd63); //BS,矩形窗 BS的阶数也应为偶数#200;cal_firwin(2'd3, 16'd1000, 16'd50, 16'd100, 4'd5, 16'd64); //BS,海明窗#200;#200;$stop;
endendmodule
仿真结果如下
读者可与 Matlab 的 fir1 函数结果进行比对。
相关文章:

verilog实现FIR滤波系数生成(阶数,FIR滤波器类型及窗函数可调)
在以往采用 FPGA 实现的 FIR 滤波功能,滤波器系数是通过 matlab 计算生成,然后作为固定参数导入到 verilog 程序中,这尽管简单,但灵活性不足。在某些需求下(例如捕获任意给定台站信号)需要随时修改滤波器的…...

OSPF的不规则区域
1.远离骨干非骨干区域 2.不连续骨干 解决方案 tunnel ---点到点GRE 在合法与非ABR间建立隧道,然后将其宣告于OSPF协议中; 缺点:1、周期和触发信息对中间穿越区域造成资源占用(当同一条路由来自不同区域,路由器会先…...

大数据新视界 --大数据大厂之 Ibis:独特架构赋能大数据分析高级抽象层
💖💖💖亲爱的朋友们,热烈欢迎你们来到 青云交的博客!能与你们在此邂逅,我满心欢喜,深感无比荣幸。在这个瞬息万变的时代,我们每个人都在苦苦追寻一处能让心灵安然栖息的港湾。而 我的…...

总结TypeScript相关知识
目录 引入认识特点安装使用变量声明类型推导 JS 和 TS 共有类型number类型boolean类型string类型Array类型null和undefined类型object类型symbol类型对象类型函数类型 可选和只读type 和 interface索引签名类型断言非空类型断言类型缩小严格赋值检测现象TS 新增类型字面量类型a…...

pdf怎么编辑修改内容?详细介绍6款pdf编辑器功能
■ pdf怎么编辑修改内容? PDF(Portable Document Format)作为一种广泛使用的文件格式,具有特点包括兼容性强、易于传输、文件安全性高、跨平台性、可读性强、完整性、可搜索性、安全性、可压缩性。 PDF文件本身是不可以直接进行编…...

【Blender Python】4.获取场景对象的几种方式
概述 有时候我们需要获取场景中已经添加或存在的对象。本节就总结在Blender Python中获取场景中对象的一些方法。 通过名称获取 py.data的objects()方法返回一个对象集合,可以使用键名或者下标形式获取具体的对象。 在默认新建的场景中,存在三个对象…...

鸿蒙harmonyos next flutter通信之EventChannel获取ohos系统时间
建立通道 flutter代码: EventChannel eventChannel EventChannel("com.xmg.eventChannel"); ohos代码: //定义eventChannelprivate eventChannel: EventChannel | null null//定义eventSinkprivate eventSink: EventSink | null null//建…...

Python 代码编写规范
本规范旨在为 Python 项目的代码编写提供一致性指南。它遵循 Python 社区的 PEP 8 标准,并结合了通用的编程最佳实践。 1. 编码风格 1.1 缩进 使用 4 个空格 作为缩进,不要使用制表符(Tab)。 def example():if True:print(&quo…...

k8s中pod的管理
一、资源管理 1.概述 说到k8s中的pod,即荚的意思,就不得不先提到k8s中的资源管理,k8s中可以用以下命令查看我们的资源: kubectl api-resources 比如我们现在需要使用k8s开启一个东西,那么k8s通过apiserver去对比etc…...

JavaScript中引用数据类型的浅拷贝
在JavaScript中,数据类型被分为“基本数据类型”和“引用数据类型”两大类。基本数据类型包括数值型、字符型、逻辑型、未定义型(undefined)、空型(null)和ES6新增的Symbol类型,引用数据类型包括数组、对象和函数。 当我们在程序中执行变量赋值操作的时候…...

自闭症寄宿学校陕西:提供综合发展的教育环境
在陕西这片古老而充满希望的土地上,有一所特殊的学校——星贝育园康复中心,它如同一座灯塔,照亮了无数自闭症儿童及其家庭前行的道路。这所全国规模较大的广泛性发育障碍全托寄宿制儿童康复训练机构,不仅以其专业的康复训练和独特…...

JS模块化工具requirejs详解
文章目录 JS模块化工具requirejs详解一、引言二、RequireJS 简介1、什么是 RequireJS2、RequireJS 的优势 三、RequireJS 的使用1、配置 RequireJS1.1、基础配置 2、定义模块3、加载模块 四、总结 JS模块化工具requirejs详解 一、引言 随着前端技术的快速发展,Jav…...

JavaScript中的异步编程:从回调到Promise
在JavaScript中,异步编程是一项至关重要的技能,它允许我们在不阻塞主线程的情况下执行耗时操作,如网络请求、文件读取或定时任务。随着JavaScript的发展,异步编程的模式也在不断演进,从最初的回调函数,到现…...

windows下DockerDesktop命令行方式指定目录安装
windows下DockerDesktop指定目录安装(重新安装) 因为DcokerDesktop占用内存较大, 并且拉去镜像后占用本地空间较多,所以建议安装时就更改默认安装路径和镜像存储路径 这里,展示了从下载到安装的过程: 首先下载DcokerDesktop;找到Docker Desktop Installer.exe 并重命名为 do…...

排查和解决JVM OOM实战
JVM OOM介绍 Java内存区域布局 下面的分析中都是基于JDK 8开始的。关于JMM不过多介绍每个区域的作用。OOM不单只会发生在堆内存,也可能是因为元空间或直接内存泄漏导致OOM,此时在OOM的详细信息中会有不同体现。 Java OOM的类别 java.lang.OutOfMemory…...

【Swift官方文档】7.Swift集合类型
集合类型 使用数组、集合和字典来组织数据。Swift 提供了三种主要的集合类型:数组、集合和字典,用于存储值的集合。数组是有序的值集合。集合是无序的唯一值集合。字典是无序的键值对集合。 Swift 中的数组、集合和字典始终清晰地指明它们可以存储的值…...

QT调用最新的libusb库
一:下载libusb文件 下载最新的库的下载网站:https://libusb.info/ 下载: 解压后目录如下: 二:库文件添加QT中 根据自己的编译器选择库: ①将头文件中添加libusb.h ②源文件中添加libusb-1.0.lib ③添加…...

白嫖EarMaster Pro 7简体中文破解版下载永久激活
EarMaster Pro 7 简体中文破解版功能介绍 俗话说得好,想要成为音乐家,就必须先拥有音乐家的耳朵,相信很多小伙伴都已经具备了一定的音乐素养,或者是说想要进一步得到提升。那我们就必须练好听耳的能力,并且把这种能力…...

使用JavaScript写一个网页端的四则运算器
目录 style(内联样式表部分) body部分 html script 总的代码 网页演示 style(内联样式表部分) <style>body {font-family: Arial, sans-serif;display: flex;justify-content: center;align-items: center;height: 100vh;background-color: #f0f0f0;}.calculator {…...

Linux find命令详解及实用示例
Linux 系统中的 find 命令是一个功能强大的工具,用于在文件系统中搜索文件并执行相应的操作。无论是系统管理员还是普通用户,掌握 find 命令都能极大地提高工作效率。本文将详细介绍 find 命令的用法,并通过多个示例展示其在实际中的应用。 …...

CSS基础-常见属性(二)
6、CSS三大特性 6.1 层叠性 如果样式发生冲突,则按照优先级进行覆盖。 6.2 继承性 元素自动继承其父元素、祖先元素所设置的某些元素,优先继承较近的元素。 6.3 优先级 6.3.1 简单分级 1、内联样式2、ID选择器3、类选择器/属性选择器4、标签名选择器/…...

Spring Boot 2.4.3 + Java 8 升级为 Java 21 + Spring Boot 3.2.0
简简单单 Online zuozuo: 简简单单 Online zuozuo 简简单单 Online zuozuo 简简单单 Online zuozuo 简简单单 Online zuozuo :本心、输入输出、结果 简简单单 Online zuozuo : 文章目录 Spring Boot 2.4.3 + Java 8 升级为 Java 21 + Spring Boot 3.2.0前言更换 Java 21 SD…...

如何利用免费音频剪辑软件制作出精彩音频
现在有许多免费的音频剪辑软件可供选择,它们为广大用户提供了丰富的功能和便捷的操作体验,让音频编辑变得更加轻松和有趣。接下来,让我们一起走进这些免费音频剪辑软件的世界,探索它们的独特魅力和强大功能。 1.福昕音频剪辑 链…...

安宝特分享 | AR技术重塑工业:数字孪生与沉浸式培训的创新应用
在数字化转型的浪潮中,AR(增强现实)技术与工业的结合正在呈现新的趋势和应用延伸。特别是“数字孪生”概念的崛起,为AR技术在工业中提供了独特而创新的切入点。 本文将探索AR如何与数字孪生、沉浸式体验和实用案例相结合…...

专题十_穷举vs暴搜vs深搜vs回溯vs剪枝_二叉树的深度优先搜索_算法专题详细总结
目录 搜索 vs 深度优先遍历 vs 深度优先搜索 vs 宽度优先遍历 vs 宽度优先搜索 vs 暴搜 1.深度优先遍历 vs 深度优先搜索(dfs) 2.宽度优先遍历 vs 宽度优先搜索(bfs) 2.关系图暴力枚举一遍所有的情况 3.拓展搜索问题全排列 决策树 1. 计算布尔⼆叉树的值(medi…...

基于springboot vue3 在线考试系统设计与实现 源码数据库 文档
博主介绍:专注于Java(springboot ssm springcloud等开发框架) vue .net php phython node.js uniapp小程序 等诸多技术领域和毕业项目实战、企业信息化系统建设,从业十五余年开发设计教学工作☆☆☆ 精彩专栏推荐订阅☆☆☆☆…...

什么是 HTTP 请求中的 options 请求?
在 Chrome 开发者工具中的 Network 面板看到的 HTTP 方法 OPTIONS,其实是 HTTP 协议的一部分,用于客户端和服务器之间进行“预检”或“协商”。OPTIONS 请求的作用是让客户端能够获取关于服务器支持的 HTTP 方法和其他跨域资源共享 (CORS) 相关的信息&am…...

[图形学]smallpt代码详解(1)
一、简介 本文介绍了著名的99行代码实现全局光照的光线跟踪代码smallpt。 包括对smallpt的功能介绍、编译运行介绍,和对代码的详细解释。希望能够帮助读者更进一步的理解光线跟踪。 二、smallpt介绍 1.smallpt是什么 smallpt(small Path Tracing) 是一个全局光照…...

Vite多环境配置与打包:
环境变量必须以VITE开头 1.VITE_BASE_API: 在开发环境中设置为 /dev-api,这是一个本地 mock 地址,通常用于模拟后端接口。 2.VITE_ENABLE_ERUDA: 设置为 "true",表示启用调试工具,通常是为了…...

git维护【.gitignore文件】
在工程下添加 .gitignore 文件【git忽略文件】 *.class .idea *.iml *.jar /*/target/...