一、同步FIFO
FIFO表示先进先出的意思。是基于RAM的存储模块,一般多是用于缓冲数据,令模块独立,调用方便、随性。学习FIFO最重要的如何建立先进先出的机制。
图1:先用一个简单的示意图说明:
图1
左边有写入使能Write,然后写入数据 Data_Write,还有写满标志位full。右边则有读使能Read,读出数据Data_Read,还有读空标志位empty。Write必须拉高Data_Write才能写入,一旦FIFO写满,那么full就会拉高;Read必须拉高,数据才能经由Data_Read读出,一旦FIFO读空,empty就会拉高。
难点是如何判断写满和读空:
首先要知道FIFO的运作机制。建立一个4bit,深度为4的RAM,如图2:
图2
Wp表示下一个将要去写的地址,比如现在还没开始,它就会一直停在00的位置等待
Rp表示下一个将要去读的地址,
- 此时图2所示为空,可以推断当Wp==Rp时为空。
empty = ( Wp == Rp );
- 当有数据输入时,如图3
图3
此时可以发现Wp会指向下一个地址,等待数据,而Rp则指向将要读取的数据,Wp和Rp不再相等,整个RAM不满也不空。如图4:
图4
那么,什么时候会满,可以发现在数据写满整个RAM后,Wp会指向下一个地址,即就是在一个循环之后Wp会回到第一个位置,如图5:
图5
此时,就无法得出full时的关系式。根据黑金原创教程(FPGA那些事儿)中提到,我们可以给Wp和Rp加一位,当一个循环后,给最高位加1,如图6
图6
原本Wp和Rp只需2位即可(2^2=4),现在加一位就可以表现出一轮循环后的标志。所以当Wp和Rp需要N位时,我们要加一位高位,即Wp和Rp是N+1位。
full = ( Wp [2] ^ Rp [2] & Wp [1:0] == Rp [1:0] );
所以有full和empty的计算方式:
full = ( Wp [2] ^ Rp [2] & Wp [1:0] == Rp [1:0] );
empty = ( Wp == Rp );
full可以写成:
full = ( Wp [N+1] ^ Rp [N+1] & Wp [N:0] == Rp [N:0] )
2.以深度16,8bit为例的同步FIFO:
`timescale 1 ns/ 1 ps
//
// Company:
// Engineer: ZHANG ZEKUN
//
// Create Date: 2019/08/22 15:01:16
// Design Name:
// Module Name: Synchronize FIFO
// Project Name:
// Target Devices:
// Tool Versions:
// Description:
//
// Dependencies:
//
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
//
//
module on_clk_fifo(
input CLK,
input RSTn,
input write,
input read,
input [7:0] iData,
output [7:0] oData,
output full,
output empty
);
reg [4:0] wp; //write point should add 1 bit(N+1)
reg [4:0] rp; //read point
reg [7:0] RAM [15:0]; //deep16,8 bit RAM
reg [7:0] oData_reg; //regsiter of oData
always @ ( posedge CLK or negedge RSTn )
begin //write to RAM
if (!RSTn)
begin
wp <= 5'b0;
end
else if ( write )
begin
RAM[wp[3:0]] <= iData;
wp <= wp + 1'b1;
end
end
always @ ( posedge CLK or negedge RSTn )
begin // read from RAM
if (!RSTn)
begin
rp <= 5'b0;
oData_reg <= 8'b0;
end
else if ( read )
begin
oData_reg <= RAM[rp[3:0]];
rp <= rp + 1'b1;
end
end
assign full = ( wp[4] ^ rp[4] & wp[3:0] == rp[3:0] );
assign empty = ( wp == rp );
assign oData = oData_reg;
endmodule
简单测试:
module on_clk_fifo_tb();
reg CLK;
reg RSTn;
reg write;
reg read;
reg [7:0] iData;
wire [7:0] oData;
wire full;
wire empty;
initial
begin
CLK = 0;
forever #100 CLK = ~CLK;
end
initial
begin
RSTn = 0;
iData = 0;
#100 RSTn = 1;
end
always @ (posedge CLK or negedge RSTn)
begin
iData <= iData + 1'b1;
end
always @ (posedge CLK or negedge RSTn)
begin
if (!RSTn)
write = 0;
else if (!full)
begin
write = 1;
end
else
write = 0;
end
always @ (posedge CLK or negedge RSTn)
begin
if (!RSTn)
read = 0;
else if (!empty)
read = 1;
else
read = 0;
end
on_clk_fifo fifo (.CLK(CLK),
.RSTn(RSTn),
.write(write),
.read(read),
.iData(iData),
.full(full),
.empty(empty),
.oData(oData));
endmodule
3.仿真结果:
二、异步FIFO
1.实现方法:
异步FIFO简单的说就是读写的时钟不同。可以在两个不同的时钟域之间传输数据,起到跨时钟域处理的作用,多用于处理跨时钟域的问题。
详细内容请移步:https://blog.csdn.net/MaoChuangAn/article/details/88783320
依旧是如何判断满和空的状态:
在上面的同步FIFO中已经说明判断满空的方法,可是那是在同时钟下;显然在不同的时钟域下需要将读、写指针进行同步才能进行判断,下图中
r2w_sync:读指针同步到写时钟域
w2r_sync:写指针同步到读时钟域
各模块结构图:
同步后就可以在同一时钟域下判断空满状态
判满:格雷码的最高位和次高为不同,剩下的都同,就是满。
assign full_reg = ( wgray_next == { -rp2_wpt [ ADDR_WIDTH : ADDR_WIDTH - 1],rp2_wpt [ ADDR_WIDTH - 2:0]});
判空:格雷码完全相同,就是空。
assign empty_reg = ( rgray_next == wp2_rpt );
2.代码实现:
双口RAM
/**Double Port RAM module**/
module double_ram#(
parameter DATA_WIDTH = 8,
parameter ADDR_WIDTH = 4,
parameter RAM_DEPTH = 16
)
(
input WCLK, //write clk
input write, //write en
input [ ADDR_WIDTH - 1:0 ] waddr, //write address from full.v
input [ DATA_WIDTH - 1:0 ] wdata, //write data
input [ ADDR_WIDTH - 1:0 ] raddr, //read address from empty.v
output [ DATA_WIDTH - 1:0 ] rdata //read data
);
reg [ DATA_WIDTH - 1:0 ] RAM [ RAM_DEPTH - 1:0 ]; //double Port ram
always @ ( posedge WCLK )
begin
if ( write == 1'b1 )
begin
RAM [ waddr ] <= wdata;
end
else
begin
RAM [ waddr ] <= RAM [ waddr ];
end
end
assign rdata = RAM [ raddr ];
endmodule
写指针同步到读时钟域
/**Read to Write Sync module**/
module r2w_sync#(
parameter ADDR_WIDTH = 4
)
(
input WRSTn, //write RSTn
input WCLK, //write CLK
input [ ADDR_WIDTH : 0 ] rpt, //output to write port gray
output reg [ ADDR_WIDTH : 0 ] rp2_wpt //D trigger sync with two levels,second level
);
reg [ ADDR_WIDTH : 0 ] rp1_wpt; //frist level
always @ ( posedge WCLK or negedge WRSTn )
begin
if ( !WRSTn )
begin
{ rp2_wpt,rp1_wpt } <= 0;
end
else
begin
{ rp2_wpt,rp1_wpt } <= { rp1_wpt,rpt };
end
end
endmodule
读指针同步到写时钟域
/**Write to Read Sync module**/
module w2r_sync#(
parameter ADDR_WIDTH = 4
)
(
input RRSTn, //read RSTn
input RCLK, //reaf CLK
input [ ADDR_WIDTH : 0 ] wpt, //output to read port gray
output reg [ ADDR_WIDTH : 0 ] wp2_rpt //D trigger sync with two levels,second level
);
reg [ ADDR_WIDTH : 0 ] wp1_rpt; //frist level
always @ ( posedge RCLK or negedge RRSTn )
begin
if ( !RRSTn )
begin
{ wp2_rpt,wp1_rpt } <= 0;
end
else
begin
{ wp2_rpt,wp1_rpt } <= { wp1_rpt,wpt};
end
end
endmodule
写满模块
module full#(
parameter ADDR_WIDTH = 4
)
(
input WRSTn,
input WCLK,
input write,
input [ ADDR_WIDTH : 0 ] rp2_wpt,
output reg [ ADDR_WIDTH : 0 ] wpt,
output [ ADDR_WIDTH - 1:0 ] waddr,
output reg full
);
reg [ ADDR_WIDTH :0 ] wbin;
wire [ ADDR_WIDTH :0 ] wbin_next;
wire [ ADDR_WIDTH :0 ] wgray_next;
wire full_reg;
always @ ( posedge WCLK or negedge WRSTn )
begin
if (!WRSTn)
begin
wpt <= 0;
wbin <= 0;
end
else
begin
wpt <= wgray_next;
wbin <= wbin_next;
end
end
assign wbin_next = ( !full ) ? ( wbin + write ) : wbin;
assign wgray_next = ( wbin_next >> 1 ) ^ wbin_next;
assign waddr = wbin [ ADDR_WIDTH - 1:0 ];
always @ ( posedge WCLK or negedge WRSTn )
begin
if (!WRSTn)
begin
full <= 0;
end
else
begin
full <= full_reg;
end
end
assign full_reg = ( wgray_next == { -rp2_wpt [ ADDR_WIDTH : ADDR_WIDTH - 1],rp2_wpt [ ADDR_WIDTH - 2:0]});
endmodule
读空模块
module empty#(
parameter ADDR_WIDTH = 4
)
(
input RRSTn,
input RCLK,
input read,
input [ ADDR_WIDTH : 0 ] wp2_rpt,
output reg [ ADDR_WIDTH : 0 ] rpt,
output reg [ ADDR_WIDTH - 1:0 ] raddr,
output reg empty
);
reg [ ADDR_WIDTH :0 ] rbin;
wire [ ADDR_WIDTH :0 ] rbin_next;
wire [ ADDR_WIDTH :0 ] rgray_next;
wire empty_reg;
always @ ( posedge RCLK or negedge RRSTn )
begin
if (!RRSTn)
begin
rpt <= 0;
rbin <= 0;
end
else
begin
rpt <= rgray_next;
rbin <= rbin_next;
end
end
assign rbin_next = ( !empty ) ? ( rbin + read ) : rbin;
assign rgray_next = ( rbin_next >> 1 ) ^ rbin_next;
always @ ( posedge RCLK or negedge RRSTn )
begin
if (read)
begin
raddr <= rbin [ ADDR_WIDTH - 1:0 ];
end
end
always @ ( posedge RCLK or negedge RRSTn )
begin
if (!RRSTn)
begin
empty <= 0;
end
else
begin
empty <= empty_reg;
end
end
assign empty_reg = ( rgray_next == wp2_rpt );
endmodule
顶层模块
`timescale 1 ns/ 1 ps
//
// Company:
// Engineer: ZHANG ZEKUN
//
// Create Date: 2019/08/30 15:01:16
// Design Name:
// Module Name: top
// Project Name: Asynchronous FIFO
// Target Devices:
// Tool Versions:
// Description:
//
// Dependencies:
//
// Revision:
// Revision 1.0 - 8.27 File Created
// Revision 2.0 - 8.29 File Changed
// Revision 3.0 - 8.30 Finish over
// Additional Comments:
//
//
module asyn_fifo_top#(
parameter DATA_WIDTH = 8,
parameter ADDR_WIDTH = 4,
parameter RAM_DEPTH = 16
)
(
input WCLK,
input WRSTn,
input RCLK,
input RRSTn,
input write,
input read,
input [ DATA_WIDTH - 1 : 0 ] wdata,
output [ DATA_WIDTH - 1 : 0 ] rdata,
output full,
output empty
);
wire [ ADDR_WIDTH : 0 ] wpt;
wire [ ADDR_WIDTH : 0 ] rpt;
wire [ ADDR_WIDTH : 0 ] rp2_wpt;
wire [ ADDR_WIDTH : 0 ] wp2_rpt;
wire [ ADDR_WIDTH - 1: 0 ] waddr;
wire [ ADDR_WIDTH - 1: 0 ] raddr;
double_ram#(.DATA_WIDTH(DATA_WIDTH),
.ADDR_WIDTH(ADDR_WIDTH),
.RAM_DEPTH(RAM_DEPTH))
ram_module (.WCLK(WCLK),
.write(write),
.waddr(waddr),
.wdata(wdata),
.raddr(raddr),
.rdata(rdata));
r2w_sync #(.ADDR_WIDTH(ADDR_WIDTH))
r2w_module (.WCLK(WCLK),
.WRSTn(WRSTn),
.rpt(rpt),
.rp2_wpt(rp2_wpt));
w2r_sync #(.ADDR_WIDTH(ADDR_WIDTH))
w2r_module (.RCLK(RCLK),
.RRSTn(RRSTn),
.wpt(wpt),
.wp2_rpt(wp2_rpt));
full#(.ADDR_WIDTH(ADDR_WIDTH))
full_module(.WCLK(WCLK),
.WRSTn(WRSTn),
.write(write),
.rp2_wpt(rp2_wpt),
.wpt(wpt),
.waddr(waddr),
.full(full));
empty #(.ADDR_WIDTH(ADDR_WIDTH))
empty_module(.RCLK(RCLK),
.RRSTn(RRSTn),
.read(read),
.wp2_rpt(wp2_rpt),
.rpt(rpt),
.raddr(raddr),
.empty(empty));
endmodule
testbench:
module asyn_fifo_tb();
parameter DATA_WIDTH = 8;
reg WCLK;
reg WRSTn;
reg RCLK;
reg RRSTn;
reg write;
reg read;
reg [ DATA_WIDTH - 1 : 0 ] wdata;
wire [ DATA_WIDTH - 1 : 0 ] rdata;
wire full;
wire empty;
initial
begin
WCLK <= 0;
forever #100 WCLK = ~WCLK;
end
initial
begin
RCLK <= 0;
forever #200 RCLK = ~RCLK;
end
initial
begin
WRSTn = 0;
wdata = 0;
#100 WRSTn = 1;
end
initial
begin
RRSTn = 0;
#100 RRSTn = 1;
end
always @ ( posedge WCLK or negedge WRSTn )
begin
wdata <= wdata + 1'b1;
end
always @ ( full or WRSTn )
begin
if (!WRSTn)
begin
write <= 0;
end
else if (!full)
begin
write <= 1;
end
else
begin
write <= 0;
end
end
always @ ( empty or RRSTn )
begin
if (!RRSTn)
begin
read <= 0;
end
else if (!empty)
begin
read <= 1;
end
else
begin
read <= 0;
end
end
asyn_fifo_top asyn_fifo(.WCLK(WCLK),
.WRSTn(WRSTn),
.RCLK(RCLK),
.RRSTn(RRSTn),
.write(write),
.read(read),
.wdata(wdata),
.rdata(rdata),
.full(full),
.empty(empty));
endmodule
3.仿真结果:
感觉不太对劲啊。。。。。
reference:
https://www.cnblogs.com/alinx/p/4223450.html
https://blog.csdn.net/MaoChuangAn/article/details/88783320
越是憧憬,越要风雨兼程!