软件环境:vivado 2017.4 硬件平台:XC7K325
FIFO那可真是太常用了,不论是跨时钟域,或者是宽度转换。官方手册PG057,想细研究的话可以直接搜,或者懒得看英文,您就接着我这篇往下看。
可以看到,读和写都有各自的域,独立的clk和rst信号,余下主要关心的信号见下表。
在时序方面,首先要注意的就是复位,对于配置时不勾选安全电路的情况。
复位需保持至少三个最慢时钟的时钟周期,且最好复位结束后三十个最慢时钟周期内,不要去操作读和写的使能,此时FIFO处于亚稳态。如果配置时勾选使能安全电路,复位计数后六十个最慢时钟周期内,最好不要去操作读和写的使能。
标准FIFO写时序中,随着wr_en写使能的拉高,写入的数据din有效,直到写到full信号拉高,表示此时FIFO满,almost_full信号会在full前一个数据拉高,表示FIFO中仅能再写入一个数据了,wr_ack会在写入数据完毕后一拍拉高,指示写入成功。
标准FIFO读时序中,在rd_en读使能拉高一拍后,FIFO出数据,同时valid为高,表示当前数据有效,当数据全部输出完毕时,empty拉高,表示当前FIFO空了,同样,almost_empty表示FIFO将空,会在FIFO读出最后一个数据前拉高。
下面说下FIFO配置相关,第一页,选择基于block ram的独立时钟模式。
第二页中,有两种模式,一种是前面提到的标准FIFO,另外还有一种first word fall trough,至于两者之间的差别,后面在仿真的时候会说;接下来就是配置写数据的宽度和FIFO深度,读数据宽度被指定后,对应的深度会自动计算。
第三个的信号可选,我这里基本都够了,是为了后面仿真时候看时序方便,这个可以根据个人使用FIFO习惯勾选。
第四页使能了两个计数器,因为深度为512所以计数器宽度9位。
都设置好以后, 可以看到第五页 summary中,读和写的实际深度是511,这点需要注意,如果后面程序按512写入和读出,肯定会出问题。
接下来是仿真分析,例化代码如下。
module fifo_test(
input clk_sys,
input rst_n
);
reg [15:0] w_data;
wire wr_en;
wire rd_en;
wire [15:0] r_data;
wire full;
wire empty;
wire wr_ack;
wire valid;
wire [8:0] rd_data_count;
wire [8:0] wr_data_count;
wire almost_full;
wire almost_empty;
wire clk_50M;
wire clk_25M;
wire locked;
wire fifo_rst_n;
wire wr_clk;
wire rd_clk;
reg [7:0] wcnt;
reg [7:0] rcnt;
clk_wiz_0 pll_clk
(
.clk_out1(clk_50M), // output clk_50M
.clk_out2(clk_25M), // output clk_25M
.reset(rst_n), // input reset
.locked(locked), // output locked
.clk_in1(clk_sys) // input clk_in1
);
assign fifo_rst_n=locked;
assign wr_clk=clk_50M;
assign rd_clk=clk_25M;
localparam W_IDLE=2'b01;
localparam W_FIFO=2'b10;
reg [2:0] write_state;
reg [2:0] next_write_state;
always@(posedge wr_clk or negedge fifo_rst_n)
begin
if(!fifo_rst_n)
write_state<=W_IDLE;
else
write_state<=next_write_state;
end
always@(*)
begin
case(write_state)
W_IDLE:
begin
if(wcnt==8'd120) //快时钟复位120拍
next_write_state<=W_FIFO;
else
next_write_state<=W_IDLE;
end
W_FIFO:
next_write_state<=W_FIFO;
default:
next_write_state<=W_IDLE;
endcase
end
always@(posedge wr_clk or negedge fifo_rst_n)
begin
if(!fifo_rst_n)
wcnt<=8'd0;
else if (write_state==W_IDLE)
wcnt<=wcnt+1'b1;
else
wcnt<=8'd0;
end
//如果FIFO不满且没有在读取,就继续向FIFO中写数据
assign wr_en=(full==0 &&rd_en==0)? 1'b1:1'b0;
always@(posedge wr_clk or negedge fifo_rst_n)
begin
if(!fifo_rst_n)
w_data<=16'd0;
else if(wr_en)
w_data<=w_data+1'b1;
end
reg [2:0] read_state;
reg [2:0] next_read_state;
//读数据时,FIFO状态机
localparam R_IDLE=2'b01;
localparam R_FIFO=2'b10;
always@(posedge rd_clk or negedge fifo_rst_n)
begin
if(!fifo_rst_n)
read_state<=R_IDLE;
else
read_state<=next_read_state;
end
always@(*)
begin
case(read_state)
R_IDLE:
begin
if(rcnt==8'd60) //慢时钟复位60拍
next_read_state<=R_FIFO;
else
next_read_state<=R_IDLE;
end
R_FIFO:
next_read_state<=R_FIFO;
default:
next_read_state<=R_IDLE;
endcase
end
always@(posedge rd_clk or negedge fifo_rst_n)
begin
if(!fifo_rst_n)
rcnt<=8'd0;
else if (read_state==R_IDLE)
rcnt<=rcnt+1'b1;
else
rcnt<=8'd0;
end
//如果FIFO不空且没有在写,就从FIFO中读数据
assign rd_en=(empty==0&&wr_en==0)? 1'b1:1'b0;
fifo_generator_0 FIFO_init (
.wr_clk(wr_clk), // input wire wr_clk
.rd_clk(rd_clk), // input wire rd_clk
.din(w_data), // input wire [15 : 0] din
.wr_en(wr_en), // input wire wr_en
.rd_en(rd_en), // input wire rd_en
.dout(r_data), // output wire [15 : 0] dout
.full(full), // output wire full
.empty(empty), // output wire empty
.wr_ack(wr_ack), // output wire wr_ack
.valid(valid), // output wire valid
.rd_data_count(rd_data_count), // output wire [8 : 0] rd_data_count
.wr_data_count(wr_data_count), // output wire [8 : 0] wr_data_count
.almost_full(almost_full), // output wire almost_full
.almost_empty(almost_empty) // output wire almost_empty
);
endmodule
先将FIFO配置为标准FIFO,可以看到复位结束后,almost_full、full、almost_empty、empty仍保持高一段时间,当full信号输出低以后,wr_en写使能立马拉高,开始往FIFO中写数,写进去的第一个数应该是0,而后一个周期,wr_ack拉高,表示0写入成功。
接下来看一下写满时候的,可以看到,在almost_full拉高时候,只能再写入一个01fe,full信号就拉高了,FIFO就写满了,而从0~01fe正好511个数,写满以后,wr_en转而拉低;当wr_en拉低,且非空时候,rd_en读信号拉高,FIFO开始读取,可以看到,下一个rd_clk上升沿时候,valid拉高,此时读到的第一个数,正好是第一个写入的0。
读空时候稍微要注意一下,可以看到,当FIFO还有一个数就读空时候,almost_empty拉高了,一个周期后,empty信号线也就拉高了,此时rd_en读使能拉低,但是但是!!!valid此时仍然是高,意味着读取仍然有效,正好读出的是最后一个写入的01fe,也就是这里需要特别注意一下。
综合以上,对于标准FIFO,写的时候,利用wr_en和din直接写,注意着full信号就行,读的时候,一方面控制rd_en并检测empty读,实际利用数据时候,可以通过valid和dout。
接下来改一下FIFO配置,第二页更改为first word fall trough,可以看到,更改以后最后一页summary显示,FIFO的实际大小为513,至于其他差别,继续往下看。
例化及仿真文件不改动,直接仿真, 可以看到,对于复位结束以后开始写入FIFO的过程,与标准FIFO差别不大,但是,还是能看出来两点问题,一方面可以看到,wr_data_count计数,在某两个连续周期内增加的值不对,另一方面,如图中黄线标识,此时即便rd_en读取使能信号没有拉高,但是读取的valid信号已经置高。
在看写满的时候,由于深度实际为513,所以最后一个写入的数据可以到0200,再看读取FIFO时,由于valid提前拉高了,所以当rd_en拉高时,可以直接从dout上立即拿到数,而不像标准FIFO,需要等rd_en拉高一周期后,读数才有效,所以这里,由于rd_en基本是在wr_en拉低时立即拉高的,与rd_clk上升沿没对齐,造成读取时会丢了第一个数,这里是有问题的,读取FIFO代码段需重新设计,这里只是为了说明这个现象,代码先不做修改。
对于FIFO读空的时候可以看到,rd_en和valid是同时在empty拉高时候拉低的,valid拉低代表着读取结束,最后一个读到的数据时0200,也可以看到,重新写入后没过多久,valid信号又重新拉高了。
所以记得,wr_data_count和rd_data_count这两个FIFO读写计数值,当个参照就行了,实际使用时候是不一定准确的,对于 first word fall trough模式,valid信号会提前拉高,所以rd_en拉高时刻,就能直接在dout读到第一个数。FIFO实际深度,两个模式也是有区别的,标准的减一,first word加一(加减的具体数值以实际情况为准,这里说的加减一只以当前配置而谈)。