异步FIFO设计代码实现
首先我们思考一个问题,为什么在异步FIFO设计中需要使用Gray Code?
之前在同步FIFO设计中我们提到,同步FIFO设计的关键在于写满和读空的判断,而在异步FIFO的设计中同样也是如此。不同的点在于:在同步FIFO中,读写指针属于同一个时钟域,可以直接进行比较;而在异步FIFO中,读写指针属于不同的时钟域,需要进行跨时钟域处理后才能进行比较。即读指针通过打两拍到写时钟域与写指针比较判断写满信号,写指针同步到读时钟域与读指针来判断读空信号。
那为什么不能使用binary编码呢?指针是一个多位的信号,如果编码的多位信号同时发生改变时,可能会产生毛刺。binary编码是多位跳变。在实现电路时不可能所有的地址总线等长,存在address bus skew,比如写地址在从0111到1000转换时4条地址线同时跳变,这样读时钟在进行写地址同步后得到的写地址可能是0000-1111的某个值,所以用这个同步后的写指针进行FIFO读空判断的时候极不可靠。
而Gray Code可以解决这一问题,首先我们得明确跨时钟域处理时在中间状态抽样是无法避免的,但我们要做到即使在中间状态抽烟,也不能影响空满的判断。Gray Code的编码方式是相邻状态只有一位跳变,在跨时钟域的时候即使出错,但也只有一位出错,不会影响空满标志的判断。
Gray Code编码如下:
总结起来就是,当Gray Code形式的读指针同步到写时钟域后与Gray Code形式的写指针相比较,如果最高位和次高位相反,其余位相同,相当于多读了一圈,则为写满;当Gray Code形式的写指针同步到读时钟域后与Gray Code形式的读指针相比较,如果两者一致,则为读空。了解了为什么使用Gray Code后,思路就比较清晰了。首先定义两个读写指针,将其转化为Gray Code形式,将Gray Code形式的读写指针分别进行跨时钟域处理与时钟域内的写读指针进行比较,判断写满和读空信号。利用写满读空信号并配合写使能和读使能,实现数据的正常写入和读出操作。
首先定义读写端口:
定义数位位宽数据深度,双端口RAM,读写指针:
读写指针转化Gray Code形式:
Gray Code形式的读写指针进行跨时钟域操作,即打两拍:
判断写满和读空信号的产生:
写满和读空信号配合写使能读使能,实现数据的写入和读出:
需要注意的是,当读写指针的值大于等于RAM深度时,读写的数据应为读写指针值减去RAM深度所对应的RAM位置。
testbench:
`timescale 1ns / 1ps module tb_async_fifo; // async_fifo Parameters parameter wr_PERIOD = 60 ; parameter rd_PERIOD = 50 ; parameter DATA_WIDTH = 8 ; parameter DATA_DEPTH = 8 ; // async_fifo Inputs reg wr_clk = 0; reg wr_rstn = 0; reg wr_en = 0; reg [ 7:0] wr_data = 0; reg rd_clk = 0; reg rd_rstn = 0; reg rd_en = 0; // async_fifo Outputs wire wr_full ; wire [ 7:0] rd_data ; wire rd_empty ; initial begin forever #(wr_PERIOD/2) wr_clk=~wr_clk; end initial begin forever #(rd_PERIOD/2) rd_clk=~rd_clk; end initial begin wr_rstn = 0; rd_rstn = 0; wr_en = 0; rd_en = 0; #10; wr_rstn = 1; rd_rstn = 1; #10; wr_en=1; wr_data=8'h69; #60; wr_data=8'hff; #60; wr_en=0; #100; rd_en=1; #110; rd_en=0; #10; wr_en=1; wr_data={$random}%30; repeat(10)begin @(negedge wr_clk) wr_data={$random}%30; end wr_en=0; //$stop; end async_fifo #( .DATA_WIDTH (DATA_WIDTH ), .DATA_DEPTH (DATA_DEPTH ) ) u_async_fifo ( .wr_clk (wr_clk ), .wr_rstn (wr_rstn ), .wr_en (wr_en ), .wr_data (wr_data [ 7:0] ), .rd_clk (rd_clk ), .rd_rstn (rd_rstn ), .rd_en (rd_en ), .wr_full (wr_full ), .rd_data (rd_data [ 7:0] ), .rd_empty (rd_empty ) ); endmodule
仿真结果:
暂时没有考虑慢时钟域同步到快时钟域和快时钟域同步到慢时钟域的不同处理方式,因此仿真细节应该会存在一些小问题,未完待续。