题意整理
本题要求实现同步FIFO,FIFO的位宽和深度可配置。
题解主体
设计FIFO的时候一般需要考虑的有两点:
1. FIFO的大小 FIFO的大小指就是双端口ram的大小,这个可以根据设计需要来设置。
2. FIFO空满状态的判断 FIFO空满状态的判断通常有两种方法。
a、FIFO中的ram一般是双端口ram,所以有独立的读写地址。因此可以一种是设置读,写指针,写指针指向下一个要写入数据的地址,读指针指向下一个要读的地址,最后通过比较读指针和写指针的大小来确定空满状态。
b、设置一个计数器,当写使能有效的时候计数器加一;当读使能有效的时候,计数器减一,将计数器与ram的size进行比较来判断fifo的空满状态。这种方法设计比较简单,但是需要的额外的计数器,就会产生额外的资源,而且当fifo比较大时,会降低fifo最终可以达到的速度。
本题解采用第二种方式。实现方式如下:
用一个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。
Verilog代码描述如下:
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
参考答案
`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