本篇是继可综合的async_fifo设计(一)的下半篇,给出了testbench代码、测试波形及几个注意事项。

异步FIFO设计

  • 三、重难点
  • 四、仿真波形
    • 4.1 读写不同时进行的逻辑测试
      • 4.1.1 FIFO初始化功能验证
      • 4.1.2 FIFO空标志产生逻辑的验证
      • 4.1.3 FIFO满标志产生逻辑的验证
      • 4.1.4 FIFO写满保护功能的验证
      • 4.1.5 FIFO读空保护(不再读)功能的验证
    • 4.2 读写同时进行的逻辑测试
      • 4.2.1 写信号不做间隔控制
      • 4.2.2 写信号做间隔控制
    • 4.3 testbench代码
    • 4.4 FIFO最小深度计算及实际应用

三、重难点

整个设计前后,提出以下几个问题:

// 1、空满标志产生(关键)

答:比较读写格雷码产生,满标志wr_full在写时钟域产生,写指针多轮回一圈,最高位标志位相异,其余位相同;空标志rd_empty在读时钟域产生,读写指针在同一奇偶圈,所有位相同时表示读指针追赶上写指针,此时读空标志产生。

// 2、空满标志产生后,持续时间是多长?

答:本设计采用的方法是:通过将一部时钟域的读写使能信号同步到本时钟域,写满后直到读使能,才清零wr_full,读空后直到写使能信号到来,才清零rd_empty。异步信号的同步处理打2拍处理,会延迟异步读写使能信号的到来,所以,标志的清空会有一定延迟。

// 3、是否有空满数据保护(满了不准写,或者空了不再读)?

答:有,通过格雷码反转为二进制地址,排除了空满标志延迟产生以及空满标志产生后继续的读写操作等造成地址的冗余增加,进而实现了空满保护。

// 4、地址跨时钟域同步是否对空满判断产生影响?

答:同步固然带来延迟,延迟直接对空满标志的产生造成影响。以rd_empty为例,若在延迟间隔内,写使能无效(不继续写),rd_empty正常产生(读、写不同时进行),若在间隔内,写使能有效,则写指针会递增,则传递到读时钟域的写指针一定小于或等于当前时刻的实际写指针,产生的读空则为"假空",因为实际上写地址已经变化了;同理,写满时,也会产生"假满"。“假空”、"假满"是一种保守操作,只是产生了假的空满标志,不会影响实际的fifo的数据读写。

// 5、同步后的地址有延迟,会造成地址多增加(如读空判断模块),怎么解决?

答:同步后的地址会导致空满标志滞后产生,从而影响地址多增加,属于空满保护的范畴,将gray地址转换成bin地址,限制地址过增。

// 6、同步器带来的影响是什么?是否致命?出现"假"空"假"满–>保守。

答:同4,是不致命的,实际上,异步fifo本身就不是100%的可靠性ram设计,有些问题必然存在。

// 7、快时钟域地址同步到慢时钟域,产生地址遗漏现象,漏掉的地址会产生致命影响吗?

答:本设计中,读时钟慢与写时钟(写的快,读的慢),在rd_logic中判断rd_empty的产生时,写指针同步到读时钟域,存在遗漏现象(慢时钟采样快时钟),在读写操作不同时进行时(若同时进行写操作),问题不大(不会影响空满标志产生),同时进行需要考虑是否会漏掉进行空标志产生所需要的关键指针(此时等待不到相等,则标志就产生不了)。

遗漏现象还会导致,格雷码的优势发挥不出,因为格雷码漏掉之后,相邻码之间就不是唯一1bit不同了,这就增加了重汇聚现象发生的概率。

// 8、判空判满标志产生逻辑的异同要理解

答:两者的判断相似却不同,结合代码仔细推敲。

// 9、读写同时进行如何考虑,本设计有隐患吗?

答:一般读慢,写快。当读得快写得慢时,为了避免出现问题需要扩展fifo_deepth。读写同时进行时,具体讨论结合仿真波形分析。

// 10、读写时钟的快慢是如何影响读写功能的?这种影响是否可以不考虑?

答:慢时钟域到快时钟域,只会出现保守问题,快时钟域到慢域,将出现漏采,漏掉的地址一般不会影响fifo的行为,但会影响fifo的效率。但如果漏掉关键指针则会出现逻辑错误。

四、仿真波形

4.1 读写不同时进行的逻辑测试

4.1.1 FIFO初始化功能验证

实际上,这部分测试对FIFO的读写是没有太大影响的,但一般来说,FIFO是应该具有初始化功能的,这部分功能在RTL代码中描述不方便(容易造成额外的硬件资源开销)。故将该部分功能放在tb中,所以用户根据需求在操作时可以添加初始化操作。初始化的测试波形如下图4-1.1所示。观察波形,0-1023地址都被初始化为0,知初始化功能正常。

特别地,初始化与否并不影响FIFO的正常读写。若使用了初始化,因为这是写操作的一种特例,当写完所有地址后,会产生wr_full标志,且circle_high会取反置1,这样,后续进行用户数据的写入时,如果不做逻辑复位,则会使逻辑功能出错。故作如下处理:

添加init_en信号,作为逻辑复位的使能信号。该信号在tb中,初始化结束后产生,并持续1个wr_clk。复位逻辑清除的波形如下图4-1.2所示。

Tb中init_en的产生:

@ (posedge wr_clk); //等待wr_clk上升沿
init_en = 1;       // init状态下的使能信号,用于wr_full和circle_high的复位
@ (posedge wr_clk); // 持续1个clk_high
init_en = 0;

图4-1.1 异步FIFO初始化清零仿真波形

图4-1.2 异步FIFO初始化后的逻辑复位仿真波形

4.1.2 FIFO空标志产生逻辑的验证

初始化后完成逻辑复位,写入100个随机数据,而后依次读出数据直到产生rd_empty,如下图4-1.3所示。

图4-1.3 异步FIFO读空标志产生的仿真波形

4.1.3 FIFO满标志产生逻辑的验证

读空后,写入1024个数据,直到写地址指向地址100,写指针再次追赶上读指针,FIFO写满标志wr_full产生,如下图4-1.4所示。

图4-1.4 异步FIFO写满标志产生的仿真波形

4.1.4 FIFO写满保护功能的验证

上述操作,写满标志wr_full产生后,再次使能写使能信号,观察FIFO中数据的变化情况,看到FIFO中100以后的地址并没有被新的数据给覆盖,则知写满保护功能有效。写满保护的仿真波形如下图4-1.5所示。

图4-1.5 异步FIFO写满保护功能测试的仿真波形

4.1.5 FIFO读空保护(不再读)功能的验证

测试读空后是否有读空保护功能,测试波形如下图4-1.6所示。

图4-1.6 异步FIFO读空保护功能测试的仿真波形

4.2 读写同时进行的逻辑测试

4.2.1 写信号不做间隔控制

Rd_en起来之后,wr_en一直有效连续写入数据,可以预测,这种情形下写的速度快于读的速度,最终的结果是,连续的wr_full产生,然后再rd_en下清零,写入数据后wr_full又产生wr_full,如此循环,此case下FIFO的效率低,不建议使用。测试的波形如下图4-2.1所示。

仿真结果与理论相符合,但出现了逻辑错误,即同一个地址吃掉了2-3个数据(发生在连续周期的wr_full信号段),使得写入的数据发生丢失现象,这在对数据要求场合较高时是不允许的。

图4-2.1 异步FIFO同时读写不作写信号间断控制时测试的仿真波形

4.2.2 写信号做间隔控制

逻辑协调wr_en间断有效,保证写入的数据快被读完后者读完后再次写入数据,如此循环控制,可使FIFO连续的读写。测试的波形如下图4-2.2所示。

图4-2.2 异步FIFO同时读写且写信号做间断控制时测试的仿真波形

观察细节,发现,写完数据后作读操作,最终读出的地址和数据会比上次写到的地址多1,原因是rd_en一直有效,在rd_empty产生间隙时,多读了一个数据,如果rd_en做好时序控制,可以避免该现象。此外,读空后再写入数据,而后读操作的起始地址和刚刚多读的地址一样,这相当于将该地址的数据读了两次,分别是写之前和写之后的。其中,多读数据的情况如下图4-2.3所示。在大多数情况下,这种情况应该没有太大的影响。

图4-2.3 异步FIFO同时读写多读一个数据细节的仿真波形

4.3 testbench代码


`timescale 1ns / 1ps
//--------------------------
// 问题记录:2020-06-19 20:00
// 1、空满标志产生(关键)
// 2、空满标志产生后,持续时间是多长?
// 3、是否有空满数据保护(满了不准写,或者空了不再读)?
// 4、地址跨时钟域同步是否对空满判断产生影响?
// 5、同步后的地址有延迟,会造成地址多增加(如读空判断模块),怎么解决
// 6、同步器带来的影响是什么?是否致命?出现“假”空“假”满-->保守
// 7、快时钟域地址同步到慢时钟域,产生地址遗漏现象,漏掉的地址会产生致命影响吗?
// 8、判空标志产生逻辑要理解
//--------------------------
module async_fifo_tb # (parameter DAT_WID  = 8 ,parameter ADDR_WID = 11 ,parameter FIFO_DEEPTH = 1024 ,parameter PIPE_NUM = 3
);reg       rst_n;
reg     wr_clk;
reg     wr_en;
reg  [DAT_WID - 1 : 0]wr_data;
wire [DAT_WID - 1 : 0]rd_data;
reg     rd_en;
reg     rd_clk;
reg     init_en;
wire    wr_full;
wire    rd_empty;
wire [ADDR_WID - 1 : 0] rd_addr;
wire [ADDR_WID - 1 : 0] wr_addr;
integer tb_start;
//----------------------------
// 激励产生及初始化
//----------------------------
initial beginrst_n = 0;wr_clk = 0;rd_clk = 0;tb_start = 0;wr_en = 0;rd_en = 0;init_en = 0;wr_data = 1;#200 rst_n = 1;#300 tb_start = 1;
endassign rd_addr = async_fifo_tb.async_fifo_test.rd_addr_bin;
assign wr_addr = async_fifo_tb.async_fifo_test.wr_addr_bin;
assign wr_en_latch_tb = async_fifo_tb.async_fifo_test.wr_logic_inst.wr_en_latch;
assign rd_en_latch_tb = async_fifo_tb.async_fifo_test.rd_logic_inst.rd_en_latch;initial beginwait(tb_start);// 测试task用例// basic_rd_wr_nocom();ext_wr_rd_com();
end// 生成读写时钟
always #20 wr_clk = ~wr_clk;
always #50 rd_clk = ~rd_clk;//----------------------------
// task:fifo初始化
// 使用初始化功能吗,需要在逻辑上作
// 单独处理,因为初始化也是写fifo的一种
// 形式,初始化所有ram后,fifo的状态处于
// wr_full,如果继续写的话,新的内容被写进
// ram,但是wr_full需要rd_en有效才会清零
//----------------------------
task fifo_init ();begin#100 ;@ (negedge wr_clk);wr_en = 1; repeat (FIFO_DEEPTH) beginwr_data = 0;@ (posedge wr_clk);end@ (negedge wr_clk);wr_en = 0;wait(wr_full);            // 等待写满标志产生// 以下实现fifo_init造成的冗余逻辑的复位@ (posedge wr_clk);init_en = 1;          // init状态下的使能信号,用于wr_full和circle_high的复位@ (posedge wr_clk);     // 持续1个clk_highinit_en = 0;    wait(~wr_full);         // 等待写满标志清零end
endtask//----------------------------
// task:读写指针显示
//----------------------------
task display_ptr (input [ADDR_WID -1 : 0] addr_ptr
);reg wr_ptr;beginif (wr_en | wr_en_latch_tb) beginwr_ptr = 1;$display("(%0t) now ptr is wr_ptr", $time);end else if (rd_en | rd_en_latch_tb) beginwr_ptr = 0;$display("(%0t) now ptr is rd_ptr", $time);end$display("(%0t) now addr_ptr is %d", $time, addr_ptr);endendtask//----------------------------
// task:读写同时进行的逻辑测试
//----------------------------
task ext_wr_rd_com ();beginfifo_init();             // fifo清零初始化$display("(%0t) wr_full = %b", $time, wr_full);  #200 wr_2_fifo(100);    // 写入100个数据wait (~wr_en_latch_tb); // 等待指针指向将要写入的地址display_ptr(wr_addr);#200 rd_en = 1;        // 开始读fifowait(wr_addr == rd_addr);// wait(rd_empty);         // 等待rd_empty产生display_ptr(rd_addr);rd_en = 0;$display("(%0t) rd_empty = %b", $time, rd_empty);// 由于写时钟快于读时钟,为了避免写入数据过快覆盖掉旧的// 未读出的数据,需要间隔wr_en信号fork begin#200 wr_2_fifo(1000);    // 写入1000个数据endbeginwait(wr_addr == 11'd500); // 中途开始读rd_en = 1;endjoinwait(rd_empty);// wr_2_fifo(150);          // 写入150个数据,用于间隔控制测试wr_2_fifo(1000);     // 不做wr_en间隔控制的测试wr_2_fifo(1000);       // 不做wr_en间隔控制的测试#20000  wr_2_fifo(100);    // 写入100个数据rd_en = 0;#1000 $stop;endendtask//----------------------------
// task:读写分开的逻辑测试
//----------------------------
task basic_rd_wr_nocom ();beginfifo_init();             // fifo清零初始化$display("(%0t) wr_full = %b", $time, wr_full);  #200 wr_2_fifo(100);    // 写入100个数据wait (~wr_en_latch_tb); // 等待指针指向将要写入的地址display_ptr(wr_addr);#200 rd_en = 1;        // 开始读fifowait(wr_addr == rd_addr);// wait(rd_empty);         // 等待rd_empty产生display_ptr(rd_addr);rd_en = 0;$display("(%0t) rd_empty = %b", $time, rd_empty);#200 wr_2_fifo(924); // 写入100个数据 #200 wr_2_fifo(100);    // 写入100个数据$display("(%0t) wr_full = %b", $time, wr_full);       // 观察wr_full状态wr_2_fifo(10);            // fifo满后,测试是否具有写满保护功能#200 rd_en = 1;           // 开始读数据$display("(%0t) rd_empty = %b", $time, rd_empty);wait(rd_en_latch_tb);   // 让读指针至少增加1,防止rd_en马上变成0wait(wr_addr == rd_addr);// wait (rd_empty);rd_en = 0;wait(rd_empty);            // 等待rd_empty产生#200 rd_en = 1;         // 测试读空后是否有读空保护(实际上,fifo数据被读出,内容还是存在ram中,如果读空后继续读,则会读到重复的数据)#1000 rd_en = 0;#400 $stop;      endendtask//----------------------------
// task:数据产生,送入fifo中
//----------------------------
task wr_2_fifo (input [ADDR_WID - 1 : 0] num            // 向fifo中存入数据的个数);
// 两种产生数据激励的方式都可以// begin// #100 ;// @ (negedge wr_clk);// wr_en = 1; // repeat (num) begin// wr_data = {$random} % 127;// @ (posedge wr_clk);// end// @ (negedge wr_clk);// wr_en = 0;// endbegin#100 ;@ (posedge wr_clk);wr_en = 1;repeat (num) beginwr_data = {$random} % 127;@ (posedge wr_clk);endwr_en = 0;end
endtask//----------------------------
// 顶层功能模块例化
//----------------------------
async_fifo # (.DAT_WID      (DAT_WID) ,.ADDR_WID    (ADDR_WID) ,.PIPE_NUM   (PIPE_NUM)) async_fifo_test (.rst_n             (rst_n) ,// 外部输入.wr_data        (wr_data) ,.wr_en           (wr_en) ,.wr_clk        (wr_clk) ,.init_en      (init_en) ,.rd_en           (rd_en) ,.rd_clk        (rd_clk) ,// fifo输出.rd_data         (rd_data) ,.wr_full         (wr_full) ,.rd_empty        (rd_empty));endmodule

以上只给出了部分case的功能测试,一定存在考虑不周之处,欢迎指正!

4.4 FIFO最小深度计算及实际应用

当读速率慢于写速率时,FIFO便可被用作系统中的缓冲元件或队列。因此FIFO的大小基本上暗示了所需缓存数据的容量,该容量取决于读写数据的速率。据统计,系统的数据速率取决于系统的负载能力。因此为了保证FIFO的大小,我们需要考虑FIFO传输的最坏情况下。所谓最坏的情况就是使得写速率最大,读速率最小;通常是考虑突发传输。

4.4.1 读写不同时进行
这种情形,突发wr_en到来,在接下来的一段时间内,FIFO一直被写入数据,直到写满。故FIFO_deepth=突发数据个数。

4.4.2 读写同时进行
这种情形,写的快,读的慢,但由于rd_en的存在,影响了整体的数据处理速度,推导前设以下几个变量:
1、写时钟wr_clk的频率Fwr;
2、读时钟rd_clk的频率Frd;
3、突发数据个数N_burst,突发时间为T_burst;
4、Y个rd_clk内读出的数据个数X,每个数据读出的时间为trd;
5、符合要求的FIFO深度最小值Deepth;
则,最小深度=突发数据个数-突发时间内被读出的数据个数,即,

举例, 如果100个写时钟周期可以写入80个数据,10个读时钟可以读出8个数据。令wclk=rclk ,考虑背靠背(20个clk不发数据+80clk发数据+80clk发数据+20个clk不发数据的200个clk,背靠背即最坏情况)
代入公式可计算FIFO的深度:

fifo_depth = 160-160(80%)=160-128=32

特别地,若Y个rd_clk时钟内读出的数据Y个,即每个时钟读出一个数据,则上述式4-1变成:

例,写50MHz,读40MHz,要不丢失读出10万个数据,则至少设置D =?
答:D=100000*(1-0.8)=20000=20K,需要设置20K深度的FIFO。

实际应用中往往是以半空半满信号来指示fifo的空满状态的,所以实际设计fifo的时候会至少留下一半深度度的裕量。或者,设置的FIFO深度比最大突发数据量多1倍,以避免数据被覆盖。

最后,给出整个工程的RTL代码链接:async_fifo_sys下载链接(点击跳转到下载界面),欢迎下载和交流!

可综合的异步fifo设计(二)相关推荐

  1. 异步fifo_【推荐】数字芯片异步FIFO设计经典论文

    之前有一篇文章我已经推荐过了数字芯片跨时钟域设计的经典论文 ([推荐]数字芯片跨时钟域设计经典论文 ),希望看过的读者都有一定的收获.不过有点遗憾的是那片论文中虽然提到了异步FIFO,却没有讲具体的原 ...

  2. 基于 FPGA 的高级数字电路设计(7)单口 RAM、同步 FIFO、异步 FIFO 设计

    一.单口 RAM 设计 module BRAM_PORTA( input clka, input ena, input wea, input [3:0] addra, input [15:0] din ...

  3. 02【Verilog实战】异步FIFO设计(附源码RTL/TB)

    脚 本:makefile 工 具:vcs 和 verdi 文 章:1. 同步FIFO的设计和功能验证(附源码)     2. Verilog的亚稳态现象和跨时钟域处理方法 写在前面 这个专栏的内容记录 ...

  4. 【FIFO】异步 FIFO 设计

    目录 写在前面 简介 传递多个异步信号 同步 FIFO 指针 异步FIFO指针 二进制 FIFO 指针注意事项 FIFO测试问题 格雷码计数器 ‑ 样式 #1 格雷码模式 格雷码计数器基础 额外的格雷 ...

  5. 异步FIFO设计详解

    目录 一.FIFO介绍 二.FIFO的"空"/"满"检测 三.同步化分析 四.异步FIFO设计与结构 一.FIFO介绍 FIFO存储器概念介绍_的博客-CSDN ...

  6. 异步fifo_异步FIFO设计

    由于知乎编辑器对markdown支持性问题,本文为直接上传markdown文档,格式混乱且图像不全,格式完整版请移步个人博客 异步FIFO设计​qiankun214.github.io 1.设计目标 ...

  7. 基于FPGA的异步FIFO设计

    今天要介绍的异步FIFO,可以有不同的读写时钟,即不同的时钟域.由于异步FIFO没有外部地址端口,因此内部采用读写指针并顺序读写,即先写进FIFO的数据先读取(简称先进先出).这里的读写指针是异步的, ...

  8. 异步FIFO设计:各个模块的作用及Verilog代码详解

    实现原理参考:异步FIFO---Verilog实现_alangaixiaoxiao的博客-CSDN博客_异步fifo 代码参考:IC基础(一):异步FIFO_MaoChuangAn的博客-CSDN博客 ...

  9. 异步fifo设计及验证verilog代码

    论文参考:The Design and Verification of a Synchronous First-In First-Out 博客参考:[原创]异步FIFO设计原理详解 (含RTL代码和T ...

  10. 异步FIFO设计原理及Verliog源代码

    1.什么是FIFO? FIFO是英文First In First Out 的缩写,是一种先进先出的数据缓存器,他与普通存储器的区别是没有外部读写地址线,这样使用起来非常简单,但缺点就是只能顺序写入数据 ...

最新文章

  1. Numpy、TensorFlow和Keras函数输入参数axis理解
  2. 手机端网页中图片之间出现白线的解决方法
  3. Redis进阶实践之五Redis的高级特性
  4. linux shell 数组倒序
  5. 字符串函数实现(strlen,strcpy,strcmp,strcat,strrev)
  6. Python 3.x 格式化输出字符串 % format 笔记
  7. 李彦宏最新演讲:移动互联网的时代已经结束了
  8. CUDA层硬件debug之路
  9. java interface 实例_Java - Interface 接口的实现方式实例
  10. 复试分数线该怎么划定呢(洛谷P1068题题解,Java语言描述)
  11. C语言获取系统当前时间的两种方式
  12. kail linux更新源、挂载
  13. 极客大学产品经理训练营:数据分析 第八章作业
  14. 二级c语言上机题库下载,二级C语言上机题库(全).doc
  15. visio 如何画光学器件
  16. Spark:Container exited with a non-zero exit code 137
  17. Codeforces Gym 100015G Guessing Game 差分约束
  18. Excel如何提取文本左边的数字
  19. linux cadaver 命令,备份Linux系统的数据到坚果云
  20. 一对一直播软件如何盈利?

热门文章

  1. lcd1602c语言编程,C51单片机LCD1602编程经验分享
  2. 注册表修改系统分辨率
  3. 超级终端之——MobaXterm
  4. 信息系统项目管理师考试难?一次过备考经验分享给大家
  5. 汇编学习从入门到精通
  6. eclipse查看代码git历史_Eclipse使用教程 : 利用Eclipse中git的插件来查看项目状态 - Break易站...
  7. CnPack应用总结
  8. 保姆级Ruby on rails安装教程
  9. HFS远程命令执行漏洞入侵抓鸡黑阔服务器
  10. AI加持,云之家V10让移动办公充满无限想象