前言
继续写写代码保持手感,这次是同步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