bright1224的个人空间 https://blog.eetop.cn/116762 [收藏] [复制] [分享] [RSS]

空间首页 动态 记录 日志 相册 主题 分享 留言板 个人资料

日志

异步FIFO设计

已有 1249 次阅读| 2015-11-9 16:05 |个人分类:常见电路设计

fifo是跨时钟域的典型应用,其设计涉不少需要注意的事项,趁着找工作之际,好好研究一下。

 

首先赶紧记录几个容易遗忘的地方:

1.       读写指针用什么形式编码?二进制还是格雷码? 答:要用二进制,因为读写memory地址还是要二进制编码。

2.       读写指针同步到对方的clock domain为什么要用格雷码?答:因为格雷码特点是每次只有一bit变化,这样可以采对,否则二进制若有两位变化引起毛刺可能会被同步到,引起地址错误。

3.       怎样判断满full和空empty标志? 方法一:Full=读指针等于写指针并且上一次写指针加1即等于读指针;方法二:多扩展一位地址位,如果读写指针最高位不等并且低位相等,那么就是满标志,如果地址完全相等,那么就是空标志;

 

FIFORTL代码

下面是我写的一种比较通用的异步FIFO,参数化数据位宽和地址位宽(这里没有列出TB和格雷码二进制相互转换电路代码)。VCS仿真过,见附件波形。


`timescale 1ns/1ps

module async_fifo(

clk_wr,rst_wr,wr_en,wr_data,

clk_rd,rst_rd,rd_en,

wr_ptr,rd_ptr,

rd_data,full,empty);

 

 parameter DATA_WIDTH=8;

 parameter FIFO_DEPTH=8;

 parameter ADDR_WIDTH=3;

 

input                              clk_wr;

input                              rst_wr;

input                              wr_en;

input  [DATA_WIDTH-1:0]  wr_data;

input                              clk_rd;

input                              rst_rd;

input                              rd_en;

output  logic [ADDR_WIDTH-1:0] wr_ptr;

output  logic [ADDR_WIDTH-1:0] rd_ptr;

output  logic [DATA_WIDTH-1:0] rd_data;

output  logic                      full;

output  logic                      empty;

 

//memory

logic [DATA_WIDTH-1:0] fifo_mem [FIFO_DEPTH-1:0];

 

 

logic  [ADDR_WIDTH-1:0] rd_ptr_gray_r,wr_ptr_gray_r; //read/write pointer gray code register out

//write clock domain begin

logic  [ADDR_WIDTH-1:0] wr_ptr_next;

logic [ADDR_WIDTH-1:0] wr_ptr_next_gray;

 

always_ff @(posedge clk_wr or negedge rst_wr)

if(~rst_wr)

begin

wr_ptr<={ADDR_WIDTH{1'b0}};

wr_ptr_gray_r<={ADDR_WIDTH{1'b0}};

end

else begin

wr_ptr<=wr_ptr_next;

wr_ptr_gray_r<=wr_ptr_next_gray;

end

 

always_comb

wr_ptr_next=wr_en ? wr_ptr+1'b1 : wr_ptr;

 

logic [ADDR_WIDTH-1:0] wr_ptr_next_gray;

/*bin2gray AUTO_TEMPLATE (

                         .gray                  (wr_ptr_next_gray[ADDR_WIDTH-1:0]),

                         .bin                   (wr_ptr_next[ADDR_WIDTH-1:0]),

);

*/

bin2gray #(

.WDITH(ADDR_WIDTH)

)

bin2gray_wr_ptr(/*AUTOINST*/

                // Outputs

                .gray                   (wr_ptr_next_gray[ADDR_WIDTH-1:0]), // Templated

                // Inputs

                .bin                    (wr_ptr_next[ADDR_WIDTH-1:0])); // Templated

 

 

always_ff @(posedge clk_wr)

if(wr_en&!full)

fifo_mem[wr_ptr]<=wr_data;

 

logic [ADDR_WIDTH-1:0] rd_ptr_sync_d1,rd_ptr_sync_d2;

always_ff @(posedge clk_wr or negedge rst_wr)

if(~rst_wr) begin

rd_ptr_sync_d1<={ADDR_WIDTH{1'b0}};

rd_ptr_sync_d2<={ADDR_WIDTH{1'b0}};

end

else begin

rd_ptr_sync_d1<=rd_ptr_gray_r;

rd_ptr_sync_d2<=rd_ptr_sync_d1;

end

 

logic [ADDR_WIDTH-1:0]  rd_ptr_wr_side;

/*gray2bin AUTO_TEMPLATE(

                         .bin                     (rd_ptr_wr_side[ADDR_WIDTH-1:0]),

                         .gray                     (rd_ptr_sync_d2[ADDR_WIDTH-1:0]),

                         );

*/

gray2bin #(ADDR_WIDTH) gray2bin_wr_ptr(/*AUTOINST*/

                                       // Outputs

                                       .bin             (rd_ptr_wr_side[ADDR_WIDTH-1:0]), // Templated

                                       // Inputs

                                       .gray            (rd_ptr_sync_d2[ADDR_WIDTH-1:0])); // Templated

 

logic fifo_nearly_full;

always_ff @(posedge clk_wr or negedge rst_wr)

if(~rst_wr)

fifo_nearly_full<=1'b0;

else if((wr_ptr_next==rd_ptr_wr_side)&wr_en&!full)

fifo_nearly_full<=1'b1;

else if(wr_ptr!=rd_ptr_sync_d2)

fifo_nearly_full<=1'b0;

 

assign full=(wr_ptr==rd_ptr_wr_side)&fifo_nearly_full;

 

 

 

//write clock domain end

 

//read clock domain begin

logic [ADDR_WIDTH-1:0] rd_ptr_next_gray;

logic [ADDR_WIDTH-1:0] rd_ptr_next;

 

always_ff @(posedge clk_rd or negedge rst_rd)

if(~rst_rd) begin

rd_ptr<={ADDR_WIDTH{1'b0}};

rd_ptr_gray_r<={ADDR_WIDTH{1'b0}};

end

else begin

rd_ptr<=rd_ptr_next;

rd_ptr_gray_r<=rd_ptr_next_gray;

end

 

always_comb

rd_ptr_next=rd_en ? rd_ptr+1'b1 : rd_ptr;

 

 

/*bin2gray AUTO_TEMPLATE (

                         .gray                  (rd_ptr_next_gray[ADDR_WIDTH-1:0]),

                         .bin                   (rd_ptr_next[ADDR_WIDTH-1:0]),

);

*/

 

bin2gray #(ADDR_WIDTH) bin2gray_rd_ptr(/*AUTOINST*/

                                       // Outputs

                                       .gray            (rd_ptr_next_gray[ADDR_WIDTH-1:0]), // Templated

                                       // Inputs

                                       .bin             (rd_ptr_next[ADDR_WIDTH-1:0])); // Templated

 

 

 

 

 

always_ff @(posedge clk_rd)

if(rd_en&!empty)

rd_data<=fifo_mem[rd_ptr];

 

 

 

logic [ADDR_WIDTH-1:0] wr_ptr_sync_d1,wr_ptr_sync_d2;

always_ff @(posedge clk_rd or negedge rst_rd)

if(~rst_rd) begin

wr_ptr_sync_d1<={ADDR_WIDTH{1'b0}};

wr_ptr_sync_d2<={ADDR_WIDTH{1'b0}};

end

else begin

wr_ptr_sync_d1<=wr_ptr_gray_r;

wr_ptr_sync_d2<=wr_ptr_sync_d1;

end

logic [ADDR_WIDTH-1:0] wr_ptr_rd_side;

/*gray2bin AUTO_TEMPLATE(

                         .bin                     (wr_ptr_rd_side[ADDR_WIDTH-1:0]),

                         .gray                     (wr_ptr_sync_d2[ADDR_WIDTH-1:0]),

                         );

*/

gray2bin #(ADDR_WIDTH) gray2bin_rd_ptr(/*AUTOINST*/

                                       // Outputs

                                       .bin             (wr_ptr_rd_side[ADDR_WIDTH-1:0]), // Templated

                                       // Inputs

                                       .gray            (wr_ptr_sync_d2[ADDR_WIDTH-1:0])); // Templated

 

 

logic fifo_nearly_empty;

always_ff @(posedge clk_rd or negedge rst_rd)

if(~rst_rd)

fifo_nearly_empty<=1'b0;

else if((rd_ptr_next==wr_ptr_rd_side)&rd_en&!empty)

fifo_nearly_empty<=1'b1;

else if(rd_ptr!=wr_ptr_rd_side)

fifo_nearly_empty<=1'b0;

 

assign empty=(rd_ptr==wr_ptr_rd_side)&fifo_nearly_empty;

 

 

//read clock domain end

 

 

 

endmodule

 

 

关于FIFO的深度计算

首先,一定要理解清楚FIFO的应用场景,这个会直接关系到FIFO深度的计算,如果是面试官抛出的问题,那么有不清楚的地方,就应该进行询问。如果是笔试或者工程中需要计算FIFO深度的话,那么就需要自己考虑清楚。

其次,异步FIFO,读写时钟不同频,那么FIFO主要用于数据缓存,我们选择的FIFO深度应该能够保证在最极端的情况下,仍然不会溢出。因此考虑的前提一般都是写时钟频率大于读时钟频率,但是若写操作是连续的数据流,那么再大的FIFO都无法保证数据不溢出。因此可以认为这种情况下写数据的传输是“突发Burst”的,即写操作并不连续,设计者需要根据满标志控制或者自己来控制写操作的起止。

宏观地,从整个时间域上看,"写数据=读数据",这个条件必须要满足,如果这个大条件不满足的话,用FIFO是没有效果的。但是在发送方"突发"发送数据的时间T内,是很有可能写数据>读数据的,因此FIFO的深度要能够保证,在这段时间T内,如果接收方未能将发送方发送的数据接收完毕的话,剩下的数据都是可以存储在FIFO内部而且不会溢出的,那么在发送方停止发送数据的"空闲时隙"内,接收方可以从容地接收剩下来的数据。

 

红字部分就是个人认为在FIFO深度计算中,最重要的部分了。接着来看一个例子,这是我看一个网友写时,是他当时遇到的一道笔试题。

一个8bit宽的AFIFO,输入时钟为100MHz,输出时钟为95MHz,设一个package4Kbit,且两个package之间的发送间距足够大。问AFIFO的深度。

因为这位网友可能只是简述,因此信息并不完整,我的个人理解是这样的场景,一个异步FIFO,读写频率不同,读写位宽相同。发送发一次Burst突发的数据量为4Kbit,即500Word,在两次Burst突发之间有足够的时间,因此我们只用考虑在发送方Burst发送数据的时间T内,如果接受方没法将数据全部接受,其余数据均可存在FIFO内且不溢出,那么在发送方停止Burst发送数据的时间段内,接收方就可以从容的从FIFO内读取数据。首先发送方Burst发送数据的时间段为 T = 500/100MHz,发送的数据量为 B_send = 500word,而在T这段时间内,接收方能够接受的数据量为B_rec =  T*95MHz = 500 * 95 / 100 word = 475word,因此 B_remain = B_send - B_rec = 500 - 475 = 25 。那么FIFO的深度至少要大于等于25才行。

 

再看另外一个例子,还是从网上找到的,

写时钟频率w_clk,
读时钟频率r_clk,
写时钟周期里,每B个时钟周期会有A个数据写入FIFO
读时钟周期里,每Y个时钟周期会有X个数据读出FIFO
则,FIFO的最小深度是?

 

首先,我们可以认为写操作是Burst突发的。

其次,写操作的效率并不是100%的,而是A/B的,因此我们可以认为实际的F_wr = (A/B)*w_clk,同理,实际中F_rd = (X/Y)*r_clk

另外,和第一个例子不同的是,这个题目里面并没有约束Burst突发的场景,在正常情况下,应该是这样的

空闲---Burst突发---空闲---Burst突发---空闲---Burst突发。但是我们在计算中,需要考虑最极端的情况,即

空闲---Burst突发---Burst突发---空闲---Burst突发---空闲。即传输过程中,可能会出现"背靠背"的情况,那么我们设计的FIFO深度必须能够保正,在"背靠背"的时间段内,如果接收方没法接受所有数据,那么剩余的数据可以被存储在FIFO内部且不会溢出。那么就可以开始计算了。假设"背靠背"时发送的数据 = BL,那么"背靠背"的时间 =  BL / w_clk ,注意,这段时间内 F_wr = w_clk  而不是之前提到的 (A/B)*w_clk。在这段时间内,接收方可以接受的数据 = (BL / w_clk)  * (X/Y)*r_clk

剩下的数据量 = BL -  ( BL / w_clk ) * (X/Y)*r_clk,那么FIFO的深度至少就要为

" depth =  BL -  ( BL / w_clk ) * (X/Y)*r_clk "这样的深度了。

将上述公式变换下,得到 depth = BL - BL  * (X/Y) * (r_clk/w_clk) 。这个公式就是网上流传的计算FIFO深度的公式,我想应该就是这个推理过程吧。

 

上述的讨论的一个前提就是FIFO的读写位宽一致,如果这个条件不满足的话,那么FIFO的深度的计算就更加复杂一些,但是我们还是可以把FIFO的读写位宽也折合成一定的因子,带入 实际的F_wr = (A/B)*w_clk F_rd = (X/Y)*r_clk 中去,应该是是可以解决的。

 

 

 

一、异步FIFO最小深度计算原理

      如果数据流连续不断则FIFO深度无论多少,只要读写时钟不同源同频则都会丢数;
FIFO
用于缓冲块数据流,一般用在写快读慢时,遵循的规则如下:


{FIFO深度 /(写入速率 - 读出速率)} = {FIFO被填满时间}  >  {数据包传送时间}= {写入数据量 / 写入速率}

即:确保对FIFO写数据时不存在overflow,FIFO读出数据时不存在underflow.

     例:A/D采样率50MHzdspA/D读的速率40MHz,要不丢失地将10万个采样数据送入DSP,在A/D在和DSP之间至少加多大容量(深度)的FIFO才行?
100,000 / 50MHz = 1/500 s = 2ms
(50MHz - 40MHz)
 * 1/500 = 20k既是FIFO深度。

一种错误的算法(我也犯了同样的错误):
100,000 / 40MHZ= 1/400s = 2.50ms
(50M - 400M)*1/400 =25K.
那么这样进去的数据就不是100K了,而是100K+50M*0.0025-0.002=125000bit,错误在时间的计算。

 

二、异步FIFO最小深度常用计算公式(假如读写FIFO是同时进行的)

写时钟频率w_clk,
读时钟频率 r_clk,
写时钟周期里,每B个时钟周期会有A个数据写入FIFO
读时钟周期里,每Y个时钟周期会有X个数据读出FIFO
则,FIFO的最小深度是?

计算公式如下:

fifo_depth = burst_length - burst_length * X/Y * r_clk/w_clk

此公式可有原理推导而来。

例举说明:

     如果100个写时钟周期可以写入80个数据,10个读时钟可以读出8个数据。令wclkrclk


点赞

评论 (0 个评论)

facelist

您需要登录后才可以评论 登录 | 注册

  • 关注TA
  • 加好友
  • 联系TA
  • 0

    周排名
  • 0

    月排名
  • 0

    总排名
  • 0

    关注
  • 1

    粉丝
  • 3

    好友
  • 0

    获赞
  • 6

    评论
  • 470

    访问数
关闭

站长推荐 上一条 /2 下一条


小黑屋| 手机版| 关于我们| 联系我们| 在线咨询| 隐私声明| EETOP 创芯网
( 京ICP备:10050787号 京公网安备:11010502037710 )

GMT+8, 2024-12-25 05:21 , Processed in 0.015918 second(s), 7 queries , Gzip On, Redis On.

eetop公众号 创芯大讲堂 创芯人才网
返回顶部