题解 | #同步FIFO#

同步FIFO

https://www.nowcoder.com/practice/3ece2bed6f044ceebd172a7bf5cfb416

参考:

https://blog.nowcoder.net/n/58228b413e6d43689a50050d94886684?f=comment

同步FIFO,FIFO的位宽和深度可配置

观察题解发现主要是两种解法。

法一:

a、FIFO中的ram一般是双端口ram,所以有独立的读写地址。因此可以一种是设置读,写指针,写指针指向下一个要写入数据的地址,读指针指向下一个要读的地址,最后通过比较读指针和写指针的大小来确定空满状态。 

b、设置一个计数器,当写使能有效的时候计数器加一;当读使能有效的时候,计数器减一,将计数器与ram的size进行比较来判断fifo的空满状态。这种方法设计比较简单,但是需要的额外的计数器,就会产生额外的资源,而且当fifo比较大时,会降低fifo最终可以达到的速度。

`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  

/**********************************SFIFO************************************/
module sfifo#(
	parameter	WIDTH = 8,
	parameter 	DEPTH = 16
)(
	input 					clk		, 
	input 					rst_n	,
	input 					winc	,
	input 			 		rinc	,
	input 		[WIDTH-1:0]	wdata	,

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

parameter ADDR_WIDTH = $clog2(DEPTH);

/**********************addr bin gen*************************/
reg 	[ADDR_WIDTH:0]	waddr;
reg 	[ADDR_WIDTH:0]	raddr;

always @(posedge clk or negedge rst_n) begin
	if(~rst_n) begin
		waddr <= 'd0;
	end 
	else if(!wfull && winc)begin
		waddr <= waddr + 1'd1;
	end
end
always @(posedge clk or negedge rst_n) begin
	if(~rst_n) begin
		raddr <= 'd0;
	end 
	else if(!rempty && rinc)begin
		raddr <= raddr + 1'd1;
	end
end

/**********************full empty gen*************************/
wire		[ADDR_WIDTH : 0]	fifo_cnt;

assign fifo_cnt = (waddr[ADDR_WIDTH] == raddr[ADDR_WIDTH]) ? (waddr[ADDR_WIDTH:0] - raddr[ADDR_WIDTH:0]) :
                  (DEPTH + waddr[ADDR_WIDTH-1:0] - raddr[ADDR_WIDTH-1:0]);
always @(posedge clk or negedge rst_n) begin
	if(~rst_n) begin
		wfull <= 'd0;
		rempty <= 'd0;
	end 
	else if(fifo_cnt == 'd0)begin
		rempty <= 1'd1;
	end
	else if(fifo_cnt == DEPTH)begin
		wfull <= 1'd1;
	end
	else begin
		wfull <= 'd0;
		rempty <= 'd0;
	end 
end

/**********************RAM*************************/
wire 	wen	;
wire	ren	;
wire 	wren;//high write
assign wen = winc & !wfull;
assign ren = rinc & !rempty;

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

法二:

经典的套圈判断。

用一个fifo_cnt来指示实际写入的数据量。

地址指针waddr和raddr比实际地址多一位,最高位用来指示套圈情况。当waddr和raddr的最高位相同时,fifo_cnt = waddr-raddr;当waddr和raddr的最高位相反时,fifo_cnt = DEPTH  - raddr[ADDR_WIDTH-1:0] + waddr[ADDR_WIDTH-1:0] 。

如何根据fifo_cnt 的值来判断空满呢?对于空,只要fifo_cnt  == 0,即为空,对于满,只要fifo_cnt  == DEPTH,即为满注意,为什么不是fifo_cnt  == DEPTH-1呢?假设FIFO深度设计为16,DEPTH=16,那么第16个写数据是写在了waddr=15的地址中,但是当写完第16个数据后,即使winc拉低,waddr也会自动加1,停在waddr = 10000,所以相当于写完数据后的写地址比最后一位数据的存储地址,多加了1,所以就不需要再DEPTH-1了。fifo_cnt = 10000 - 0000 + 0000 = DEPTH。

`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  

/**********************************SFIFO************************************/
module sfifo#(
	parameter	WIDTH = 8,
	parameter 	DEPTH = 16
)(
	input 					clk		, 
	input 					rst_n	,
	input 					winc	,
	input 			 		rinc	,
	input 		[WIDTH-1:0]	wdata	,

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

parameter ADDR_WIDTH = $clog2(DEPTH);

/**********************addr bin gen*************************/
reg 	[ADDR_WIDTH:0]	waddr;
reg 	[ADDR_WIDTH:0]	raddr;

always @(posedge clk or negedge rst_n) begin
	if(~rst_n) begin
		waddr <= 'd0;
	end 
	else if(!wfull && winc)begin
		waddr <= waddr + 1'd1;
	end
end
always @(posedge clk or negedge rst_n) begin
	if(~rst_n) begin
		raddr <= 'd0;
	end 
	else if(!rempty && rinc)begin
		raddr <= raddr + 1'd1;
	end
end

/**********************full empty gen*************************/
wire		[ADDR_WIDTH : 0]	fifo_cnt;

assign fifo_cnt = (waddr[ADDR_WIDTH] == raddr[ADDR_WIDTH]) ? (waddr[ADDR_WIDTH:0] - raddr[ADDR_WIDTH:0]) :
                  (DEPTH + waddr[ADDR_WIDTH-1:0] - raddr[ADDR_WIDTH-1:0]);
always @(posedge clk or negedge rst_n) begin
	if(~rst_n) begin
		wfull <= 'd0;
		rempty <= 'd0;
	end 
	else if(fifo_cnt == 'd0)begin
		rempty <= 1'd1;
	end
	else if(fifo_cnt == DEPTH)begin
		wfull <= 1'd1;
	end
	else begin
		wfull <= 'd0;
		rempty <= 'd0;
	end 
end

/**********************RAM*************************/
wire 	wen	;
wire	ren	;
wire 	wren;//high write
assign wen = winc & !wfull;
assign ren = rinc & !rempty;

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

法三:

下面这个解法比较全面。就是关于经典套圈方法中的容易被忽略的点,上面两个解法都有下面提的问题。

https://blog.nowcoder.net/n/d5411484475d4c5c84ce711a6d668f7d?f=comment

1、首先第一个就是;这种用最高位表示套圈的思路是没问题的,这样的做法只适用于深度为2^N的fifo,一旦深度非2^N那么addr就乱了。大佬给出了解决办法,就是把最高位作为标志位,单独拎出来。

reg  [DP_WD   :0]waddr;
wire             wenc;
wire             waddr_d_h;
wire [DP_WD -1:0]waddr_d_l;
assign wenc = winc & (!wfull);
assign waddr_d_h = (waddr[DP_WD-1:0] == DEPTH-1) ? ~waddr[DP_WD] : waddr[DP_WD];
assign waddr_d_l = (waddr[DP_WD-1:0] == DEPTH-1) ? 0 : waddr[DP_WD-1:0] + 1;
always @(posedge clk or negedge rst_n)begin
    if(~rst_n)    waddr <= 0;
    else if(wenc) waddr <= {waddr_d_h, waddr_d_l};
end

最高位彻底的作为标志位,当低位计数到DEPTH-1时,高位翻转。如此一来仍旧可以用经典的方式计算fifo_cnt:
wire [DP_WD :0]fifo_cnt = (waddr[DP_WD] == raddr[DP_WD]) ? waddr[DP_WD-1:0] - raddr[DP_WD-1:0]:
                          (waddr[DP_WD-1:0] + DEPTH - raddr[DP_WD-1:0]);

2、第二个问题就是wfull/rempty信号的判断。原解法wfull/rempty信号的输出都是在fifo_cnt的下一拍,即winc/rinc的延后两拍,有问题。

wfull/rempty这两个信号的反馈必须在winc/rinc的下一拍得到,否则控制器无法及时的调整winc/rinc逻辑。大佬给了解决方案:

wfull/rempty的产生必须在winc/rinc的下一拍:wire rempty = (fifo_cnt == 0);wire wfull  = (fifo_cnt == DEPTH);

具体可以看大佬文章的详细简介

全部评论

相关推荐

拒绝无效加班的小师弟很中意你:求职意向没有,年龄、课程冗余信息可以删掉,需要提升项目经历。排版需要修改。
点赞 评论 收藏
分享
评论
3
2
分享
牛客网
牛客企业服务