【JokerのKintex7325】FIFO。

软件环境: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加一(加减的具体数值以实际情况为准,这里说的加减一只以当前配置而谈)。

  • 4
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值