verilog实现FIR滤波系数生成(阶数,FIR滤波器类型及窗函数可调)

news2024/11/18 19:52:52

  在以往采用 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,...,N1

  • 三角窗

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∣1N12n, n=0,1,...,N1

  • 图基窗 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.50.5cos(k+1),1.0,0.50.5cos(k+1π(Nn1)), 0nk k<nNk2 Nk2<nN1 , where k=10N2

  • 汉宁窗 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.0cos(N12πn)], n=0,1,...,N1

  • 汉明窗 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.540.46cos(N12πn), n=0,1,...,N1

  • 布莱克曼窗 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.420.5cos(N12πn)+0.08cos(N14πn), n=0,1,...,N1

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) begin
	if(~rst_n) begin
		state	<= S_IDLE;
	end
	else begin
		state	<= next_state;
	end
end

always @(*) begin
	case(state)
	S_IDLE: begin
		if(en_pe) begin
			next_state	<= S_CAL;
		end
		else begin
			next_state	<= S_IDLE;
		end
	end
	S_CAL: begin
		case(win_type)
		4'd1: begin		//矩形窗
			next_state	<= S_END;
		end
		4'd2: begin		//图基窗
			if(cnt >= 4'd4) begin
				next_state	<= S_END;
			end
			else begin
				next_state	<= S_CAL;
			end
		end
		4'd3: begin		//三角窗
			if(cnt >= 4'd1) begin
				next_state	<= S_END;
			end
			else begin
				next_state	<= S_CAL;
			end
		end
		4'd4: begin		//汉宁窗
			if(cnt >= 4'd3) begin
				next_state	<= S_END;
			end
			else begin
				next_state	<= S_CAL;
			end
		end
		4'd5: begin		//海明窗
			if(cnt >= 4'd3) begin
				next_state	<= S_END;
			end
			else begin
				next_state	<= S_CAL;
			end
		end
		4'd6: begin		//布莱克曼窗
			if(cnt >= 4'd5) begin
				next_state	<= S_END;
			end
			else begin
				next_state	<= S_CAL;
			end
		end
		// 4'd7: begin		//凯塞窗		这个涉及到循环逼近bessel函数和sqrt计算,FPGA比较麻烦,就先不实现这个窗口类型了
		// 	next_state	<= S_END;
		// end
		default: begin
			next_state	<= S_END;
		end
		endcase
	end
	S_END: begin
		next_state	<= S_IDLE;
	end
	default: begin
		next_state	<= S_IDLE;
	end
	endcase
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) begin
	case(state)
	S_IDLE: begin
		cnt		<= 4'd0;
	end
	S_CAL: begin
		cnt		<= cnt + 1'b1;
	end
	default: begin
		cnt		<= 4'd0;
	end
	endcase
end

//win_buf
reg		signed	[31:0]	multi_tmp	= 32'sd0;
always @(posedge clk) begin
	if(~rst_n) begin
		win_buf		<= 16'sd256;	//1.0
	end
	else case(state)
	S_CAL: begin
		case(win_type)
		4'd1: begin								//矩形窗
			win_buf		<= 16'sd256;
		end
		4'd2: begin								//图基窗
			if(cnt == 4'd4) begin
				if(i <= k) begin
					win_buf		<= (16'sd256 - (cos_val_s >>> 7)) >>> 1;
				end
				else if(i > n - k - 4'd2) begin
					win_buf		<= (16'sd256 - (cos_val_s >>> 7)) >>> 1;
				end
				else begin
					win_buf		<= 16'sd256;
				end
			end
			else begin
				win_buf		<= win_buf;
			end
		end
		4'd3: begin								//三角窗
			if(cnt == 4'd0) begin
				multi_tmp	<= 16'sd512 * i / (n - 1'b1);
			end
			else if(cnt == 4'd1) begin
				win_buf 	<= 16'sd256 - abs(16'sd256 - multi_tmp[15:0]);
			end
			else begin
				win_buf		<= win_buf;
			end
		end
		4'd4: begin								//汉宁窗
			if(cnt == 4'd3) begin
				win_buf		<= 16'sd128 - (cos_val_s >>> 8);	//0.5 * (1.0 - cos(2 * i * pi / (n - 1)));
			end
			else begin
				win_buf		<= win_buf;
			end
		end
		4'd5: begin								//海明窗
			if(cnt == 4'd3) begin
				win_buf		<= 16'sd138 - ((16'sd118 * (cos_val_s >>> 7)) >>> 8);	//0.54 - 0.46 * cos(2 * i * pi / (n - 1));
			end
			else begin
				win_buf		<= win_buf;
			end
		end
		4'd6: begin								//布莱克曼窗
			if(cnt == 4'd3) begin
				win_buf		<= 16'sd108 - (cos_val_s >>> 8);
			end
			else if(cnt == 4'd5) begin
				win_buf		<= win_buf + ((16'sd82 * (cos_val_s >>> 7)) >>> 10);
			end
			else begin
				win_buf		<= win_buf;
			end
		end
		// 4'd7: begin							//凯塞窗
		// 	win_buf		<= 16'sd0;
		// end
		default: begin
			win_buf		<= 16'sd256;
		end
		endcase
	end
	default: begin
		win_buf		<= win_buf;
	end
	endcase
end

//busy_buf
always @(*) begin
	case(state)
	S_IDLE: begin
		busy_buf	<= 1'b0;
	end
	default: begin
		busy_buf	<= 1'b1;
	end
	endcase
end

//cos_phase
reg		[15:0]	k	= 16'd0;
always @(posedge clk) begin
	case(state)
	S_CAL: begin
		case(win_type)
		4'd1: begin								//矩形窗
			cos_phase	<= 16'd0;
		end
		4'd2: begin								//图基窗
			if(cnt == 4'd0) begin
				k 			<= (n - 2'd2) / 4'd10;
				cos_phase	<= cos_phase;
			end
			else if(cnt == 4'd1) begin
				k			<= k;

				if(i <= k) begin
					cos_phase	<= i * (16'd32768 / (k + 1'b1)) + 16'd16384;
				end
				else if(i > n - k - 2'd2) begin
					cos_phase	<= (n - i - 1'b1) * (16'd32768 / (k + 1'b1)) + 16'd16384;
				end
				else begin
					cos_phase	<= 16'd0;
				end
			end
			else begin
				cos_phase	<= cos_phase;
				k			<= k;
			end
		end
		4'd3: begin								//三角窗
			cos_phase	<= 16'd0;
		end
		4'd4: begin								//汉宁窗
			cos_phase	<= 2'd2 * i * (16'd32768 / (n - 1'b1)) + 16'd16384;
		end
		4'd5: begin								//海明窗
			cos_phase	<= 2'd2 * i * (16'd32768 / (n - 1'b1)) + 16'd16384;
		end
		4'd6: begin								//布莱克曼窗
			if(cnt == 4'd0) begin
				cos_phase	<= 2'd2 * i * (16'd32768 / (n - 1'b1)) + 16'd16384;
			end
			else if(cnt == 4'd2) begin
				cos_phase	<= 4'd4 * i * (16'd32768 / (n - 1'b1)) + 16'd16384;
			end
			else begin
				cos_phase	<= cos_phase;
			end
		end
		// 4'd7: begin							//凯塞窗
		// 	cos_phase	<= 16'd0;
		// end
		default: begin
			cos_phase	<= 16'd0;
		end
		endcase
	end
	default: begin
		cos_phase	<= cos_phase;
	end
	endcase
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) begin
	case(state)
	S_END: begin
		win_buf_d0	<= win_buf;
	end
	default: begin
		win_buf_d0	<= win_buf_d0;
	end
	endcase
end

//------------------func------------------------------
function signed [15:0] abs(input signed [15:0] a);
	begin
		abs = (a >= 16'sd0)? a : -a;
	end
endfunction

endmodule

测试

  testbench 如下

`timescale 1ns/100ps

module FIR_windows_generate_tb();

reg		clk_100M	= 1'b1;
always #5 begin
	clk_100M	<= ~clk_100M;
end

reg				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-1

wire					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;
	begin
		n			= N;
		win_type	= WIN_TYPE;
		#10;

		for (k = 0; k < N; k = k + 1'b1) begin
			i	= k;
			en	= 1'b1;
			wait(busy);
			#10;
			en	= 1'b0;
			wait(~busy);
			#10;
		end
	end
endtask

initial begin
	rst_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;
end

endmodule

  仿真结果如下

在这里插入图片描述

滤波器系数计算

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=n2N, 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=n2N, 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=n2N, 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=n2N, 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) begin
	if(~rst_n) begin
		state	<= S_IDLE;
	end
	else begin
		state	<= next_state;
	end
end

always @(*) begin
	case(state)
	S_IDLE: begin
		if(en_pe) begin
			next_state	<= S_CAL;
		end
		else begin
			next_state	<= S_IDLE;
		end
	end
	S_CAL: begin
		if(cnt >= 4'd12) begin		//最迟在cnt=7可以读取窗函数值并计算firwin,随后本模块可计算firwin
			next_state	<= S_END;
		end
		else begin
			next_state	<= S_CAL;
		end
	end
	S_END: begin
		next_state	<= S_IDLE;
	end
	default: begin
		next_state	<= S_IDLE;
	end
	endcase
end

//firwin_buf
always @(posedge clk) begin
	case(state)
	S_CAL: begin
		case(band_type)
		2'd0: begin			//LP
			if(cnt == 4'd3) begin
				if((~n[0]) && (i_buf == n/4'sd2)) begin	//偶数阶滤波器,计算最中间的滤波器系数   即洛必达求=0时的值
					firwin_buf		<= (({fln, 16'b0} / fs) * 16'sd804) >>> 8;		//3.1415 = 804/256
				end
				else begin
					firwin_buf		<= sin_val_s / s_mlti2 * 4'sd2;
				end
			end
			else if(cnt == 4'd7) begin
				firwin_buf		<= (firwin_buf * win) >>> 16;
			end
			else begin
				firwin_buf		<= firwin_buf;
			end
		end
		2'd1: begin			//HP
			if(cnt == 4'd3) begin
				if((~n[0]) && (i_buf == n/4'sd2)) begin	//偶数阶滤波器,计算最中间的滤波器系数   即洛必达求=0时的值
					firwin_buf		<= ((32'sd32768 - {fln, 16'b0} / fs) * 16'sd804) >>> 8;
				end
				else begin
					firwin_buf		<= sin_val_s;
				end
			end
			else if(cnt == 4'd7) begin
				if((~n[0]) && (i_buf == n/4'sd2)) begin
					firwin_buf		<= firwin_buf;
				end
				else begin
					firwin_buf		<= (firwin_buf - sin_val_s) / s_mlti2 * 4'sd2;
				end
			end
			else if(cnt == 4'd8) begin
				firwin_buf		<= (firwin_buf * win) >>> 16;
			end
			else begin
				firwin_buf		<= firwin_buf;
			end
		end
		2'd2: begin			//BP
			if(cnt == 4'd3) begin
				if((~n[0]) && (i_buf == n/4'sd2)) begin	//偶数阶滤波器,计算最中间的滤波器系数   即洛必达求=0时的值
					firwin_buf		<= ((({fhn, 16'b0} - {fln, 16'b0}) / fs) * 16'sd804) >>> 8;
				end
				else begin
					firwin_buf		<= sin_val_s;
				end
			end
			else if(cnt == 4'd7) begin
				if((~n[0]) && (i_buf == n/4'sd2)) begin
					firwin_buf		<= firwin_buf;
				end
				else begin
					firwin_buf		<= (firwin_buf - sin_val_s) / s_mlti2 * 4'sd2;
				end
			end
			else if(cnt == 4'd8) begin
				firwin_buf		<= (firwin_buf * win) >>> 16;
			end
			else begin
				firwin_buf		<= firwin_buf;
			end
		end
		2'd3: begin			//BS
			if(cnt == 4'd3) begin
				if((~n[0]) && (i_buf == n/4'sd2)) begin	//偶数阶滤波器,计算最中间的滤波器系数   即洛必达求=0时的值
					firwin_buf		<= (({fln, 16'b0} / fs + 32'sd32768 - {fhn, 16'b0} / fs) * 16'sd804) >>> 8;
				end
				else begin
					firwin_buf		<= sin_val_s;
				end
			end
			else if(cnt == 4'd7) begin
				if((~n[0]) && (i_buf == n/4'sd2)) begin
					firwin_buf		<= firwin_buf;
				end
				else begin
					firwin_buf		<= firwin_buf + sin_val_s;
				end
			end
			else if(cnt == 4'd11) begin
				if((~n[0]) && (i_buf == n/4'sd2)) begin
					firwin_buf		<= firwin_buf;
				end
				else begin
					firwin_buf		<= (firwin_buf - sin_val_s) / s_mlti2 * 4'sd2;
				end
			end
			else if(cnt == 4'd12) begin
				firwin_buf		<= (firwin_buf * win) >>> 16;
			end
			else begin
				firwin_buf		<= firwin_buf;
			end
		end
		default: begin
			firwin_buf	<= firwin_buf;
		end
		endcase
	end
	default: begin
		firwin_buf		<= firwin_buf;
	end
	endcase
end

//i_buf
reg		signed	[15:0]	i_buf;
always @(posedge clk) begin
	if(i > (n >>> 1)) begin
		i_buf	<= n - i;	//滤波器是对称的,这里处理后利用i_buf计算滤波器系数
	end
	else begin
		i_buf	<= i;
	end
end

//sin_phase
reg		[31:0]	multi_tmp;
always @(posedge clk) begin
	case(state)
	S_CAL: begin
		sin_phase	<= multi_tmp[15:0];
	end
	default: begin
		sin_phase	<= sin_phase;
	end
	endcase
end

reg		signed	[15:0]	s_mlti2;
always @(*) begin
	s_mlti2	<= n - i_buf * 4'sd2;
end

always @(*) begin
	case(state)
	S_CAL: begin
		case(band_type)
		2'd0: begin			//LP
			multi_tmp	<= (({fln, 16'b0} / fs) * s_mlti2) >> 1;
		end
		2'd1: begin			//HP
			if(cnt <= 4'd3) begin
				multi_tmp	<= {s_mlti2, 14'b0};
			end
			else begin
				multi_tmp	<= (({fln, 16'b0} / fs) * s_mlti2) >> 1;
			end
		end
		2'd2: begin			//BP
			if(cnt <= 4'd3) begin
				multi_tmp	<= (({fhn, 16'b0} / fs) * s_mlti2) >> 1;
			end
			else begin
				multi_tmp	<= (({fln, 16'b0} / fs) * s_mlti2) >> 1;
			end
		end
		2'd3: begin			//BS
			if(cnt <= 4'd3) begin
				multi_tmp	<= (({fln, 16'b0} / fs) * s_mlti2) >> 1;
			end
			else if(cnt <= 4'd7) begin
				multi_tmp	<= {s_mlti2, 14'b0};
			end
			else begin
				multi_tmp	<= (({fhn, 16'b0} / fs) * s_mlti2) >> 1;
			end
		end
		default: begin
			multi_tmp	<= 32'd0;
		end
		endcase
	end
	default: begin
		multi_tmp	<= 32'd0;
	end
	endcase
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) begin
	case(state)
	S_IDLE: begin
		cnt		<= 4'd0;
	end
	S_CAL: begin
		cnt		<= cnt + 1'b1;
	end
	default: begin
		cnt		<= 4'd0;
	end
	endcase
end

//busy_buf
always @(*) begin
	case(state)
	S_IDLE: begin
		busy_buf	<= 1'b0;
	end
	default: begin
		busy_buf	<= 1'b1;
	end
	endcase
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) begin
	case(state)
	S_END: begin
		firwin_buf_d0	<= firwin_buf[15:0];
	end
	default: begin
		firwin_buf_d0	<= firwin_buf_d0;
	end
	endcase
end

endmodule

测试

  testbench 如下

`timescale 1ns/100ps

module FIR_firwin_generate_tb();

reg		clk_100M	= 1'b1;
always #5 begin
	clk_100M	<= ~clk_100M;
end

reg						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 ~ n

wire					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;
	begin
		band_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) begin
			i	= k;
			en	= 1'b1;
			wait(busy);
			#10;
			en	= 1'b0;
			wait(~busy);
			#10;
		end
	end
endtask

initial begin
	rst_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/2
	cal_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;
end

endmodule

  仿真结果如下

在这里插入图片描述

读者可与 Matlab 的 fir1 函数结果进行比对。

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

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

相关文章

产品管理- 互联网产品(5):运营知识与技能

了解运营 1、运营的基础是产品认清受众&#xff0c;切实解决问题、用户需求 2、运营活动贯穿产品的整个生命周期 3、找准用户&#xff0c;建立MVP 4、明确产品的应用场景。用户在何场景下基于何种需求使用产品&#xff1f;务必短流程 5、AARRR模型 6、运营管理流程类似产品管理…

【CAM350】使用总结 <一>{ 光绘Gerber 对齐 }

〇、废话&#xff1a; 由于allegro和CAM350之间参数设置的问题&#xff1b;导致钻孔层和数据交付生产出现数据问题&#xff0c;或者同一个工程前后迭代&#xff0c;需要找出差别。 一、打开CAM350,打开两份光绘文件 二、增加工艺边后&#xff0c;不是很方便的找出区别&#x…

水波荡漾效果+渲染顺序+简单UI绘制

创建场景及布置 创建新场景Main,在Main场景中创建一个plane物体&#xff0c;命名为WaterWavePla,具体数值及层级面板排布如下&#xff1a; 编写脚本 创建一个文件夹&#xff0c;用于存放脚本&#xff0c;命名Scripts,创建一个子文件夹Effect,存放特效相关脚本&#xff0c;创建…

[Linux]:线程(二)

✨✨ 欢迎大家来到贝蒂大讲堂✨✨ &#x1f388;&#x1f388;养成好习惯&#xff0c;先赞后看哦~&#x1f388;&#x1f388; 所属专栏&#xff1a;Linux学习 贝蒂的主页&#xff1a;Betty’s blog 与Windows环境不同&#xff0c;我们在linux环境下需要通过指令进行各操作&…

LQR算法核心思想

本章以倒立摆为解决目的 什么是线性二次型控制器&#xff08;LQR&#xff09; 开环系统 即状态变量的倒数 系统的状态空间矩阵A * 系统状态变量x A状态矩阵&#xff1a;描述系统本身物理特性的一个矩阵&#xff0c;它是由系统本身的机械结构、物理结构决定的&#xff0c;无法…

基于单片机8路数字电压表电压采集系统

**单片机设计介绍&#xff0c;基于单片机8路数字电压表电压采集系统 文章目录 前言概要功能设计设计思路 软件设计效果图 程序设计程序文章目录 前言 &#x1f497;博主介绍&#xff1a;✌全网粉丝10W,CSDN特邀作者、博客专家、CSDN新星计划导师&#xff0c;一名热衷于单片机技…

【C#】BotSharp:开源机器学习平台

随着人工智能&#xff08;AI&#xff09;和自然语言处理&#xff08;NLP&#xff09;技术的迅速发展&#xff0c;聊天机器人已经成为现代应用和服务的重要组成部分。无论是智能客服、虚拟助手&#xff0c;还是业务自动化和数据分析&#xff0c;智能对话系统正在各个领域发挥重要…

【2024.9.29练习】R 格式

题目描述 题目分析 带小数点的高精度乘法。小数点在计算时忽略&#xff0c;只需在最终打印字符串的时候在合适位置四舍五入即可。对于&#xff0c;可理解为对d乘2总共n次。因此使用“单精度高精度”类型的算法足矣。 我的代码 一开始代码有错误&#xff0c;我只想到了对小数点…

GAMES101(作业8)

作业8 题目&#xff1a; 模拟绳子动画&#xff0c;包括基于物理的&#xff0c;和非物理的&#xff0c;应该修改的函数是:rope.cpp 中的void Rope::simulateEuler(... Rope::rope(...)&#xff0c;&#xff0c;void Rope::simulateVerlet(...) 代码框架&#xff1a; main:负…

9.26-9.29学习

一.项目结构的建立 5个微服务模块 新建好各个模块后&#xff0c;在项目pom下引入各模块。各pom文件指定springboot版本2.1.8.RELEASE .gitignore #表示任意路径下的xx文件 **/mvnw **/mvnw.cmd**/.mvn **/target/.idea**/.gitignore 二.数据库初始化 一个微服务模块对应一个数…

微信小程序 蓝牙通讯

客户的需求如下&#xff1a;通过微信小程序控制蓝牙ble设备(电子面膜)&#xff0c;通过不同指令控制面膜的亮度和时间。 01.首先看下客户的ble设备服务文档&#xff1a;(本部分需要有点蓝牙基础,在调试过程中可以用安卓软件nRF Connect软件来执行测试命令) 0xFFF1灯控命令 命…

PCL 法线空间采样

目录 一、概述 1.1原理 1.2实现步骤 1.3应用场景 二、代码实现 2.1关键函数 2.1.1 法线计算 2.1.2 基于法线进行采样 2.1.3 可视化原始点云和采样后的点云 2.2完整代码 三、实现效果 PCL点云算法汇总及实战案例汇总的目录地址链接&#xff1a; PCL点云算法与项目实…

英伟达的AI一键生成数字人物理运动动画框架:统一控制模式,提升交互性和沉浸感

在虚拟现实(VR)、增强现实(AR)和3D内容创作领域,创建具有真实感和动态性的虚拟角色一直是技术上的挑战。最近,英伟达推出了一种新的框架,通过将物理驱动的角色控制视为运动修复问题,实现了跨场景的虚拟角色控制。这一创新方法不仅支持多种控制模式,还能够生成连贯且自…

container_of 函数的分析

这个函数的目的是&#xff0c; 通过结构体里面的内容 找到 大结构体的 基地址。 函数的原型是&#xff1a;  &#xff30;&#xff34;&#xff32;是指针 &#xff54;&#xff59;&#xff50;&#xff45; &#xff0c; &#xff4d;&#xff45;&#xff4d;&#xff…

PCL 快速均匀下采样

目录 一、概述 1.1原理 1.2实现步骤 1.3应用场景 二、代码实现 2.1关键函数 2.1.1 快速均匀下采样 2.1.2 可视化原始点云和下采样后的点云 2.2完整代码 三、实现效果 PCL点云算法汇总及实战案例汇总的目录地址链接&#xff1a; PCL点云算法与项目实战案例汇总&#…

恋爱辅助应用小程序app开发之广告策略

恋爱话术小程序带流量主广告开启&#xff0c;是一个有效的盈利模式&#xff0c;可以增加小程序的收入来源。以下是对此的详细分析 一、流量主广告的定义与优势 流量主广告是指在小程序中嵌入广告位&#xff0c;通过展示广告内容来获取广告主的付费。对于恋爱话术小程序而言&am…

图解C#高级教程(一):委托

什么是委托 可以认为委托是持有一个或多个方法的对象。但它与对象不同&#xff0c;因为委托可以被执行。当执行委托时&#xff0c;委托会执行它所“持有”的方法。先看一个完整的使用示例。 // See https://aka.ms/new-console-template for more informationdelegate void M…

无人机避障—— 激光雷达定高北醒TF03-UART(二)

无人机避障过程&#xff0c;光靠大疆飞控内部的气压计不准&#xff0c;很容易在高度较低的时候受到地面植被等障碍物影响&#xff0c;使得掉高严重&#xff0c;因此采用激光雷达定高模块进行定高。 硬件&#xff1a; 北醒TF03-UART、Xavier-NX 软件代码&#xff1a; 北醒官…

关于没有启用root问题,分区表挂载错误,导致系统无法启动

方法一、root没有登陆过&#xff0c;改root密码 1、为啥这样设置&#xff0c;root 2、密码破解也无效 2.1、开机启动&#xff0c;按 e 进入启动文件界面 2.2、把ro修改为rw&#xff0c;注意r和o之间包了个反斜杠 2.3、ctrl x退出当前模式 2.4、rw initsysroot/bin/sh 2.5、c…

HarmonyOs 查看官方文档使用弹窗

1. 学会查看官方文档 HarmonyOS跟上网上的视频学习一段时间后&#xff0c;基本也就入门了&#xff0c;但是有一些操作网上没有找到合适教学的视频&#xff0c;这时&#xff0c;大家就需要养成参考官方文档的习惯了&#xff0c;因为官方的开发文档是我们学习深度任何一门语言或…