HLS+System Generator实现FIR低通滤波器

硬件: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 
#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 
#include 
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;
}

HLS+System Generator实现FIR低通滤波器_第1张图片
  首先编写一个没有经过任何优化的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。

HLS+System Generator实现FIR低通滤波器_第2张图片
  优化 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 
#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 仿真时情况会发生变化,注意看。

HLS+System Generator实现FIR低通滤波器_第3张图片HLS+System Generator实现FIR低通滤波器_第4张图片

3、搭建System Generator模型,导入HLS模块

  搭建一个如下图所示的 System Generator 模型,其中 counter 用于产生 ROM 的地址信号,ROM 中存着叠加信号的数据。这些模块都是高电平复位,而我的开发板按键按下去后是低电平,所以在 reset 后加了 not 模块翻转电平。HLS 模块导入了优化后的 fir 代码,并且将模块的端口协议改为了 ap_ctrl_none。
HLS+System Generator实现FIR低通滤波器_第5张图片
  下图给出了 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 时只是采样频率变小了,但是采样点数没有少,导致输出的正弦信号频率也减小。

HLS+System Generator实现FIR低通滤波器_第6张图片  把这个模型生成 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+System Generator实现FIR低通滤波器_第7张图片完整工程下载地址:HLS设计FIR滤波器工程

你可能感兴趣的:(FPGA,fpga开发)