FPGA笔试面试之FIFO篇

1、FIFO简介

FIFO是英文First In First Out 的缩写,是一种先进先出的数据缓存器,它与普通存储器的区别是没有外部读写地址线,这样使用起来非常简单,但缺点就是只能顺序写入数据,顺序的读出数据,其数据地址由内部读写指针自动加1完成,不能像普通存储器那样可以由地址线决定读取或写入某个指定的地址。

(1)FIFO的作用
作用1:异步FIFO可以在两个不同时钟系统之间快速而方便地传输实时数据
  异步FIFO读写分别采用相互异步的不同时钟。在现代集成电路芯片中,随着设计规模的不断扩大,一个系统中往往含有数个时钟,多时钟域带来的一个问题就是,如何设计异步时钟之间的接口电路。异步FIFO是这个问题的一种简便、快捷的解决方案,使用异步FIFO可以在两个不同时钟系统之间快速而方便地传输实时数据。
  
作用2:使用FIFO来达到数据匹配的目的
  对于不同宽度的数据接口也可以用FIFO,例如单片机位8位数据输出,而DSP可能是16位数据输入,在单片机与DSP连接时就可以使用FIFO来达到数据匹配的目的。

(2)FIFO分类

同步FIFO:是指读时钟和写时钟为同一个时钟,在时钟沿来临时同时发生读写操作;

异步FIFO:是指读写时钟不一致,读写时钟是互相独立的。

(3)FIFO的常见参数

FIFO的宽度:即FIFO一次读写操作的数据位;
FIFO的深度:指的是FIFO可以存储多少个N位的数据(如果宽度为N)。
满标志:FIFO已满或将要满时由FIFO的状态电路送出的一个信号,以阻止FIFO的写操作继续向FIFO中写数据而造成溢出(overflow)。
空标志:FIFO已空或将要空时由FIFO的状态电路送出的一个信号,以阻止FIFO的读操作继续从FIFO中读出数据而造成无效数据的读出(underflow)。
读时钟:读操作所遵循的时钟,在每个时钟沿来临时读数据。
写时钟:写操作所遵循的时钟,在每个时钟沿来临时写数据。

2、FIFO工作原理及应用方法
(1)读写指针的工作原理
读指针:总是指向下一个将要被写入的单元,复位时,指向第1个单元(编号为0)。

写指针:总是指向当前要被读出的数据,复位时,指向第1个单元(编号为0)

(2)FIFO的“空”/“满”检测
FIFO设计的关键:产生可靠的FIFO读写指针和生成FIFO“空”/“满”状态标志。
当读写指针相等时,既可表明FIFO为空,又可表明FIFO为满。

当读写指针相等时,表明FIFO为空,这种情况发生在复位操作时,或者当读指针读出FIFO中最后一个字后,追赶上了写指针时,如下图所示:

在这里插入图片描述
当读写指针再次相等时,表明FIFO为满,这种情况发生在,当写指针转了一圈,折回来(wrapped around)又追上了读指针,如下图:
在这里插入图片描述
为了区分到底是满状态还是空状态,可以采用以下方法:

方法1:在指针中添加一个额外的位(extra bit),当写指针增加并越过最后一个FIFO地址时,就将写指针这个未用的MSB加1,其它位回零。对读指针也进行同样的操作。此时,对于深度为2n的FIFO,需要的读/写指针位宽为(n+1)位,如对于深度为8的FIFO,需要采用4bit的计数器,0000~1000、1001~1111,MSB作为折回标志位,而低3位作为地址指针。

如果两个指针的MSB不同,说明写指针比读指针多折回了一次;如r_addr=0000,而w_addr = 1000,为满。
如果两个指针的MSB相同,则说明两个指针折回的次数相等。其余位相等,说明FIFO为空;

(3)二进制FIFO指针的考虑
  将一个二进制的计数值从一个时钟域同步到另一个时钟域的时候很容易出现问题,因为采用二进制计数器时所有位都可能同时变化,在同一个时钟沿同步多个信号的变化会产生亚稳态问题。而使用格雷码只有一位变化,因此在两个时钟域间同步多个位不会产生问题。所以需要一个二进制到gray码的转换电路,将地址值转换为相应的gray码,然后将该gray码同步到另一个时钟域进行对比,作为空满状态的检测。
在这里插入图片描述
使用gray码进行对比,如何判断“空”与“满”?

在格雷码域如何判断空与满
对于“空”的判断依然依据二者完全相等(包括MSB);

而对于“满”的判断,如下图,由于gray码除了MSB外,具有镜像对称的特点,当读指针指向7,写指针指向8时,除了MSB,其余位皆相同,不能说它为满。因此不能单纯的只检测最高位了,在gray码上判断为满必须同时满足以下3条:

a. wptr和同步过来的rptr的MSB不相等,因为wptr必须比rptr多折回一次。
b. wptr与rptr的次高位不相等,如上图位置7和位置15,转化为二进制对应的是0111和1111,MSB不同说明多折回一次,111相同代表同一位置。
c. 剩下的其余位完全相等。
在这里插入图片描述
(4)总体实现
系统的总体框图如下:
在这里插入图片描述
3、同步化分析

由于是异步FIFO的设计,读写时钟不一样,在产生读空信号和写满信号时,会涉及到跨时钟域的问题,如何解决?
  跨时钟域的问题:由于读指针是属于读时钟域的,写指针是属于写时钟域的,而异步FIFO的读写时钟域不同,是异步的,要是将读时钟域的读指针与写时钟域的写指针不做任何处理直接比较肯定是错误的,因此我们需要进行同步处理以后仔进行比较
  解决方法:加两级寄存器同步 + 格雷码(目的都是消除亚稳态)

(1)使用异步信号进行使用的时候,好的设计都会对异步信号进行同步处理,同步一般采用多级D触发器级联处理,如下图。这种模型大部分资料都说的是第一级寄存器产生亚稳态后,第二级寄存器稳定输出概率为90%,第三极寄存器稳定输出的概率为99%,如果亚稳态跟随电路一直传递下去,那就会另自我修护能力较弱的系统直接崩溃。
在这里插入图片描述

(2)将一个二进制的计数值从一个时钟域同步到另一个时钟域的时候很容易出现问题,因为采用二进制计数器时所有位都可能同时变化,在同一个时钟沿同步多个信号的变化会产生亚稳态问题。而使用格雷码只有一位变化,因此在两个时钟域间同步多个位不会产生问题。所以需要一个二进制到gray码的转换电路,将地址值转换为相应的gray码,然后将该gray码同步到另一个时钟域进行对比,作为空满状态的检测。

那么,多位二进制码如何转化为格雷码?
在这里插入图片描述
换一种描述方法:
在这里插入图片描述
verilog代码实现就一句:assign gray_code = (bin_code>>1) ^ bin_code;

使用gray码解决了一个问题,但同时也带来另一个问题,即在格雷码域如何判断空与满。

这里直接给出结论:

判断读空时:需要读时钟域的格雷码rgray_next和被同步到读时钟域的写指针rd2_wp每一位完全相同;

判断写满时:需要写时钟域的格雷码wgray_next和被同步到写时钟域的读指针wr2_rp高两位不相同,其余各位完全相同;

assign full = (wr_addr_gray == {~(rd_addr_gray_d2[addr_width-:2]),rd_addr_gray_d2[addr_width-2:0]}) ;//高两位不同 assign empty = ( rd_addr_gray == wr_addr_gray_d2 );

4、Verilog实现

//异步FIFO  
//FIFO设计的关键:产生可靠的FIFO读写指针和生成FIFO“空”/“满”状态标志。
module fifo_async#(
                 parameter   data_width = 16,
                 parameter   data_depth = 256,
                 parameter   addr_width = 8
)
(
                  input                           rst,    //复位
                  input                           wr_clk, //写时钟
                  input                           wr_en,  //写使能
                  input      [data_width-1:0]     din,    //写数据      
                  input                           rd_clk, //读时钟
                  input                           rd_en,  //读使能
                  output reg                      valid,  //
                  output reg [data_width-1:0]     dout,   //读数据
                  output                          empty,  //空
                  output                          full    //满
    );


reg    [addr_width:0]    wr_addr_ptr;//地址指针,比地址多一位,MSB用于检测在同一圈
reg    [addr_width:0]    rd_addr_ptr;
wire   [addr_width-1:0]  wr_addr;//RAM 地址
wire   [addr_width-1:0]  rd_addr;

wire   [addr_width:0]    wr_addr_gray;//地址指针对应的格雷码
reg    [addr_width:0]    wr_addr_gray_d1;
reg    [addr_width:0]    wr_addr_gray_d2;
wire   [addr_width:0]    rd_addr_gray;
reg    [addr_width:0]    rd_addr_gray_d1;
reg    [addr_width:0]    rd_addr_gray_d2;


reg [data_width-1:0] fifo_ram [data_depth-1:0]; //?

//=========================================================write fifo 
genvar i;  //声明循环变量
generate   //generate 条件编译
for(i = 0; i < data_depth; i = i + 1 )
begin:fifo_init  //语句块命名
always@(posedge wr_clk or posedge rst)
    begin
       if(rst)
          fifo_ram[i] <= 'h0;//fifo复位后输出总线上是0,并非ram中真的复位。可无
       else if(wr_en && (~full))
          fifo_ram[wr_addr] <= din;
       else
          fifo_ram[wr_addr] <= fifo_ram[wr_addr];
    end   
end    
endgenerate    
//========================================================read_fifo
always@(posedge rd_clk or posedge rst)
   begin
      if(rst)
         begin
            dout <= 'h0;
            valid <= 1'b0;
         end
      else if(rd_en && (~empty))
         begin
            dout <= fifo_ram[rd_addr];
            valid <= 1'b1;
         end
      else
         begin
            dout <=   'h0;//fifo复位后输出总线上是0,并非ram中真的复位,只是让总线为0;
            valid <= 1'b0;
         end
   end
assign wr_addr = wr_addr_ptr[addr_width-1-:addr_width];
assign rd_addr = rd_addr_ptr[addr_width-1-:addr_width];
//=============================================================格雷码同步化
always@(posedge wr_clk )
   begin
      rd_addr_gray_d1 <= rd_addr_gray;
      rd_addr_gray_d2 <= rd_addr_gray_d1;
   end
always@(posedge wr_clk or posedge rst)
   begin
      if(rst)
         wr_addr_ptr <= 'h0;
      else if(wr_en && (~full))
         wr_addr_ptr <= wr_addr_ptr + 1;
      else 
         wr_addr_ptr <= wr_addr_ptr;
   end
//===================================================rd_clk
always@(posedge rd_clk )
      begin
         wr_addr_gray_d1 <= wr_addr_gray;
         wr_addr_gray_d2 <= wr_addr_gray_d1;
      end
always@(posedge rd_clk or posedge rst)
   begin
      if(rst)
         rd_addr_ptr <= 'h0;
      else if(rd_en && (~empty))
         rd_addr_ptr <= rd_addr_ptr + 1;
      else 
         rd_addr_ptr <= rd_addr_ptr;
   end

//========================================================== translation gary code
assign wr_addr_gray = (wr_addr_ptr >> 1) ^ wr_addr_ptr;
assign rd_addr_gray = (rd_addr_ptr >> 1) ^ rd_addr_ptr;

assign full = (wr_addr_gray == {~(rd_addr_gray_d2[addr_width-:2]),rd_addr_gray_d2[addr_width-2:0]}) ;//高两位不同
assign empty = ( rd_addr_gray == wr_addr_gray_d2 );

endmodule

参考:
(1)https://blog.csdn.net/alangaixiaoxiao/article/details/81432144

  • 0
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
FPGA(现场可编程门阵列)是一种可编程逻辑器件,它可以根据需要进行重新配置,以实现各种不同的功能。串口是一种用于数据传输的通信接口,常用于连接电脑和外部设备。而FIFO(First In First Out,先进先出)是一种存储数据的缓冲区,数据以先进先出的方式进行读写。 FPGA串口转发进FIFO是指将串口接收到的数据通过FPGA进行转发,并将其存储到FIFO缓冲区中。这个过程可以分为以下几个步骤: 首先,需要确定使用的FPGA开发板和串口通信的协议。通常情况下,我们可以选择UART(通用异步收发器)协议来进行串口通信。 然后,在FPGA上设计一个接收串口数据的模块。这个模块的功能是接收串口信号,并将其转换为数字信号,以便FPGA进行处理。 接着,设计一个FIFO模块。这个模块的作用是接收来自串口数据接收模块的数据,并以先进先出的方式存储到FIFO缓冲区中。当有新数据到达时,FIFO模块会将其写入到FIFO中。 最后,设计一个发送模块,将FIFO缓冲区中的数据取出,并通过串口发送出去。这个模块的功能是读取FIFO缓冲区中的数据,并将其转换为串口信号,以便发送出去。 通过以上设计,就可以实现FPGA串口转发进FIFO的功能。当有数据通过串口发送过来时,FPGA会将其接收并存储到FIFO中,然后从FIFO中读取数据,并通过串口发送出去。这样就实现了串口数据的转发功能。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值