今天我想分享一个使用Vivado HLS设计FIR低通滤波器的完整过程。作为一名FPGA开发者,我经常需要在项目中实现各种数字信号处理功能,而FIR滤波器是最基础也是最常用的模块之一。传统的手写RTL方式虽然灵活,但开发周期长,调试困难。Vivado HLS的出现彻底改变了这一局面,它允许我们使用C/C++这样的高级语言来描述硬件功能,然后自动转换为Verilog或VHDL代码。
这个项目的主要目标是:
FIR(Finite Impulse Response)滤波器是一种数字滤波器,其特点是:
数学表达式为:
y[n] = Σ h[k]·x[n-k] (k=0 to N-1)
其中h[k]是滤波器系数,N是滤波器阶数。
在设计FIR滤波器时,我们需要明确几个关键参数:
采样频率(fs):100MHz
通带截止频率(fp):20MHz
阻带起始频率(fs):30MHz
过渡带宽:fs-fp=10MHz
常见的FIR滤波器设计方法有:
本项目选择Parks-McClellan算法,因为:
使用Python计算FIR系数非常方便,主要步骤:
python复制import numpy as np
from scipy import signal
import matplotlib.pyplot as plt
# 滤波器参数
order = 15 # 滤波器阶数
fs = 100e6 # 采样频率 (Hz)
f_pass = 20e6 # 通带截止频率 (Hz)
f_stop = 30e6 # 阻带起始频率 (Hz)
A_pass = 0.1 # 通带波纹 (dB)
A_stop = 60 # 阻带衰减 (dB)
# 归一化频率
nyquist = 0.5 * fs
wp = f_pass / nyquist
ws = f_stop / nyquist
# 计算FIR系数(使用 Parks-McClellan 算法)
h = signal.remez(order + 1, [0, wp, ws, 1.0], [1, 0], Hz=1.0)
# 打印系数(量化为16位定点数)
coeffs_q15 = [int(round(c * 32767)) for c in h]
print("FIR系数 (Q15格式):")
for i, c in enumerate(coeffs_q15):
print(f"h[{i}] = {c}, 即 {c/32768:.10f}")
# 绘制频率响应
w, h_freq = signal.freqz(h)
plt.figure()
plt.plot(0.5*fs*w/np.pi, 20*np.log10(np.abs(h_freq)))
plt.title('FIR滤波器频率响应')
plt.xlabel('频率 (Hz)')
plt.ylabel('幅度 (dB)')
plt.grid(True)
plt.axvline(f_pass, color='green') # 通带截止频率
plt.axvline(f_stop, color='red') # 阻带起始频率
plt.show()
运行上述代码后,我们得到16个系数(15阶FIR滤波器需要16个系数):
code复制h[0] = -10, 即 -0.0003051758
h[1] = -22, 即 -0.0006713867
h[2] = -32, 即 -0.0009765625
...
h[15] = -10, 即 -0.0003051758
观察这些系数,可以发现:
从生成的频率响应图中可以验证:
FIR滤波器HLS工程包含以下文件:
在fir.h中定义定点数类型:
cpp复制#ifndef _FIR_H_
#define _FIR_H_
#include "ap_fixed.h"
// 定义数据类型
typedef ap_fixed<16, 1> data_t; // 16位定点数,1位整数,15位小数
typedef ap_fixed<16, 1> coeff_t; // 系数类型
typedef ap_fixed<32, 17> acc_t; // 累加器类型,防止溢出
// 定义滤波器抽头数
#define NUM_TAPS 16
// 函数原型
void fir(data_t *output, data_t input);
#endif
选择定点数的考虑:
fir.c中的主要逻辑:
cpp复制#include "fir.h"
void fir(data_t *output, data_t input) {
// 定义FIR系数(Q15格式)
const coeff_t h[NUM_TAPS] = {
-10, -22, -32, -37, -26, 10, 72, 133,
171, 171, 133, 72, 10, -26, -37, -32, -22, -10
};
// 声明移位寄存器数组
static data_t shift_reg[NUM_TAPS];
// pragma指令,优化循环展开
#pragma HLS ARRAY_PARTITION variable=shift_reg complete dim=1
// 数据移位操作
for(int i = NUM_TAPS - 1; i > 0; i--) {
#pragma HLS UNROLL
shift_reg[i] = shift_reg[i-1];
}
shift_reg[0] = input;
// 执行乘法累加操作
acc_t acc = 0;
for(int i = 0; i < NUM_TAPS; i++) {
#pragma HLS UNROLL
acc += shift_reg[i] * h[i];
}
// 输出结果
*output = acc >> 15; // Q15格式转换
}
关键优化点:
tb_fir.c用于验证滤波器功能:
cpp复制#include "fir.h"
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#define PI 3.14159265358979323846
int main() {
// 测试数据
data_t input[100];
data_t output[100];
// 生成测试信号(混合了5MHz和40MHz的正弦波)
for(int i = 0; i < 100; i++) {
// 5MHz信号(应该通过)
float sig1 = 0.5 * sin(2.0 * PI * 5e6 * i / 100e6);
// 40MHz信号(应该被衰减)
float sig2 = 0.5 * sin(2.0 * PI * 40e6 * i / 100e6);
// 混合信号
input[i] = sig1 + sig2;
}
// 应用FIR滤波器
for(int i = 0; i < 100; i++) {
fir(&output[i], input[i]);
}
// 输出结果到文件
FILE *fp_in = fopen("input_data.txt", "w");
FILE *fp_out = fopen("output_data.txt", "w");
for(int i = 0; i < 100; i++) {
fprintf(fp_in, "%f\n", (float)input[i]);
fprintf(fp_out, "%f\n", (float)output[i]);
}
fclose(fp_in);
fclose(fp_out);
printf("测试完成!数据已输出到input_data.txt和output_data.txt\n");
// 简单验证(检查高频分量是否被衰减)
float sum_input = 0, sum_output = 0;
for(int i = 80; i < 100; i++) {
sum_input += fabs((float)input[i]);
sum_output += fabs((float)output[i]);
}
if(sum_output < sum_input * 0.1) {
printf("验证通过:高频分量被有效衰减\n");
return 0;
} else {
printf("验证失败:高频分量衰减不足\n");
return 1;
}
}
测试信号设计:
使用Python绘制输入输出波形:
python复制import numpy as np
import matplotlib.pyplot as plt
# 读取数据
input_data = np.loadtxt('input_data.txt')
output_data = np.loadtxt('output_data.txt')
# 绘制波形
plt.figure(figsize=(10,6))
plt.plot(input_data, label='Input (5MHz+40MHz)')
plt.plot(output_data, label='Output (Filtered)')
plt.title('FIR滤波器时域响应')
plt.xlabel('Sample Index')
plt.ylabel('Amplitude')
plt.legend()
plt.grid()
plt.show()
预期结果:
关键指标:
优化技巧:
注意事项:
生成的IP核包含:
常见问题排查:
在实际项目中,我总结了以下几点经验:
定点数精度选择:
HLS调试技巧:
性能瓶颈分析:
测试策略:
这个FIR滤波器项目展示了Vivado HLS的强大能力,从算法到硬件的转换过程变得前所未有的高效。通过合理使用HLS指令和优化策略,我们可以在保证性能的前提下大幅缩短开发周期。