题解 | #异步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

全部评论

相关推荐

01-17 10:48
门头沟学院 Java
xxxxOxo:这公司幽默得很,要了简历半天一点动静都没有,过一会就给你发个邮件让你做测试,做完又没后文了,纯溜人
点赞 评论 收藏
分享
02-14 15:34
门头沟学院 Java
Java抽象带篮子:专业技能怎么写可以看看我发的帖子
点赞 评论 收藏
分享
评论
3
收藏
分享

创作者周榜

更多
牛客网
牛客企业服务