我是靠谱客的博主 俊秀大地,最近开发中收集的这篇文章主要介绍同步FIFO同步FIFO,觉得挺不错的,现在分享给大家,希望可以做个参考。

概述

同步FIFO

本文参考:

  • 同步FIFO的两种Verilog设计方法(计数器法、高位扩展法)
  • 01 【Verilog实战】同步FIFO的设计(附源码RTL/TB)

一、概念介绍

1.1 什么是FIFO

  FIFO是一种先进先出的数据缓存器,一般用在隔离两边读写带宽不一致,或者位宽不一样的地方。在FPGA设计中,使用FIFO一般有两个方法,第一个方法是直接调用官方的FIFO IP,第二个方法是自己设计FIFO控制逻辑。

  FIFO包括同步FIFO异步FIFO两种,同步FIFO有一个时钟信号,读和写逻辑全部使用这一个时钟信号;异步FIFO有两个时钟信号,读和写逻辑使用各自的时钟。FIFO 与普通存储器 RAM 的区别是没有外部读写地址线,使用起来非常简单,但缺点就是只能顺序写入数据,顺序的读出数据,其数据地址由内部读写指针自动加1完成,不能像普通存储器那样可以由地址线决定读取或写入某个指定的地址。 FIFO 本质上是由 RAM 加读写控制逻辑构成的一种先进先出的数据缓冲器。

1.2 FIFO常见参数

  • FIFO的宽度:即FIFO一次读写操作的数据位
  • FIFO的深度:即FIFO可以存储多少个N位数据
  • 满标志:FIFO 已满或将要满时由 FIFO 的状态送出的一个信号,以阻止 FIFO 的写操作继续向 FIFO 中写数据而造成溢出
  • 空标志:FIFO 已空或将要空时由 FIFO 的状态送出的一个信号,以阻止 FIFO 的读操作继续从 FIFO 中读出数据而造成无效数据的读出
  • 读时钟:读操作所遵循的时钟,在每个时钟沿来临时读数据(同步FIFO中与写时钟一致)
  • 写时钟:写操作所遵循的时钟,在每个时钟沿来临时写数据(同步FIFO中与读时钟一致)

二、同步FIFO设计方法

2.1 数据机制

  FIFO是一个顺序数据结构,先存入的数据先取出,可以看成是一个上下都没有封口的羽毛球桶,先放进去的羽毛球也一定会先掉出来。

在这里插入图片描述

  FIFO数据的写入写出依靠读指针和写指针的移动,写指针RP指向下一个要写入的数据单元,读指针WP指向即将取出的数据单元。如下图所示,可以加一个计数器来标志FIFO的状态,初始化时,读指针和写指针指向同一数据地址,计数器为0。


在这里插入图片描述

  指针的移动分为三种情况:

  • 写使能有效:写地址加一,读地址不变,计数器加一,标志着FIFO里多存入了一个数;
  • 读使能有效:读地址加一,写地址不变,计数器减一,标志着FIFO里空出来一个单元;
  • 读写使能同时有效:读写地址均加一,计数器不变。

  计数器等于0的时候,表示FIFO为空;计数器等于FIFO深度的时候,表示FIFO已满,以此产生空信号和满信号。

1.4 端口描述

在这里插入图片描述
在这里插入图片描述

二、RTL实现

`timescale 1ns/1ps
module sync_fifo #(
	parameter	DATA_WIDTH	= 32,
	parameter	DATA_DEPTH	= 8,
	parameter	PTR_WIDTH	= $clog2(DATA_DEPTH)
)(
	input	wire						clk_i,
	input	wire						rst_n_i,
	
	// write interface
	input	wire						wr_en_i,
	input	wire	[DATA_WIDTH-1:0]	wr_data_i,

	// read interface
	input	wire						rd_en_i,	
	output	reg		[DATA_WIDTH-1:0]	rd_data_o,
	
	//Flags_o
	output	wire						full_o,
	output	wire						empty_o
);
	reg				[DATA_WIDTH-1:0]	FIFO[0:DATA_DEPTH-1];
	reg				[PTR_WIDTH-1:0]		wr_ptr;
	reg				[PTR_WIDTH-1:0]		rd_ptr;
	reg				[PTR_WIDTH:0]		fifo_cnt;
	
/*---------------------------------------------------
  --------------- FIFO counter ---------------------
---------------------------------------------------*/
always @(posedge clk_i or negedge rst_n_i) begin
	if(!rst_n_i)
		fifo_cnt <= 4'b0;
	else
		if(rd_en_i==1'b1 && wr_en_i==1'b0 && empty_o==1'b0)
			fifo_cnt <= fifo_cnt - 4'b1;
		else if(wr_en_i==1'b1 && rd_en_i==1'b0 && full_o==1'b0)
			fifo_cnt <= fifo_cnt + 1;
//		else if(wr_en_i==1'b1 && rd_en_i==1'b1)
//			fifo_cnt <= fifo_cnt;
		else
			fifo_cnt <= fifo_cnt;
end

/*---------------------------------------------------
  ------------- generate the flags -----------------
---------------------------------------------------*/
assign full_o	= (fifo_cnt==DATA_DEPTH)	? 1'b1 : 1'b0;
assign empty_o	= (fifo_cnt==4'b0) 			? 1'b1 : 1'b0;

/*---------------------------------------------------
  -------------------- read data -------------------
---------------------------------------------------*/
always @(posedge clk_i or negedge rst_n_i) begin
	if(!rst_n_i)
		rd_ptr <= 3'b0;
	else if(rd_en_i==1'b1 && empty_o==1'b0)
		rd_ptr <= rd_ptr + 3'b1;
	else
		rd_ptr <= rd_ptr;
end


always @(posedge clk_i or negedge rst_n_i) begin
	if(!rst_n_i)
		rd_data_o <= 32'b0;
	else
		if(rd_en_i==1'b1 && empty_o==1'b0)
			rd_data_o <= FIFO[rd_ptr];
end 

/*---------------------------------------------------
  -------------------- write data ------------------
---------------------------------------------------*/
always @(posedge clk_i or negedge rst_n_i) begin
	if(!rst_n_i)
		wr_ptr <= 3'b0;
	else if(wr_en_i==1'b1 && full_o==1'b0)
		wr_ptr <= wr_ptr + 3'b1;
	else
		wr_ptr <= wr_ptr;
end

integer i;
always @(posedge clk_i or negedge rst_n_i) begin
	if(!rst_n_i)
		for(i=0;i<DATA_DEPTH;i=i+1)
			FIFO[i] <= 32'b0;
	else
		if(wr_en_i==1'b1 && full_o==1'b0)
			FIFO[wr_ptr] <= wr_data_i;
end 

endmodule

测试文件:

`timescale 1ns/1ps
module sync_fifo_tb;
	reg				clk_i;
	reg				rst_n_i;
		// write interface
	reg				wr_en_i;
	reg		[31:0]	wr_data_i;

		// read interface
	reg				rd_en_i;
	wire	[31:0]	rd_data_o;
		
		//Flags_o
	wire			full_o;
	wire			empty_o;

initial begin
	clk_i			= 0;
	rst_n_i			= 1;
	wr_data_i		= 0;
	#100 rst_n_i 	= 0;
	#100 rst_n_i 	= 1;
end

always #50 	clk_i 		= ~clk_i;
always #100	wr_data_i	= {wr_data_i+1}%10;

initial begin
	wr_en_i = 1;
	rd_en_i = 0;
	#800;
	wr_en_i = 0;
	rd_en_i = 1;
	#300;
	wr_en_i = 1;
	rd_en_i = 0;
	#1000;
	wr_en_i = 0;
	rd_en_i = 1;
	#1000;
	wr_en_i = 1;
	rd_en_i = 0;
	#300;
	wr_en_i = 1;
	rd_en_i = 1;
	#1000;
	$stop;
end

sync_fifo u_sync_fifo(
  .clk_i    (clk_i    ),
  .rst_n_i  (rst_n_i  ),
  .wr_en_i  (wr_en_i  ),
  .wr_data_i(wr_data_i),
  .rd_en_i  (rd_en_i  ),
  .rd_data_o(rd_data_o),
  .full_o   (full_o   ),
  .empty_o  (empty_o  )
);

endmodule

测试结果:
在这里插入图片描述

三、高位扩展法

  在之前的方法中,使用了一个计数器判断FIFO中数据的存储,如在深度为8的FIFO中,需要3bit的读写指针来分别指示读写地址3’b000-3’b111这8个地址。若将地址指针扩展1bit,则变成4bit的地址,而地址表示区间则变成了4’b0000-4’b1111。假设不看最高位的话,后面3位的表示区间仍然是3’b000-3’b111,也就意味着最高位可以拿来作为指示位。

  • 当最高位不同,且其他位相同,则表示读指针或者写指针多跑了一圈,而显然不会让读指针多跑一圈(多跑一圈读啥?),所以可能出现的情况只能是写指针多跑了一圈,与就意味着FIFO被写满了
  • 当最高位相同,且其他位相同,则表示读指针追到了写指针或者写指针追到了读指针,而显然不会让写指针追读指针(这种情况只能是写指针超过读指针一圈),所以可能出现的情况只能是读指针追到了写指针,也就意味着FIFO被读空了
`timescale 1ns/1ps
module sync_fifo2 #(
	parameter	DATA_WIDTH	= 32,
	parameter	DATA_DEPTH	= 8,
	parameter	PTR_WIDTH	= $clog2(DATA_DEPTH)
)(
	input	wire						clk_i,
	input	wire						rst_n_i,
	
	// write interface
	input	wire						wr_en_i,
	input	wire	[DATA_WIDTH-1:0]	wr_data_i,

	// read interface
	input	wire						rd_en_i,	
	output	reg		[DATA_WIDTH-1:0]	rd_data_o,
	
	//Flags_o
	output	wire						full_o,
	output	wire						empty_o
);
	reg				[DATA_WIDTH-1:0]	FIFO[0:DATA_DEPTH-1];
	reg				[PTR_WIDTH:0]		wr_ptr;
	reg				[PTR_WIDTH:0]		rd_ptr;
	
	wire								wr_ptr_msb;
	wire								rd_ptr_msb;
	wire			[PTR_WIDTH-1:0]		wr_ptr_true;
	wire			[PTR_WIDTH-1:0]		rd_ptr_true;

/*---------------------------------------------------
  ------------- generate the flags -----------------
---------------------------------------------------*/
assign full_o	= (rd_ptr=={~wr_ptr_msb, wr_ptr_true})	? 1'b1 : 1'b0;
assign empty_o	= (rd_ptr==wr_ptr) 						? 1'b1 : 1'b0;

/*---------------------------------------------------
  -------------------- read data -------------------
---------------------------------------------------*/
always @(posedge clk_i or negedge rst_n_i) begin
	if(!rst_n_i)
		rd_ptr <= 3'b0;
	else if(rd_en_i==1'b1 && empty_o==1'b0)
		rd_ptr <= rd_ptr + 3'b1;
	else
		rd_ptr <= rd_ptr;
end

assign {rd_ptr_msb, rd_ptr_true} = rd_ptr;


always @(posedge clk_i or negedge rst_n_i) begin
	if(!rst_n_i)
		rd_data_o <= 32'b0;
	else
		if(rd_en_i==1'b1 && empty_o==1'b0)
			rd_data_o <= FIFO[rd_ptr_true];
end 

/*---------------------------------------------------
  -------------------- write data ------------------
---------------------------------------------------*/
always @(posedge clk_i or negedge rst_n_i) begin
	if(!rst_n_i)
		wr_ptr <= 3'b0;
	else if(wr_en_i==1'b1 && full_o==1'b0)
		wr_ptr <= wr_ptr + 3'b1;
	else
		wr_ptr <= wr_ptr;
end

assign {wr_ptr_msb, wr_ptr_true} = wr_ptr;

integer i;
always @(posedge clk_i or negedge rst_n_i) begin
	if(!rst_n_i)
		for(i=0;i<DATA_DEPTH;i=i+1)
			FIFO[i] <= 32'b0;
	else
		if(wr_en_i==1'b1 && full_o==1'b0)
			FIFO[wr_ptr_true] <= wr_data_i;
end 

endmodule

最后

以上就是俊秀大地为你收集整理的同步FIFO同步FIFO的全部内容,希望文章能够帮你解决同步FIFO同步FIFO所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(201)

评论列表共有 0 条评论

立即
投稿
返回
顶部