1,经过同步FIFO的设计,了解FIFO ,现在来写异步FIFO,应该有点思考了,由于要解决跨时钟域的问题,前面设计同步FIFO的计数器方法就不太实用了,所以就选择高位扩展法,但是还有个问题如果采取二进制也会造成亚稳态,所以选择格雷码
2,注意点
1他于普通存储器的区别是没有外部读写地址线,使用相对简单。
2缺点是只能顺序写入数据,顺序读出数据,其数据地址有内部读写指针自动加一完成,不能读取指定位置的数据
3有同步和异步FIFO,同步是指读写时钟是同一个时钟,异步是指读写时钟不同
4同步FIFO一般做数据的缓冲,主要作用是buffer
5异步FIFO主要作用就是跨时钟域处理,除此之外可以实现两个不同数据宽度的数据链接
3 ,读写跨时钟域的问题,这里都是采取打两拍来解决,但是如何判断空满了?
总结一下别人的结论,这里就不再讨论了,
写满判断:将读指针同步到写指针,在于写指针判断
读空判断: 将写指针同步到读指针,在于读指针判断
3,二进制转格雷码,之前的文章讲过
Verilog实现二进制与格雷码的转换_weixin_45230720的博客-CSDN博客
4Verilog实现
设计是位宽为8 深度为16的FIFO,
module async_fifo
#(
parameter DATA_WIDTH = 'd8 ,
parameter DATA_DEPTH = 'd16
)
(
//写数据
input wr_clk,
input wr_rst_n ,
input wr_en ,
input [DATA_WIDTH -1 : 0] data_in ,
//读数据
input rd_clk ,
input rd_rst_n ,
input rd_en ,
input [DATA_WIDTH -1 :0] data_out ,
//状态标志
output empty ,
output full
);
//=======================================================================
reg [DATA_WIDTH-1:0] fifo_buffer [DATA_DEPTH -1 :0] ;
reg [4:0] wr_ptr ;
reg [4:0] rd_ptr ;
reg [4:0] wr_ptr_g_d1;
reg [4:0] wr_ptr_g_d2;
reg [4:0] rd_ptr_g_d1;
reg [4:0] rd_ptr_g_d2;
//
wire [4:0:] wr_ptr_g ;
wire [4:0:] rd_ptr_g ;
wire [3:0:] wr_ptr_ture ;
wire [3:0:] rd_ptr_ture ;
//=========================================================================
//二进制转成格雷码
assign wr_ptr_g = wr_ptr^(wr_ptr>>1);
assign rd_ptr_g = rd_ptr^(rd_ptr>>1);
//writ
always@(posedge wr_clk or negedge wr_rst_n)begin
if(!wr_rst_n)
wr_ptr <= 1'b0 ;
else if(wr_en && !full)begin
wr_ptr <= wr_ptr + 1'b1 ;
fifo_buffer[wr_ptr] <= data_in ;
end
end
always@(posedge wr_clk or negedge wr_rst_n )begin
if(!wr_rst_n)begin
rd_ptr_g_d1 <= 0;
rd_ptr_g_d2 <= 0 ;
end
else
begin
rd_ptr_g_d1<= rd_ptr_g ;
rd_ptr_g_d2 <= rd_ptr_g_d1 ;
end
end
//read
always@(posedge rd_clk or negedge rd_rst_n)begin
if(!rd_rst_n)
rd_ptr <= 0;
else if(rd_en && !empty)begin
data_out <= fifo_buffer[rd_ptr];
rd_ptr <= rd_ptr +1'b1 ;
end
end
//打俩拍。判断读空
always @(posedge rd_clk or negedge rd_rst_n )begin
if(!rd_rst_n)begin
wr_ptr_g_d1 <= 0;
wr_ptr_g_d2 <= 0;
end
else begin
wr_ptr_g_d1 <= wr_ptr_g ;
wr_ptr_g_d2 < wr_ptr_g_d1 ;
end
end
//===========================================================================
assign empty = (wr_ptr_g_d2 == rd_ptr_g) ? 1'b1 :1'b0 ;
assign full = (wr_ptr_g == {~rd_ptr_g_d2[4:3],rd_ptr_g_d2[2:0]}) ? 1'b1 :1'b0;
endmodule
如果有问题可以一起学习讨论,留言
4 测试与仿真
testbech与同步FIFO的类似,这里只给出Verilog实现
`timescale 1ns/1ns //时间单位/精度
//=================================================================================
module tb_async_fifo();
parameter DATA_WIDTH = 8 ; //FIFO位宽
parameter DATA_DEPTH = 16 ; //FIFO深度
reg wr_clk ; //写时钟
reg wr_rst_n ; //低电平有效的写复位信号
reg wr_en ; //写使能信号,高电平有效
reg [DATA_WIDTH-1:0] data_in ; //写入的数据
reg rd_clk ; //读时钟
reg rd_rst_n ; //低电平有效的读复位信号
reg rd_en ; //读使能信号,高电平有效
wire[DATA_WIDTH-1:0] data_out ; //输出的数据
wire empty ; //空标志,高电平表示当前FIFO已被写满
wire full ; //满标志,高电平表示当前FIFO已被读空
//===================================================================================
async_fifo
#(
.DATA_WIDTH (DATA_WIDTH), //FIFO位宽
.DATA_DEPTH (DATA_DEPTH) //FIFO深度
)
async_fifo_inst(
.wr_clk (wr_clk ),
.wr_rst_n (wr_rst_n ),
.wr_en (wr_en ),
.data_in (data_in ),
.rd_clk (rd_clk ),
.rd_rst_n (rd_rst_n ),
.rd_en (rd_en ),
.data_out (data_out ),
.empty (empty ),
.full (full )
);
//=======================================================================================
initial begin
rd_clk = 1'b0; //初始时钟为0
wr_clk = 1'b0; //初始时钟为0
wr_rst_n <= 1'b0; //初始复位
rd_rst_n <= 1'b0; //初始复位
wr_en <= 1'b0;
rd_en <= 1'b0;
data_in <= 'd0;
#5
wr_rst_n <= 1'b1;
rd_rst_n <= 1'b1;
//重复16次写操作,让FIFO写满
repeat(16) begin
@(negedge wr_clk)begin
wr_en <= 1'b1;
data_in <= $random; //生成8位随机数
end
end
//拉低写使能
@(negedge wr_clk) wr_en <= 1'b0;
//重复16次读操作,让FIFO读空
repeat(16) begin
@(negedge rd_clk)rd_en <= 1'd1;
end
//拉低读使能
@(negedge rd_clk)rd_en <= 1'd0;
//重复4次写操作,写入4个随机数据
repeat(4) begin
@(negedge wr_clk)begin
wr_en <= 1'b1;
data_in <= $random; //生成8位随机数
end
end
//持续同时对FIFO读
@(negedge rd_clk)rd_en <= 1'b1;
//持续同时对FIFO写,写入数据为随机数据
forever begin
@(negedge wr_clk)begin
wr_en <= 1'b1;
data_in <= $random; //生成8位随机数
end
end
end
//=======================================================================================
always #10 rd_clk = ~rd_clk; //读时钟周期20ns
always #20 wr_clk = ~wr_clk; //写时钟周期40ns
endmodule