一开始是想既然是极简教程,就应该只给出FIFO的概念,没想到还是给出了同步以及异步FIFO的设计,要不然总感觉内容不完整,也好,自己设计的FIFO模块不用去担心因IP核跨平台不通用的缺陷!那我们开始吧。 上一篇博客讲了同步FIFO的概念以及同步FIFO的设计问题,并给出了同步FIFO的Verilog代码以及VHDL代码,并经过了行为仿真测试,链接如下: FPGA基础知识极简教程(3)从FIFO设计讲起之同步FIFO篇 这里简单提一下,同步FIFO的代码中用到了一个系统函数$clog2(),这个系统函数的使用方法很简单: 例如我定义了FIFO缓冲区的深度为DATA_DEPTH = 8,那么其地址(指针)位宽是多少呢? 指针就可以定义为: 还有一点需要提的是,我们都知道在FPGA中FIFO的实现可以使用分布式资源或者BLOCK RAM,那么如何掌控呢? Vivado 随笔(1) 综合属性之 ram_style & rom_style? 就上述同步FIFO而言,我们可以在缓冲区定义时候添加如下约束: 为了验证是否有用,我们在Vivado中进行验证如下: 当设计中使用BLOCK RAM约束: 综合后的电路图如下,可见FIFO缓存区使用的资源为BLOCK RAM; 当使用Distributed RAM约束时: 综合后电路图FIFO缓冲区部分: 再设计异步FIFO电路之前,有必要说明一下FIFO的用途,上篇博文提到: FPGA或者ASIC设计内部电路多位数据在不同的时钟域交互,为了数据安全、正确、稳定交互,我们需要设计异步FIFO进行跨时钟域交互。正如之前博客所写:漫谈时序设计(1)跨时钟域是设计出来的,而非约束出来的! 这三条大概讲的都是一个意思,总结起来就是FIFO可以起到数据缓冲或缓存的作用,例如突然数据,我们就需要先将其缓存起来,之后再从FIFO中读出出来进行处理,这样也可以保证数据不会丢失。 引用互联网上其他说法就是:数据写入过快,并且间隔时间长,也就是突发写入。那么通过设置一定深度的FIFO,可以起到数据暂存的功能,且使得后续处理流程平滑。 无论是同步FIFO还是异步FIFO,其大致原理都是一致的,先入先出自然不必多说,关于空满的判断都是通过读写指针之间的关系来判断;还有就是异步FIFO的指针需要进行一定的处理,例如格雷码处理,这样可以减小读指针同步到写指针时钟域,或者写指针同步到读指针时钟域时出现亚稳态的概率,这是因为格雷码每次只有一位变化,这样一位数据在进行跨时钟域传输的时候亚稳态出现的概率会大大减小。同步之后便进行对比,以此来判断FIFO的空满。 在同步FIFO中,我们使用计数的方式进行判断空满,运用的也是这个原理,写一个数据时,计数值加1,读出一个数据时,计数值减1,如下图: 如果再接着读2个 数据,则计数值为0,FIFO就被读空了。 好了,我们分析完了同步FIFO是如何判断空满的,下面重点放在异步FIFO的原理上。 我曾写过一篇CDC问题的博客,谈到了异步FIFO的设计: 这篇博客中说,同步FIFO可以使用计数方式来判断空满,但是异步FIFO不能,因为写指针和读指针根本不在同一个时钟域,计数器无法处理这样的计数。 这只是解决了判断空满的一个问题,也就是确定指针的关系! 下面是判断是否写满的示意图: 上面是写满判断的情况,下面给出读空判断的可能情形分析: 下面给出手绘示意图: 到此,这一种设计方式的异步FIFO算是讲完了,下面就是设计的问题了。 如果你认真分析了上述异步FIFO的实现方式,那么你会分分钟写出实现代码,我的版本如下: 注意事项 一定要二者相等的下一个读周期empty信号为1; 当然你用时序逻辑也可以哦。 我们对上述设计进行行为仿真,先给出我的测试文件: 仿真波形为: 由于本博客写的时候有点长,几乎一天了,所以就到这里吧!不得不说的是,异步FIFO的实现方式肯定不只有这一种,还有很多其他实现方式,各位可以自行尝试。博文目录
写在前面
正文
同步FIFO回顾
$clog2()系统函数使用
parameter DATA_WIDTH = 8; parameter DATA_DEPTH = 8; reg [DATA_WIDTH - 1 : 0] fifo_buffer[0 : DATA_DEPTH - 1]; reg [$clog2(DATA_DEPTH) - 1 : 0] wr_pointer = 0; reg [$clog2(DATA_DEPTH) - 1 : 0] rd_pointer = 0;
这时候就可以使用系统函数$clog2()了,位宽可以表示为:$clog2(DATA_DEPTH) // = 3;
reg [$clog2(DATA_DEPTH) - 1 : 0] wr_pointer = 0; reg [$clog2(DATA_DEPTH) - 1 : 0] rd_pointer = 0;
综合属性控制资源使用
当使用FIFO缓冲空间较小时,我们选择使用Distributed RAM;当使用FIFO缓冲空间较大时,我们选择使用BLOCK RAM资源;这是一般的选择原则。
我们可以通过在设计代码中加入约束条件来控制,之前有写过(*ram_style = "distributed"*) reg [DATA_WIDTH - 1 : 0] fifo_buffer[0 : DATA_DEPTH - 1]; 或者: (*ram_style = "block"*) reg [DATA_WIDTH - 1 : 0] fifo_buffer[0 : DATA_DEPTH - 1];
(*ram_style = "block"*)reg [DATA_WIDTH - 1 : 0] fifo_buffer[0 : DATA_DEPTH - 1];
同时给出资源利用率报告:
可见存在BLOCK RAM ,由于我仅仅综合了一个同步FIFO,因此这个Block RAM一定是FIFO缓冲区消耗的。(*ram_style = "distributed"*)reg [DATA_WIDTH - 1 : 0] fifo_buffer[0 : DATA_DEPTH - 1];
资源利用率情况;
可见 ,并未使用BLOCK RAM,而是使用了LUT RAM,也即分布式RAM。
综上,验证了这条约束的有效性。异步FIFO设计
FIFO用途回顾
我们在时序分析时候,通常都将跨时钟域路径进行伪路径约束,因此我们必须在设计时候解决跨时钟域数据传输问题,异步FIFO在此起到关键作用。
异步FIFO原理回顾
那异步FIFO如何判断空满呢?
回答这个问题之前,我想先统一的说明FIFO(同步或者异步)是如何判断空满的?
起始,读写指针都是0,FIFO一定为空;之后对FIFO进行一系列的读写操作,导致读写指针关系发生了变化,可以分为下面两种情况:
我最喜欢用这幅图来分析FIFO,下面一行一行的分析:
谈谈跨时钟域传输问题(CDC)
那么怎么处理呢?
博客里采用的方法是对读写指针的位宽多添1位,这样可以在读写指针相等时,表示FIFO空,而在写指针和读指针最高位不同,而其他位相等时,也即写指针大于读指针一个FIFO深度的数值,表示FIFO满,这不就是意味着写指针绕了一圈,又追上了读指针了吗?
恰是如此,用来解决不用计数而具体判断FIFO空满的问题。
那下一个问题就是如何判断?
由于读写指针不在同一个时钟域,二者需要同步到同一个时钟域后进行判断大小。
具体的操作就是在各自的时钟域内进行读写操作,同时:
细心的人恐怕能否发现,这里存在的一个小插曲,当读指针转换成格雷码以及同步到写时钟域的过程中,读写指针可能还都在递增,这样的话,等同步后的读指针与写指针相等时(不包括最高位),实际的读指针可能已经变了,这样的话其实还有几个空间没有写满!但这样设计就有问题吗?没有问题!这叫保守设计,可以增加FIFO的安全性。
还是和第一种情况有同样的插曲,当写指针转换成格雷码以及同步到读时钟域的过程中,写指针和读指针都可能还在递增,这样当二者判断相等的时候,则写指针可能还多写了几个空间,实际上并没有读空。
那问题来了,这样操作就有问题了吗?同样没有问题,这样也保证来了FIFO的安全,防止被读空。异步FIFO设计
`timescale 1ns / 1ps //////////////////////////////////////////////////// // Engineer: Reborn Lee // Module Name: asy_fifo // https://blog.csdn.net/Reborn_Lee //////////////////////////////////////////////////// module asy_fifo#( parameter DATA_WIDTH = 8, parameter DATA_DEPTH = 32 )( input wr_clk, input wr_rst, input wr_en, input [DATA_WIDTH - 1 : 0] wr_data, output reg full, input rd_clk, input rd_rst, input rd_en, output reg [DATA_WIDTH - 1 : 0] rd_data, output reg empty ); // define FIFO buffer reg [DATA_WIDTH - 1 : 0] fifo_buffer[0 : DATA_DEPTH - 1]; //define the write and read pointer and //pay attention to the size of pointer which should be greater one to normal reg [$clog2(DATA_DEPTH) : 0] wr_pointer = 0, rd_pointer = 0; //write data to fifo buffer and wr_pointer control always@(posedge wr_clk) begin if(wr_rst) begin wr_pointer <= 0; end else if(wr_en) begin wr_pointer <= wr_pointer + 1; fifo_buffer[wr_pointer] <= wr_data; end end //read data from fifo buffer and rd_pointer control always@(posedge rd_clk) begin if(rd_rst) begin rd_pointer <= 0; end else if(rd_en) begin rd_pointer <= rd_pointer + 1; rd_data <= fifo_buffer[rd_pointer]; end end //wr_pointer and rd_pointer translate into gray code wire [$clog2(DATA_DEPTH) : 0] wr_ptr_g, rd_ptr_g; assign wr_ptr_g = wr_pointer ^ (wr_pointer >>> 1); assign rd_ptr_g = rd_pointer ^ (rd_pointer >>> 1); //wr_pointer after gray coding synchronize into read clock region reg [$clog2(DATA_DEPTH) : 0] wr_ptr_gr, wr_ptr_grr, rd_ptr_gr, rd_ptr_grr; always@(rd_clk) begin if(rd_rst) begin wr_ptr_gr <= 0; wr_ptr_grr <= 0; end else begin wr_ptr_gr <= wr_ptr_g; wr_ptr_grr <= wr_ptr_gr; end end //rd_pointer after gray coding synchronize into write clock region always@(wr_clk) begin if(wr_rst) begin rd_ptr_gr <= 0; rd_ptr_grr <= 0; end else begin rd_ptr_gr <= rd_ptr_g; rd_ptr_grr <= rd_ptr_gr; end end // judge full or empty always@(posedge rd_clk) begin if(rd_rst) empty <= 0; else if(wr_ptr_grr == rd_ptr_g) begin empty <= 1; end else empty <= 0; end always@(posedge wr_clk) begin if(wr_rst) full <= 0; else if( (rd_ptr_grr[$clog2(DATA_DEPTH) - 1 : 0] == wr_ptr_g[$clog2(DATA_DEPTH) - 1 : 0]) && ( rd_ptr_grr[$clog2(DATA_DEPTH)] != wr_ptr_g[$clog2(DATA_DEPTH)] ) ) begin full <= 1; end else full <= 0; end endmodule
reg [$clog2(DATA_DEPTH) : 0] wr_pointer = 0, rd_pointer = 0;
always@(posedge rd_clk) begin if(rd_rst) empty <= 0; else if(wr_ptr_grr == rd_ptr_g) begin empty <= 1; end else empty <= 0; end
always@(posedge wr_clk) begin if(wr_rst) full <= 0; else if( (rd_ptr_grr[$clog2(DATA_DEPTH) - 1 : 0] == wr_ptr_g[$clog2(DATA_DEPTH) - 1 : 0]) && ( rd_ptr_grr[$clog2(DATA_DEPTH)] != wr_ptr_g[$clog2(DATA_DEPTH)] ) ) begin full <= 1; end else full <= 0; end
//wr_pointer and rd_pointer translate into gray code wire [$clog2(DATA_DEPTH) : 0] wr_ptr_g, rd_ptr_g; assign wr_ptr_g = wr_pointer ^ (wr_pointer >>> 1); assign rd_ptr_g = rd_pointer ^ (rd_pointer >>> 1);
异步FIFO仿真
`timescale 1ns/1ps module asy_fifo_tb; parameter DATA_WIDTH = 8; parameter DATA_DEPTH = 16; reg wr_clk; reg wr_rst; reg wr_en; reg [DATA_WIDTH - 1 : 0] wr_data; wire full; reg rd_clk; reg rd_rst; reg rd_en; wire [DATA_WIDTH - 1 : 0] rd_data; wire empty; initial begin wr_clk = 0; forever begin #5 wr_clk = ~wr_clk; end end initial begin rd_clk = 0; forever begin #10 rd_clk = ~rd_clk; end end initial begin wr_rst = 1; rd_rst = 1; wr_en = 0; rd_en = 0; #30 wr_rst = 0; rd_rst = 0; //write data into fifo buffer @(negedge wr_clk) wr_data = $random; wr_en = 1; repeat(7) begin @(negedge wr_clk) wr_data = $random; // write into fifo 8 datas in all; end // read parts @(negedge wr_clk) wr_en = 0; @(negedge rd_clk) rd_en = 1; repeat(7) begin @(negedge rd_clk); // read empty end @(negedge rd_clk) rd_en = 0; //write full # 150 @(negedge wr_clk) wr_en = 1; wr_data = $random; repeat(15) begin @(negedge wr_clk) wr_data = $random; end @(negedge wr_clk) wr_en = 0; #50 $finish; end asy_fifo #( .DATA_WIDTH(DATA_WIDTH), .DATA_DEPTH(DATA_DEPTH) ) inst_asy_fifo ( .wr_clk (wr_clk), .wr_rst (wr_rst), .wr_en (wr_en), .wr_data (wr_data), .full (full), .rd_clk (rd_clk), .rd_rst (rd_rst), .rd_en (rd_en), .rd_data (rd_data), .empty (empty) ); endmodule
仿真通过,且功能符合预期。
后面如果有更多有关FIFO的有趣知识或者心得体会,我会继续补充!
参考资料
交个朋友
本网页所有视频内容由 imoviebox边看边下-网页视频下载, iurlBox网页地址收藏管理器 下载并得到。
ImovieBox网页视频下载器 下载地址: ImovieBox网页视频下载器-最新版本下载
本文章由: imapbox邮箱云存储,邮箱网盘,ImageBox 图片批量下载器,网页图片批量下载专家,网页图片批量下载器,获取到文章图片,imoviebox网页视频批量下载器,下载视频内容,为您提供.
阅读和此文章类似的: 全球云计算