FPGA 的数字信号处理:Verilog 实现简单的 FIR 滤波器

该项目介绍了如何使用 Verilog 实现具有预生成系数的简单 FIR 滤波器。

FPGA 的数字信号处理:Verilog 实现简单的 FIR 滤波器 - 第1张

绪论

不起眼的 FIR 滤波器是 FPGA 数字信号处理中最基本的模块之一,因此了解如何将具有给定抽头数及其相应系数值的基本模块组合在一起非常重要。因此,在这个关于 FPGA 上 DSP 基础实用入门的教程中,将从一个简单的 15 抽头低通滤波器 FIR 开始,在 Matlab 中为其生成初始系数值,然后转换这些值用于编写 Verilog 模块。

有限脉冲响应或 FIR 滤波器定义为脉冲响应在特定时间段内稳定为零值的滤波器。脉冲响应稳定到零所花费的时间与滤波器阶数(抽头数)直接相关,滤波器阶数是 FIR 的基础传递函数多项式的阶数。FIR 的传递函数不包含反馈,因此如果输入一个值为 1 的脉冲,然后输入一串零值,输出将只是滤波器的系数值。

滤波器的作用基本都是用于信号调节,主要集中在选择滤除或允许通过哪些频率。最简单的例子之一是低通滤波器,它允许低于某个阈值(截止频率)的频率通过,同时大大衰减高于该阈值的频率,如下图所示。

FPGA 的数字信号处理:Verilog 实现简单的 FIR 滤波器 - 第2张

该项目的主要重点是在 HDL(具体为 Verilog)中实现 FIR,它可以分解为三个主要逻辑组件:一个循环缓冲器,用于将每个样本计时到适当地考虑了串行输入的延迟、每个抽头系数值的乘法器以及每个抽头输出的求和结果的累加器。

FPGA 的数字信号处理:Verilog 实现简单的 FIR 滤波器 - 第3张

由于本项目专注于 FPGA 逻辑中 FIR 的设计机制,所以只是使用 Simulink 中的 FDA 工具和 Matlab 为低通滤波器插入一些简单参数,然后使用生成的系数值放到 Verilog 模块中完成滤波器的设计(在后面的步骤中完成)。

FPGA 的数字信号处理:Verilog 实现简单的 FIR 滤波器 - 第4张

选择实现一个简单的 15 抽头低通滤波器 FIR,采样率为 1Ms/s,通带频率为 200kHz,阻带频率为 355kHz,得到以下系数:

  1. -0.0265
  2. 0
  3. 0.0441
  4. 0
  5. -0.0934
  6. 0
  7. 0.3139
  8. 0.5000
  9. 0.3139
  10. 0
  11. -0.0934
  12. 0
  13. 0.0441
  14. 0
  15. -0.0265

为 FIR 模块创建设计文件

在 Vivado 项目中添加源文件。

FPGA 的数字信号处理:Verilog 实现简单的 FIR 滤波器 - 第5张

在确定 FIR 的顺序(抽头数)并获得系数值后,接下来需要定义的下一组参数就是输入样本、输出样本和系数本身的位宽。

对于这个 FIR,选择将输入样本和系数寄存器设置为 16 位宽,并将输出样本寄存器设置为 32 位,因为两个 16 位值的乘积是一个 32 位值(两个值的宽度相乘得到乘积的宽度,所以如果选择了 8 位抽头的 16 位输入样本,那么输出样本将为 24 位宽)。

这些值也都是带符号的,因此 MSB 用作符号位,在选择输入样本寄存器的初始宽度时一定要记住这一点。要在 Verilog 中将这些值设置为有符号数据类型,使用关键字signed :

  1. reg signed [15:0] register_name;

接下来要解决的是如何在 Verilog 中处理系数值,小数点值需要转换为定点值。由于所有系数值都小于 1,因此寄存器的所有 15 位(总共 16 位,MSB 是有符号位)都可以用于小数位。通常,必须决定要将寄存器中的多少位用于数字的整数部分与数字的小数部分。因此,转换分数值抽头的数学是:(fractional coefficient value)*(2^(15))该乘积的小数值被四舍五入,并且如果系数为负,则计算该值的二进制补码:

  1. tap0 = twos(-0.0265 * 32768) = 0xFC9C
  2. tap1 = 0
  3. tap2 = 0.0441 * 32768 = 1445.0688 = 1445 = 0x05A5
  4. tap3 = 0
  5. tap4 = twos(-0.0934 * 32768) = 0xF40C
  6. tap5 = 0
  7. tap6 = 0.3139 * 32768 = 10285.8752 = 10285 = 0x282D
  8. tap7 = 0.5000 * 32768 = 16384 = 0x4000
  9. tap8 = 0.3139 * 32768 = 10285.8752 = 10285 = 0x282D
  10. tap9 = 0
  11. tap10 = twos(-0.0934 * 32768) = 0xF40C
  12. tap11 = 0
  13. tap12 = 0.0441 * 32768 = 1445.0688 = 1445 = 0x05A5
  14. tap13 = 0
  15. tap14 = twos(-0.0265 * 32768) = 0xFC9C

现在我们终于准备好关注 FIR 模块的逻辑,第一个是循环缓冲区,它引入串行输入样本流并为滤波器的 15 个抽头创建一个包含 15 个输入样本的数组。

  1. always @ (posedge clk)
  2. begin
  3. if(enable_buff == 1'b1)
  4. begin
  5. buff0 <= in_sample;
  6. buff1 <= buff0;
  7. buff2 <= buff1;
  8. buff3 <= buff2;
  9. buff4 <= buff3;
  10. buff5 <= buff4;
  11. buff6 <= buff5;
  12. buff7 <= buff6;
  13. buff8 <= buff7;
  14. buff9 <= buff8;
  15. buff10 <= buff9;
  16. buff11 <= buff10;
  17. buff12 <= buff11;
  18. buff13 <= buff12;
  19. buff14 <= buff13;
  20. end
  21. end

接下来,乘法阶段将每个样本乘以每个系数值:

  1. /* Multiply stage of FIR */
  2. always @ (posedge clk)
  3. begin
  4. if (enable_fir == 1'b1)
  5. begin
  6. acc0 <= tap0 * buff0;
  7. acc1 <= tap1 * buff1;
  8. acc2 <= tap2 * buff2;
  9. acc3 <= tap3 * buff3;
  10. acc4 <= tap4 * buff4;
  11. acc5 <= tap5 * buff5;
  12. acc6 <= tap6 * buff6;
  13. acc7 <= tap7 * buff7;
  14. acc8 <= tap8 * buff8;
  15. acc9 <= tap9 * buff9;
  16. acc10 <= tap10 * buff10;
  17. acc11 <= tap11 * buff11;
  18. acc12 <= tap12 * buff12;
  19. acc13 <= tap13 * buff13;
  20. acc14 <= tap14 * buff14;
  21. end
  22. end

乘法阶段的结果值通过加法累加到寄存器中,最终成为滤波器的输出数据流。

  1. /* Accumulate stage of FIR */
  2. always @ (posedge clk)
  3. begin
  4. if (enable_fir == 1'b1)
  5. begin
  6. m_axis_fir_tdata <= acc0 + acc1 + acc2 + acc3 + acc4 + acc5 + acc6 + acc7 + acc8 + acc9 + acc10 + acc11 + acc12 + acc13 + acc14;
  7. end
  8. end

最后,逻辑的最后一部分是将数据流进和流出 FIR 模块的接口。AXI Stream 接口是最常见的接口之一。关键方面是允许控制上游和下游设备之间的数据流的tready和tvalid信号。这意味着 FIR 模块需要向其下游设备提供tvalid信号以指示其输出是有效数据,并且如果下游设备解除其tready信号,则能够暂停(但仍保留)其输出。FIR 模块还必须能够与其主端接口上的上游设备以同样的方式运行。

FPGA 的数字信号处理:Verilog 实现简单的 FIR 滤波器 - 第6张

以下是 FIR 模块的逻辑设计概述:

FPGA 的数字信号处理:Verilog 实现简单的 FIR 滤波器 - 第7张

请注意tready和tvalid信号如何设置输入循环缓冲器的使能值和 FIR 的乘法级以及数据或系数通过的每个寄存器都被声明为有符号的。

FIR模块Verilog代码:

  1. `timescale 1ns / 1ps
  2. module FIR(
  3. input clk,
  4. input reset,
  5. input signed [15:0] s_axis_fir_tdata,
  6. input [3:0] s_axis_fir_tkeep,
  7. input s_axis_fir_tlast,
  8. input s_axis_fir_tvalid,
  9. input m_axis_fir_tready,
  10. output reg m_axis_fir_tvalid,
  11. output reg s_axis_fir_tready,
  12. output reg m_axis_fir_tlast,
  13. output reg [3:0] m_axis_fir_tkeep,
  14. output reg signed [31:0] m_axis_fir_tdata
  15. );
  16. always @ (posedge clk)
  17. begin
  18. m_axis_fir_tkeep <= 4'hf;
  19. end
  20. always @ (posedge clk)
  21. begin
  22. if (s_axis_fir_tlast == 1'b1)
  23. begin
  24. m_axis_fir_tlast <= 1'b1;
  25. end
  26. else
  27. begin
  28. m_axis_fir_tlast <= 1'b0;
  29. end
  30. end
  31. // 15-tap FIR
  32. reg enable_fir, enable_buff;
  33. reg [3:0] buff_cnt;
  34. reg signed [15:0] in_sample;
  35. reg signed [15:0] buff0, buff1, buff2, buff3, buff4, buff5, buff6, buff7, buff8, buff9, buff10, buff11, buff12, buff13, buff14;
  36. wire signed [15:0] tap0, tap1, tap2, tap3, tap4, tap5, tap6, tap7, tap8, tap9, tap10, tap11, tap12, tap13, tap14;
  37. reg signed [31:0] acc0, acc1, acc2, acc3, acc4, acc5, acc6, acc7, acc8, acc9, acc10, acc11, acc12, acc13, acc14;
  38. /* Taps for LPF running @ 1MSps with a cutoff freq of 400kHz*/
  39. assign tap0 = 16'hFC9C; // twos(-0.0265 * 32768) = 0xFC9C
  40. assign tap1 = 16'h0000; // 0
  41. assign tap2 = 16'h05A5; // 0.0441 * 32768 = 1445.0688 = 1445 = 0x05A5
  42. assign tap3 = 16'h0000; // 0
  43. assign tap4 = 16'hF40C; // twos(-0.0934 * 32768) = 0xF40C
  44. assign tap5 = 16'h0000; // 0
  45. assign tap6 = 16'h282D; // 0.3139 * 32768 = 10285.8752 = 10285 = 0x282D
  46. assign tap7 = 16'h4000; // 0.5000 * 32768 = 16384 = 0x4000
  47. assign tap8 = 16'h282D; // 0.3139 * 32768 = 10285.8752 = 10285 = 0x282D
  48. assign tap9 = 16'h0000; // 0
  49. assign tap10 = 16'hF40C; // twos(-0.0934 * 32768) = 0xF40C
  50. assign tap11 = 16'h0000; // 0
  51. assign tap12 = 16'h05A5; // 0.0441 * 32768 = 1445.0688 = 1445 = 0x05A5
  52. assign tap13 = 16'h0000; // 0
  53. assign tap14 = 16'hFC9C; // twos(-0.0265 * 32768) = 0xFC9C
  54. /* This loop sets the tvalid flag on the output of the FIR high once
  55. * the circular buffer has been filled with input samples for the
  56. * first time after a reset condition. */
  57. always @ (posedge clk or negedge reset)
  58. begin
  59. if (reset == 1'b0) //if (reset == 1'b0 || tvalid_in == 1'b0)
  60. begin
  61. buff_cnt <= 4'd0;
  62. enable_fir <= 1'b0;
  63. in_sample <= 8'd0;
  64. end
  65. else if (m_axis_fir_tready == 1'b0 || s_axis_fir_tvalid == 1'b0)
  66. begin
  67. enable_fir <= 1'b0;
  68. buff_cnt <= 4'd15;
  69. in_sample <= in_sample;
  70. end
  71. else if (buff_cnt == 4'd15)
  72. begin
  73. buff_cnt <= 4'd0;
  74. enable_fir <= 1'b1;
  75. in_sample <= s_axis_fir_tdata;
  76. end
  77. else
  78. begin
  79. buff_cnt <= buff_cnt + 1;
  80. in_sample <= s_axis_fir_tdata;
  81. end
  82. end
  83. always @ (posedge clk)
  84. begin
  85. if(reset == 1'b0 || m_axis_fir_tready == 1'b0 || s_axis_fir_tvalid == 1'b0)
  86. begin
  87. s_axis_fir_tready <= 1'b0;
  88. m_axis_fir_tvalid <= 1'b0;
  89. enable_buff <= 1'b0;
  90. end
  91. else
  92. begin
  93. s_axis_fir_tready <= 1'b1;
  94. m_axis_fir_tvalid <= 1'b1;
  95. enable_buff <= 1'b1;
  96. end
  97. end
  98. /* Circular buffer bring in a serial input sample stream that
  99. * creates an array of 15 input samples for the 15 taps of the filter. */
  100. always @ (posedge clk)
  101. begin
  102. if(enable_buff == 1'b1)
  103. begin
  104. buff0 <= in_sample;
  105. buff1 <= buff0;
  106. buff2 <= buff1;
  107. buff3 <= buff2;
  108. buff4 <= buff3;
  109. buff5 <= buff4;
  110. buff6 <= buff5;
  111. buff7 <= buff6;
  112. buff8 <= buff7;
  113. buff9 <= buff8;
  114. buff10 <= buff9;
  115. buff11 <= buff10;
  116. buff12 <= buff11;
  117. buff13 <= buff12;
  118. buff14 <= buff13;
  119. end
  120. else
  121. begin
  122. buff0 <= buff0;
  123. buff1 <= buff1;
  124. buff2 <= buff2;
  125. buff3 <= buff3;
  126. buff4 <= buff4;
  127. buff5 <= buff5;
  128. buff6 <= buff6;
  129. buff7 <= buff7;
  130. buff8 <= buff8;
  131. buff9 <= buff9;
  132. buff10 <= buff10;
  133. buff11 <= buff11;
  134. buff12 <= buff12;
  135. buff13 <= buff13;
  136. buff14 <= buff14;
  137. end
  138. end
  139. /* Multiply stage of FIR */
  140. always @ (posedge clk)
  141. begin
  142. if (enable_fir == 1'b1)
  143. begin
  144. acc0 <= tap0 * buff0;
  145. acc1 <= tap1 * buff1;
  146. acc2 <= tap2 * buff2;
  147. acc3 <= tap3 * buff3;
  148. acc4 <= tap4 * buff4;
  149. acc5 <= tap5 * buff5;
  150. acc6 <= tap6 * buff6;
  151. acc7 <= tap7 * buff7;
  152. acc8 <= tap8 * buff8;
  153. acc9 <= tap9 * buff9;
  154. acc10 <= tap10 * buff10;
  155. acc11 <= tap11 * buff11;
  156. acc12 <= tap12 * buff12;
  157. acc13 <= tap13 * buff13;
  158. acc14 <= tap14 * buff14;
  159. end
  160. end
  161. /* Accumulate stage of FIR */
  162. always @ (posedge clk)
  163. begin
  164. if (enable_fir == 1'b1)
  165. begin
  166. m_axis_fir_tdata <= acc0 + acc1 + acc2 + acc3 + acc4 + acc5 + acc6 + acc7 + acc8 + acc9 + acc10 + acc11 + acc12 + acc13 + acc14;
  167. end
  168. end
  169. endmodule

创建仿真文件

要测试 FIR 模块,需要创建一个测试平台作为其仿真源:

FPGA 的数字信号处理:Verilog 实现简单的 FIR 滤波器 - 第8张

在 FIR 模块中有两个主要的东西需要测试:滤波器算法和 AXI 流接口。为实现这一点,测试台中创建了一个状态机,它生成一个简单的 200kHz 正弦波,并切换从属端的有效信号和 FIR 接口主控端的tready信号。

FIR 模块的测试平台:

  1. `timescale 1ns / 1ps
  2. module tb_FIR;
  3. reg clk, reset, s_axis_fir_tvalid, m_axis_fir_tready;
  4. reg signed [15:0] s_axis_fir_tdata;
  5. wire m_axis_fir_tvalid;
  6. wire [3:0] m_axis_fir_tkeep;
  7. wire [31:0] m_axis_fir_tdata;
  8. /*
  9. * 100Mhz (10ns) clock
  10. */
  11. always begin
  12. clk = 1; #5;
  13. clk = 0; #5;
  14. end
  15. always begin
  16. reset = 1; #20;
  17. reset = 0; #50;
  18. reset = 1; #1000000;
  19. end
  20. always begin
  21. s_axis_fir_tvalid = 0; #100;
  22. s_axis_fir_tvalid = 1; #1000;
  23. s_axis_fir_tvalid = 0; #50;
  24. s_axis_fir_tvalid = 1; #998920;
  25. end
  26. always begin
  27. m_axis_fir_tready = 1; #1500;
  28. m_axis_fir_tready = 0; #100;
  29. m_axis_fir_tready = 1; #998400;
  30. end
  31. /* Instantiate FIR module to test. */
  32. FIR FIR_i(
  33. .clk(clk),
  34. .reset(reset),
  35. .s_axis_fir_tdata(s_axis_fir_tdata),
  36. .s_axis_fir_tkeep(s_axis_fir_tkeep),
  37. .s_axis_fir_tlast(s_axis_fir_tlast),
  38. .s_axis_fir_tvalid(s_axis_fir_tvalid),
  39. .m_axis_fir_tready(m_axis_fir_tready),
  40. .m_axis_fir_tvalid(m_axis_fir_tvalid),
  41. .s_axis_fir_tready(s_axis_fir_tready),
  42. .m_axis_fir_tlast(m_axis_fir_tlast),
  43. .m_axis_fir_tkeep(m_axis_fir_tkeep),
  44. .m_axis_fir_tdata(m_axis_fir_tdata));
  45. reg [4:0] state_reg;
  46. reg [3:0] cntr;
  47. parameter wvfm_period = 4'd4;
  48. parameter init = 5'd0;
  49. parameter sendSample0 = 5'd1;
  50. parameter sendSample1 = 5'd2;
  51. parameter sendSample2 = 5'd3;
  52. parameter sendSample3 = 5'd4;
  53. parameter sendSample4 = 5'd5;
  54. parameter sendSample5 = 5'd6;
  55. parameter sendSample6 = 5'd7;
  56. parameter sendSample7 = 5'd8;
  57. /* This state machine generates a 200kHz sinusoid. */
  58. always @ (posedge clk or posedge reset)
  59. begin
  60. if (reset == 1'b0)
  61. begin
  62. cntr <= 4'd0;
  63. s_axis_fir_tdata <= 16'd0;
  64. state_reg <= init;
  65. end
  66. else
  67. begin
  68. case (state_reg)
  69. init : //0
  70. begin
  71. cntr <= 4'd0;
  72. s_axis_fir_tdata <= 16'h0000;
  73. state_reg <= sendSample0;
  74. end
  75. sendSample0 : //1
  76. begin
  77. s_axis_fir_tdata <= 16'h0000;
  78. if (cntr == wvfm_period)
  79. begin
  80. cntr <= 4'd0;
  81. state_reg <= sendSample1;
  82. end
  83. else
  84. begin
  85. cntr <= cntr + 1;
  86. state_reg <= sendSample0;
  87. end
  88. end
  89. sendSample1 : //2
  90. begin
  91. s_axis_fir_tdata <= 16'h5A7E;
  92. if (cntr == wvfm_period)
  93. begin
  94. cntr <= 4'd0;
  95. state_reg <= sendSample2;
  96. end
  97. else
  98. begin
  99. cntr <= cntr + 1;
  100. state_reg <= sendSample1;
  101. end
  102. end
  103. sendSample2 : //3
  104. begin
  105. s_axis_fir_tdata <= 16'h7FFF;
  106. if (cntr == wvfm_period)
  107. begin
  108. cntr <= 4'd0;
  109. state_reg <= sendSample3;
  110. end
  111. else
  112. begin
  113. cntr <= cntr + 1;
  114. state_reg <= sendSample2;
  115. end
  116. end
  117. sendSample3 : //4
  118. begin
  119. s_axis_fir_tdata <= 16'h5A7E;
  120. if (cntr == wvfm_period)
  121. begin
  122. cntr <= 4'd0;
  123. state_reg <= sendSample4;
  124. end
  125. else
  126. begin
  127. cntr <= cntr + 1;
  128. state_reg <= sendSample3;
  129. end
  130. end
  131. sendSample4 : //5
  132. begin
  133. s_axis_fir_tdata <= 16'h0000;
  134. if (cntr == wvfm_period)
  135. begin
  136. cntr <= 4'd0;
  137. state_reg <= sendSample5;
  138. end
  139. else
  140. begin
  141. cntr <= cntr + 1;
  142. state_reg <= sendSample4;
  143. end
  144. end
  145. sendSample5 : //6
  146. begin
  147. s_axis_fir_tdata <= 16'hA582;
  148. if (cntr == wvfm_period)
  149. begin
  150. cntr <= 4'd0;
  151. state_reg <= sendSample6;
  152. end
  153. else
  154. begin
  155. cntr <= cntr + 1;
  156. state_reg <= sendSample5;
  157. end
  158. end
  159. sendSample6 : //6
  160. begin
  161. s_axis_fir_tdata <= 16'h8000;
  162. if (cntr == wvfm_period)
  163. begin
  164. cntr <= 4'd0;
  165. state_reg <= sendSample7;
  166. end
  167. else
  168. begin
  169. cntr <= cntr + 1;
  170. state_reg <= sendSample6;
  171. end
  172. end
  173. sendSample7 : //6
  174. begin
  175. s_axis_fir_tdata <= 16'hA582;
  176. if (cntr == wvfm_period)
  177. begin
  178. cntr <= 4'd0;
  179. state_reg <= sendSample0;
  180. end
  181. else
  182. begin
  183. cntr <= cntr + 1;
  184. state_reg <= sendSample7;
  185. end
  186. end
  187. endcase
  188. end
  189. end
  190. endmodule

运行行为仿真

FIR 模块及其测试平台文件就位后,从 Flow Navigator 窗口启动 Vivado 中的仿真器,选择 Run Behavioral Simulation 选项。

FPGA 的数字信号处理:Verilog 实现简单的 FIR 滤波器 - 第9张

如行为仿真所示,FIR 正确过滤信号并正确响应 AXI 流信号。

FPGA 的数字信号处理:Verilog 实现简单的 FIR 滤波器 - 第10张

总结

代码都在上面大家有兴趣可以自行运行,但是大家可能会注意到,这个 FIR 模块在设计上运行综合和实现时时序应该是不能通过的。我们将在下一篇文章中详细介绍如何在无法满足时序要求时重新设计你的设计~

本文原创,作者:FPGA小白,其版权均为FPGA线上课程平台|最全栈的FPGA学习平台|FPGA工程师认证培训所有。
如需转载,请注明出处:https://z.shaonianxue.cn/8764.html

"愿我的文字能带给您一丝美好"

还没有人赞赏,支持一下

评论

A 为本文作者,G 为游客总数:0
加载中…

提交评论

游客,您好,欢迎参与讨论。

我的购物车

购物车为空

优惠券

没有优惠券