FC2カウンター FPGAの部屋 画像処理

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

画像のフレームバッファ

私の作ってきたカメラの画像表示装置はシングルバッファでフレームレートを変換している。
Spartan-3A Starter KitでCMOSカメラ・ディスプレイ回路1(ブロック図)
カメラ表示用プラットフォームが完成
ZedBoard用CMOSカメラ回路の作製14(カメラボード改2)

シングルバッファでフレームレートの異なるカメラとディスプレイを同期すると、どんなことが起こるかというと縦に動くものを見ると横に筋が見える。
(ディスプレイのフレームレート)/(カメラのフレームレート)- 1 の数の横筋が見える。(注:カメラのフレームレートがディスプレイよりも低く、カメラのフレームレートの整数倍がディスプレイのフレームレートとする)
これは、ディスプレイが表示している間にカメラの書き換えている部分が通算(ディスプレイのフレームレート)/(カメラのフレームレート)- 1 箇所あって、それが積算して見えるということだと思う。つまり、同期のとれていないオシロスコープで波形が複数見えるのと一緒。
これを解消するには、ダブルバッファリングしてカメラとディスプレイの同期を取る必要があるかな?と考えていて、回路構成も考えていた。でも、AXI VDMAではトリプルバッファリングで任意のフラームレートのカメラとディスプレイを接続できることがわかった(たぶん)。
LogiCORE IP AXI Video Direct Memory Access v5.04a Product Guide PG020 December 18, 2012 の113ページからの”Triple Frame Buffer Example”がそれだ。最初に読んだときは、なんでトリプルバッファで任意のフレームレートの変換ができるかわからなかったが、ツィッターでikwzm さんに教えていただいてガッテンした。(ikzwm さん、ありがとうございました。とってもよく分かりました)
次は、そのフレームバッファのライフサイクルをシミュレーションしてみようと思う。

#ツィッターは偉大だと思う。と言うか、TLの方、いつも教えていただいてありがとうございます。これからも、よろしくお願いします。

VDMAにはそのほかにも、Genlock Synchronizationなどもあって、奥が深そう。よ~く、勉強したいと思う。とっても勉強になる。そして、AXI VDMAを使う方向で行ってみたい。
  1. 2013年05月02日 05:38 |
  2. 画像処理
  3. | トラックバック:0
  4. | コメント:0

RGB―YCbCr変換の検討3(切り捨てと0捨1入)

RGB―YCbCr変換の検討2(Verilog HDLで実装)”の続き。(タイトルを四捨五入から0捨1入に変更しました)

今回、今までやってきたのは計算時に256を掛け算して(つまり2進数で言うと8ビット左論理シフト)して、計算してから、結果として15ビット目から8ビット目までを取り出していた(256で割り算する。つまり2進数で言うと8ビット右論理シフト)。これはつまり7ビット目から0ビット目の小数部を切り捨てていたわけだ。
0捨1入も行うことができる。7ビット目を見て、1だったら15ビット目から8ビット目に+1を行えば、0捨1入となる。実は0捨1入は演算が1つ増えるため(+1の演算器が1つ増える)採用していないで切り捨てていた。切り捨てによる誤差は、0≦x<-1に収まり、0捨1入による誤差は、-0.5≦x<0.5に収まる。この様に切り捨ては四捨五入に比べて平均で0.5だけ値が低くなるといえると思う。今回は切り捨てと0捨1入によるリソース使用量の違いと動作周波数について、Zynq-7020デバイスについて見ていこうと思う。(実際の型番は、xc7z020-1clg484)

今回、0捨1入のRGB-YCbCr変換のVerilog HDLを下に示す。
(2012/11/26:Y の変域が16から235だったためVerilog HDLソースファイルを修正しました。下のインプリメント後の結果と合わなくなる場合があります)

// RGB - YCbCr変換
// Y = 0.257R + 0.504G + 0.098B + 16
// Cb = -0.148R - 0.291G + 0.439B + 128
// Cr = 0.439R - 0.368G - 0.071B + 128
// 但し、Yは255以上だったら飽和演算をして255に丸める。Cb, Crは16以下だったら16に、240以上だったら240に飽和演算を行う。
// 0捨1入バージョン

`default_nettype none

module conv_rgb2ycbcr (
    input    wire    [7:0]    red,
    input    wire    [7:0]    green,
    input    wire    [7:0]    blue,
    output    reg        [7:0]    y,
    output    reg        [7:0]    cb,
    output    reg        [7:0]    cr
);
    
    wire    [18:0]    y_lshift8;
    wire    [18:0]    cb_lshift8;
    wire    [18:0]    cr_lshift8;
    

    assign y_lshift8 = ({5'd0, red, 6'd0} + {10'd0, red, 1'd0}) +  ({4'd0, green, 7'd0} + {11'd0, green}) + ({7'd0, blue, 4'd0} + {8'd0, blue, 3'd0} + {11'd0, blue}) + 19'd4096;
    
    assign cb_lshift8 = 19'd0 - ({6'd0, red, 5'd0} + {9'd0, red, 2'd0} + {10'd0, red, 1'd0}) - ({5'd0, green, 6'd0} + {8'd0, green, 3'd0} + {10'd0, green, 1'd0}) + ({5'd0, blue, 6'd0} + {6'd0, blue, 5'd0} + {7'd0, blue, 4'd0}) + 19'd32768;
    
    assign cr_lshift8 = ({5'd0, red, 6'd0} + {6'd0, red, 5'd0} + {7'd0, red, 4'd0}) - ({5'd0, green, 6'd0} + {7'd0, green, 4'd0} + {8'd0, green, 3'd0} + {9'd0, green, 2'd0} + {10'd0, green, 1'd0}) - ({7'd0, blue, 4'd0} + {10'd0, blue , 1'd0}) + 19'd32768;
    
    always @* begin
        if (y_lshift8[18]==1'b1 || y_lshift8[17:8]<16) // マイナスまたは16以下なので16に丸める
            y <= 8'd16;
        else if (y_lshift8[17:8] > 235) // 235より大きければ235に丸める
            y <= 8'd235;
        else begin
            if (y_lshift8[7] == 1'b1) begin
                if (y_lshift8[15:8] == 8'd235)
                    y <= 8'd235;
                else
                    y <=  y_lshift8[15:8] + 8'd1;
            end else
                y <=  y_lshift8[15:8];
        end
    end
    
    always @* begin
        if (cb_lshift8[18]==1'b1 || cb_lshift8[17:8]<16) // マイナスまたは16以下なので16に丸める
            cb <= 8'd16;
        else if (cb_lshift8[17:8] > 240) // 240より大きければ240に丸める
            cb <= 8'd240;
        else begin
            if (cb_lshift8[7] == 1'b1) begin
                if (cb_lshift8[15:8] == 8'd240)
                    cb <= 8'd240;
                else
                    cb <=  cb_lshift8[15:8] + 8'd1;
            end else
                cb <=  cb_lshift8[15:8];
        end
    end
    
    always @* begin
        if (cr_lshift8[18]==1'b1 || cr_lshift8[17:8]<16) // マイナスまたは16以下なので16に丸める
            cr <= 8'd16;
        else if (cr_lshift8[17:8] > 240) // 240より大きければ240に丸める
            cr <= 8'd240;
        else begin
            if (cr_lshift8[7] == 1'b1) begin
                if (cr_lshift8[15:8] == 8'd240)
                    cr <= 8'd240;
                else
                    cr <=  cr_lshift8[15:8] + 8'd1;
            end else
                cr <=  cr_lshift8[15:8];
        end
    end
endmodule

`default_nettype wire


切り捨てと四捨五入の違いを示す。前回の切り捨てのタイミングを下図に示す。
Conv_RGB2YCbCr_2_121118.png

次に四捨五入のタイミングを下図に示す。
Conv_RGB2YCbCr_3_121119.png

両方の図は同じ、red = 120, green = 212, blue = 76 の位置にカーソルを置いてある。切り捨てに比べて、0捨1入はカーソルの前から cb の値が 82 になっているのがわかると思う。これは、カーソルの前でcb_lshif8 が 0x051b8 となって、7ビット目が1になって、cb の値が0捨1入で繰り上がっているからである。

切り捨てと0捨1入の違いがわかったところで、使用リソースを比較してみる。
前回の最後に示した切り捨ての時の使用リソースはSlice Registersを41個、Slice LUTsを240個使用していた。
今回の四捨五入は、Slice Registersを43個、Slice LUTsを262個使用していた。なお、どちらも、Slice Registersは、AND/OR logicsとしての使用だった。FPGA Editorを見ると、Slice Registersは6入力2出力あるLUTの出力を2つ出すためにスルーロジックとして使われているようだ。
2つを比較すると、0捨1入(43 + 262) / 切り捨て(41 + 240) x 100 = 108.5% となり、0捨1入の方が切り捨てより 8.5% のリソースを余計に使用していた。

次に、動作周波数を検証してみよう。
切り捨てのRGB-YCbCr変換回路をconv_rgb2ycbcr_round_down.v、0捨1入のRGB-YCbCr変換回路をconv_rgb2ycbcr_round_off.v として、それぞれに入力、出力両方にFFを入れたトップファイルを作製した。(conv_rgb2ycbcr_rd_top.v, conv_rgb2ycbcr_ro_top.v)
conv_rgb2ycbcr_rd_top.v を下に示す。

// conv_rgb2ycbcr_top.v

`default_nettype none

module conv_rgb2ycbcr_rd_top (
    input    wire    clk,
    input    wire    reset,
    input    wire    [7:0]    red,
    input    wire    [7:0]    green,
    input    wire    [7:0]    blue,
    output    reg        [7:0]    y,
    output    reg        [7:0]    cb,
    output    reg        [7:0]    cr
);

    reg        [7:0]    red_ff, green_ff, blue_ff;
    wire    [7:0]    y_node, cb_node, cr_node;
    
    always @(posedge clk) begin
        if (reset) begin
            red_ff        <= 0;
            green_ff    <= 0;
            blue_ff        <= 0;
        end else begin
            red_ff        <= red;
            green_ff    <= green;
            blue_ff        <= blue;
        end
    end
    
    conv_rgb2ycbcr_round_down conv_rgb2ycbcr_rd_inst (
        .red(red_ff),
        .green(green_ff),
        .blue(blue_ff),
        .y(y_node),
        .cb(cb_node),
        .cr(cr_node)
    );
    
    always @(posedge clk) begin
        if (reset) begin
            y    <= 0;
            cb    <= 0;
            cr    <= 0;
        end else begin
            y     <= y_node;
            cb    <= cb_node;
            cr    <= cr_node;
        end
    end
endmodule

`default_nettype wire


これで制約に、クロック周波数を148.5MHzにして、インプリメントを行った。
切り捨ての方のタイミング制約は満たされずに、クリティカル・パスは 10.237ns となった。最大の動作周波数は、97.684MHzということになる。
Conv_RGB2YCbCr_4_121119.png

0捨1入の方のタイミング制約も満たされずに、クリティカル・パスは 10.565ns となった。最大の動作周波数は94.652MHz ということになる。
Conv_RGB2YCbCr_5_121119.png

案外、切り捨てと0捨1入の遅延時間の差が少ない。クリティカル・パスの経路を調べてみることにした。
上のProject Navigator の右側のウインドウのConstraint の "clk" 148.5MHz HIGH 50% のリンクをクリックする。
クリティカル・パスにカーソルを持って行って、右クリックメニューからshow in Technology Viewer を選択する。
Conv_RGB2YCbCr_6_121119.png

Technology Viewer が見えて、切り捨てでは9個のブロックがFFからFFまでで使用されていることがわかる。
Conv_RGB2YCbCr_7_121119.png

同様に、四捨五入も見てみると、0捨1入でも9個のブロックが使用されていることがわかった。
Conv_RGB2YCbCr_8_121119.png

1つ加算器が増えても、クリティカル・パスで使用するスライス数は同じなので、使用リソースは多少増えるが、値が確からしい0捨1入を使用することにした。
今回はLUTがどのようにコンフィグされているかまで、踏み込んで調べなかったが、+1の演算器だと足される数?が定数なので、簡単化されているのだと思う。
  1. 2012年11月19日 05:31 |
  2. 画像処理
  3. | トラックバック:0
  4. | コメント:0

RGB―YCbCr変換の検討2(Verilog HDLで実装)

”RGB―YCbCr変換の検討1(変換式)”の続き。

前回は変換式をVerilog HDLで記述できる形に変換した。今回は、実際にVerilog-HDLでRGB-YCbCr変換を記述した。
まず、RGB-YCbCr変換を行うconv_rgb2ycbcr.v を下に示す。
(2012/11/27:conv_rgb2ycbcr.v を修正しました。以下のインプリメントの回路規模とかが違っている場合があります)

// RGB - YCbCr変換
// Y = 0.257R + 0.504G + 0.098B + 16
// Cb = -0.148R - 0.291G + 0.439B + 128
// Cr = 0.439R - 0.368G - 0.071B + 128
// 但し、Yは255以上だったら飽和演算をして255に丸める。Cb, Crは16以下だったら16に、240以上だったら240に飽和演算を行う。
// 切り捨てバージョン

`default_nettype none

module conv_rgb2ycbcr_round_down (
    input    wire    [7:0]    red,
    input    wire    [7:0]    green,
    input    wire    [7:0]    blue,
    output    reg        [7:0]    y,
    output    reg        [7:0]    cb,
    output    reg        [7:0]    cr
);
    
    wire    [18:0]    y_lshift8;
    wire    [18:0]    cb_lshift8;
    wire    [18:0]    cr_lshift8;
    

    assign y_lshift8 = ({5'd0, red, 6'd0} + {10'd0, red, 1'd0}) +  ({4'd0, green, 7'd0} + {11'd0, green}) + ({7'd0, blue, 4'd0} + {8'd0, blue, 3'd0} + {11'd0, blue}) + 19'd4096;
    
    assign cb_lshift8 = 19'd0 - ({6'd0, red, 5'd0} + {9'd0, red, 2'd0} + {10'd0, red, 1'd0}) - ({5'd0, green, 6'd0} + {8'd0, green, 3'd0} + {10'd0, green, 1'd0}) + ({5'd0, blue, 6'd0} + {6'd0, blue, 5'd0} + {7'd0, blue, 4'd0}) + 19'd32768;
    
    assign cr_lshift8 = ({5'd0, red, 6'd0} + {6'd0, red, 5'd0} + {7'd0, red, 4'd0}) - ({5'd0, green, 6'd0} + {7'd0, green, 4'd0} + {8'd0, green, 3'd0} + {9'd0, green, 2'd0} + {10'd0, green, 1'd0}) - ({7'd0, blue, 4'd0} + {10'd0, blue , 1'd0}) + 19'd32768;
    
    always @* begin
        if (y_lshift8[18] == 1'b1 || y_lshift8[17:8]<16) // マイナスまたは16より小さいので16に丸める
            y <= 8'd16;
        else if (y_lshift8[17:8] > 235) // 235より大きければ235に丸める
            y <= 8'd235;
        else
            y <=  y_lshift8[15:8];
    end
    
    always @* begin
        if (cb_lshift8[18] == 1'b1 || cb_lshift8[17:8]<16) // マイナスまたは16より小さいので16に丸める
            cb <= 8'd16;
        else if (cb_lshift8[17:8] > 240) // 240より大きければ240に丸める
            cb <= 8'd240;
        else
            cb <=  cb_lshift8[15:8];
    end
    
    always @* begin
        if (cr_lshift8[18] == 1'b1 || cr_lshift8[17:8]<16) // マイナスまたは16より小さいので16に丸める
            cr <= 8'd16;
        else if (cr_lshift8[17:8] > 240) // 240より大きければ240に丸める
            cr <= 8'd240;
        else
            cr <=  cr_lshift8[15:8];
    end
endmodule

`default_nettype wire


次に、ISEのプロジェクトを作成して、conv_rgb2ycbcr.v をテストするテストベンチファイルconb_rgb2ycbcr.v を作製した。conb_rgb2ycbcr.v を下に示す。

`timescale 1ns / 1ps

////////////////////////////////////////////////////////////////////////////////
// Company: 
// Engineer:
//
// Create Date:   04:57:17 11/18/2012
// Design Name:   conv_rgb2ycbcr
// Module Name:   K:/HDL/FndtnISEWork/Zynq-7000/ZedBoard/test/conv_rgb2ycbcr/conv_rgb2ycbcr_tb.v
// Project Name:  conv_rgb2ycbcr
// Target Device:  
// Tool versions:  
// Description: 
//
// Verilog Test Fixture created by ISE for module: conv_rgb2ycbcr
//
// Dependencies:
// 
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
// 
////////////////////////////////////////////////////////////////////////////////

module conv_rgb2ycbcr_tb;

    // Inputs
    reg [7:0] red;
    reg [7:0] green;
    reg [7:0] blue;

    // Outputs
    wire [7:0] y;
    wire [7:0] cb;
    wire [7:0] cr;
    
    integer i = 0;
    integer j = 0;
    integer k = 0;

    // Instantiate the Unit Under Test (UUT)
    conv_rgb2ycbcr uut (
        .red(red), 
        .green(green), 
        .blue(blue), 
        .y(y), 
        .cb(cb), 
        .cr(cr)
    );

    initial begin
        // Initialize Inputs
        red = 0;
        green = 0;
        blue = 0;

        // Wait 100 ns for global reset to finish
        #100;
        
        // Add stimulus here
        for (i=0; i < 100; i = i + 10) begin
            red = i;
            green = i;
            blue = i;    // 色は白
            #10;
        end
        
        for (i=0; i<256; i=i+1) begin
            for (j=0; j<256; j=j+1) begin
                for (k=0; k<256; k=k+1) begin
                    red = i;
                    green = j;
                    blue = k;
                    #10;
                end
            end
        end
        
        $finish;
    end
      
endmodule


最初は、RGBとも同じ値をシミュレーションしている。つまり白色をシミュレーションしているわけだ。
白色の場合はYの値は変わるが、Cb, Crの値は128で固定されている。なお、RGBがオール0の場合はYは16となる。下に白色の場合のシミュレーション結果を示す。
Conv_RGB2YCbCr_1_121118.png

シミュレーション結果の一部を下に示す。
Conv_RGB2YCbCr_2_121118.png

(追加)試しにインプリメントしてみました。MAPのリポートの一部を下に示します。

Release 14.3 Map P.40xd (nt)
Xilinx Mapping Report File for Design 'conv_rgb2ycbcr'

Design Information
------------------
Command Line   : map -intstyle ise -p xc7z020-clg484-1 -w -logic_opt off -ol
high -t 1 -xt 0 -register_duplication off -r 4 -mt off -ir off -pr off -lc off
-power off -o conv_rgb2ycbcr_map.ncd conv_rgb2ycbcr.ngd conv_rgb2ycbcr.pcf 
Target Device  : xc7z020
Target Package : clg484
Target Speed   : -1
Mapper Version : zynq -- $Revision: 1.55 $
Mapped Date    : SUN 18 NOV 6:1:11 2012

Design Summary
--------------
Number of errors:      0
Number of warnings:   50
Slice Logic Utilization:
  Number of Slice Registers:                    41 out of 106,400    1%
    Number used as Flip Flops:                   0
    Number used as Latches:                      0
    Number used as Latch-thrus:                  0
    Number used as AND/OR logics:               41
  Number of Slice LUTs:                        240 out of  53,200    1%
    Number used as logic:                      238 out of  53,200    1%
      Number using O6 output only:             117
      Number using O5 output only:               8
      Number using O5 and O6:                  113
      Number used as ROM:                        0
    Number used as Memory:                       0 out of  17,400    0%
    Number used exclusively as route-thrus:      2
      Number with same-slice register load:      0
      Number with same-slice carry load:         2
      Number with other load:                    0

Slice Logic Distribution:
  Number of occupied Slices:                    93 out of  13,300    1%
  Number of LUT Flip Flop pairs used:          240
    Number with an unused Flip Flop:           199 out of     240   82%
    Number with an unused LUT:                   0 out of     240    0%
    Number of fully used LUT-FF pairs:          41 out of     240   17%
    Number of slice register sites lost
      to control set restrictions:               0 out of 106,400    0%

  1. 2012年11月18日 05:57 |
  2. 画像処理
  3. | トラックバック:0
  4. | コメント:0

RGB―YCbCr変換の検討1(変換式)

RGBで表されたピクセルをADV7511に表示させるためにはYCbCrに返還する必要がある。その変換回路を実現するためにRGB-YCbCr変換回路を設計することにした。先ずは変換式を検討してみた。

YUV-RGB変換1(方式の検討)”を式変換の参考にしながら、”YUVフォーマット及び YUV<->RGB変換”ページの”ITU-R BT.601 規定YCbCrと8bitフルスケールRGBの相互変換”を参考にさせて頂いた。
そのページによるとのRGB-YCbCrの変換式は下のようになる。

Y = 0.257R + 0.504G + 0.098B + 16
Cb = -0.148R - 0.291G + 0.439B + 128
Cr = 0.439R - 0.368G - 0.071B + 128
但し、Yは255以上だったら飽和演算をして255に丸める。Cb, Crは16以下だったら16に、240以上だったら240に飽和演算を行う。


これを式1とする。

式1の右辺に256を掛けて256で割ることにする。256は8ビットのシフト演算で行うことができる。
電卓の関係上10進数で計算する。最初のRの係数0.257に256を掛けると65.792になった。小数点以下を四捨五入すると、66になる。この様に係数に256を掛けて四捨五入を行うと式2が導けた。

Y = (66R + 129G + 25B + 4096) >> 8
Cb = (-38R - 74G + 112B + 32768) >> 8
Cr = (112R - 94G - 18B + 32768) >> 8


これを式2とする。>>8は8ビット右シフトするということで、これは256で割ったのと等価になる。
32768は0x8000_0000で、16ビット長で、最大値になる項は、129Gだから、最大値はGを255とした時で、129x255 = 32895 = 0x807F でやはり16ビットになる。演算器のビット幅は、16bit + (log2(項の数 = 4)の小数点以下切り上げ) + 符号ビット1bit = 19bit とする。

式2を更に2進数に変換する。

Y = (0100_0010R + 1000_0001G + 0001_1001B + 19'd4096) >> 8
Cb = (-0010_0110R - 0100_1010G + 0111_0000B + 19'd32768) >> 8
Cr = (0111_0000R - 0101_1110G - 0001_0010B + 19'd32768) >> 8


これを式3とする。

これで、Verilog HDL の式にできる形になった。これからVerilogでRGB-YCbCr変換回路を作る予定だ。
  1. 2012年11月17日 05:39 |
  2. 画像処理
  3. | トラックバック:0
  4. | コメント:0

Spartan-3A Starter KitでCMOSカメラ・ディスプレイ回路29(シミュレーション方法)

Spartan-3A Starter KitでCMOSカメラ・ディスプレイ回路28(プロジェクトの公開)”のシミュレーション方法を説明する。OVL(Open Verification Library)も入っているので、かなりハードルが高いと思う。
(注:シミュレーションを行う場合は、2010/9/28 午前5時27分以降にダウンロードしたプロジェクトをお使いください。ファイルを修正しました。)

最初に、”コマンドプロンプトからISimが起動できない”を参照して、自分のXILINX環境変数などを修正しておく。
次に、512Mb DDR2 Verilog ModelをMicron社のWebサイトからダウンロードする。解凍すると下のファイルが見えると思う。
CamDisp_DDR2_3_100928.png

Spartan-3A Starter KitでCMOSカメラ・ディスプレイ回路28(プロジェクトの公開)”で公開したフォルダのうちのDDR2_SDRAM_cont_266フォルダの下のSimulationフォルダに、512Mb_ddr2フォルダを新規作成する。その下に512Mb DDR2 Verilog Modelのすべてのファイルをコピーする。
CamDisp_DDR2_4_100928.png

次に、OVL(Open Verification Library)の準備をする。
1. ここからOVL Version 2.5を手に入れる。std_ovl_v2p5_July232010.tgz.tarがダウンロードされるので、適当なフォルダに解凍する。
2. ”ISimでOVLのVHDL, Verilog混在シミュレーション(OVLライブラリのコンパイルと登録)”を参照して、accellera_ovl_vlog, accellera_ovl_vhdlライブラリを生成して登録する。
3. std_ovlをインストールしたフォルダに合わせて、CamDispCntrler_DDR2_Capt_SCCB\Simulation\ISimフォルダの下のCamDispCntrler_DDR2_tb.batファイルを編集する。ピンクの四角で囲った部分のパスを修正する。
CamDisp_DDR2_5_100928.png

これで準備は終了した。次に、ISimを起動してシミュレーションを行う。
1. CamDispCntrler_DDR2_Capt_SCCB\Simulation\ISimフォルダの下のCamDispCntrler_DDR2_tb.batファイルをダブルクリックで起動すると、ISimが起動する。
2. FileメニューからOpenを選択して、出てきたダイアログからCamDispCntrler_DDR2_tb.wcfg を選んで開くと、波形ウインドウに信号名が表示される。
3. Consoleウインドウで"run all"とタイプしてリターンキーを押すとシミュレーションが開始し、自動的にSTOPする。
4. CamDispCntrler_DDR2_tb.vの$stopコマンドで止まったとの表示が出る。
CamDisp_DDR2_6_100928.png

5. 波形ウインドウのタブからCamDispCntrler_DDR2_tb.wcfg を選択し、Zoom to Full Viewアイコンをクリックすると下の図になる。
CamDisp_DDR2_7_100928.png

結構、面倒だと自分でも思ったが、これでシミュレーションをすることができる。
  1. 2010年09月28日 05:42 |
  2. 画像処理
  3. | トラックバック:0
  4. | コメント:0

Spartan-3A Starter KitでCMOSカメラ・ディスプレイ回路28(プロジェクトの公開)

今まで作ってきたSpartan-3A Starter KitでCMOSカメラ・ディスプレイ回路をダウンロード公開しようと思います。
いつものように保証はないので、自己責任でお使いください。原則転載禁止(転載したい方はメールでお知らせください)とします。
ここにSpartan-3A Starter KitでCMOSカメラ・ディスプレイ回路のプロジェクトを置いておくので、興味のある方はダウンロードして下さい。

今回は、CMOSカメラを購入したり、変換基板を作成しないとCMOSカメラがSpartan-3A Starter Kitのつながらないので、その説明をする。
CMOSカメラはaitendo カメラモジュール(OV7670)[CAMERA30W-OV7670] を使用している。
これをSpartan-3A Starter Kitに接続するために、ユニバーサル基板を使って、変換基板を作成した。使用した基板は秋月電子の両面スルーホールガラス・ユニバーサル基板 Cタイプ めっき仕上げ (72x48mm)P-03231で、これを必要な大きさに切って使っている。変換基板とCMOSカメラを結ぶソケットは秋月電子のピンソケット(メス) 2×8 (16P)C-02761、変換基板とSpartan-3A Starter KitのJ2を結ぶソケットは秋月電子のピンソケット(メス) 2×20 (40P) C-000085を使用した。変換基板とSpartan-3A Starter KitのJ2を結ぶソケットは40ピンだが、J2は36ピンなので、40ピンソケットの端の4ピン分はセロハンテープを2重に貼って、ヘッダが入らないようにしている。
下に、配線表を示す。
CamDispCntrler_DDR2_49_100927.png

SCCBのクロックとデータはJ2では端子数が足らなかったので、aitendo のピンヘッダ用接続ケーブ[CB-PH10P-250] を購入して、2ピン分だけ引き裂いて使用した。(なお、変換基板を作らなくても、aitendo のピンヘッダ用接続ケーブ[CB-PH10P-250] を2つ購入して、CMOSカメラとSpartan-3A Starter Kitを結んで、コードにアルミフォイルを巻けば、もしかしたら、いけるかもしれない?ただし、ノイズは多いものと思われる。”Spartan-3A Starter KitでCMOSカメラ・ディスプレイ回路12(ノイズ?)”参照)
下に変換基板の表側の写真を示す。配線表でcam_sio_cとcam_sio_dはJ2の端子が足りないため、J15に接続するように
、写真の左下のヘッダに配線されている。左下のヘッダからaitendo のピンヘッダ用接続ケーブ[CB-PH10P-250] でJ15に配線した。
CamDisp_DDR2_pict_50_100927.jpg

次に変換基板の裏側の写真を示す。
CamDisp_DDR2_pict_51_100927.jpg

実際に変換基板をSpartan-3A Starter KitのJ2に挿入して、J15の 5, 6番ピンに配線したのが下の写真だ。
CamDisp_DDR2_pict_52_100927.jpg

CMOSカメラをつけると下の写真のようになる。
CamDisp_DDR2_pict_53_100927.jpg

上の写真で操作の説明をすると、ピンクの四角はDDR2 SDRAMの上位アドレスを決めるスライドスイッチで、これを変更するとDDR2 SDRAMバッファのアドレスが変わるので、値を変えると違う画面になる。そして、黄色のスライドスイッチをONにすると、キャプチャ開始、つまり画像が表示される。黄色のスライドスイッチをOFFにすると、キャプチャ終了。つまり、直前の静止画になる。
これで、変換基板とCMOSカメラのつけ方は終了とする。

次に、プロジェクトの使い方を説明する。Spartan-3A Starter KitでCMOSカメラ・ディスプレイ回路のプロジェクトを解凍すると、2つのフォルダが見えると思う。CamDispCntrler_DDR2_Capt_SCCBとDDR2_SDRAM_cont_266だ。DDR2_SDRAM_cont_266は、実際はDDR2-250のDDR2 SDRAMコントローラで、つまり125MHz動作になっている。これは、DDR2 SDRAMコントローラのVerilog-HDLソースだけで、Simulationフォルダは空になっている。ここには、後で、Micron社からダウンロードした512MbitのDDR2 SDRAMのモデルを入れる予定だ。
CamDispCntrler_DDR2_Capt_SCCBの下に、Simulation、Soureces、Synth121フォルダがある。SimulationフォルダにはシミュレーションのテストベンチやISim用のバッチファイル等が置いてある。Sourecesフォルダには、CCMOSカメラ・ディスプレイ回路のソースが入っている。こっちはVerilog-HDLとVHDLの混在となっている。最後のSynth121ファルダは、ISE12.1のプロジェクトが置いてあるフォルダだ。ISE12.2でもプロジェクトを読み込んでそのままインプリメントすることができる。
それでは、ISE12.1を立ち上げて、CamDispCntrler_DDR2_Capt_SCCB\Synth121のCamDisp_Cntrler_DDR2.xise プロジェクトを読み込もう。下がプロジェクトを読み込んだISE12.1だ。
CamDisp_DDR2_1_100927.png

例によって、Generate Programming Fileをダブルクリックすると、論理合成が始まって、bitファイルが生成される。
CamDisp_DDR2_2_100927.png

bitファイルが生成されたので、iMPACTでコンフィギュレーションすれば、画像が写るはずだ。その前に、液晶ディスプレイをVGA端子に接続すればだが。。。

実際に動作させてみた方は、コメント欄等でお知らせ下さい。よろしくお願いします。
  1. 2010年09月27日 05:58 |
  2. 画像処理
  3. | トラックバック:0
  4. | コメント:0
»