硬件:ZYNQ7010
软件:MATLAB 2019b、Vivado 2017.4、HLS 2017.4、System Generator 2017.4
1、MATLAB设计低通滤波器
FPGA系统时钟 50MHz,也是采样频率。用 MATLAB 生成 1MHz 和 10MHz 的正弦波叠加的信号,并量化为 14bit 整数。把叠加信号输出到 txt 文件用于 HLS 的仿真。MATLAB 工作空间里的变量用于搭建 System Generator 模型。
N = 1024;
fs = 50e6; %50MHz
ts = 1/fs;
Q = 14;
A = 2;
t = (1:N)*ts;
f1 = 1e6; %1MHz
f2 = 10e6;%10MHz
s1 = A*sin(2*pi*f1*t);
s2 = A*sin(2*pi*f2*t);
s = s1+s2;
s = s./max(abs(s));
s = round(s.*(2^(Q-1)-1)); % quantize
% output for testbench
fid = fopen('.\data.txt','w');
for i = 1:length(s)
fprintf(fid,'%d\n', s(i));
end
fclose(fid);
用 MATLAB 的 fir1 函数设计一个归一化截止频率为 0.2 的 10 阶低通 FIR 滤波器,即截止频率为 5MHz,有 11 个滤波器系数。最后也将滤波器系数量化为 14bit 整数。
Q = 14;
b = fir1(10,0.2);
figure();
freqz(b,1);
b = b./max(abs(b));
b = round(b.*(2^(Q-1)-1)); % quantize
2、HLS编写FIR滤波器代码并优化、仿真
// fir.h
#ifndef _FIR_H_
#define _FIR_H_
#include <ap_int.h>
#define N 11
typedef ap_int<32> coef_t;
typedef ap_int<32> data_t;
typedef ap_int<32> acc_t;
void fir(acc_t *y,data_t x);
#endif
// fir.cpp
#include "fir.h"
void fir(acc_t *y,data_t x)
{
const coef_t c[N] = {0,322,1644,4229,6989,8191,6989,4229,1644,322,0}; //low pass 0.2
static data_t shift_reg[N];
acc_t acc=0;
Shift_Accum_Loop:
for(int i = N - 1;i >= 0;i--)
{
if(i == 0){
acc += x * c[0];
shift_reg[0] = x;
}
else
{
shift_reg[i] = shift_reg[i - 1];
acc += shift_reg[i] * c[i];
}
}
*y = acc;
}
// tb_fir.cpp
#include "fir.h"
#include <fstream>
#include <iostream>
using namespace std;
int main()
{
ifstream fp_strmi("data.txt");
ofstream fp_strmo("..\\..\\..\\..\\fir_matlab\\fir_out.txt");
int val;
acc_t fir_out;
if(!fp_strmi.is_open())
{
cerr << "Error! data.txt is not able to open.\n";
}
if(!fp_strmo.is_open())
{
cerr << "Error! fir_out.txt is not able to open.\n";
}
for(int i=0; i<1024; i++)
{
fp_strmi >> val;
fir(&fir_out, (data_t)val);
fp_strmo << (int)fir_out << "\n";
}
fp_strmi.close();
fp_strmo.close();
return 0;
}
首先编写一个没有经过任何优化的C语言代码,C Synthesis后得到的性能估计,见上图。Shift_Accum_Loop 循环了 11 次,每次循环用时两个时钟周期,这说明了这个循环是顺序执行的,没有充分发挥 FPGA 能够并行计算的特点。fir 函数的执行延时(Latency)是 23 个时钟周期,执行间隔(Interval)也是 23 个时钟这期。进行 C/RTL Cosimulation,输出的波形见下图,波形很奇怪,其实只有 y_V_ap_vld为高电平时的 y_V 数据是正确的,y_V_ap_vld 的相邻两个上升沿之间间隔了 24 个时钟周期(480ns)。ap_read 为高电平时,读入一个叠加信号数据到 x_V,可以看出整个系统的采样频率不是 50MHz,而是 (50/24)MHz。
优化 FIR 滤波器的代码,将滤波器系数和输入信号的数据类型改为 ap_int<14>,shift_reg 指定用寄存器实现,Shift_Accum_Loop 循环中的寄存器移位操作(延时线,TDL)和乘累加(MAC)操作分开写到两个 for 循环里,再将这两个循环展开,Cpp 代码和directive 指令在下面列出。因为 TDL 的循环次数是 10 次,所以 factor 是 10,MAC 循环次数是 11 次, factor 填 11。
// fir.h
#ifndef _FIR_H_
#define _FIR_H_
#include <ap_int.h>
#define N 11
typedef ap_int<14> coef_t;
typedef ap_int<14> data_t;
typedef ap_int<32> acc_t;
void fir(acc_t *y,data_t x);
#endif
// fir.cpp
#include "fir.h"
void fir(acc_t *y,data_t x)
{
const coef_t c[N] = {0,322,1644,4229,6989,8191,6989,4229,1644,322,0}; //low pass 0.2
static data_t shift_reg[N];
acc_t acc=0;
shift_reg[0] = x;
TDL: // time delay line
for(int i = N - 1; i > 0; i--)
{
shift_reg[i] = shift_reg[i - 1];
}
MAC: // multiple accumulate
for(int i = N - 1; i >= 0; i--)
{
acc += shift_reg[i] * c[i];
}
*y = acc;
}
# directive
set_directive_array_partition -type complete -dim 1 "fir" shift_reg
set_directive_unroll -skip_exit_check -factor 10 "fir/TDL"
set_directive_unroll -skip_exit_check -factor 11 "fir/MAC"
set_directive_interface -mode ap_ctrl_none "fir"
C Synthesis后得到的性能估计如下图所示。fir 函数的执行延时(Latency)是 1 个时钟周期,执行间隔(Interval)也是 1 个时钟这期。进行 C/RTL Cosimulation,此时波形好看一点,依然是y_V_ap_vld为高电平时的 y_V 数据是正确的,y_V_ap_vld 的相邻两个上升沿之间间隔了 2 个时钟周期(40ns),整个系统的采样频率是 25MHz,输出的低频正弦信号频率是 500KHz。在后续System Generator 仿真时情况会发生变化,注意看。
3、搭建System Generator模型,导入HLS模块
搭建一个如下图所示的 System Generator 模型,其中 counter 用于产生 ROM 的地址信号,ROM 中存着叠加信号的数据。这些模块都是高电平复位,而我的开发板按键按下去后是低电平,所以在 reset 后加了 not 模块翻转电平。HLS 模块导入了优化后的 fir 代码,并且将模块的端口协议改为了 ap_ctrl_none。
下图给出了 System Generator 的仿真结果。可以看到滤波后的正弦信号不平滑,输出数据是 y_V_ap_vld 高电平时有效,y_V_ap_vld 的相邻两个上升沿之间间隔两个时钟周期,正弦信号的周期是 50 个时钟周期,正好对应 50MHz 时钟频率下的 1MHz。为什么和 HLS 中的 C/RTL Cosimulation 结果不一样呢?因为这里输入的叠加信号是按 50MHz 的采样频率输入到 HLS 模块的,但是 HLS 模块处理一个输入数据需要两个时钟周期,相当于对输入信号又进行了一次下采样,采样频率变成了 25MHz,同时采样点数也减少了,此时滤波器的截止频率为
0.2
×
25
/
2
=
2.5
0.2×25/2=2.5
0.2×25/2=2.5MHz,同样可以滤出 1MHz 的正弦信号。前面 C/RTL Cosimulation 时只是采样频率变小了,但是采样点数没有少,导致输出的正弦信号频率也减小。
把这个模型生成 IP 核,下载到开发板上进行验证。
4、上板验证
创建一个 Vivado 工程,例化 System Generator 模型生成的 IP 核和一个 ila IP 核,写一个寄存器把 fir_out 根据 fir_out_vld 寄存一次,代码如下。
module fir_hls_sysgen_top(
input resetn,
input clk
);
wire [31:0] fir_out;
wire fir_out_vld;
fir_filter_0 fir_filter_inst (
.reset(resetn), // input wire [0 : 0] reset
.clk(clk), // input wire clk
.fir_out(fir_out), // output wire [31 : 0] fir_out
.fir_out_vld(fir_out_vld) // output wire [0 : 0] fir_out_vld
);
reg [31:0] fir_out_reg;
always @(posedge clk or negedge resetn) begin
if(!resetn) begin
fir_out_reg <= 32'd0;
end
else begin
if(fir_out_vld) begin
fir_out_reg <= fir_out;
end
end
end
ila_0 ila0_inst (
.clk(clk), // input wire clk
.probe0(fir_out), // input wire [31:0] probe0
.probe1(fir_out_reg), // input wire [31:0] probe1
.probe2(fir_out_vld) // input wire [0:0] probe2
);
endmodule
ila 抓取的波形如下图所示。可以看到 fir_out_vld 相邻两个上升沿间隔两个时钟周期,滤波输出的正弦信号周期为 50 个时钟周期,fir_out 波形和 simulink 仿真的是一样的,并且 fir_out_reg 的波形更平滑一些。
完整工程下载地址:HLS设计FIR滤波器工程