同步FIFO

同步FIFO

本文参考:

一、概念介绍

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

  • 1
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
FPGA 同步 FIFO 是一种用于在 FPGA 设备中实现数据缓冲和数据转移的组件。它由一个读取指针和一个写入指针组成,可以实现读写操作的同步和互斥。 使用 FPGA 同步 FIFO 的一个常见场景是在不同频率的数据传输之间进行数据缓冲和同步。当输入以不同频率产生数据时,为了保证数据的可靠传输和处理,可以使用同步 FIFO 来缓冲输入数据,并在输出端以相同或不同的频率读取数据。 FPGA 同步 FIFO 的实现可以采用 FPGA 内部的存储单元,如 Block RAM 或 Distributed RAM。写入操作将数据写入 FIFO 的写入指针所指向的位置,并将写入指针前移。读取操作将数据从 FIFO 的读取指针所指向的位置读取出来,并将读取指针前移。读写指针的移动是同步的,以保证数据的正确性和完整性。 FPGA 同步 FIFO 的大小通常取决于数据传输的需求和 FPGA 设备的资源限制。可以根据数据产生和处理的速度来确定 FIFO 的大小,并且需要根据需要调整读写指针的顺序和移动策略,以满足数据的传输需求。 尽管 FPGA 同步 FIFO 在数据传输中起到了重要的作用,但同时也会增加设计的复杂性和资源消耗。在使用 FPGA 同步 FIFO 时,需要注意处理数据的同步和互斥问题,以及避免出现数据丢失、溢出等异常情况。 总之,FPGA 同步 FIFO 是一种用于实现数据缓冲和转移的组件,在不同频率的数据传输中发挥了关键作用。它可以通过读写指针的同步移动来保证数据的可靠性和完整性,并且可根据需求和硬件资源进行灵活调整。但同时也需要注意处理同步和互斥问题,以确保数据的正确传输。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值