手撕FIFO(Verilog)

本文详细介绍了FPGA设计中同步FIFO的实现原理和异步FIFO的结构、原理,包括双口RAM、地址指针处理以及跨时钟域的技术。同时,讨论了手写代码而非直接使用IP核的必要性,如芯片更换和面试技巧

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1.简介

FIFO,先进先出存储,没有地址。何为异步?即读写为不同的时钟。
我们在FPGA的设计中经常会使用到FIFO,一般的做法都是直接调用IP核,可为什么要动手亲自写代码呢?1.当我们更换芯片厂家时,IP是不可移植的,这时候就需要重新改代码,工作量大.;2.有的面试可能会让你手撕代码。以上个人见解。

2.同步FIFO

FIFO的读写均由同一个时钟控制,实现如下(高位判别法)

module syn_fifo #(
    parameter   width = 8,
    parameter   depth = 64)(
	input   clk                           ,
    input   rstn                          ,
    input   wr_en                         ,
    input   rd_en                         ,
										  
    input   [width - 1 : 0] wr_data       ,
    output  reg [width - 1 : 0] rd_data   ,
    output  fifo_full                     ,
    output  fifo_empty	                  
);

	localparam	addr_width = $clog2(depth); 

    //定义读写指针
    reg [addr_width  : 0] wr_ptr;
    reg [addr_width  : 0] rd_ptr;
	
	//定义读写地址
	wire [addr_width - 1 : 0] wr_addr;
	wire [addr_width - 1 : 0] rd_addr;

    //定义存储
    reg [width - 1 : 0] fifo [depth - 1 : 0];


	assign wr_addr = wr_ptr[addr_width - 1:0];
	assign rd_addr = rd_ptr[addr_width - 1:0];


    //写地址操作
    always @ (posedge clk or negedge rstn) begin
        if(!rstn)
            wr_ptr <= 0;
        else if(wr_en && !fifo_full)    //写使能,且fifo未写满
            wr_ptr <= wr_ptr + 1;
        else
            wr_ptr <= wr_ptr;
    end

    //读地址操作
    always @ (posedge clk or negedge rstn) begin
        if(!rstn)
            rd_ptr <= 0;
        else if(rd_en && !fifo_empty)   //读使能,且fifo不为空
            rd_ptr <= rd_ptr + 1;
        else
            rd_ptr <= rd_ptr;
    end

    //写数据
    integer i;

    always @ (posedge clk or negedge rstn) begin
        if(!rstn) begin 
            for(i = 0; i < depth; i = i + 1)
                fifo[i] <= 0;
        end
        else if(wr_en)  	
            fifo[wr_addr] <= wr_data;
    end

    //读数据
    always @ (posedge clk or negedge rstn) begin
        if(!rstn)
            rd_data <= 0;
        else if (rd_en)
            rd_data <= fifo[rd_addr];   
        else
            rd_data <= rd_data;
    end

//空满判断
assign	fifo_empty = ( wr_ptr == rd_ptr ) ? 1'b1 : 1'b0;
assign	fifo_full  = ( rd_ptr == {!wr_ptr[addr_width],wr_ptr[addr_width-1:0]})? 1'b1 : 1'b0;

endmodule

仿真结果分析:初始设置FIFO深度为64,tb仿真写入了66个数据。由于代码设定fifo写满后wr_ptr就不再增加了,因此重复向0地址写入数据。当开始读出数据时,0地址读出65。
在这里插入图片描述
tb代码:

module tb_fifo();

reg	clk;
reg rstn;
reg	wr_en;
reg rd_en;
reg [7:0]wr_data;

wire	[7:0]rd_data;
wire 	fifo_full,fifo_empty;

initial begin
	clk=1;
	rstn=0;
	wr_en=0;
	rd_en=0;
	wr_data='b0;
	#100
	rstn = 1;
	@(posedge clk);
	wr();
	@(posedge clk);
	rd_en = 1;
	#100
	rd_en = 0;
	
end

integer i;
task wr();
	begin
		for(i = 0; i < 66 ;i = i + 1)begin
			wr_en <= 1;
			wr_data <= i;
			@(posedge clk);
		end
		wr_en <= 0;
	end
endtask




always #10 clk =~clk;


syn_fifo #(
	.width(8),
	.depth(64))
syn_fifo_u(
	.clk                (clk),        
	.rstn               (rstn),        
	.wr_en              (wr_en),        
	.rd_en              (rd_en),        
							
	.wr_data            (wr_data),
	.rd_data            (rd_data),
	.fifo_full          (fifo_full),        
    .fifo_empty	        (fifo_empty)     
);

endmodule

3.异步FIFO原理

首先奉上大牛文章:http://www.sunburst-design.com/papers/CummingsSNUG2002SJ_FIFO1.pdf 原理和方法都讲得很清楚。
异步FIFO整体架构
在这里插入图片描述

观察FIFO结构,主要由以下几部分组成:
1.双口RAM
2.读地址(指针)控制
3.写地址(指针)控制
4.跨时钟域处理
异步FIFO中主要技术
1.读空与写满
先做个比喻,异步FIFO的读写你可以想象成两个人在操场上跑,写在前面跑,读在后面追。因为操场是圆的,所以有两种情况发生:1.读追上了写,两个人跑了一样的距离,表示读空;2. 写追上了读,写多跑了一圈,表示写满。
读空很好判断,就是两个地址一样就表示读空,可是写满呢?很简单,就是地址多加一位,用最高位表示是否多跑了一圈。如下图:
在这里插入图片描述

写满标志就是读写地址最高位不同,其余位相同
2.地址指针使用格雷码
地址指针使用格雷码的原因主要是因为要进行跨时钟域的处理。因为读写指针是不同的时钟域,跨时钟域传递会出现亚稳态现象,但是因为相邻的格雷码只改变一位,可以降低亚稳态的风险。

3.Verilog实现

这个代码是我在牛客刷题碰到的,写的很不错,推荐!

`timescale 1ns/1ns

/***************************************RAM*****************************************/
module dual_port_RAM #(parameter DEPTH = 16,
					   parameter WIDTH = 8)(
	 input wclk
	,input wenc
	,input [$clog2(DEPTH)-1:0] waddr  //深度对2取对数,得到地址的位宽。
	,input [WIDTH-1:0] wdata      	//数据写入
	,input rclk
	,input renc
	,input [$clog2(DEPTH)-1:0] raddr  //深度对2取对数,得到地址的位宽。
	,output reg [WIDTH-1:0] rdata 		//数据输出
);

reg [WIDTH-1:0] RAM_MEM [0:DEPTH-1];

always @(posedge wclk) begin
	if(wenc)
		RAM_MEM[waddr] <= wdata;
end 

always @(posedge rclk) begin
	if(renc)
		rdata <= RAM_MEM[raddr];
end 

endmodule  

/***************************************AFIFO*****************************************/
module asyn_fifo#(
	parameter	WIDTH = 8,
	parameter 	DEPTH = 16
)(
	input 					wclk	, 
	input 					rclk	,   
	input 					wrstn	,
	input					rrstn	,
	input 					winc	,
	input 			 		rinc	,
	input 		[WIDTH-1:0]	wdata	,

	output wire				wfull	,
	output wire				rempty	,
	output wire [WIDTH-1:0]	rdata
);

parameter ADDR_WIDTH = $clog2(DEPTH);

/**********************addr bin gen*************************/

reg 	[ADDR_WIDTH:0]	waddr_bin;
reg 	[ADDR_WIDTH:0]	raddr_bin;

always @(posedge wclk or negedge wrstn) begin			//写判断
	if(~wrstn) begin
		waddr_bin <= 'd0;
	end 
	else if(!wfull && winc)begin
		waddr_bin <= waddr_bin + 1'd1;
	end
end
always @(posedge rclk or negedge rrstn) begin			//读判断
	if(~rrstn) begin
		raddr_bin <= 'd0;
	end 
	else if(!rempty && rinc)begin
		raddr_bin <= raddr_bin + 1'd1;
	end
end






/**********************addr gray gen*************************/
wire 	[ADDR_WIDTH:0]	waddr_gray;
wire 	[ADDR_WIDTH:0]	raddr_gray;
reg 	[ADDR_WIDTH:0]	wptr;
reg 	[ADDR_WIDTH:0]	rptr;
assign waddr_gray = waddr_bin ^ (waddr_bin>>1);			//转格雷码
assign raddr_gray = raddr_bin ^ (raddr_bin>>1);
always @(posedge wclk or negedge wrstn) begin 
	if(~wrstn) begin
		wptr <= 'd0;
	end 
	else begin
		wptr <= waddr_gray;
	end
end
always @(posedge rclk or negedge rrstn) begin 
	if(~rrstn) begin
		rptr <= 'd0;
	end 
	else begin
		rptr <= raddr_gray;
	end
end
/**********************syn addr gray*************************/
reg		[ADDR_WIDTH:0]	wptr_buff;
reg		[ADDR_WIDTH:0]	wptr_syn;
reg		[ADDR_WIDTH:0]	rptr_buff;
reg		[ADDR_WIDTH:0]	rptr_syn;
always @(posedge wclk or negedge wrstn) begin 			//打拍做跨时钟域处理
	if(~wrstn) begin
		rptr_buff <= 'd0;
		rptr_syn <= 'd0;
	end 
	else begin
		rptr_buff <= rptr;
		rptr_syn <= rptr_buff;
	end
end
always @(posedge rclk or negedge rrstn) begin 
	if(~rrstn) begin
		wptr_buff <= 'd0;
		wptr_syn <= 'd0;
	end 
	else begin
		wptr_buff <= wptr;
		wptr_syn <= wptr_buff;
	end
end
/**********************full empty gen*************************/
assign wfull = (wptr == {~rptr_syn[ADDR_WIDTH:ADDR_WIDTH-1],rptr_syn[ADDR_WIDTH-2:0]});		//写满标志判断
assign rempty = (rptr == wptr_syn);															//读空标志判断
	
/**********************RAM*************************/
wire 	wen	;
wire	ren	;
wire 	wren;//high write
wire [ADDR_WIDTH-1:0]	waddr;
wire [ADDR_WIDTH-1:0]	raddr;
assign wen = winc & !wfull;
assign ren = rinc & !rempty;
assign waddr = waddr_bin[ADDR_WIDTH-1:0];
assign raddr = raddr_bin[ADDR_WIDTH-1:0];

dual_port_RAM #(.DEPTH(DEPTH),
				.WIDTH(WIDTH)
)dual_port_RAM(
	.wclk (wclk),  
	.wenc (wen),  
	.waddr(waddr),  //深度对2取对数,得到地址的位宽。
	.wdata(wdata),       	//数据写入
	.rclk (rclk), 
	.renc (ren), 
	.raddr(raddr),   //深度对2取对数,得到地址的位宽。
	.rdata(rdata)  		//数据输出
);

endmodule
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值