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