verilog實現FIFO設計(一)之同步8位深度

最近學習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的設計~

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章