| ## 异步FIFO简介 |
| ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| 异步FIFO(First In First Out)可以很好解决多比特数据跨时钟域的数据传输与同步问题。异步FIFO的作用就像一个蓄水池,用于调节上下游水量。 |
| |
| ### FIFO |
| |
| FIFO是一种先进先出的存储结构,其与普通存储器的区别是,FIFO没有读写地址总线,读写简单,但相应缺点是无法控制读写的位置,只能由内部的读写指针自动加,顺序读写数据。FIFO示意图如下: |
| |
| ![](https://www.www.zyiz.net/i/l/?n=20&i=blog/2674620/202112/2674620-20211208224741152-933795124.png)
|
| |
| 图1 |
| |
| 如图1所示,输入信号有读写时钟、读写复位信号、读写使能信号、写数据;输出信号有空满信号、读数据。 |
| |
| ### 异步时序电路 |
| |
| 异步时序逻辑指电路时序逻辑没有接在统一的时钟脉冲上,或者电路中无时钟脉冲,如SR锁存器构成的时序电路,电路中各存储单元的状态不是同时发生的。 |
| |
| ![](https://www.www.zyiz.net/i/l/?n=20&i=blog/2674620/202112/2674620-20211208224804179-90982391.png)
|
| |
| 图2 |
| |
| 如上图所示,常见的异步时序电路有不同的时钟。 |
| |
| 相应地,同步时序电路指电路中所有受时钟控制的单元都接在统一的全局时钟源上,存储电路状态的转换在同一时钟源的同一脉冲边沿下同步进行。 |
| |
| 异步时序电路触发器状态刷新不同步,信号延迟可能会累积导致输出结果异常,应当避免。 |
| |
| 目前ASIC与FPGA的设计中,通常是全局异步,局部同步的设计方法,但需注意异步信号与同步电路的交互问题。 |
| |
| 常用的异步时序逻辑同步的方法有: |
| |
| + 单比特信号同步:结绳法 |
| + 多比特信号同步:SRAM、**异步FIFO** |
| |
| 注意,时钟域是否相同针对的是时钟源点,如果不同时钟都是从同一个PLL生成,则这些时钟相位和倍数都可控,认为是同步时钟;若不同时钟是由不同PLL生成,则即使这些时钟为相同频率,也认为是异步时钟,因为这些时钟间的相位关系无法确定。 |
| |
| ### 亚稳态 |
| |
| 亚稳态指触发器无法在某个规定的时间内到达一个可确定的状态。当一个触发器进入亚稳态时,无法确定该单元的输出电平,也无法确定其何时能稳定在正确的电平。在此期间,触发器输出的一些不确定电平,可能沿着信号通道上的各个触发器传递下去。 |
| |
| ## 设计详解 |
| |
| ### 思路 |
| |
| 如图1, |
| |
| + 异步FIFO是一个存储结构,并且可读可写,因此需要一个双口RAM; |
| |
| + 其次异步FIFO读写时钟域分别控制读写地址,因此需要读写地址生成模块; |
| + 异步FIFO需要判断是否已写满,或已读空,且读写时钟为异步时钟,因此需要同步逻辑与判断空满逻辑。 |
| |
| ![](https://www.www.zyiz.net/i/l/?n=20&i=blog/2674620/202112/2674620-20211208224855541-1993712051.png)
|
| |
| 图3 |
| |
| 如图3所示,`DualRAM`为双口RAM模块,`sync_w2r`模块用于控制写地址,判断写满信号,`sync_r2w`模块用于控制读地址,判断读空信号。 |
| |
| ### 细节 |
| |
| #### 双口RAM |
| |
| 双口RAM设计较为简单,主要为生成一块memory,将写数据写入写地址对应内存空间,从读地址对应内存空间读取读数据,原理图如下: |
| |
| ![](https://www.www.zyiz.net/i/l/?n=20&i=blog/2674620/202112/2674620-20211208224916505-1520496566.png)
|
| |
| 图4 |
| |
| 小细节,已知地址宽度`ASIZE`,求FIFO深度`DEPTH`,`DEPTH = 2 ^ ASIZE`,可用移位实现: |
| |
| ```verilog |
| DEPTH = 1 << ASIZE; |
| ``` |
| |
| #### 判断空满 |
| |
| 上文提到,FIFO只能通过内部地址指针自动加,因此需要有空满判断逻辑,以免写数据溢出,读数据已空。本设计中判断空满采用的方法是比较读地址与写地址。读写时钟为异步时钟,在判断空满时需要用格雷码比较读写读写地址,因此需要对读写地址进行同步。 |
| |
| ##### 同步 |
| |
| 同步使用打一拍的方法,即将待同步信号延时一个时钟周期,原理图如下: |
| |
| ![](https://www.www.zyiz.net/i/l/?n=20&i=blog/2674620/202112/2674620-20211208224935673-1006804122.png)
|
| |
| 图5 |
| |
| 在写时钟域同步读地址,在读时钟域同步写地址。 |
| |
| ##### 格雷码 |
| |
| 上述提到的打一拍的同步方法适合于单比特信号,但显然读写地址都大概率不为单比特信号。我们知道格雷码的特征为相邻格雷码间只有一位不同。将读写地址转换为格雷码就即可应用打一拍的同步方法。 |
| |
| | Binary | Gray | |
| | :----: | :--: | |
| | 000 | 000 | |
| | 001 | 001 | |
| | 010 | 011 | |
| | 011 | 010 | |
| | 100 | 110 | |
| | 101 | 111 | |
| | 110 | 101 | |
| | 111 | 100 | |
| |
| 图6 |
| |
| 上图为三位二进制码与格雷码的转换。可以看出,二进制向格雷码转换时,格雷码最高位为二进制码最高位,格雷码次高位为二进制码最高位与次高位的异或,其余各位规律一致。 |
| |
| ```verilog |
| assign graynext = (binnext >> 1) ^ binnext; |
| ``` |
| |
| 本设计为地址位宽为4位,利用格雷码判断空满时需扩展1位。 |
| |
| ##### 判断空 |
| |
| 判断FIFO是否为空,在读时钟域同步转换为格雷码的写地址,与转换为格雷码的写地址比较,如果读写地址的格雷码完全相等,则说明FIFO已空。 |
| |
| ```verilog |
| // 判断空信号 |
| assign empty = (graynext == rq2_wptr); |
| always @(posedge rclk or negedge rrst_n) begin |
| if (!rrst_n) begin |
| rempty <= 1'b1; |
| end |
| else begin |
| rempty <= empty; |
| end |
| ``` |
| |
| ##### 判断满 |
| |
| 判断FIFO是否已满也是比较读写地址。在二进制地址中,FIFO已满时,读写地址相等,与已空时一样,无法判断。上述说到判断空满时地址需扩展一位,实际上如果已写满,说明写比都快,那写地址比读地址多走一轮,此时扩展的最高位不相同,如`4‘b0000`与`4’b1000`,最高位位扩展位,`4'b1000`说明比`4’b0000`多走一轮。转换为格雷码分别为`4'b0000`与`4’b1100`。可知FIFO已满时,读写地址格雷码最高位与次高位相反,其余位相同。 |
| |
| ```verilog |
| //判断满信号 |
| assign full = (graynext == {~wq2_rptr[ASIZE: ASIZE - 1], wq2_rptr[ASIZE - 2: 0]}); |
| always @(posedge wclk or negedge wrst_n) begin |
| if (!wrst_n) begin |
| wfull <= 1'b0; |
| end |
| else begin |
| wfull <= full; |
| end |
| ``` |
| |
| #### 控制读写地址 |
| |
| 控制读写地址时,若读写使能为0,则不读写,读写地址不变;若读写使能为1,则读写地址加1。 |
| |
| 小技巧,可将读写地址加读写使能信号,则可巧妙实现上述功能。 |
| |
| ```verilog |
| assign binnext = !wfull? (wbin + write_en): wbin; |
| assign binnext = !rempty? (rbin + read_en): rbin; |
| ``` |
| |
| ## 仿真 |
| |
| VCS仿真结果如下: |
| |
| ![](https://www.www.zyiz.net/i/l/?n=20&i=blog/2674620/202112/2674620-20211208224954450-136035303.png)
|
| |
| 图7 |
| |
| ## 完整代码 |
| |
| top.v |
| |
| ```verilog |
| `timescale 1ns / 1ns |
| module Top #( |
| parameter WIDTH = 8, |
| parameter ASIZE = 4 //地址位宽 |
| ) ( |
| input wire [WIDTH - 1: 0] data_w, |
| input wire wclk, |
| input wire rclk, |
| input wire wrst_n, |
| input wire rrst_n, |
| input wire write_en, |
| input wire read_en, |
| output wire wfull, |
| output wire rempty, |
| output wire [WIDTH - 1: 0] data_r |
| ); |
| |
| wire [ASIZE - 1: 0] waddr, raddr; |
| wire [ASIZE: 0] wq2_rptr, rq2_wptr; |
| DualRAM #(WIDTH, ASIZE) u1( |
| .wclk(wclk), |
| .data_w(data_w), |
| .write_en(write_en), |
| .addr_w(waddr), |
| .addr_r(raddr), |
| .data_r(data_r) |
| ); |
| |
| sync_r2w #(WIDTH, ASIZE) u2( |
| .rclk(rclk), |
| .wclk(wclk), |
| .wrst_n(wrst_n), |
| .rrst_n(rrst_n), |
| .read_en(read_en), |
| .wq2_rptr(wq2_rptr), |
| .raddr(raddr), |
| .rq2_wptr(rq2_wptr), |
| .rempty(rempty) |
| ); |
| |
| sync_w2r #(WIDTH, ASIZE) u3( |
| .rclk(rclk), |
| .wclk(wclk), |
| .wrst_n(wrst_n), |
| .rrst_n(rrst_n), |
| .write_en(write_en), |
| .wq2_rptr(wq2_rptr), |
| .waddr(waddr), |
| .rq2_wptr(rq2_wptr), |
| .wfull(wfull) |
| ); |
| |
| endmodule |
| ``` |
| |
| sync.v |
| |
| ```verilog |
| `timescale 1ns / 1ns |
| |
| module sync #( |
| parameter WIDTH = 8, |
| parameter ASIZE = 4 |
| ) ( |
| input wire clk, |
| input wire rst_n, |
| input wire [ASIZE: 0] ptr, |
| output reg [ASIZE: 0] q2ptr |
| ); |
| |
| reg [ASIZE: 0] q1ptr; |
| always @(posedge clk or negedge rst_n) begin |
| if (!rst_n) begin |
| {q2ptr, q1ptr} <= 0; |
| end |
| else begin |
| {q2ptr, q1ptr} <= {q1ptr, ptr}; |
| end |
| end |
| |
| endmodule |
| ``` |
| |
| sync_r2w.v |
| |
| ```verilog |
| `timescale 1ns / 1ns |
| |
| module sync_r2w #( |
| parameter WIDTH = 8, |
| parameter ASIZE = 4 //地址位宽 |
| ) ( |
| input wire rclk, |
| input wire wclk, |
| input wire wrst_n, |
| input wire rrst_n, |
| input wire read_en, |
| input wire [ASIZE: 0] rq2_wptr, |
| output wire [ASIZE: 0] wq2_rptr, |
| output wire [ASIZE - 1: 0] raddr, |
| output reg rempty |
| ); |
| |
| reg [ASIZE: 0] rbin, rptr; |
| wire [ASIZE: 0] graynext, binnext; |
| |
| // 读地址逻辑 |
| always @(posedge rclk or negedge rrst_n) begin |
| if (!rrst_n) begin |
| rbin <= 'd0; |
| rptr <= 'd0; |
| end |
| else begin |
| rbin <= binnext; |
| rptr <= graynext; |
| end |
| end |
| |
| assign binnext = !rempty? (rbin + read_en): rbin; |
| assign graynext = (binnext >> 1) ^ binnext; |
| assign raddr = rbin[ASIZE - 1: 0]; |
| |
| // 同步读地址 |
| sync #(WIDTH, ASIZE) u_sync( |
| .clk(wclk), |
| .rst_n(wrst_n), |
| .ptr(rptr), |
| .q2ptr(wq2_rptr) |
| ); |
| |
| // 判断空信号 |
| assign empty = (graynext == rq2_wptr); |
| always @(posedge rclk or negedge rrst_n) begin |
| if (!rrst_n) begin |
| rempty <= 1'b1; |
| end |
| else begin |
| rempty <= empty; |
| end |
| end |
| |
| endmodule |
| ``` |
| |
| sync_w2r.v |
| |
| ```verilog |
| `timescale 1ns / 1ns |
| |
| module sync_w2r #( |
| parameter WIDTH = 8, |
| parameter ASIZE = 4 //地址位宽 |
| ) ( |
| input wire rclk, |
| input wire wclk, |
| input wire wrst_n, |
| input wire rrst_n, |
| input wire write_en, |
| input wire [ASIZE: 0] wq2_rptr, |
| output wire [ASIZE: 0] rq2_wptr, |
| output wire [ASIZE - 1: 0] waddr, |
| output reg wfull |
| ); |
| |
| wire [ASIZE: 0] graynext, binnext; |
| reg [ASIZE: 0] wbin, wptr; |
| // 写地址控制 |
| always @(posedge wclk or negedge wrst_n) begin |
| if (!wrst_n) begin |
| wbin <= 'd0; |
| wptr <= 'd0; |
| end |
| else begin |
| wbin <= binnext; |
| wptr <= graynext; |
| end |
| end |
| |
| assign binnext = !wfull? (wbin + write_en): wbin; |
| assign graynext = (binnext >> 1) ^ binnext; |
| assign waddr = wbin[ASIZE - 1: 0]; |
| |
| //同步写地址 |
| sync #(WIDTH, ASIZE) u_sync( |
| .clk(rclk), |
| .rst_n(rrst_n), |
| .ptr(wptr), |
| .q2ptr(rq2_wptr) |
| ); |
| |
| //判断满信号 |
| assign full = (graynext == {~wq2_rptr[ASIZE: ASIZE - 1], wq2_rptr[ASIZE - 2: 0]}); |
| always @(posedge wclk or negedge wrst_n) begin |
| if (!wrst_n) begin |
| wfull <= 1'b0; |
| end |
| else begin |
| wfull <= full; |
| end |
| end |
| |
| endmodule |
| ``` |
| |
| DualRAM.v |
| |
| ```verilog |
| `timescale 1ns / 1ns |
| |
| module DualRAM #( |
| parameter WIDTH = 8, |
| parameter ASIZE = 4 //地址位宽 |
| ) ( |
| input wire [WIDTH - 1: 0] data_w, |
| input wire wclk, |
| input wire write_en, |
| input wire [ASIZE - 1: 0] addr_w, |
| input wire [ASIZE - 1: 0] addr_r, |
| output wire [WIDTH - 1: 0] data_r |
| ); |
| |
| parameter DEPTH = 1 << ASIZE; |
| |
| reg [WIDTH - 1: 0] mem [DEPTH - 1: 0]; |
| reg [ASIZE - 1: 0] raddr_r; |
| |
| always @(posedge wclk) begin |
| if (write_en) begin |
| mem[addr_w] <= data_w; |
| end |
| end |
| assign data_r = mem[addr_r]; |
| endmodule |
| ``` |
| |
| top_tb.sv |
| |
| ```verilog |
| `timescale 1ns / 1ns |
| |
| module top_tb #( |
| parameter WIDTH = 8, |
| parameter ASIZE = 4 //地址位宽 |
| ) ( |
| ); |
| |
| reg [WIDTH - 1: 0] data_w; |
| reg wclk; |
| reg rclk; |
| reg wrst_n; |
| reg rrst_n; |
| reg write_en; |
| reg read_en; |
| wire wfull; |
| wire rempty; |
| wire [WIDTH - 1: 0] data_r; |
| |
| Top u1( |
| .data_w(data_w), |
| .wclk(wclk), |
| .rclk(rclk), |
| .wrst_n(wrst_n), |
| .rrst_n(rrst_n), |
| .write_en(write_en), |
| .read_en(read_en), |
| .wfull(wfull), |
| .rempty(rempty), |
| .data_r(data_r) |
| ); |
| |
| initial begin |
| data_w = 1'b0; |
| wclk = 1'b0; |
| rclk = 1'b0; |
| wrst_n = 1'b1; |
| rrst_n = 1'b1; |
| write_en = 1'b1; |
| read_en = 1'b1; |
| #10 wrst_n = 1'b0; |
| rrst_n = 1'b0; |
| #10 wrst_n = 1'b1; |
| rrst_n = 1'b1; |
| #400 read_en = 1'b0; |
| end |
| |
| initial begin |
| forever begin |
| #10 wclk = ~wclk; |
| end |
| end |
| |
| initial begin |
| forever begin |
| #7 rclk = ~rclk; |
| end |
| end |
| |
| |
| reg [7: 0] i; |
| reg [7: 0] mem [15: 0]; |
| initial begin |
| for (i = 8'd0; i < 8'd16; i = i + 8'd1) begin |
| mem[i] = i; |
| end |
| end |
| |
| reg [7: 0] j = 8'd0; |
| |
| always @(posedge wclk or negedge wrst_n) begin |
| if (!wrst_n) begin |
| j = 8'd0; |
| end |
| if (j > 8'd15) begin |
| j = 8'd0; |
| end |
| else begin |
| data_w = mem[j]; |
| j = j + 8'd1; |
| end |
| |
| end |
| |
| |
| |
| endmodule |
| ``` |
| |
| |
| |
| > 参考链接 |
| > |
| > [Verilog描述——异步时序电路与同步时序电路浅析_我要变强Wow-CSDN博客_同步时序电路和异步时序电路差异](https://blog.csdn.net/sinat_31206523/article/details/103837133?spm=1001.2101.3001.6650.1&utm_medium=distribute.pc_relevant.none-task-blog-2~default~CTRLIST~default-1.fixedcolumn&depth_1-utm_source=distribute.pc_relevant.none-task-blog-2~default~CTRLIST~default-1.fixedcolumn) |
| > |
| > [同步时序电路和异步时序电路_ltfysa的博客-CSDN博客_同步时序电路和异步时序电路差异](https://blog.csdn.net/weixin_44453255/article/details/92764440?spm=1001.2101.3001.6650.1&utm_medium=distribute.pc_relevant.none-task-blog-2~default~CTRLIST~default-1.no_search_link&depth_1-utm_source=distribute.pc_relevant.none-task-blog-2~default~CTRLIST~default-1.no_search_link) |
| > |
| > [芯动力——硬件加速设计方法_中国大学MOOC(慕课) (icourse163.org)](https://www.icourse163.org/learn/SWJTU-1207492806?tid=1207824209#/learn/content?type=detail&id=1213980322&sm=1) |
| > |
| > [异步FIFO设计 - 知乎 (zhihu.com)](https://zhuanlan.zhihu.com/p/101519479) |
| > |
| > [异步FIFO总结 - 乔_木 - 博客园 (cnblogs.com)](https://www.cnblogs.com/OneFri/p/7141368.html) |