最近學習verilog設計FIFO,記錄一下。
一. 設計原理
FIFO( First in First out) 使用在需要產生數據接口的部分,用來存儲、緩衝在兩個異步時鐘之間的數據傳輸。在異步電路中,由於時鐘之間週期和相位完全獨立,因此數據丟失概率不爲零。使用 FIFO 可以在兩個不同時鐘域系統之間快速而方便地傳輸實時數據。這次的設計我們就來學習一下設計一個 8 位 8 深度的 同步FIFO。
FIFO 的原理框圖如下圖所示:
我們看到圖中有一個具有獨立的讀端口和獨立的寫端口的 RAM 存儲器。這樣選擇是爲了分析方便。如果是一個單端口的存儲器,還應包含一個仲裁器,保證同一時刻只能進行一項操作(讀或寫),我們選擇雙口 RAM(無需真正的雙口 RAM,因爲我們只是希望有一個簡單的相互獨立的讀寫端口)是因爲這些實例非常接近實際情況。
讀、寫端口擁有兩個計數器( wr_ptr、 rd_ptr)產生的互相獨立的讀、寫地址。計數器的值在讀寫使能信號來臨時傳遞給“ 讀指針” ( rd)和“ 寫指針” ( wr)。寫指針指向下一個將要寫入的位置,讀指針指向下一個將要讀取的位置。每次寫操作使寫指針加 1,讀操作使讀指針加1。左右兩側的模塊爲讀寫指針與滿空信號產生模塊。這兩個模塊的任務是給 FIFO 提供“ 空”( empty)和“ 滿” ( full)信號。這些信號告訴外部電路 FIFO 已經達到了臨界條件:如果出現“ 滿” 信號,那麼 FIFO 爲寫操作的臨界狀態,如果出現“ 空” 信號,則 FIFO 爲讀操作的臨界狀態。寫操作的臨界狀態表示 FIFO 已經沒有空間來存儲更多的數據,讀操作的臨界表示 FIFO 沒有更多的數據可以讀出。讀寫指針與滿空信號產生模塊還可告訴 FIFO 中“ 滿”或“ 空” 位置的數值。這是由指針的算術運算來完成了。實際的“ 滿” 或“ 空” 位置計算並不是爲 FIFO 自身提供的。它是作爲一個報告機構給外部電路用的。
從功能上看, FIFO 的工作原理如下所述:復位時,讀、寫指針均爲 0。這是 FIFO 的空狀態,空標誌( empty)爲高電平,此時滿標誌( full)爲低電平。當 FIFO 出現空標誌( empty)時,不允許讀操作,只能允許寫操作。寫操作寫入到位置 0,並使寫指針加 1。此時,空標誌( empty)變爲低電平。假設沒有發生讀操作且隨後的一段時間中 FIFO 只有寫操作,一定時間後,寫指針的值等於 7。這就意味着在存儲器中,要寫入數據的最後一個位置就是下一個位置。在這種情況下,寫操作將寫指針變爲 0,並將輸出滿標誌( full)。
爲了更好地判斷空狀態和滿狀態,這裏設置一個四位的計數器( sfifo_cnt),代表存儲器( mem)中寫入但還未讀取的數據個數。當 FIFO 未進行任何讀寫操作時,計數器保持不變;當進行寫操作時,計數器加 1;當進行讀操作時,計數器減 1;當同時進行寫操作和讀操作時,計數器值保持不變。這樣就可以根據計數器中的值來判斷狀態的空與滿,即:當計時器 sfifo_cnt=0 時,表示存儲器處於空狀態,輸出空標誌( empty);當計數器 sfifo_cnt=8時,表示存儲器處於滿狀態,輸出滿標誌( full)。
二. 設計要求
讀寫指針都指向一個內存的初始位置,每進行一次讀寫操作,相應的指針就遞增一次,指向下一個內存位置。當指針移動到了內存的最後一個位置時,它又重新跳回初始位置。在FIFO 非滿或非空的情況下,這個過程將隨着讀寫控制信號的變化一直進行下去。如果 FIFO處於空的狀態,下一個讀動作將會導致向下溢(underflow),一個無效的數據被讀入;同樣,對於一個滿了的 FIFO,進行寫動作將會導致向上溢出(overflow),一個有用的數據被新寫入的數據覆蓋。這兩種情況都屬於誤動作,因此需要設置滿和空兩個信號,對滿信號置位表示FIFO 處於滿狀態,對滿信號復位表示 FIFO 非滿,還有空間可以寫入數據;對空信號置位表示 FIFO 處於空狀態,對空信號復位表示 FIFO 非空,還有有效的數據可以讀出。設計波形如圖所示。
三. 設計流程
3.1 模型
3.1.1 模塊設計
3.1.2 引腳說明
3.1.3 算法設計
已添加註釋:
module sfifo(
clk,
rst_n,
data_in,
wr,
rd,
full,
empty,
data_out,
sfifo_cnt);
input clk;
input rst_n;
input [7:0] data_in;
input wr;
input rd;
output full;
output empty;
output [7:0] data_out;
output [3:0] sfifo_cnt;
wire clk;
wire rst_n;
wire [7:0] data_in;
wire wr;
wire rd;
wire full;
wire empty;
reg [7:0] data_out;
reg [3:0] sfifo_cnt;
`define DEL 1 // Clock-to-output delay
reg [7:0] sfifo_ram[0:7]; // sfifo_ram initialized
reg [2:0] rd_ptr; // Read pointer
reg [2:0] wr_ptr; // Write pointer
assign empty = ( sfifo_cnt == 0 ) ? 1 : 0; //Empty signal
assign full = ( sfifo_cnt == 8 ) ? 1 : 0; //Full signal
// sfifo_cnt changed
// While rd is valid, cnt--
// While wr is valid, cnt++
always @( posedge clk or negedge rst_n) begin
if( ~rst_n ) begin
sfifo_cnt <= #`DEL 4'h0;
end
else if( rd && ~wr ) begin
sfifo_cnt <= #`DEL sfifo_cnt - 1;
end
else if( ~rd && wr ) begin
sfifo_cnt <= #`DEL sfifo_cnt + 1;
end
else begin
sfifo_cnt <= sfifo_cnt;
end
end
/*
//
// The change of sfifo_cnt can also be written like below.
//
always @( posedge clk or negedge rst_n) begin
if( ~rst_n ) begin
sfifo_cnt <= #`DEL 4'h0;
end
else begin
case({ wr,rd })
2'b00 : sfifo_cnt <= #`DEL sfifo_cnt;
2'b01 : sfifo_cnt <= #`DEL (sfifo_cnt==0) ? 0 : fifo_cnt-1;
2'b10 : sfifo_cnt <= #`DEL (sfifo_cnt==8) ? 8 : fifo_cnt+1;
2'b11 : sfifo_cnt<=sfifo_cnt;
default: sfifo_cnt <= sfifo_cnt;
endcase
end
end
*/
// Increment of rd_ptr
// Check if the read pointer has gone beyond the depth of fifo
always @( posedge clk or negedge rst_n) begin
if( ~rst_n ) begin
rd_ptr <= #`DEL 3'h0;
end
else if( rd ) begin
if( rd_ptr == 3'h7 ) begin
rd_ptr <= #`DEL 3'h0;
end
else begin
rd_ptr <= #`DEL rd_ptr +1;
end
end
else begin
rd_ptr <= rd_ptr;
end
end
// Increment of wr_ptr
// Check if the write pointer has gone beyond the depth of fifo
always @( posedge clk or negedge rst_n) begin
if( ~rst_n ) begin
wr_ptr <= #`DEL 3'h0;
end
else if( wr ) begin
if( wr_ptr == 3'h7 ) begin
wr_ptr <= #`DEL 3'h0;
end
else begin
wr_ptr <= #`DEL wr_ptr +1;
end
end
else begin
wr_ptr <= wr_ptr;
end
end
/*
//
// The incerment of pointer can also be written like below.
//
always @( posedge clk or negedge rst_n) begin
if( ~rst_n ) begin
wr_ptr <= #`DEL 3'h0;
rd_ptr <= #`DEL 3'h0;
end
else begin
wr_ptr <= #`DEL wr ? wr_ptr + 1 : wr_ptr;
rd_ptr <= #`DEL rd ? rd_ptr + 1 : rd_ptr;
end
end
*/
//
// Deal with the data
//
always @( posedge clk or negedge rst_n) begin
if( ~rst_n ) begin
data_out <= #`DEL 8'h0;
end
else if( wr ) begin
sfifo_ram[wr_ptr] <= #`DEL data_in;
end
else if( rd ) begin
data_out <= #`DEL sfifo_ram[rd_ptr];
end
end
endmodule //sfifo
3.2 測試模塊
3.2.1 模塊設計
3.2.2 引腳說明
3.1.3 激勵輸入
這裏測試代碼寫的比較簡單,下一篇文章有詳細的設計代碼。
module sfifo_test();
reg clk;
reg rst_n;
reg [7:0] data_in;
reg wr;
reg rd;
wire full;
wire empty;
wire [7:0] data_out;
wire [3:0] sfifo_cnt;
initial begin
rst_n=1;
clk=0;
wr=0;
rd=0;
data_in=0;
#1 rst_n=0;
#5 rst_n=1;
#3 wr=1;
#5 rd=1;
#5 rd=0;
#5 wr=0;
#5 wr=1;
#10 rd=1;
#10 rd=0;
#14 $finish;
end
always begin
#5 clk=~clk;
end
always @(posedge clk or negedge rst_n) begin
if (~rst_n) begin
data_in<=0;
wr<=0;
rd<=0;
end
else begin
data_in<=$random;
end
end
initial begin
$dumpfile ("F:/Robei/practice/prac_07_sfifo/sfifo_test.vcd");
$dumpvars;
end
//---Module instantiation---
sfifo sfifo1(
.clk(clk),
.rst_n(rst_n),
.data_in(data_in),
.wr(wr),
.rd(rd),
.full(full),
.empty(empty),
.data_out(data_out),
.sfifo_cnt(sfifo_cnt));
endmodule //sfifo_test
3.2.4 仿真查看波形
以上就實現了8位同步fifo的設計~