1、异步FIFO的作用
(1)跨时钟域传输;
(2)数据缓冲;
(3)不同位宽数据传输。
2、异步FIFO的主要模块
(1)写控制逻辑;
(2)读控制逻辑;
(3)二进制转格雷码;
(4)格雷码同步(防止亚稳态,亚稳态产生的原因:信号没有满足目的寄存器的建立和保持时间。亚稳态消失的原因:电路中存在的噪声和能量变化。通过打两拍的方法,第一拍稳定数据,第二拍提取数据。通常由慢时钟域跨到快时钟域。可能产生的问题:如果两个上升沿发生两次变化,则中间数据会丢失,如32-33-34,33会丢失,就是因为这个情况导致almost_empty和empty同时拉低,而不是almost_empty先拉低);
(5)满信号和空信号(与同步后的格雷码比较,即与前两拍的读指针比较,保留两个指针余量,格雷码满信号:最高位和次高位相同,其余位不同,格雷码空信号:全部位相同);
(6)数据存储单元。
3、异步FIFO的拓展模块
(1)格雷码转二进制(同步后的格雷码转二进制,用于将满信号和将空信号);
(2)将满信号和将空信号(二进制将满信号:最高位不同时,其余位相减后小于GAP,最高位相同时,其余位相减后加存储单元深度小于GAP。格雷码将空信号:要么读指针为全1且写指针为全0(就是因为这个情况导致almost_empty和empty同时拉高,而不是almost_empty先拉高),要么全部位相减后小于GAP);
(3)输入输出数据位宽不同(增加EXTENT和SHRINK,当输出数据位宽大于输入数据位宽时,读操作一个时钟执行EXTENT次;当输出数据位宽大于输入数据位宽时,同理)。
代码如下:
module ramdp#(
parameter AWI = 8,
parameter AWO = 6,
parameter DWI = 16,
parameter DWO = 64
)(
input [AWI-1 : 0] wr_addr , //写地址
input wr_clk , //写时钟
input wr_en , //写使能
input wr_rst_n, //写复位
input [DWI-1 : 0] wr_data , //写数据
input [AWO-1 : 0] rd_addr , //读地址
input rd_clk , //读时钟
input rd_rst_n, //读复位
input rd_en , //读使能
output reg [DWO-1 : 0] rd_data //读数据
);
//输出位宽大于输入位宽,求取扩大的倍数及对应的位数
parameter EXTENT = DWO/DWI;
parameter EXTENT_BIT = AWO - AWI > 0 ? AWO - AWI : 'b1;
//输入位宽大于输出位宽,求取缩小的倍数及对应的位数
parameter SHRINK = DWI/DWO;
parameter SHRINK_BIT = AWI - AWO > 0 ? AWI - AWO : 'b1;
genvar i;
generate
if(DWO >= DWI) begin
reg [AWI:0] j;
reg [DWI-1:0] mem [(1<<AWI)-1 : 0];//mem有256个储存单元,每个单元为16bit,
always@(posedge wr_clk or negedge wr_rst_n)
if(wr_rst_n == 1'b0)
for(j=0;j<(1<<AWI);j=j+1)
mem[j] <= {DWI{1'b0}};
else
if(wr_en)
mem[wr_addr] <= wr_data;//一个时钟写1次,每次写16bit
for(i=0;i<EXTENT;i=i+1)
always@(posedge rd_clk or negedge rd_rst_n)
if(rd_rst_n == 1'b0)
rd_data <= {DWO{1'b0}};
else
if(rd_en)
rd_data[DWI*(i+1)-1 : DWI*i] <= mem[rd_addr*EXTENT+i];//一个时钟读4次,每次读16bit
end
else begin
reg [AWO:0] j;
reg [DWO-1:0] mem [(1<<AWO)-1 : 0];//mem有256个储存单元,每个单元为16bit,
for(i=0;i<SHRINK;i=i+1)
always@(posedge wr_clk or negedge wr_rst_n)
if(wr_rst_n == 1'b0)
for(j=0;j<(1<<AWO);j=j+1)
mem[j] <= {DWO{1'b0}};
else
if(wr_en)
mem[wr_addr*SHRINK+i] <= wr_data[DWO*(i+1)-1 : DWO*i];//一个时钟写4次,每次写16bit
always@(posedge rd_clk or negedge rd_rst_n)
if(rd_rst_n == 1'b0)
rd_data <= {DWO{1'b0}};
else
if(rd_en)
rd_data <= mem[rd_addr];//一个时钟读1次,每次读16bit
end
endgenerate
endmodule
module fifo#(
parameter AWI = 11,
parameter AWO = 11,
parameter DWI = 32,
parameter DWO = 32,
parameter PROG_DEPTH = 16, //这个是啥?
parameter ALMOST_FULL_GAP = 1,
parameter ALMOST_EMPTY_GAP = 1
)(
input wr_clk,
input wr_en,
input [DWI-1:0] wr_data,
input wr_rst_n,
input rd_clk,
input rd_en,
input rd_rst_n,
output [DWO-1:0] rd_data, //为什么加reg会报错illegal input or output
output reg wr_full,
output reg wr_almost_full,
output reg rd_empty,
output reg rd_almost_empty
);
parameter EXTENT = DWO/DWI;
parameter EXTENT_BITS = AWI-AWO;
parameter SHRINK = DWI/DWO;
parameter SHRINK_BITS = AWO-AWI;
reg [AWI:0] wr_ptr;
reg [AWO:0] rd_ptr;
generate
if(DWO>=DWI)begin
wire [AWI:0] rd_ptr_ex = rd_ptr<<EXTENT_BITS; //导致读指针只能取4的整数倍,导致满信号、将满信号的下降沿和空信号、将空信号的上升沿是同时的
wire [AWI:0] wr_ptr_gray;
wire [AWI:0] rd_ptr_gray;
reg [AWI:0] wr_ptr_gray_d1,wr_ptr_gray_d2;
reg [AWI:0] rd_ptr_gray_d1,rd_ptr_gray_d2;
reg [AWI:0] wr_almost_full_val;
parameter DEPTH = 1 << AWI;
//写控制逻辑
always@(posedge wr_clk or negedge wr_rst_n)
if(wr_rst_n == 1'b0)
wr_ptr <= {AWI{1'b0}};
else
if(wr_en&&(!wr_full))
wr_ptr <= wr_ptr + 1'b1;
else
wr_ptr <= wr_ptr;
//读控制逻辑
always@(posedge rd_clk or negedge rd_rst_n)
if(rd_rst_n == 1'b0)
rd_ptr <= {AWO{1'b0}};
else
if(rd_en&&(!rd_empty))
rd_ptr <= rd_ptr + 1'b1;
else
rd_ptr <= rd_ptr;
//格雷码转换
assign wr_ptr_gray = wr_ptr ^ (wr_ptr>>1);
assign rd_ptr_gray = rd_ptr_ex ^ (rd_ptr_ex>>1);
//格雷码同步
//发生亚稳态的原因:从源寄存器传递过来的信号adata没有满足目的寄存器的建立和保持时间,发生亚稳态。
//同步的作用:第一级寄存器的q会最终稳定下来的,而且在绝大多数时候,可以在一个bclk周期内稳定下来,这样第二级寄存器的d输入就是一个稳定的值,进而第二级寄存器的q是满足clk-to-q,没有亚稳态的产生。
//同步的局限性:源寄存器发过来的信号必须保证稳定不变至少碰见目的域时钟3个连续的沿,所以“打两拍”一般适用于单比特信号从慢时钟域传递快时钟域的场景。
always@(posedge wr_clk or negedge rd_rst_n)
if(rd_rst_n == 1'b0)begin
rd_ptr_gray_d1 <= {AWO{1'b0}};
rd_ptr_gray_d2 <= {AWO{1'b0}};
end
else begin
rd_ptr_gray_d1 <= rd_ptr_gray;
rd_ptr_gray_d2 <= rd_ptr_gray_d1; //由于实际电路中存在的噪声和能量变化等一定会让亚稳态很快产生不平衡,迅速向0或者1靠拢。rd_ptr_gray_d1如果是亚稳态,可以在一个wr_clk周期内稳定下来
end
/*
//如果两个上升沿发生两次变化,则中间数据会丢失,如32-33-34,33会丢失
always@(posedge rd_clk or negedge wr_rst_n)
if(wr_rst_n == 1'b0)begin
wr_ptr_gray_d1 <= {AWI{1'b0}};
wr_ptr_gray_d2 <= {AWI{1'b0}};
end
else begin
wr_ptr_gray_d1 <= wr_ptr_gray;
wr_ptr_gray_d2 <= wr_ptr_gray_d1; //由于实际电路中存在的噪声和能量变化等一定会让亚稳态很快产生不平衡,迅速向0或者1靠拢。wr_ptr_gray_d1如果是亚稳态,可以在一个rd_clk周期内稳定下来
end
*/
always@(*)
wr_ptr_gray_d2 = wr_ptr_gray;
//满信号,与前两拍的读指针比较,保留两个指针余量
always@(*)
if(wr_rst_n == 1'b0)
wr_full = 1'b0;
else
if(wr_ptr_gray == {~rd_ptr_gray_d2[AWI:AWI-1], rd_ptr_gray_d2[AWI-2:0]}) //wr_en = 1'b0, wr_ptr = 0, wr_ptr_gray = 0, rd_ptr_gray_d2 = 0, wr_full = 1'b0, 写进去了
wr_full = 1'b1; //wr_en = 1'b1, wr_ptr = 1, wr_ptr_gray = 1, rd_ptr_gray_d2 = 0, wr_full = 1'b0, 写进去了
else //wr_en = 1'b1, wr_ptr = 2, wr_ptr_gray = 3, rd_ptr_gray_d2 = 0, wr_full = 1'b0, 写进去了
wr_full = 1'b0; //wr_en = 1'b1, wr_ptr = 3, wr_ptr_gray = 2, rd_ptr_gray_d2 = 0, wr_full = 1'b0, 写进去了
//wr_en = 1'b1, wr_ptr = 4, wr_ptr_gray = 6, rd_ptr_gray_d2 = 0, wr_full = 1'b1, 没写进去
//都由wr_clk时钟控制
//空信号,与前两拍的写指针比较,保留两个指针余量
always@(*)
if(rd_rst_n == 1'b0)
rd_empty = 1'b0;
else
if(rd_ptr_gray == wr_ptr_gray_d2) //rd_en = 1'b0, rd_ptr = 0, rd_ptr_gray = 0, wr_ptr_gray_d2 = 6, rd_full = 1'b0, 读出来了
rd_empty = 1'b1; //rd_en = 1'b1, rd_ptr = 1, rd_ptr_gray = 1, wr_ptr_gray_d2 = 6, rd_full = 1'b0, 读出来了
else //rd_en = 1'b1, rd_ptr = 2, rd_ptr_gray = 3, wr_ptr_gray_d2 = 6, rd_full = 1'b0, 读出来了
rd_empty = 1'b0; //rd_en = 1'b1, rd_ptr = 3, rd_ptr_gray = 2, wr_ptr_gray_d2 = 6, rd_full = 1'b0, 读出来了
//rd_en = 1'b1, rd_ptr = 4, rd_ptr_gray = 6, wr_ptr_gray_d2 = 6, rd_full = 1'b1, 没读出来
//都由rd_clk时钟控制
//格雷码反解码
//如果只需要空、满状态信号,则不需要反解码
//因为可编程满状态信号的存在,地址反解码后便于比较
reg [AWI:0] wr_ptr_gray_to_bin;
reg [AWI:0] rd_ptr_gray_to_bin;
integer i;
always @(*) begin
//格雷码转二进制,最高位相同
wr_ptr_gray_to_bin[AWI] = wr_ptr_gray_d2[AWI];
//格雷码转二进制,除最高位,其余位都等于所有比自己高的位和格雷码本身位相异或
for (i=AWI-1; i>=0; i=i-1)
wr_ptr_gray_to_bin[i] = wr_ptr_gray_to_bin[i+1] ^ wr_ptr_gray_d2[i] ;
end
always @(*) begin
//同上
rd_ptr_gray_to_bin[AWI] = rd_ptr_gray_d2[AWI];
for (i=AWI-1; i>=0; i=i-1)
rd_ptr_gray_to_bin[i] = rd_ptr_gray_to_bin[i+1] ^ rd_ptr_gray_d2[i] ;
end
//满信号,最高bit相反,其他相同。则将满信号为最高bit相反,其他相减小于等于1
always@(*)
if(wr_rst_n == 1'b0)
wr_almost_full_val = 1'b0;
else
//最高位不一致说明写指针回环
if(rd_ptr_gray_to_bin[AWI] != wr_ptr[AWI])
wr_almost_full_val = rd_ptr_gray_to_bin[AWI-1:0] - wr_ptr[AWI-1:0];
else
wr_almost_full_val = rd_ptr_gray_to_bin - wr_ptr + DEPTH;
always@(*)
if(wr_rst_n == 1'b0)
wr_almost_full = 1'b0;
else
if(wr_almost_full_val <= ALMOST_FULL_GAP)
wr_almost_full = 1'b1;
else
wr_almost_full = 1'b0;
//空信号,所有bit相同。则将空信号为所有bit相减小于等于1
always@(*)
if(rd_rst_n == 1'b0)
rd_almost_empty = 1'b0;
else
//解决:写地址为0,读地址为63时,不会拉高
if(wr_ptr_gray_to_bin == 6'b000000 && rd_ptr_ex == 6'b111111)
rd_almost_empty = 1'b1;
else if(wr_ptr_gray_to_bin - rd_ptr_ex <= ALMOST_EMPTY_GAP)
rd_almost_empty = 1'b1;
else
rd_almost_empty = 1'b0;
end
endgenerate
ramdp #(
.AWI(AWI),
.AWO(AWO),
.DWI(DWI),
.DWO(DWO)
)u_ramdp(
.wr_addr (wr_ptr[AWI-1:0]) ,
.wr_clk (wr_clk) ,
.wr_en (wr_en&&(!wr_full)) ,
.wr_rst_n (wr_rst_n) ,
.wr_data (wr_data) ,
.rd_addr (rd_ptr[AWO-1:0]) ,
.rd_clk (rd_clk) ,
.rd_rst_n (rd_rst_n) ,
.rd_en (rd_en&&(!rd_empty)),
.rd_data (rd_data)
);
endmodule
tb文件代码如下:
`timescale 1ns/1ps
module tb_fifo();
parameter AWI = 5;
parameter AWO = 5;
parameter DWI = 16;
parameter DWO = 16;
parameter WR_PERIOD = 50;
parameter RD_PERIOD = 100;
reg wr_clk;
reg wr_en;
reg [DWI-1:0] wr_data;
reg wr_rst_n;
reg rd_clk;
reg rd_en;
reg rd_rst_n;
wire[DWO-1:0] rd_data;
wire wr_full;
wire rd_empty;
wire wr_almost_full;
wire rd_almost_empty;
initial begin
wr_clk = 1'b1;
rd_clk = 1'b1;
end
always #(WR_PERIOD/2) wr_clk = ~wr_clk; //50ns
always #(RD_PERIOD/2) rd_clk = ~rd_clk; //100ns
initial begin
wr_clk = 1'b0;
wr_en = 1'b0;
wr_rst_n= 1'b1;
rd_clk = 1'b0;
rd_en = 1'b0;
rd_rst_n= 1'b1;
#WR_PERIOD wr_rst_n = 1'b0;
#(WR_PERIOD*2) wr_rst_n = 1'b1;
#RD_PERIOD rd_rst_n = 1'b0;
#(RD_PERIOD*2) rd_rst_n = 1'b1;
repeat(100)
@(posedge wr_clk) begin
wr_en <= {$random}%2;
wr_data <= {$random}%DWI;
end
#WR_PERIOD wr_en = 1'b0;
repeat(100)
@(posedge rd_clk) begin
rd_en <= {$random}%2;
end
#WR_PERIOD rd_en = 1'b0;
repeat(100) begin
@(posedge wr_clk) begin
wr_en <= {$random}%2;
wr_data <= {$random}%DWI;
end
end
#WR_PERIOD wr_en = 1'b0;
#RD_PERIOD rd_en = 1'b1;
end
fifo #(
.AWI(AWI),
.AWO(AWO),
.DWI(DWI),
.DWO(DWO)
)u_fifo(
.wr_clk (wr_clk),
.wr_en (wr_en),
.wr_data(wr_data) ,
.wr_rst_n(wr_rst_n),
.rd_clk (rd_clk),
.rd_en (rd_en),
.rd_rst_n(rd_rst_n),
.rd_data(rd_data) ,
.wr_full(wr_full) ,
.wr_almost_full(wr_almost_full),
.rd_empty(rd_empty),
.rd_almost_empty(rd_almost_empty)
);
endmodule