单时钟同步 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位,就有 2 N 2^{N} 2N个存储单元,标号从0到( 2 N − 1 2^{N-1} 2N1)。三位地址存储地址就是从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]

  • 2
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

wangbowj123

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值