基于双口RAM的串行FIR滤波器实现总结
基于双口RAM的串行FIR滤波器实现总结
一、概述
本文总结了基于双口RAM实现串行FIR滤波器的设计方案和HDL具体实现。FIR滤波器串行设计方案的特点是可以节省乘累加器资源,仅需要一个乘累加器,代价是滤波处理速率有限,适用于低采样率的数字滤波。
二、滤波器参数说明
本滤波器是对100KHz+1MHz的双频信号进行低通滤波,滤波器系数抽头系数为17个,通带截止频率为100KHz,采样频率为100M/17=5.882Msps,滤波器的具体参数如下:
滤波器频率响应如下,对1MHz信号衰减大概为-45dB。
考虑滤波器设计方案的通用性和一般性,滤波器抽头数没有采用2的整数次幂(RAM地址递增或递减时可以利用循环寻址),而是采用了奇数的17个抽头。另外,系统工作时钟为100MHz,因此采样频率为100M/17。滤波器抽头系数为16bit,具体如下:
三、总体设计方案总结
基于双口RAM的设计思想是输入的滤波数据周期性的流动是以读写地址的改变而实现的,这里的数据的周期即是滤波器的抽头数。例如x0写入RAM的0号地址,在整个生命周期内该数据都存于0号地址,直到x17写入。
采用双口RAM的串行FIR滤波器硬件结构如下图所示,主要包括控制模块、用于存储滤波数据的双口RAM、用于存储滤波器抽头系数的ROM,以及乘法累加器组成。
以4抽头为例,系统的时序关系如下图所示:
该时序是以读系数地址raddcoe为基础,产生各个信号,因为raddcoe是很有规律的周期变化的。可以发现每次滤波器运算地址以递减方式计数,且读的首地址(见图中箭头)就是当前的写地址,从而使读写地址具有很强的相关性,实现电路的自我纠错,这是FPGA设计的重要思想。
四、HDL代码实现
下面先介绍顶层模块,然后分别总结用于存储滤波数据的双口RAM、用于存储滤波器抽头系数的ROM,乘法累加器和控制模块的调用或HDL代码总结,代码中有详细注释说明。
1、顶层代码实现
//FIR滤波器顶层模块,其中包括fir控制模块、双口RAM滤波读写模块、ROM滤波器系数产生模块、乘累加器,以及最终滤波数据输出及乘累加器清零判断
module fir_top(
input wire sclk,
input wire rstn,
output reg[47:0] dataout//滤波器输出
);
wire[8:0] sin_sig;//产生的100kHz+1MHz的混合信号
wire wen;//双口RAM写使能
wire[4:0] waddr;//双口RAM写地址
wire[4:0] raddrdata;//双口RAM读地址
wire[4:0] raddrcoe;//ROM读地址
wire[7:0] data;//双口RAM读出来的数据
wire[15:0] coe;//ROM读出来的系数
wire[47:0] firout;//乘累加的输出线
wire[47:0] firout_bk;//乘累加器的反馈线
reg multadd_clr2,multadd_clr1,multadd_clr0;//根据时序情况,将wen延时3拍,作为fir经过17次乘累加后的fir输出标识及乘累加器反馈线的清零标识
//产生的100kHz+1MHz的混合信号
design_dds design_dds_inst(
.sclk(sclk),
.rst_n(rstn),
.sin_sig(sin_sig)
);
//产生fir滤波器的控制信号
fir_ctl fir_ctl_inst(
.sclk(sclk),
.rstn(rstn),
.wen(wen),
.waddr(waddr),
.raddrdata(raddrdata),
.raddrcoe(raddrcoe)
);
//双口RAM作为滤波数据的写入与读取
dp_ram_data dp_ram_data_inst (
.clka(sclk), // input wire clka
.ena(1'b1), // input wire ena
.wea(wen), // input wire [0 : 0] wea
.addra(waddr), // input wire [4 : 0] addra
.dina(sin_sig[8:1]), // input wire [7 : 0] dina
.clkb(sclk), // input wire clkb
.enb(1'b1), // input wire enb
.addrb(raddrdata), // input wire [4 : 0] addrb
.doutb(data) // output wire [7 : 0] doutb
);
//ROM产生滤波器系数输出
sp_rom_coe sp_rom_coe_inst (
.clka(sclk), // input wire clka
.ena(1'b1), // input wire ena
.addra(raddrcoe), // input wire [4 : 0] addra
.douta(coe) // output wire [7 : 0] douta
);
//乘累加器
multadd multadd_inst (
.CLK(sclk), // input wire CLK
.CE(1'b1), // input wire CE
.SCLR(~rstn), // input wire SCLR
.A(data), // input wire [7 : 0] A
.B(coe), // input wire [15 : 0] B
.C(), // input wire [23 : 0] C
.PCIN(firout_bk), // input wire [47 : 0] PCIN
.SUBTRACT(1'b0), // input wire SUBTRACT
.P(firout), // output wire [47 : 0] P
.PCOUT() // output wire [47 : 0] PCOUT
);
//根据时序情况,将wen延时3拍,作为fir经过17次乘累加后的fir输出标识及乘累加器反馈线的清零标识
always@(posedge sclk or negedge rstn)
begin
if(rstn==1'b0)
begin
multadd_clr2<=1'b1;
multadd_clr1<=1'b1;
multadd_clr0<=1'b1;
end
else
{multadd_clr2,multadd_clr1,multadd_clr0}<={multadd_clr1,multadd_clr0,wen};
end
assign firout_bk=(multadd_clr2==1'b1)?48'd0:firout;//乘累加器反馈线的清零判断
always@(posedge sclk or negedge rstn)
begin
if(rstn==1'b0)
dataout<=48'd0;
else if(multadd_clr2==1'b1)//乘累加器反馈线的清零判断
dataout<=firout;
end
endmodule
2、双口RAM的IP调用总结
调用xilinx双口RAM的IP时注意:写入端口(Port A)要选择读优先,这样可以读出之前的数据后,再写入新数据,这条非常方便和重要。
3、系数ROM的IP调用
这个没啥特殊说明。
4、乘法累加器的调用
乘法累加器的参数配置如下
各输入输出信号定义如下:
这里我们没有使用C信号,而采用了PCIN作为乘累加器的反馈信号,其乘累加器内部连接关系如下:
5、控制模块的代码实现
module fir_ctl(
input wire sclk,
input wire rstn,
output reg wen,
output wire [4:0] waddr,
output reg [4:0] raddrdata,
output wire[4:0] raddrcoe
);
parameter N=5'd16;
reg[4:0] cnt_r;
reg[4:0] cnt_w;
reg start;
reg[4:0] cnt_start;
//产生起始工作的信号start
always@(posedge sclk or negedge rstn)
begin
if(rstn==1'b0)
cnt_start<=5'd0;
else
cnt_start<=cnt_start+1'b1;
end
always@(posedge sclk or negedge rstn)
begin
if(rstn==1'b0)
start<=1'b0;
else if(cnt_start==5'd28)
start<=1'b1;
end
//产生计数基准信号,即滤波器系数读地址
always@(posedge sclk or negedge rstn)
begin
if(rstn==1'b0)
cnt_r<=5'd0;
else if(cnt_r==N)
cnt_r<=5'd0;
else
cnt_r<=cnt_r+1'b1;
end
assign raddrcoe=cnt_r;
//产生双口RAM写使能信号
always@(posedge sclk or negedge rstn)
begin
if(rstn==1'b0)
wen<=1'b0;
else if(cnt_r==N-1)
wen<=1'b1;
else
wen<=1'b0;
end
//产生双口RAM写地址信号
always@(posedge sclk or negedge rstn)
begin
if(rstn==1'b0)
cnt_w<=5'd0;
else if((cnt_w==N)&&(cnt_r==N-1)&&(start==1'b1))//每产生17个地址后清零
cnt_w<=5'd0;
else if((cnt_r==N-1)&&(start==1'b1))
cnt_w<=cnt_w+1'b1;
end
assign waddr=cnt_w;
//根据时序要求,产生双口RAM读地址信号
always@(posedge sclk or negedge rstn)
begin
if(rstn==1'b0)
raddrdata<=5'd0;
else if(cnt_r==N)
raddrdata<=cnt_w;//读优先的双口RAM,根据时序关系产生读地址初始值为刚写入RAM中的地址
else if(raddrdata==5'd0)//读地址是递减的,减到0后重新赋值为16
raddrdata<=5'd16;
else
raddrdata<=raddrdata-1'b1;
end
endmodule
五、仿真结果
滤波后的仿真效果如下:
时序关系如下: