学习目的:
FIFO(First In First Out)队列通常在数字系统中实现两个模块间数据的暂存,达到减少两模块之间的速度依赖,使得两个模块能够相互相对独立的运行。
FIFO是FPGA设计的一个非常基本的单元。FIFO一般用一个双口RAM和分别指示读写地址的指针,以及状态生成和指示的逻辑组成,下图是典型的FIFO结构图:
我们看到中上部是一个双口RAM,所谓双口RAM是分别有两套读写数据线地址线以及控制线,用以实现读写的同时操作。
再看到读写指针和读写控制界面,左边是写控制界面和写指针,实现对写指针的更新。右边是读控制界面和读指针,实现对读指针的更新。我们还有比较逻辑,这里产生空empty信号和full信号。
我们来看一下存储器的工作方式,实际是一个循环队列:我们来跟踪一下情景:
以下存储器有8个存储单元,在初始化的时候,读指针RP和WP都指向0地址,当向存储器里写入了8个数据后,写指针会从新回到0,这就是循环队列。
这里地址是0-7是3个BIT宽度,WP和RP也要对应的是3个bit,加1就能实现从0,1,2.。6.7.0.1.2…这样循环。下图中阴影的部分标示存储区已经占用。
以上分析了WP和RP的行为,还需要产生空和满信号的逻辑,实现方法我们这里设置一个计数器 FIFO_CNTR,用以表示有效可读数据的个数,初始化的时候是0,标示存储器内没有数据可供读。
当写入接口进行一次有效的写操作(这里规定有效的写操作是FIFO不满的时候实施的写操作)计数器FIFO_CNTR加1,
当读出接口进行一次有效的读操作(这里规定有效的读操作是FIFO不空的时候实施的读操作)计数器FIFO_CNTR减1;
而当同时实施有效的写操作和读操作时,FIFO_CNTER不发生变化(从图上可以分析出实际读指针和写指针都往前走了,他们间距还是一样的,有效可读数据的存储个数还是保持不变)。
另外关于FIFO_CONTER的个数,我们这里看到范围是是0-8,(0时为空,8时为满),因此需要4个bit来定义FIFO_CNTR.因此通常情况下FIFO_CNTR要比地址位数多1位。
至于说到full和empty很简单的由FIFO_COUNTER产生:当且仅当FIFO_COUNTER=0时候,empty=1;当且仅当FIFO_CNTER为最多可容纳存储个数时,这里说FIFO_CNTR = 8时候,full=1;
我们再总结以上的分析,具体细化一下:
1,需要一个buff双口存储器做存储载体,当地址有n位,就有 2 N 2^{N} 2N个存储单元,标号从0到( 2 N − 1 2^{N-1} 2N−1)。三位地址存储地址就是从0到7。
2,需要wr_ptr指针,与地址位数相同,指向下个可以写的地址。
3,需要rd_ptr指针,与地址位数相同,指向下个可以读的地址。
4,需要fifo_cntr计数器,比地址位数多一位,指示当前可读的数据个数。
5,full和empty由fifo_cntr生成。
6,区别读写操作是否有效。
7,实现上述功能。
代码呼之欲出了,我们来写代码,首先是定义端口,之后就开始写代码。写完之后进行检查。
module sc_fifo(
input clk,rst,
input [7:0] din,
input wr,
output full,
output reg [7:0] dout,
input rd,
output empty
);
//1,需要一个buff双口存储器做存储载体,当地址有n位,就有2^N个存储单元,标号从0到(2^N-1)。三位地址存储地址就是从0到7。
reg [7:0] buff[0:7] ;
//2,需要wr_ptr指针,与地址位数相同,指向下个可以写的地址。
reg [2:0] wr_ptr ;
//3,需要rd_ptr指针,与地址位数相同,指向下个可以读的地址。
reg [2:0] rd_ptr ;
//4,需要fifo_cntr计数器,比地址位数多一位,指示当前可读的数据个数。
reg [3:0] fifo_cntr ;
//5,full和empty由fifo_cntr生成。
assign full = fifo_cntr ==8 ;
assign empty= fifo_cntr==0;
//6,区别读写操作是否有效。
wire valid_rd = ~empty & rd ;
wire valid_wr = ~full & wr ;
//7,实现上述功能。
always@(posedge clk)
if (rst)
wr_ptr <= 0;
else if(valid_wr)
wr_ptr<=wr_ptr+1;
always@(posedge clk)
if (rst)
rd_ptr <= 0 ;
else if (valid_rd)
rd_ptr <= rd_ptr+1;
/*
always@(posedge clk) if (rst)fifo_cntr<=0;else
if ((valid_rd==0) &&(valid_wr==1))fifo_cntr<=fifo_cntr+1;
else if ((valid_rd==1)&&(valid_wr==0))fifo_cntr
always@(posedge clk)
casex ({rst,valid_wr,valid_rd})
3'b1xx : fifo_cntr<=0;
3'b010 : fifo_cntr<=fifo_cntr+1;
3'b001 : fifo_cntr<=fifo_cntr-1;
3'b011 ,3'b000 :fifo_cntr<=fifo_cntr ;
endcase
always@(posedge clk)
if (valid_wr)
buff[wr_ptr] <=din ;
always@(posedge clk)
if (valid_rd)
dout<= buff[rd_ptr] ;
endmodule
其实FIFO是也是在RAM的基础上增加了许多功能,FIFO的典型结构如下,主要分为读和写两部分,另外就是状态信号,空和满信号,同时还有数据的数量状态信号,与RAM最大的不同是FIFO没有地址线,不能进行随机地址读取数据,什么是随机读取数据呢,也就是可以任意读取某个地址的数据。而FIFO则不同,不能进行随机读取,这样的好处是不用频繁地控制地址线。
最终将在 AX-7020 FPGA 上实现。
虽然用户看不到地址线,但是在FFO内部还是有地址的操作的,用来控制RAM的读写接口。其地址在读写操作时如下图所示,其中深度值也就是一个FIFO里最大可以存放多少个数 据。初始状态下,读写地址都为0,在向FIFO中写入一个数据后,写地址加1,从FFO中读出 一个数据后,读地址加1。此时FFO的状态即为空,因为写了一个数据,又读出了一个数据。
可以把 FIFO 想象成一个水池,写通道即为加水,读通道即为放水,假如不间断的加水和放水,如果加水速度比放水速度快,那么FIFO就会有满的时候,如果满了还继续加水就会溢出overflow,如果放水速度比加水速度快,那么FIFO就会有空的时候,所以把握好加水与放水的时机和速度,保证水池一直有水是一项很艰巨的任务。也就是判断空与满的状态,择机写数据或读数据。
下面主要介绍异步 FFO 的控制,其中读时钟为 25 MHz,写时钟为 50 MHz。实验中会通过 vivado 集成的在想逻辑分析仪 ila ,我们可以观察FIFO的读写时序。
FIFO IP 核的端口列表如下:
FIFO 的数据写入和读出都是按时钟的上升沿操作的,当 wr_en 信号为高时写入 FIFO 数据,当 almost_full 信号有效时,表示 FIFO 只能再写入一个数据,一旦写入一个数据了,full 信号就会拉高,如果在 full 的情况下 wr_en 仍然有效,也就是继续向 FIFO 写数据,则 FIFO 的 overflow 就会有效,表示溢出。(如下图所示)
当 rd_en 信号为高时读FIFO数据,数据在下个周期有效。valid为数据有效信号,almost_empty表示还有一个数据读,当再读一个数据,empty信号有效,如果继续读,则underflow有效,表示下溢,此时读出的数据无效。(如下图所示)
而从 FWFT 模式读数据时序图可以看出, rden 信号有效时,有效数据D已经在数据线上准备好有效了,不会再延后一个周期。这就是与标准FIFO的不同之处。(如下图所示)
有一点需要注意的是,FFO设置默认为采用 safety circuit,此功能是保证到达内部RAM的输入信号是同步的,在这种情况下,如果异步复位后,则需要等待60个最慢时钟周期。(如下图所示,更详细的还请参考文档)
在有了上面的基础知识后,我们开始对异步 FIFO 进行代码的编写,要实现以下几个功能:
代码如下:
`timescale 1ns / 1ps
module fifo(
input clk,
input rst_n
);
wire div2;
wire wr_clk;
wire rd_clk;
wire [15:0] dout;
assign rd_clk = div2;
assign wr_clk = clk;
reg [6:0] wcn;
reg [6:0] rcn;
reg [15:0] din_reg;
wire wr_en;
wire rd_en;
wire full;
wire empty;
wire [9:0] rd_data_count;
wire [9:0] wr_data_count;
localparam IDLE = 0;
localparam WRITE = 1;
localparam READ = 2;
// write FSM
reg [1:0] write_state;
assign wr_en = (write_state == WRITE) ? ~full : 1'b0;
always @ (posedge clk or negedge rst_n)
begin
if(!rst_n)
begin
write_state <= IDLE;
din_reg <= 16'd0;
end
else
begin
case(write_state)
IDLE:
begin
if(wcn >= 7'd60)
write_state <= WRITE;
else
write_state <= IDLE;
end
WRITE:
begin
if (wr_en)
begin
write_state <= WRITE;
din_reg <= din_reg + 16'd1;
end
else
write_state <= write_state;
end
default:
write_state <= write_state;
endcase
end
end
always @ (posedge clk or negedge rst_n) begin
if (!rst_n)
wcn <= 7'd0;
else if(write_state == IDLE)
wcn <= wcn + 7'd1;
else
wcn <= 7'd0;
end
// read fsm
reg [1:0] read_state;
assign rd_en = (read_state == READ) ? ~empty : 1'b0;
always @ (posedge rd_clk or negedge rst_n) begin
if (!rst_n)
begin
read_state <= IDLE;
end
else
begin
case(read_state)
IDLE:
begin
if (rcn >= 7'd60)
begin
read_state <= READ;
end
else
read_state <= IDLE;
end
READ:
begin
read_state <= READ;
end
default:
read_state <= read_state;
endcase
end
end
always @ (posedge rd_clk or negedge rst_n) begin
if (!rst_n)
rcn <= 7'd0;
else if(read_state == IDLE)
rcn <= rcn + 7'd1;
else
rcn <= 7'd0;
end
div2 diver2(
.clk(clk),
.rst_n(rst_n),
.div2_o(div2)
);
fifo_ip fifo_wr (
.rst(~rst_n), // input wire rst
.wr_clk(wr_clk), // input wire wr_clk
.rd_clk(rd_clk), // input wire rd_clk
.din(din_reg), // 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 [15 : 0] dout
.full(full), // output wire full
.empty(empty), // output wire empty
.rd_data_count(rd_data_count), // output wire [9 : 0] rd_data_count
.wr_data_count(wr_data_count), // output wire [9 : 0] wr_data_count
.wr_rst_busy(), // output wire wr_rst_busy
.rd_rst_busy() // output wire rd_rst_busy
);
ila_0 ila_ip (
.clk(rd_clk), // input wire clk
.probe0(dout) // input wire [15:0] probe0
);
endmodule
// 分频操作
module div2(
input clk ,
input rst_n ,
output div2_o
);
reg div2_o_r ;
assign div2_o = div2_o_r;
always @ (posedge clk or negedge rst_n) begin
if(!rst_n)
div2_o_r <= 0;
else
div2_o_r <= ~div2_o_r;
end
endmodule
仿真文件如下:
module fifo_tb;
reg clk;
reg rst_n;
always
begin
clk = 0;
#10;
clk = 1;
#10;
end
initial
begin
rst_n = 0;
#20;
rst_n = 1;
end
fifo fifo_tb(
.clk(clk),
.rst_n(rst_n)
);
endmodule
另附上约束文件:
set_property PACKAGE_PIN N15 [get_ports rst_n]
set_property PACKAGE_PIN U18 [get_ports clk]
set_property IOSTANDARD LVCMOS33 [get_ports rst_n]
set_property IOSTANDARD LVCMOS33 [get_ports clk]
create_clock -period 20.000 -name clk -waveform {0.000 10.000} [get_ports clk]
set_property C_CLK_INPUT_FREQ_HZ 300000000 [get_debug_cores dbg_hub]
set_property C_ENABLE_CLK_DIVIDER false [get_debug_cores dbg_hub]
set_property C_USER_SCAN_CHAIN 1 [get_debug_cores dbg_hub]
connect_debug_port dbg_hub/clk [get_nets div2]