部分还是跟同步fifo相同的,可以看我的上一篇博客:两种简单的同步fifo(不同的输出时序)
直接上代码:
module fifo#(
parameter RAM_WIDTH = 6'd32,
parameter RAM_DEPTH = 4'd8,
parameter ADDR_WIDTH = 4'd4
)(
input wclk,
input rclk,
input resetn,
input [RAM_WIDTH-1:0] data_in,
input write_en,
input read_en,
output [RAM_WIDTH-1:0] data_out,
output full,
output empty
);
reg [RAM_WIDTH-1:0] ram[RAM_DEPTH-1:0];
reg [ADDR_WIDTH-1:0] write_addr_wclk;
reg [ADDR_WIDTH-1:0] read_addr_rclk;
wire [ADDR_WIDTH-1:0] write_addr_gray_wclk;
reg [ADDR_WIDTH-1:0] write_addr_gray_rclk0;
reg [ADDR_WIDTH-1:0] write_addr_gray_rclk1;
wire [ADDR_WIDTH-1:0] read_addr_gray_rclk;
reg [ADDR_WIDTH-1:0] read_addr_gray_wclk0;
reg [ADDR_WIDTH-1:0] read_addr_gray_wclk1;
always@(posedge wclk or negedge resetn)//write addr wclk operation
if(!resetn)
write_addr_wclk <= 'b0;
else if(write_en == 1'b1 && full != 1'b1)
write_addr_wclk <= write_addr_wclk + 1'b1;
else
write_addr_wclk <= write_addr_wclk;
always@(posedge rclk or negedge resetn)//read addr read operation
if(!resetn)
read_addr_rclk <= 'b0;
else if(read_en == 1'b1 && empty != 1'b1)
read_addr_rclk <= read_addr_rclk + 1'b1;
else
read_addr_rclk <= read_addr_rclk;
always@(posedge wclk or negedge resetn)//sync
if(!resetn)begin
read_addr_gray_wclk0 <= 'b0;
read_addr_gray_wclk1 <= 'b0;
end
else begin
read_addr_gray_wclk0 <= read_addr_gray_rclk;
read_addr_gray_wclk1 <= read_addr_gray_wclk0;
end
always@(posedge rclk or negedge resetn)//sync
if(!resetn)begin
write_addr_gray_rclk0 <= 'b0;
write_addr_gray_rclk1 <= 'b0;
end
else begin
write_addr_gray_rclk0 <= write_addr_gray_wclk;
write_addr_gray_rclk1 <= write_addr_gray_rclk0;
end
always@(posedge wclk)//data out
if(full == 1'b0 && write_en == 1'b1)
ram[write_addr_wclk[ADDR_WIDTH-2:0]] <= data_in;
assign full = (write_addr_gray_wclk[ADDR_WIDTH-1] != read_addr_gray_wclk1[ADDR_WIDTH-1])?
(write_addr_gray_wclk[ADDR_WIDTH-2] != read_addr_gray_wclk1[ADDR_WIDTH-2])?
(write_addr_gray_wclk[ADDR_WIDTH-3:0] == read_addr_gray_wclk1[ADDR_WIDTH-3:0])?1'b1:1'b0:1'b0:1'b0;
assign empty = (write_addr_gray_rclk1 == read_addr_gray_rclk)?1'b1:1'b0;
assign data_out = ((read_en == 1'b1) && (empty != 1'b1))?ram[read_addr_rclk[ADDR_WIDTH-2:0]]:'b0;
assign write_addr_gray_wclk = (write_addr_wclk>>1)^write_addr_wclk;
assign read_addr_gray_rclk = (read_addr_rclk>>1)^read_addr_rclk;
endmodule
时钟变成了两个:读时钟与写时钟。与同步fifo不同的是,异步fifo的地址需要分开判断,即写时钟域有写地址与同步过来的读地址,读时钟域有读地址与同步过来的写地址。地址同步直接用的两级寄存器打拍采样,读写时钟谁快谁慢不用在意。
为什么能直接两级寄存器打拍采?我们来分析一下:
写时钟域:
1.在写时钟域内,如果此时fifo为空,读时钟域同步过来的地址与写时钟域相同,这时候fifo空信号拉高,fifo能写入数据。一般来说,同步数据出错是发生寄存器数据出现翻转的时候,因为空的时候没法读数据,同步不会出错。如果运气是真的烂,或者芯片有问题,同步过来的地址出错,fifo可能非满可能满,但这并不影响我们写入数据。如果为非满,不影响往正确的写地址写入数据,只要后续读地址同步正确就行了;如果为满,此时不能往fifo写数据,那么就不可能写错。因为写时钟域只控制写,所以在这种情况下你读指针同步出错了对写时钟域几乎没影响。
2.如果fifo此时为非空,读地址同步正常的话fifo正常,该干嘛干嘛。如果同步出错,因为使用的地址是转换成格雷码后的地址,地址大概率只会发生一位的错误变化,及错误后的地址在实际地址的前一位或者后一位,也只会出现非满和满的情况,不会影响写入。
3.如果fifo此时为满,读地址同步发生错误,一般也是在fifo开始读数据时发生的错误。既然已经读走了数据,那么我们写入数据也没问题了,在下一次读地址同步正确就行。因为地址的格雷码是wire类型的,所以地址变化后能被同步的地址一定是当时的地址。
读时钟域:
1.在读时钟域内,如果此时fifo为空,写时钟域同步过来的地址与读时钟域地址相同,这时候fifo空信号拉高,fifo不会读。如果真出错了,那就错了吧= =这真没办法了。
2.如果为非空,同步出错,写指针往相邻的地址跳变,结果可能是空、满、非空,也不影响读。
3.如果为满,写地址不会变,不影响读;如果同步出错了原有的数据不会被覆盖(因为在写时钟域内写指针是正确的,不会写入新数据),不影响读的结果。
如果有不懂或者我说错的地方欢迎指正与讨论(๑•̀ㅂ•́)و✧