非同期FIFOの作り方

[] [] []


同期FIFOにならって、ライトとリードのクロックが独立である非同期FIFOを作る。

FPGAのデュアルポートメモリは各ポートを独立なクロックで動かせるので、1ワードをライトする毎にライトアドレス(wadr)をカウントアップし、1ワードをリードする毎にリードアドレス(radr)をカウントアップすればよい。

同期FIFOと違うのは、ライトとリードが非同期だと1個のカウンタでFIFOに入っているワード数を数えられないことである。そこで、リード側にライトアドレスの値を転送して読み出せるデータがあるかを判定し、ライト側にリードアドレスの値を転送してデータを書き込む余地があるかを判定する。

リード側で読み出せるデータがあるかを判定する方法として一番単純なのは空(empty)であるかを見ることで、wadr[WA-1:0]==radr[WA-1:0]のときFIFOは空(empty)である。しかし、wadrが1周してradrに追いついてwadr[WA-1:0]==radr[WA-1:0]になったときはFIFOはフル(full)である。emptyfullを区別するためには1周の差がわかるようにアドレスのピット幅WA1ビット増やさなければならない。すなわち

 リード側でFIFOが空(empty)である条件:wadr[WA:0]==radr[WA:0]

 ライト側でFIFOがフル(full)である条件:wadr[WA:0]=={~radr[WA],radr[WA-1:0]}

さて、アドレスを非同期クロック間転送するのにグレイコードに変換すればダブルFFシンクロナイザで転送できる。バイナリコードのアドレスと区別するために、アドレスをグレイコードに変換したものをライトポインタ(wptr)、リードポインタ(rptr)と呼ぶことにする。

項で見たようにグレイコードからバイナリコードへ戻す変換はビット幅に比例して遅くなるので、転送したグレイコードのままでemptyfullを判定することにする。このときfullの判定は注意が必要で、グレイコードの並びをよく見ると(→グレイコードカウンタ)、最上位ビットptr[WA]=0のときとptr[WA]=1のときとで、下位のptr[WA-2:0]の並び順は同じだが、最上位ビットの次のビットptr[WA-1]0→11→0と逆の順番になっているので、上位2ビットを反転させて比較しなければならない。すなわち

 リード側でFIFOが空(empty)である条件:wptr[WA:0]==rptr[WA:0]

 ライト側でFIFOがフル(full)である条件:wptr[WA:0]=={~rptr[WA:WA-1],rptr[WA-2:0]}

非同期FIFORTL記述は次のようになる

/* 非同期FIFO */

module fifo #(parameter WA=8,WD=16)

    (input  rst                 // reset

    ,input  wclk                // write clock

    ,input  wen                 // write enable

    ,input[WD-1:0] wdat         // write data

    ,output reg wfull           // write full

    ,input  rclk                // read clock

    ,input  ren                 // read enable

    ,output reg[WD-1:0] rdat    // read data

    ,output reg rempty          // read empty

    );

reg[WA:0] wadr_reg;

reg[WA:0] radr_reg;

reg[WA:0] wptr_reg,wptr0_reg,wptr1_reg;

reg[WA:0] rptr_reg,rptr0_reg,rptr1_reg;

wire[WA:0] next_wadr,next_wptr;

wire[WA:0] next_radr,next_rptr;

reg[WD-1:0] ram[0:2**WA-1];

/**************************************************************

 * DPM

 *************************************************************/

always @(posedge wclk)

    if(wen) ram[wadr_reg[WA-1:0]] <= wdat;

always @(posedge rclk)

    if(ren) rdat <= ram[radr_reg[WA-1:0]];

/**************************************************************

 * wclk domain

 *************************************************************/

/* write address */

always @(posedge wclk or posedge rst)

    begin

    if(rst)

        {wadr_reg,wptr_reg} <= {{(WA+1){1'b0}},{(WA+1){1'b0}}};

    else if(wen)

        {wadr_reg,wptr_reg} <= {next_wadr, next_wptr};

    end

assign next_wadr = wadr_reg + (wen & ~wfull);     // binary

assign next_wptr = next_wadr ^ (next_wadr>>1'b1); // gray

/* cdc transfer of rptr */

always @(posedge wclk or posedge rst)

    begin

    if(rst)

        {rptr1_reg, rptr0_reg} <= {{(WA+1){1'b0}},{(WA+1){1'b0}}};

    else

        {rptr1_reg, rptr0_reg} <= {rptr0_reg, rptr_reg};

    end

/* full flag */

always @(posedge wclk or posedge rst)

    begin

    if(rst)

        wfull <= 1'b0;

    else if(next_wptr=={~rptr1_reg[WA:WA-1], rptr1_reg[WA-2:0]})

        wfull <= 1'b1;

    else

        wfull <= 1'b0;

    end

/**************************************************************

 * rclk domain

 *************************************************************/

/* read address */

always @(posedge rclk or posedge rst)

    begin

    if(rst)

        {radr_reg,rptr_reg} <= {{(WA+1){1'b0}},{(WA+1){1'b0}}};

    else if(ren)

        {radr_reg,rptr_reg} <= {next_radr, next_rptr};

    end

assign next_radr = radr_reg + (ren & ~rempty);    // binary

assign next_rptr = next_radr ^ (next_radr >> 1);  // gray

/* cdc transfer of wptr */

always @(posedge rclk or posedge rst)

    begin

    if(rst)

        {wptr1_reg, wptr0_reg} <= {{(WA+1){1'b0}},{(WA+1){1'b0}}};

    else

        {wptr1_reg, wptr0_reg} <= {wptr0_reg, wptr_reg};

    end

/* empty flag */

always @(posedge rclk or posedge rst)

    begin

    if(rst)

        rempty <= 1'b1;

    else if(next_rptr==wptr1_reg)

        rempty <= 1'b1;

    else

        rempty <= 1'b0;

    end

endmodule

ライトのタイミングチャートは次のようになる。

リードは、ren = ~remptyとして使えばFIFOが空でなければ常にリードするようになる。

DPMを使っていると、ライトはwenと同時にwdatを入力できるが、リードはrenから1クロック遅れてrdatが出てくる。

これだと使いにくいので、rdat1クロック前倒しにするモードがある。AlteraIPではShow-aheadモード、XilinxではFirst-word Fall-throughモードと呼ばれている。上記のRTL記述ではDPMのリードを次のように書き換えればよい。

/* Show-ahead mode / First-word Fall-through mode */

always @(posedge rclk)

    rdat <= ram[radr_reg[WA-1:0]+(ren? 1'b1:1'b0)];

このときのリードのタイミングチャートは次のようになる。最初のrdat値はライトされたらremptyのネゲートと同時に出力される。


[] [] []