FIFO隊列是一種數據緩沖器,用于數據的緩存。他是一種先入先出的存儲器,即最先寫入的數據,最先讀。FIFO的參數有數據深度和數據寬度。數據寬度是指存儲數據的寬度。深度是指存儲器可以存儲多少個數據。
FIFO隊列有兩個標志位。一個滿和一個空標志位。分別表示FIFO是數據寫滿,還是數據讀空。在數據寫滿狀態下,數據寫入是不允許的,因此在這個狀態下,寫入的數據無效。而數據讀空狀態下,數據讀取是不允許的,因此在這個狀態下,讀取的數據無效。
FIFO隊列有兩個位置指示指針。一個是寫指針,指向隊列的第一個存儲單元。一個讀指針,指向隊列的最后一個存儲單元。當有寫命令的時候,數據寫入寫指針指向的存儲單元,然后指針加一。當有讀命令的時候,讀指針加一,在讀出讀指針指向的存儲單元的數據。這里讀命令,指針要加一,是定義讀數據,是讀出讀指針的下一個存儲單元的數據。
當寫指針和讀指針的指向存儲單元一樣時,這時候根據之前是讀命令還是寫命令來判斷隊列是空,還是滿。在讀命令,兩個指針值一樣時候,則隊列空。在寫命令,兩個指針值一樣,則隊列滿。
以后就開始寫代碼實現上訴FIFO隊列,并進行仿真。
以下,是實現數據寬度為8.深度為2^4的深度的FIFO。。讀/寫時鐘是同一個。
module fifo_cus
#(
parameter N = 8, //數據寬度
parameter M = 4 //fifo的地址寬度
)
//對隊列的參數設置。建議這樣寫,便于以后代碼的移植。
//如果以后要實現數據寬度為16,深度為2^8的FIFO。只需改N =16 M=8即可
(
input clk, //輸入時鐘
input wr, //輸入寫使能
input[N-1:0] w_data, //輸入輸入
input rd, //輸入讀使能
output empty, //輸出fifo空標志
output full, //輸出fifo滿標志
output[N-1:0] r_data //輸出讀取的數據
);
//寄存器組,用來充當FIFO隊列
reg [N-1:0] array_reg [2**M - 1:0];
//定義寫指針,指示當前寫的位置,下一個狀態寫的位置,寫位置的下一個位置
reg [M-1:0] w_ptr_reg, w_ptr_next,w_ptr_succ;
//定義讀指針,指示當前讀的位置,下一個狀態讀的位置,讀位置的下一個位置
reg [M-1:0] r_ptr_reg, r_ptr_next,r_ptr_succ;
//定義FIFO滿和空的信號
reg full_reg, full_next;
reg empty_reg, empty_next;
wire wr_en;
//數據的寫入,在數據的上升沿的時候,有寫使能信號,將數據寫入。而
always@( posedge clk ) begin
if( wr_en )
array_reg[w_ptr_reg] <= w_data;
else
array_reg[w_ptr_reg] <= array_reg[w_ptr_reg];
end
// 數據的讀取。數據讀取是一直在讀取的,不過讀取的是之前的值。
assign r_data = array_reg[r_ptr_reg];
assign wr_en = wr & ~full_reg;
/*狀態跳轉
在復位信號有效,讀/寫指針都指向0地址。此時隊列狀態為空。
在復位不有效,且在時鐘的上升沿,讀/寫指針的值,隊列空,滿狀態的值又下一狀態決定。否則保持 */
always@( posedge clk ) begin
if( !rst_n )
begin
w_ptr_reg <= 0;
r_ptr_reg <= 0;
full_reg <= 1'b0;
empty_reg <= 1'b1;
end
else
begin
w_ptr_reg <= w_ptr_next;
r_ptr_reg <= r_ptr_next;
full_reg <= full_next;
empty_reg <= empty_next;
end
end
//下一個狀態的判定
always@ * begin
w_ptr_next = w_ptr_reg;
r_ptr_next = r_ptr_reg;
full_next = full_reg;
empty_next = empty;
w_ptr_succ = w_ptr_reg + 1'b1;
r_ptr_succ = r_ptr_reg + 1'b1;
case( {wr,rd} )
/*讀命令:在讀命令下,如果隊列不為空,講當前讀指針的下一個指針賦值給讀指針的下一個狀態,同時將隊列的滿標志置0。
然后判斷讀指針的下一個指針是否和寫指針的值一樣。一樣的話,說明,隊列為空。否則不為空。 */
2'b01:
begin
if( ~empty_reg )
begin
r_ptr_next = r_ptr_succ;
full_next = 0;
if( r_ptr_succ == w_ptr_reg )
empty_next = 1'b1;
else
empty_next = 1'b0;
end
end
/*寫命令:在寫命令下,如果隊列不為滿,將當前寫指針的下一個指針賦值給讀指針的下一個狀態,同時將隊列的空標志置0。
然后判斷寫指針的下一個指針是否和讀指針的值一樣。一樣的話,說明,隊列為滿。否則不為滿。
*/
2'b10:
begin
if( ~full_reg )
begin
w_ptr_next = w_ptr_succ;
empty_next= 0;
if( w_ptr_succ == r_ptr_reg )
full_next = 1'b1;
else
full_next = 1'b0;
end
end
/*讀寫命令:在讀寫命令下, 直接改變對應指針的下一個狀態值。
*/
2'b11:
begin
w_ptr_next = w_ptr_succ;
r_ptr_next = r_ptr_succ;
endcase
end
// 滿/空輸出信號的賦值。
assign full = full_reg;
assign empty = empty_reg;
endmodule
好了,終于搞定FIFO的代碼了。下面來仿真看看結果。
以下分析仿真的結果:
寫數據:
從下圖仿真,可看出。在最開始的時候,隊列是空的狀態。讀指針和寫指針都是0。在寫使能情況下,在每個時鐘的上升沿(藍色線),數據寫入隊列array_reg中。同時,寫指針加一。而讀指針是不變的。
從下圖發現,在隊列滿狀態下,即使寫使能,FIFO也不接受寫數據。依舊保持原來的值。
讀數據
從下圖中看出,最開始,數據讀出是有值的。為初始化的讀指針指向的存儲單元的值。這里為4。
當有讀命令時候,在時鐘的上升沿(藍色線),讀指針加一。讀取的數據隨之改變。
在數據讀完后,即隊列為空狀態下。此時對數據的讀取是無效的。從圖中可看出,讀完后,讀指針為0.回到存儲器的第一個地址。而此時讀出的值是無效的。
讀寫命令:
在同時讀同時寫的時候。從下圖,可看出,結果有問題了。在隊列為空的狀態下,此時讀取的值,應為此時寫的數據才對了。但是從圖中,可看出,讀取的值不是當前寫的數據的值。而是之前存儲在FIFO中的值。這樣的話,讀取的值就不是正確的值了。
從上圖仿真結果,可知。程序在讀寫命令時候,編寫得不正確。造成結果不對。
返回程序分析。程序不對的地方在于讀寫命令的時候,處理 得不正確。在空的狀態下,數據寫入是先寫入,然后寫指針加一。而讀取命令是,指針先加一,然后再讀取。而讀和寫指針的值一樣的。這樣造成,讀取的FIFO的存儲單元的值,為寫的存儲單元的下一個存儲單元的值。因此造成讀取不正確。
改正的程序如下:
2'b11:
begin
if( ~full_reg && ~empty_reg )
begin
w_ptr_next = w_ptr_succ;
r_ptr_next = r_ptr_succ;
end
else if( full_reg ) //在滿的狀態,不允許寫
begin
r_ptr_next = r_ptr_succ;
full_next = 0;
end
else if( empty_reg ) //在空的狀態,不允許寫
begin
w_ptr_next = w_ptr_succ;
empty_next = 0;
end
end
只需要規定以下:在滿的狀態,不允許寫,在空的狀態下,不允許讀。這樣就可以了。
然后再進行仿真:
這里只看讀寫命令的圖。從下圖中,可看出,此時讀取的數據,為剛剛寫的數據。這樣就正確了。
這樣,就完成了FPGA的FIFO了。通過這樣一個簡單的練習,可看出,仿真,是很重要的,能發現程序中的問題。
以上仿真沒有覆蓋到所有情況,有興趣的,可以自己仿真看看仿真圖,驗證程序寫得是否正確。
評論
查看更多