FC2カウンター FPGAの部屋 2012年11月19日
FC2ブログ

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

FPGAの部屋

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

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