目录
1.引言
256点FFT设计时的双口RAM模块,其核心功能是实现数据的乒乓缓冲、并行读写、地址逆序重排,解决FFT运算中数据输入、缓存、输出的时序冲突问题。这个模块支持10位位宽的实部DR和虚部DI数据并行存储,内置256深度的双分区RAM。
2.双口RAM的FPGA实现原理
模块将512深度的RAM划分为两个256深度的子分区,通过最高位地址控制分区切换:一个分区用于写入新数据,另一个分区用于读取旧数据,实现读写完全并行,无数据冲突,满足实时信号处理的流水线需求。
其中:
**地址逆序映射原理:**输出地址采用4位比特位逆序(addr[3:0]与addr[7:4]交换),这是FFT算法的核心要求------FFT运算需要输入数据按位逆序排列,模块内置地址逆序逻辑,无需外部电路处理,简化系统设计。
**同步时序控制原理:**所有操作基于时钟上升沿同步,包含复位、启动、使能三级控制,内置地址计数器和就绪计数器,自动完成256点数据的缓存与输出,输出信号标记数据处理完成。
**双配置兼容原理:**子模块RAM2x256C支持单端口RAM和双端口RAM两种硬件实现,兼容不同FPGA器件的RAM资源,提升工程移植性。
3.子模块RAM2x256C的verilog实现
我们首先给出这个模块的整体程序,然后再介绍下其具体的实现过程:
// DESCRIPTION : 2-port RAM
`timescale 1 ns / 1 ps
module RAM2x256C(
CLK,
ED,
WE,
ODD,
ADDRW,
ADDRR,
DR,
DI,
DOR,
DOI);
parameter nb=10;
output [nb-1:0] DOR ;
wire [nb-1:0] DOR ;
output [nb-1:0] DOI ;
wire [nb-1:0] DOI ;
input CLK ;
wire CLK ;
input ED ;
wire ED ;
input WE ; //write enable
wire WE ;
input ODD ; // RAM part switshing
wire ODD ;
input [7:0] ADDRW ;
wire [7:0] ADDRW ;
input [7:0] ADDRR ;
wire [7:0] ADDRR ;
input [nb-1:0] DR ;
wire [nb-1:0] DR ;
input [nb-1:0] DI ;
wire [nb-1:0] DI ;
reg oddd,odd2;
always @( posedge CLK) begin //switch which reswiches the RAM parts
if (ED) begin
oddd<=ODD;
odd2<=oddd;
end
end
`ifdef FFT256bufferports1
//One-port RAMs are used
wire we0,we1;
wire [nb-1:0] dor0,dor1,doi0,doi1;
wire [7:0] addr0,addr1;
assign addr0 =ODD? ADDRW: ADDRR; //MUXA0
assign addr1 = ~ODD? ADDRW:ADDRR; // MUXA1
assign we0 =ODD? WE: 0; // MUXW0:
assign we1 =~ODD? WE: 0; // MUXW1:
//1-st half - write when odd=1 read when odd=0
RAM256 #(nb) URAM0(.CLK(CLK),.ED(ED),.WE(we0), .ADDR(addr0),.DI(DR),.DO(dor0)); //
RAM256 #(nb) URAM1(.CLK(CLK),.ED(ED),.WE(we0), .ADDR(addr0),.DI(DI),.DO(doi0));
//2-d half
RAM256 #(nb) URAM2(.CLK(CLK),.ED(ED),.WE(we1), .ADDR(addr1),.DI(DR),.DO(dor1));//
RAM256 #(nb) URAM3(.CLK(CLK),.ED(ED),.WE(we1), .ADDR(addr1),.DI(DI),.DO(doi1));
assign DOR=~odd2? dor0 : dor1; // MUXDR:
assign DOI=~odd2? doi0 : doi1; // MUXDI:
`else
//Two-port RAM is used
wire [8:0] addrr2 = {ODD,ADDRR};
wire [8:0] addrw2 = {~ODD,ADDRW};
wire [2*nb-1:0] di= {DR,DI} ;
//wire [2*nb-1:0] doi;
reg [2*nb-1:0] doi;
reg [2*nb-1:0] ram [511:0];
reg [8:0] read_addra;
always @(posedge CLK) begin
if (ED)
begin
if (WE)
ram[addrw2] <= di;
read_addra <= addrr2;
doi = ram[read_addra];
end
end
//assign
assign DOR=doi[2*nb-1:nb]; // Real read data
assign DOI=doi[nb-1:0]; // Imaginary read data
`endif
endmodule
上述程序实现的是实际的存储单元,支持双配置模式,实现并行读写、分区切换、数据输出,实现步骤如下:
步骤1:端口与参数定义
输出端口:
RDY:就绪标志,高电平表示256点数据缓存完成,可读取;
DOR:实部数据输出;
DOI:虚部数据输出;
输入端口:
CLK:系统时钟;
RST:高电平同步复位;
ED:数据使能信号(高电平有效,触发读写操作);
START:启动信号(高电平初始化缓存);
DR:实部数据输入;
DI:虚部数据输入。
步骤2:分区信号同步寄存
通过oddd和odd2两个寄存器,对分区信号ODD进行两级同步寄存,保证跨时钟域数据稳定,避免亚稳态,最终odd2用于输出数据选择。
步骤3:双模式硬件实现
模块通过宏定义,支持两种RAM实现方式:
模式1:单端口RAM模式(宏定义开启)
适用于不支持双端口RAM的低端FPGA,用4个256深度单端口RAM实现乒乓缓冲:
地址多路选择:根据ODD信号,将写地址/读地址分配给两个RAM分区;
写使能多路选择:一个分区写入时,另一个分区禁止写入;
数据输出选择:根据同步后的odd2信号,选择对应分区的实部/虚部数据输出。
模式2:双端口RAM模式(默认模式)
适用于主流FPGA,使用1个512深度双端口RAM,硬件资源更高效:
地址拼接:将分区信号与读写地址拼接为9位地址(512深度);
数据拼接:将实部(DR)和虚部(DI)拼接为20位数据并行存储;
同步读写:时钟上升沿触发,写使能有效时写入数据,同时寄存读地址,输出对应数据;
数据拆分:将读取的20位数据拆分为实部(DOR)和虚部(DOI)输出。
4.双口RAM顶层模块的verilog实现
该模块是整个缓存的控制核心,负责地址生成、计数器管理、就绪信号输出、子模块调用,其整体的verilog程序如下:
//FPGA/MATLAB/simulink仿真工作室
//微信公众号:matworld
`timescale 1 ns / 1 ps
module BUFRAM256C ( CLK ,RST ,ED ,START ,DR ,DI ,RDY ,DOR ,DOI );
parameter nb=10;
output RDY ;
reg RDY ;
output [nb-1:0] DOR ;
wire [nb-1:0] DOR ;
output [nb-1:0] DOI ;
wire [nb-1:0] DOI ;
input CLK ;
wire CLK ;
input RST ;
wire RST ;
input ED ;
wire ED ;
input START ;
wire START ;
input [nb-1:0] DR ;
wire [nb-1:0] DR ;
input [nb-1:0] DI ;
wire [nb-1:0] DI ;
wire odd, we;
wire [7:0] addrw,addrr;
reg [8:0] addr;
reg [9:0] ct2; //counter for the RDY signal
always @(posedge CLK) // CTADDR
begin
if (RST) begin
addr<=8'b0000_0000;
ct2<= 9'b10000_0001;
RDY<=1'b0; end
else if (START) begin
addr<=8'b0000_0000;
ct2<= 8'b0000_0000;
RDY<=1'b0;end
else if (ED) begin
RDY<=1'b0;
addr<=addr+1;
if (ct2!=257)
ct2<=ct2+1;
if (ct2==256)
RDY<=1'b1;
end
end
assign addrw= addr[7:0];
assign odd=addr[8]; // signal which switches the 2 parts of the buffer
assign addrr={addr[3 : 0], addr[7 : 4]}; // 16-th inverse output address
assign we = ED;
RAM2x256C #(nb) URAM(.CLK(CLK),.ED(ED),.WE(we),.ODD(odd),
.ADDRW(addrw), .ADDRR(addrr),
.DR(DR),.DI(DI),
.DOR(DOR), .DOI(DOI));
endmodule
上述程序中:
输出端口:
RDY:就绪标志,高电平表示256点数据缓存完成,可读取;
DOR:实部数据输出;
DOI:虚部数据输出;
输入端口:
CLK:系统时钟;
RST:高电平同步复位;
ED:数据使能信号(高电平有效,触发读写操作);
START:启动信号(高电平初始化缓存);
DR:实部数据输入;
DI:虚部数据输入。
上述verilog程序中:
addr[8:0]:9位地址寄存器,低8位为读写地址,最高位为分区切换信号(odd);
ct2[9:0]:10位计数器,用于生成RDY就绪信号;
addrw:写地址(8位);
addrr:读地址(8位,逆序映射);
we:写使能信号,直接映射ED信号。
程序分三种工作状态:
复位状态(RST=1)
地址寄存器addr清零,计数器ct2初始化为 9'b10000_0001,RDY 信号拉低,模块进入初始待机状态。
启动状态(START=1)
地址清零,计数器ct2清零,RDY拉低,模块开始准备接收256点数据。
数据使能状态(ED=1)
RDY拉低,地址自动加1;计数器ct2自增(最大257);
当ct2=256时,RDY拉高,标记256点数据缓存完成。
通过上述过程,在连续输入256个数据后,自动输出就绪信号,通知外部电路读取数据。
然后调用子模块RAM2x256C,将时钟、使能、写使能、分区信号、读写地址、输入输出数据全部传递给子模块,完成数据的实际存储与读取。
整个双口RAM存储模块的RTL结构图如下图所示:
