デジタル IC ティアリング コード - 同期 FIFO

 序文: 

        このコラムは、デジタル フロントエンドの秋採用者向けに、頻繁に行われるペン インタビューの手引きコード質問を記録することを目的としており、このコラムのすべての記事には原理分析、コードと波形が提供されており、すべてのコードは私自身によって検証されています。

ディレクトリは次のとおりです。

1. デジタルICハンドティアコード分周器(任意の偶数分周)

2. デジタル IC ハンドティアコード分周器 (任意の奇数分周)

3. デジタル IC ハンドティアコード分周器 (任意の 10 進分周)

4.デジタルICハンドティアリングコード - 非同期リセットと同期リリース

5.デジタルICハンドティアコード - エッジ検出(立ち上がりエッジ、立ち下がりエッジ、ダブルエッジ)

6. デジタルICハンドティアリングコード列検出(ステートマシン書き込み方式)

7. デジタルICハンドティアリングコード列検出(シフトレジスタ書き込み方式)

8. デジタルICティアリングコード - 半加算器、全加算器

9. デジタル IC ハンド ティアリング コード - シリアルからパラレル、パラレルからシリアル

10.デジタルICハンドティアリングコード-データビット幅変換器(幅-狭幅、狭幅変換)

11. デジタル IC ハンド ティアリング コード - 有限状態マシン FSM - 飲料マシン

12.デジタルICハンドティアコード - ハンドシェイク信号(READY-VALID)

13. デジタル IC ハンド ティアリング コード - 水ハンドシェイク (ハンドシェイクを使用してパイプラインの中断と背圧の問題を解決します)

14. デジタル IC ハンド テアリング コード - Telink マイクロ筆記試験問題

15. デジタル IC 手引きコード - Pingtouge 技術最終面手引き本当の質問

16.デジタルICマニュアルティアリングコード-Zhaoyiイノベーション筆記試験実際の質問

17. デジタル IC ハンド ティアリング コード - Espressif Technology 筆記試験実際の問題 (4 回の頻度)

18. デジタル IC ティアリング コード - デュアルポート RAM (デュアルポート RAM)

        ...継続的に更新

ハンドテアリング コードに関するその他の質問については、 デジタル IC ハンドテアリング コード - 質問バンクにアクセスしてください。


目次

原理紹介

同期 FIFO の仕組み

FIFO の完全な生成

カウンターが空であると判断される

コード

デュアルポートRAM

同期FIFO

         テストベンチ

波形


原理紹介

        システムを設計するときは、さまざまなクロック周波数で動作するプロセッサや周辺機器などの要素を含めます。FIFO 先入れ先出し配列は、これらの要素間でデータが転送されるときに重要な役割を果たします。FIFO は、通信バス上で送信するデータを整理するために使用される単純なストレージ構造です。

        したがって、FIFO は、異なるクロック ドメイン間でデータを転送するためによく使用されます。

        このセクションでは、読み取りと書き込みに同じクロックを使用する単純な同期 FIFO アーキテクチャを紹介します。これは、その後の非同期 FIFO の書き込みへの道を開きます (読み取りクロックと書き込みクロックは同じソースのものではありません)。

同期 FIFO の一般的な構造は        次のとおりです。情報を保存するメモリとして DPRAM (Dual Port RAM) が使用されます。DPRAM が満杯か空かを判断するコンポーネントを追加すると、モジュール全体が同期 FIFO になります。読み出しおよびwrite モジュール全体を同時に読み書きできるように、異なるイネーブル信号とアドレス信号 (読み取りイネーブルと書き込みイネーブル、個別の読み取りアドレスと書き込みアドレス) を使用します。

         読み取りポインタと書き込みポインタを介してそれぞれの読み取りアドレスと書き込みアドレスを生成し、それらを読み取りポートと書き込みポートに送信します。書き込みポインタは次に書き込まれるアドレスを指し、読み取りポインタは次に読み取られるアドレスを指します。アクティブな書き込みイネーブルは書き込みポインタをインクリメントし、アクティブな読み取りイネーブルは読み取りポインタをインクリメントします。

        図中の「ステータスモジュール」はFIFOエンプティフル信号を生成しており、「fifo_full」が有効な場合はFIFO内がフルでこれ以上データを書き込めないことを意味します。「fifo_empty」が有効な場合は、FIFO 内に次に読み取り可能な有効なデータがあることを意味します。読み書きポインタの位置を判断することにより、モジュールはいつでも FIFO の空の領域または満杯の領域の数を示すことができます。

同期 FIFO の仕組み

        リセット後、読み取りポインタと書き込みポインタは両方とも 0 に戻ります。このとき、「fifo_empty」信号がアサートされ、「fifo_full」は Low のままになります。FIFO が空であるため、FIFO への読み取り操作はブロックされ、書き込み操作のみを実行できます。後続の書き込み操作では、書き込みポインタがインクリメントされ、「fifo_empty」信号がディアサートされます。最後のデータが書き込まれるとき、書き込みポインタは RAM_SIZE-1 に等しくなります。この時点で書き込みポインタを実行すると、書き込みポインタが 0 にロールバックされ、「fifo_full」信号が High に設定されます。

       つまり、読み取りポインタと書き込みポインタが等しい場合、FIFO は空か満杯のいずれかであるため、この 2 つのケースを区別する必要があります。

FIFO の完全な生成

        深さ 4 の FIFO を例にとると、最初は読み取りポインタと書き込みポインタが同じ位置を指しており、FIFO は空です。3 つのデータを書き込んだ後、ライト ポインタは RAM_SIZE-1=3 の位置を指しますが、この時点で別のデータが書き込まれ、ライト ポインタ (wr_ptr) はリード ポインタと同じ位置を指す 0 に戻ります。 . この時点で、FIFO はフルです。

        このロジックによれば、このような結論を導き出すのは簡単です。現時点で読み取り/書き込みポインタがどこを指していても、wr_ptr+1==rd_ptr の場合、FIFO はもう 1 つのデータを書き込んだ後にいっぱいになるため、次のようになります。 

        fifo_full = (rd_ptr == (wr_ptr + 1'b1))&& wr_fifo

したがって、FIFO がいっぱいであることを判断するための RTL コードがあります。

always@(posedge clk or negedge rstn)begin
    if(!rstn)
        fifo_full <= 1'b0;
    else if(wr_fifo && rd_fifo)
        ;//do nothing
    else if(rd_fifo)
        fifo_full <= 1'b0;
    else if((rd_ptr = wr_ptr + 1'b1) && wr_fifo)
        fifo_full <= 1'b1;
end

        同様に、読み取り操作により次のクロックで 2 つのポインターが等しくなる場合、FIFO は空になり、「fifo_empty」信号が生成されます。このとき、読み取り/書き込みポインタがどの位置を指していても、rd_ptr+1==wr_ptr の場合、別のデータを読み込んだ後、FIFO は空になります。

        fifo_empty = (wr_ptr == (rd_ptr + 1'b1))&& rd_fifo

したがって、FIFO が空であることを判断するための RTL コードがあります。 

always @(posedge clk or negedge rstn)begin
    if(!rstn)
        fifo_empty <= 1'b1;
    else if(wr_fifo && rd_fifo)
        ;//do nothing
    else if(wr_fifo)
        fifo_empty <= 1'b0;
    else if((wr_ptr = rd_ptr + 1'b1) && rd_fifo)
        fifo_empty <= 1'b1;
end

カウンターが空であると判断される

        FIFO には、カウンターを使用して FIFO がいっぱいになったことを示す別の方法があります。

        カウンタが最大数の FIFO データを記録できるように、カウンタの幅は FIFO の深さと同じである必要があります。カウンタはリセット時に 0 に初期化され、その後の書き込み操作では 1 ずつ増加し、読み取り操作では 1 ずつ減少します。

        カウンタが0であればFIFOが空の状態であると判断しやすく、カウンタの値がFIFOのサイズと等しい場合にはFIFOがフル状態であると判断できる。

        カウンタを使用して空か満杯かを判断するこの方法は実装が比較的簡単ですが、読み取りポインタと書き込みポインタの位置を比較する以前の方法と比較すると、リソースの使用量が高くなります。この方法ではカウントするために追加のハードウェア (カウンター) が必要になるためです。

コード

        簡単に言うと、FIFO は空か満杯かを判断するロジックを備えたデュアルポート RAM です。ポインタサイクルによって満杯か空かを判断する同期 FIFO を作成しましょう。ただし、デュアルポート RAM を書く前に、データを保存します。

デュアルポートRAM

module dual_port_ram#(
    parameter DEPTH = 16,
    parameter WIDTH = 8
)(
    input                       wr_clk      ,
    input                       wr_en       ,
    input   [$clog(DEPTH)-1:0]  wr_addr     ,
    input   [WIDTH-1:0]         wr_data     , 

    input                       rd_clk      ,
    input                       rd_en       ,
    input   [$clog(DEPTH)-1:0]  rd_addr     ,
    output  [WIDTH-1:0]         rd_data
);
reg [WIDTH-1:0] RAM_MEM [DEPTH-1:0];

always @(posedge wr_clk)begin
    if(wr_en)
        RAM_MEM[wr_addr] <= wr_data;
end

always @(posedge rd_clk)begin
    if(rd_en)
        RAM_MEM[rd_addr] <= rd_data;     
end

endmodule

        デュアルポート RAM 全体は実際には、書き込みが有効になっているときに入力にデータを書き込む単純な書き込みアドレスです。読み取りが有効になると、アドレスからデータを読み取る機能の後に、FIFO 内のデュアル ポート RAM モジュールがインスタンス化されます。

同期FIFO

`include "clog.v"

module sync_fifo#(
    parameter WIDTH = 8     ,
    parameter DEPTH = 16 
)(
    input                       clk         ,       
    input                       rstn        ,   // reset while rstn is negative
    
    //write interface
    input       [WIDTH-1:0]     data_in     ,   // input data 
    input                       wr_en       ,   // write enable 
    
    //read interface
    input                       rd_en       ,   // read enable
    output      [WIDTH-1:0]     data_out    ,

    output  reg                 fifo_empty  ,
    output  reg                 fifo_full     
);

//signal define 
reg [clog(DEPTH)-1:0]    wr_ptr;
reg [clog(DEPTH)-1:0]    rd_ptr;

wire wr_fifo;
wire rd_fifo;

//write data opration
always @(posedge clk or negedge rstn)begin
    if(!rstn)
        wr_ptr <= 1'b0;
    else if(wr_fifo)
        wr_ptr <= wr_ptr + 1'b1;  
end

assign wr_fifo = wr_en && !fifo_full;

//read data opration
always @(posedge clk or negedge rstn)begin
    if(!rstn)
        rd_ptr <= 1'b0;
    else if(rd_fifo)
        rd_ptr <= rd_ptr + 1'b1;
end

assign rd_fifo = rd_en && !fifo_empty;

//full signal judgment
always@(posedge clk or negedge rstn)begin
    if(!rstn)
        fifo_full <= 1'b0;
    else if(wr_fifo && rd_fifo)
        ;//do nothing
    else if(rd_fifo)
        fifo_full <= 1'b0;
    else if((rd_ptr == wr_ptr + 1'b1) && wr_fifo)
        fifo_full <= 1'b1;
end

//empty signal judgment
always @(posedge clk or negedge rstn)begin
    if(!rstn)
        fifo_empty <= 1'b1;
    else if(wr_fifo && rd_fifo)
        ;//do nothing
    else if(wr_fifo)
        fifo_empty <= 1'b0;
    else if((wr_ptr == rd_ptr + 1'b1) && rd_fifo)
        fifo_empty <= 1'b1;
end


dual_port_ram #(
    .DEPTH      (DEPTH)     ,
    .WIDTH      (WIDTH)
)u_dual_port_ram
(
    .wr_clk      (clk)       ,       //sync FIFO ,wr_clk = rd_clk
    .wr_en       (wr_fifo)   ,
    .wr_addr     (wr_ptr)    ,
    .wr_data     (data_in)   ,

    .rd_clk      (clk)       ,
    .rd_en       (rd_fifo)   ,
    .rd_addr     (rd_ptr)    ,
    .rd_data     (data_out)
);

endmodule

        さらに、ビット幅を判断する関数 clog.v も書きました

`ifndef MY_CLOG
`define MY_CLOG

function integer clog (input integer depth);
    begin
        for (clog=0; depth-1>0; clog=clog+1) 
            depth = depth >>1;                          
    end
endfunction

`endif

テストベンチ

`timescale 1ns/1ns

module sync_fifo_tb();

parameter WIDTH = 8;
parameter DEPTH = 16;

reg                     clk         ;
reg                     rstn        ;

reg     [WIDTH-1:0]     data_in     ;
reg                     rd_en       ;
reg                     wr_en       ;

wire    [WIDTH-1:0]     data_out    ;
wire                    empty       ;
wire                    full        ;

always #5 clk = ~clk;

initial begin
    clk     <= 1'b0;
    rstn    <= 1'b0;
    data_in <= 'd0;
    rd_en   <= 1'b0;
    wr_en   <= 1'b0;
    
    //write 16 times to make fifo full
    #10
    rstn    <= 1'b1;
    repeat(16)begin
        @(negedge clk)begin
            wr_en   <= 1'b1;
            data_in <= $random; // generate 8bit random number data_in
        end
    end
    
    //read 16 times to make fifo empty
    repeat(16)begin
        @(negedge clk)begin
            wr_en   <= 1'b0;
            rd_en   <= 1'b1;
        end
    end

    //read and write 8 times
    repeat(8)begin
        @(negedge clk)begin
            wr_en   <= 1'b1;
            data_in <= $random; 
            rd_en   <= 1'b0;
        end
    end

    //Continuous read and write
    forever begin
        @(negedge clk)begin
            wr_en   <= 1'b1;
            data_in <= $random;
            rd_en   <= 1'b1;
        end
    end
end

initial begin
    #800
    $finish();
end

initial begin
    $fsdbDumpfile("sync_fifo.fsdb");
    $fsdbDumpvars(0);
end

sync_fifo #(
    .WIDTH      (WIDTH)     ,
    .DEPTH      (DEPTH)
)u_sync_fifo
(
    .clk        (clk)       ,
    .rstn       (rstn)      ,
    .data_in    (data_in)   ,
    .rd_en      (rd_en)     ,
    .wr_en      (wr_en)     ,

    .data_out   (data_out)  ,
    .fifo_empty (empty)     ,
    .fifo_full  (full)
);

endmodule

波形

        シミュレーション結果は解析結果と一致しています。16 個のデータを書き込んで FIFO を埋めると、この時点でフル信号が High に引き上げられます。16 個のデータを読み出して FIFO を空にすると、この時点で空の信号が High に引き上げられます。書き込み後8 データ、同時読み取りと書き込み、書き込みデータと読み取りデータは一貫しており、正しく機能します。

        同期 FIFO の設計方法を理解すると、非同期 FIFO の設計は比較的簡単になります。次のブログでは、FIFO のクロック ドメインをまたぐ問題を解決するための非同期 FIFO の作成方法を記録します。


        ハンドテアリング コードに関するその他の質問については、 デジタル IC ハンドテアリング コード - 質問バンクにアクセスしてください。

おすすめ

転載: blog.csdn.net/qq_57502075/article/details/128180751