1.定義
FIFO是英文First In First Out 的縮寫,是一種先進先出的數(shù)據(jù)緩存器,他與普通存儲器的區(qū)別是沒有外部讀寫地址線,這樣使用起來非常簡單,但缺點就是只能順序?qū)懭霐?shù)據(jù),順序的讀出數(shù)據(jù), 其數(shù)據(jù)地址由內(nèi)部讀寫指針自動加1完成,不能像普通存儲器那樣可以由地址線決定讀取或?qū)懭肽硞€指定的地址。
FIFO一般用于不同時鐘域之間的數(shù)據(jù)傳輸,比如FIFO的一端是AD數(shù)據(jù)采集, 另一端是計算機的PCI總線,假設(shè)其AD采集的速率為16位 100K SPS,那么每秒的數(shù)據(jù)量為100K×16bit=1.6Mbps,而PCI總線的速度為33MHz,總線寬度32bit,其最大傳輸速率為 1056Mbps,在兩個不同的時鐘域間就可以采用FIFO來作為數(shù)據(jù)緩沖。另外對于不同寬度的數(shù)據(jù)接口也可以用FIFO,例如單片機位8位數(shù)據(jù)輸出,而 DSP可能是16位數(shù)據(jù)輸入,在單片機與DSP連接時就可以使用FIFO來達到數(shù)據(jù)匹配的目的。
FIFO的分類根均FIFO工作的時鐘域,可以將FIFO分為同步FIFO和異步FIFO。同步FIFO是指讀時鐘和寫時鐘為同一個時鐘。在時鐘沿來臨時同時發(fā)生讀寫操作。異步FIFO是指讀寫時鐘不一致,讀寫時鐘是互相獨立的。
FIFO設(shè)計的難點 FIFO設(shè)計的難點在于怎樣判斷FIFO的空/滿狀態(tài)。為了保證數(shù)據(jù)正確的寫入或讀出,而不發(fā)生益處或讀空的狀態(tài)出現(xiàn),必須保證FIFO在滿的情況下,不 能進行寫操作。在空的狀態(tài)下不能進行讀操作。怎樣判斷FIFO的滿/空就成了FIFO設(shè)計的核心問題。
1.同步FIFO之Verilog實現(xiàn)
同步FIFO的意思是說FIFO的讀寫時鐘是同一個時鐘,不同于異步FIFO,異步FIFO的讀寫時鐘是完全異步的。同步FIFO的對外接口包括時鐘,清零,讀請求,寫請求,數(shù)據(jù)輸入總線,數(shù)據(jù)輸出總線,空以及滿信號。下面分別對同步FIFO的對外接口信號作一描述:
1. 時鐘,輸入,用于同步FIFO的讀和寫,上升沿有效;
2. 清零,輸入,異步清零信號,低電平有效,該信號有效時,F(xiàn)IFO被清空;
3. 寫請求,輸入,低電平有效,該信號有效時,表明外部電路請求向FIFO寫入數(shù)據(jù);
4. 讀請求,輸入,低電平有效,該信號有效時,表明外部電路請求從FIFO中讀取數(shù)據(jù);
5. 數(shù)據(jù)輸入總線,輸入,當(dāng)寫信號有效時,數(shù)據(jù)輸入總線上的數(shù)據(jù)被寫入到FIFO中;
6. 數(shù)據(jù)輸出總線,輸出,當(dāng)讀信號有效時,數(shù)據(jù)從FIFO中被讀出并放到數(shù)據(jù)輸出總線上;
7. 空,輸出,高電平有效,當(dāng)該信號有效時,表明FIFO中沒有任何數(shù)據(jù),全部為空;
8. 滿,輸出,高電平有效,當(dāng)該信號有效時,表明FIFO已經(jīng)滿了,沒有空間可用來存貯數(shù)據(jù)。
下面的框圖主要描述同步FIFO的內(nèi)部結(jié)構(gòu),畫出框圖有助于對電路結(jié)構(gòu)的理解,同樣也有助于RTL代碼的編寫 :
…………………………………………………………………………………………………………………………………………………………………………………………..
同步FIFO的Verilog代碼 之一?
在modlesim中驗證過。
/****************************************************** A fifo controller verilog description. ******************************************************/ module fifo(datain, rd, wr, rst, clk, dataout, full, empty); input [7:0] datain; input rd, wr, rst, clk; output [7:0] dataout; output full, empty; wire [7:0] dataout; reg full_in, empty_in; reg [7:0] mem [15:0]; reg [3:0] rp, wp; assign full = full_in; assign empty = empty_in; // memory read out assign dataout = mem[rp]; // memory write in always@(posedge clk) begin if(wr && ~full_in) mem[wp]<=datain; end // memory write pointer increment always@(posedge clk or negedge rst) begin if(!rst) wp<=0; else begin if(wr && ~full_in) wp<= wp+1'b1; end end // memory read pointer increment always@(posedge clk or negedge rst)begin if(!rst) rp <= 0; else begin if(rd && ~empty_in) rp <= rp + 1'b1; end end // Full signal generate always@(posedge clk or negedge rst) begin if(!rst) full_in <= 1'b0; else begin if( (~rd && wr)&&((wp==rp-1)||(rp==4'h0&&wp==4'hf))) full_in <= 1'b1; else if(full_in && rd) full_in <= 1'b0; end end // Empty signal generate always@(posedge clk or negedge rst) begin if(!rst) empty_in <= 1'b1; else begin if((rd&&~wr)&&(rp==wp-1 || (rp==4'hf&&wp==4'h0))) empty_in<=1'b1; else if(empty_in && wr) empty_in<=1'b0; end end endmodule
……………………………………………………………………………………………………………………………………………………………………………………..
同步FIFO的Verilog代碼 之二? 這一種設(shè)計的FIFO,是基于觸發(fā)器的。寬度,深度的擴展更加方便,結(jié)構(gòu)化跟強。以下代碼在modelsim中驗證過。
module fifo_cell (sys_clk, sys_rst_n, read_fifo, write_fifo, fifo_input_data, next_cell_data, next_cell_full, last_cell_full, cell_data_out, cell_full); parameter WIDTH =8; parameter D = 2; input sys_clk; input sys_rst_n; input read_fifo, write_fifo; input [WIDTH-1:0] fifo_input_data; input [WIDTH-1:0] next_cell_data; input next_cell_full, last_cell_full; output [WIDTH-1:0] cell_data_out; output cell_full; reg [WIDTH-1:0] cell_data_reg_array; reg [WIDTH-1:0] cell_data_ld; reg cell_data_ld_en; reg cell_full; reg cell_full_next; assign cell_data_out=cell_data_reg_array; always @(posedge sys_clk or negedge sys_rst_n) if (!sys_rst_n) cell_full <= #D 0; else if (read_fifo || write_fifo) cell_full <= #D cell_full_next; always @(write_fifo or read_fifo or next_cell_full or last_cell_full or cell_full) casex ({read_fifo, write_fifo}) 2'b00: cell_full_next = cell_full; 2'b01: cell_full_next = next_cell_full; 2'b10: cell_full_next = last_cell_full; 2'b11: cell_full_next = cell_full; endcase always @(posedge sys_clk or negedge sys_rst_n) if (!sys_rst_n) cell_data_reg_array [WIDTH-1:0] <= #D 0; else if (cell_data_ld_en) cell_data_reg_array [WIDTH-1:0] <= #D cell_data_ld [WIDTH-1:0]; always @(write_fifo or read_fifo or cell_full or last_cell_full) casex ({write_fifo,read_fifo,cell_full,last_cell_full}) 4'bx1_xx: cell_data_ld_en = 1'b1; 4'b10_01: cell_data_ld_en = 1'b1; default: cell_data_ld_en =1'b0; endcase always @(write_fifo or read_fifo or next_cell_full or cell_full or last_cell_full or fifo_input_data or next_cell_data) casex ({write_fifo, read_fifo, next_cell_full, cell_full, last_cell_full}) 5'b10_x01: cell_data_ld[WIDTH-1:0] = fifo_input_data[WIDTH-1:0]; 5'b11_01x: cell_data_ld[WIDTH-1:0] = fifo_input_data[WIDTH-1:0]; default: cell_data_ld[WIDTH-1:0] = next_cell_data[WIDTH-1:0]; endcase endmodule module fifo_4cell(sys_clk, sys_rst_n, fifo_input_data, write_fifo, fifo_out_data, read_fifo, full_cell0, full_cell1, full_cell2, full_cell3); parameter WIDTH = 8; parameter D = 2; input sys_clk; input sys_rst_n; input [WIDTH-1:0] fifo_input_data; output [WIDTH-1:0] fifo_out_data; input read_fifo, write_fifo; output full_cell0, full_cell1, full_cell2, full_cell3; wire [WIDTH-1:0] dara_out_cell0, data_out_cell1, data_out_cell2, data_out_cell3, data_out_cell4; wire full_cell4; fifo_cell #(WIDTH,D) cell0 ( .sys_clk (sys_clk), .sys_rst_n (sys_rst_n), .fifo_input_data (fifo_input_data[WIDTH-1:0]), .write_fifo (write_fifo), .next_cell_data (data_out_cell1[WIDTH-1:0]), .next_cell_full (full_cell1), .last_cell_full (1'b1), .cell_data_out (fifo_out_data [WIDTH-1:0]), .read_fifo (read_fifo), .cell_full (full_cell0) ); fifo_cell #(WIDTH,D) cell1 ( .sys_clk (sys_clk), .sys_rst_n (sys_rst_n), .fifo_input_data (fifo_input_data[WIDTH-1:0]), .write_fifo (write_fifo), .next_cell_data (data_out_cell2[WIDTH-1:0]), .next_cell_full (full_cell2), .last_cell_full (full_cell0), .cell_data_out (data_out_cell1[WIDTH-1:0]), .read_fifo (read_fifo), .cell_full (full_cell1) ); fifo_cell #(WIDTH,D) cell2 ( .sys_clk (sys_clk), .sys_rst_n (sys_rst_n), .fifo_input_data (fifo_input_data[WIDTH-1:0]), .write_fifo (write_fifo), .next_cell_data (data_out_cell3[WIDTH-1:0]), .next_cell_full (full_cell3), .last_cell_full (full_cell1), .cell_data_out (data_out_cell2[WIDTH-1:0]), .read_fifo (read_fifo), .cell_full (full_cell2) ); fifo_cell #(WIDTH,D) cell3 ( .sys_clk (sys_clk), .sys_rst_n (sys_rst_n), .fifo_input_data (fifo_input_data[WIDTH-1:0]), .write_fifo (write_fifo), .next_cell_data (data_out_cell4[WIDTH-1:0]), .next_cell_full (full_cell4), .last_cell_full (full_cell2), .cell_data_out (data_out_cell3[WIDTH-1:0]), .read_fifo (read_fifo), .cell_full (full_cell3) ); assign data_out_cell4[WIDTH-1:0] = {WIDTH{1'B0}}; assign full_cell4 = 1'b0; endmodule
2.異步FIFO之Verilog實現(xiàn)
FIFO (先進先出隊列)是一種在電子系統(tǒng)得到廣泛應(yīng)用的器件,通常用于數(shù)據(jù)的緩存和用于容納異步信號的頻率或相位的差異。FIFO的實現(xiàn)通常是利用雙口RAM和讀寫地址產(chǎn)生模塊來實現(xiàn)的。FIFO的接口信號包括異步的寫時鐘(wr_clk)和讀時鐘(rd_clk)、與寫時鐘同步的寫有效(wren)和寫數(shù)據(jù)(wr_data)、與讀時鐘同步的讀有效(rden)和讀數(shù)據(jù)(rd_data)。為了實現(xiàn)正確的讀寫和避免FIFO的上溢或下溢,通常還應(yīng)該給出與讀時鐘和寫時鐘同步的FIFO的空標(biāo)志(empty)和滿標(biāo)志(full)以禁止讀寫操作。
1 異步FIFO功能描述
圖1給出了FIFO的接口信號和內(nèi)部模塊圖。?
由圖1可以看出,寫地址產(chǎn)生模塊根據(jù)寫時鐘和寫有效信號產(chǎn)生遞增的寫地睛,讀地址產(chǎn)生模塊根據(jù)讀時鐘和讀有效信號產(chǎn)生遞增的讀地址。FIFO的操作如下:在寫時鐘wr_clk的升沿,當(dāng)wren有效時,將wr_data寫入雙口RAM中寫地址對應(yīng)的位置中;始終將讀地址對應(yīng)的雙口RAM中的數(shù)據(jù)輸出到讀數(shù)據(jù)總線上。這樣就實現(xiàn)了先進先出的功能。?
寫地址產(chǎn)生模塊還根據(jù)讀地址和寫地址關(guān)系產(chǎn)生FIFO的滿標(biāo)志。當(dāng)wren有效時,若寫地址+2=讀地址時,full為1;當(dāng)wren無效時,若寫地址+ 1=讀地址時,full為1。讀地址產(chǎn)生模塊還根據(jù)讀地址和寫地址的差產(chǎn)生FIFO的空標(biāo)志。當(dāng)rden有效時,若寫地址-1=讀地址時,empty為 1;當(dāng)rden無效時,若寫地址=讀地址時,empty為1。按照以上方式產(chǎn)生標(biāo)志信號是為了提前一個時鐘周期產(chǎn)生對應(yīng)的標(biāo)志信號。?
由于空標(biāo)志和滿標(biāo)志控制了FIFO的操作,因此標(biāo)志錯誤會引起操作的錯誤。如上所述,標(biāo)志的產(chǎn)生是通過對讀寫地址的比較產(chǎn)生的,當(dāng)讀寫時鐘完全異步時,對讀寫地址進行比較時,可能得出錯誤的結(jié)果。例如,在讀地址變化過程中,由于讀地址的各位變化并不同步,計算讀寫地址的差值,可能產(chǎn)生錯誤的差值,導(dǎo)致產(chǎn)生錯誤的滿標(biāo)志信號。若將未滿標(biāo)志置為滿標(biāo)志時,可能降低了應(yīng)用的性能,降低寫數(shù)據(jù)速率;而將滿置標(biāo)志置為未滿時,執(zhí)行一次寫操作,則可能產(chǎn)生溢出錯誤,這對于實際應(yīng)用來說是絕對應(yīng)該避免的??諛?biāo)志信號的產(chǎn)生也可能產(chǎn)生類似的錯誤。
2 異步FIFO的改進設(shè)計
從以上分析中可以看出,異步FIFO之所以會發(fā)生錯誤是國為在地址變化時,由于多位地址各位變化時間不同,異步時鐘對其進行采樣時數(shù)值可能為不同于地址變化喪后數(shù)值的其他值,異步產(chǎn)生錯誤的空標(biāo)志和滿標(biāo)志,以致于產(chǎn)生FIFO的操作錯誤。?
格雷碼是一種在相鄰計數(shù)值之間只有一位發(fā)生變化的編碼方式??梢钥闯?,若讀寫地址采用格雷碼編碼方式,就可以解決上面的問題。?
為了應(yīng)用的靈活,還增加了兩個標(biāo)志信號,將滿(almosf_full)標(biāo)志和空(almost_empty)標(biāo)志分別定義如下:當(dāng)寫地址與讀地址的距離小于某個預(yù)先定義數(shù)值時,almost_full為1;當(dāng)讀地址與寫地址的距離小于這個預(yù)先定義的數(shù)值時,almost_empty為1。
3 異步FIFO的Verilog
…………………………………………………………………………………………………………………………………………………………………………………………………………………………….?
異步FIFO的Verilog代碼 之一?
這個是基于RAM的異步FIFO代碼,個人認(rèn)為代碼結(jié)構(gòu)簡單易懂,非常適合于考試中填寫。記得10月份參加威盛的筆試的時候,就考過異步FIFO的實現(xiàn)。想當(dāng)初要是早點復(fù)習(xí),可能就可以通過威盛的筆試了。
與之前的用RAM實現(xiàn)的同步FIFO的程序相比,異步更為復(fù)雜。增加了讀寫控制信號的跨時鐘域的同步。此外,判空與判滿的也稍有不同。
module fifo1(rdata, wfull, rempty, wdata, winc, wclk, wrst_n,rinc, rclk, rrst_n); parameter DSIZE = 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; reg wfull,rempty; reg [ASIZE:0] wptr, rptr, wq2_rptr, rq2_wptr, wq1_rptr,rq1_wptr; reg [ASIZE:0] rbin, wbin; reg [DSIZE-1:0] mem[0:(1<>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) begin if (!rrst_n) rempty <= 1'b1; else rempty <= rempty_val; end //---------------wfull產(chǎn)生與waddr產(chǎn)生------------------------------ always @(posedge wclk or negedge wrst_n) // GRAYSTYLE2 pointer if (!wrst_n) {wbin, wptr} <= 0; else {wbin, wptr} <= {wbinnext, wgraynext}; // Memory write-address pointer (okay to use binary to address memory) assign waddr = wbin[ASIZE-1:0]; assign wbinnext = wbin + (winc & ~wfull); assign wgraynext = (wbinnext>>1) ^ wbinnext; assign wfull_val = (wgraynext=={~wq2_rptr[ASIZE:ASIZE-1], wq2_rptr[ASIZE-2:0]}); //:ASIZE-1] always @(posedge wclk or negedge wrst_n) if (!wrst_n) wfull <= 1'b0; else wfull <= wfull_val; endmodule
………………………………………………………………………………………………………………………………………………………………
異步FIFO的Verilog代碼 之二?
與前一段異步FIFO代碼的主要區(qū)別在于,空/滿狀態(tài)標(biāo)志的不同算法。
第一個算法:Clifford E. Cummings的文章中提到的STYLE #1,構(gòu)造一個指針寬度為N+1,深度為2^N字節(jié)的FIFO(為便方比較將格雷碼指針轉(zhuǎn)換為二進制指針)。當(dāng)指針的二進制碼中最高位不一致而其它N位都 相等時,F(xiàn)IFO為滿(在Clifford E. Cummings的文章中以格雷碼表示是前兩位均不相同,而后兩位LSB相同為滿,這與換成二進制表示的MSB不同其他相同為滿是一樣的)。當(dāng)指針完全相 等時,F(xiàn)IFO為空。
這種方法思路非常明了,為了比較不同時鐘產(chǎn)生的指針,需要把不同時鐘域的信號同步到本時鐘域中來,而使用Gray碼的目的就是使這個異步同步化的過 程發(fā)生亞穩(wěn)態(tài)的機率最小,而為什么要構(gòu)造一個N+1的指針,Clifford E. Cummings也闡述的很明白,有興趣的讀者可以看下作者原文是怎么論述的,Clifford E. Cummings的這篇文章有Rev1.1 Rev1.2兩個版本,兩者在比較Gray碼指針時的方法略有不同,個Rev1.2版更為精簡。
第二種算法:Clifford E. Cummings的文章中提到的STYLE #2。它將FIFO地址分成了4部分,每部分分別用高兩位的MSB 00 、01、 11、 10決定FIFO是否為going full 或going empty (即將滿或空)。如果寫指針的高兩位MSB小于讀指針的高兩位MSB則FIFO為“幾乎滿”,若寫指針的高兩位MSB大于讀指針的高兩位MSB則FIFO 為“幾乎空”。
它是利用將地址空間分成4個象限(也就是四個等大小的區(qū)域),然后觀察兩個指針的相對位置,如果寫指針落后讀指針一個象限(25%的距離,呵呵), 則證明很可能要寫滿,反之則很可能要讀空,這個時候分別設(shè)置兩個標(biāo)志位dirset和dirrst,然后在地址完全相等的情況下,如果dirset有效就 是寫滿,如果dirrst有效就是讀空。?
?
這種方法對深度為2^N字節(jié)的FIFO只需N位的指針即可,處理的速度也較第一種方法快。
這段是說明的原話,算法一,還好理解。算法二,似乎沒有說清楚,不太明白。有興趣的可以查查論文,詳細(xì)研究下。
總之,第二種寫法是推薦的寫法。因為異步的多時鐘設(shè)計應(yīng)按以下幾個原則進行設(shè)計:?
1,盡可能的將多時鐘的邏輯電路(非同步器)分割為多個單時鐘的模塊,這樣有利于靜態(tài)時序分析工具來進行時序驗證。?
2,同步器的實現(xiàn)應(yīng)使得所有輸入來自同一個時鐘域,而使用另一個時鐘域的異步時鐘信號采樣數(shù)據(jù)。?
3,面向時鐘信號的命名方式可以幫助我們確定那些在不同異步時鐘域間需要處理的信號。?
4,當(dāng)存在多個跨時鐘域的控制信號時,我們必須特別注意這些信號,保證這些控制信號到達新的時鐘域仍然能夠保持正確的順序。
module fifo2 (rdata, wfull, rempty, wdata, winc, wclk, wrst_n, rinc, rclk, rrst_n); parameter DSIZE = 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] wptr, rptr; wire [ASIZE-1:0] waddr, raddr; async_cmp #(ASIZE) async_cmp(.aempty_n(aempty_n), .afull_n(afull_n), .wptr(wptr), .rptr(rptr), .wrst_n(wrst_n)); fifomem2 #(DSIZE, ASIZE) fifomem2(.rdata(rdata), .wdata(wdata), .waddr(wptr), .raddr(rptr), .wclken(winc), .wclk(wclk)); rptr_empty2 #(ASIZE) rptr_empty2(.rempty(rempty), .rptr(rptr), .aempty_n(aempty_n), .rinc(rinc), .rclk(rclk), .rrst_n(rrst_n)); wptr_full2 #(ASIZE) wptr_full2(.wfull(wfull), .wptr(wptr), .afull_n(afull_n), .winc(winc), .wclk(wclk), .wrst_n(wrst_n)); endmodule module fifomem2 (rdata, wdata, waddr, raddr, wclken, wclk); parameter DATASIZE = 8; // Memory data word width parameter ADDRSIZE = 4; // Number of memory address bits parameter DEPTH = 1<>1) ^ rbnext; // binary-to-gray conversion always @(posedge rclk or negedge aempty_n) if (!aempty_n) {rempty,rempty2} <= 2'b11; else {rempty,rempty2} <= {rempty2,~aempty_n}; endmodule module wptr_full2 (wfull, wptr, afull_n, winc, wclk, wrst_n); parameter ADDRSIZE = 4; output wfull; output [ADDRSIZE-1:0] wptr; input afull_n; input winc, wclk, wrst_n; reg [ADDRSIZE-1:0] wptr, wbin; reg wfull, wfull2; wire [ADDRSIZE-1:0] wgnext, wbnext; //--------------------------------------------------------------- // GRAYSTYLE2 pointer //--------------------------------------------------------------- always @(posedge wclk or negedge wrst_n) if (!wrst_n) begin wbin <= 0; wptr <= 0; end else begin wbin <= wbnext; wptr <= wgnext; end //--------------------------------------------------------------- // increment the binary count if not full //--------------------------------------------------------------- assign wbnext = !wfull ? wbin + winc : wbin; assign wgnext = (wbnext>>1) ^ wbnext; // binary-to-gray conversion always @(posedge wclk or negedge wrst_n or negedge afull_n) if (!wrst_n ) {wfull,wfull2} <= 2'b00; else if (!afull_n) {wfull,wfull2} <= 2'b11; else {wfull,wfull2} <= {wfull2,~afull_n}; endmodule
編輯:黃飛
?
評論
查看更多