是否可以在以采样频率计时的 FPGA 中创建 IIR 滤波器?

电器工程 FPGA 筛选 数字信号处理器 伊尔
2022-01-30 15:14:22

这个问题是关于在具有 DSP 片的 FPGA 中实现 IIR 滤波器,具有非常具体的标准。

假设您正在制作一个没有正向抽头且只有 1 个反向抽头的滤波器,使用以下等式:

y[n]=y[n1]b1+x[n]

(见图)

以赛灵思的 DSP48A1 Slice 为例——大多数硬核 IP DSP Slice 都是类似的。

假设您有模拟数据以每个时钟 1 个样本输入。我想设计一个在采样时钟上同步运行的 IIR 滤波器。

问题在于,为了以最大速率运行 DSP Slice,您不能在同一个周期中相乘和相加。您必须在这些组件之间有一个管道寄存器。

因此,如果每个时钟有 1 个新样本,则每个时钟需要产生 1 个输出。但是,您需要之前的输出 2 个时钟才能在此设计中产生新的时钟。

显而易见的解决方案是以双倍时钟速率处理数据,或者禁用流水线寄存器,以便您可以在同一周期内进行乘法和加法。

不幸的是,如果假设您以全流水线 DSP Slice 的最大时钟速率进行采样,那么这些解决方案都不可能。有没有其他方法来构建这个?

(如果您可以使用任意数量的 DSP 片设计一个以一半采样率运行的 IIR 滤波器,则可以加分)

目标是为 Xilinx Artix FPGA 中的 1 GSPS ADC 运行补偿滤波器。当完全流水线时,他们的 DSP 片可以运行超过 500 MHz。如果每个时钟有 1 个样本的解决方案,我想尝试将解决方案缩放为每个时钟 2 个样本。使用 FIR 滤波器,这一切都非常容易。

单反馈 IIR 滤波器示例

2个回答

我还没有使用 IIR 滤波器,但如果你只需要计算给定的方程

y[n] = y[n-1]*b1 + x[n]

每个 CPU 周期一次,您可以使用流水线。

在一个周期中,您进行乘法运算,在一个周期中,您需要对每个输入样本进行求和。这意味着当以给定的采样率计时时,您的 FPGA 必须能够在一个周期内进行乘法运算!然后你只需要并行地做当前样本的乘法和最后一个样本的乘法结果的总和。这将导致 2 个周期的恒定处理延迟。

好的,我们看一下公式,设计一个流水线:

y[n] = y[n-1]*b1 + x[n]

您的管道代码可能如下所示:

output <= last_output_times_b1 + last_input
last_output_times_b1 <= output * b1;
last_input <= input

请注意,所有三个命令都需要并行执行,因此第二行中的“输出”使用上一个时钟周期的输出!

我对 Verilog 的工作不多,所以这段代码的语法很可能是错误的(例如,缺少输入/输出信号的位宽;乘法的执行语法)。但是,您应该明白:

module IIRFilter( clk, reset, x, b, y );
  input clk, reset, x, b;
  output y;

  reg y, t, t2;
  wire clk, reset, x, b;

  always @ (posedge clk or posedge reset)
  if (reset) begin
    y <= 0;
    t <= 0;
    t2 <= 0;
  end else begin
    y <= t + t2;
    t <= mult(y, b);
    t2 <= x
  end

endmodule

PS:也许一些有经验的 Verilog 程序员可以编辑此代码并删除此注释和代码上方的注释。谢谢!

PPS:如果您的因子“b1”是一个固定常数,您可以通过实施一个仅采用一个标量输入并仅计算“倍 b1”的特殊乘法器来优化设计。

回应:“不幸的是,这实际上等价于 y[n] = y[n-2] * b1 + x[n]。这是因为额外的流水线阶段。” 作为对旧版本答案的评论

是的,这实际上适用于以下旧(错误!!!)版本:

  always @ (posedge clk or posedge reset)
  if (reset) begin
    t <= 0;
  end else begin
    y <= t + x;
    t <= mult(y, b);
  end

我希望现在通过在第二个寄存器中延迟输入值来纠正这个错误:

  always @ (posedge clk or posedge reset)
  if (reset) begin
    y <= 0;
    t <= 0;
    t2 <= 0;
  end else begin
    y <= t + t2;
    t <= mult(y, b);
    t2 <= x
  end

为了确保这次它正常工作,让我们看看前几个周期会发生什么。请注意,前 2 个周期会产生或多或少(已定义)的垃圾,因为没有可用的先前输出值(例如 y[-1] == ??)。寄存器 y 初始化为 0,相当于假设 y[-1] == 0。

第一个周期(n=0):

BEFORE: INPUT (x=x[0], b); REGISTERS (t=0, t2=0, y=0)

y <= t + t2;      == 0
t <= mult(y, b);  == y[-1] * b  = 0
t2 <= x           == x[0]

AFTERWARDS: REGISTERS (t=0, t2=x[0], y=0), OUTPUT: y[0]=0

第二个周期(n=1):

BEFORE: INPUT (x=x[1], b); REGISTERS (t=0, t2=x[0], y=y[0])

y <= t + t2;      ==     0  +  x[0]
t <= mult(y, b);  ==  y[0]  *  b
t2 <= x           ==  x[1]

AFTERWARDS: REGISTERS (t=y[0]*b, t2=x[1], y=x[0]), OUTPUT: y[1]=x[0]

第三周期(n=2):

BEFORE: INPUT (x=x[2], b); REGISTERS (t=y[0]*b, t2=x[1], y=y[1])

y <= t + t2;      ==  y[0]*b +  x[1]
t <= mult(y, b);  ==  y[1]   *  b
t2 <= x           ==  x[2]

AFTERWARDS: REGISTERS (t=y[1]*b, t2=x[2], y=y[0]*b+x[1]), OUTPUT: y[2]=y[0]*b+x[1]

第四周期(n=3):

BEFORE: INPUT (x=x[3], b); REGISTERS (t=y[1]*b, t2=x[2], y=y[2])

y <= t + t2;      ==  y[1]*b +  x[2]
t <= mult(y, b);  ==  y[2]   *  b
t2 <= x           ==  x[3]

AFTERWARDS: REGISTERS (t=y[2]*b, t2=x[3], y=y[1]*b+x[2]), OUTPUT: y[3]=y[1]*b+x[2]

我们可以看到,从 cylce n=2 开始,我们得到以下输出:

y[2]=y[0]*b+x[1]
y[3]=y[1]*b+x[2]

这相当于

y[n]=y[n-2]*b + x[n-1]
y[n]=y[n-1-l]*b1 + x[n-l],  where l = 1
y[n+l]=y[n-1]*b1 + x[n],  where l = 1

如上所述,我们引入了 l=1 个周期的额外滞后。这意味着您的输出 y[n] 延迟了 lag l=1。这意味着输出数据是等效的,但延迟了一个“索引”。更清楚地说:输出数据延迟了 2 个周期,因为需要一个(正常)时钟周期,并且为中间阶段增加了 1 个额外的(滞后 l=1)时钟周期。

这是一个以图形方式描述数据如何流动的草图:

数据流示意图

PS:感谢您仔细查看我的代码。所以我也学到了一些东西!;-) 让我知道此版本是否正确或您是否发现更多问题。

是的,您可以按采样频率计时。

此问题的解决方案是操纵原始表达式,以便可以插入流水线寄存器,同时保持所需的输出序列。

给定:y[n] = y[n-1]*b1 +x[n];

这可以被处理成:y[n] = y[n-2]*b1*b1 +x[n-1]*b1 +x[n]。

为了验证这是相同的序列,请考虑前几个样本 x[0]、x[1]、x[2] 等发生的情况,其中在 x[0] 之前所有 x,y 样本均为零。

对于原始表达式,序列为:

y = x[0],

x[1] +x[0]*b1,

x[2] +x[1]*b1 +x[0]*b1*b1,

x[3] +x[2]*b1 +x[1]*b1*b1 +x[0]*b1*b1*b1, ...

很明显,b1 < 1 是必须的,否则它将无限增长。

现在考虑操纵表达式:

y = x[0],

x[0]*b1 +x[1],

x[0]*b1*b1 +x[1]*b1 +x[2],

x[0]*b1*b1*b1 +x[1]*b1*b1 +x[2]*b1 +x[3], ...

这是相同的顺序。

Xilinx 库原语中的硬件解决方案需要两个级联的 DSP48E。下面的端口和寄存器名称请参考UG193 v3.6中的图1-1。第一个原语是乘以 b1 并稍后添加一个时钟;第二个是乘以 b1*b1 并稍后添加一个时钟。此逻辑有 4 个时钟流水线延迟。

-- DSP48E #1

a_port1 := b1; -- 常数系数,设置 AREG=1

b_port1 := x; -- 设置属性 BREG=1

c_port1 := x; -- 设置 CREG=1

-- DSP48E #1 内部

reg_a1 <= a_port1;

reg_b1 <= b_port1;

reg_c1 <= c_port1;

reg_m1 <= reg_a1 * reg_b1;

reg_p1 <= reg_m1 + reg_c1;-- 第一个 DSP48E 的输出

-- DSP48E #1 结束

-- DSP48E #2

a_port2 := reg_p2; -- 设置属性 AREG=0

                -- this means the output of register reg_p2

                -- directly feeds back to the multiplier

b_port2 := b1*b1; -- 常量,设置 BREG=1

c_port2 := reg_p1; -- 设置 CREG=1

-- DSP48E #2 内部

reg_b2 <= b_port2;

reg_c2 <= c_port2;

reg_m2 <= a_port2 * reg_b2;

reg_p2 <= reg_m2 + reg_c2;

-- DSP48E #2 结束

reg_p1 处的序列:

x[0],

x[1] +x[0]*b1,

x[2] +x[1]*b1,

x[3] +x[2]*b1,

等等。

reg_p2 处的序列是所需的结果。在第二个 DSP48E 内部,寄存器 reg_m2 有一个序列:

x[0]*b1*b1,

x[1]*b1*b1 +x[0]*b1*b1*b1,

x[2]*b1*b1 +x[1]*b1*b1*b1 +x[0]*b1*b1*b1*b1

这个结果很优雅。显然,DSP48E 不会在同一个时钟中进行乘法和加法运算,但这正是差分方程所要求的。操纵差分方程允许我们容忍 DSP48E 中的 M 和 P 寄存器和全速时钟。