同步FIFO + 异步FIFO 【设计详解及代码分享】

FIFO表示先入先出,是一种存储结构。可满足一下需求:

1、当输入数据速率和输出速率不匹配时。可作为临时存储单元。

2、用于不同时钟域之间的同步。

3、输入数据路径和输出数据路径之间的数据宽度不匹配时,可用于数据宽度调整电路。

同步FIFO + 异步FIFO 【设计详解及代码分享】_第1张图片


同步FIFO

同步FIFO主要是空满信号的产生,

一般情况下写使能并且非满的情况下,写地址加1;

    always @(posedge clk or negedge rst_n) begin
        if(~rst_n) begin
            waddr <= 'b0;
        end else begin
            if(winc & ~wfull)
                waddr <= waddr + 1'b1;
        end
    end

读使能并且非空的情况下,读地址加1。

    always @(posedge clk or negedge rst_n) begin
        if(~rst_n) begin
            raddr <= 'b0;
        end else begin
            if(rinc & ~rempty)
                raddr <= raddr + 1'b1;
        end
    end 

因此空和满的信号就要根据读写地址的比较来产生。

当我们只写不读的时候,FIFO总会被填满,因此就产生满信号。当我们一直读的时候,FIFO的数据总会被读完,因此就产生空信号。

通常我们会把写地址和读地址的位宽设置比FIFO深度多1bit,这一bit就是用来判断产生空满的。(假设我们定义FIFO可以存8个数据,那么要设置  :

    reg [3:0] waddr;
    reg [3:0] raddr;

当waddr[2:0]==raddr[2:0] && waddr[3]==raddr[3](也可以直接写成waddr==raddr)时,说明FIFO里没有能读出的数据,因此产生空信号:

assign empty = (waddr==raddr)? 1:0;

当waddr[2:0]==raddr[2:0] && waddr[3]!=raddr[3]时,说明写地址正好比读地址大一个FIFO的存储深度,因此FIFO已无空位置可写,产生满信号:

assign wfull = (waddr=={~raddr[3],raddr[2:0]})? 1:0;

同步FIFO一般注意这两点就行。


异步FIFO 

异步FIFO原理和同步FIFO一样,区别在于由于写时钟和读时钟的异步所带来的多bit数据同步问题。【这也是为什么要用格雷码,格雷码只有1bit数据变化,降低亚稳态概率】。

异步FIFO的读地址和写地址加1的操作和同步一样,只是注意always里时钟的区别:

always @(posedge wclk or negedge wrstn) begin
    if(~wrstn) begin
        waddr <= 'b0;
    end else begin
        if(winc && !wfull)
            waddr <= waddr + 1'b1;
    end
end
    
always @(posedge rclk or negedge rrstn) begin
    if(~rrstn) begin
        raddr <= 'b0;
    end else begin
        if(rinc && !rempty)
            raddr <= raddr + 1'b1;
    end
end  

在把 写地址 同步到 读时钟域 之前,需要将 写地址 转换成格雷码,减小亚稳态概率;同理,在把 读地址 同步到 写时钟域 之前,需要将 读地址 转换成格雷码,减小亚稳态概率。

assign raddr_gray = raddr ^ (raddr >> 1);
assign waddr_gray = waddr ^ (waddr >> 1);

转换完成后,剩下的就是同步问题,只需要打2拍即可。【用写时钟去采样读地址,用读时钟采样写地址。这些地址都是格雷码

    // 写时钟域,采样读地址
    always @(posedge wclk or negedge wrstn) begin
        if(~wrstn) begin
            raddr_wclk <= 'b0;
            raddr_wclk_1d <= 'b0;
        end else begin
            {raddr_wclk, raddr_wclk_1d} <= {raddr_wclk_1d, raddr_gray};
        end
    end 
    
    // 读时钟域,采样写地址
    always @(posedge rclk or negedge rrstn) begin
        if(~rrstn) begin
            waddr_rclk <= 'b0;
            waddr_rclk_1d <= 'b0;
        end else begin
            {waddr_rclk, waddr_rclk_1d} <= {waddr_rclk_1d, waddr_gray};
        end
    end 

至此:我们在写时钟域有了自己的原生的写地址的格雷码(waddr_gray )和同步过来的读地址(raddr_wclk);在读时钟域有了自己的原生的读地址的格雷码(raddr_gray )和同步过来的写地址(waddr_rclk)。

我们可以用其来判断满信号和空信号。由于格雷码和二进制码不同,因此比较方式稍有改变【最高位和次高位取反】:

assign rempty = (raddr_gray_r==waddr_rclk)? 1:0;
assign wfull  = (waddr_gray_r=={~raddr_wclk[3:2],raddr_wclk[1:0]})? 1:0;

异步FIFO一般多注意这两点【格雷码和打2拍同步】就行。



另外不知道大家是否还有一个疑问,就是为什么要在读时钟域产生空信号,在写时钟域产生满信号,反过来行不行??

我们想一下:读时钟域所同步的写地址是延时了两个读时钟周期的,也就是说当读时钟得到了用于判断空信号的写地址时,实际上的写地址 是要比 读时钟所同步的写地址 大。因此当判断FIFO为空时,实际上FIFO里可能还有数据,这个数据是在读时钟用打2拍来同步写地址时,写时钟域写进去的数据。同样的道理:写时钟域所同步的读地址是延时了两个写时钟周期的,也就是说当写时钟得到了用于判断满信号的读地址时,实际上的读地址 是要比 写时钟所同步的读地址 大。因此当判断FIFO为满时,实际上FIFO里可能还有空位,这个空位是在写时钟用打2拍来同步读地址时,读时钟域又读出了几个数据所造成的空位。

但是这个并不会造成异步FIFO的功能问题,大不了多写两个数据或者多读两个数据就行。但是如果空满信号在错误的时钟域产生,就会造成异步FIFO功能错误。

你可能感兴趣的:(Verilog,fpga开发,经验分享,硬件工程)