0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

怎样设计一个同步FIFO?(1)

jf_78858299 来源:IC加油站 作者: 硅谷老李 2023-05-04 15:48 次阅读

今天咱们开始聊聊FIFO的设计。FIFO是一个数字电路中常见的模块,主要作用是数据产生端和接受端在短期内速率不匹配时作为数据缓存。FIFO是指First In, First Out,即先进先出,跟大家排队一样。越早排队的人排在越前面,轮到他的次序也越早,所以FIFO有些时候也被称为队列queue。

这一篇我们先介绍利用Flip flop来作为FIFO存储单元的设计方法,这也是同步FIFO中最为简单的内容,内容比较基础。之后老李会带大家了解基于SRAM的FIFO设计。 而且我们这里只讲同步FIFO, 即写入端和读出端是属于同一个时钟域。如果写入和读出是不同的时钟域,那么就是异步FIFO。关于异步FIFO之前老李在CDC系列里有讲过,大家有兴趣可以直接在公众号底部点击CDC可以了解。

我们先来看一个FIFO模块需要那几个基本的输入输出。

其中写入端写入操作为push,要写入的data为D,当push为高时,数据D被写入FIFO。对于写入端,只需要在乎FIFO是不是满:如果FIFO已经满了,是不允许再写入的。对于读出端,数据读出为pop,Q为读出的数据。在读出端,只需要在乎FIFO是不是空:如果为空,则不能进行pop操作。

对于读出端来说,这里有一点需要明确:当FIFO里面有数据的时候,Q应该输出当前FIFO最前面(最早进入)的那个数据,而不是需要pop才能输出。也就是说,假设FIFO为空,这个时候我们写入一个数据D1,那么在下一个周期,Q应该立刻变为D1,同时empty为0。当只有读第二次写入的数据的时候,我们才需要pop第一次,Q才会指向D2。 这样的行为和一个D触发器非常类似,所以上面我们才将输入数据表示为D,输出数据表示为Q,便于和D触发器类比起来。为什么强调这一点,因为在后面利用SRAM来实现FIFO的时候如果要实现这一点是需要技巧的,我们后面会看到。(老李也见过要想读出第一个数必须要先pop一次的FIFO设计,这种设计就不是很高效,要多花一个周期来才能读出第一个数)。

另外FIFO还有一个特性,即当FIFO不是空也不是满的时候,是允许读和写发生在同一个周期的,即一边写入,一边读出。这个对于Flip Flop来实现的FIFO很容易做到,但是对于SRAM来实现的FIFO就不是那么容易了,特别是SRAM只有一个端口,一个周期内要么读,要么写。这样设计的时候就更需要技巧了,我们在后面的文章中再细聊。

下面我们来聊FIFO的内部细节。首先我们说存储单元,对于利用FlipFlop来实现的FIFO,存储单元就是一个flop array。

reg [DATA_WIDTH-1:0]    mem[DEPTH]

其中DATA_WIDTH和DEPTH都是两个参数parameter。

然后我们需要两个指针pointer,来分别用于读和写,分别为wr_ptr, rd_ptr。有人也喜欢用wr_addr, rd_addr。这两个指针的意义为:

wr_ptr: 接下来要写入的位置。

rd_ptr: 当前读出的位置。

初始的时候,wr_ptr和rd_ptr都被reset成0,那么可以理解为,第一个要写入的location是mem[0],第一个要读出的位置也是mem[0]。

当有一次push操作的时候,wr_ptr要加1。当有一次pop的时候,rd_ptr要+1。

那么我们可以写出下面的逻辑

always_ff@(posedge clk) begin

if(!rst_n)

for(int i = 0; i < DEPTH; i++)

   mem[i] <= '0;

else begin

if(push & ~full)

   mem[wr_ptr] <= d;

end

end

assign q = mem[rd_ptr];

那么接下来有两个问题:一是如何来判断空和满,另个一问题是如何给wr_ptr和rd_ptr加1。

在思考这两个问题之前,我们看我们需要几位来表示wr_ptr和rd_ptr。如果FIFO的深度是DEPTH,那么要来取址mem[DEPTH],我们需要的位数应该是$clog2(DEPTH)。比如DEPTH=8,那么需要3位ptr用来取址。

再来回答上面两个问题。通常我们有两种方式来处理。第一种方式,如果DEPTH刚好是2的幂次,那么做法是给wr_ptr和rd_ptr各多分配一位。比如DEPTH=8,则分配4位给wr_ptr和rd_ptr。这样做的好处是我们可以利用2进制的特性

空:wr_ptr == rd_ptr。

满:wr_ptr把rd_ptr套圈了,即低位相等,但是MSB相反。

举个例子当把mem[0]到mem[7]都写完之后,wr_ptr 由4’b0111再加1就来到了4'b1000,而如果我们还没有pop过的话rd_ptr就还停留在4‘b0000,这样就是达到了套圈,FIFO变满了。

而且这样做简化了rd_ptr和wr_ptr加1的操作,直接利用2进制的进位加法,当记到4‘b0111的时候再加1就直接变为4'b1000,这样MSB自动表示是不是套圈,而低位可以直接用来取址mem。

localparam PTR_WIDTH = $clog2 (DEPTH) + 1;

logic [DATA_WIDTH-1:0] mem[DEPTH];

logic [PTR_WIDTH-1:0] wr_ptr;

logic [PTR_WIDTH-1:0] rd_ptr;

always_ff@(posedge clk) begin

if(!rst_n)

for(int i = 0; i < DEPTH; i++)

   mem[i] <= '0;

else begin

if(push & ~full)

   mem[wr_ptr[PTR_WIDTH-2:0]] <= d;

end

end

always_ff@(posedge clk) begin

if(!rst_n)

wr_ptr <= '0;

else

if(push & ~full)

  wr_ptr <= wr_ptr + 1'b1;

end

always_ff@(posedge clk) begin

if(!rst_n)

rd_ptr <= '0;

else

if(pop & ~empty)

  rd_ptr <= rd_ptr + 1'b1;

end

assign q = mem[rd_ptr[PTR_WIDTH-2:0]];

assign full = (rd_ptr[PTR_WIDTH-1] ^ wr_ptr[PTR_WIDTH-1]) &&

(rd_ptr[PTR_WIDTH-2:0] == wr_ptr[PTR_WIDTH-2:0]);

assign empty = rd_ptr == wr_ptr;

但是这样做的限制在于DEPTH必须是2的幂次方个。 如果不是,比如是6,那么当wr_ptr记到3'b101的时候,下一次写入就不能直接二进制加1了,而是要回到3'b000。这个时候稍微方便一点的做法是设计一个计数器,用来计数当前FIFO已经被写入但是还未读出的数据个数。这样做的好处是FIFO的空满可以直接利用这个计数器与0和与DEPTH相比较而得到。老李更推荐这一种写法,而且这个时候wr_ptr和rd_ptr也不需要多加1位。

localparam PTR_WIDTH = $clog2 (DEPTH);

logic [DATA_WIDTH-1:0] mem[DEPTH];

logic [PTR_WIDTH-1:0] wr_ptr;

logic [PTR_WIDTH-1:0] rd_ptr;

logic [PTR_WIDTH:0] cnt; //current fifo count

always_ff@(posedge clk) begin

if(!rst_n)

for(int i = 0; i < DEPTH; i++)

   mem[i] <= '0;

else begin

if(push & ~full)

   mem[wr_ptr] <= d;

end

end

always_ff@(posedge clk) begin

if(!rst_n)

wr_ptr <= '0;

else

if(push & ~full)

  wr_ptr <= (wr_ptr == DEPTH-1) ? '0 : (wr_ptr + 1'b1);

end

always_ff@(posedge clk) begin

if(!rst_n)

rd_ptr <= '0;

else

if(pop & ~empty)

  rd_ptr <= (rd_ptr == DEPTH-1) ? '0 : (rd_ptr + 1'b1);

end

always_ff@(posedge clk) begin

if(!rst_n)

cnt <= '0;

else begin

//only push, no pop

if(push && !pop && !full)

  cnt <= cnt + 1'b1;

//only pop, no push

else if(!push && pop && !empty)

  cnt <= cnt - 1'b1;

//no pop or push,

//pop and push in the same cycle

// else cnt <= cnt;

end

end

assign q = mem[rd_ptr];

assign full = cnt == DEPTH;

assign empty = cnt == '0;

这就是基于Flip Flop的同步FIFO的基本原理,还是比较简单直接的,RTL code加起来也就几十行。下面老李希望大家思考几个问题:

  1. 什么时候使用基于Flip-flop的同步FIFO?什么时候使用基于SRAM的FIFO?
  2. 最后的q是来自于寄存器输出还是来自于组合逻辑电路输出?如果是来自于组合逻辑输出,如何优化?
  3. 如果希望full和empty也直接来自寄存器的输出,要怎么更改设计?

最后再附送一个老李一个老朋友的作为面试官的出的面试题,大家可以自己思考一下:如何设计一个深度为1的同步FIFO?

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • fifo
    +关注

    关注

    3

    文章

    369

    浏览量

    43069
  • 数字电路
    +关注

    关注

    192

    文章

    1397

    浏览量

    79752
  • 数据缓存
    +关注

    关注

    0

    文章

    22

    浏览量

    6950
收藏 人收藏

    评论

    相关推荐

    同步FIFO设计详解及代码分享

    FIFO (先入先出, First In First Out )存储器,在 FPGA 和数字 IC 设计中非常常用。 根据接入的时钟信号,可以分为同步 FIFO 和异步 FIFO
    发表于 06-27 10:24 1322次阅读
    <b class='flag-5'>同步</b><b class='flag-5'>FIFO</b>设计详解及代码分享

    求大神帮忙给VHDL 写的同步FIFO

    急需同步FIFO,我这有,但是仿真图不对,我也不知道是不是代码有问题。
    发表于 03-15 09:25

    stm32 FIFO接收4完整can消息

    使用stm32接收can消息时,发现FIFO0/FIFO1只能接收3完整报文,当接收第4报文时,第三报文会被自动覆盖,想使用双
    发表于 12-28 10:58

    请问怎样去设计种异步FIFO

    为什么要设计种异步FIFO?异步FIFO的设计原理是什么?怎样去设计种异步FIFO
    发表于 06-18 09:20

    FPGA之FIFO练习3:设计思路

    根据FIFO工作的时钟域,可以将FIFO分为同步FIFO和异步FIFO同步
    的头像 发表于 11-29 07:08 1656次阅读

    同步FIFO之Verilog实现

    FIFO的分类根均FIFO工作的时钟域,可以将FIFO分为同步FIFO和异步FIFO
    的头像 发表于 11-01 09:57 1358次阅读

    怎样设计一个同步FIFO?(2)

    开始往下读之前,老李先问一个问题,假如现在让你设计一个深度为N的基于2port SRAM的同步FIFO,请问至少需要多大的SRAM? 假设SRAM的位宽就是你的数据宽度,那么问题就是问你需要的SRAM的行数至少是多少?如果你觉得答案是显而易见的N,那么你值得读完这一篇。
    的头像 发表于 05-04 15:55 680次阅读
    <b class='flag-5'>怎样</b>设计一个<b class='flag-5'>同步</b><b class='flag-5'>FIFO</b>?(2)

    怎样设计一个同步FIFO?(3)

    我们说这个结构之所以使得FIFO的输出Q在读完SRAM之后保持稳定,其实需要SRAM本身可以保持RDATA在读操作之后的多个周期保持稳定。即SRAM本身的读时序如下图所示:图中cycle 4,5,6都没有读操作,SRAM的RDATA依然保持D0不变。
    的头像 发表于 05-04 15:59 431次阅读
    <b class='flag-5'>怎样</b>设计一个<b class='flag-5'>同步</b><b class='flag-5'>FIFO</b>?(3)

    FIFO设计—同步FIFO

    FIFO是异步数据传输时常用的存储器,多bit数据异步传输时,无论是从快时钟域到慢时钟域,还是从慢时钟域到快时钟域,都可以使用FIFO处理。
    发表于 05-26 16:12 1067次阅读
    <b class='flag-5'>FIFO</b>设计—<b class='flag-5'>同步</b><b class='flag-5'>FIFO</b>

    FIFO设计—异步FIFO

    异步FIFO主要由五部分组成:写控制端、读控制端、FIFO Memory和两个时钟同步
    发表于 05-26 16:17 1050次阅读
    <b class='flag-5'>FIFO</b>设计—异步<b class='flag-5'>FIFO</b>

    一个简单的RTL同步FIFO设计

    FIFO 是FPGA设计中最有用的模块之一。FIFO 在模块之间提供简单的握手和同步机制,是设计人员将数据从一个模块传输到另一个模块的常用选择。
    发表于 06-14 08:59 258次阅读

    基于寄存器的同步FIFO

      FIFO 是FPGA设计中最有用的模块之一。FIFO 在模块之间提供简单的握手和同步机制,是设计人员将数据从一个模块传输到另一个模块的常用选择。 在这篇文章中,展示了一个简单的 RTL
    的头像 发表于 06-14 09:02 491次阅读

    基于Verilog的同步FIFO的设计方法

    同步FIFO的设计主要包括读写地址的产生、数据的读写、以及状态的控制。下面我们将分别介绍这三个方面的设计。
    发表于 08-31 12:53 303次阅读

    同步FIFO设计分析

    模块虽小但是要有新意,首先写一个同步FIFO,这是一个烂大街的入门级项目,但是我肯定不会写的那么简单
    的头像 发表于 09-11 17:11 367次阅读
    <b class='flag-5'>同步</b><b class='flag-5'>FIFO</b>设计分析

    同步FIFO和异步FIFO的区别 同步FIFO和异步FIFO各在什么情况下应用

    同步FIFO和异步FIFO的区别 同步FIFO和异步FIFO各在什么情况下应用? 1.
    的头像 发表于 10-18 15:23 1016次阅读