一、什么是FIFO
FIFO 是 First In First Out 的简称。是指在FPGA内部用逻辑资源实现的能对数据的存储具有先进先出特性的一种缓存器。
FIFO 与 FPGA 内部的 RAM 和 ROM 的区别是 FIFO 没有外部读写地址线,采取顺序写入数据,顺序读出数据的方式,其数据地址由内部读写指针自动加1完成。FIFO 使用起来简单方便,由此带来的缺点是不能像 RAM 和 ROM 那样可以由地址线决定读取或写入某个指定的地址。
二、为什么要用FIFO
FPGA程序实现的电路实际上是由一个个独立的功能模块组成的,各个模块又通过相关信号关联在一起,当存在模块间处理数据速度不同(有快有慢)时,处理得快的模块就需要等一等处理得慢的模块,这个等待其实就是缓存的实现。我们可以采用FIFO来解决数据的缓存。
打个比方,就像水龙头放水慢(输入慢),但我们人提水的时候是一次处理一桶水(输出快),所以需要一个水桶作为缓存,等存满一桶水,再一次被人提走。
又或者像我们人喝水,一次接一杯水(输入快), 渴的时候喝两口(输出慢)。这里,杯子作为缓存。
另外,在现代集成电路芯片中,随着设计规模的不断扩大,一个系统中往往含有数个时钟,此时,异步时钟之间的接口电路的设计将成为关键。而使用异步FIFO可以在两个不同时钟系统之间快速而方便地传输实时数据。
三、什么时候用FIFO
数据缓存、协议处理、串并转换、跨时钟域数据处理。
四、FIFO分类
FIFO根据读写时钟是否为同一时钟分为同步FIFO和异步FIFO。
同步FIFO是指读时钟和写时钟为同一个时钟,在时钟沿来临时可同时发生读写操作。
异步FIFO是指读写时钟不一致,读写时钟是互相独立的2个时钟。
同步FIFO在实际应用中比较少见,常用的是异步FIFO,但基于学习的目的,下文对两种FIFO都进行讲解。
五、同步FIFO
同步FIFO电路框图:
简单来说,FIFO其实就是一个双口RAM加上两个读写控制模块。FIFO的常见参数和信号如下:
- FIFO的宽度:即FIFO一次读写操作的数据位;
- FIFO的深度:指的是FIFO可以存储多少个N位的数据(如果宽度为N)。
- 满标志:FIFO已满或将要满时由FIFO的状态电路送出的一个信号,以阻止FIFO的写操作继续向FIFO中写数据而造成溢出(overflow)。
- 空标志:FIFO已空或将要空时由FIFO的状态电路送出的一个信号,以阻止FIFO的读操作继续从FIFO中读出数据而造成无效数据的读出(underflow)。
- 读时钟:读操作所遵循的时钟,在每个时钟沿来临时读数据。(同步FIFO 读写只有一个时钟)
- 写时钟:写操作所遵循的时钟,在每个时钟沿来临时写数据。(异步FIFO读写时钟分开)
- 读指针:总是指向下一个将要被写入的单元,复位时,指向第1个单元(编号为0)。
- 写指针:总是指向当前要被读出的数据,复位时,指向第1个单元(编号为0)
其实可以把FIFO比作一个单向行驶的隧道,隧道两端都有一个门进行控制,FIFO宽度就是这个隧道单向有几个车道,FIFO的深度就是一个车道能容纳多少辆车,当隧道内停满车辆时,这就是FIFO的写满状态,当隧道内没有一辆车时,这便是FIFO的读空状态。
FIFO 的设计原则是任何时候都不能向满FIFO中写入数据(写溢出),任何时候都不能从空FIFO中读取数据(读溢出)。FIFO 设计的核心是空满判断。FIFO设置读,写地址指针,FIFO初始化的时候 读指针和写指针都指向地址为0的位置, 当往FIFO里面每写一个数据,写地址指针自动加1指向下一个要写入的地址。当从FIFO里面每读一个数据,读地址指针自动加1指向下一个要读出的地址,最后通过比较读地址指针和写地址指针的大小来确定空满状态。
当读地址指针追上写地址指针,写地址指针跟读地址指针相等,此时FIFO是读空状态。
,
当写地址指针再次追上读地址指针,写指针跟读地址指针再次相等的时候,此时FIFO是写满状态。
可以设置一个计数器,当写使能有效的时候计数器加一;当读使能有效的时候,计数器减一,将计数器与FIFO的size进行比较来判断FIFO的空满状态。这种方法设计比较简单,但是需要的额外的计数器,就会产生额外的资源,而且当FIFO比较大时,会降低FIFO最终可以达到的速度。
6、同步FIFO设计代码
同步FIFO基本接口:
信号 | 描述 |
clk | 系统时钟 |
rstn | 系统复位信号 |
wr_en | 写使能端 |
wr_data | FIFO写数据 |
fifo_full | FIFO的满标志位 |
rd_en | 读使能端 |
rd_data | FIFO读数据 |
fifo_empty | FIFO的空标志位 |
同步FIFO实现代码如下:
1 module sync_fifo#(parameter BUF_SIZE=8, BUF_WIDTH=8) ( 2 //FIFO的数据位宽默认为8bit 3 //FIFO深度默认为8 4 5 6 input i_clk,//输入时钟 7 input i_rst,//复位信号 8 input i_w_en,//写使能信号 9 input i_r_en,//读使能信号 10 input [BUF_WIDTH-1:0] i_data,//写入数据 11 12 output reg [BUF_WIDTH-1:0] o_data,//读出数据 13 output o_buf_empty,//FIFO空标志 14 output o_buf_full );//FIFO满标志 15 16 reg [3:0] fifo_cnt; //记录FIFO数据个数 17 reg [$clog2(BUF_SIZE)-1:0] r_ptr,w_ptr; //数据指针为3位宽度,0-7索引,8个数据深度,循环指针0-7-0-7 18 reg [BUF_WIDTH-1:0] buf_mem[0:BUF_SIZE-1]; //定义FIFO大小 19 20 21 //判断空满 22 assign o_buf_empty=(fifo_cnt==4'd0)?1'b1:1'b0; 23 assign o_buf_full=(fifo_cnt==4'd8)?1'b1:1'b0; 24 25 26 always@(posedge i_clk or posedge i_rst) //用于修改计数器 27 begin 28 if(i_rst) 29 fifo_cnt<=4'd0; 30 else if((!o_buf_full&&i_w_en)&&(!o_buf_empty&&i_r_en)) //同时读写,计数器不变 31 fifo_cnt<=fifo_cnt; 32 else if(!o_buf_full&&i_w_en) //写数据,计数器加1 33 fifo_cnt<=fifo_cnt+1; 34 else if(!o_buf_empty&&i_r_en) //读数据,计数器减1 35 fifo_cnt<=fifo_cnt-1; 36 else 37 fifo_cnt <= fifo_cnt; //其他情况,计数器不变 38 end 39 40 always@(posedge i_clk or posedge i_rst) //读数据 41 begin 42 if(i_rst) 43 o_data<=8'd0; 44 else if(!o_buf_empty&&i_r_en) 45 o_data<=buf_mem[r_ptr]; 46 end 47 48 always@(posedge i_clk) //写数据 49 begin 50 if(!o_buf_full&&i_w_en) 51 buf_mem[w_ptr]<=i_data; 52 end 53 54 always@(posedge i_clk or posedge i_rst) //读写地址指针变化 55 begin 56 if(i_rst) begin 57 w_ptr <= 0; 58 r_ptr <= 0; 59 end 60 else begin 61 if(!o_buf_full&&i_w_en) // 写数据,地址加1,溢出后自动回到0开始 62 w_ptr <= w_ptr + 1; 63 if(!o_buf_empty&&i_r_en) // 读数据,地址加1,溢出后自动回到0开始 64 r_ptr <= r_ptr + 1; 65 end 66 end 67 68 endmodule
同步FIFO仿真测试文件
1 `timescale 1ns/1ns 2 3 module sync_fifo_tb; 4 reg i_clk,i_rst; 5 reg i_w_en,i_r_en; 6 reg [7:0] i_data; 7 wire [7:0] o_data; 8 wire o_buf_empty,o_buf_full; 9 10 sync_fifo dut( 11 .i_clk(i_clk), 12 .i_rst(i_rst), 13 .i_data(i_data), 14 .i_w_en(i_w_en), 15 .i_r_en(i_r_en), 16 .o_buf_empty(o_buf_empty), 17 .o_buf_full(o_buf_full), 18 .o_data(o_data) 19 ); 20 21 initial begin 22 #30; 23 forever #10 i_clk = ~i_clk; //时钟 24 end 25 reg [7:0] r_data=8'd0; 26 initial begin 27 i_clk=1'b0; 28 i_rst=1'b0; 29 i_w_en=1'b0; 30 i_r_en=1'b0; 31 i_data=8'd0; 32 #5 i_rst=1'b1; 33 #10 i_rst=1'b0; 34 35 push(1); 36 fork //同时执行push和pop 37 push(2); 38 pop(r_data); 39 join 40 push(3); 41 push(4); 42 push(5); 43 push(6); 44 push(7); 45 push(8); 46 push(9); 47 push(10); 48 push(11); 49 push(12); 50 push(13); 51 push(14); 52 push(15); 53 push(16); 54 push(17); 55 pop(r_data); 56 push(18); 57 pop(r_data); 58 pop(r_data); 59 pop(r_data); 60 pop(r_data); 61 push(19); 62 pop(r_data); 63 push(20); 64 pop(r_data); 65 pop(r_data); 66 pop(r_data); 67 pop(r_data); 68 pop(r_data); 69 pop(r_data); 70 pop(r_data); 71 pop(r_data); 72 pop(r_data); 73 pop(r_data); 74 pop(r_data); 75 push(21); 76 pop(r_data); 77 pop(r_data); 78 pop(r_data); 79 pop(r_data); 80 #100 $stop; 81 end 82 83 task push (input [7:0] data); 84 if(o_buf_full) 85 $display("Cannot push %d: Buffer Full",data); 86 else begin 87 $display("Push",,data); 88 i_data=data; 89 i_w_en=1; 90 @(posedge i_clk) #4 i_w_en= 0; //时钟上升沿后4ns,写使能清零 91 end 92 endtask 93 94 task pop(output[7:0] data); 95 if(o_buf_empty) 96 $display("Cannot Pop: Buffer Empty"); 97 else begin 98 i_r_en=1; 99 @(posedge i_clk) #4 i_r_en= 0; //时钟上升沿4ns后,读使能清零 100 data = o_data; 101 $display("Pop:",,data); 102 end 103 endtask 104 endmodule
采用Modelsim仿真得到如下波形:
可以在Modelsim的View——Transcript窗口看到有如下打印信息:
# run -all # Push 1 # Push 2 # Pop: 1 # Push 3 # Push 4 # Push 5 # Push 6 # Push 7 # Push 8 # Push 9 # Cannot push 10: Buffer Full # Cannot push 11: Buffer Full # Cannot push 12: Buffer Full # Cannot push 13: Buffer Full # Cannot push 14: Buffer Full # Cannot push 15: Buffer Full # Cannot push 16: Buffer Full # Cannot push 17: Buffer Full # Pop: 2 # Push 18 # Pop: 3 # Pop: 4 # Pop: 5 # Pop: 6 # Push 19 # Pop: 7 # Push 20 # Pop: 8 # Pop: 9 # Pop: 18 # Pop: 19 # Pop: 20 # Cannot Pop: Buffer Empty # Cannot Pop: Buffer Empty # Cannot Pop: Buffer Empty # Cannot Pop: Buffer Empty # Cannot Pop: Buffer Empty # Cannot Pop: Buffer Empty # Push 21 # Pop: 21 # Cannot Pop: Buffer Empty # Cannot Pop: Buffer Empty # Cannot Pop: Buffer Empty
六、异步FIFO
虽然各大厂商都有自己的FIFO IP可供调用,但是我们仍然需要学习FIFO的设计原理,这样我们在移植或设计中查找问题起来将有据可循。所以掌握FIFO设计原理是一名合格FPGA工程师的基本功。
下面是异步FIFO的系统框图:
可以看到异步FIFO实质上也是基于中间的双口RAM,外加一些读写控制电路组成的。但是这里读写用的是两个不同的时钟。这将涉及到跨时钟域问题。
1 、亚稳态
同步时钟:
假设数据从0跳变到1,一般数据的跳变不是立马跳变,而是有一个上升时间,有个斜坡。如果是同步时钟采集数据则不会有什么影响。
异步时钟:
如果是异步时钟,第2个时钟比第1个时钟滞后一点点,有可能时钟上升沿正好对应在数据跳变的阶段,那此时读到的数据可能是0, 可能是1, 是个随机态。这就是出现了亚稳态。
亚稳态不可避免, 只能通过一些手段(如 打2拍 以及 格雷码)来降低亚稳态出现的机率。
3、打两拍
2、格雷码
格雷码是一种相邻数据只有1bit变化的码制。
自然二进制码 | 格雷码 |
0000 | 0000 |
0001 | 0001 |
0010 | 0011 |
0011 | 0010 |
0100 | 0110 |
0101 | 0111 |
0110 | 0101 |
0111 | 0100 |
降低概率。
格雷码是二进制码右移1位再与原码相异或的结果。
4、如何判断FIFO的空和满
(1)空判断
(2)满判断
(3)虚空、虚满
虚空虚满不会产生错误, 只是影响FIFO 效率。 理解这些原理后,分析问题就知道去哪里分析。
(4)FIFO的乒乓操作
5、如何选择FIFO深度
6、顶层模块
7、双端口RAM模块
8、同步模块1
9、同步模块2
10、空判断模块
11、满判断模块
七、FIFO IP调用
八、FIFO应用实例 (ADC)
标签:FIFO,掰开,Pop,pop,揉碎,push,data,时钟 From: https://www.cnblogs.com/DoreenLiu/p/17348480.html