FPGA基礎知識12(FIFO同期、非同期、Verilogコード実装)


需要説明:IC設計基礎
内容      :FIFO設計、同期FIFO、非同期FIFO
から      :時の詩
原文:http://xilinx.eetrend.com/blog/2118
FIFOは重要で、これまで参加した各種電子会社の論理設計の筆記試験はほとんど合格した.
FIFOは英語のFirst In First Outの略で、先進的な先出しのデータバッファで、彼と普通のメモリの違いは外部の読み書きアドレス線がなくて、このように使うのはとても簡単で、しかし欠点は順番にデータを書き込むことしかできなくて、順番の読み出しデータ、そのデータアドレスは内部の読み書きポインタによって自動的に1を加えて完成します.通常のメモリのように、指定されたアドレスの読み取りまたは書き込みをアドレス線で決定することはできません.
 FIFOは一般的に異なるクロックドメイン間のデータ伝送に用いられる.例えば、FIFOの一端はADデータ収集であり、他端はコンピュータのPCIバスであり、そのAD収集速度が16ビット100 K SPSであると仮定すると、毎秒のデータ量は100 Kである.×16 bit=1.6 Mbps、PCIバスの速度は33 MHz、バス幅32 bit、その最大伝送速度は1056 Mbpsであり、2つの異なるクロックドメイン間でFIFOをデータバッファとして採用することができる.また、異なる幅のデータインタフェースについても、FIFO、例えば、シングルチップセット8ビットのデータ出力を用いることができ、DSPは16ビットのデータ入力であり、シングルチップマシンがDSPに接続されている場合、FIFOを用いてデータマッチングの目的を達成することができる.FIFOの分類ルートは、FIFOが動作するクロックドメインであり、FIFOを同期FIFOと非同期FIFOに分けることができる.同期FIFOとは、リードクロックとライトクロックが同一クロックであることを意味する.クロックエッジが到来すると同時に読み書き操作が発生する.非同期FIFOとは、読み書きクロックが一致せず、読み書きクロックが互いに独立していることを意味する.FIFO設計の難点FIFO設計の難点は,FIFOの空/満状態をどのように判断するかである.データの正確な書き込みや読み出しを保証し、利益や読み取り空の状態が発生しないように、FIFOが満タンの場合、書き込み操作ができないことを保証しなければならない.空の状態では読み取り操作はできません.FIFOの満/空をどう判断するかがFIFO設計の核心問題となっている........................................................
1同期FIFOのVerilogコードの1つ
modlesimで検証しました.
/******************************************************
A fifo controller verilog description.
******************************************************/
module fifo(datain, rd, wr, rst, clk, dataout, full, empty);
input [7:0] datain;
input rd, wr, rst, clk;
output [7:0] dataout;
output full, empty;
wire [7:0] dataout;
reg full_in, empty_in;
reg [7:0] mem [15:0];
reg [3:0] rp, wp;
assign full = full_in;
assign empty = empty_in;
// memory read out
assign dataout = mem[rp];
// memory write in
always@(posedge clk) begin
    if(wr && ~full_in) mem[wp]<=datain;
end
// memory write pointer increment
always@(posedge clk or negedge rst) begin
    if(!rst) wp<=0;
    else begin
      if(wr && ~full_in) wp<= wp+1'b1;
    end
end
// memory read pointer increment
always@(posedge clk or negedge rst)begin
    if(!rst) rp <= 0;
    else begin
      if(rd && ~empty_in) rp <= rp + 1'b1;
    end
end
// Full signal generate
always@(posedge clk or negedge rst) begin
    if(!rst) full_in <= 1'b0;
    else begin
      if( (~rd && wr)&&((wp==rp-1)||(rp==4'h0&&wp==4'hf)))
          full_in <= 1'b1;
      else if(full_in && rd) full_in <= 1'b0;
    end
end
// Empty signal generate
always@(posedge clk or negedge rst) begin
    if(!rst) empty_in <= 1'b1;
    else begin
      if((rd&&~wr)&&(rp==wp-1 || (rp==4'hf&&wp==4'h0)))
        empty_in<=1'b1;
      else if(empty_in && wr) empty_in<=1'b0;
    end
end
endmodule
...........................................................................................................................

2 FIFOのVerilogコードを同期する2
この設計のFIFOは、トリガに基づいています.幅、深さの拡張がより便利で、構造化が強くなっています.次のコードはmodelsimで検証されています.
module fifo_cell (sys_clk, sys_rst_n, read_fifo, write_fifo, fifo_input_data,
                        next_cell_data, next_cell_full, last_cell_full, cell_data_out, cell_full);
                        parameter WIDTH =8;
                        parameter D = 2;
                        input sys_clk;
                        input sys_rst_n;
                        input read_fifo, write_fifo;
                        input [WIDTH-1:0] fifo_input_data;
                        input [WIDTH-1:0] next_cell_data;
                        input next_cell_full, last_cell_full;
                        output [WIDTH-1:0] cell_data_out;
                        output cell_full;
                        reg [WIDTH-1:0] cell_data_reg_array;
                        reg [WIDTH-1:0] cell_data_ld;
                        reg cell_data_ld_en;
                        reg cell_full;
                        reg cell_full_next;
                        assign cell_data_out=cell_data_reg_array;
                        always @(posedge sys_clk or negedge sys_rst_n)
                           if (!sys_rst_n)
                              cell_full <= #D 0;
                           else if (read_fifo || write_fifo)
                              cell_full <= #D cell_full_next;
                        always @(write_fifo or read_fifo or next_cell_full or last_cell_full or cell_full)
                           casex ({read_fifo, write_fifo})
                               2'b00: cell_full_next = cell_full;
                               2'b01: cell_full_next = next_cell_full;
                               2'b10: cell_full_next = last_cell_full;
                               2'b11: cell_full_next = cell_full;
                           endcase
                         always @(posedge sys_clk or negedge sys_rst_n)
                              if (!sys_rst_n)
                                 cell_data_reg_array [WIDTH-1:0] <= #D 0;
                              else if (cell_data_ld_en)
                                 cell_data_reg_array [WIDTH-1:0] <= #D cell_data_ld [WIDTH-1:0];
                         always @(write_fifo or read_fifo or cell_full or last_cell_full)   
                              casex ({write_fifo,read_fifo,cell_full,last_cell_full})
                                  4'bx1_xx: cell_data_ld_en = 1'b1;
                                  4'b10_01: cell_data_ld_en = 1'b1;
                                  default: cell_data_ld_en =1'b0;
                              endcase
                         always @(write_fifo or read_fifo or next_cell_full or cell_full or last_cell_full or fifo_input_data or next_cell_data)
                              casex ({write_fifo, read_fifo, next_cell_full, cell_full, last_cell_full})
                                 5'b10_x01: cell_data_ld[WIDTH-1:0] = fifo_input_data[WIDTH-1:0];
                                 5'b11_01x: cell_data_ld[WIDTH-1:0] = fifo_input_data[WIDTH-1:0];
                                 default: cell_data_ld[WIDTH-1:0] = next_cell_data[WIDTH-1:0];
                              endcase
endmodule

module fifo_4cell(sys_clk, sys_rst_n, fifo_input_data, write_fifo, fifo_out_data,
                  read_fifo, full_cell0, full_cell1, full_cell2, full_cell3);
                  parameter WIDTH = 8;
                  parameter D = 2;
                  input sys_clk;
                  input sys_rst_n;
                  input [WIDTH-1:0] fifo_input_data;
                  output [WIDTH-1:0] fifo_out_data;
                  input read_fifo, write_fifo;
                  output full_cell0, full_cell1, full_cell2, full_cell3;
                  wire [WIDTH-1:0] dara_out_cell0, data_out_cell1, data_out_cell2,
                                   data_out_cell3, data_out_cell4;
                  wire full_cell4;
                  fifo_cell #(WIDTH,D) cell0
                  ( .sys_clk (sys_clk),
                    .sys_rst_n (sys_rst_n),
                    .fifo_input_data (fifo_input_data[WIDTH-1:0]),
                    .write_fifo (write_fifo),
                    .next_cell_data (data_out_cell1[WIDTH-1:0]),
                    .next_cell_full (full_cell1),
                    .last_cell_full (1'b1),
                    .cell_data_out (fifo_out_data [WIDTH-1:0]),
                    .read_fifo (read_fifo),
                    .cell_full (full_cell0)
                   );

                  fifo_cell #(WIDTH,D) cell1
                  ( .sys_clk (sys_clk),
                    .sys_rst_n (sys_rst_n),
                    .fifo_input_data (fifo_input_data[WIDTH-1:0]),
                    .write_fifo (write_fifo),
                    .next_cell_data (data_out_cell2[WIDTH-1:0]),
                    .next_cell_full (full_cell2),
                    .last_cell_full (full_cell0),
                    .cell_data_out (data_out_cell1[WIDTH-1:0]),
                    .read_fifo (read_fifo),
                    .cell_full (full_cell1)
                   );                  
                  fifo_cell #(WIDTH,D) cell2
                  ( .sys_clk (sys_clk),
                    .sys_rst_n (sys_rst_n),
                    .fifo_input_data (fifo_input_data[WIDTH-1:0]),
                    .write_fifo (write_fifo),
                    .next_cell_data (data_out_cell3[WIDTH-1:0]),
                    .next_cell_full (full_cell3),
                    .last_cell_full (full_cell1),
                    .cell_data_out (data_out_cell2[WIDTH-1:0]),
                    .read_fifo (read_fifo),
                    .cell_full (full_cell2)
                   );                  

                  fifo_cell #(WIDTH,D) cell3
                  ( .sys_clk (sys_clk),
                    .sys_rst_n (sys_rst_n),
                    .fifo_input_data (fifo_input_data[WIDTH-1:0]),
                    .write_fifo (write_fifo),
                    .next_cell_data (data_out_cell4[WIDTH-1:0]),
                    .next_cell_full (full_cell4),
                    .last_cell_full (full_cell2),
                    .cell_data_out (data_out_cell3[WIDTH-1:0]),
                    .read_fifo (read_fifo),
                    .cell_full (full_cell3)
                   );     
                   assign data_out_cell4[WIDTH-1:0] = {WIDTH{1'B0}};
                   assign full_cell4 = 1'b0;
endmodule                              
..........................................................................................................................

3非同期FIFOのVerilogコードの1つ
これはRAMベースの非同期FIFOコードで、個人的にはコード構造が分かりやすく、試験で記入するのに適していると思います.10月に威盛の筆記試験に参加したとき、非同期FIFOの実現を受けたのを覚えています.最初は早く復習すれば、威盛の筆記試験に合格できるかもしれないと思っていました.従来のRAMで実現された同期FIFOのプログラムよりも非同期の方が複雑である.読み書き制御信号のクロックドメイン間同期が増加した.また、判空と判満は少し違います.
module fifo1(rdata, wfull, rempty, wdata, winc, wclk, wrst_n,rinc, rclk, rrst_n);
parameter DSIZE = 8; parameter ASIZE = 4;
output [DSIZE-1:0] rdata;
output wfull;
output rempty;
input [DSIZE-1:0] wdata;
input winc, wclk, wrst_n;
input rinc, rclk, rrst_n;
reg wfull,rempty;
reg [ASIZE:0] wptr, rptr, wq2_rptr, rq2_wptr, wq1_rptr,rq1_wptr;
reg [ASIZE:0] rbin, wbin;
reg [DSIZE-1:0] mem[0:(1<<ASIZE)-1];
wire [ASIZE-1:0] waddr, raddr;
wire [ASIZE:0] rgraynext, rbinnext,wgraynext,wbinnext;
wire rempty_val,wfull_val;
//-----------------  RAM   --------------------
assign rdata=mem[raddr];
always@(posedge wclk)
if (winc && !wfull) mem[waddr] <= wdata;
//-------------  rptr   -------------------------
always @(posedge wclk or negedge wrst_n)
if (!wrst_n) {wq2_rptr,wq1_rptr} <= 0;
else {wq2_rptr,wq1_rptr} <= {wq1_rptr,rptr};
//-------------  wptr  ---------------------------
always @(posedge rclk or negedge rrst_n)
if (!rrst_n) {rq2_wptr,rq1_wptr} <= 0;
else {rq2_wptr,rq1_wptr} <= {rq1_wptr,wptr};
//-------------rempty   raddr  -------------------
always @(posedge rclk or negedge rrst_n) // GRAYSTYLE2 pointer
begin
if (!rrst_n) {rbin, rptr} <= 0;
else {rbin, rptr} <= {rbinnext, rgraynext};
end
// Memory read-address pointer (okay to use binary to address memory)
assign raddr = rbin[ASIZE-1:0];
assign rbinnext = rbin + (rinc & ~rempty);
assign rgraynext = (rbinnext>>1) ^ rbinnext;
// FIFO empty when the next rptr == synchronized wptr or on reset
assign rempty_val = (rgraynext == rq2_wptr);
always @(posedge rclk or negedge rrst_n)
begin
if (!rrst_n) rempty <= 1'b1;
else rempty <= rempty_val;
end
//---------------wfull   waddr  ------------------------------
always @(posedge wclk or negedge wrst_n) // GRAYSTYLE2 pointer
if (!wrst_n) {wbin, wptr} <= 0;
else {wbin, wptr} <= {wbinnext, wgraynext};
// Memory write-address pointer (okay to use binary to address memory)
assign waddr = wbin[ASIZE-1:0];
assign wbinnext = wbin + (winc & ~wfull);
assign wgraynext = (wbinnext>>1) ^ wbinnext;
assign wfull_val = (wgraynext=={~wq2_rptr[ASIZE:ASIZE-1], wq2_rptr[ASIZE-2:0]}); //:ASIZE-1]
always @(posedge wclk or negedge wrst_n)
if (!wrst_n) wfull <= 1'b0;
else wfull <= wfull_val;
endmodule
..........................................................................................................................

4非同期FIFOのVerilogコードの2
前の非同期FIFOコードとの主な違いは、空/満状態フラグの異なるアルゴリズムである.
最初のアルゴリズム:Clifford E.Cummingsの文章で述べたSTYLE 1は、ポインタ幅がN+1で、深さが2^NバイトのFIFOを構築する(比較のためにグレイコードポインタをバイナリポインタに変換する).ポインタのバイナリコードの中で最上位が一致せず、他のNビットが等しい場合、FIFOは満(Clifford E.Cummingsの文章ではグレイコードで表されるのが上位2桁とも異なり、その後2桁のLSBは同じ満であり、これはバイナリで表されるMSBとは異なり、他の同じ満である)である.ポインタが完全に等しい場合、FIFOは空です.
この方法は,異なるクロックで発生するポインタを比較するためには,異なるクロック領域の信号を本クロック領域に同期させる必要があるが,Grayコードを用いる目的は,この非同期同期同期化の過程で準定常状態が発生する確率を最小にすることであり,なぜN+1のポインタを構築するのか,Clifford E.Cummingsもよく述べている.興味のある読者は、著者の原文がどのように論じられているかを見ることができます.Clifford E.Cummingsのこの文章にはRev 1.1Rev 1.2の2つのバージョンがあり、両者はGrayコードポインタを比較する際の方法が少し異なり、Rev 1.2版がより簡素です.
2番目のアルゴリズム:Clifford E.Cummingsの記事で述べたSTYLE 2.FIFOアドレスは4つの部分に分けられ、各部分はそれぞれ上位2ビットのMSB 00、01、11、10でFIFOがgoing fullまたはgoing empty(満または空)であるかどうかを決定する.書き込みポインタの上位2ビットMSBが読み取りポインタの上位2ビットMSBより小さい場合、FIFOは「ほぼ満タン」であり、書き込みポインタの上位2ビットMSBが読み取りポインタの上位2ビットMSBより大きい場合、FIFOは「ほぼ空」である.
アドレス空間を4つの象限(すなわち4つの等大領域)に分けて2つのポインタの相対位置を観察し、書き込みポインタが読み取りポインタの1つの象限(25%の距離、ほほほ)に遅れると、満タンになる可能性が高いことを証明し、逆に空を読む可能性が高い.このとき、2つのフラグビットdirsetとdirrstをそれぞれ設定し、そして、アドレスが完全に等しい場合、dirsetが有効であれば書き込みであり、dirrstが有効であれば読み取りである.
この方法は深さ2^NバイトのFIFOに対してNビットのポインタだけでよく,処理速度も第1の方法より速い. 
この段落は説明の原話で、アルゴリズム1、まだ理解しています.アルゴリズム2は、はっきり言っていないようで、よく分かりません.興味のある人は論文を調べて、詳しく研究することができます.
要するに、2つ目の書き方はお勧めの書き方です.非同期のマルチクロック設計は、以下の原則に従って設計されるべきである.
1.マルチクロックの論理回路(非同期器)を可能な限り複数の単一クロックのモジュールに分割することで、静的タイミング解析ツールによるタイミング検証を容易にすることができる.
2.同期器の実装は、すべての入力が同じクロックドメインから来て、別のクロックドメインの非同期クロック信号を使用してデータをサンプリングするようにしなければならない.
3、クロック信号に対する命名方式は、異なる非同期クロックドメイン間で処理する必要がある信号を決定するのに役立つ.
4.クロックドメインにまたがる複数の制御信号が存在する場合、これらの信号に特に注意し、これらの制御信号が新しいクロックドメインに到達しても正しい順序を維持できることを保証しなければならない.
module fifo2 (rdata, wfull, rempty, wdata,
winc, wclk, wrst_n, rinc, rclk, rrst_n);
parameter DSIZE = 8;
parameter ASIZE = 4;
output [DSIZE-1:0] rdata;
output wfull;
output rempty;
input [DSIZE-1:0] wdata;
input winc, wclk, wrst_n;
input rinc, rclk, rrst_n;
wire [ASIZE-1:0] wptr, rptr;
wire [ASIZE-1:0] waddr, raddr;
async_cmp #(ASIZE) async_cmp(.aempty_n(aempty_n),
.afull_n(afull_n),
.wptr(wptr), .rptr(rptr),
.wrst_n(wrst_n));
fifomem2 #(DSIZE, ASIZE) fifomem2(.rdata(rdata),
.wdata(wdata),
.waddr(wptr),
.raddr(rptr),
.wclken(winc),
.wclk(wclk));
rptr_empty2 #(ASIZE) rptr_empty2(.rempty(rempty),
.rptr(rptr),
.aempty_n(aempty_n),
.rinc(rinc),
.rclk(rclk),
.rrst_n(rrst_n));
wptr_full2 #(ASIZE) wptr_full2(.wfull(wfull),
.wptr(wptr),
.afull_n(afull_n),
.winc(winc),
.wclk(wclk),
.wrst_n(wrst_n));
endmodule
module fifomem2 (rdata, wdata, waddr, raddr, wclken, wclk);
parameter DATASIZE = 8; // Memory data word width
parameter ADDRSIZE = 4; // Number of memory address bits
parameter DEPTH = 1<<ADDRSIZE; // DEPTH = 2**ADDRSIZE
output [DATASIZE-1:0] rdata;
input [DATASIZE-1:0] wdata;
input [ADDRSIZE-1:0] waddr, raddr;
input wclken, wclk;
`ifdef VENDORRAM
// instantiation of a vendor's dual-port RAM
VENDOR_RAM MEM (.dout(rdata), .din(wdata),
.waddr(waddr), .raddr(raddr),
.wclken(wclken), .clk(wclk));
`else
reg [DATASIZE-1:0] MEM [0:DEPTH-1];
assign rdata = MEM[raddr];
always @(posedge wclk)
if (wclken) MEM[waddr] <= wdata;
`endif
endmodule
module async_cmp (aempty_n, afull_n, wptr, rptr, wrst_n);
parameter ADDRSIZE = 4;
parameter N = ADDRSIZE-1;
output aempty_n, afull_n;
input [N:0] wptr, rptr;
input wrst_n;
reg direction;
wire high = 1'b1;
wire dirset_n = ~( (wptr[N]^rptr[N-1]) & ~(wptr[N-1]^rptr[N]));
wire dirclr_n = ~((~(wptr[N]^rptr[N-1]) & (wptr[N-1]^rptr[N])) |
~wrst_n);
always @(posedge high or negedge dirset_n or negedge dirclr_n)
if (!dirclr_n) direction <= 1'b0;
else if (!dirset_n) direction <= 1'b1;
else direction <= high;
//always @(negedge dirset_n or negedge dirclr_n)
//if (!dirclr_n) direction <= 1'b0;
//else direction <= 1'b1;
assign aempty_n = ~((wptr == rptr) && !direction);
assign afull_n = ~((wptr == rptr) && direction);
endmodule
module rptr_empty2 (rempty, rptr, aempty_n, rinc, rclk, rrst_n);
parameter ADDRSIZE = 4;
output rempty;
output [ADDRSIZE-1:0] rptr;
input aempty_n;
input rinc, rclk, rrst_n;
reg [ADDRSIZE-1:0] rptr, rbin;
reg rempty, rempty2;
wire [ADDRSIZE-1:0] rgnext, rbnext;
//---------------------------------------------------------------
// GRAYSTYLE2 pointer
//---------------------------------------------------------------
always @(posedge rclk or negedge rrst_n)
if (!rrst_n) begin
rbin <= 0;
rptr <= 0;
end
else begin
rbin <= rbnext;
rptr <= rgnext;
end
//---------------------------------------------------------------
// increment the binary count if not empty
//---------------------------------------------------------------
assign rbnext = !rempty ? rbin + rinc : rbin;
assign rgnext = (rbnext>>1) ^ rbnext; // binary-to-gray conversion
always @(posedge rclk or negedge aempty_n)
if (!aempty_n) {rempty,rempty2} <= 2'b11;
else {rempty,rempty2} <= {rempty2,~aempty_n};
endmodule
module wptr_full2 (wfull, wptr, afull_n, winc, wclk, wrst_n);
parameter ADDRSIZE = 4;
output wfull;
output [ADDRSIZE-1:0] wptr;
input afull_n;
input winc, wclk, wrst_n;
reg [ADDRSIZE-1:0] wptr, wbin;
reg wfull, wfull2;
wire [ADDRSIZE-1:0] wgnext, wbnext;
//---------------------------------------------------------------
// GRAYSTYLE2 pointer
//---------------------------------------------------------------
always @(posedge wclk or negedge wrst_n)
if (!wrst_n) begin
wbin <= 0;
wptr <= 0;
end
else begin
wbin <= wbnext;
wptr <= wgnext;
end
//---------------------------------------------------------------
// increment the binary count if not full
//---------------------------------------------------------------
assign wbnext = !wfull ? wbin + winc : wbin;
assign wgnext = (wbnext>>1) ^ wbnext; // binary-to-gray conversion
always @(posedge wclk or negedge wrst_n or negedge afull_n)
if (!wrst_n ) {wfull,wfull2} <= 2'b00;
else if (!afull_n) {wfull,wfull2} <= 2'b11;
else {wfull,wfull2} <= {wfull2,~afull_n};
endmodule