【芯片前端】保持代码手感——同步FIFO

本文分析了一个同步FIFO的设计,指出其在读写地址处理和空满信号产生的问题,并提供了修正方案。FIFO的读写地址需支持循环累加,确保对任意深度都能正确工作。同时,空满信号的反馈应在写入/读取操作的下一拍给出,以确保控制器能及时响应。修正后的代码已通过网站测评,解决了这些问题。
摘要由CSDN通过智能技术生成

前言

继续写写代码保持手感,这次是同步FIFO的RTL代码,不过这次网站给出的答案和对比波形是有问题的,而且不只一处,先确认下我下面放的这个代码是通过了网站的对比的:

 但是这个RTL完全是根据答案波形去凑得,有挺多问题的,一点点来说。

答案解析

提交的代码如下,分步解析下:

`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
);

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]);

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

dual_port_RAM #(.DEPTH(DEPTH), .WIDTH(WIDTH))
u_ram (
	.wclk	(clk),
	.wenc	(wenc),
	.waddr	(waddr),
	.wdata	(wdata),
	.rclk	(clk),
	.renc	(renc),
	.raddr	(raddr),
	.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波形。

网站提供的参考答案与波形中还有一个问题,rempty如果是作为reg输出,那么他的复位值应为该1而不是0,fifo复位后必然是空的。

最后的代码

/**********************************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]);

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


dual_port_RAM #(.DEPTH(DEPTH), .WIDTH(WIDTH))
u_ram (
	.wclk	(clk),
	.wenc	(wenc),
	.waddr	(waddr),
	.wdata	(wdata),
	.rclk	(clk),
	.renc	(renc),
	.raddr	(raddr),
	.rdata	(rdata)
);

endmodule

  • 7
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
SV小项目-异步FIFO是一个简单的项目,旨在让学生理解FIFO缓存的原理和实现方式。 FIFO缓存是一种常用的数据存储方式,可以用于解决数据传输时的不匹配问题。在异步FIFO中,数据的写入和读取是异步的,意味着数据可以在任何时候写入或读取。这种异步的方式可以增加FIFO的灵活性,并且允许数据的写入和读取在不同的时钟域上应用。 这个小项目的主要目标是实现一个基于Verilog的异步FIFO模块,包括以下功能: 1. 内部缓存的储存和检索 2. 空和满指示器的生成 3. 数据的写入和读取 对于写入操作,当FIFO未满时,它将数据存储在内部缓存中,并更新其指针以指向下一个空位置。当缓存已满时,写入操作将被忽略,并且FIFO满指示器将被设置为高电平。同样,读取操作从内部缓存中检索数据,并将其指针更新以指向下一个位置。当缓存为空时,读操作将被忽略,并且FIFO空指示器将被设置为高电平。 在设计过程中,需要考虑解决的问题包括时序和异步信号的同步。时序问题可以通过FIFO指针和状态机解决,而异步信号可以通过信号同步器进行同步。此外,还需要考虑FIFO的读写顺序和存储器的尺寸,并确保FIFO模块的有效性和可靠性。 总之,通过实现SV异步FIFO项目,学生可以加深对FIFO缓存的理解,并学习基于Verilog的设计和实现。此外,学生还可以通过在项目中遇到的挑战来提高他们的编程和设计技能。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

尼德兰的喵

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

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

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

打赏作者

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

抵扣说明:

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

余额充值