同步FIFO學習筆錄--
同步FIFO學習
1、撰寫緣由
這幾天在初步學習verilog,學習到了同步FIFO,寫點東西記錄一下,寫寫心得體會和大家一起交流學習,中間有不對的地方希望大家能多多包涵,歡迎指正,共同進步。學習時主要參考:https://www.cnblogs.com/SYoong/p/6108780.html,感謝大神的分享。本文與參考有些不同,其中我自己認為有些需要改動的地方,若不對,多多指正。其實同步FIFO在實際中應用很少,應用多的還是異步FIFO,不過作為一個新手拿來練習練習感覺是很不錯的。
2、什麽是FIFO
簡而言之,FIFO就是先進先出的數據緩存器。沒有外部讀寫地址線,順序的寫入與讀出,數據地址由內部讀寫指針自動加1完成,不能像普通存儲器那樣由地址線讀取或者寫入某個指定的地址。
3、FIFO有什麽作用
兩個不同時鐘域間數據傳輸使用FIFO作為緩沖;允許時鐘進行DMA操作,提高數據傳輸速度;不同寬度的數據接口也可以使用FIFO等。
4、同步FIFO的Verilog實現
此同步FIFO深度為16,位寬位8,分為了4個子模塊:(1)寫地址產生模塊wr_addr_gen;(2)16個8bits寄存器模塊;(3)讀地址產生模塊;(4)標誌位寫滿full和讀空empty模塊。
鼓勵大家寫代碼時能夠分模塊寫並且加註釋,這樣使代碼更加清晰,可讀性與可移植性更高。
(1)寫地址產生模塊wr_addr_gen
當寫使能有效並且FIFO還沒有被寫滿時,寫地址在時鐘上升沿時順序加1。
module w_addr_gen( input wire clk, //時鐘信號 input wire full,//寫滿標誌 input wire wr_en,//寫使能 input wire rst_n,//復位信號,低電平有效 output reg [3:0] wr_addr//寫地址 ); always @ (posedge clk) begin if (!rst_n) begin wr_addr <= end else if (!full && wr_en == 1‘b1) begin wr_addr <= wr_addr + 1‘b1; end end endmodule
|
(2)讀地址產生模塊rd_addr_gen
當讀使能有效並且FIFO還沒有被讀空時,讀地址在時鐘上升沿時順序加1。
module r_addr_gen ( input wire clk, //時鐘 input wire rst_n,//復位 input wire rd_en,//讀使能 input wire empty,//讀空標誌 output reg [3:0]rd_addr//讀地址信號 ); always @ (posedge clk) begin if (!rst_n) begin rd_addr <= 4‘b0; end else if ((rd_en==1‘b1) && (empty != 1‘b1)) begin rd_addr <= rd_addr + 1‘b1; end end endmodule
|
(3)存儲器RAM模塊的實現
module RAM ( input wire clk, input wire wr_en, input wire [3:0] wr_addr,//寫地址 input wire [7:0] data_in, //輸入數據 input wire rd_en, //讀使能 input wire [3:0] rd_addr,//讀地址 input wire full, input wire empty, output reg [7:0] data_out //輸出數據 ); reg [7:0] fifo [15:0]; //8bits register*16 always @ (posedge clk) begin //write if ((wr_en == 1‘b1) && (full!=1)) fifo[wr_addr] <= data_in; //上一行與參考不一致,當寫使能有效並且FIFO未寫滿時,將數據寫入FIFO對應地址 //若寫使能有效而FIFO已經處於滿狀態的話,再寫入數據就會覆蓋原先地址的數據,導致數據丟失 end always @ (posedge clk) begin //read if ((rd_en == 1‘b1) && (empty!=1)) //上一行與參考不一致,當讀使能有效並且FIFO不為空時,將FIFO中對應地址中的數據讀出 //若讀使能有效而FIFO為空的話,再讀出就會導致讀出空數據出錯 data_out <= fifo[rd_addr]; end endmodule
|
(4)標誌位產生模塊flag_gen的實現
module flag_gen ( input wire clk, input wire rst_n, input wire rd_en, input wire wr_en, output wire empty,//FIFO空標誌位 output wire full//FIFO滿標誌位 ); parameter max=5‘b10000; reg [4:0] count;//16位的FIFO用5位的寄存器記錄被寫入的數據個數,見下 always @ (posedge clk) begin if (!rst_n) begin count <= 5‘b0; end else case({rd_en,wr_en}) 2‘b00: count <= count; //讀寫均無效,FIFO中被寫入數據個數不變 2‘b01: if (count!=max) count <= count + 1‘b1;//當寫有效且FIFO未被寫滿,計數加1 2‘b10: if (count!=5‘b0000) count <= count - 1‘b1;//當讀有效且FIFO未被讀空,計數減1 2‘b11: count <= count; default: count <= count; endcase end assign full = (count == max);//當計數等於16時,產生滿信號,所以用5位的count //若count==4‘b1111產生滿信號,會導致FIFO中一個存儲空間不能被寫入數據,仿真可以試一下 assign empty = (count == 5‘b0000); endmodule
|
(5)頂層模塊syn_fifo_top的實現
用wire將各個模塊連接起來即可。
module syn_fifo_top( input wire clk, input wire rst_n, input wire wr_en, input wire rd_en, input wire [7:0] data_in, output wire [7:0] data_out ); wire [3:0] wr_addr; wire [3:0] rd_addr; RAM iRAM( .clk(clk), .wr_en(wr_en), .wr_addr(wr_addr), .data_in(data_in), .rd_en(rd_en), .rd_addr(rd_addr), .data_out(data_out), .full(full), .empty(empty) ); r_addr_gen ir_addr_gen( .clk(clk), .rst_n(rst_n), .rd_en(rd_en), .empty(empty), .rd_addr(rd_addr) ); w_addr_gen iw_addr_gen( .clk(clk), .full(full), .wr_en(wr_en), .rst_n(rst_n), .wr_addr(wr_addr) ); flag_gen iflag_gen( .clk(clk), .rst_n(rst_n), .rd_en(rd_en), .wr_en(wr_en), .empty(empty), .full(full) ); endmodule
|
(6)仿真testbench的實現
`timescale 1ns/1ps `define clk_period 20 module syn_fifo_tb(); reg clk; reg rst_n; reg wr_en,rd_en; reg [7:0] data_in; wire [7:0] data_out; syn_fifo_top isyn_fifo_top( .clk(clk), .rst_n(rst_n), .wr_en(wr_en), .rd_en(rd_en), .data_in(data_in), .data_out(data_out) ); initial begin clk = 1‘b0; end always #(`clk_period/2) clk = ~clk; initial begin wr_en = 1‘b1; rd_en = 1‘b0; rst_n = 1‘b0; data_in =8‘b0; #(`clk_period+1) rst_n = 1‘b1; repeat(16) begin #(`clk_period+1) data_in = data_in + 1‘b1; end # (`clk_period*20+1) rd_en = 1‘b1; wr_en =1‘b0; # (`clk_period*200+1) $finish; end endmodule
|
5、標誌位產生模塊flag_gen的另一種實現方法
分別將讀/寫地址寄存器擴展一位,將最高位設置為狀態位,其余低位作為地址位,指針由地址位以及狀態位組成。首先把讀、寫狀態位全部復位,如果地址循環了奇數次,則狀態位置1,偶數次則又重新復位,應用地址位和狀態位的結合實現對空、滿標誌位的控制。當讀寫指針的地址位和狀態位全部吻合的時候,讀寫指針經歷了相同次數的循環移動,也就是說,FIFO 處於空狀態;如果讀寫指針的地址位相同而狀態位相反,寫指針比讀指針多循環一次,標誌FIFO處於滿狀態。
module flag_gen ( input wire [4:0] wr_addr, input wire [4:0] rd_addr, output wire empty, output wire full ); assign full = ((wr_addr[4]!=rd_addr[4]) && (wr_addr[3:0]==rd_addr[3:0])); assign empty = (wr_addr == rd_addr); endmodule
|
註意深度為16的FIFO,方法1使用4位的地址線即可,方法二需要使用5位的地址線,即需要將每個模塊中的[3:0]wr_addr、[3:0]rd_addr替換為[4:0]wr_addr、[4:0]rd_addr,需要註意一下。
同步FIFO學習筆錄--