前言:
FIFO本質爲RAM,分爲同步FIFO(SCFIFO)和異步FIFO(DCFIFO),前者讀寫用同一個時鐘信號,後者則使用雙時鐘讀寫。不過同步FIFO實際運用中較爲少(可用做數據緩存),一般多用異步FIFO,因爲在FPGA設計中,往往都是多時鐘系統,很少爲單時鐘(除非你單純做一個流水燈之類的簡單實驗)。這裏,筆者給大家做一個簡單的異步FIFO實驗,供大家參考。(在看這個實驗之前建議大家先學習同步FIFO,文字最後面我會附上同步FIFO的實驗鏈接)
1. 實驗內容
瞭解掌握DCFIFO的工作原理以及其結構,設計DCFIFO模塊以及其test_bench,最後VIVADO進行功能實現和仿真驗證。
2. 實驗原理
在兩個時鐘域之間進行數據傳遞最常用的的使用方法就是異步FIFO。異步FIFO一般包括兩個端口,其中端口A是寫入端,端口B是讀入端。AFIFO中最常用的控制信號是“空”(empty)和“滿”(full),另外,“將空”(almost empty)、“將滿”(almost full)也是兩個經常使用的控制信號。
**下圖爲DCFIFO外部鏈接關係**
確定DCFIFO的空狀態或滿狀態需要一定的數學處理以及讀指針和寫指針的比較。關鍵問題是兩個指針在不同的時鐘域產生,所以指針必須經過同步化才能在另一時鐘域中進行比較和用於計算。對於異步FIFO來說,產生準確的“空”和“滿”指示信號是比較困難的,但爲了保證操作的安全,避免“滿”後仍繼續寫入和“空”後仍繼續讀出是可以做到的。
這裏爲了方便大家理解代碼,指針以及滿空信號的產生,及爲什麼要使用格雷碼指針,我找了相關資料供大家參考:
以上圖片資料來源於百度條詞:異步FIFO
簡單描述二進制與格雷碼轉換:
二進制數 —————————— 1 0 1 1 0
(進制數右移1位,空位補 0) — 01 0 1 1
異或運算 —————————— 1 1 1 0 1
這樣就可以實現二進制到格雷碼的轉換了,總結就是移位並且異或,verilog代碼實現就一句話:assign rgraynext = (rbinnext>>1) ^ rbinnext;
3. 實驗代碼
①設計代碼
module DCFIFO(
wdata,
wclk,
rinc,
rdata,
wfull,
rempty,
rclk,
rrst_n,
wrst_n,
winc);
input [7:0] wdata;
input wclk;
input rinc;
output [7:0] rdata;
output wfull;
output rempty;
input rclk;
input rrst_n;
input wrst_n;
input winc;
//輸入,輸出引腳類型定義
wire [7:0] wdata;
wire wclk;
wire rinc;
wire [7:0] rdata;
reg wfull;
reg rempty;
wire rclk;
wire rrst_n;
wire wrst_n;
wire winc;
reg [4:0] wptr, rptr, wq2_rptr, rq2_wptr, wq1_rptr,rq1_wptr;
reg [4:0] rbin, wbin;
reg [7:0] mem[0:(1<<4)-1];
wire[3:0] waddr, raddr;
wire [4:0] rgraynext, rbinnext,wgraynext,wbinnext;
wire rempty_val,wfull_val;
assign rdata=mem[raddr];
always@(posedge wclk)
if (winc && !wfull)
mem[waddr] <= wdata;
always @(posedge wclk or negedge wrst_n)
if (!wrst_n)
{wq2_rptr,wq1_rptr} <= 0;
else
{wq2_rptr,wq1_rptr} <= {wq1_rptr,rptr};
always @(posedge rclk or negedge rrst_n)
if (!rrst_n)
{rq2_wptr,rq1_wptr} <= 0;
else
{rq2_wptr,rq1_wptr} <= {rq1_wptr,wptr};
always @(posedge rclk or negedge rrst_n)
if (!rrst_n)
{rbin, rptr} <= 0;
else
{rbin, rptr} <= {rbinnext, rgraynext};
assign raddr = rbin[3:0];
assign rbinnext = rbin + (rinc & ~rempty);
assign rgraynext = (rbinnext>>1) ^ rbinnext; //二進制與格雷碼轉換
assign rempty_val = (rgraynext == ~rq2_wptr);
always @(posedge rclk or negedge rrst_n)
if (!rrst_n)
rempty <= 1'b1;
else
rempty <= rempty_val;
always @(posedge wclk or negedge wrst_n)
if (!wrst_n)
{wbin, wptr} <= 0;
else
{wbin, wptr} <= {wbinnext, wgraynext};
assign waddr = wbin[3:0];
assign wbinnext = wbin + (winc & ~wfull);
assign wgraynext = (wbinnext>>1) ^ wbinnext;
assign wfull_val = (wgraynext=={~wq2_rptr[4:4-1],
wq2_rptr[4-2:0]});
always @(posedge wclk or negedge wrst_n)
if (!wrst_n)
wfull <= 1'b0;
else
wfull <= wfull_val;
endmodule
設計原理圖如下:
②仿真激代碼
`timescale 1ns/1ns
module DCFIFO_test();
reg [7:0] wdata;
reg winc;
reg wclk;
reg wrst_n;
reg rinc;
reg rclk;
reg rrst_n;
wire [7:0] rdata;
wire wfull;
wire rempty;
integer i;
always begin
#10 wclk=1;
#10 wclk=0;
end
always begin
#15 rclk=1;
#15 rclk=0;
end
initial begin
wclk=0;rclk=0;wdata=0;winc=0;rinc=0;wrst_n=0;rrst_n=0;i=0;
#2 winc=1;
#2 rinc=0;
for(i=0;i<=20;i=i+1)
begin
repeat(1)@(posedge wclk);
wrst_n=1;
wdata=wdata+1;
end
repeat(1)@(posedge wclk);
wrst_n=0;
#100 rinc=1;
#2 winc=0;
for(i=0;i<=20;i=i+1)
begin
repeat(1)@(posedge rclk);
#2;
rrst_n=1;
end
repeat(1)@(posedge rclk);
#2;
rrst_n=0;
#100 rinc=0;
#2 $finish;
end
//將設計模塊例化到仿真模塊中
DCFIFO DCFIFO1(
.wdata(wdata),
.wclk(wclk),
.rinc(rinc),
.rdata(rdata),
.wfull(wfull),
.rempty(rempty),
.rclk(rclk),
.rrst_n(rrst_n),
.wrst_n(wrst_n),
.winc(winc));
endmodule
仿真波形圖如下:
**後續:**因爲時間原因代碼的註釋還沒寫,後面如果有時間會補上,並對文章進行完整講解。
這裏推薦一位朋友寫的同步FIFO,內容很詳細,建議大家看學習異步FIFO之前先看這篇文章:verilog實現FIFO設計(一)之同步8位深度