非同期FIFOの作り方

[] [] []


同期FIFOにならって、ライトとリードが異なるクロックで動く非同期FIFOを作る。

FPGAのデュアルポートメモリ(DPM)2つのポートを異なるクロックで動かせるので、同期FIFOと同様にライトとリードを2ポートに割り当てて、1ワードをライト/リードする毎にライト/リードアドレス(wadr/radr)をカウントアップすればよい。ただ、同期FIFOと違ってライトとリードのクロックが異なるため、1個のカウンタでFIFOに溜まっているワード数を数えられない。そこで、wadr/radrを反対側にCDC転送してから溜まっているワード数を判定する必要がある。

まず判定条件を考えると、FIFOが空(empty)であるのはwdar[WA-1:0]radr[WA-1:0]が一致したときだが、wadr1周してradrに追いついて一致したときはFIFOが満杯(full)である。そこで1周差を識別できるようにwadr/radr1ビット増やして判定すればよい。

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

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

次にwadr/radrCDC転送する方法を考えると、グレイコードに変換して2FFシンクロナイザで転送すればよい。バイナリコードのアドレスと区別するために、グレイコードに変換したアドレスをライトポインタ(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[WA:0] radr;

reg[WA:0] wptr,wptr0,wptr1;

reg[WA:0] rptr,rptr0,rptr1;

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[WA-1:0]] <= wdat;

always @(posedge rclk)

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

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

 * wclk domain

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

/* write address */

always @(posedge wclk or posedge rst) begin

  if(rst)

    {wadr,wptr} <= {{(WA+1){1'b0}},{(WA+1){1'b0}}};

  else if(wen)

    {wadr,wptr} <= {next_wadr,next_wptr};

end

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

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

/* 2FF-sync of rptr */

always @(posedge wclk or posedge rst) begin

  if(rst)

    {rptr1,rptr0} <= {{(WA+1){1'b0}},{(WA+1){1'b0}}};

  else

    {rptr1,rptr0} <= {rptr0,rptr};

end

/* full flag */

always @(posedge wclk or posedge rst) begin

  if(rst)

    wfull <= 1'b0;

  else if(next_wptr=={~rptr1[WA:WA-1],rptr1[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,rptr} <= {{(WA+1){1'b0}},{(WA+1){1'b0}}};

  else if(ren)

    {radr,rptr} <= {next_radr,next_rptr};

end

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

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

/* 2FF-sync of wptr */

always @(posedge rclk or posedge rst) begin

  if(rst)

    {wptr1,wptr0} <= {{(WA+1){1'b0}},{(WA+1){1'b0}}};

  else

    {wptr1,wptr0} <= {wptr0,wptr};

end

/* empty flag */

always @(posedge rclk or posedge rst) begin

  if(rst)

    rempty <= 1'b1;

  else if(next_rptr==wptr1)

    rempty <= 1'b1;

  else

    rempty <= 1'b0;

end

endmodule

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

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

◇◇◇

DPMを使っていると、ライトはwenと同時にwdatを入力できるが、リードはrenから1クロック遅れてrdatが出てくる。これだと使いにくいので、rdat1クロック前倒しにするモードがある。IntelIPではShow-aheadモード、XilinxではFirst-word Fall-throughモードと呼ばれている。上記のRTL記述ではDPMのリードを次のように書き換えればよい。

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

always @(posedge rclk)

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

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


[] [] []