异步FIFO

学习文章:

1、https://www.cnblogs.com/aslmer/p/6114216.html

2、https://www.cnblogs.com/streetlive/p/12872619.html

3、https://blog.csdn.net/dongdongnihao_/article/details/79873555

原理分析:

FIFO(First In First Out)先进先出,从字面意思就是只能够顺序写入数据,顺序读出数据,就不能够像普通存储器一样由地址线决定写入或读取某个指定地址。同样,不同宽度的数据接口也可以用FIFO。异步FIFO的本质是RAM,只不过就是读写操作由不同的时钟来控制。

同步FIFO是指读写时钟为同一个时钟,异步FIFO是指读写时钟不一致,读写时钟相互独立的。

FIFO的宽度是指FIFO一次读写操作的数据位,FIFO的深度是指FIFO可以存储多少个这样宽度的数据。

如何解决跨时钟?

处理跨时钟域的数据有单bit和多bits之分,而打两拍的方式常见于处理单bit数据的跨时钟域问题。打两拍本质就是定义两级寄存器对数据进行延迟。比如读指针同步到写时钟域,原理如图:

 

 代码实现:

如何判断写满w_full?


 代码实现:

综合代码:

module fifo
#(
parameter width=8,
parameter depth=4
)
(
input w_clk,
input w_rst_n,
input r_clk,
input r_rst_n,
input w_en,
input r_en,
input [width-1:0]w_data,
output [width-1:0]r_data,
output reg w_full,
output reg r_empty
);

wire [depth-1:0]w_addr,r_addr;//为什么地址这里是wire型

reg [width-1:0]ram[0:(1<<depth)-1];
reg [depth:0]rptr;//读指针
reg [depth:0]rptr_to_wclk_1,rptr_to_wclk_2;//同步到写时钟域的读指针
reg [depth:0]wptr;//写指针
reg [depth:0]wptr_to_rclk_1,wptr_to_rclk_2;//同步到读时钟域的写指针

assign r_data=ram[r_addr];
always @(posedge w_clk)
if(w_en && !w_full)
ram[w_addr]<=w_data;

//读指针同步到写时钟域,需要打两拍
always @(posedge w_clk or negedge w_rst_n)
if(!w_rst_n)begin
rptr_to_wclk_1<=5'd0;
rptr_to_wclk_2<=5'd0;
end
else begin
rptr_to_wclk_1<=rptr;
rptr_to_wclk_2<=rptr_to_wclk_1;
end

//写指针同步到读时钟域,需要打两拍
always @(posedge r_clk or negedge r_rst_n)
if(!r_rst_n)begin
wptr_to_rclk_1<=5'd0;
wptr_to_rclk_2<=5'd0;
end
else begin
wptr_to_rclk_1<=wptr;
wptr_to_rclk_2<=wptr_to_rclk_1;
end

//生成r_empty
reg [depth:0]r_binary;
wire [depth:0]r_gray,r_binary_next;
always @(posedge r_clk or negedge r_rst_n)
if(!r_rst_n)begin
r_binary<=5'd0;
rptr<=5'd0;
end
else begin
r_binary<=r_binary_next;
rptr<=r_gray;
end
assign r_addr=r_binary[depth-1:0];
assign r_binary_next=r_binary + (r_en & ~r_empty);
assign r_gray=(r_binary_next>>1) ^ r_binary_next;
assign r_empty_t=(r_gray == wptr_to_rclk_2);
always @(posedge r_clk or negedge r_rst_n)
if(!r_rst_n)
r_empty<=1'b1;
else
r_empty<=r_empty_t;

//生成w_full
reg [depth:0]w_binary;
wire [depth:0]w_gray,w_binary_next;
always @(posedge w_clk or negedge w_rst_n)
if(!w_rst_n)begin
w_binary<=5'd0;
wptr<=5'd0;
end
else begin
wptr<=w_gray;
w_binary<=w_binary_next;
end
assign w_addr=w_binary[depth-1:0];
assign w_binary_next=w_binary + (w_en & ~w_full);
assign w_gray=(w_binary_next>>1) ^ w_binary_next;
assign w_full_t=(w_gray == {~rptr_to_wclk_2[depth:depth-1],rptr_to_wclk_2[depth-2:0]});
always @(posedge w_clk or negedge w_rst_n)
if(!w_rst_n)
w_full<=1'b0;
else
w_full<=w_full_t;

endmodule 

仿真代码:

`timescale 1ns/1ns
module fifo_tb;
reg w_clk;
reg w_rst_n;
reg r_clk;
reg r_rst_n;
reg w_en;
reg r_en;
reg [7:0]w_data;
wire [7:0]r_data;
wire w_full;
wire r_empty;

fifo u_fifo(
.w_clk (w_clk),
.w_rst_n (w_rst_n),
.r_clk (r_clk),
.r_rst_n (r_rst_n),
.w_en (w_en),
.r_en (r_en),
.w_data (w_data),
.r_data (r_data),
.w_full (w_full),
.r_empty (r_empty)
);

//生成时钟
initial begin
w_clk=0;
r_clk=0;
end
always #10 w_clk=~w_clk;
always #20 r_clk=~r_clk;

//产生复位信号
initial begin
w_rst_n=0;
r_rst_n=0;
#60;
w_rst_n=1;
r_rst_n=1;
end

always @(posedge w_clk or negedge w_rst_n)begin
if(!w_rst_n)begin
w_en<=1'b0;
r_en<=1'b0;
end
else begin
w_en<=$random;
r_en<=$random;
end
end

always @(posedge r_clk or negedge r_rst_n)begin
if(!r_rst_n)
r_en<=1'b0;
else
r_en<=$random;
end

always @(*)begin //这个模块只能在这个位置,不能放到写和读的两个always前
if(w_en)
w_data=$random;
else
w_data=0;
end

endmodule 

仿真结果: 

posted @ 2020-10-28 20:27  LiYiRui  阅读(681)  评论(0编辑  收藏  举报