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)405x31551x40
CHERRY~1.ACM(cherry04.bmp)405x31551x40
CHERRY~2.ACM(cherry05.bmp)405x31551x40


【カスタム命令ナシ】

FILE: NATU03~1.ACM, size = 45912

Total Time: 0.233295 seconds (23105221 clock-cycles)

Section %Time (sec) Time (clocks)Occurrences
mcu decode72.50.16922167592802040
dcb decode16.90.03945390741812240

FILE: CHERRY~1.ACM, size = 48362

Total Time: 0.234586 seconds (23232991 clock-cycles)

Section %Time (sec) Time (clocks)Occurrences
mcu decode72.90.17110169453172040
dcb decode17.60.04137409739012240

FILE: CHERRY~2.ACM, size = 33638

Total Time: 0.216018 seconds (21394127 clock-cycles)

Section %Time (sec) Time (clocks)Occurrences
mcu decode71.30.15393152447562040
dcb decode11.30.02450242644812240

【カスタム命令アリ】

FILE: NATU03~1.ACM, size = 45912

Total Time: 0.149199 seconds (14776427 clock-cycles)

Section %Time (sec) Time (clocks)Occurrences
mcu decode58.40.0871486302262040
dcb decode25.40.03783374633812240

FILE: CHERRY~1.ACM, size = 48362

Total Time: 0.15123 seconds (14977634 clock-cycles)

Section %Time (sec) Time (clocks)Occurrences
mcu decode58.90.0890788210802040
dcb decode26.30.03976393770212240

FILE: CHERRY~2.ACM, size = 33638

Total Time: 0.133595 seconds (13231058 clock-cycles)

Section %Time (sec) Time (clocks)Occurrences
mcu decode540.0721071410352040
dcb decode17.20.02301227874412240


カスタム命令

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サイズ)

*1 : 要確認.HW乗算器使用でコレだった気がする.後日データシート引用します..どこいったっけ...

2009/01/29(木)[NiosII][組込] IOアクセスの注意事項など

[NiosII][組込] NiosII コアを/fにしてD-cacheを有効にする場合の注意

NiosIIシステムはメモリマップドI/Oなので,I/Oに関するキャッシュ制御はどのようになっているのか気にはなっていた.

コアのconfigurationでキャッシュレスとして遊んできていたので,特に問題は起きてこなかった.キャッシュ有りとしてソフトの動作が不安定になったので,そのときに参照した資料へのポインタと,ドライバを各ユーザ向けのメモを残しておく.

こんなもんにはまってたらいかんのだが...

(引用元)

Nios II Software Developer’s Handbook (NII5V2-8.0, n2sw_nii5v2.pdf)\9. Cache and Tightly-Coupled Memory\ Writing Device Drivers

(意訳)

デバイスドライバでは,データキャッシュは命令セットのldio/stioファミリを使うことでバイパスしなければなりません.\データキャッシュの無いNios IIコアでは,これらの命令の挙動は 対応するld/st命令と同じように振舞うので,親切です?(benign).

Cプログラマのために.\volatileのようにポインタを宣言し,そのvolatileポインタを使ってアクセスしても,データキャッシュをバイパスすることはできないことに注意ください.\volatileキーワードはポインタを使ったアクセスを,コンパイラ最適化から防ぐ(除外する)だけです.

で,HAL上でコードを書いているなら,このマクロを使ってくれ,とのこと.

ファイル:C:\altera\81\nios2eds\components\altera_nios2\HAL\inc\io.h

/* Dynamic bus access functions */
IORD_32DIRECT(BASE, OFFSET)
IORD_16DIRECT(BASE, OFFSET)
IORD_8DIRECT(BASE, OFFSET)
IOWR_32DIRECT(BASE, OFFSET, DATA)
IOWR_16DIRECT(BASE, OFFSET, DATA)
IOWR_8DIRECT(BASE, OFFSET, DATA)

これらは,offsetBYTEアドレスを記述します.NG箇所の,DATA FIFOへの吐き出しコードを記します.

#define IOWR_UJIYA_DAI_DATAFIFO(base,data)	IOWR_32DIRECT(base, ((3) * 4), (data))

"アドレス"が必要なので,ベースアドレスから3つ目のレジスタとなる,12バイト目をoffsetに渡しています.


/* Native bus access functions */
IORD(BASE, REGNUM)
IOWR(BASE, REGNUM, DATA)

これらは,REGNUMBYTEアドレスを記述します.\同ファイルに,以下の定義がなされています.バス幅を32bitにしていれば,REGNUM×4をオフセット指定したのと同じになりますね.

#define __IO_CALC_ADDRESS_NATIVE(BASE, REGNUM) \
  ((void *)(((alt_u8*)BASE) + ((REGNUM) * (SYSTEM_BUS_WIDTH/8))))

作成するモジュールを,バス幅固定を前提とするか,任意幅(8bit幅で抑えることになる?)で実装するかによって,使うマクロを分けたほうが良い感じがしますね.\明示的に幅を規定するほうが安心感はありますけれど.


[NiosII][組込] 追記

長船さんからコメントをいただいておりますが,資料をあさっているとイロイロと書いてありました(^^;


cache controlの下記関数でcache対象外にしてしまう方法もある.memcpyするときとか,DDRRAMをVRAMにするときなんかに使える模様.

volatile void* alt_remap_uncached (void* ptr, alt_u32 len);

# memcpyのIO空間版を作るのが早そうだけれど.


さらに追記.というか順序からいうとこっちが先だろう...

Nios II Processor Reference Handbook ("NII5V1-8.1", "n2cpu_nii5v1.pdf")\Chapter 2: Processor Architecture\Memory and I/O OrganizationCache Memory\Cache Bypass Methods

  • I/O Load and Store Instructions Method\The load and store I/O instructions such as ldio and stio bypass the data cache and

force an Avalon-MM data transfer to a specified address.

  • The Bit-31 Cache Bypass Method\The bit-31 cache bypass method on the data master port uses bit 31 of the address as a

tag that indicates whether the processor should transfer data to/from cache, or bypass it.\This is a convenience for software, which might need to cache certain addresses and bypass others. Software can pass addresses as parameters between functions, without having to specify any further information about whether the addressed data is cached or not.

MSBに1たてとけばおk... 和訳は日本語ドキュメント見るなり華麗にスルーするなりで(笑



おまけ

memcpy()はsrc,dstが(4byte)alignmentされていればlong単位での転送.\burstを狙って*src++ = *dst++を記述(C言語で書かれている.)ただし,逆アセ結果は"ldw/add/stw/add"がセットになっているだけのコード.

インストラクションセットを見ると,こうせざるをえないようですね.ARMみたいなヘンタイ命令体系だと,複数転送命令があるのになぁ.(AMBAがburstコマンドを持っているから,必要なんだろうけど.Niosのburstはcache fillだけでburst動作かな...メモリが対応して無いと項か薄いだろうけど)



[NiosII][組込] 最適化有効(release build)時に漢字フォントが化ける

適当にいじくってるコードの話をしても通じませんが,恥ずかしい事例をひとつ.

globalにおいた char配列が奇数アドレスにマッピングされたために,まずいことになった.一部のメンバを shortでアクセスするため,alignment違反で変な値を読み込んでいた.

gcc方言となるが,__attribute__修飾子を使って4byte境界に置いておいた.

∵システムのバス幅が32bitなので,long(32buit)までのアクセスしかありえない

  const char font_table_kanji[] __attribute__ ((aligned (4))) = {
  #include "./VMGOL16.inc"

参考サイト ttp://developer.apple.com/DOCUMENTATION/DeveloperTools/gcc-3.3/gcc/Variable-Attributes.htmlあぽーになってるけどgcc manualがあればなんでも良い...

2009/01/16(金)[SOPC][NEEK] WM8731を使う

[NEEK] オーディオ出力(本日のデバッグ)

avalon-I2C module

opencores.comで公開されているI2C moduleを拾ってきて,バスインタフェース部分をAvalon仕様に変更して使おうとしています.


NEEKの場合は,SCKのPAD部に注意が必要です.Pull Upされていないので,FPGAがドライブする必要があります.実装例を以下に記します.

assign HC_I2C_SDAT = (sda_padoen_o==1'b0 ? sda_pad_o : 1'bz) ;

assign scl_pad_i = HC_I2C_SCLK ;
assign HC_I2C_SCLK = scl_padoen_o ;	// if module request SCL=1, then padoen=high.

シミュレーションしてみると挙動が変でした.信号を見ていくと,cr[7:4]のクリア動作が効いていないように見えます.夜を挟んで半日かけて気づくという失態をしてしまいましたが,問題箇所は自分で実装したこのあたり.
コーダーとしては最悪ですなw

	if (avs_s0_write==1'b1 && CmdReg_sel==1'b1 && core_en==1'b1)
		if (avs_s0_byteenable[0]==1'b1)
			cr <= avs_s0_writedata[7:0] ;
	else begin
		if (done | i2c_al)
		  cr[7:4] <= #1 4'h0;			// clear command bits  when done or	 when aribitration lost
		cr[2:1] <= #1 2'b0;				// reserved bits
		cr[0]	<= #1 1'b0;				// clear IRQ_ACK bit
	end

続きを読む

2009/01/14(水)[HDL][Quartus II] constant function

[Quartus II][HDL]今日の不思議:constant functionが作れない?

状況

parameter化を行うため,カウンタに必要なビット数を得たいと考えた.同様のVHDL実装は,FPGAの部屋で紹介されていました.どこかで見かけたなぁと思ったら,Verilog2001-LRMの"constant function"の使い方として紹介されていました.

ところが,QuartusII v8.1(SOPC Builderのmodule登録時のAnalysis)にてsynthesisを試みたところ,エラーが出てきました.エラーはVerilog codeの中にコメントで記します.

妄想

function文が,constantを返すと認識していないようですねぇ.\QuartusIIの制限事項にでも記載されているのかしら(未確認).Verilog-2001対応のはずですよね...

とりあえずcounter変数をintegerとして逃げました.regでビット幅を指定すると,余計なレジスタを作らないので安心なのですが.integerならbit幅違いや定数のビット数指定が抜けてもwarningが出ないので見た目が気持ち良いというか何というか….

	parameter CLK_DIVIDER = 2 ;
	localparam CLK_DIVIDER_WIDTH = clogb2(CLK_DIVIDER) ;
	// Error: Error (10192):
	// Verilog HDL Defparam Statement error at avalonif_dai.v(49):
	// value for parameter "CLK_DIVIDER_WIDTH" must be constant expression File: ----


//////////////////////////////////////////////////////////////////////
// Verilog LRM "10.3.5 Use of constant functions"
//

// define the clogb2 function
  function integer clogb2;
    input depth;
    integer i,result;
    begin
      for (i = 0; 2 ** i < depth; i = i + 1)
        result = i + 1;
      clogb2 = result;
    end
  endfunction

参考(VHDLでのLOG2実装例)

ttp://www.opencores.org/cvsweb.cgi/~checkout~/AVR_Core/VHDL/AVRuCPackage.vhd?rev=1.1.1.4;content-type=text%2Fplain

勝手に持ってきたのでこっそりと(ぇ

-- Functions
function LOG2(Number : positive) return natural is
  variable Temp : positive := 1;
  begin
    if Number=1 then
      return 0;
    else
      for i in 1 to integer'high loop
        Temp := 2*Temp;
          if Temp>=Number then
            return i;
          end if;
      end loop;
    end if;
end LOG2;
-- End of functions

2009/01/06(火)[SOPC] 長船さんのMMC/SPIインタフェース

はじめに

SPIコアのコメントで,長船さんからSDカードアクセスまで動作した実績のIPを開示いただいた.これをNEEKに組み込んでみようというネタ.

動作確認ができたのでクローズ.HDL開示は別記事にて予定...



問題発生

いただいたVHDLコードをとりこんで,SOPC Builderで論理合成をパス.しかし,Nios II IDEで作ったコードをダウンロード実行してもSDカードのIDLEへの遷移が確認できず(デバッガによるトレース).

実績のあるIPであることから,端子割付やタイミング設定が怪しまれる.ModelSimは,WEB editionだとVerilog-HDLとVHDLの混在シミュレーションができない*1

勉強もかねて,Verilog-HDLへの移植を行うこととした.


*1 : ModelSIM SE以上でないといけないハズ...?

やったこと/やってること/やろうとしていること

やったこと~1

  • 机上確認
    • Verilog移植\OK@6/Jan./2009\長船さんから公開していただいたSPI interface IPを元に移植.FatFSも最近のものへと置換した.\レジスタ初期化漏れをsimulationで検出し,修正済み.\(実使用上は問題ない.合成時にロジックが増えるかもしれないので,ゲート数削減のためにリセット時未初期化にしたのかもしれない.)\OK@7/Jan./2009\tclを手動編集:set_interface_property avalon_slave_0 addressSpan 1024\TOPレベルモジュールのport宣言でin/outを間違っていた.\Timeout検出用のカウンタ(FRC)の実装漏れ….あとで処理しようとして放置していた.ボケとる.
    • Simulation確認\たぶんOK@6/Jan./2009\制御レジスタへのアクセス,SCKtxdatレジスタの変化を確認できた.
    • ピンアサイン確認\SDまわり問題なしぽい.@1650訂正\TOP moduleにて,DI/DOを逆に接続していた.下表のようにまとめてみると間違いに気づけるわ….\本日実機を持ってきていないのでここまで.\参考:NEEKの端子一覧(まとめなおして移動するかも?)
SD pin symbol name Direction HSMC name HSMC pin FPGA pin name
CMD/DIHC_SD_CMDFPGA.outputHSMC_D3HSMC#44FPGA-L6
DAT/DOHC_SD_DATFPGA.inputHSMC_D5HSMC#48FPGA-M3
DAT3/CSHC_SD_DAT3FPGA.outputHSMC_D8HSMC#53FPGA-N8
CLKHC_SD_CLKFPGA.outputHSMC_TX_p8HSMC#101FPGA-M2

  • 実機確認
    • signal測定\レベル確認(アナログ),SCK確認,SDI確認(SD挿入時).(TxDataは0xFFなので変化しない)
    • タイミング確認(実時間)\\

何か出ているならロジアナで吸い上げてタイミングを確認する.

    • SD応答確認

期待通りならSDカードを取り替えたりして確認する.

やったこと~2

ModelSim(WebEdition)にて,タイミング確認.

  • DMA機能を使わず,SPI部のみを使った動作確認\→正常に動作した
  • DMA機能のステートマシンチェック\→シミュレーションレベルでは正常そう
  • DPRAMアクセス\write機能が無かったのでエイヤで実装した.\byte enableを使ってBYTEアクセスを許容し,write時にもDMA転送ができるように見越しても良いだろう.

最終日のオチ

  • Verilogへの移植の際に実装漏れ,実装ミスがあった.
  • MegaWizardで生成したDualport RAMブロックについても,出力段にレジスタを入れていたため,リードレイテンシ2でのCPU読み出しが成功するときとしないときがあった.\NiosII IDEから8bit/16bit/32bitアクセスを行った際に,連続・ステップ実行・Memory Dumpで読み出せる値が変化したことから明確になった.

※32bit接続のIPであっても,NiosIIのDynamicBUS sizing?により必要な値(BYTE)だけを正しく取得することができた.おそらくarbitorのあたりでうまいことやってるのだろう.



メモ

SPI/MMCインタフェースIPの移植

Avalon-MM I/Fの参考にさせていただきました.Quartus II 8.1の SOPC builderにある,Wizardを使ったりしながら再構築.tclファイルを作らせるために,Wizardを使ったほうが便利かもしれません(慣れればテキストのほうが早いか?).

当初はAvalon-MMの読み出しにレイテンシ2としていたのが不思議であったが,メモリセルのレイテンシであることが理解できた.レジスタリードについては,常にデコードしており,chipselect信号がアサートされればreaddataに正しい値が出力される.

レイテンシ0の場合って,前段のDDがアドレス・制御信号を出した次のクロックで出力をラッチするんですよね...アービタも通るし,配置制約として効いてきそうな気がしますねぇ.

動作としては,SDからリード時に,IP内のDPRAMへ指定ワード数だけデータ転送を行うIPでした.CPUのポーリング動作が要らないので便利そうです.
書き出しはCPU転送になっているようですが,現状読み出ししか考えて無いので問題なし.

拡張してAvalon-ST source portとしてデータクラスタだけ吐き出せると旨いかもしれないですね.ストリーミング再生時,CPUを介さずに音声なり動画なりを再生させることができそう.
まだまだ先の話ですがtp252_shock



モジュールのアドレス空間

offset symbol description
0statusSPIステータス,送受信データ(8bit)
1divideSPI clockに用いるmodule入力クロックの分周比(FofSCK = 入力周波数/divide/2)
2timerタイムアウト検出用ダウンカウンタ(module input clockで動作).0で止まる
3reserved0(予約/現実装ではstatusが見える)
4dmastatusDMAステータス
5~reserved[123](予約)
128~readbuff[128]DMA転送バッファ(現状読み出しのみ)

※offsetは,32bit word指定.CPUからのアドレスは x4して見える.※使用メモリ256Word = 1024Byte = 10bit

ビットフィールド

status(MMC IF status)
bit description mmc_spi.hシンボル
31-16const zero-
15割込み許可フラグ1:mmc_irq_enable
14-13const zero-
12MMC I/F FRCゼロフラグ1:mmc_zf_bitmask
11MMC-Write Protect1:mmc_wp_bitmask
10MMC-Card Detection1:mmc_cd_bitmask
9MMC I/F転送フラグ(0書き込みで転送開始要求)1:mmc_commexit / 0:mmc_commstart
8MMC Chip Select(Active Low)1:mmc_selnegete / 0:mmc_selassert
7-0MMC I/F受信データ8bitデータ.読み出し=RxData,書き込み=TxData(txdataのリードバック不可)
dmastatus
bit description mmc_spi.hシンボル
31-16const zero-
15DMA function IRQ enable bit(1:enable, 0:disable)mmc_dmairq_enable
14assert(0->1) when transfer-endsmmc_dmadone_bitmask
131:受信データエラー@SPImmc_dmade_bitmask
121:受信タイムアウト@SPImmc_dmato_bitmask
11const zero-
10w1:転送開始,r1:転送中/r0:転送終了mmc_dmastart
9const zero-
8-0転送バイト数-