题解 | #异步FIFO#
异步FIFO
https://www.nowcoder.com/practice/40246577a1a04c08b3b7f529f9a268cf
`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 [DEPTH-1:0]; always @(posedge wclk) begin if(wenc) RAM_MEM[waddr] <= wdata; end always @(posedge rclk) begin if(renc) rdata <= RAM_MEM[raddr]; end endmodule /**************************************AFIFO*************************************/ module asyn_fifo#( parameter WIDTH = 8, parameter DEPTH = 16 )( input wclk , input rclk , input wrstn , input rrstn , input winc , input rinc , input [WIDTH-1:0] wdata , output wire wfull , output wire rempty , output wire [WIDTH-1:0] rdata ); // 异步FIFO的与同步FIFO的核心区别是它的读时钟和写时钟是不同步的。所以用对比读写地址的方法产生空满信号时,要进行跨时钟域处理。二进制的计数值跨时钟域出现亚稳态,二进制计数时所有位都可能同时变化。 // 为了降低亚稳态可能性,异步FIFO还引入了格雷码。同时,格雷码也更方便产生空满信号 // 在同步FIFO基础上,进行异步FIFO设计。保持类似的读写逻辑和读写地址控制,区别是增加了格雷码跨时钟,另外空满逻辑是使用格雷码判断空满。 parameter DEPTH_WK = $clog2(DEPTH); //深度对2取对数,得到地址的位宽 wire wenc, renc; assign wenc=winc&&~wfull; // 写使能 assign renc=rinc&&~rempty; // 读使能 //******************二进制******************// reg [DEPTH_WK:0] waddr_bin, raddr_bin; //写地址(waddr_bin)变化 always@(posedge wclk or negedge wrstn)begin if (!wrstn) waddr_bin<='d0; else if( wenc) waddr_bin<=waddr_bin+1'b1; end //读地址(raddr_bin)变化 always@(posedge rclk or negedge rrstn)begin if (!rrstn) raddr_bin<='d0; else if(renc) raddr_bin<=raddr_bin+1'b1; end //************ 格雷码*************// wire [DEPTH_WK:0] waddr_gray , raddr_gray; //格雷码 //二进制转格雷码 (组合逻辑) assign waddr_gray = waddr_bin ^ (waddr_bin>>1); assign raddr_gray = raddr_bin ^ (raddr_bin>>1); // ***************格雷码打拍*************// //在原时钟下进行一拍寄存,对于输入进行寄存是很常见的方法,主要是去除毛刺等,做一个本时钟域的同步 reg [DEPTH_WK:0] waddr_gray_reg ,raddr_gray_reg; always @ (posedge wclk or negedge wrstn) begin if(!wrstn) waddr_gray_reg <= 'd0; else waddr_gray_reg <= waddr_gray; end always @ (posedge rclk or negedge rrstn) begin if(!rrstn) raddr_gray_reg <= 'd0; else raddr_gray_reg <= raddr_gray; end //打两拍 读时钟和写时钟是不同步的。用对比读写地址的方法产生空满信号时,要进行跨时钟域处理。 reg [DEPTH_WK:0] waddr_gray_reg1 , waddr_gray_reg2; reg [DEPTH_WK:0] raddr_gray_reg1 , raddr_gray_reg2; always @ (posedge wclk or negedge wrstn) begin if(!wrstn) begin raddr_gray_reg1 <= 'd0; raddr_gray_reg2 <= 'd0; end else begin raddr_gray_reg1 <= raddr_gray_reg; raddr_gray_reg2 <= raddr_gray_reg1; end end always @ (posedge rclk or negedge rrstn) begin if(!rrstn) begin waddr_gray_reg1 <= 'd0; waddr_gray_reg2 <= 'd0; end else begin waddr_gray_reg1 <= waddr_gray_reg; waddr_gray_reg2 <= waddr_gray_reg1; end end // *************空满信号发生器*************// // 判断空rempty、满信号wfull assign wfull = (waddr_gray_reg == {~raddr_gray_reg2[DEPTH_WK:DEPTH_WK-1], raddr_gray_reg2[DEPTH_WK-2:0]}); assign rempty = (raddr_gray_reg == waddr_gray_reg2); wire[DEPTH_WK-1:0] waddr, raddr; assign waddr= waddr_bin[DEPTH_WK-1:0] ; assign raddr= raddr_bin[DEPTH_WK-1:0] ; dual_port_RAM #( .DEPTH(DEPTH), .WIDTH(WIDTH) ) myRAM( .wclk (wclk ), .wenc (wenc ), .waddr(waddr_bin), .wdata(wdata), .rclk (rclk ), .renc (renc ), .raddr(raddr_bin), .rdata(rdata) ); endmodule