题解 | #异步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 [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 /***************************************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 ); //写时钟域信号定义 reg [$clog2(DEPTH):0] w_addr_bin; //这里的信号定义的位宽都是5位,也就是说比正常的信号要多一位,这个主要是用来进行空满的判别以及循环 reg [$clog2(DEPTH):0] w_addr_gray; //在0到15之间,最高位都是0,而后面的在变,在16到31之间,最高位都是1,后面的在变,但是变的和之前的前十五个一样 reg [$clog2(DEPTH):0] wtr_addr_gray_ff1; //在二进制码中,当后四位相同,最高位不同时,两者之间相差刚好就是一个循环也就是存储深度, reg [$clog2(DEPTH):0] wtr_addr_gray_ff2; //在格雷码中,当后三位相同最高位和次高位皆不同时,两者在二进制码中差一个循环,但是换到格雷码就是DEPTH + (DEPTH >> 1)个位置,整个运算就是二进制的10000(差值)需要换算到格雷码 wire wenc; //写工作使能,在输入的写指令下和写时钟下的未满情况下拉高 //读时钟域信号定义 reg [$clog2(DEPTH):0] r_addr_bin; reg [$clog2(DEPTH):0] r_addr_gray; reg [$clog2(DEPTH):0] rtw_addr_gray_ff1; reg [$clog2(DEPTH):0] rtw_addr_gray_ff2; wire renc; //读工作使能,在输入的读指令和读时钟下的未空情况下拉高 //双端口RAM读写使能信号 assign wenc = winc & !wfull; assign renc = rinc & !rempty; //*************************写时钟域*************************// //二进制写地址递增 always@(posedge wclk or negedge wrstn) begin if(!wrstn) w_addr_bin <= 'b0; else if(wenc) w_addr_bin <= w_addr_bin + 1'b1; else w_addr_bin <= w_addr_bin; end //二进制地址转格雷码地址 always@(posedge wclk or negedge wrstn) begin if(!wrstn) w_addr_gray <= 'b0; else //这里若是用组合逻辑,下面的同步就要打三拍,但是这里是时序逻辑就可以下面打两拍,因为这里已经是一拍了 //w_addr_gray <= {w_addr_bin[$clog2(DEPTH)] , w_addr_bin[$clog2(DEPTH):1] ^ w_addr_bin[$clog2(DEPTH) - 1:1]}; w_addr_gray <= (w_addr_bin >> 1) ^ w_addr_bin; //两种方法都可以 end //读格雷码地址同步到写时钟域(这里只需打两拍操作,原因是上面的格雷码的生成时已经打了一拍了,另外打三拍的原因是两拍要同步到写时钟域,一拍是为了消除竞争冒险,主要是怕组合逻辑的中间态被采集到) always@(posedge wclk or negedge wrstn) begin if(!wrstn) begin rtw_addr_gray_ff1 <= 'b0; rtw_addr_gray_ff2 <= 'b0; end else begin rtw_addr_gray_ff1 <= r_addr_gray; rtw_addr_gray_ff2 <= rtw_addr_gray_ff1; end end //*************************读时钟域*************************// //二进制读地址递增 always@(posedge rclk or negedge rrstn) begin if(!rrstn) r_addr_bin <= 'b0; else if(renc) r_addr_bin <= r_addr_bin + 1'b1; else r_addr_bin <= r_addr_bin; end //二进制地址转格雷码地址 always@(posedge rclk or negedge rrstn) begin if(!rrstn) r_addr_gray <= 'b0; else r_addr_gray <= (r_addr_bin >> 1) ^ r_addr_bin; end //写格雷码地址同步到读时钟域 always@(posedge rclk or negedge rrstn) begin if(!rrstn) begin wtr_addr_gray_ff1 <= 'b0; wtr_addr_gray_ff2 <= 'b0; end else begin wtr_addr_gray_ff1 <= w_addr_gray; wtr_addr_gray_ff2 <= wtr_addr_gray_ff1; end end //****************************空满标志产生*****************************// assign wfull = w_addr_gray == (rtw_addr_gray_ff2 + DEPTH + (DEPTH >> 1)) ? 1'b1 : 1'b0; //这里所用的判断是格雷码判断,也可以用二进制码判断,但是需要把格雷码转换为二进制码 assign rempty = (r_addr_gray == wtr_addr_gray_ff2) ? 1'b1 : 1'b0; //此处的满标志的判断就是在写时钟域下,写的地址要比读的地址整整大一圈,就是刚好一个循环,若是我们之前设定的地址都是4位的,那么此处就无法判断 //但是我们是5位,最高位可以作为循环位,所以在判断条件可以为,在二进制码中,最高位写地址和读打两拍到写的地址不一致,但是低四位都一致,这样的就是刚好套了一圈,两者的差值为DEPTH,这就是写满 //但是在格雷码中,两者就是二进制的差值为DEPTH,换算过来格雷码就是DEPTH + (DEPTH >> 1),这样也就是格雷码的套圈 //同样此处的空标志就是在读时钟域下,读的地址要和写打两拍到读的地址相同,是完全相同,之前所想的套圈的问题在最高位完美解决,最高位判断套圈 dual_port_RAM #( .DEPTH (DEPTH), .WIDTH (WIDTH) ) u_dual_port_RAM ( .wclk (wclk), .wenc (wenc), .waddr (w_addr_bin[$clog2(DEPTH) - 1 : 0]), //深度对2取对数,得到地址的位宽。 .wdata (wdata), //数据写入 .rclk (rclk), .renc (renc), .raddr (r_addr_bin[$clog2(DEPTH) - 1 : 0]), //深度对2取对数,得到地址的位宽。 .rdata (rdata) //数据输出 ); endmodule