异步fifo的工作原理,内部结构,在第一部分已经做了详细的说明,本小节主要介绍如何手写一个任意位宽,任意深度的异步fifo模块。
首先,需要一个fifo控制器,用来设置产生各种信号,例如空满水限,写读地址,该控制器的写数据由设计者定义,读数据由例化的双端口存储器模块送出。
module MyFIFO_Ctrl #(
parameter Full_Limit = 1022,
parameter Empty_Limit = 1 ,
parameter Data_Width = 8 ,
parameter Addr_Width = 10
)
(
input WClk ,
input RClk ,
input Rst ,
input Wen ,
input Ren ,
input [Data_Width - 1 : 0] Din ,
input [Data_Width - 1 : 0] RAM_Rd ,
output [Data_Width - 1 : 0] Dout,
output RAM_We ,
output reg [Addr_Width - 1 : 0] RAM_Waddr,
output reg [Addr_Width - 1 : 0] RAM_Raddr,
output [Data_Width - 1 : 0] RAM_Wd ,
output reg Empty ,
output reg Full ,
);
端口定义好后,需要控制写读使能控制下的存储器地址变化,并将地址信号的值转换为格雷码表示。
//write addr on wclk
always @(posedge WClk or posedge Rst)
begin
if (Rst)
RAM_Waddr <= 0;
else if (Wen)
RAM_Waddr <= RAM_Waddr + 1'b1;
end//write addr's binary to grey logic circuit
defparam U1_Norm2Gray.Data_Width = Addr_Width;
Norm2Gray U1_Norm2Gray (
.Din (RAM_Waddr),
.Dout (RAM_Waddr_Gray_Wire)
);//RAM_Waddr_Gray_Wire's flip_flop output on wclk
always @(posedge WClk or posedge Rst)
begin
if (Rst)
RAM_Waddr_Gray <= 0;
else
RAM_Waddr_Gray <= RAM_Waddr_Gray_Wire;
end//read addr on rclk
always @(posedge RClk or posedge Rst)
begin
if (Rst)
RAM_Raddr <= 0;
else if (Ren)
RAM_Raddr <= RAM_Raddr + 1'b1;
end//read addr's binary to gray circuit
defparam U2_Norm2Gray.Data_Width = Addr_Width;
Norm2Gray U2_Norm2Gray (
.Din (RAM_Raddr),
.Dout (RAM_Raddr_Gray_Wire)
);//RAM_Raddr_Gray_Wire's flip_flop output on rclk
always @(posedge RClk or posedge Rst)
begin
if (Rst)
RAM_Raddr_Gray <= 0;
else
RAM_Raddr_Gray <= RAM_Raddr_Gray_Wire;
end
使能与地址对应产生完成后,考虑空满信号何时产生,首先,需要实现地址的跨时钟域传输;
//ram read addr (gray) to sync by wclk
always @(posedge WClk or posedge Rst)
begin
if (Rst)
begin
RAM_Raddr_Gray_OnWClk_P <= 0;
RAM_Raddr_Gray_OnWClk <= 0;
end
else
begin
RAM_Raddr_Gray_OnWClk_P <= RAM_Raddr_Gray;
RAM_Raddr_Gray_OnWClk <= RAM_Raddr_Gray_OnWClk_P;
end
end//read (gray) addr on wclk to gray to binary circuitGray2Norm U1_Gray2Norm (
.Din (RAM_Raddr_Gray_OnWClk),
.Dout (RAM_Raddr_OnWClk_Wire)
);
defparam U1_Gray2Norm.Data_Width = Addr_Width;//RAM_Raddr_OnWClk_wire to flip_flop output on WClk
always @(posedge WClk or posedge Rst)
begin
if (Rst)
RAM_Raddr_OnWClk <= 0;
else
RAM_Raddr_OnWClk <= RAM_Raddr_OnWClk_Wire;
end
将读地址同步到写时钟域后,判断full信号的产生。
//write full circuit
assign diff_WRaddr_OnWClk = RAM_Waddr - RAM_Raddr_OnWClk;always @(posedge WClk or posedge Rst)
begin
if (Rst)
Full <= 1'b0;
else
begin
if (diff_WRaddr_OnWClk >= Full_Limit)
Full <= 1'b1;
else
Full <= 1'b0;
end
end
将写地址同步到读时钟域产生空信号也是一样的处理方式,代码如下:
//write addr (gray) to sync by rclk
always @(posedge RClk or posedge Rst)
begin
if (Rst)
begin
RAM_Waddr_Gray_OnRClk_P <= 0;
RAM_Waddr_Gray_OnRClk <= 0;
end
else
begin
RAM_Waddr_Gray_OnRClk_P <= RAM_Waddr_Gray;
RAM_Waddr_Gray_OnRClk <= RAM_Waddr_Gray_OnRClk_P;
end
end//write addr (gray) on wclk to gray to binary logic circuit
Gray2Norm U2_Gray2Norm (
.Din (RAM_Waddr_Gray_OnRClk),
.Dout (RAM_Waddr_OnRClk_Wire)
);defparam U2_Gray2Norm.Data_Width = Addr_Width;
//RAM_Waddr_OnRClk_wire to flip_flop on rclk
always @(posedge RClk or posedge Rst)
begin
if (Rst)
RAM_Waddr_OnRClk <= 0;
else
RAM_Waddr_OnRClk <= RAM_Waddr_OnRClk_Wire;
end//read empty circuit
assign diff_WRaddr_OnRClk = RAM_Waddr_OnRClk - RAM_Raddr;always @(posedge RClk or posedge Rst)
begin
if (Rst)
Empty <= 1'b1;
else
begin
if (diff_WRaddr_OnRClk <= Empty_Limit)
Empty <= 1'b1;
else
Empty <= 1'b0;
end
end
格雷码与二进制互相转换的电路统一封装在一个模块内。
`timescale 1ns/1ps
module Norm2Gray #(parameter Data_Width = 8) (
input [Data_Width - 1 : 0] Din,
output [Data_Width - 1 : 0] Dout
);assign Dout = Din ^ {1'b0, Din[Data_Width - 1 : 1]};
endmodule
module Gray2Norm #(parameter Data_Width = 8) (
input [Data_Width - 1 : 0] Din,
output reg [Data_Width - 1 : 0] Dout
);integer i, j;
reg Tmp;
always @(*)
begin
for (i = Data_Width - 1; i >= 0; i = i - 1)
begin
Tmp = 1'b0;
begin
for (j = Data_Width - 1; j >= i; j = j - 1)
Tmp = Tmp ^ Din[j];
end
Dout[i] = Tmp;
end
endendmodule
以上就是整个fifo控制器的内容,但这还不能直接使用,需要添加一个顶层模块,将该模块与存储器模块相连,从而实现fifo的功能。存储器模块的RTL代码不在本节的设计内容以内,可自行选择调用IP核或RTL描述,顶层代码如下。
module MyFIFO1024X8 #(
parameter Data_Width = 8 ,
parameter Addr_Width = 10
)
(
input WClk ,
input RClk ,
input Rst ,
input Wen ,
input Ren ,
input [Data_Width - 1 : 0] Din ,
output [Data_Width - 1 : 0] Dout,
output Empty ,
output Full
);wire [Data_Width - 1 : 0] RAM_Rd ;
wire [Data_Width - 1 : 0] RAM_Wd ;
wire RAM_We ;
wire [Addr_Width - 1 : 0] RAM_Waddr;
wire [Addr_Width - 1 : 0] RAM_Raddr;MyFIFO_Ctrl MyFIFO_Ctrl
(
.WClk(WClk) ,
.RClk(RClk) ,
.Rst(Rst) ,
.Wen(Wen) ,
.Ren(Ren) ,
.Din(Din) ,
.RAM_Rd(RAM_Rd) ,
.Dout(Dout) ,
.RAM_We(RAM_We) ,
.RAM_Waddr(RAM_Waddr) ,
.RAM_Raddr(RAM_Raddr) ,
.RAM_Wd(RAM_Wd) ,
.Empty(Empty) ,
.Full(Full)
);blk_mem_gen_0 U_bram_1024x8 (
.dina ( RAM_Wd ),
.addrb ( RAM_Raddr ),
.clkb ( RClk ),
.addra ( RAM_Waddr ),
.clka ( WClk ),
.wea ( RAM_We ),
.doutb ( RAM_Rd )
);endmodule
验证代码波形
写使能拉高,第一个数据ff一拍后被送到输出端口,拉高读使能后等待两拍出数据,满足第一节中双端口RAM逻辑时序,且空满信号正常产生,设计正确。
以上,是标准模式下的fifo,即读的数据会延迟两拍送出,经实测可直接使用,只需修改数据位宽,地址位宽。