FC2カウンター FPGAの部屋 画像のフレームバッファ2(ライフサイクルをシミュレーション)
FC2ブログ

FPGAやCPLDの話題やFPGA用のツールの話題などです。 マニアックです。 日記も書きます。

FPGAの部屋

FPGAの部屋の有用と思われるコンテンツのまとめサイトを作りました。Xilinx ISEの初心者の方には、FPGAリテラシーおよびチュートリアルのページをお勧めいたします。

画像のフレームバッファ2(ライフサイクルをシミュレーション)

画像のフレームバッファ”でAXI VDMAのProduct Guide にトリプルバッファでフレームレートの異なるWrite, Read で表示が乱れないようだということを学習した。つまり、カメラ表示システムで言うと、カメラデータのWrite とディスプレイに表示するためのRead のフレームレートが異なっていても、トリプルバッファで切断ラインが出ないように表示することができるということだ。今回は、ツィッターの @ikwzm さんのツィートをご紹介してから、そのアルゴリズムをシミュレーションしてみる。トリプルバッファの各フレームバッファの状態遷移をシミュレーションで見ることにする。

最初に、ツィッターの @ikwzm さんに教えていただいたツィートを下に示す。ツィートの引用を許可していただいてありがとうございました。





このアルゴリズムに従って、トリプルバッファのライフサイクルを模式的にシミュレーションしてみた。
多分、AXI VDMAはWriteストリームクロックとReadストリームクロック、それにAXIバスのクロックと3つのクロックがあるので、推測だが、Write, Read の信号をAXIバスのクロックに載せ替えてステートマシンを組んであるのではないか?と思う。今回のシミュレーションは1つのクロックで動作するので、そのまま実際のハードウェアに持っていくことは出来ないが、各フレームバッファの振る舞いは見ることができる。
さて、それではVerilog HDL ソースから示す。下に、フレームバッファのVerilog HDLファイルの fb_life_cycle.v を下に示す。(D:\HDL\FndtnISEWork\Test\fb_life_cycle)

// frame buffer life cycle
//

`default_nettype none

module fb_life_cycle (
    input    wire    clk,
    input    wire    reset,
    input    wire    write_req,
    input    wire    display_req,
    input    wire    wtime_is_zero,
    input    wire    dtime_is_zero,
    output    reg        [1:0]    state
);
    localparam     wait_write =     2'b00,
                writing    =        2'b01,
                displaying =    2'b10,
                wait_display =    2'b11;
    
    always @(posedge clk) begin
        if (reset)
            state <= wait_write;
        else begin
            case (state)
                wait_write :
                    if (write_req)
                        state <= writing;
                writing :
                    if (write_req)
                        state <= writing;
                    else if (wtime_is_zero)
                        state <= wait_display;
                wait_display :
                    if (display_req)
                        state <= displaying;
                displaying :
                    if (display_req)
                        state <= displaying;
                    else if (dtime_is_zero)
                        state <=wait_write;
            endcase
        end
    end
    
    // synthesis translate_off
     reg [12*8:1] FB_STATE;
     
     always @(state) begin
        case (state)
            wait_write:        FB_STATE <= "WAIT_WRITE";
            writing:        FB_STATE <= "WRITING";
            displaying :    FB_STATE <= "DISPLAYING";
            wait_display :    FB_STATE <= "WAIT_DISPLAY";
        endcase
    end    
    // synthesis translate_on
endmodule

`default_nettype wire    


テストベンチ fb_life_cycle_tb.v を下に示す。9行目と10行目の write_life_cycle, display_life_cycle は画像のWrite, Read のフレームレートを式の分母に代入する。現在の例は、どちらも 60fps の場合だ。

// Frame Buffer Life Cycle Testbench
//

`default_nettype none

`timescale 1us / 100ps

module fb_life_cycle_tb;
    parameter    integer    write_life_cycle    = 100000/60;    // 60fps
    parameter    integer display_life_cycle    = 100000/60;    // 60fps

    localparam     wait_write =     2'b00,
                writing    =        2'b01,
                displaying =    2'b10,
                wait_display =    2'b11;

    wire    clk;
    wire    reset;
    wire    [1:0]    state_1;
    wire    [1:0]    state_2;
    wire    [1:0]    state_3;
    reg        write_req_1, write_req_2, write_req_3;
    reg        display_req_1, display_req_2, display_req_3;
    reg        [23:0] wtime;
    reg        [23:0] dtime;
    wire    wtime_is_zero, dtime_is_zero;
    reg        reset_1b;
    
    // clk のインスタンス
    clk_gen #(
        .CLK_PERIOD(10),    // 10usec, 100kHz
        .CLK_DUTY_CYCLE(0.5),
        .CLK_OFFSET(0),
        .START_STATE(1'b0)
    ) clki (
        .clk_out(clk)
    );
    
    // reset_gen のインスタンス
    reset_gen #(
        .RESET_STATE(1'b1),
        .RESET_TIME(10)    // 10usec
    ) RESETi (
        .reset_out(reset)
    );
    
    // Frame Buffer のインスタンス
    fb_life_cycle fb_life_cycle_1 (
        .clk(clk),
        .reset(reset),
        .write_req(write_req_1),
        .display_req(display_req_1),
        .wtime_is_zero(wtime_is_zero),
        .dtime_is_zero(dtime_is_zero),
        .state(state_1)
    );
    
    fb_life_cycle fb_life_cycle_2 (
        .clk(clk),
        .reset(reset),
        .write_req(write_req_2),
        .display_req(display_req_2),
        .wtime_is_zero(wtime_is_zero),
        .dtime_is_zero(dtime_is_zero),
        .state(state_2)
    );
    
    fb_life_cycle fb_life_cycle_3 (
        .clk(clk),
        .reset(reset),
        .write_req(write_req_3),
        .display_req(display_req_3),
        .wtime_is_zero(wtime_is_zero),
        .dtime_is_zero(dtime_is_zero),
        .state(state_3)
    );
    
    always @(posedge clk) begin
        if (reset)
            wtime <= write_life_cycle - 1;
        else begin
            if (wtime == 24'd0)
                wtime <= write_life_cycle - 1;
            else
                wtime <= wtime - 24'd1;
        end
    end
    assign wtime_is_zero = (wtime == 24'd0) ? 1'b1 : 1'b0;
    
    always @(posedge clk) begin
        if (reset)
            dtime <= display_life_cycle - 1;
        else begin
            if (dtime == 24'd0)
                dtime <= display_life_cycle - 1;
            else
                dtime <= dtime - 24'd1;
        end
    end
    assign dtime_is_zero = (dtime == 24'd0) ? 1'b1 : 1'b0;
    
    always @(posedge clk) begin
        reset_1b <= reset;
    end 
    
    always @* begin
        if (~reset & reset_1b) begin
            write_req_1 <= 1'b1; write_req_2 <= 1'b0; write_req_3 <= 1'b0;
        end else if (wtime_is_zero) begin
            if (state_1 == wait_write) begin
                write_req_1 <= 1'b1; write_req_2 <= 1'b0; write_req_3 <= 1'b0;
            end else if (state_2 == wait_write) begin
                write_req_1 <= 1'b0; write_req_2 <= 1'b1; write_req_3 <= 1'b0;
            end else if (state_3 == wait_write) begin
                write_req_1 <= 1'b0; write_req_2 <= 1'b0; write_req_3 <= 1'b1;
            end else if (state_1==writing && state_2!=wait_write && state_3!=wait_write) begin
                write_req_1 <= 1'b1; write_req_2 <= 1'b0; write_req_3 <= 1'b0;
            end else if (state_2==writing && state_1!=wait_write && state_3!=wait_write) begin
                write_req_1 <= 1'b0; write_req_2 <= 1'b1; write_req_3 <= 1'b0;
            end else if (state_3==writing && state_2!=wait_write && state_3!=wait_write) begin
                write_req_1 <= 1'b0; write_req_2 <= 1'b0; write_req_3 <= 1'b1;
            end else begin
                write_req_1 <= 1'b0; write_req_2 <= 1'b0; write_req_3 <= 1'b0;
            end
        end else begin
            write_req_1 <= 1'b0; write_req_2 <= 1'b0; write_req_3 <= 1'b0;
        end
    end
    
    always @* begin
        if (dtime_is_zero) begin
            if (state_1 == wait_display) begin
                display_req_1 <= 1'b1; display_req_2 <= 1'b0; display_req_3 <= 1'b0;
            end else if (state_2 == wait_display) begin
                display_req_1 <= 1'b0; display_req_2 <= 1'b1; display_req_3 <= 1'b0;
            end else if (state_3 == wait_display) begin
                display_req_1 <= 1'b0; display_req_2 <= 1'b0; display_req_3 <= 1'b1;
            end else if (state_1==displaying && state_2!=wait_display && state_3!=wait_display) begin
                display_req_1 <= 1'b1; display_req_2 <= 1'b0; display_req_3 <= 1'b0;
            end else if (state_2==displaying && state_1!=wait_display && state_3!=wait_display) begin
                display_req_1 <= 1'b0; display_req_2 <= 1'b1; display_req_3 <= 1'b0;
            end else if (state_3==displaying && state_1!=wait_display && state_2!=wait_display) begin
                display_req_1 <= 1'b0; display_req_2 <= 1'b0; display_req_3 <= 1'b1;
            end else begin
                display_req_1 <= 1'b0; display_req_2 <= 1'b0; display_req_3 <= 1'b0;
            end
        end else begin
            display_req_1 <= 1'b0; display_req_2 <= 1'b0; display_req_3 <= 1'b0;
        end
    end

    // synthesis translate_off
    localparam    integer    CHAR_NUM    = 12;
    
    reg [CHAR_NUM*8:1] FB_STATE_1;
    reg [CHAR_NUM*8:1] FB_STATE_2;
    reg [CHAR_NUM*8:1] FB_STATE_3;

    always @(state_1) begin
        case (state_1)
            wait_write:        FB_STATE_1 <= "WAIT_WRITE";
            writing:        FB_STATE_1 <= "WRITING";
            displaying :    FB_STATE_1 <= "DISPLAYING";
            wait_display :    FB_STATE_1 <= "WAIT_DISPLAY";
        endcase
    end

    always @(state_2) begin
        case (state_2)
            wait_write:        FB_STATE_2 <= "WAIT_WRITE";
            writing:        FB_STATE_2 <= "WRITING";
            displaying :    FB_STATE_2 <= "DISPLAYING";
            wait_display :    FB_STATE_2 <= "WAIT_DISPLAY";
        endcase
    end
    
    always @(state_3) begin
        case (state_3)
            wait_write:        FB_STATE_3 <= "WAIT_WRITE";
            writing:        FB_STATE_3 <= "WRITING";
            displaying :    FB_STATE_3 <= "DISPLAYING";
            wait_display :    FB_STATE_3 <= "WAIT_DISPLAY";
        endcase
    end    
    // synthesis translate_on
endmodule
    
module clk_gen #(
    parameter         CLK_PERIOD = 100,
    parameter real    CLK_DUTY_CYCLE = 0.5,
    parameter        CLK_OFFSET = 0,
    parameter        START_STATE    = 1'b0 )
(
    output    reg        clk_out
);
    begin
        initial begin
            #CLK_OFFSET;
            forever
            begin
                clk_out = START_STATE;
                #(CLK_PERIOD-(CLK_PERIOD*CLK_DUTY_CYCLE)) clk_out = ~START_STATE;
                #(CLK_PERIOD*CLK_DUTY_CYCLE);
            end
        end
    end
endmodule

module reset_gen #(
    parameter    RESET_STATE = 1'b1,
    parameter    RESET_TIME = 100 )
(
    output    reg        reset_out
);
    begin
        initial begin
            reset_out = RESET_STATE;
            #RESET_TIME;
            reset_out = ~RESET_STATE;
        end
    end
endmodule

`default_nettype wire


これをISimでシミュレーションしてみた。これが 1sec シミュレーションした結果だ。
TripleBufferLifeCycle_1_130504.png

これだとわからないと思うので、後ろの方を拡大してみた。
TripleBufferLifeCycle_2_130504.png

FB_STATE_1, FB_STATE_2, FB_STATE_3 が各フレームバッファのステートを示す。それを見ると、どれかのフレームバッファが常にDISPLAYING となって表示が途切れないことがわかる。
ただし、DISPLAYINGのステートが本来のフレームレートの2倍に伸びている。これはフレームレートがぴったり同じだからで、どちらかのフレームレートが少しでも違っていれば、表示のフレームレートは本来のフレームレート 60fps になる。
下に、”write_life_cycle = 100000/61;”に変更した例を示す。ここでは、表示のフレームレートが本来の 60fps となった。
TripleBufferLifeCycle_3_130504.png

次に、Write が 30fps の例を下に示す。
TripleBufferLifeCycle_4_130504.png

Write が 15fps の例を下に示す。
TripleBufferLifeCycle_5_130504.png

Write が 12fps の例を下に示す。
TripleBufferLifeCycle_6_130504.png

このようにトリプルバッファリングすると、DISPLAYING が途切れることがなく、しかもWRITING と異なるので、画像に横筋が入ることはないということが推測できる。
  1. 2013年05月04日 05:06 |
  2. 画像処理
  3. | トラックバック:0
  4. | コメント:0

コメント

コメントの投稿


管理者にだけ表示を許可する

トラックバック URL
http://marsee101.blog.fc2.com/tb.php/2456-ebeff144
この記事にトラックバックする(FC2ブログユーザー)