阅读本文前,建议先阅读下面几篇文章:
同步FIFO
二进制转格雷码的实现
前言
在上篇文章同步FIFO中简要介绍了FIFO的基本概念以及同步FIFO的实现。本篇文章将重点介绍异步FIFO的工作原理以及硬件实现。
异步FIFO的工作原理
1. 概述
异步FIFO的读写时钟不同,FIFO的读写需要进行异步处理,异步FIFO常用于多bit数据跨时钟域处理。异步FIFO一般有复位rst_n、读端口和写端口。读端口一般包括读时钟(rd_clk)、读使能(rd_en)、读数据(data_out)、读空(empty)。写端口一般包括写时钟(wr_clk)、写使能(wr_en)、写数据(data_in)、写满(wr_full)。
2. 地址的跨时钟问题
实现异步FIFO难点仍在于如何得到读空和写满信号。由于异步FIFO的读写具有不同的时钟,读写独立,但是空满信号的判决仍需要通过读写地址(多bit数据)来实现,这里涉及到了多bit数据的跨时钟域处理,当然这里无法使用异步FIFO来实现这个跨时钟处理,毕竟我们就是为了做异步FIFO。多bit跨时钟处理还可以通过改变编码方式来降低亚稳态的发生概率。当读地址由4’b0111向4’b1000变化时,所有位都需要变化,如果写时钟恰好在地址变化时采样,写时钟得到的读地址是不确定的(为0000~1111中任意一个),因此为了降低该亚稳态的发生概率,地址采用格雷码编码。格雷码每次只变化一位,可以有效降低亚稳态的发生概率,同时单bit又可以采用打两拍的方法再次降低亚稳态发生的概率。
综上所述,为有效解决地址的跨时钟问题,采取格雷码编码+打两拍的方式降低地址变化发生亚稳态的概率。二进制转格雷码的实现。
3. 空满信号的判决条件
由于异步FIFO的读写地址采用格雷码,空满信号的判决条件不同于同步FIFO。同同步FIFO,可以将读写地址扩展一位,用于判断是否读写完一轮,即深度为8,地址为4bit。以下为深度为8的异步FIFO进行读写情况,以下只列举了四种操作,其余读写少于深度个数据的情况也是类似的。
综上所述,读空empty信号拉高的判决条件是读写地址的格雷码相同。写满信号拉高的判决条件是读写地址的格雷码的高2位不同,其余位相同。
但是上述判决条件可能存在一些问题。由于读写地址需要打两拍,如果打拍期间还有读写操作,那读写地址又改变了,得到的并不是真正的空满信号,那会出现数据丢失的情况吗?举例说明如下:文章来源:https://www.toymoban.com/news/detail-759219.html
- 判断空信号:判断空信号,需要比较rd_clk的读地址rd_addr和从wr_clk打拍到rd_clk的写地址wr_addr_w2r,如果在打拍过程中,还写入数据(wr_addr增加),那么用于判断空信号的wr_addr_w2r会小于实际的写地址wr_addr。如果此时判断为空,其实FIFO并不是真空,只不过此时FIFO不能再读出数据,此种情况并不会发生数据丢失。并且下一个时钟,空信号便会失效,并不会影响FIFO的正常使用。
- 判断满信号:判断满信号,需要比较wr_clk的写地址wr_addr和从rd_clk打拍到wr_clk的读地址rd_addr_r2w,如果在打拍过程中,还读出数据(rd_addr增加),那么用于判断满信号的rd_addr_r2w会小于实际的读地址rd_addr。如果此时判断为满,其实FIFO并不是真满,只不过此时FIFO不能再写入数据,此种情况并不会发生数据丢失。并且下一个时钟,满信号便会失效,并不会影响FIFO的正常使用。
异步FIFO的实现
module async_fifo#(
parameter DEPTH = 16,
parameter WIDTH = 8,
parameter ADDR_BIT = 4, //log2(DEPTH)
parameter RAM_STYLE_VAL = "distributed"
)(
input wr_clk,
input rst_n,
input wr_en,
input [WIDTH-1:0] data_in,
input rd_clk,
input rd_en,
output [WIDTH-1:0] data_out,
output full,
output empty
);
(*ram_style=RAM_STYLE_VAL*)
reg [WIDTH-1:0] mem [DEPTH-1:0];
reg [WIDTH-1:0] data_out_r;
reg [ADDR_BIT:0] wr_addr;
wire [ADDR_BIT:0] wr_addr_gray;
reg [ADDR_BIT:0] wr_addr_w2r1;
reg [ADDR_BIT:0] wr_addr_w2r2;
reg [ADDR_BIT:0] rd_addr;
wire [ADDR_BIT:0] rd_addr_gray;
reg [ADDR_BIT:0] rd_addr_r2w1;
reg [ADDR_BIT:0] rd_addr_r2w2;
//*********************** Address ***********************//
//write address
always @ (posedge wr_clk or negedge rst_n)begin
if(!rst_n) wr_addr <= 'd0;
else if(wr_en) wr_addr <= wr_addr + 1;
else wr_addr <= wr_addr;
end
//write address - > gray
assign wr_addr_gray = (wr_addr>>1)^wr_addr;
//Write address synchronization to read clock domain
always @ (posedge wr_clk or negedge rst_n)begin
if(!rst_n)
{wr_addr_w2r2,wr_addr_w2r1} <= 'd0;
else
{wr_addr_w2r2,wr_addr_w2r1} <= {wr_addr_w2r1,wr_addr_gray};
end
//read address
always @ (posedge rd_clk or negedge rst_n)begin
if(!rst_n)
rd_addr <= 'd0;
else if(rd_en)
rd_addr <= rd_addr + 1;
else
rd_addr <= rd_addr;
end
//write address - > gray
assign rd_addr_gray = (rd_addr>>1)^rd_addr;
//Read address synchronization to write clock domain
always @ (posedge rd_clk or negedge rst_n)begin
if(!rst_n)
{rd_addr_r2w2,rd_addr_r2w1} <= 'd0;
else
{rd_addr_r2w2,rd_addr_r2w1} <= {rd_addr_r2w1,rd_addr_gray};
end
//************************* Data *************************//
//write data
always @ (posedge wr_clk)begin
if(wr_en)
mem[wr_addr] <= data_in;
else
mem[wr_addr] <= mem[wr_addr];
end
//read data delay 1clk
assign data_out = data_out_r;
always @ (posedge rd_clk or negedge rst_n)begin
if(!rst_n)
data_out_r <= {WIDTH{1'b0}};
else if(rd_en==1)
data_out_r <= mem[rd_addr];
else
data_out_r <= data_out_r;
end
//********************** Full/Empty *********************//
//Empty signal judgment
assign empty = (wr_addr_w2r2 == rd_addr_gray);
//Full signal judgment
assign full = ({~(rd_addr_r2w2[4:3]),rd_addr_r2w2[2:0]}==wr_addr_gray[4:0]);
endmodule
异步FIFO的仿真测试
module tb_async;
reg wr_clk,rd_clk;
reg rst_n;
reg wr_en;
reg rd_en;
reg [7:0] data_in;
wire [7:0] data_out;
wire full;
wire empty;
parameter WR_PERIOD = 10;
parameter RD_PERIOD = 20 ;
async_fifo async_fifo(
.wr_clk (wr_clk ),
.rst_n (rst_n ),
.wr_en (wr_en ),
.data_in (data_in ),
.rd_clk (rd_clk ),
.rd_en (rd_en ),
.data_out (data_out ),
.full (full ),
.empty (empty )
);
initial begin
wr_clk = 0;rd_clk=0;rst_n = 0;
wr_en <= 0;rd_en <= 0;data_in = 'd0;
#15 rst_n = 1;
write_data(16);
#100
read_data(16);
#100
$finish;
end
always # (WR_PERIOD/2) wr_clk = ~wr_clk;
always # (RD_PERIOD/2) rd_clk = ~rd_clk;
task write_data(input [7:0] t);
begin
repeat(t)begin
@(posedge wr_clk)
data_in <= {$random}%256;
wr_en <= 1;
end
@(posedge wr_clk) wr_en <= 0;data_in <= 'd0;
end
endtask
task read_data(input [7:0] t);
integer i;
begin
repeat(t)begin
@(posedge rd_clk)
rd_en <= 1;
end
@(posedge rd_clk) rd_en <= 0;
end
endtask
endmodule
仿真结果如下,可以发现该异步fifo逻辑正确。
文章来源地址https://www.toymoban.com/news/detail-759219.html
到了这里,关于FPGA的Verilog设计(二)——异步FIFO的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!