题解 | #同步FIFO#

同步FIFO

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

答案解析

最终提交的代码如下:

/**********************************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 				    wfull	,
	output 				    rempty	,
	output wire [WIDTH-1:0]	rdata
);
 
localparam DP_WD = $clog2(DEPTH);
 
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
 
reg  [DP_WD   :0]raddr;
wire             renc;
wire             raddr_d_h;
wire [DP_WD -1:0]raddr_d_l;
assign renc = rinc & (!rempty);
assign raddr_d_h = (raddr[DP_WD-1:0] == DEPTH-1) ? ~raddr[DP_WD] : raddr[DP_WD];
assign raddr_d_l = (raddr[DP_WD-1:0] == DEPTH-1) ? 0 : raddr[DP_WD-1:0] + 1;
always @(posedge clk or negedge rst_n)begin
	if(~rst_n)    raddr <= 0;
	else if(renc) raddr <= {raddr_d_h, raddr_d_l};
end
 
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]);
 
assign rempty = (fifo_cnt == 0);
assign wfull = (fifo_cnt == DEPTH);
 
 
dual_port_RAM #(.DEPTH(DEPTH), .WIDTH(WIDTH))
u_ram (
	.wclk	(clk),
	.wenc	(wenc),
	.waddr	(waddr[$clog2(DEPTH)-1:0]),
	.wdata	(wdata),
	.rclk	(clk),
	.renc	(renc),
	.raddr	(raddr[$clog2(DEPTH)-1:0]),
	.rdata	(rdata)
);
 
endmodule

关于读写地址以及fifo_cnt的产生,在FIFO里经典的做法读写地址均做循环累加:地址指针waddr和raddr均比实际地址多一位,最高位用来指示套圈情况。当waddr和raddr的最高位相同时,fifo_cnt = waddr-raddr;当waddr和raddr的最高位相反时,fifo_cnt = DEPTH + waddr[ADDR_WIDTH-1:0]   - raddr[ADDR_WIDTH-1:0]。这种用最高位表示套圈的思路是没问题的,但是参考答案里的做法是有问题的,他的做法是直接定义waddr[ADDR_WIDTH:0]然后从0开始加,加到高位自动翻转:

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

这样的做法只适用于深度为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]);

当高位值一致时就waddr-raddr,当高位值不一样时就waddr+DEPTH-raddr,得到了目前的fifo内数据量,注意,此时计算的fifo_cnt在时序上处于winc/rinc的下一拍,也就是数据真正写入/读出的那一拍。 下一步空满信号,空满信号的产生原理也很简单,fifo_cnt==0时为空,fifo_cnt==DEPTH时为满。但是网站上给出的参考***括对照波形,wfull/rempty信号的输出都是在fifo_cnt的下一拍,即winc/rinc的延后两拍,这是有问题的,下面这段代码是按照对比波形写出来的,也就是对比pass的行为:

wire rempty_d = (fifo_cnt == 0);
always @(posedge clk or negedge rst_n)begin
    if(~rst_n)    rempty <= 0;
    else          rempty <= rempty_d;
end
wire wfull_d = (fifo_cnt == DEPTH);
always @(posedge clk or negedge rst_n)begin
    if(~rst_n)    wfull <= 0;
    else          wfull <= wfull_d;
end

而后ram的wenc/renc的产生与wfull/rempty相关:

assign wenc = winc & (!wfull);
assign renc = rinc & (!rempty);

wenc/renc的产生思路没有什么,当满时不再写入ram空时不再读取ram这个行为是合理的,当然fifo内部不看wfull/rempty也没有什么问题,毕竟fifo把wfull/rempty给到外面就是为了让控制器做处理的:wfull置起后,winc不能为高,否则写行为不可控可能数据覆盖;rempty置起后,rinc不能为高,否则读行为不可控读数据不准。

那么话题回到wfull/rempty,根据上面的分析,wfull/rempty这两个信号的反馈必须在winc/rinc的下一拍得到,否则控制器无法及时的调整winc/rinc逻辑。举个例子,当前深度16的FIFO内已有15个数,cyc0 winc起请求写下一个数,cyc1 数据写入,cys2 wfull信号起。那么在cyc1 winc仍然可以置起写数(因为没看到wfull),哪怕这个时候在fifo内做了ram保护 wenc = winc & (!wfull) 也没有用,因此cyc1在内部也没有看到wfull信号。

用上面那个通过了网站测评的代码做以下测试,向深度16的fifo里写了18个数,然后读数,可以看到第一个数据已经被覆盖:

同时后续的wfull/rempty信号也乱了,因为fifo_cnt连带着都跳乱了。因此wfull/rempty的产生必须在winc/rinc的下一拍:

wire rempty = (fifo_cnt == 0);
wire wfull  = (fifo_cnt == DEPTH);

当然了,这样也会导致fifo的winc/rinc时序变差(要看wfull/rempty),因此可以考虑winc/rinc当拍产生wfull_d/rempty_d,然后打拍得到wfull/rempty,代价是wfull_d/rempty_d的产生逻辑比较深,而且做起来稍微复杂了一点点(其实不复杂,就是要做一些选择信号啥的,我懒得做了)看取舍吧。我就用上面那种了:

这才是一个合理的fifo波形。

最后附一下testbench,也欢迎大家使用auto_testbench脚本(https://blog.csdn.net/moon9999/article/details/126912972):

`define DELAY(N, clk) begin \
	repeat(N) @(posedge clk);\
	#1ps;\
end

module testbench();

//-------------------------------------{{{common cfg
timeunit 1ns;
timeprecision 1ps;
initial $timeformat(-9,3,"ns",6);

string tc_name;
int tc_seed;

initial begin
    if(!$value$plusargs("tc_name=%s", tc_name)) $error("no tc_name!");
    else $display("tc name = %0s", tc_name);
    if(!$value$plusargs("ntb_random_seed=%0d", tc_seed)) $error("no tc_seed");
    else $display("tc seed = %0d", tc_seed);
end
//-------------------------------------}}}

//-------------------------------------{{{parameter declare
parameter WIDTH = 8;
parameter DEPTH = 16;
//-------------------------------------}}}

//-------------------------------------{{{signal declare
logic  clk;
logic  rst_n;
logic  winc;
logic  rinc;
logic [WIDTH-1:0] wdata;
logic  wfull;
logic  rempty;
logic [WIDTH-1:0] rdata;
//-------------------------------------}}}

//-------------------------------------{{{clk/rst cfg
initial forever #5ns clk = ~clk;
initial begin
    rst_n = 1'b0;
	`DELAY(30, clk);
	rst_n = 1'b1;
end
initial begin
    #100000ns $finish;
end
//-------------------------------------}}}

//-------------------------------------{{{valid sig assign
//-------------------------------------}}}

//-------------------------------------{{{ready sig assign
//-------------------------------------}}}

//-------------------------------------{{{data  sig assign
always @(posedge clk or negedge rst_n)begin
    if(!rst_n) wdata <= 0;
    else wdata <= wdata + 1;
end
//-------------------------------------}}}

//-------------------------------------{{{other sig assign
initial begin
    winc = 0;
    rinc = 0;
    `DELAY(50, clk);
    winc = 1;
    `DELAY(12, clk);
    rinc = 1;
    `DELAY(6, clk);
    winc = 0;
    `DELAY(3, clk);
    rinc = 0;
    `DELAY(2, clk);
    winc = 1;
    `DELAY(8, clk);
    winc = 0;
    `DELAY(8, clk);
    rinc = 1;

end

//-------------------------------------}}}

//-------------------------------------{{{rtl inst
sfifo #(
    .WIDTH(WIDTH),
    .DEPTH(DEPTH)) 
u_sfifo(
    .clk(clk),
    .rst_n(rst_n),
    .winc(winc),
    .rinc(rinc),
    .wdata(wdata),
    .wfull(wfull),
    .rempty(rempty),
    .rdata(rdata)
);
//-------------------------------------}}}

endmodule

全部评论
看完茅塞顿开啊!我是初学者,我看原答案一直不明白地址多一位,怎么就做到循环了,明明全加满了之后就没办法再加了。还是您这种把最高位和低位分开的写法比较科学!
2
送花
回复
分享
发布于 2022-03-30 17:07
写的好,我就一直纳闷按照pass了的代码来分析,full信号其实是晚来了一拍的,明显不合理。
2
送花
回复
分享
发布于 2022-09-16 00:05 四川
滴滴
校招火热招聘中
官网直投
"当然了,这样也会导致fifo的winc/rinc时序变差(要看wfull/rempty)"这里的winc/ rinc时序变差是为什么😢,是因为加了个组合逻辑所以变差了吗
1
送花
回复
分享
发布于 2023-02-13 16:30 江西
写的很好哦
1
送花
回复
分享
发布于 2023-05-19 14:56 四川
而且我不知道你什么时候把题解编辑了一下,题目要求满空是reg类型的,你现在改成了wire类型的。如果改成用组合逻辑直接判断的这样,输出的波形看上去就是对的了
1
送花
回复
分享
发布于 2023-06-23 21:10 陕西
大佬能不能出个详细版本的?
点赞
送花
回复
分享
发布于 2023-05-07 18:29 广东
真心看不懂
点赞
送花
回复
分享
发布于 2023-05-09 11:49 广东
这个代码换我的testbench压根不能正常运行。。
点赞
送花
回复
分享
发布于 2023-06-09 20:51 陕西
这是直接复制的你的代码。一次写入18个数,写入12个数之后读9个数,此时有9个数留在FIFO里,再写8个数,会在最后一个数的地方同时拉高full。但是无论是full的行为还是读出数据的行为都显然不对,牛客的这个测试用例可以
点赞
送花
回复
分享
发布于 2023-06-12 14:44 陕西
@devinaaaa 我参考你的波形写了下testbench,用vcs进行了仿真,我这边的波形wfull起的时间和读数都是没有问题的,参考下面的波形。所以我猜测,可能是modelsim的行为与vcs有差别,或者是你仿真的winc rinc不是在testbench中使用<=赋值或通过#1ps的方式在沿后赋值的?
点赞
送花
回复
分享
发布于 2023-06-23 20:26 天津
谢谢,你后来修改的那个我试了下怎么测都是正常的了,除了输出不是reg,不过貌似也没必要是
点赞
送花
回复
分享
发布于 2023-06-23 22:05 陕西
这个代码没有考虑读写信号的时候考虑是否读写使能吧?
点赞
送花
回复
分享
发布于 04-23 11:24 广东

相关推荐

42 12 评论
分享
牛客网
牛客企业服务