單時鐘同步 FIFO 和二時鐘異步 FIFO 的基本原理和實現

同步 FIFO 的實現

學習目的:

  • 1.給大家介紹FIFO的基本原理和組成。
  • 2.學習同步單時鐘FIFO的分析以及編寫。
  • 3.以實例體驗如何將實際需求分析轉換成代碼。

FIFO(First In First Out)隊列通常在數字系統中實現兩個模塊間數據的暫存,達到減少兩模塊之間的速度依賴,使得兩個模塊能夠相互相對獨立的運行。

FIFO是FPGA設計的一個非常基本的單元。FIFO一般用一個雙口RAM和分別指示讀寫地址的指針,以及狀態生成和指示的邏輯組成,下圖是典型的FIFO結構圖:
在這裏插入圖片描述

我們看到中上部是一個雙口RAM,所謂雙口RAM是分別有兩套讀寫數據線地址線以及控制線,用以實現讀寫的同時操作。

再看到讀寫指針和讀寫控制界面,左邊是寫控制界面和寫指針,實現對寫指針的更新。右邊是讀控制界面和讀指針,實現對讀指針的更新。我們還有比較邏輯,這裏產生空empty信號和full信號。

我們來看一下存儲器的工作方式,實際是一個循環隊列:我們來跟蹤一下情景:

以下存儲器有8個存儲單元,在初始化的時候,讀指針RP和WP都指向0地址,當向存儲器裏寫入了8個數據後,寫指針會從新回到0,這就是循環隊列。

這裏地址是0-7是3個BIT寬度,WP和RP也要對應的是3個bit,加1就能實現從0,1,2.。6.7.0.1.2…這樣循環。下圖中陰影的部分標示存儲區已經佔用。
在這裏插入圖片描述
以上分析了WP和RP的行爲,還需要產生空和滿信號的邏輯,實現方法我們這裏設置一個計數器 FIFO_CNTR,用以表示有效可讀數據的個數,初始化的時候是0,標示存儲器內沒有數據可供讀。

當寫入接口進行一次有效的寫操作(這裏規定有效的寫操作是FIFO不滿的時候實施的寫操作)計數器FIFO_CNTR加1,
當讀出接口進行一次有效的讀操作(這裏規定有效的讀操作是FIFO不空的時候實施的讀操作)計數器FIFO_CNTR減1;

而當同時實施有效的寫操作和讀操作時,FIFO_CNTER不發生變化(從圖上可以分析出實際讀指針和寫指針都往前走了,他們間距還是一樣的,有效可讀數據的存儲個數還是保持不變)。

另外關於FIFO_CONTER的個數,我們這裏看到範圍是是0-8,(0時爲空,8時爲滿),因此需要4個bit來定義FIFO_CNTR.因此通常情況下FIFO_CNTR要比地址位數多1位。

至於說到full和empty很簡單的由FIFO_COUNTER產生:當且僅當FIFO_COUNTER=0時候,empty=1;當且僅當FIFO_CNTER爲最多可容納存儲個數時,這裏說FIFO_CNTR = 8時候,full=1;

我們再總結以上的分析,具體細化一下:
1,需要一個buff雙口存儲器做存儲載體,當地址有n位,就有2N2^{N}個存儲單元,標號從0到(2N12^{N-1})。三位地址存儲地址就是從0到7。
2,需要wr_ptr指針,與地址位數相同,指向下個可以寫的地址。
3,需要rd_ptr指針,與地址位數相同,指向下個可以讀的地址。
4,需要fifo_cntr計數器,比地址位數多一位,指示當前可讀的數據個數。
5,full和empty由fifo_cntr生成。
6,區別讀寫操作是否有效。
7,實現上述功能。

代碼呼之欲出了,我們來寫代碼,首先是定義端口,之後就開始寫代碼。寫完之後進行檢查。

module sc_fifo(
	input clk,rst,
	
	input [7:0] din,
	input wr,
	output full,
	
	output reg [7:0] dout,
	input rd,
	output empty
);

//1,需要一個buff雙口存儲器做存儲載體,當地址有n位,就有2^N個存儲單元,標號從0到(2^N-1)。三位地址存儲地址就是從0到7。
reg [7:0] buff[0:7] ;


//2,需要wr_ptr指針,與地址位數相同,指向下個可以寫的地址。
reg [2:0] wr_ptr ;

//3,需要rd_ptr指針,與地址位數相同,指向下個可以讀的地址。
reg [2:0] rd_ptr ;

//4,需要fifo_cntr計數器,比地址位數多一位,指示當前可讀的數據個數。
reg [3:0] fifo_cntr ;

//5,full和empty由fifo_cntr生成。
assign full = fifo_cntr ==8 ;
assign empty= fifo_cntr==0;

//6,區別讀寫操作是否有效。
wire valid_rd =  ~empty & rd ;
wire valid_wr = ~full & wr ;

//7,實現上述功能。
always@(posedge clk) 
	if (rst) 
		wr_ptr <= 0;
	else if(valid_wr)
		wr_ptr<=wr_ptr+1;
		
always@(posedge clk) 
	if (rst)
		rd_ptr <= 0 ;
	else if (valid_rd)
		rd_ptr <= rd_ptr+1;

/*
always@(posedge clk) if (rst)fifo_cntr<=0;else 
if ((valid_rd==0) &&(valid_wr==1))fifo_cntr<=fifo_cntr+1;
else if ((valid_rd==1)&&(valid_wr==0))fifo_cntr<fifo_cntr-1;
*/

always@(posedge clk) 
casex ({rst,valid_wr,valid_rd})
	3'b1xx : fifo_cntr<=0;
	3'b010 : fifo_cntr<=fifo_cntr+1;
	3'b001 : fifo_cntr<=fifo_cntr-1;
	3'b011 ,3'b000 :fifo_cntr<=fifo_cntr ;
endcase 


always@(posedge clk) 
	if (valid_wr) 
		buff[wr_ptr] <=din ;
		
always@(posedge clk) 
	if (valid_rd)
		dout<= buff[rd_ptr] ;

endmodule

異步 FIFO 的實現

其實FIFO是也是在RAM的基礎上增加了許多功能,FIFO的典型結構如下,主要分爲讀和寫兩部分,另外就是狀態信號,空和滿信號,同時還有數據的數量狀態信號,與RAM最大的不同是FIFO沒有地址線不能進行隨機地址讀取數據,什麼是隨機讀取數據呢,也就是可以任意讀取某個地址的數據。而FIFO則不同,不能進行隨機讀取,這樣的好處是不用頻繁地控制地址線

最終將在 AX-7020 FPGA 上實現。
在這裏插入圖片描述
雖然用戶看不到地址線,但是在FFO內部還是有地址的操作的,用來控制RAM的讀寫接口。其地址在讀寫操作時如下圖所示,其中深度值也就是一個FIFO裏最大可以存放多少個數 據。初始狀態下,讀寫地址都爲0,在向FIFO中寫入一個數據後,寫地址加1,從FFO中讀出 一個數據後,讀地址加1。此時FFO的狀態即爲空,因爲寫了一個數據,又讀出了一個數據。
在這裏插入圖片描述
可以把 FIFO 想象成一個水池,寫通道即爲加水,讀通道即爲放水,假如不間斷的加水和放水,如果加水速度比放水速度快,那麼FIFO就會有滿的時候,如果滿了還繼續加水就會溢出overflow,如果放水速度比加水速度快,那麼FIFO就會有空的時候,所以把握好加水與放水的時機和速度,保證水池一直有水是一項很艱鉅的任務。也就是判斷空與滿的狀態,擇機寫數據或讀數據。

下面主要介紹異步 FFO 的控制,其中讀時鐘爲 25 MHz,寫時鐘爲 50 MHz。實驗中會通過 vivado 集成的在想邏輯分析儀 ila ,我們可以觀察FIFO的讀寫時序。

FIFO IP 核的端口列表如下:
在這裏插入圖片描述
FIFO 的數據寫入和讀出都是按時鐘的上升沿操作的,當 wr_en 信號爲高時寫入 FIFO 數據,當 almost_full 信號有效時,表示 FIFO 只能再寫入一個數據,一旦寫入一個數據了,full 信號就會拉高,如果在 full 的情況下 wr_en 仍然有效,也就是繼續向 FIFO 寫數據,則 FIFO 的 overflow 就會有效,表示溢出。(如下圖所示)
在這裏插入圖片描述
當 rd_en 信號爲高時讀FIFO數據,數據在下個週期有效。valid爲數據有效信號,almost_empty表示還有一個數據讀,當再讀一個數據,empty信號有效,如果繼續讀,則underflow有效,表示下溢,此時讀出的數據無效。(如下圖所示)
在這裏插入圖片描述
而從 FWFT 模式讀數據時序圖可以看出, rden 信號有效時,有效數據D已經在數據線上準備好有效了,不會再延後一個週期。這就是與標準FIFO的不同之處。(如下圖所示)
在這裏插入圖片描述
有一點需要注意的是,FFO設置默認爲採用 safety circuit,此功能是保證到達內部RAM的輸入信號是同步的,在這種情況下,如果異步復位後,則需要等待60個最慢時鐘週期。(如下圖所示,更詳細的還請參考文檔)
在這裏插入圖片描述
在有了上面的基礎知識後,我們開始對異步 FIFO 進行代碼的編寫,要實現以下幾個功能:

    1. 對輸入時鐘進行分頻(二分頻)PS:分頻操作也可以調用鎖相環 IP 核
    1. 調用 Xilinx 的FIFO IP核
    1. 需要編寫對FIFO 的控制邏輯(狀態機)
    1. 調用 XIlinx 的 ILA IP 覈對讀到的輸出進行捕捉

代碼如下:

`timescale 1ns / 1ps

module fifo(
	input clk,
	input rst_n
    );

	wire div2;

	wire wr_clk;
	wire rd_clk;
    wire [15:0] dout;
	assign rd_clk = div2;
	assign wr_clk = clk;

	reg [6:0] wcn;
	reg [6:0] rcn;

	reg [15:0] din_reg;

	wire wr_en;
	wire rd_en;

	wire full;
	wire empty;

	wire [9:0] rd_data_count;
	wire [9:0] wr_data_count;

	localparam IDLE = 0;
	localparam WRITE = 1;
	localparam READ = 2;

	// write FSM
	reg [1:0] write_state;
	assign wr_en = (write_state == WRITE) ? ~full : 1'b0;
	always @ (posedge clk or negedge rst_n)
	begin
		if(!rst_n)
		begin
			write_state <= IDLE;
			din_reg <= 16'd0;
		end
		else
		begin
			case(write_state)

				IDLE:
				begin
					if(wcn >= 7'd60)
						write_state <= WRITE;
					else
						write_state <= IDLE;
				end

				WRITE:
				begin
					if (wr_en)
					begin     
						write_state <= WRITE;
						din_reg <= din_reg + 16'd1;
					end
					else
						write_state <= write_state;
				end

				default:
					write_state <= write_state;

			endcase
		end
	end

	always @ (posedge clk or negedge rst_n) begin
		if (!rst_n)
			wcn <= 7'd0;
		else if(write_state == IDLE)
			wcn <= wcn + 7'd1;
		else
			wcn <= 7'd0;	
	end
	
	// read fsm
	reg [1:0] read_state;
	assign rd_en = (read_state == READ) ? ~empty : 1'b0;
	always @ (posedge rd_clk or negedge rst_n) begin
		if (!rst_n)
		begin
			read_state <= IDLE;
		end
		else
		begin
			case(read_state)

			IDLE:
			begin
				if (rcn >= 7'd60)
				begin
					read_state <= READ;
				end
				else
					read_state <= IDLE;
				        
			end

			READ:
			begin
				 read_state <= READ;       
			end

			default:
				read_state <= read_state;

			endcase

		end 
	        
	end

	always @ (posedge rd_clk or negedge rst_n) begin
		if (!rst_n)
			rcn <= 7'd0;
		else if(read_state == IDLE)
			rcn <= rcn + 7'd1;
		else
			rcn <= 7'd0;	        
	end

div2 diver2(
	.clk(clk),
	.rst_n(rst_n),
	.div2_o(div2)

);

fifo_ip fifo_wr (
  .rst(~rst_n),                      // input wire rst
  .wr_clk(wr_clk),                // input wire wr_clk
  .rd_clk(rd_clk),                // input wire rd_clk

  .din(din_reg),                      // input wire [15 : 0] din
  .wr_en(wr_en),                  // input wire wr_en
  .rd_en(rd_en),                  // input wire rd_en

  .dout(dout),                    // output wire [15 : 0] dout
  .full(full),                    // output wire full
  .empty(empty),                  // output wire empty
  .rd_data_count(rd_data_count),  // output wire [9 : 0] rd_data_count
  .wr_data_count(wr_data_count),  // output wire [9 : 0] wr_data_count
  .wr_rst_busy(),      // output wire wr_rst_busy
  .rd_rst_busy()      // output wire rd_rst_busy
);

ila_0 ila_ip (
	.clk(rd_clk), // input wire clk
	.probe0(dout) // input wire [15:0] probe0
);

endmodule

// 分頻操作
module div2(
	input						clk			,
	input						rst_n			,
	output						div2_o			
    );

	reg 						div2_o_r		;
	
	assign div2_o = div2_o_r;

	always @ (posedge clk or negedge rst_n) begin
		if(!rst_n)
			div2_o_r <= 0;
		else
			div2_o_r <= ~div2_o_r;
	        
	end


endmodule

仿真文件如下:

module fifo_tb;

reg clk;
reg rst_n;

always
begin
	clk = 0;
	#10;
	clk = 1;
	#10;
end

initial
begin
	rst_n = 0;
	#20;
	rst_n = 1;
end

fifo fifo_tb(
	.clk(clk),
	.rst_n(rst_n)
	);

endmodule

最終經過綜合、比特文件生成,在板子上跑的結果如下:
在這裏插入圖片描述

另附上約束文件:

set_property PACKAGE_PIN N15 [get_ports rst_n]
set_property PACKAGE_PIN U18 [get_ports clk]
set_property IOSTANDARD LVCMOS33 [get_ports rst_n]
set_property IOSTANDARD LVCMOS33 [get_ports clk]

create_clock -period 20.000 -name clk -waveform {0.000 10.000} [get_ports clk]
set_property C_CLK_INPUT_FREQ_HZ 300000000 [get_debug_cores dbg_hub]
set_property C_ENABLE_CLK_DIVIDER false [get_debug_cores dbg_hub]
set_property C_USER_SCAN_CHAIN 1 [get_debug_cores dbg_hub]
connect_debug_port dbg_hub/clk [get_nets div2]

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