異步FIFO簡介
異步FIFO(First In First Out)可以很好解決多比特數據跨時鍾域的數據傳輸與同步問題。異步FIFO的作用就像一個蓄水池,用於調節上下游水量。
FIFO
FIFO是一種先進先出的存儲結構,其與普通存儲器的區別是,FIFO沒有讀寫地址總線,讀寫簡單,但相應缺點是無法控制讀寫的位置,只能由內部的讀寫指針自動加,順序讀寫數據。FIFO示意圖如下:
如圖1所示,輸入信號有讀寫時鍾、讀寫復位信號、讀寫使能信號、寫數據;輸出信號有空滿信號、讀數據。
異步時序電路
異步時序邏輯指電路時序邏輯沒有接在統一的時鍾脈沖上,或者電路中無時鍾脈沖,如SR鎖存器構成的時序電路,電路中各存儲單元的狀態不是同時發生的。
如上圖所示,常見的異步時序電路有不同的時鍾。
相應地,同步時序電路指電路中所有受時鍾控制的單元都接在統一的全局時鍾源上,存儲電路狀態的轉換在同一時鍾源的同一脈沖邊沿下同步進行。
異步時序電路觸發器狀態刷新不同步,信號延遲可能會累積導致輸出結果異常,應當避免。
目前ASIC與FPGA的設計中,通常是全局異步,局部同步的設計方法,但需注意異步信號與同步電路的交互問題。
常用的異步時序邏輯同步的方法有:
- 單比特信號同步:結繩法
- 多比特信號同步:SRAM、異步FIFO
注意,時鍾域是否相同針對的是時鍾源點,如果不同時鍾都是從同一個PLL生成,則這些時鍾相位和倍數都可控,認為是同步時鍾;若不同時鍾是由不同PLL生成,則即使這些時鍾為相同頻率,也認為是異步時鍾,因為這些時鍾間的相位關系無法確定。
亞穩態
亞穩態指觸發器無法在某個規定的時間內到達一個可確定的狀態。當一個觸發器進入亞穩態時,無法確定該單元的輸出電平,也無法確定其何時能穩定在正確的電平。在此期間,觸發器輸出的一些不確定電平,可能沿着信號通道上的各個觸發器傳遞下去。
設計詳解
思路
如圖1,
- 異步FIFO是一個存儲結構,並且可讀可寫,因此需要一個雙口RAM;
- 其次異步FIFO讀寫時鍾域分別控制讀寫地址,因此需要讀寫地址生成模塊;
- 異步FIFO需要判斷是否已寫滿,或已讀空,且讀寫時鍾為異步時鍾,因此需要同步邏輯與判斷空滿邏輯
如圖3所示,DualRAM
為雙口RAM模塊,sync_w2r
模塊用於控制寫地址,判斷寫滿信號,sync_r2w
模塊用於控制讀地址,判斷讀空信號。
細節
雙口RAM
雙口RAM設計較為簡單,主要為生成一塊memory,將寫數據寫入寫地址對應內存空間,從讀地址對應內存空間讀取讀數據,原理圖如下:
小細節,已知地址寬度ASIZE
,求FIFO深度DEPTH
,DEPTH = 2 ^ ASIZE
,可用移位實現:
DEPTH = 1 << ASIZE;
判斷空滿
上文提到,FIFO只能通過內部地址指針自動加,因此需要有空滿判斷邏輯,以免寫數據溢出,讀數據已空。本設計中判斷空滿采用的方法是比較讀地址與寫地址。讀寫時鍾為異步時鍾,在判斷空滿時需要用格雷碼比較讀寫讀寫地址,因此需要對讀寫地址進行同步。
同步
同步使用打一拍的方法,即將待同步信號延時一個時鍾周期,原理圖如下:
在寫時鍾域同步讀地址,在讀時鍾域同步寫地址。
格雷碼
上述提到的打一拍的同步方法適合於單比特信號,但顯然讀寫地址都大概率不為單比特信號。我們知道格雷碼的特征為相鄰格雷碼間只有一位不同。將讀寫地址轉換為格雷碼即可應用打一拍的同步方法。
Binary | Gray |
---|---|
000 | 000 |
001 | 001 |
010 | 011 |
011 | 010 |
100 | 110 |
101 | 111 |
110 | 101 |
111 | 100 |
上圖為三位二進制碼與格雷碼的轉換。可以看出,二進制向格雷碼轉換時,格雷碼最高位為二進制碼最高位,格雷碼次高位為二進制碼最高位與次高位的異或,其余各位規律一致。
assign graynext = (binnext >> 1) ^ binnext; `
本設計為地址位寬為4位,利用格雷碼判斷空滿時需擴展1位.
判斷空
判斷FIFO是否為空,在讀時鍾域同步轉換為格雷碼的寫地址,與轉換為格雷碼的寫地址比較,如果讀寫地址的格雷碼完全相等,則說明FIFO已空。
//判斷空信號
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
判斷滿
判斷FIFO是否已滿也是比較讀寫地址。在二進制地址中,FIFO已滿時,讀寫地址相等,與已空時一樣,無法判斷。上述說到判斷空滿時地址需擴展一位,實際上如果已寫滿,說明寫比都快,那寫地址比讀地址多走一輪,此時擴展的最高位不相同,如4'b0000
與4'b1000
,最高位擴展位,4'b1000
說明比4'b0000
多走一輪。轉換為格雷碼分別為4'b0000
與4'b1100
。可知FIFO已滿時,讀寫地址格雷碼最高位與次高位相反,其余位相同。
//判斷滿信號
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
控制讀寫地址
控制讀寫地址時,若讀寫使能為0,則不讀寫,讀寫地址不變;若讀寫使能為1,則讀寫地址加1。
小技巧,可將讀寫地址加讀寫使能信號,則可巧妙實現上述功能。
assign binnext = !wfull? (wbin + write_en): wbin;
assign binnext = !rempty? (rbin + read_en): rbin;
仿真
VCS仿真結果如下
完整代碼
top.v
點擊查看代碼
`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
點擊查看代碼
`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_w2r.v
點擊查看代碼
`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
sync_r2w.v
點擊查看代碼
`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
DualRAM.v
點擊查看代碼
`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
點擊查看代碼
`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博客_同步時序電路和異步時序電路差異
同步時序電路和異步時序電路_ltfysa的博客-CSDN博客_同步時序電路和異步時序電路差異
芯動力——硬件加速設計方法_中國大學MOOC(慕課) (icourse163.org)
異步FIFO設計 - 知乎 (zhihu.com)
異步FIFO總結 - 喬_木 - 博客園 (cnblogs.com)