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