1、FIFO分类
同步FIFO,读和写应用同一个时钟。它的作用一般是做交互数据的一个缓冲,也就是说它的主要作用就是一个buffer。
异步FIFO,读写应用不同的时钟,它有两个主要的作用,一个是实现数据在不同时钟域进行传递,另一个作用就是实现不同数据宽度的数据接口(ADC:8->16,16->8)。
2、同步FIFO
clk,rst,读和写共用一个时钟
r_en,w_en,读和写使能
read_empty,write_full,读空和写满标志,不需要格雷码
read_data,write_data
内部模块还需要定义,读地址和写地址 read_addr,write_addr
需要例化ram模块
表明FIFO深度和位宽
核心在判断读空与写满标志,有两种方法:1、计数器法;2、高位扩展法
1、计数器法
构建一个计数器,利用计数器(fifo_cnt)来表示FIFO中的数据个数,并且利用其来判断是否写满和读空。
(1)、当读使能(r_en)与写使能(w_en)同时有效时,FIFO同时进行读和写操作,即fifo_cnt的值不变;
(2)、当读使能(r_en)有效且read_empty=0时,即FIFO未读空时,继续读出,fifo_cnt-1;
(3)、当写使能(w_en)有效且write_full=0时,即FIFO未写满时,继续写入,fifo_cnt+1;
(4)、当fifo_cnt=0时,表明FIFO读空,将read_empty=1,当fifo_cnt=FIFO的深度时,表明FIFO写满,将write_full=1。
2、高位扩展法
多扩展1bit地址来判断读空和写满标志,如原地址[2:0],扩展1bit为[3:0]
当扩展后的读地址与写地址相等时,表明写地址追上读地址,即写满
当扩展后的读写地址最高位不同,其他位相同同时,表明读地址追上写地址,即读空
`timescale 1ns/1ns
/**********************************RAM************************************/
module dual_port_RAM #(parameter DEPTH = 16,
parameter WIDTH = 8)(
input wclk
,input wenc
,input [$clog2(DEPTH)-1:0] waddr //深度对2取对数,得到地址的位宽。
,input [WIDTH-1:0] wdata //数据写入
,input rclk
,input renc
,input [$clog2(DEPTH)-1:0] raddr //深度对2取对数,得到地址的位宽。
,output reg [WIDTH-1:0] rdata //数据输出
);
reg [WIDTH-1:0] RAM_MEM [0:DEPTH-1];
always @(posedge wclk) begin
if(wenc)
RAM_MEM[waddr] <= wdata;
end
always @(posedge rclk) begin
if(renc)
rdata <= RAM_MEM[raddr];
end
endmodule
/**********************************SFIFO************************************/
module sfifo#(
parameter WIDTH = 8,
parameter DEPTH = 16
)(
input clk ,
input rst_n ,
input winc ,
input rinc ,
input [WIDTH-1:0] wdata ,
output reg wfull ,
output reg rempty ,
output wire [WIDTH-1:0] rdata
);
localparam addwidth=$clog2(DEPTH);
reg [addwidth:0] waddr;
reg [addwidth:0] raddr;
always@(posedge clk or negedge rst_n)begin
if(!rst_n)begin
waddr<=0;
end
else if(winc&&!wfull)begin
waddr<=waddr+1;
end
else begin
waddr<=waddr;
end
end
always@(posedge clk or negedge rst_n)begin
if(!rst_n)begin
raddr<=0;
end
else if(rinc&&!rempty)begin
raddr<=raddr+1;
end
else begin
raddr<=raddr;
end
end
always@(posedge clk or negedge rst_n )begin
if(!rst_n)begin
wfull<=0;
rempty<=0;
end
else begin
rempty<=waddr==raddr;
wfull<=(raddr=={~waddr[addwidth],waddr[addwidth-1:0]});
end
end
dual_port_RAM #(. DEPTH(DEPTH),
. WIDTH (WIDTH))
ram_d(
. wclk(clk)
,. wenc(winc&&!wfull)
,. waddr(waddr[addwidth-1:0]) //深度对2取对数,得到地址的位宽。
,. wdata(wdata) //数据写入
,. rclk(clk)
,. renc(rinc&&!rempty)
,. raddr(raddr[addwidth-1:0]) //深度对2取对数,得到地址的位宽。
,. rdata(rdata) //数据输出
);
endmodule
3、异步FIFO
由于读和写是不同时钟控制的,所以不能用计数器法来实现,可用高位扩展法,扩展一位地址
在同步FIFO中,读空时,读地址=写地址;写满时,写地址多跑一圈,所以读写地址最高位不同,其他位相同。
而在异步FIFO中,由于读或写地址都要同步到另一个时钟域中进行比较,此时如果仍使用二进制进行同步,就会出现多位bit数据同时进行跳变,此时就会增大出错概率;如果采用格雷码,相邻位仅有一位bit发生变化,就可以大幅度降低出错概率。
所以在异步FIFO中,在原二进制读写地址加1后,要先进行格雷码转换,再进行同步和判断读空、写满。
二进制转换为格雷码:最高位相同,然后最低位与次低位依次异或得到格雷码的每一位。
assign waddr_gray=waddr^(waddr>>1);
assign raddr_gray=raddr^(raddr>>1);
此时判断空满的标志不再是二进制时判断标准:
当读空时,仍是读地址与写地址相同
当写满时,读地址与写地址最高位、次高位不同,其余相同
在转换为格雷码后,先存储到寄存器中,然后再进行同步;
在同步中,采用打两拍的方式进行同步,即串行两个D触发器,防止亚稳态。
此时打两拍延时的同步对空满标志的影响
当读或写标志经过两级触发器,形成新的同步信号,在比较时不再是实时的比较,此时写地址格雷码或者读地址格雷码(格雷码转换后的,即两拍前的)与新同步后的信号进行比较,这其实是留有余量的设计,同于比较时读地址一定小于或等于当前的读地址(数据同步的这两拍中有可能再进行读操作),此时产生full信号,其实FIFO有可能还没有满,这也就为设计留了一些设计的余量。同理,就算有empty信号的产生,FIFO有可能还有数据。这种留余量的设计在实际的工程项目中是很常见的。
`timescale 1ns/1ns
/***************************************RAM*****************************************/
module dual_port_RAM #(parameter WIDTH=8,
parameter DEPTH = 16)
(
input wclk,
input rclk,
input wenc,
input renc,
input [$clog2(DEPTH)-1:0]waddr,
input [$clog2(DEPTH)-1:0]raddr,
input [WIDTH-1:0] wdata,
output reg [WIDTH-1:0] rdata
);
reg [WIDTH-1:0] ram_reg [0:DEPTH-1];
always@(posedge wclk )begin
if(wenc)
ram_reg[waddr] <= wdata;
end
always@(posedge rclk )begin
if(renc)
rdata <= ram_reg[raddr];
end
endmodule
/***************************************AFIFO*****************************************/
module asyn_fifo#(
parameter WIDTH = 8,
parameter DEPTH = 16
)(
input wclk ,
input rclk ,
input wrstn ,
input rrstn ,
input winc ,
input rinc ,
input [WIDTH-1:0] wdata ,
output wire wfull ,
output wire rempty ,
output wire [WIDTH-1:0] rdata
);
localparam a_width = $clog2(DEPTH);
reg [a_width:0] waddr;
reg [a_width:0] raddr;
always@(posedge wclk or negedge wrstn)begin
if(!wrstn)begin
waddr <= 'd0;
end
else if(winc&&(~wfull))begin
waddr <= waddr + 1'b1;
end
else begin
waddr <= waddr ;
end
end
always@(posedge rclk or negedge rrstn)begin
if(!rrstn)begin
raddr <= 'd0;
end
else if(rinc&&(~rempty))begin
raddr <= raddr + 1'b1;
end
else begin
raddr <= raddr;
end
end
wire [a_width:0] waddr_gray;
wire [a_width:0] raddr_gray;
assign waddr_gray=waddr^(waddr>>1);
assign raddr_gray=raddr^(raddr>>1);
reg [a_width:0] waddr_gray_reg;
reg [a_width:0] raddr_gray_reg;
always@(posedge wclk or negedge wrstn)begin
if(!wrstn)begin
waddr_gray_reg<='d0;
end
else begin
waddr_gray_reg <= waddr_gray;
end
end
always@(posedge rclk or negedge rrstn )begin
if(!rrstn)begin
raddr_gray_reg <= 'd0;
end
else begin
raddr_gray_reg <= raddr_gray;
end
end
reg [a_width:0] waddr_1;
reg [a_width:0] waddr_2;
reg [a_width:0] raddr_1;
reg [a_width:0] raddr_2;
always@(posedge wclk or negedge wrstn)begin
if(!wrstn)begin
raddr_1<= 'd0;
raddr_2 <= 'd0;
end
else begin
raddr_1 <= raddr_gray_reg;
raddr_2 <= raddr_1;
end
end
always@(posedge rclk or negedge rrstn)begin
if(!rrstn)begin
waddr_1 <= 'd0;
waddr_2 <= 'd0;
end
else begin
waddr_1 <= waddr_gray_reg;
waddr_2 <= waddr_1;
end
end
assign wfull = waddr_gray_reg=={~raddr_2[a_width:a_width-1],raddr_2[a_width-2:0]};
assign rempty = raddr_gray_reg==waddr_2;
dual_port_RAM #(. WIDTH(WIDTH),
. DEPTH(DEPTH))
d_ram
(
. wclk(wclk),
. rclk(rclk),
. wenc(winc&&(!wfull)),
. renc(rinc&&(!rempty)),
. waddr(waddr[a_width-1:0]),
. raddr(raddr[a_width-1:0]),
. wdata(wdata),
. rdata(rdata)
);
endmodule
还有FIFO深度的计算,下一篇写