非同期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]が一致したときだが、wadrが1周してradrに追いついて一致したときはFIFOが満杯(full)である。そこで1周差を識別できるようにwadr/radrを1ビット増やして判定すればよい。
リード側でFIFOが空(empty)である条件:wadr[WA:0]==radr[WA:0]
ライト側でFIFOが満杯(full)である条件:wadr[WA:0]=={~radr[WA],radr[WA-1:0]}
次にwadr/radrをCDC転送する方法を考えると、グレイコードに変換して2FFシンクロナイザで転送すればよい。バイナリコードのアドレスと区別するために、グレイコードに変換したアドレスをライトポインタ(wptr)、リードポインタ(rptr)と呼ぶことにする。
グレイコードカウンタの項で見たようにグレイコードからバイナリコードへ戻す変換はビット幅に比例して遅くなるので、グレイコードのままemptyとfullを判定する。このときfullの判定は注意が必要で、グレイコードの並びをよく見ると、最上位ビットptr[WA]=0のときとptr[WA]=1のときとで、下位のptr[WA-2:0]の並び順は同じだが、最上位ビットの次のビットptr[WA-1]は0→1と1→0と逆の順番になっているので、上位2ビットを反転させて比較しなければならない。すなわち
リード側でFIFOが空(empty)である条件:wptr[WA:0]==rptr[WA:0]
ライト側でFIFOが満杯(full)である条件:wptr[WA:0]=={~rptr[WA:WA-1],rptr[WA-2:0]}
◇◇
非同期FIFOのRTL記述は次のようになる。
/* 非同期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が出てくる。これだと使いにくいので、rdatを1クロック前倒しにするモードがある。IntelのIPでは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のネゲートと同時に出力される。