学习文章:
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
仿真结果: