2009/01/29(木)[NiosII][ACM] 高速化・ベンチマーク測定
@IT MONOistで新記事があがっとります.FPGAですが,Xilinxで,且つlinux環境下という個人的に役立ちそうな情報です.\HDLシミュレータの紹介・インスコ方法から記載されていますので,良い資料になるでしょう.
-ザ・組み込み-ソフトウェアのハードウェア化(6)\追い出したソフトウェアを“ハードウェア化”する準備\鳥海 佳孝 設計アナリスト 2009/1/29
こうやって入門記事はそれなりに出てくるものなので,あえて競合する必要は無いなぁと思う次第であります.\そこで我々中途半端層の使命は,本能の赴くままに遊んで資料を残していくことだけ.見返りを求めて手を動かしていてはいけないと悟りました(キリッ\まぁ突き詰めて考えれば,誰かに認めてもらいたくて行動しているのでしょうがねぇ(笑)
QuartusIIもperlやらコアなところはcygwin環境でまわしてるようなので、Windowsよりもlinux版のほうが軽いような気がするのですが,実際のところはどうなんですかね….
閑話休題.本編は以降へ...
[FPGA][NiosII][ACM] 高速化・ベンチマーク測定
ALTERAで行こう!で紹介されている,暫定公開 → 軽量動画フォーマットの研究のアレをナニして楽しよう!検証していこうという企画であります(しれっ\管理日誌のほうに,マイコン側の展開高速化や時期verの構想の話があったりするので,NEEKで動画するならこれだろう,と.
全部ソフトだと話しにならないので,YUV210→ RGB変換処理をカスタム命令にしてみまんた.コアは/fを適用し,MMU/MPUは殺してあります.D-cacheも有効にして期待が高まりますが...?
緒言ぽいもの:最適化あり,Nios/f・Icache/Dcacheあり,Release向けbuild
~ばらつきは数ミリオーダで存在する模様.\この日記を書きながらコードを見てると,明らかに要らん処理がはいってます.全く同じ処理の比較ではないので,これはちょっとイカサマくさいです.それでも結構なステップを端折ることができているでしょう.
以下,測定結果.画像はどこかの果物の写真を拾ってきました.それなりに大きいかな?
File | dot size | MCU size |
---|---|---|
NATU03~1.ACM(natu03.bmp) | 405x315 | 51x40 |
CHERRY~1.ACM(cherry04.bmp) | 405x315 | 51x40 |
CHERRY~2.ACM(cherry05.bmp) | 405x315 | 51x40 |
【カスタム命令ナシ】
FILE: NATU03~1.ACM, size = 45912
Total Time: 0.233295 seconds (23105221 clock-cycles)
Section | % | Time (sec) | Time (clocks) | Occurrences |
---|---|---|---|---|
mcu decode | 72.5 | 0.16922 | 16759280 | 2040 |
dcb decode | 16.9 | 0.03945 | 3907418 | 12240 |
FILE: CHERRY~1.ACM, size = 48362
Total Time: 0.234586 seconds (23232991 clock-cycles)
Section | % | Time (sec) | Time (clocks) | Occurrences |
---|---|---|---|---|
mcu decode | 72.9 | 0.17110 | 16945317 | 2040 |
dcb decode | 17.6 | 0.04137 | 4097390 | 12240 |
FILE: CHERRY~2.ACM, size = 33638
Total Time: 0.216018 seconds (21394127 clock-cycles)
Section | % | Time (sec) | Time (clocks) | Occurrences |
---|---|---|---|---|
mcu decode | 71.3 | 0.15393 | 15244756 | 2040 |
dcb decode | 11.3 | 0.02450 | 2426448 | 12240 |
【カスタム命令アリ】
FILE: NATU03~1.ACM, size = 45912
Total Time: 0.149199 seconds (14776427 clock-cycles)
Section | % | Time (sec) | Time (clocks) | Occurrences |
---|---|---|---|---|
mcu decode | 58.4 | 0.08714 | 8630226 | 2040 |
dcb decode | 25.4 | 0.03783 | 3746338 | 12240 |
FILE: CHERRY~1.ACM, size = 48362
Total Time: 0.15123 seconds (14977634 clock-cycles)
Section | % | Time (sec) | Time (clocks) | Occurrences |
---|---|---|---|---|
mcu decode | 58.9 | 0.08907 | 8821080 | 2040 |
dcb decode | 26.3 | 0.03976 | 3937702 | 12240 |
FILE: CHERRY~2.ACM, size = 33638
Total Time: 0.133595 seconds (13231058 clock-cycles)
Section | % | Time (sec) | Time (clocks) | Occurrences |
---|---|---|---|---|
mcu decode | 54 | 0.07210 | 7141035 | 2040 |
dcb decode | 17.2 | 0.02301 | 2278744 | 12240 |
カスタム命令
HDL持ってくるの忘れたので,後日貼ります.誰が書いても同じようなモンになりそうですしネw
// Verilog Custom Instruction Template File for Internal Register Logic module custom_inst_yuv2rgb ( clk, // CPU system clock (required for multi-cycle or extended multi-cycle) reset, // CPU master asynchronous active high reset (required for multi-cycle or extended multicycle) clk_en, // Clock-qualifier (required for multi-cycle or extended multi-cycle) start, // Active high signal used to specify that inputs are valid (required for multi-cycle or extended multi-cycle) done, // Active high signal used to notify the CPU that result is valid (required for variable multi-cycle or extended variable multi-cycle) dataa, // Operand A (always required) datab, // Operand B (optional) result // Result (always required) ); // INPUTS input clk; input reset; input clk_en; input start; input [31:0] dataa; // Y(0.255) input [31:0] datab; // u(-128..127), v(-128..127) // OUTPUTS output reg done; output reg [31:0] result; // temporary regs. reg signed [17:0] calc_R, calc_G, calc_B ; // to refer parameter wire signed [8:0] dy_i ; wire signed [8:0] du_i, dv_i ; // internal regs/wires reg [3:0] conv_state ; wire signed [17:0] d_Rv1, d_Rv2 ; wire signed [17:0] d_Gu1, d_Gv2 ; wire signed [17:0] d_Bu1, d_Bu2 ; localparam STAT_IDLE = 4'b0001; localparam STAT_STEP1 = 4'b0010; localparam STAT_STEP2 = 4'b0100; localparam STAT_FINISH = 4'b1000; assign dy_i = { 1'b0, dataa[ 7: 0] } ; // unsigned 0..255 assign du_i = { datab[15], datab[15: 8] } ; // signed -128..127 assign dv_i = { datab[7], datab[ 7: 0] } ; // signed -128..127 always @ (posedge reset or posedge clk) begin if (reset) begin conv_state <= STAT_IDLE ; done <= 1'b0 ; result <= 32'h00000000 ; calc_R <= 18'h0; calc_G <= 18'h0; calc_B <= 18'h0; end else begin if (clk_en) begin case (conv_state) STAT_IDLE: begin if (start) begin calc_R[17:0] <= {1'b0, dy_i[8:0], 8'h0000 } ; calc_G[17:0] <= {1'b0, dy_i[8:0], 8'h0000 } ; calc_B[17:0] <= {1'b0, dy_i[8:0], 8'h0000 } ; conv_state <= STAT_STEP1 ; end else begin done <= 1'b0 ; end end STAT_STEP1: begin calc_R <= calc_R + d_Rv1 ; calc_G <= calc_G + d_Gu1 ; calc_B <= calc_B + d_Bu1 ; conv_state <= STAT_STEP2 ; end STAT_STEP2: begin calc_R <= calc_R + d_Rv2 ; calc_G <= calc_G + d_Gv2 ; calc_B <= calc_B + d_Bu2 ; conv_state <= STAT_FINISH ; end STAT_FINISH: begin done <= 1'b1 ; result[31:24] <= 8'h0 ; if (calc_R[17]==1'b1) result[23:16] <= 8'h0 ; else if (calc_R[16]==1'b1) result[23:16] <= 8'hFF ; else result[23:16] <= calc_R[15:8] ; if (calc_G[17]==1'b1) result[15: 8] <= 8'h0 ; else if (calc_G[16]==1'b1) result[15: 8] <= 8'hFF ; else result[15: 8] <= calc_G[15:8] ; if (calc_B[17]==1'b1) result[ 7: 0] <= 8'h0 ; else if (calc_B[16]==1'b1) result[ 7: 0] <= 8'hFF ; else result[ 7: 0] <= calc_B[15:8] ; conv_state <= STAT_IDLE ; end endcase end end end assign d_Rv1 = { dv_i[8], dv_i[8:0], 8'h00 }; // v x 256 MUL_RV MUL_RV_inst ( // 9'h067; .clken ( clk_en ), .clock ( clk ), .dataa ( dv_i ), .result ( d_Rv2 ) ); MUL_GU MUL_GU_inst( // 9'hfa8; .clken ( clk_en ), .clock ( clk ), .dataa ( du_i ) , .result ( d_Gu1 ) ); MUL_GV MUL_GV_inst( // 9'hf49; .clken ( clk_en ), .clock ( clk ), .dataa ( dv_i ) , .result ( d_Gv2 ) ); assign d_Bu1 = {du_i[8], du_i[8:0], 8'h00 }; // u x 256 MUL_BU MUL_BU_inst ( // 9'h0c6; .clken ( clk_en ), .clock ( clk ), .dataa ( du_i ), // signed 9bit (mul 198. you must mult 256 and add) .result ( d_Bu2 ) // 18bit ); endmodule
※MUL_xxは乗算器をMega Wizardで生成しました.それぞれ9bitx9bit符号有りの乗算器です.
ソースコード(抜粋)
未掲載部分はオリジナルと同一.ベータ版というか動作保障ナシで各自自己責任でお使いください.\改造ポイントと,計測ポイントを示すために貼っておきます.ノークレームノーリターンでお願いします.(?)
UJIYA_CI_YUV2RGB_MACRO は 引数2つのカスタムインストラクション番号0呼び出しです.これももってくるの忘れたので,後日追記しておきます.
// カスタム命令呼び出しのマクロ #define UJIYA_CI_YUV2RGB_N 0x00 #define UJIYA_CI_YUV2RGB_MACRO(n,Y,UV)\ __builtin_custom_inii( UJIYA_CI_YUV2RGB_N, (Y),(UV)) int mcu_decode ( // MCUのワードサイズを返す unsigned short *pMCU, // MCUデータポインタ mcu_rgb32 *pP, // 展開先ポインタ int cmode // 出力カラーモード ) { int i,n,x,y; int r,g,b; int tu[16],tv[16],ty[16], *yy; int c,mcu_n; // U成分とV成分の展開 PERF_BEGIN( PERFORMANCE_COUNTER_BASE, 2); c = dcb_decode(pMCU, tu, -128); PERF_END(PERFORMANCE_COUNTER_BASE, 2); pMCU += c; mcu_n = c; PERF_BEGIN( PERFORMANCE_COUNTER_BASE, 2); c = dcb_decode(pMCU, tv, -128); PERF_END(PERFORMANCE_COUNTER_BASE, 2); pMCU += c; mcu_n += c; // Y成分の展開とRGB変換 i = 0; // U,Vのindex for(n=0 ; n<4 ; n++) { PERF_BEGIN( PERFORMANCE_COUNTER_BASE, 2); c = dcb_decode(pMCU, ty, 0); PERF_END(PERFORMANCE_COUNTER_BASE, 2); pMCU += c; mcu_n += c; yy = &ty[0] ; // yyはシーケンシャル for(y=0 ; y<4 ; y++) { // YUV420をRGBに変換 for(x=0 ; x<4 ; x++) { #if 1 /* ここでカスタム命令とソフト処理とを切り替える */ mcu_rgb32 d_rgb ; d_rgb = UJIYA_CI_YUV2RGB_MACRO(0, *yy, (((tu[i]&0xFF)<<8) | (tv[i]&0xFF))) ; // & bitmask[cmode]; *pP++ = d_rgb; #else r = (256 * (*yy) + 359 * tv[i]) >> 8; g = (256 * (*yy) - 88 * tu[i] - 183 * tv[i]) >> 8; b = (256 * (*yy) + 454 * tu[i] ) >> 8; if (r < 0) { r = 0; } else { r += dither[cmode][y][x]; if (r > 255) r = 255; } if (g < 0) { g = 0; } else { g += dither[cmode][y][x]; if (g > 255) g = 255; } if (b < 0) { b = 0; } else { b += dither[cmode][y][x]; if (b > 255) b = 255; } *pP++ = mcu_rgb32_pack(r, g, b) & bitmask[cmode]; // ビット落としをシミュレート #endif // Y,U,V index更新. U,Vはxが2dot単位だから,展開してしまったほうが分岐が減ると思うのね. yy++ ; if ((x & 3) == 3) { i-- ; } else if (x & 1) { i++; } } if (y & 1) { i += 4 ; } pP += 4; // 4dot進める } if (n==1) { i -= 2 ; pP -= 4 ; // 左下の座標へ移動. } else { i -= 6 ; pP += (4 - 8*4) ; // 右上の座標へ移動. } } return (mcu_n); } int load_acm2mem( char* fname, gr_off_bitmap** pOffBitmap ) { FRESULT res ; FIL fp ; UINT rsz ; ACM_HEADER* acm_header ; gr_off_bitmap* pRet = NULL; int x, y, mcu_blocks ; int xx, yy ; unsigned int p[8*8]; // p[8][8] ; unsigned int* pBuft ; unsigned short* pACM = NULL; unsigned short* pACM_buf = NULL; int mcu_words; if (pOffBitmap==NULL) { return E_PAR ; } *pOffBitmap = NULL ; if ((res = f_open (&fp, fname, FA_READ)) != FR_OK) { return (res | 0x1000); } pACM = malloc(fp.fsize); if (!pACM){ goto ERROR ; }// error res = f_read(&fp, pACM, fp.fsize, &rsz) ; if (rsz < fp.fsize) { goto ERROR ; } // error acm_header = (ACM_HEADER*)pACM ; pRet = (gr_off_bitmap*)malloc( sizeof(gr_off_bitmap) + acm_header->width * acm_header->height * sizeof(unsigned int) ) ; if (pRet==NULL) { goto ERROR; } pRet->width = acm_header->width ; pRet->height = acm_header->height ; pRet->depth = 4; // とりあえずRGB888で固定する. pBuft = (unsigned int*)&pRet->data[0] ; pACM_buf = &acm_header->data[0] ; mcu_blocks = 0 ; PERF_RESET( PERFORMANCE_COUNTER_BASE ); PERF_START_MEASURING( PERFORMANCE_COUNTER_BASE ); for(y=0 ; y< acm_header->height ; y+=8) { for(x=0 ; x< acm_header->width ; x+=8) { if (++mcu_blocks > acm_header->wNumOfMCU) { } // デコードしてみる PERF_BEGIN( PERFORMANCE_COUNTER_BASE, 1); mcu_words = mcu_decode(pACM_buf, &p[0], 0); // output color mode=0(as RGB888) PERF_END(PERFORMANCE_COUNTER_BASE, 1); pACM_buf += mcu_words ; for(yy=0 ; yy<8 ; yy++) { for(xx=0 ; xx<8 ; xx++) { if((x+xx)< acm_header->width && (y+yy)< acm_header->height) { pBuft[ (y+yy) * acm_header->width + (x+xx) ] = p[yy*8+xx] ;// p[yy][xx] ; } } } } } PERF_STOP_MEASURING(PERFORMANCE_COUNTER_BASE) ; perf_print_formatted_report( (void*)PERFORMANCE_COUNTER_BASE, alt_get_cpu_freq(), 2, "mcu decode","dcb decode" ); ERROR: if (pACM) { free(pACM) ; pACM = NULL; } f_close(&fp) ; *pOffBitmap = pRet ; return E_OK ; }
goto文否定信仰気味でしたが,アセンブラレベルで想像できるときや,ヘタに書いて可読性が下がるとき・個人でいじくってるときの逃げなどでは大いに活用してよいと思います.これもC++の例外を触ってから考えが変わりました...\制御できる(問題を全て把握した上での)使用は同じだろう,と.あまりグダグダ書くと叩かれそうなのでこの辺で(笑)
少しだけ考察(未検証)
YUVの配列要素番号の算出を,オリジナルでは(乗算+加算)としてあります.とりあえず乗算は悪という思想で展開してみましたが,サイクル数を見るとcycloneIIのコア/fでは4サイクル程度*1….\命令フェッチ考えるとヘタにステップが増えるほうが遅くなるという罠.フルアセンブラでチマチマ最適化してやるのがベストですね.レジスタ変数にすればある程度スタックアクセスも減らせるかもですね.
ステップ数,メインメモリからのデータ読み出し,命令読み出しを考慮して最適化しないと,実際の最速を得ることができないでしょう.特にI/Dどちらもcache有効なコアなので,単純に演算速度だけが聞いてくるわけでもないですネ.\いかにコア内部でデータ移動を閉じられるかがカギですね.
gccのインラインアセンブラか….AVRでちょっとかじった程度だからなぁ….
- Update 2.Feb.2009.
- "後日掲載"アイテムを追加(HDL/マクロ/bmpサイズ)