实现同步FIFO的两种方式(Verilog)

文章详细介绍了同步FIFO的概念及其在FPGA设计中的应用,重点讲述了两种实现方法:计数器法和高位扩展法。通过Verilog代码展示了如何设计和测试这两个方法,并讨论了FIFO的满标志和空标志的判断。

1. FIFO简介

  • FIFO 是一种先进先出的数据缓存器,一般用在隔离两边读写带宽不一致,或者位宽不一样的地方。
  • 在 FPGA 设计,使用 FIFO 一般有两个方法,第一个方法是直接调用官方的 FIFO IP,另外一个方法是自己设计 FIFO 控制逻辑。
  • FIFO 包括同步 FIFO 和异步 FIFO 两种。
  • 同步 FIFO 有一个时钟信号,读和写逻辑全部使用这一个时钟信号。
  • 异步 FIFO 有两个时钟信号,读和写逻辑用的各自的读写时钟。
  • FIFO 与 RAM 的区别是没有外部读写地址线;缺点是只能顺序写入数据,顺序的读出数据,其数据地址由内部读写指针自动加 1 完成,不能像RAM那样可以由地址线决定读取或写入某个指定的地址。
  • FIFO 本质上是由 RAM 加读写控制逻辑构成的一种先进先出的数据缓冲器。
  • FIFO 的宽度:即 FIFO 一次读写操作的数据位。
  • FIFO 的深度:指的是 FIFO 可以存储多少个 N 位的数据(如果宽度为 N)。
  • 满标志:FIFO 已满或将要满时由 FIFO 的状态送出的一个信号,以阻止 FIFO 的写操作继续向 FIFO 中写数据而造成溢出(overflow)。
  • 空标志:FIFO 已空或将要空时由 FIFO 的状态送出的一个信号,以阻止 FIFO 的读操作继续从 FIFO 中读出数据而造成无效数据的读出(underflow)。
  • 读时钟:读操作所遵循的时钟,在每个时钟沿来临时读数据(同步FIFO中与写时钟一致)。
  • 写时钟:写操作所遵循的时钟,在每个时钟沿来临时写数据(同步FIFO中与读时钟一致)。

2. 同步FIFO的实现

  • 两种实现方式:计数器法和高位扩展法。
  • FIFO设计关键:产生可靠的 FIFO 读写指针和生成 FIFO“空”/“满”状态标志。
  • FIFO读空的情况:(读指针追上写指针)–读写指针实质为读写地址
    ① 在复位操作时,当读写指针相等时,表明 FIFO 为空;
    ② 当读指针读出 FIFO 中最后一 个字后,追赶上了写指针时,表明 FIFO 为空。如下左图。
  • FIFO写满的情况:(写指针追上读指针)
    ① 当读写指针再次相等时,表明 FIFO 为满,这种情况发生在,当写指针转了一圈,折回来(wrapped around) 又追上了读指针。如下右图。
    左图为读空,右图为写满

2.1 计数器法

构建一个计数器,该计数器(fifo_cnt)用于指示当前 FIFO 中数据的个数

  • 复位时,该计数器为0,FIFO中的数据个数为0,当wr_en = rd_en=1时,说明又读又写,计数器不变,FIFO中的数据个数无变化。
  • wr_en =1,rd_en = 0时且 full=0,则 fifo_cnt +1;表示写操作且 FIFO 未满时候,FIFO 中的数据个数增加了 1 。
  • wr_en =0,rd_en = 1时且 empty=0,则 fifo_cnt -1; 表示读操作且 FIFO 未满时候,FIFO 中的数据个数减少了 1 。
  • fifo_cnt =0 的时候,表示 FIFO 空,需要设置 empty=1;fifo_cnt = fifo的深度 的时候,表示 FIFO现在已经满,需要设置 full=1。

2.1.1 计数器实现同步FIFO–Verilog代码

//计算器法:
//wr_en = rd_en = 1 -> cnt不变;  (cnt指的是当前FIFO内部的数据个数)
//wr_en =0, rd_en = 1 -> cnt=cnt-1;
//wr_en =1, rd_en = 0 -> cnt=cnt+1.
//cnt = fifo_len, -> full=1;
//cnt = 0,  -> empty = 1;

module Sync_FIFO_cnt(
    input wr_en,rd_en,clk,reset,
    input [7:0] dataIn,
    output [7:0] dataOut,
    output full,empty
    );
parameter fifo_len = 8;
reg [3:0] fifo_cnt; // 当前fifo内部数据的个数
reg [7:0] dataOut_reg; 
reg [2:0] addr_wr;
reg [2:0] addr_rd; 
reg full_reg,empty_reg;
reg [7:0] RAM [fifo_len-1:0];

// generate addr_write
always @(posedge clk or posedge reset)
if (reset) begin
    addr_wr <= 3'd0;
    RAM[addr_wr] <= 8'd0;
end
else if (full_reg == 1'd0 && wr_en) begin
    addr_wr <= addr_wr + 1'b1;
    RAM[addr_wr] <= dataIn;
end
else begin
    addr_wr <= addr_wr;
    RAM[addr_wr] <= RAM[addr_wr];
end

// generate addr_read
always @(posedge clk or posedge reset)
if (reset) begin
    addr_rd <= 3'd0;
    dataOut_reg <= 8'd0;
end
else if (empty_reg == 1'd0 && rd_en) begin
    addr_rd <= addr_rd + 1'b1;
    dataOut_reg <= RAM[addr_rd];
end
else begin
    addr_rd <= addr_rd;
    dataOut_reg <= dataOut_reg;
end

// count the number of fifo data
always @(posedge clk or posedge reset)
 if (reset)
    fifo_cnt <= 3'd0;
 else if (wr_en == 1'd1 && rd_en == 1'd0) begin
    fifo_cnt <= fifo_cnt + 1'd1;
    end
 else if (wr_en == 1'd0 && rd_en == 1'd1) begin
    fifo_cnt <= fifo_cnt - 1'd1;
    end
 else begin
    fifo_cnt <= fifo_cnt;
 end

// generate full and empty signal
 always @(*)
 if  (fifo_cnt == (fifo_len)) begin
    full_reg <= 1'd1;
    empty_reg <= 1'd0;
    end
 else if  (fifo_cnt == 3'd0) begin
    full_reg <= 1'd0;
    empty_reg <= 1'd1;
 end
 else begin
    full_reg <= 1'd0;
    empty_reg <= 1'd0;
 end
 
 assign dataOut = dataOut_reg;
 assign full    = full_reg;
 assign empty   = empty_reg;
   
endmodule

2.1.2 Sync_FIFO_cnt_tb

`timescale 1ns/1ns	//时间单位/精度
 
//------------<模块及端口声明>----------------------------------------
module Sync_FIFO_cnt_tb();
 
//parameter   DATA_WIDTH = 8  ;			//FIFO位宽
//parameter   DATA_DEPTH = 8 ;			//FIFO深度
//parameter fifo_depth = 8;
//parameter fifo_depth_bit = 3;
//parameter data_width = 8;
 
reg									clk		;
reg									rst_n	;
reg                                 rst;
reg		[DATA_WIDTH-1:0]			data_in	;
reg									rd_en	;
reg									wr_en	;
						
wire	[DATA_WIDTH-1:0]			data_out;	
wire								empty	;	
wire								full	;
wire	[$clog2(DATA_DEPTH) : 0]	fifo_cnt;
 
 
//------------<例化被测试模块>----------------------------------------
Sync_FIFO_cnt
//#(
//     .fifo_depth(fifo_depth),
//     .fifo_depth_bit(fifo_depth_bit),
//     .data_width(data_width)
//)
Sync_FIFO_cnt_u(
	.clk		(clk		),
	.reset		(~rst_n		),
	.dataIn	(data_in	),
	.rd_en		(rd_en		),
	.wr_en		(wr_en		),
                 
	.dataOut	(data_out	),	
	.empty		(empty		),	
	.full		(full		)		
);
 
//------------<设置初始测试条件>----------------------------------------
initial begin
	clk = 1'b0;							//初始时钟为0
	rst_n <= 1'b0;						//初始复位
	data_in <= 'd0;		
	wr_en <= 1'b0;		
	rd_en <= 1'b0;
//重复8次写操作,让FIFO写满 	
	repeat(8) begin		
		@(negedge clk)begin		
			rst_n <= 1'b1;				
			wr_en <= 1'b1;		
			data_in <= $random;			//生成8位随机数
		end
	end
//重复8次读操作,让FIFO读空	
	repeat(8) begin
		@(negedge clk)begin		
			wr_en <= 1'b0;
			rd_en <= 1'd1;
		end
	end
//重复4次写操作,写入4个随机数据	
	repeat(4) begin
		@(negedge clk)begin		
			wr_en <= 1'b1;
			data_in <= $random;	//生成8位随机数
			rd_en <= 1'b0;
		end
	end
//持续同时对FIFO读写,写入数据为随机数据	
	forever begin
		@(negedge clk)begin		
			wr_en <= 1'b1;
			data_in <= $random;	//生成8位随机数
			rd_en <= 1'b1;
		end
	end
end
//------------<设置时钟>----------------------------------------------
always #10 clk = ~clk;			//系统时钟周期20ns
//rst = ~ rst_n;
endmodule

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

2.2 高位扩展法

  • 当最高位不同,且其他位相同,则表示读指针或者写指针多跑了一圈,而显然不会让读指针多跑一圈,所以可能出现的情况只能是写指针多跑了一圈,就意味着FIFO被写满了。
  • 当最高位相同,且其他位相同,则表示读指针追到了写指针或者写指针追到了读指针,而显然不会让写指针追读指针(这种情况只能是写指针超过读指针一圈),所以可能出现的情况只能是读指针追到了写指针,也就意味着FIFO被读空了。
  • 此处要考虑FIFO深度不是2^N的情况,因此需要把高低位的判断分开。

2.2.1 位扩展实现同步FIFO–Verilog代码

module Sync_FIFO_addBit
#(
parameter fifo_depth = 8,
parameter fifo_depth_bit = 3,
parameter data_width = 8
)
(
    input wr_en,rd_en,clk,reset,
    input [data_width-1:0] dataIn,
    output [data_width-1:0] dataOut,
    output full,empty
    );

reg [data_width-1:0] RAM [fifo_depth-1:0];
reg  [fifo_depth_bit:0] addr_wr;
reg  [fifo_depth_bit:0] addr_rd;
reg [data_width-1:0] dataOut_reg; 
reg full_reg,empty_reg;

// generate addr_wr 
always @(posedge clk or posedge reset) 
if (reset) begin
    addr_wr <= 'd0;
    RAM[addr_wr] <= 'd0;
end
else if (addr_wr[fifo_depth_bit-1:0] < (fifo_depth - 1) && wr_en==1'd1 && full_reg == 1'd0) begin
    addr_wr <= addr_wr + 1'd1;
    RAM[addr_wr] <= dataIn;
end
else if (addr_wr[fifo_depth_bit-1:0] == (fifo_depth - 1) && wr_en==1'd1 && full_reg == 1'd0) begin
    addr_wr[fifo_depth_bit] <= ~addr_wr[fifo_depth_bit];
    addr_wr[fifo_depth_bit-1:0] <= 'd0;
    RAM[addr_wr[fifo_depth_bit-1:0]] <= dataIn;   
end
else begin    
    addr_wr <= addr_wr;
    RAM[addr_wr] <= RAM[addr_wr];
end    

// generate addr_rd 
always @(posedge clk or posedge reset) 
if (reset) begin
    addr_rd <= 'd0;
    dataOut_reg <= 'd0; 
end
else if (addr_rd[fifo_depth_bit-1:0] < (fifo_depth - 1) && rd_en==1'd1 && empty_reg == 1'd0) begin
    addr_rd <= addr_rd + 1'd1;
    dataOut_reg <= RAM[addr_rd[fifo_depth_bit-1:0]];
end
else if (addr_rd[fifo_depth_bit-1:0] == (fifo_depth - 1) && rd_en==1'd1 && empty_reg == 1'd0) begin
    addr_rd[fifo_depth_bit] <= ~addr_rd[fifo_depth_bit];
    addr_rd[fifo_depth_bit-1:0] <= 'd0;
    dataOut_reg <= RAM[addr_rd[fifo_depth_bit-1:0]];
end
else begin
    addr_rd <= addr_rd;
    dataOut_reg <= dataOut_reg;
end    
// judge full and empty
always @(*)
if (reset) begin
    empty_reg <= 1'd0;
    full_reg <= 1'd0;
end
else if (addr_wr[fifo_depth_bit-1:0] == addr_rd[fifo_depth_bit-1:0] && addr_wr[fifo_depth_bit] != addr_rd[fifo_depth_bit]) begin
    empty_reg <= 1'd0;
    full_reg <= 1'd1;
end
else if (addr_wr == addr_rd) begin
    empty_reg <= 1'd1;
    full_reg <= 1'd0;
end    
else begin
    empty_reg <= 1'd0;
    full_reg <= 1'd0;
end

assign dataOut = dataOut_reg;
assign full = full_reg;
assign empty = empty_reg;

endmodule

2.2.2 Sync_FIFO_addBit_tb

`timescale 1ns/1ns	//时间单位/精度
 
//------------<模块及端口声明>----------------------------------------
module Sync_FIFO_cnt_tb();
 
parameter   DATA_WIDTH = 8  ;			//FIFO位宽
parameter   DATA_DEPTH = 8 ;			//FIFO深度
parameter fifo_depth = 8;
parameter fifo_depth_bit = 3;
parameter data_width = 8;
 
reg									clk		;
reg									rst_n	;
reg                                 rst;
reg		[DATA_WIDTH-1:0]			data_in	;
reg									rd_en	;
reg									wr_en	;
						
wire	[DATA_WIDTH-1:0]			data_out;	
wire								empty	;	
wire								full	;
wire	[$clog2(DATA_DEPTH) : 0]	fifo_cnt;
 
 
//------------<例化被测试模块>----------------------------------------
Sync_FIFO_addBit
#(
     .fifo_depth(fifo_depth),
     .fifo_depth_bit(fifo_depth_bit),
     .data_width(data_width)
)
Sync_FIFO_addBit_u(
	.clk		(clk		),
	.reset		(~rst_n		),
	.dataIn	(data_in	),
	.rd_en		(rd_en		),
	.wr_en		(wr_en		),
                 
	.dataOut	(data_out	),	
	.empty		(empty		),	
	.full		(full		)		
);
 
//------------<设置初始测试条件>----------------------------------------
initial begin
	clk = 1'b0;							//初始时钟为0
	rst_n <= 1'b0;						//初始复位
	data_in <= 'd0;		
	wr_en <= 1'b0;		
	rd_en <= 1'b0;
//重复8次写操作,让FIFO写满 	
	repeat(8) begin		
		@(negedge clk)begin		
			rst_n <= 1'b1;				
			wr_en <= 1'b1;		
			data_in <= $random;			//生成8位随机数
		end
	end
//重复8次读操作,让FIFO读空	
	repeat(8) begin
		@(negedge clk)begin		
			wr_en <= 1'b0;
			rd_en <= 1'd1;
		end
	end
//重复4次写操作,写入4个随机数据	
	repeat(4) begin
		@(negedge clk)begin		
			wr_en <= 1'b1;
			data_in <= $random;	//生成8位随机数
			rd_en <= 1'b0;
		end
	end
//持续同时对FIFO读写,写入数据为随机数据	
	forever begin
		@(negedge clk)begin		
			wr_en <= 1'b1;
			data_in <= $random;	//生成8位随机数
			rd_en <= 1'b1;
		end
	end
end
//------------<设置时钟>----------------------------------------------
always #10 clk = ~clk;			//系统时钟周期20ns
//rst = ~ rst_n;
endmodule

仿真结果:
在这里插入图片描述

  • 实现了同步FIFO,下一篇学习异步FIFO。

参考文章:
同步FIFO的两种Verilog设计方法(计数器法、高位扩展法)

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值