參考:https://www.cnblogs.com/aslmer/p/6114216.html
文章:Simulation and Synthesis Techniques for Asynchronous
Asynchronous FIFO Design
異步FIFO的讀寫指針
寫指針
寫指針指向當前將要寫入數據的位置,復位之后,讀寫指針被置零。
執行寫操作的時候,向寫指針指向的存儲區寫入數據,之后寫指針加1,指向接下來要被寫入數據的位置。
On a FIFO-write operation, the memory location that is pointed to by the write pointer is written, and then the write pointer is incremented to point to the next location to be written.
讀指針:
讀指針指向當前要被讀取數據的位置,復位時,讀寫指針被置零,FIFO為空讀指針指向一個無效的數據(FIFO為空,empty信號有效——拉高)。當第一個有效數據被寫入FIFO之后,寫指針增加,empty flag信號被拉低,且讀指針一直指向FIFO第一個數據的存儲區域。接收邏輯沒必要使用兩個時鍾周期讀取數據,這樣會使得效率很低。
FIFO空標志:
當讀寫指針是相等的時候:分兩種情況
1.當讀寫指針執行復位操作的時候。
2.當讀指針趕上寫指針的時候,最后一筆數據從FIFO讀出后FIFO為空
FIFO滿標志:
讀寫指針相等,當FIFO里面的寫指針寫滿一圈之后又轉回到和讀指針同樣的位置。有個問題,讀寫指針相等的時候怎么判斷FIFO是empty還是full?
設計的時候增加一位bit去輔助判斷FIFO是空還是滿。當寫指針超過FIFO的最大尋址范圍時,寫指針將使輔助位zhi高,其余位為0.
FIFO滿的時候:讀寫指針的低位(n-1位bit)相等,高位(第n位bit)不同。
FIFO空的時候,讀寫指針的低位和高位都相等。(針對二進制)
但是二進制FIFO指針綜合電路復雜,一般采用**格雷碼**,文章中采用二進制轉換格雷碼的方法,判斷FIFO的空滿標志4位二進制格雷碼,有效地址位為三位。
二進制轉換為格雷碼的算法:rgraynext = (rbinnext>>1) ^ rbinnext;
1.頂層模塊fifo:例化各個子模塊
//頂層模塊 實例化各個子模塊
module fifo
#(
parameter DSIZE = 8, //讀寫數據位寬均設置為8位
parameter ASIZE = 4 // 存儲地址位寬設置
)
(
output [DSIZE-1:0] rdata,
output wfull,
output rempty,
input [DSIZE-1:0] wdata,
input winc, wclk, wrst_n,
input rinc, rclk, rrst_n
);
wire [ASIZE-1:0] waddr, raddr;
wire [ASIZE:0] wptr, rptr, wq2_rptr, rq2_wptr;// 內部線網
// synchronize the read pointer into the write-clock domain
sync_r2w sync_r2w
(
.wq2_rptr (wq2_rptr),
.rptr (rptr ),
.wclk (wclk ),
.wrst_n (wrst_n )
);
// synchronize the write pointer into the read-clock domain
sync_w2r sync_w2r
(
.rq2_wptr(rq2_wptr),
.wptr(wptr),
.rclk(rclk),
.rrst_n(rrst_n)
);
//this is the FIFO memory buffer that is accessed by both the write and read clock domains.
//This buffer is most likely an instantiated, synchronous dual-port RAM.
//Other memory styles can be adapted to function as the FIFO buffer.
fifomem
#(DSIZE, ASIZE)
fifomem
(
.rdata(rdata),
.wdata(wdata),
.waddr(waddr),
.raddr(raddr),
.wclken(winc),
.wfull(wfull),
.wclk(wclk)
);
//this module is completely synchronous to the read-clock domain and contains the FIFO read pointer and empty-flag logic.
rptr_empty
#(ASIZE)
rptr_empty
(
.rempty(rempty),
.raddr(raddr),
.rptr(rptr),
.rq2_wptr(rq2_wptr),
.rinc(rinc),
.rclk(rclk),
.rrst_n(rrst_n)
);
//this module is completely synchronous to the write-clock domain and contains the FIFO write pointer and full-flag logic
wptr_full
#(ASIZE)
wptr_full
(
.wfull(wfull),
.waddr(waddr),
.wptr(wptr),
.wq2_rptr(wq2_rptr),
.winc(winc),
.wclk(wclk),
.wrst_n(wrst_n)
);
endmodule
2.時鍾域同步模塊sync_r2w:讀指針同步到寫時鍾域wclk
// 采用兩級寄存器同步讀指針到寫時鍾域
module sync_r2w
#(
parameter ADDRSIZE = 4
)
(
output reg [ADDRSIZE:0] wq2_rptr, //讀指針同步到寫時鍾域
input [ADDRSIZE:0] rptr, // 格雷碼形式的讀指針,格雷碼的好處后面會細說
input wclk, wrst_n
);
reg [ADDRSIZE:0] wq1_rptr;
always @(posedge wclk or negedge wrst_n)
if (!wrst_n) begin
wq1_rptr <= 0;
wq2_rptr <= 0;
end
else begin
wq1_rptr<= rptr;
wq2_rptr<=wq1_rptr;
end
endmodule
原理圖
3.時鍾域同步模塊sync_w2r:寫指針同步到讀時鍾域rclk
//采用兩級寄存器同步寫指針到讀時鍾域
module sync_w2r
#(parameter ADDRSIZE = 4)
(
output reg [ADDRSIZE:0] rq2_wptr, //寫指針同步到讀時鍾域
input [ADDRSIZE:0] wptr, //格雷碼形式的寫指針
input rclk, rrst_n
);
reg [ADDRSIZE:0] rq1_wptr;
always @(posedge rclk or negedge rrst_n)
if (!rrst_n)begin
rq1_wptr <= 0;
rq2_wptr <= 0;
end
else begin
rq1_wptr <= wptr;
rq2_wptr <= rq1_wptr;
end
endmodule
RTL原理圖
4.存儲模塊
//存儲模塊
module fifomem
#(
parameter DATASIZE = 8, // Memory data word width
parameter ADDRSIZE = 4 // 深度為8即地址為3位即可,這里多定義一位的原因是用來判斷是空還是滿,詳細在后文講到
) // Number of mem address bits
(
output [DATASIZE-1:0] rdata,
input [DATASIZE-1:0] wdata,
input [ADDRSIZE-1:0] waddr, raddr,
input wclken, wfull, wclk
);
////////////////////////////////這部分沒用到,可以單獨寫一個模塊來調用//////////////
`ifdef RAM //可以調用一個RAM IP核
// instantiation of a vendor's dual-port RAM
my_ram mem
(
.dout(rdata),
.din(wdata),
.waddr(waddr),
.raddr(raddr),
.wclken(wclken),
.wclken_n(wfull),
.clk(wclk)
);
//////////////////////////這部分沒用到,可以單獨寫一個模塊來調用//////////////////
`else //用數組生成存儲體
// RTL Verilog memory model
localparam DEPTH = 1<<ADDRSIZE; // 左移相當於乘法,2^4 將1左移4位
reg [DATASIZE-1:0] mem [0:DEPTH-1]; //生成2^4個位寬位8的數組
assign rdata = mem[raddr];
always @(posedge wclk) //當寫使能有效且還未寫滿的時候將數據寫入存儲實體中,注意這里是與wclk同步的
if (wclken && !wfull)
mem[waddr] <= wdata;
`endif
endmodule
原理圖
5. rptr_empty模塊:產生rempty和raddr信號
//產生empty信號和raddar信號的模塊
module rptr_empty
#(
parameter ADDRSIZE = 4
)
(
output reg rempty,
output [ADDRSIZE-1:0] raddr, //二進制形式的讀指針
output reg [ADDRSIZE :0] rptr, //格雷碼形式的讀指針
input [ADDRSIZE :0] rq2_wptr, //同步后的寫指針 同步到讀時鍾域
input rinc, rclk, rrst_n
);
reg [ADDRSIZE:0] rbin;
wire [ADDRSIZE:0] rgraynext, rbinnext;
// GRAYSTYLE2 pointer
//將二進制的讀指針與格雷碼進制的讀指針同步
always @(posedge rclk or negedge rrst_n)
if (!rrst_n) begin
rbin <= 0;
rptr <= 0;
end
else begin
rbin<=rbinnext; //直接作為存儲實體的地址
rptr<=rgraynext;//輸出到 sync_r2w.v模塊,被同步到 wrclk 時鍾域
end
// Memory read-address pointer (okay to use binary to address memory)
assign raddr = rbin[ADDRSIZE-1:0]; //直接作為存儲實體的地址,比如連接到RAM存儲實體的讀地址端。
assign rbinnext = rbin + (rinc & ~rempty); //不空且有讀請求的時候讀指針加1,//否則輸出原先地址的數據waq
assign rgraynext = (rbinnext>>1) ^ rbinnext; //將二進制的讀指針轉為格雷碼 先右移一位然后與原二進制數異或
// FIFO empty when the next rptr == synchronized wptr or on reset
assign rempty_val = (rgraynext == rq2_wptr); //當讀指針等於同步后的寫指針,則為空。
always @(posedge rclk or negedge rrst_n)
if (!rrst_n)
rempty <= 1'b1;
else
rempty <= rempty_val;
endmodule
RTL原理圖
6.wfull和waddr信號產生的模塊
//產生寫滿信號(wptr_full)以及寫地址的邏輯部分
module wptr_full
#(
parameter ADDRSIZE = 4
)
(
output reg wfull,
output [ADDRSIZE-1:0] waddr,
output reg [ADDRSIZE :0] wptr,
//同步后的讀指針,注意是多了一個位,用作判斷是否fifo滿(已經在rptr_empty模塊將二進制讀地址轉化為格雷碼形式,經過寫時鍾域同步后為wq2_rptr)
input [ADDRSIZE :0] wq2_rptr,
input winc, wclk, wrst_n //
);
reg [ADDRSIZE:0] wbin;
wire [ADDRSIZE:0] wgraynext, wbinnext;
// GRAYSTYLE2 pointer
always @(posedge wclk or negedge wrst_n)
if (!wrst_n)
{wbin, wptr} <= 0;
else
{wbin, wptr} <= {wbinnext, wgraynext};// wptr 被同步到sync_w2r.v讀時鍾域rclk
// Memory write-address pointer (okay to use binary to address memory)
assign waddr = wbin[ADDRSIZE-1:0];
assign wbinnext = wbin + (winc & ~wfull);// winc為1的時候數據才能寫入對應的地址中,否則即使wdata=0,數據是沒有被寫入地址的!
assign wgraynext = (wbinnext>>1) ^ wbinnext; //二進制轉為格雷碼
//-----------------------------------------------------------------
assign wfull_val = (wgraynext=={~wq2_rptr[ADDRSIZE:ADDRSIZE-1],wq2_rptr[ADDRSIZE-2:0]}); //當最高位和次高位不同其余位相同時則寫指針超前於讀指針一圈,即寫滿。
// assign wfull_val = ( (wgraynext[ADDRSIZE] !=wq2_rptr[ADDRSIZE])&&
// (wgraynext[ADDRSIZE-1] !=wq2_rptr[ADDRSIZE-1])&&
// (wgraynext[ADDRSIZE-2] ==wq2_rptr[ADDRSIZE-2]) );
//寫滿舉例:假設讀地址為fifo起始位置:00000(格雷碼)
//當寫地址由(01111)b增加1后變為(10000)b,此時要先判斷存儲區域是否寫滿,然后才會考慮是否寫入數據
//轉換為格雷碼01000^10000=11000
//此時對比最高位和次高位可知,FIFO已滿,實際上寫指針和讀指針此時剛好跨越一圈重合
always @(posedge wclk or negedge wrst_n)
if (!wrst_n)
wfull <= 1'b0;
else
wfull <= wfull_val;
endmodule
RTL原理圖
testbench文件
`timescale 1ns /1ns
module test();
reg [7:0] wdata;
reg winc, wclk, wrst_n;
reg rinc, rclk, rrst_n;
wire [7:0] rdata;
wire wfull;
wire rempty;
fifo
u_fifo (
.rdata(rdata),
.wfull(wfull),
.rempty(rempty),
.wdata (wdata),
.winc (winc),
.wclk (wclk),
.wrst_n(wrst_n),
.rinc(rinc),
.rclk(rclk),
.rrst_n(rrst_n)
);
localparam CYCLE = 20;
localparam CYCLE1 = 40;
//時鍾周期,單位為ns,可在此修改時鍾周期。
//生成本地時鍾50M (寫時鍾50M)
initial begin
wclk = 0;
forever
#(CYCLE/2)
wclk=~wclk;
end
// 讀時鍾25M
initial begin
rclk = 0;
forever
#(CYCLE1/2)
rclk=~rclk;
end
//產生復位信號
initial begin
wrst_n = 1;
#2;
wrst_n = 0;
#(CYCLE*3);
wrst_n = 1;
end
initial begin
rrst_n = 1;
#2;
rrst_n = 0;
#(CYCLE*3);
rrst_n = 1;
end
always @(posedge wclk or negedge wrst_n)begin
if(wrst_n==1'b0)begin
winc <= 0;
// rinc <= 0;
end
else begin
winc <= $random;
$display ("winc=%h", winc);
// rinc <= $random;
// $display ("winc=%h", winc);
end
end
always @(posedge rclk or negedge rrst_n)begin
if(rrst_n==1'b0)begin
rinc <= 0;
end
else begin
rinc <= $random;
$display ("rinc=%h", rinc);
end
end
always@(*)begin
if(winc == 1)
wdata= $random ;
else
wdata = 0;
end
endmodule
仿真圖:
原理圖: