设计一个异步fifo?
请设计一个异步fifo?宽度为8bit,深度为4bit。
异步fifo:从硬件的观点来看,就是一块数据内存。它有两个端口,一个用来写数据,就是将数据存入FIFO;另一个用来读数据,也就是将数据从FIFO当中取出。与FIFO操作相关的有两个指针,写指针指向要写的内存部分,读指针指向要读的内存部分。FIFO控制器通过外部的读写信号控制这两个指针移动,并由此产生FIFO空信号或满信号。来自异步FIFO_百度百科 (baidu.com)。
异步fifo作用:用于将数据从一个时钟域安全的传送到另一个时钟域。
异步fifo设计的关键:生成fifo读写指针,fifo空/满状态。
异步fifo框图如下:整体分为五个小模块:1、写指针同步至读时钟域。2、读指针同步至写时钟域。3、双端口ram。4、写指针、写地址、满信号的产生。5、读指针、读地址、空信号的产生。
二进制编码在传递中容易产生亚稳态,故采用格雷码传递。格雷码具有良好的对称性。
使用格雷码判断空满状态:
异步fifo的设计代码&激励&仿真波形:
module asyn_fifo #( parameter DATA_WIDTH = 8 , parameter DATA_DEPTH = 16 , parameter PTR = 4 //2^4=16 ) ( //写时钟域 input w_clk , input w_rst_n , input w_en , input [DATA_WIDTH-1:0] data_in , output reg full , //读时钟域 input r_clk , input r_rst_n , input r_en , output [DATA_WIDTH-1:0] data_out , output reg empty ); reg [DATA_WIDTH-1:0] DPRAM [DATA_DEPTH-1:0]; //双端口RAM //写时钟域 reg [PTR:0] w_bin ; wire [PTR:0] w_bin_next ; reg [PTR:0] w_gray ; wire [PTR:0] w_gray_next ; reg [PTR:0] w_gray_ff0 ; reg [PTR:0] w_gray_ff1 ; //wq2_rptr wire [PTR-1:0] w_addr ; wire full_flag ; //读时钟域 reg [PTR:0] r_bin ; wire [PTR:0] r_bin_next ; reg [PTR:0] r_gray ; wire [PTR:0] r_gray_next ; reg [PTR:0] r_gray_ff0 ; reg [PTR:0] r_gray_ff1 ; //rq2_wptr wire [PTR-1:0] r_addr ; wire empty_flag ; //读时钟域下 // 产生读地址计数(r_bin) 读指针(r_gray) 需要打一拍 always@(posedge r_clk or negedge r_rst_n) begin if(!r_rst_n) begin r_bin<='d0; r_gray<='d0; end else begin r_bin<=r_bin_next; r_gray<=r_gray_next; end end assign r_bin_next =r_bin+(r_en&&(!empty)); //在读使能有效 且未空 读地址+1 assign r_gray_next = (r_bin_next>>1)^r_bin_next; //二进制码转格雷码 assign r_addr=r_bin[PTR-1:0] ; //读地址 always@(posedge r_clk or negedge r_rst_n) begin //打两拍 将写指针同步至读时钟域下 if(!r_rst_n) begin r_gray_ff0<='d0; r_gray_ff1<='d0; end else begin r_gray_ff0<=w_gray; r_gray_ff1<=r_gray_ff0; //rq2_wptr end end //空信号标志产生 这里比较的是同步至读时钟域下写指针 和下一读指针比较 重点 assign empty_flag=(r_gray_ff1==r_gray_next)?1'b1:1'b0; //上面空信号标志产生 提前一个读时钟周期 产生真的空信号 需要打一拍 always@(posedge r_clk or negedge r_rst_n) begin if(!r_rst_n) empty<=1'b0; else empty<=empty_flag; end //开始读取双端口RAM中的数据 assign data_out = DPRAM[r_addr]; //需要注意的地方 与写入数据不同 //写时钟域下 // 产生写地址计数(w_bin) 写指针(w_gray) 需要打一拍 always@(posedge w_clk or negedge w_rst_n) begin if(!w_rst_n) begin w_bin<='d0; w_gray<='d0; end else begin w_bin<=w_bin_next; w_gray<=w_gray_next; end end assign w_gray_next =(w_bin_next>>1)^w_bin_next ; //在写使能有效 且未满 写地址+1 assign w_bin_next = w_bin +(w_en&&!full) ; //二进制码转格雷码 assign w_addr = w_bin[PTR-1:0] ; //读地址 //打两拍 将读指针同步至写时钟域下 always@(posedge w_clk or negedge w_rst_n) begin if(!w_rst_n) begin w_gray_ff0<='d0; w_gray_ff1<='d0; end else begin w_gray_ff0<=r_gray; w_gray_ff1<=w_gray_ff0; //wq2_rptr end end //满信号标志产生 这里比较的是同步至写时钟域下读指针 和下一写指针比较 重点 assign full_flag=(w_gray_next=={~w_gray_ff1[PTR:PTR-1],w_gray_ff1[PTR-2:0]})?1'b1:1'b0; //上面满信号标志产生 提前一个写时钟周期 产生真的空信号 需要打一拍 always@(posedge w_clk or negedge w_rst_n) begin if(!w_rst_n) full<=1'b0; else full<=full_flag; end //开始写入双端口RAM中的数据 在写使能有效 且非满下写入 always@(posedge w_clk or negedge w_rst_n) begin if(!w_rst_n) DPRAM[w_addr]<='d0; else if (w_en&&!full) DPRAM[w_addr] <= data_in; else DPRAM[w_addr]<='d0; end endmodule
`timescale 1ns/1ns module tb_asyn_fifo(); reg w_clk ; reg w_rst_n ; reg w_en ; reg [7:0] data_in ; wire full ; reg r_clk ; reg r_rst_n ; reg r_en ; wire [7:0] data_out ; wire empty ; initial begin w_clk<=1'b0 ; //模拟激励产生 r_clk<=1'b0 ; w_rst_n<=1'b0 ; r_rst_n<=1'b0 ; w_en<=1'b0 ; r_en<=1'b0 ; data_in<='d0 ; #20 w_rst_n<=1'b1 ; r_rst_n<=1'b1 ; #10 w_en<=1'b1 ; #200 r_en<=1'b1 ; #10 w_en<=1'b0 ; #200 r_en<=1'b0 ; #10 w_en<=1'b1 ; #300 w_en<=1'b0 ; #10 r_en<=1'b1 ; #400 r_en<=1'b0 ; #10 w_en<=1'b1 ; #500 w_en<=1'b0 ; #10 r_en<=1'b1 ; #100 w_en<=1'b1 ; #30 r_en<=1'b0 ; #10 w_en<=1'b0 ; #20 r_en<=1'b1 ; #20 w_en<=1'b1 ; #50 r_en<=1'b0 ; #10 w_en<=1'b0 ; #20 r_en<=1'b1 ; end always@(posedge w_clk or negedge w_rst_n) begin if(!w_rst_n) data_in<='d0; else if(w_en&&(!full)) data_in<=data_in+1'b1; else data_in<=data_in; end always #10 w_clk<= ~w_clk ; always #20 r_clk<= ~r_clk ; asyn_fifo #( .DATA_WIDTH(8 ) , .DATA_DEPTH(16) , .PTR (4 ) //2^4=16 ) asyn_fifo_inst ( //写时钟域 .w_clk (w_clk ) , .w_rst_n (w_rst_n ) , .w_en (w_en ) , .data_in (data_in ) , .full (full ) , //读时钟域 .r_clk (r_clk ) , .r_rst_n (r_rst_n ) , .r_en (r_en ) , .data_out (data_out) , .empty (empty ) ); endmodule
若有不对的地方,敬请指正,万分感谢。
参考资料:
1、Simulation and Synthesis Techniques for Asynchronous FIFO Design