1. 程式人生 > >同步FIFO學習筆錄--

同步FIFO學習筆錄--

nis left sig 空間 tro 標誌位 sed 先進先出 什麽是

同步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 <=

4‘b0;

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學習筆錄--