异步FIFO设计
作者:互联网
引言
异步FIFO被广泛使用在数字电路中,不论是作为数据buffer还是数据跨时钟域处理、不同位宽数据的缓冲。
本人以往的使用经验都是直接调用IP。但是异步FIFO的原理也是应该熟悉的,这样一种经典的电路设计十分巧妙,如何做到安全缓冲并有效避免了数据上溢或下溢?
本文参考了CE Cummings的经典论文“Simulation and Synthesis Techniques for Asynchronous FIFO Design”
设计框架
整体的框架如下所示:(ps.结构精妙,且以一种非协调的对称美深深地抓住了读者的眼球)
设计主要分为4个部分即:
- 写控制端
- 读控制端
- 读指针同步电路、写指针同步电路
- 双端口RAM
下面对各个模块作一个简单的介绍:
一、写控制端
winc即写使能,当它拉高时写控制端内部的写地址自增。在这模块中需要产生写满标志(后文以设计一个8位宽,深度为16的异步FIFO为例),判断是不是写满了,需要写指针与读指针进行比较,这里读指针是读控制端这个模块产生的,因此需要将这个处在读时钟下的信号同步到写时钟下的写控制端模块来,这就是sync_r2w模块的意义,一个二级同步电路对读指针打两拍。而写满标志的产生条件是:写指针赶上读指针——假设读写指针都从0开始,那么写指针爬完了地址然后回到原点,这时候就是赶上了读指针。
这里有两个关键点:
一是要使用格雷码作为比较的读写指针即wptr和rptr(然而waddr和raddr保持是二进制指针),因为FIFO读写指针是多位数据进行比较,因此地址变化时容易产生毛刺,但是如果采用格雷码一次只跳变一位,如果跳变正确就没出错,如果跳变错误,本来是000到001,跳变失败变成了000,由于异步FIFO的读写裕量即读写指针的一个延迟情况,一般来说也不会影响到空满信号的产生。另外也比较节能,翻转率降低了。
二是格雷码读写指针进行比较,那么满信号的产生要比二进制地址多一位,且以MSB前2位进行判断。多出来的一位,即地址是log2(16)=4位宽,这里需要设计5位的格雷码读写指针, 因为空信号的判断是读指针=写指针。那么满信号的判断就不能用这个条件,而是多一位代表了写指针多跑了一圈,即读指针是0,而写指针是1,或反之的情况时,代表了写指针赶上一圈达到读指针的位置。但是还存在一个情况,不能仅仅用第一位进行判断,如下图所示:
考虑写指针在7即0100而读指针在8即1100,这时候最高位不同但其他位相同,但并不是写满了的情况。
因此正确的方法是用高2位进行判断,高两位不相同而其他位相同时代表了写满。full信号拉高。
二、读控制端
这个模块分析类似,但是空信号的判断条件是经过同步的写指针=读指针。
三、读指针同步电路、写指针同步电路
打两拍消除亚稳态的影响。简单的两级同步。
四、双端口RAM
可以调用IP或者自己用寄存器设计。
巧妙之处
开篇说的巧妙就在于不同读写时钟频率下也能做到数据的正确缓冲,根据上文的框图可以注意到写指针同步到读时钟域与读指针进行比较,当写指针同步到读端时,在这一时刻与读指针相同,即空信号可以拉高,外部不能继续读数据,但是实际上这一时刻写端地址也同时增加了几次,即在这个同步的两拍内仍然写了几个数据进去。因此这个空并不是真正的空,这也保证外部端口如果继续请求FIFO的数据不会读空。反之亦然,满并不标志着真正的满,满信号拉高时读端已然读出了几个数据,因此外部在满信号拉高后如果继续写数据进去也能保证一定的正确写入。不会丢失掉这部分数据。
代码及仿真结果
1.顶层代码
1 module asfifo( 2 input wire wclk, 3 input wire wrst_n, 4 input wire winc, 5 input wire [7:0] wdata, 6 input wire rclk, 7 input wire rrst_n, 8 input wire rinc, 9 output wire [7:0] rdata, 10 output wire wfull, 11 output wire rempty 12 ); 13 14 wire [3:0] waddr; 15 wire [3:0] raddr; 16 wire [4:0] wptr,rptr; 17 wire [4:0] rq2_wptr; 18 wire [4:0] wq2_rptr; 19 wire full; 20 wire empty; 21 wire [7:0] rdata_o; 22 23 assign wfull = full; 24 assign rempty = empty; 25 assign rdata = rdata_o; 26 27 //inst mem 28 fifomem inst_fifomem 29 ( 30 .wclk (wclk), 31 .wdata (wdata), 32 .wclken (winc), 33 .waddr (waddr), 34 .raddr (raddr), 35 .rdata (rdata_o), 36 .wfull (full) 37 ); 38 39 //inst wptr_full 40 wptr_full inst_wptr_full 41 ( 42 .wclk (wclk), 43 .wrst_n (wrst_n), 44 .winc (winc), 45 .wq2_rptr (wq2_rptr), 46 .wfull (full), 47 .waddr (waddr), 48 .wptr (wptr) 49 ); 50 51 //inst rptr_empty 52 rptr_empty inst_rptr_empty 53 ( 54 .rclk (rclk), 55 .rrst_n (rrst_n), 56 .rinc (rinc), 57 .rq2_wptr (rq2_wptr), 58 .raddr (raddr), 59 .rptr (rptr), 60 .rempty (empty) 61 ); 62 63 64 //inst sync_w2r 65 sync_w2r inst_sync_w2r (.rclk(rclk), .rrst_n(rrst_n), .wptr(wptr), .rq2_wptr(rq2_wptr)); 66 67 //inst sync_r2w 68 sync_r2w inst_sync_r2w (.wclk(wclk), .wrst_n(wrst_n), .rptr(rptr), .wq2_rptr(wq2_rptr)); 69 70 71 72 endmoduleView Code
2.写控制模块
1 module wptr_full( 2 input wire wclk, 3 input wire wrst_n, 4 input wire winc, 5 input wire [4:0] wq2_rptr, 6 output reg wfull, 7 output wire [3:0] waddr, 8 output reg [4:0] wptr 9 ); 10 11 reg [4:0] wbin; 12 wire [4:0] wgreynext,wbinnext; 13 wire wfullnext; 14 15 assign wbinnext = wbin + (winc & ~wfull); 16 assign wgreynext = (wbinnext>>1) ^ wbinnext; 17 assign waddr = wbin[3:0]; 18 19 always @(posedge wclk) begin 20 if (wrst_n==1'b0) begin 21 // reset 22 wbin <= 'd0; 23 wptr <= 'd0; 24 end 25 else begin 26 wbin <= wbinnext; 27 wptr <= wgreynext; 28 end 29 end 30 31 assign wfullnext = (wgreynext[4:3]!=wq2_rptr[4:3]) && (wgreynext[2:0]==wq2_rptr[2:0]); 32 33 always @(posedge wclk) begin 34 if (wrst_n==1'b0) begin 35 // reset 36 wfull <= 1'b0; 37 end 38 else begin 39 wfull <= wfullnext; 40 end 41 end 42 43 endmoduleView Code
3.读控制模块
1 module rptr_empty( 2 input wire rclk, 3 input wire rrst_n, 4 input wire rinc, 5 input wire [4:0] rq2_wptr, 6 output wire [3:0] raddr, 7 output reg [4:0] rptr, 8 output reg rempty 9 ); 10 11 reg [4:0] rbin; 12 wire [4:0] rbinnext,rgreynext; 13 wire remptynext; 14 15 assign rbinnext = rbin + (rinc & ~rempty); 16 assign rgreynext = (rbinnext>>1) ^ rbinnext; 17 assign raddr = rbin[3:0]; 18 19 always @(posedge rclk) begin 20 if (rrst_n==1'b0) begin 21 // reset 22 rbin <= 'd0; 23 rptr <= 'd0; 24 end 25 else begin 26 rbin <= rbinnext; 27 rptr <= rgreynext; 28 end 29 end 30 31 assign remptynext = rgreynext==rq2_wptr; 32 33 always @(posedge rclk) begin 34 if (rrst_n==1'b0) begin 35 // reset 36 rempty <= 1'b1; 37 end 38 else begin 39 rempty <= remptynext; 40 end 41 end 42 43 44 endmoduleView Code
4.读指针同步、写指针同步
1 module sync_w2r( 2 input wire rclk, 3 input wire rrst_n, 4 input wire [4:0] wptr, 5 output wire [4:0] rq2_wptr 6 ); 7 8 reg [4:0] rq1_wptr_r,rq2_wptr_r; 9 10 assign rq2_wptr = rq2_wptr_r; 11 12 always @(posedge rclk) begin 13 if (rrst_n==1'b0) begin 14 // reset 15 rq1_wptr_r <= 'd0; 16 rq2_wptr_r <= 'd0; 17 end 18 else begin 19 rq1_wptr_r <= wptr; 20 rq2_wptr_r <= rq1_wptr_r; 21 end 22 end 23 24 25 endmoduleView Code
1 module sync_r2w( 2 input wire wclk, 3 input wire wrst_n, 4 input wire [4:0] rptr, 5 output wire [4:0] wq2_rptr 6 ); 7 8 reg [4:0] wq1_rptr_r,wq2_rptr_r; 9 10 assign wq2_rptr = wq2_rptr_r; 11 12 always @(posedge wclk) begin 13 if (wrst_n==1'b0) begin 14 // reset 15 wq1_rptr_r <= 'd0; 16 wq2_rptr_r <= 'd0; 17 end 18 else begin 19 wq1_rptr_r <= rptr; 20 wq2_rptr_r <= wq1_rptr_r; 21 end 22 end 23 24 25 endmoduleView Code
5.双口ram
1 module fifomem( 2 input wire wclk, 3 input wire [7:0] wdata, 4 input wire wclken, 5 input wire [3:0] waddr, 6 input wire [3:0] raddr, 7 output wire [7:0] rdata, 8 input wire wfull 9 ); 10 11 reg [7:0] mem [0:15]; 12 13 assign rdata = mem[raddr]; 14 15 always @(posedge wclk) begin 16 if (wclken==1'b1 && wfull!=1'b1) begin 17 mem[waddr] <= wdata; 18 end 19 end 20 21 22 23 endmoduleView Code
6.测试文件
1 `timescale 1ns/1ps 2 module tb_asfifo(); 3 4 reg wclk; 5 reg wrst_n; 6 reg winc; 7 reg [7:0] wdata; 8 reg rclk; 9 reg rrst_n; 10 reg rinc; 11 wire [7:0] rdata; 12 wire wfull; 13 wire rempty; 14 15 initial begin 16 wclk = 0; 17 rclk = 0; 18 wrst_n = 0; 19 rrst_n = 0; 20 winc = 0; 21 rinc = 0; 22 wdata = 0; 23 #100 24 rrst_n = 1; 25 wrst_n = 1; 26 #3000 27 rinc = 1; 28 end 29 30 always #5 wclk = ~wclk; 31 always #10 rclk = ~rclk; 32 33 initial begin 34 gen_data(); 35 end 36 37 task gen_data; 38 integer i; 39 begin 40 @(posedge wrst_n); 41 for(i=0;i<16;i=i+1)begin 42 @(posedge wclk); 43 wdata = i; 44 winc = 1; 45 end 46 @(posedge wclk); 47 winc = 0; 48 wdata = 0; 49 end 50 endtask 51 52 53 //inst asfifo 54 asfifo inst_asfifo 55 ( 56 .wclk (wclk), 57 .wrst_n (wrst_n), 58 .winc (winc), 59 .wdata (wdata), 60 .rclk (rclk), 61 .rrst_n (rrst_n), 62 .rinc (rinc), 63 .rdata (rdata), 64 .wfull (wfull), 65 .rempty (rempty) 66 ); 67 68 69 70 endmoduleView Code
Modelsim仿真结果如下图所示:
首先写入16个数据,这里可以看到空信号并不是一写入数据就拉低的,而是延迟了几个时钟周期。同样满信号提前了一拍就拉高了。因为写时钟是读时钟频率的2倍,因此两者的延迟也不相同。
接着读出这16个数据,同样满信号在读到第三个数据时才拉低,这里空信号正好在读到最后一个数据时拉高了。
参考文献
Verilog E , Cummings C E . Simulation and Synthesis Techniques for Asynchronous FIFO Design[J]. Snug, 2002.
标签:异步,wire,rptr,wptr,FIFO,wclk,设计,input,指针 来源: https://www.cnblogs.com/Achilles7/p/15980826.html