2009/03/02(月)[SOPC][IP] MMC/SPI FatFs - f_writeベンチマーク

[SOPC][IP] MMC/SPI FatFs - f_writeベンチマーク

[SOPC][IP] MMC/SPI FatFsで紹介しているFatFsについて,コメント欄で問合せいただいたファイル書き込み速度のベンチマークをとってみました.


ベンチマーク概要

FatFsのfopen("wb"相当)で確認する

File systemを使う場合でのベンチマークをとることをテーマとする.*1


脳内シミュレーション(a.k.a.妄想)

FatFs(R0.06)ソースコードより,以下の特徴が推定できる.

  • FatFsはマルチセクタライトもサポートしている模様.
  • セクタ境界・クラスタ境界でクラスタ調整・更新・buffer吐き出し処理を走らせる模様.

上記を踏まえてベンチマークを取得すべきケースは以下のパターンを考えた.

  • クラスタサイズでの一括書き込みが最高スループットを出すだろう
  • クラスタをまたぐ,ランダムアクセスが最低速になる.
  • バッファサイズ(またはセクタサイズ)をまたぐ場合も同様.

ベンチマーク対象とコード

[NiosII][ACM] 動画再生装置(お試し版)の構成で試しているため,キャッシュの影響が結構効いてくることも考えられるので,データ転送量を2Mbytesとします.

実行時間測定には,パフォーマンスカウンタを用います.カウンタを3つとして合成してあるので,測定対象を以下のように絞ります.(なんとなく時間のかかりそうなところ,短縮できそうなところに目星をつけてみたつもりです)

  • 総合時間(規定サイズ・規定回数のブロック転送に要した時間.出力されたファイルの検証は行うべき)
  • SPI転送待ち時間
  • 書き込みデータのCRC計算
ベンチマーク部分本体
const int trans_size[] =
	{
	512*64,
	512*128,
	512,
	4096,
	1024,
	64,
	256,
	768,
	1280,
	1636,
	2048,

	0
	} ;

const int sz_write = 512*2*1024*2 ;

~~~
	while (*sz) {
        f_unlink( fn ) ;    // delete the file before test

        if ((res = f_open (&fp, fn, FA_WRITE|FA_OPEN_ALWAYS|FA_CREATE_ALWAYS)) != FR_OK) {
            return (res | 0x1000);
        }
		// size / loop
		i = 0 ;
		PERF_RESET( PERFORMANCE_COUNTER_BASE );
		PERF_START_MEASURING( PERFORMANCE_COUNTER_BASE );
		PERF_BEGIN( PERFORMANCE_COUNTER_BASE, 1);
		do {
			res = f_write(&fp, sdbuf[ i  ], *sz, &rsz) ;
			if (res != FR_OK || rsz == 0) {
				printf ("Error : Bench-fwrite (%d/%d)\n", res, rsz);
				break;
			}
			i +=  rsz;
		} while (i<sz_write) ;
		PERF_END(PERFORMANCE_COUNTER_BASE, 1);

		PERF_STOP_MEASURING(PERFORMANCE_COUNTER_BASE) ;
		printf( "*** fwrite-size = %d ***\n", *sz );
		perf_print_formatted_report( (void*)PERFORMANCE_COUNTER_BASE, alt_get_cpu_freq(), 3, "total ", "xmit","CRC" );
		WAIT_SEC(5) ;

        f_close(&fp);

		sz++ ;
	}

データ転送部・CRC計算部の処理時間測定位置(mmc.c)
BOOL MMC_xmit_datablock(const BYTE * buff,  /* 512 byte data block to be transmitted */
                    BYTE token  /* Data/Stop token */
    )
{
    BYTE resp, wc = 0;
#ifdef _CALC_CRC
PERF_BEGIN( PERFORMANCE_COUNTER_BASE, 3);
    WORD crc = CRC16((BYTE *) buff, 512);
PERF_END(PERFORMANCE_COUNTER_BASE, 3);
#endif

    if (MMC_wait_ready() != 0xFF)
        return FALSE;

PERF_BEGIN( PERFORMANCE_COUNTER_BASE, 2);
    MMC_xmit_spi(token);            /* Xmit data token */
    if (token != 0xFD) {        /* Is data token */
        do {                    /* Xmit the 512 byte data block to MMC */
            MMC_xmit_spi(*buff++);
            MMC_xmit_spi(*buff++);
        } while (--wc);
#ifdef _CALC_CRC
        MMC_xmit_spi(crc >> 8);     /* CRC */
        MMC_xmit_spi(crc & 0xff);
#else
 @       MMC_xmit_spi(0xFF);         /* CRC (Dummy) */
 @       MMC_xmit_spi(0xFF);
#endif
        resp = MMC_rcvr_spi();      /* Reveive data response */
        if ((resp & 0x1F) != 0x05)      /* If not accepted, return with error */
            return FALSE;
    }
PERF_END(PERFORMANCE_COUNTER_BASE, 2);

    return TRUE;
}

*1 : SDカード本来の性能を最大限に生かすのであれば,マルチセクタライトコマンドを放っておけばよいだろう.

測定結果

使用用したカードは,Transcend mini SDカード 2GBをSDアダプタに挿したものです.

    • セクタあたりのバイト数は,512bytes.
    • クラスタあたりのセクタ数は,32です.
    • データセクタエリアの理想write回数は,4096回,128クラスタを消費します.
    • FATエリアの1セクタあたりのクラスタ数:FAT16ならば1セクタに256個,FAT12なら341個を格納できます.

2MBytesのファイルがクラスタ不連続になっているかどうかの確認をしていません.個人的に使用しているSDカードで,この評価用に使っていないため,テスト用のファイルがクラスタ連続で使用しているかは未確認です.あき暮らす他の探索は先頭からのシーケンシャル探索であり,ファイルのunlink処理では空きクラスタにしているため,パフォーマンスの比較に影響は無いと考えました.


結果のまとめと考察

測定結果と主要項目一覧
転送サイズ総時間512byte書込転送要求回数着目点
655364.03239+712クラスタ,マルチセクタCMD
327684.92526+891クラスタ,マルチセクタCMD
40964.43872+5138セクタ連続書込み,1/8クラスタ,マルチセクタCMD
20484.43872+10254セクタ連続書込み,マルチセクタCMD
1636204.466+12713セクタ連続書込み,100バイトコピー,クラスタ境界をまたぐ,マルチセクタ+シングルセクタCMD
1280135.29+16152セクタ連続書込み,256バイトコピー,マルチセクタ+シングルセクタCMD
10247.80936+20491/32クラスタ,マルチセクタCMD
768199.459+13セクタ連続書込み,クラスタ境界をまたぐ
51213.9297+11/64クラスタ, 1セクタ,シングルライトコマンド
25614.0029+01/2セクタ,シングルライトコマンド
6414.0578+61/8セクタ,シングルライトコマンド
  • セクタ境界をまたがないときは,内部バッファへのコピー処理が走る\→ セクタ数の整数倍ではないモノ全てが対象.\とくに小さいものはコピー処理とループ処理で時間を消費する
  • セクタ境界をまたぐと,書き込み処理が走る.\場合によってはリード処理も走る.
  • DRAM間のコピー処理は効率も悪く,処理時間が延びる傾向にある

考察

転送呼び出し回数と,処理時間とが比例していないのは,MMC側の書き込みバッファがあって,連続した書き込み要求は待たされるが,完結して呼び出す分にはFLASHへの書き込みと,ソフトの走行がパラで走っている状況である,と推察する.

512の整数倍・約数を除く転送サイズでは,軒並み桁違いの遅さが目立つ.メモリコピー処理に加えて,セクタ・クラスタ境界をまたぐ処理が追加されるためと考えらる.加えて,端数はシングルセクタライトコマンドを用いるため,転送効率が悪化すると見られる.

1024byte時に中途半端な速度になっているのは,SD内のFLASH書込み待ち時間が発生しているためと考えられる.

512以下の約数では,不要なコピー処理が走らないが,シングルライトコマンドによるコマンド・レスポンス処理時間がかかるため,カードのbuffered-writeが機能しないのかもしれない.




実行結果ナマデータ

fwrite-size = 32768

Total Time: 4.92526 seconds (487789773 clock-cycles)

Section %Time (sec) Time (clocks)Occurrences
total1004.925264877897671
xmit29.91.471581457432374185
CRC51.52.534742510364714185

fwrite-size = 65536

Total Time: 4.03239 seconds (399361828 clock-cycles)

Section %Time (sec) Time (clocks)Occurrences
total1004.032393993618221
xmit36.31.465131451044754167
CRC62.52.518662494441814167

fwrite-size = 512

Total Time: 13.9297 seconds (1379575366 clock-cycles)

Section %Time (sec) Time (clocks)Occurrences
total10013.9296913795753601
xmit10.51.457821443799864097
CRC19.72.741222714866154097

fwrite-size = 4096

Total Time: 4.43872 seconds (439603921 clock-cycles)

Section %Time (sec) Time (clocks)Occurrences
total1004.438724396039151
xmit331.463601449530084609
CRC64.32.854522827077134609

fwrite-size = 1024

Total Time: 7.80936 seconds (773427070 clock-cycles)

Section %Time (sec) Time (clocks)Occurrences
total1007.809367734270641
xmit18.71.461001446947596145
CRC50.83.966453928315936145

fwrite-size = 64

Total Time: 14.0578 seconds (1392260079 clock-cycles)

Section %Time (sec) Time (clocks)Occurrences
total10014.0577713922600731
xmit10.41.465021450932744102
CRC20.12.831102803873134102

fwrite-size = 256

Total Time: 14.0029 seconds (1386825298 clock-cycles)

Section %Time (sec) Time (clocks)Occurrences
total10014.0029013868252921
xmit10.41.462951448886934096
CRC202.801532774594664096

fwrite-size = 768

Total Time: 199.459 seconds (19754119034 clock-cycles)

Section %Time (sec) Time (clocks)Occurrences
total100199.45907197541190281
xmit0.7321.460151446106294097
CRC1.32.594872569914484097

fwrite-size = 1280

Total Time: 135.29 seconds (13398914257 clock-cycles)

Section %Time (sec) Time (clocks)Occurrences
total100135.29001133989142511
xmit1.081.461521447470515711
CRC2.683.623653588810705711

fwrite-size = 1636

Total Time: 204.466 seconds (20250035225 clock-cycles)

Section %Time (sec) Time (clocks)Occurrences
total100204.46638202500352191
xmit0.7181.467311453200055367
CRC1.663.392833360204435367

fwrite-size = 2048

Total Time: 4.88632 seconds (483933442 clock-cycles)

Section %Time (sec) Time (clocks)Occurrences
total1004.886324839334361
xmit301.463841449761165121
CRC65.33.190863160180685121


今後の課題

SD側の応答待ち時間の計測,f_write内の分岐処理回数を付け加えれば,想像の裏づけを取ることもできるだろう.

カードの性能にも左右されるベンチマークであるため,必ずしも上記の結果どおりとはならないでしょう.カード性能を測定するベンチマークハードウェアを作るのも面白いかもしれませんね.File systemを無視して,SDのwrite buffer sizeや応答時間なんかの測定を自動的にやってくれるのがあると,実力チェックに使えて面白そうです.

実使用に耐えるかどうかを判断するためには,file system経由での(sequential|random)(read|write)も必要でしょう.

まぁ,あくまでもMMCのベンチマークということで... :p

2009/02/12(木)[SOPC][IP] MMC/SPI FatFs

[SOPC][IP] MMC/SPI FatFs

MMC/SDカードのアクセスを行うためのIPです.\ オリジナルは長船さん作成のVHDLで記述されたものでした.\ Verilogに移植して,ソフトウェア部分もFatFs(R0.06, Apr 01, 2008)に 差し替え,BBSにあったLFN対応パッチもマージしました*1.\ それぞれNEEK向けに修正*2してあります.


*1 : ただし,7bit文字のみ対応.2バイト文字はUNICODEになるので,このままではうまく認識されません.

*2 : WP/CS信号を常時Lowに変更.tclを編集\ set_interface_property avalon_slave_0 addressSpan 1024

収録ファイルと個々の概要説明

  • avalonif_mmcdma.txt\ このファイル.IPの簡単な説明等を記載する
  • avalonif_mmcdma_hw.tcl\ SOPC builderが食うファイル. Avalon-MM I/Fの仕様や追加すべきファイル,パラメータ初期値などの情報が入っている.
  • avalonif_mmcdma.v\ Avalon I/Fを含む本モジュールのtopレベルモジュールを記述したファイル.
  • SDIF.v\ SPIモードのビット転送モジュール.
  • SDIF_MEM.v\ Mega Wizardで生成したSRAMモジュール.\ 8bit write, 32bit readの512bytesメモリ.\ 出力にはバッファを設けず,readaddressにしたがってq出力が出てくるようにしてある.

ダウンロード


ハードウェア仕様

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

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転送バイト数-


ハードウェアの動作イメージ

MMC/SDからのデータ読み出し

読み出し時,DMA機能を用いることで,本モジュールのアドレス空間に配置されているバッファメモリにSDからのデータを転送することができます.
MMC/SDからの転送は,byte単位で行われるため,CPUで転送するよりは高速な読み出しが可能でしょう.\ただし,転送できるメモリは本モジュール内の先頭からのみという制約もあります.

※ポートを読み出してのPIO転送も勿論可能です.

Avalon-MM Master portを増設し,Avalon-DMAを内包すれば,任意の空間への転送も可能となるでしょう.


MMC/SDへのデータ書き出し

書き込みについては,バッファからの転送は未サポートであり,PIO転送する必要があります.



注意事項

DMA転送を要求した際には,SDIF module(SDIF.v)のアクセスを本IPのtop level moduleが行うため,ユーザがCPU等からアクセスしてはいけません.\dmastatusの読み出し動作のみ許容します.(DMA転送の終了をポーリングで知るため)\SDIFのほかのレジスタへのアクセスを試みても,本IPのステートマシンがSDIFへのアクセスを占有し,statusレジスタの値しか参照できない状態となります.

2009/02/10(火)[NiosII][ACM] 動画再生装置(お試し版)

はじめに

NEEKを使用したACM code*1データの再生装置を試作してみた.ALTERA FPGA/cyclone IIIを用いて,NiosIIコア,SOPC Builder・QuartusIIなどの使用に慣れることを目的とした事例である.

実機動作動画

ニコニコのほうにうpしておいたので,会員の方は参照されたい.


構成

cycloneIII内部のブロック図を以下に記す.(UWXGAであればpixel/dotで表示できるだろう)

system_block.png
\図中,背景色が赤いモジュールが自主開発となるモジュールである.\制御ソフトやHDL記述については,別所で記載したいと思う.(ゼロから作り上げたものは少なく,多くは参照したコードやHDL記述がある)

また,システム構成として,NEEKのブロック図を引用させていただく.

Blk_LCD.png
Blk_NEEK.png

本試作においても,全てのリソースを使用しつくしていない.半田付けもせずに.それなりに遊べる評価ボードであると考える.


緒言

ITEM Description part name
FPGAAltera Cyclone IIIEP3C25F324
Parallel Flash Memory8M x16 low voltage parallel flashPC28F128P30B85
DDR SDRAM4M x16 x 4 DDR SDRAMA2S56D40CTP-G5PP
Syncronus SRAM256K x 32 synchronous SRAMIS61LPS25636A-200TQL1


*1 : 適応的コードマッピング(Adaptive Code Mapping).ALTERAで行こう!にて公開されている軽量動画フォーマットです.ここでは,Rev20080421を便宜上v0.1と書かせていただきます.

ブロック説明

前述したブロック図を,SOPC Builderで配置・接続した例を以下に示す.

SOPC_view_ALL.PNG

基本的には,NEEKサンプルの"picture_viewer"をベースとして捕らえ,QuartusIIでフルスクラッチでプロジェクトを作成した.\チュートリアルでは,既存プロジェクトを改変して云々とあるが,新規に自主開発を行ううえで必要となる知識を得ることができないと判断した.\そもそもQuartusIIのver.upに伴う差分に対応できるかも不安であり,また,SDカードI/F等,サードパーティ製のIPを用いている点が引っかかった.\ためしに無償評価ライセンスを取り寄せてみたりもしたが,期限が一月ということもあり,個人でまったりと遊ぶには心もとない.\無ければ作るの精神で,増築していく方針で進めた.


NiosIIコアの構成

ゲート規模から考えると,余力が十分にあるため,Nios II/fコアの採用とした./eについては,1命令の実行に複数サイクルを要することから,今回のようにCPU演算量が多くなりそうな用途の場合,不向きである.SOPC Builderで,Nios II Processorの編集ダイアログを見ておきましょう.


cpucore_01.png
乗除算器もH/Wで実装させます.cyclone IIIにはmultiplierが載っているので,コレを使わせます.使用するものが無ければ,LUTでテーブルを作ってくれるのでしょう….\リセットベクタ・割込みベクタはSRAMに割り付けておきます.\DDRSDRAMに比べてオーバーヘッドが少ないためです.(今となってはTCMのほうが良いかな)\MMUは未使用としておきます.テーブルルックアップ処理などで,メモリアクセスのオーバヘッドが増えるため,OSが要求してこない限りは使わなくて良いかと思います.


cpucore_02.png
\cacheを有効にします.サイズは適当.本来ならばアプリケーション作成後にチューニングしていくと良いかと思います.D-cacheのline sizeは,SDRAMのburst readサイズにあわせておくのが無難でしょう.


cpucore_03.png
\例外処理等.まぁ特に要らないかなぁ.まじめに製品化を考えるなら,fail safeやエラートラップ・障害発生時のログ機能を作りこんでおくと良いでしょう.


cpucore_04.png
\MMU/MPU設定.今回は MMUを無効化しているので設定はできません.


cpucore_05.png
\デバッグ機能の設定.あまりまじめにやる気が無かったのでlevel2で.1でも十分:)\現段階では,trace機能を使う必要がでるほどチューニングも行っていません.


cpucore_06.png
\カスタム命令の追加.YCbCr→RGB変換の演算をHWで処理してしまいます.ソフトのみでコーディングするのに比べてかなり高速化されます.\カスタム命令の(実装|使用法|概要)については,[NiosII][ACM] 高速化・ベンチマーク測定を参照してください.



記憶領域

コアのインストラクション・データは,それぞれがAvalon-MM Masterとなっています.PCからのダウンロードで,全てのメモリへのデータ供給後にCPU startとなるようです.\それ以外はconfig ROMから吸い上げorスタートアップルーチンがFLASHから吸い上げて転送してくれるのかどうか(未確認).

基本的にSRAMにマッピングしておるので,DDRSDRAM側へのアクセスは少ないはずです.ただし,heap領域およびVRAM(裏面含む)を配置してあるので,データ転送量は相当*2なものとなります.CPUコアによる展開処理での転送と,LCD表示のためのSGDMAによる読み出しの転送とが発生しますので,さらに負荷があがります.LCDは,800x480x60FPSなので,92,160,000 Bytes/Secとなります.

このLCD用のSG-DMAは,DDRSDRAMコントローラとは64bitバスで接続されているので,帯域としては66.5x8=532 Mbytes/sec を有するので,占有は2割ほどです.ただし,コントローラのオーバヘッドや,CPUからのアクセス・SGDMA自身のデスクリプタテーブル読み出しによるアクセスが衝突することで,転送レートに大きな影響を与えます.*3


LCDC

前述のとおり,VRAMとしてDDRSDRAMを用いております.CPUとシェアしており,裏RAMからの転送処理が不要な反面,アクセス衝突によるパフォーマンス劣化が生じえます.

LCDとは,MAX IIを介して接続されており,レベル変換・demax処理をさせています.FPGAからは,RGB各8bitのデータを時分割で送信する形態をとっています.

現状は,picture viewerに習って,altera-video-sync-generatorを用いています.これはAavlon-STを受け取って,LCDに必要な同期信号・フロントポーチ・バックポーチ信号(クロック)を生成します.データはAvalon-ST Packetのback pressure機能を用いて,データ本体の未送出時には取り出さないようにさせています.

SG-DMAと,video sync generatorとは動作クロックも異なり,データ幅も異なります.途中でDFAやFIFOを噛ませて所望の形に直していきます.

表示装置や,Dot Per Pixelに手を加えるには,このあたりのモジュールや接続部分を変更していくと便利かもしれません.



MMC SPI

長船さんが公開されているVHDLのIPをVerilogに移植してみました.無償版のModelSimでのHDL混在シミュレーションができないため,やむを得ず移植しました.本来なら流用してウマーという流れなのでしょうが,無念ですw


その他

ALTERAの基本IPをぺたぺたと貼り付けただけなので,説明は不要でしょう.適宜邦訳版コンテンツやメモ,ALTERAから出ている資料に目を通してください;)



*2 : 240 x 160 x 24bpp x 24fps で 3,686,400Bytes/Sec.

*3 : SOPC Builderの接続図で細サイクル数を指定しているのも,裏RAMアクセス時にLCD表示が乱れたので手を入れてあるのです….

ドライバレベルソフト

ALTERA IPについては,基本的にドライバ(Hardware Adaption Layer)が用意されています.newlibの標準入出力関数に適応したドライバも用意されており,シリアル入出力ポートを用意しておけばデバッグ時に便利でしょう.\無論,JTAG debug機能が有効であれば Eclipse上(NiosII IDE上)からGDB経由でらくらくデバッグが可能でしょうが.


LCDC

NEEKサンプルのソフトを流用します.

C:\altera\72\kits\cycloneIII_3c25_niosII_eval\examples\picture_viewer\software_examples\app\picture_viewer\alt_video_display

SGDMAドライバも内部で使用しています.個人的にVsyncカウントもほしかったので,このソフトにも手を入れています.

SGDMAをPackモードで使用せず,デスクリプタチェインの終了を要因とした割り込みを用いてソフトで再キックしています.プロジェクトが落ち着いてから公開しましょう.恥ずかしいコメントだらけなので(ぇ-


MMC

当該IP制御に関するコードは長船さんのコードをパク…参考に,もってきました.FAT File Systemについては,おなじみのFatFsを使わせていただきます*4

個人でいじっている分には自己責任でアレですが,製品や人に配布するものとして適用してしまうとゲイツ陣営と争う必要が出てくるかもしれません.

また,SDカードの使用については,MMCへのアクセスを行っているだけである,という主張でSDカード非対応を宣言すればライセンシ不要という見方もあるようですが,軽くWEBサイトを見てまわった感じだと,わかりかねます.

データライン4本で転送してみたいものですがね….SPI 25MHz*5だとちょっときつい.



DAI

今回のACM v0.1対応版お試し公開では未使用.MMC I/Fのテストとして,waveファイルオープン・DAI経由でDACへデータを流して再生までは確認済.DAIモジュールにもFIFOを用意してあり,FIFO残量が減ってきたときに割り込みをかけて,FIFOへのデータ転送を行う.将来的にはAvalon-ST sinkポートを設けて,DMA経由で流し込めればCPUの処理を削減することができよう.

現状,ソフトでメモリ上にFIFOを構築して,割込みハンドラでデータを吐き出させている.メイン処理ではメモリ上のFIFO残量をポーリングして,ファイルからデータを吸い出している.


ACM decoder

ALTERAで行こう!暫定公開 → 軽量動画フォーマットの研究を参照ください.\更新されて名前が変わったりしているかもしれませんが,適宜対応ください(^^;




*4 : ChaN氏のFatFs Rev.0.06を取り込み,MMCを論理番号0と成るようにするなどの修正をしています.

*5 : MMCは20MHzまでの仕様だから,それ以上の使用はNGか!? デバイス内のレジスタに上限値が入っているから問題なし?

アプリケーションレベルソフト

Test Programとしてのみ実装.RTOSは未使用であり,単純にSDからデータを吸い出してデコード・表示しているだけです.

frame rateにあわせて時間間隔を所望の時間に近い値でウェイトを入れている.frame rateに対応する待ち時間はシステムクロックの整数倍にはならないので,誤差を吸収する措置を入れてある.(誤差を積算してウェイト値に加算するだけですが)





[コラム] SDカード

つい本日(2009年2月10日)のことです.

トラ技2009年3月号 BASICS [連載] Cによるマイコン操作術〈第12回〉SPIを利用してSDメモリーカードにアクセスしてみよう

ちょうど,この記事を目にできた.立ち読み程度で申し訳ないが,FATFSのライセンス・SDカード使用のライセンスについて,伝聞調で記されていた.参照資料も特別明記無く,商用利用時は調査すべきだ,程度で終わっている.

軽くググってみたものの,SDライセンス・FATライセンスに関する詳細がみあたらない.MSのパテントとして2007年末に米国で認められているようだが,パテント期限が17年(スラドJP過去ログ)とかなんとか….FAT12/FAT16/FAT32/VFATそれぞれで期限が異なるのかしら?

相変わらず疑問だけが残るけれども,ここでも解決せずに筆をおくことになる…(ゴメンナサイ)

  • 切れるとしていつごろ切れるのか.
  • 切れないとして無断使用はNGということで認識は正しいか.
  • linuxのfile systemとしてサポートが残っているが,ライセンス問題はどうなっているのか.

容易に組込み機器とPCとの間でデータをシェアしようと考えると,SD+FATFSが現状の最適解だと思う.active syncプロトコル自体もライセンスが要るようだし

最悪,セガサターンのRAMカセット並みのフォーマットで作ってもいいわけだが….MSDOSのメモリチェーン並みのしょぼさだけど(笑)

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があればなんでも良い...