FPGA:异步FIFO

前言

异步FIFO是面试中常提的问题

正文

一、什么是异步FIFO

1.1 什么是FIFO

FIFO可以被看做成一个水池:

  1. 只有写通道打开,表示往水池加水
  2. 只有读通道打开,表示放水
  3. 写速度>读速度,可能会写满
  4. 写速度<读速度,可能会读空

那如何理解先入先出呢?
同样用水池作比喻,我们只考虑理想情况下,先放到水池里的水在池子最下面,那么也先被放出水池

1.2 异步FIFO

同步FIFO:读、写时钟域相同
异步FIFO:读、写时钟域不同,读、写的位宽也可以不同

1.3 异步FIFO的重要概念

FIFO的作用:缓存数据
FIFO的原则:满不写、空不读(否则会造成数据丢失、读取无效数据)
FIFO的关键:设计产生full满信号、empty空信号

二、如何建立FIFO

在这里插入图片描述

在这里插入图片描述

2.1 FIFO的核心:双端口RAM

双端口:输入端口和输出端口独立,因此可以同时进行读、写;在RAM中,使能信号、数据、数据地址这三个数据是对应的,

2.1.1 SDPRAM:Simple Dual Port RAM

首先明确该模块中可以将读、写端的信号分为:

  1. 时钟
  2. 使能信号
  3. 数据
  4. 地址
  5. 读FIFO独有的的复位信号

其次,设定好RAM的宽度和深度
数据宽度:8bit,RAM中存储的数据位宽
地址深度:16bit,RAM中能存多少个位宽为8bit的数据
地址宽度:4bit(2^4=16,使用4位二进制数就能表示16个地址)
写法:16×8
该FIFO能存放:16个8bit的数据

在这里插入图片描述

双端口RAM设计文件

实现RAM的读、写操作

module SDPRAM_ly (
    input                   w_en,
    input                   w_clk,
    input       [7:0]      w_data,
    input       [3:0]      w_addr,

    input                   r_en,
    input                   r_clk,
    input                   r_rst_n,
    input       [3:0]      r_addr,

    output  reg [7:0]      r_data

);

// 首先声明一个内存用来放数据和地址
reg     [7:0]  SAVE    [15:0];

// 往SAVE写数据
always @(posedge w_clk) begin
    if(w_en)
        SAVE[w_addr] <= w_data;
    
end

// 从SAVE里读数据
always @(posedge r_clk or negedge r_rst_n) begin
    if(!r_rst_n)
        r_data <= 16'd0;
    else if(r_en)
        r_data <= SAVE[r_addr];
    else
        r_data <= r_data;
    
end
    
endmodule

2.2 RAM外围模块的核心:设计写满、读空信号

这部分的重点是处理跨时钟域的问题:
单比特:

2.2.1二进制地址自增

在有了双端口RAM后,我们要设计外围的模块去控制RAM正确进行读写操作,下面的核心是如何设计出写满和读空信号


地址自增条件:使能有效+可写/可读

reg [4:0]   w_bin;// 对地址扩充一位
reg [4:0]   r_bin;

// ===========================  写  ===============================

// 写地址扩充一位后,地址自加1
always @(posedge w_clk or negedge w_rst_n) begin
    if(!w_rst_n)
        w_bin <= 5'd0;
    else if(w_en && !w_full) // && 逻辑与,得到一位;&按位与,保持相与元素位宽
        w_bin <= w_bin + 5'd1;
    else   
        w_bin <= w_bin;    
end

// ===========================  读  ===============================
always @(posedge r_clk or negedge r_rst_n) begin
    if(!r_rst_n)
        r_bin <= 5'd0;
    else if(r_en && !r_empty) // && 逻辑与,得到一位;&按位与,保持相与元素位宽
        r_bin <= r_bin + 5'd1;
    else   
        r_bin <= r_bin;    
end

2.2.2 二进制转格雷码:减小跨时钟域下发生亚稳态的概率

这样做的目的:当信号发生变化时,二进制的变化位数远远多于格雷码,因此发生亚稳态的几率就更大,而格雷码只有一位变化
转化方法:
二进制最高位不变,直接复制到格雷码最高位,其余位两两之间异或
在这里插入图片描述
二进制转格雷码本身是组合逻辑,但是为了避免产生竞争冒险,于是将组合逻辑的输出打一拍
如果不打拍,可以参照这里的写法:异步FIFO的Verilg实现方法-孤独的单刀

//地址指针从二进制转换成格雷码
assign 	wr_ptr_g = wr_ptr ^ (wr_ptr >> 1);					
assign 	rd_ptr_g = rd_ptr ^ (rd_ptr >> 1);

打一拍的写法:

// 二进制转格雷码

// ===========================  写  ===============================

// 方法:最高位不变,按位异或
always @(posedge w_clk or negedge w_rst_n) begin
    if(!w_rst_n)
        w_gay <= 5'd0;
    else 
        w_gay <= {w_bin[4],(w_bin[4:1]^w_bin[3:0])}; 
end

// ===========================  读  ===============================
// 二进制转格雷码
always @(posedge r_clk or negedge r_rst_n) begin
    if(!r_rst_n)
        r_gay <= 5'd0;
    else 
        r_gay <= {r_bin[4],(r_bin[4:1]^r_bin[3:0])}; 
end
2.2.3 将格雷码打2拍:多比特信号的跨时钟域

先将二进制编码为格雷码,然后对格雷码打2拍(同步器),将格雷码同步到另一个时钟域下

// ===========================  写  ===============================
// 将读部分得到的格雷码打2拍后,同步到写时钟下
always @(posedge w_clk or negedge w_rst_n) begin
    if(!w_rst_n)
        r_gay_dff1 <= 5'd0;
        r_gay_dff2 <= 5'd0;
    else 
        r_gay_dff1 <= r_gay;
        r_gay_dff2 <= r_gay_dff1;         
end

// ===========================  读  ===============================
// 将写部分得到的格雷码打2拍后,同步到读时钟下
always @(posedge r_clk or negedge r_rst_n) begin
    if(!r_rst_n)
        w_gay_dff1 <= 5'd0;
        w_gay_dff2 <= 5'd0;
    else 
        w_gay_dff1 <= w_gay;
        w_gay_dff2 <= w_gay_dff1;         
end
2.2.4 将打2拍后的格雷码解码为二进制:组合逻辑

在这里插入图片描述

// ===========================  写  ===============================
// 将同步过来的读格雷码解码为读二进制,方便和写二进制地址比较
always @(*) begin
        for(i=4;i>=0;i=i-1)begin
            if(i==4)
                r_gay_2_bin[i] <= r_gay_dff2[i];
            else
                r_gay_2_bin[i] <= r_gay_dff2[i] ^ r_gay_2_bin[i+1]      
        end    
end
// ===========================  读  ===============================
// 格雷码解码为二进制
integer j;
always @(*) begin
        for(j=4;j>=0;j=j-1)begin
            if(j==4)
                w_gay_2_bin[j] <= w_gay_dff2[j];
            else
                w_gay_2_bin[j] <= w_gay_dff2[j] ^ w_gay_2_bin[j+1]      
        end    
end
2.2.5 得到写满、读空信号:组合逻辑
// ===========================  写  ===============================
// 方法一:写满信号:最高位不同,其余位相同——用格雷码
assign w_full = ((w_bin[4] != r_gay_dff2[4]) && (w_bin[3] != r_gay_dff2[3]) && (w_bin[2:0] == r_gay_dff2[2:0]))?1'b1:1'b0;
// 方法二:写满信号:最高位、次高位不同,其余位相同——用格雷码解码后的二进制
assign w_full = ((w_bin[4] != r_gay_2_bin[4]) && (w_bin[30] != r_gay_2_bin[3:0]))?1'b1:1'b0;
// ===========================  读  ===============================
// 读空信号:所有位相同
assign r_empty = (r_bin == w_gay_2_bin)?1'b1:1'b0;

2.2.6 汇总
module top_FIFO_ly (
    //写
    input           w_en,
    input           w_clk,
    input           w_rst_n,
    input   [7:0]  w_data,

    output          w_full,


    //读
    input           r_en,
    input           r_clk,
    input           r_rst_n,
    output          r_empty,
    output  [7:0]  r_data
);

reg [4:0]   w_bin;// 对地址扩充一位
reg [4:0]   r_bin;
reg [4:0]   w_gay;
reg [4:0]   r_gay;
reg [4:0]   r_gay_dff1;
reg [4:0]   r_gay_dff2;
reg [4:0]   w_gay_dff1;
reg [4:0]   w_gay_dff2;
reg [4:0]   r_gay_2_bin;
reg [4:0]   w_gay_2_bin;



// ===========================  写  ===============================
// 写地址扩充一位后,地址自加1
always @(posedge w_clk or negedge w_rst_n) begin
    if(!w_rst_n)
        w_bin <= 5'd0;
    else if(w_en && !w_full) // && 逻辑与,得到一位;&按位与,保持相与元素位宽
        w_bin <= w_bin + 4'd1;
    else   
        w_bin <= w_bin;    
end

// 二进制转格雷码
// 方法:最高位不变,按位异或
always @(posedge w_clk or negedge w_rst_n) begin
    if(!w_rst_n)
        w_gay <= 5'd0;
    else 
        w_gay <= {w_bin[4],(w_bin[4:1]^w_bin[3:0])}; 
end

// 将读部分得到的格雷码打2拍后,同步到写时钟下
always @(posedge w_clk or negedge w_rst_n) begin
    if(!w_rst_n)begin
        r_gay_dff1 <= 5'd0;
        r_gay_dff2 <= 5'd0;
    end

    else begin
        r_gay_dff1 <= r_gay;
        r_gay_dff2 <= r_gay_dff1;    
    end
     
end

integer i;
// 将同步过来的读格雷码解码为读二进制,方便和写二进制地址比较
always @(*) begin
        for(i=4;i>=0;i=i-1)begin
            if(i==4)
                r_gay_2_bin[i] <= r_gay_dff2[i];
            else
                r_gay_2_bin[i] <= r_gay_dff2[i] ^ r_gay_2_bin[i+1];  
        end    
end

// 方法一:写满信号:最高位不同,其余位相同——用格雷码
//assign w_full = ((w_bin[4] != r_gay_dff2[4]) && (w_bin[3] != r_gay_dff2[3]) && (w_bin[2:0] == r_gay_dff2[2:0]))?1'b1:1'b0;
// 方法二:写满信号:最高位、次高位不同,其余位相同——用格雷码解码后的二进制
assign w_full = ((w_bin[4] != r_gay_2_bin[4]) && (w_bin[3:0] == r_gay_2_bin[3:0]))?1'b1:1'b0;


// ===========================  读  ===============================
always @(posedge r_clk or negedge r_rst_n) begin
    if(!r_rst_n)
        r_bin <= 5'd0;
    else if(r_en && !r_empty) // && 逻辑与,得到一位;&按位与,保持相与元素位宽
        r_bin <= r_bin + 4'd1;
    else   
        r_bin <= r_bin;    
end


// 二进制转格雷码
always @(posedge r_clk or negedge r_rst_n) begin
    if(!r_rst_n)
        r_gay <= 5'd0;
    else 
        r_gay <= {r_bin[4],(r_bin[4:1]^r_bin[3:0])}; 
end

// 将写部分得到的格雷码打2拍后,同步到读时钟下
always @(posedge r_clk or negedge r_rst_n) begin
    if(!r_rst_n)begin
        w_gay_dff1 <= 5'd0;
        w_gay_dff2 <= 5'd0;        
    end

    else begin
        w_gay_dff1 <= w_gay;
        w_gay_dff2 <= w_gay_dff1;           
    end

     
end
// 格雷码解码为二进制
integer j;
always @(*) begin
        for(j=4;j>=0;j=j-1)begin
            if(j==4)
                w_gay_2_bin[j] <= w_gay_dff2[j];
            else
                w_gay_2_bin[j] <= w_gay_dff2[j] ^ w_gay_2_bin[j+1];   
        end    
end

// 读空信号:所有位相同
assign r_empty = (r_bin == w_gay_2_bin)?1'b1:1'b0;


SDPRAM_ly SDPRAM_inst(
    .w_en(w_en_RAM),
    .w_clk(w_clk),
    .w_data(w_data),
    .w_addr(w_bin[3:0]),

    .r_en(r_en_RAM),
    .r_clk(r_clk),
    .r_rst_n(r_rst_n),
    .r_addr(r_bin[3:0]),

    .r_data(r_data)

);

assign w_en_RAM = (w_en && (!w_full))?1'b1:1'b0;
assign r_en_RAM = (r_en && (!r_empty))?1'b1:1'b0;
endmodule

三、如何使用FIFO

这部分内容以后遇到了再进行补充

3.1 数据跨时钟域

3.2 缓存写速度快的数据

3.3 读写位宽不同

  • 3
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值