> 文章列表 > G2—FIFO IP核2023-04-07

G2—FIFO IP核2023-04-07

G2—FIFO IP核2023-04-07

1.简介

FIFO是英文First In First Out 的缩写,是一种先进先出的数据缓存器,fifo在FPGA内部是RAM加上已编写好的读写控制模块(RTL级)然后“封装”在一起的一个module。他与普通存储器的区别是没有外部读写地址线,这样使用起来非常简单,但缺点就是只能顺序写入数据,顺序的读出数据,其数据地址由内部读写指针自动加1完成,不能像普通存储器那样可以由地址线决定读取或写入某个指定的地址。

2.FIFO的用途

FIFO有三个作用,一是作为数据缓存匹配写入和读出端之间的速度,二是跨时钟域同步数据,三是位宽变换。常被用于数据的缓存或者高速异步数据的交互。例如,有一个场景是在 FPGA 的控制下从某设备读取到的一连数据,首先被写入 FIFO 中,然后再以 UART 串口波特率将数据依次发送出去。假设设备的单次读取数据可能很快,但并不是时刻都需要采集数据,例如该设备使用SPI 接口的协议,FPGA 以 2M 的 SPI 数据速率从该传感器中读取 20 个数据,然后以 9600的波特率通过串口发送出去。因为 2M 的数据速率远高于串口 9600 的波特率,因此需要将从设备中采集到的数据首先用 FIFO 缓存起来,然后再以串口的数据速率缓慢发送出去。这就是作为数据缓存发挥的作用,任何数据缓存都可以起到匹配写入和读出速度的作用,同时也都面临数据空读和写入溢出的问题需要解决。

3.创建和使用FIFO IP

1.选择 IP Catalog,输入fifo进行搜索,选择fifo generator。

 2. 包括了fifo ip例化的名字(图中A);接口类型(图中B)。选择Native;选择时钟域,主要选择同步fifo(读写用的是一个时钟)用common,异步fifo用independent(本次配置);存储器类型(图中C),这里主要是block RAM和distribute RAM之间的区别。简而言之,block RAM是FPGA中定制的ram资源,而distribute RAM则是由LUT构成的RAM资源。为了合理利用FPGA的资源,当FIFO较大时应选择block RAM,当FIFO较小时,选择distribute RAM。另外一个很重要的就是block RAM支持读写不同宽度,而distribute不支持。在这里为了更全面的了解FIFO,选择block RAM以拥有非对称方向速率的特性。

3.在标准模式下可以选择勾选,勾选之后dout数据输出口会在原来标准模式的基础上延迟一个时钟输出。本次配置为Fisrt Word Fall Through。

  1. 选择标准fifo或者Frist Word Full Throught模式。二者之间有三点区别:

A1:标准模式是数据延时一个时钟周期进入或者输出;frist word full模式时数据直接随时钟同步进入或者输出,如3.2节所述,即读使能与读总线的时序关系有所不同。

A2:需要注意,选择First Word Fall Through我们定义的数据的深度为128,后面显示的Actual Write Depth(实际写深度却为129)。选择Standard FIFO 我们定义的数据的深度为128,后面显示的Actual Write Depth(实际写深度也为127)。(手册99页)

 

A3:与Standard FIFO相比较First Word Fall Through第一个数据写入空的FIFO时,empty信号的变化增加了两个时钟周期的延迟。

B、设置写fifo的数据位宽,设置写fifo的数据位深,单位是write width,如图fifo大小为16*128bit。设置输出数据位宽、读fifo深度会根据fifo的大小自动生成。

C、在标准模式下,勾选寄存器会增加读使能信号与数据信号之间的延迟,First模式不起作用。

 

 D、是否勾选复位管脚,同步复位信号,以及使能保护电路。选择复位后fifo_full的状态是1还是0,若设置为1在FIFO复位结束后,经历了一定的周期Full信号才会被拉低(手册131)。选择复位后数据总线上的值是多少?不勾选为先前的数据,勾选则可配置。

重要说明:rst表示的是复位信号,高电平有效;fifo的空满信号将自动生成,但不是可以做计数的准确值,当选择异步复位的时候,如图所示,有一个enable safety circuit信号,这个勾选后会生成如第二个图所示的信号,这个信号的作用是,当fifo复位之后,异步复位至少持续3个时钟周期,才能给wr_en写使能信号,这时写入数据才会有效。当有了这个信号之后,可以监听wr_rst_busy的信号,当为低的时候,表明复位过程结束,可以给wr_en信号进行fifo的写入操作。这样做避免了在复位过程中将写信号拉高(wr_en)会导致empty信号不正常,表现为在wr_en为高的时候,empty也仍然为高,此外,计数信号count也随之失效。

4. A代表的即将满和将空信号,在空或满的前一个周期将此信号拉高,用于做精准的流控制BC、分别代表应答,溢出,空读情况下的一些信号,overflow上溢出标识信号,表示 FIFO 数据满情况下继续往 FIFO 里写数据,该信号会拉高标志向上溢出。 underflow:下溢出标识信号,表示 FIFO 数据空情况下继续对 FIFO 进行读操作,该信号会拉高标志向下溢出。D、E、分别代表可编程的信号,选择信号可编程满、空阈值、可以自主设置满、空标志位的阈值,保证fifio运行的可靠性,尽量多留些阈值,此处选择no programmable full threshold。

5. 这是fifo读写操作过程中的计数信号,需要注意的是,对于同步fifo,相同位宽的写入读出,read_count与write_count是一样的大小;对于异步fifo,他们要等到各自时钟的上升沿来的时候才会刷新这个信号;并且是读和写计数分别按照各自的位宽为单位的,例如写入位宽为16,write_count计数为32,代表此时fifo中的数据量为 16*32bit数据。另外需要注意的是,计数信号的位宽承载的数值一定要大于fifo的大小,否则会导致计数不正常。

6.资源使用情况,表格最后一行写的是read latency指的是读使能与读时序之间的关系。 

4.仿真代码

仿真代码如下,先进行写操作,单次写和连续写,随后进行读操作也分为单次读和连续读。

1.fifo-top文件

`timescale 1ns / 1ps
module fifo_top(input sys_clk,input rst_n,output done);wire clk_50;
wire clk_100;
wire clk_rst;
reg [15:0]wr_data;
reg wr_en;
reg rd_en;
wire [7:0]dout;
wire full;
wire almost_full;
wire wr_ack;
wire overflow;
wire empty;
wire almost_empty;
wire valid;
wire underflow;
wire [7:0]rd_data_count;
wire [6:0]wr_data_count;
wire  wr_rst_busy;
wire  rd_rst_busy;
assign done = rd_s==4;
wire wr_done;
assign wr_done = state==3;
clk_wiz_0 u1_clk(.clk_out1(clk_50),.clk_out2(clk_100),.reset(!rst_n),.locked(clk_rst),.clk_in1(sys_clk));  
fifo_generator_0 your_instance_name (.rst(!clk_rst),                      // input wire rst.wr_clk(clk_50),                // input wire wr_clk.rd_clk(clk_100),                // input wire rd_clk.din(wr_data),                      // input wire [15 : 0] din.wr_en(wr_en),                  // input wire wr_en.rd_en(rd_en),                  // input wire rd_en.dout(dout),                    // output wire [7 : 0] dout.full(full),                    // output wire full.almost_full(almost_full),      // output wire almost_full.wr_ack(wr_ack),                // output wire wr_ack.overflow(overflow),            // output wire overflow.empty(empty),                  // output wire empty.almost_empty(almost_empty),    // output wire almost_empty.valid(valid),                  // output wire valid.underflow(underflow),          // output wire underflow.rd_data_count(rd_data_count),  // output wire [7 : 0] rd_data_count.wr_data_count(wr_data_count),  // output wire [6 : 0] wr_data_count.wr_rst_busy(wr_rst_busy),      // output wire wr_rst_busy.rd_rst_busy(rd_rst_busy)      // output wire rd_rst_busy
);
// wire rst_n;
reg [1:0]state;
reg [3:0]dl_cnt;
reg [6:0]wr_cnt;
//写入过程
always @(posedge clk_50,negedge rst_n) beginif(!rst_n)beginstate <= 0;wr_en <= 0;wr_data<=0;dl_cnt <= 0;wr_cnt <= 0;end else begincase (state)0:begin //单个�?if(!wr_rst_busy)beginstate <= state + 1;wr_en <= 1;wr_data <= 10;end  end 1:begin //空闲wr_en <= 0;wr_data <= 0;dl_cnt <= dl_cnt + 1;if(dl_cnt==4'b1111)begindl_cnt <= 0;state <= 6; endend2:begin // 连续�?wr_cnt <= wr_cnt + 1;wr_en <= 1;wr_data <= wr_cnt;if(wr_cnt==7'b1111111)beginwr_en <= 0;wr_data <= 0;wr_cnt <= 0;state <= state + 1;endend3:begin //等待state <= state;  enddefault: ;endcaseend
end
//读出过程
reg [2:0]rd_s;
reg [3:0]w_cnt;
reg [7:0]rd_cnt;
always @(posedge clk_100,negedge rst_n) beginif(!rst_n)beginrd_s<= 0;w_cnt <=0;rd_en <= 0;rd_cnt <= 0;end else begincase (rd_s)0:beginif(!rd_rst_busy && wr_done)beginrd_s <=  rd_s + 1;endend 1:beginrd_en <= 1;rd_s <=  rd_s + 1;end2:beginrd_en <= 0;w_cnt <= w_cnt + 1;if(w_cnt == 4'b1111)beginw_cnt <= 0;rd_s <= rd_s + 1;endend3:beginrd_en <= 1;rd_cnt <= rd_cnt + 1;if(rd_cnt == 8'b11111111)beginrd_cnt <= 0;rd_s <= rd_s + 1;end end4:beginrd_s <= rd_s ;enddefault: ;endcaseend
end
endmodule

2.Sim_Fifo文件

`timescale 1ns / 1ps
module Sim_Fifo();
reg clk;
reg rst_n;
wire done;
always #10 beginclk = ~clk;
end
initial beginclk = 0;rst_n = 0;#2000#1rst_n = 1;@(posedge done);#2000rst_n = 0;#1000$stop;
end
fifo_top sub_fifo(.sys_clk(clk),.rst_n(rst_n),.done(done));
endmodule

 

5.仿真结果分析

使用第三节的ip配置以及第四节的仿真代码进行仿真。

①在复位期间,full是处于高电平状态,这是ip核可配置的。需要说明的是,fifo的复位信号应该至少保持3个时钟周期,此处时钟指读和写中较慢的那一个。

②在复位期间,busy信号处于高电平,这是ip配置勾选Safety Circuit出来的两个信号,此时不能进行任何操作,只有在busy信号为低时才可以操作。

③此处wr_rst_busy信号拉低,状态机开始工作。

④拉高wr_en一个周期,进行单次写,写入数据为10。以下截图出自fifo手册,所有写相关的信号应该同步于写时钟信号。

⑤wr_ack是写操作响应信号,当向 FIFO 写入数据时,如果正确写入到 FIFO 了,会输出

高电平表示数据已成功写入。在数据要求严格的场合,可以用此信号计数判断写入值。

⑥写入第一个数据后,empty信号的反应是滞后的。

⑦valid信号是读数据输出数据有效信号,在进行读操作时,valid 信号伴随数据输出而拉高,高电平表示输出数据有效,用户逻辑可以根据该信号对读出的数据做后续进一步的处理使用。此处可以看到在FWMT模式下,该信号指示不准确。

⑧rd_fifo_count与wr_fifo_count是计数信号,具体在第4节ip配置中介绍,这个计数也会有不确定性,可能会出错。图片中写入一个数据后,计数先正常在未读取的情况下后变为0。

①可以看到,写入了127个数,加上之前单次写的数据,一共写入了128个数,在127个数的时候almost_full拉高,128个数是full拉高。在写入操作的时候,full是准确的,empty是有延迟的。 

关注rd_en信号,此处单次读操作后full信号并没有拉低。另外读出的数据是0,第二个数据10已经在数据线上了,这就是FWMT模式。另外使用Xilinx FIFO进行位宽转换需要注意的问题:高位宽转换为低位宽:高位先输出,例:32bit转为8bit :wr_data = 0x01020304输出顺序为:rd_data0 =0x01,rd_data1 =0x02,低位宽转高位宽:先进的在高位,例:8bit转32bit,写入顺序为:wr_data0 =0x01,wr_data1 =0x02,读出顺序:rd_data = 0x01020304。总之是高位数据可以写入。

①当数据全部读出后,仍然保持rd_en高电平,会发生空读,overflow信号将拉高。

②empty信号和almost_empty信号会按时拉低在读出过程时,full信号会有延迟。结合上一副图的仿真结论是,对于fifo型的设备避免写溢出和空读现象发生才能避免数据出错,是很重要的一步,因此xilinx根据empty(及almost_empty)信号准确指示用户停止读。根据full及almost_full信号准确指示用户停止写。

③计数信号的变化是有延迟的。