ITCM (CPU に内臓されているコード用メモリ領域) にコードを配置するサンプルです。 ARM9 側のコードのみ、ITCM を活用することができます。 ARM7 には ITCM が無いようです。 このページでは、ARM9 のサンプルを書いていきます。

このページに書かれている内容は、ARM のコンパイラや 
ARM 用 gcc 全般に適用できるものではありません。
DS 用の DevKit に入っている開発ツールでしか当てはまらない内容です。

ITCM にコードを配置すると何が良いのか

・CPU に内臓されているので、キャッシュの有無やヒット・ミスヒットに関係なくフルスピードで動作する  (ITCM と DTCM 内にコードが完結している場合)

・CPU からデータバスへのアクセスが無くなるので、データバスが空く

というのがあります。

sample_itcm.png

ARM 組み込みソフトウェア入門 (CQ出版社) より 2.5.2 メモリ管理 (図2.15)

具体的に、ITCM にコードを配置する作業

・コードのファイル名の末尾を、".itcm.c" のようにする。(例:test.itcm.c) ・関数のプロトタイプ宣言に、ITCM セクションにコードを配置したという属性を付ける

まず、ファイル名の末尾に ".itcm.c" と付けるとなぜ配置が変わるかについてですが、 通常、関数を定義すると、メインメモリの .text セクションにコードが配置されます。 ".itcm.c" とネーミングしたファイルは、コードの配置場所が自動的に ITCM 領域を指すように変更されます。 この動作は、ld (リンク)コマンド実行時のリンカスクリプトと、 devkitPro/devkitARM/base_rules というファイルに書かれた、 Makefile の断片が関係しているようです。

コード例

test.itcm.c

#include "test.itcm.h"
void itcmFunc()
{

}

test.itcm.h

#ifndef TEST_ITCM_H_INCLUDED
#define TEST_ITCM_H_INCLUDED 1

/* gcc の拡張機能を使って、コードセクションを変更する */
extern void itcmFunc() __attribute__ ((section (".itcm")));

#endif

itcmFunc を使いたい場所で、test.itcm.h をインクルードしてください。

配置アドレスの確認

printf("%x", (u32)itcmFunc);

のように書くと、メインメモリでない場所に itcmFunc 関数が 置かれていることを確認できると思います。

printf("%x", (u32)main);

と書いてみて、main() 関数との配置場所の違いを見てみてください。 main の本体は、メインメモリ領域におかれているはずです。 どのアドレスが ITCM を指すのか、メインメモリを指すのか というのは、ARM9 側のメモリマップを見て確認してください。

map ファイルでさらに確認

コンパイルした後、build ディレクトリに ".map" というファイルができていると思いますが (DevKit のサンプルコードに入っている Makefile をそのまま流用した場合)、 .map ファイルを見ることで、関数の配置セクションとアドレスを見ることができます。

.itcm          0x01000000       0x50 test.itcm.o
               0x01000000                itcmFunc

例えば、このような場所が見つかると思います。 これは、.itcm セクションに配置された、test.itcm.o のコードサイズが 0x50 バイトで、 0x01000000 から itcmFunc の内容が始まるということを指しているようです。

注意事項

Makefile の中で、

ARCH	:=	-mthumb -mthumb-interwork

このような場所があると思いますが、これは、コードを Thumb モード (16 ビット長命令モード) でコンパイルすることを gcc に伝えるところです。 Thumb モードでコンパイルした場合、ITCM に書かれた関数を呼び出すことができませんでした。 Thumb モードの指定を外さなければいけないようです。

ARCH	:=	

このように、空にすると ITCM のコードを呼び出すことができました。 Thumb モードと Thumb モードを外した場合の違いは、

CFLAGS	:=	

のところに、 "-S" オプションを付けてコンパイルした後、 .o ファイルをテキストエディタで開いて見てみてください。 "-S" オプションを付けると、出力ファイルを中間オブジェクト形式ではなく、 アセンブリ言語形式で出してくれるので、なんとか読めるようになります。

16 ビット長のコードはファイルの先頭あたりに、

.code 16

のような指定があることが確認できると思います。

なぜ 16 ビットのコードを ITCM に配置すると呼び出せなくなるかまでは分かりませんでしたが、 想像では、配置するセクションによって、呼び出し規約が違うためだと思います。

今回の内容とは直接関係しない話ですが、 Thumb モードで今までコードを作ってきた場合でも、 Thumb モードでないモード (32 ビット長のモード) から、 Thumb モードの関数を呼び出すことができるようです。

Makefile (gcc) で、

-mthumb-interwork

指定を付けてコンパイルしたオブジェクトコードは、違うモード間の関数呼び出し時に、 CPU モードを Thumb にしたり、 Thumb から出たりという命令を 関数の前後に自動的に付けてくれるようです。

まとめ

これだけ書いておいてなんですが、実は ITCM に置いたコードと、 メインメモリに置いたコードで実行時間を計ってみたところ、 違いが分かりませんでした…。

ものすごく簡単な、

volatile int i, j;
for ( i = 0; i < 1000000; i++ ) {
   j++;
}

というコードで実験したのが原因かもしれませんが。。w キャッシュがフルに効いているせいか、実行時間はほぼ同じでした。 (というか、1ms の精度で誤差は全くありませんでした。)

キャッシュコントロールの仕組みを使って、キャッシュを無効にすると また結果が変わると思います。 実験できたらまたその結果をここに書いておきます。

追記

まとめのところに書いたコードで、速度の違いが分からなかった原因ですが、 関数内の局所変数はメインメモリではなく、DTCM という、 データ用 TCM 領域にデータが置かれることが原因の 1 つだったようです。 これは、ITCM もそうですが、普通に書いた ITCM 外の関数でも そのような配置になるようです。

また、メインメモリの .text セクションに置かれたコードも、 上のような短いコードだと、命令キャッシュが効いて、 フルスピードでアクセスできてしまいます。

下のようにコードを変更して、再度テストしてみました。 こうすることで、メインメモリ内に配置されたコードで使われる変数は メインメモリに確保され、ITCM 内に配置されたコードで使われる変数は DTCM 内に確保することができます。

  • メインメモリ内
    void testFunc()
    {
    	volatile int *i;
    	volatile int *n;
    	
    	i = (int *)malloc(sizeof(int));
    	n = (int *)malloc(sizeof(int));
    	
    	for ((*i) = 0; (*i) < 2000000; (*i)++)
    	{
    		(*n)++;
    	}
    	free(i);
    	free(n);
    }
  • ITCM 内
    void itcmTest()
    {
    	volatile int i;
    	volatile int n;
    	
    	for (i = 0; i < 2000000; i++)
    	{
    		n++;
    	}
    }

最適化によりレジスタ内に局所変数が収まってしまうことを避けるため、 gcc に、 -O0 オプションを付けてコンパイルしました。

結果は、

・メインメモリ → 807ms ・ITCM → 538ms

でした。 しかし、このテストは ITCM 内のコードのフェッチ速度が速くなったことを確認するというより、 メインメモリと DTCM の違いを表すものでしかありません…。 コード部分は、命令キャッシュを無効にして実験する必要があります。

また、キャッシュを無効にする方法も ARM の本から探してみました。

void disableWriteCache()
{
	asm("\n\t"
		"ldr	r5, =0x00\n\t"
		"mcr	p15, 0, r5, c3, c0, 0\n\t"
		);
}

これは、書き込みキャッシュを無効にする命令です。 CP15 の、c3, c0, 0 という場所に、 0 を書き込んでいます。 これを実験前に呼び出すことで、 n++ , i++ という部分の 書き込みキャッシュの効き具合を確認することができます。 DTCM 内に置いたデータは、書き込みキャッシュの有効/無効に関係なく、 先ほどの速度でアクセスできるはずです。

実行結果は、

・メインメモリ → 1074ms ・ITCM → 538ms

となりました。 やはり、メインメモリの方だけ実行速度が遅くなっています。 …とまだこれでも ITCM の速度の違いを見るには不十分です。 書き込みキャッシュではなく、命令キャッシュ・データキャッシュを無効にして 実験する必要があります。

…が、命令キャッシュとデータキャッシュを無効にすると、 DS 本体が動かなくなってしまい、うまく実験することができませんでした。 うまくいく方法をまた探してみようと思います。

さらに追記

命令キャッシュを無効にする方法が分かりましたので追記します。

void disableInstructionCache()
{
	asm("\n\t"
		"ldr	r5, =0x00\n\t"
		"mcr	p15, 0, r5, c2, c0, 1\n\t"
		);
}

CP15 の、 c2, c0, 1 という場所に 0 を書き込んでいます。 これを先ほどの実験の前に実行し、速度の違いを見てみました。

実行結果は、

・メインメモリ → 9171ms ・ITCM → 480ms

でした。 メインメモリの方は、想像通り大幅に遅くなっていました。 なぜか ITCM の速度が上がっていますが、これは謎です…。

この実験から分かるのは、キャッシュが全くヒットしない領域を 連続して読み込む場合、メインメモリ内に置いたコードでは 最大 11 倍もの実行時間がかかるということです。 ( 9171 / 807 )

また、ITCM 内に置いたコードはキャッシュの有無に関係なく、 常に同じ速度で実行できることも分かりました。

最後に…

メインメモリ内に置いたコードは、ITCM に置いたコードと 内容が違うかったので、同じにして再度実験しました。

void testFunc()
{
	volatile int i;
	volatile int n;
	
	for (i = 0; i < 2000000; i++)
	{
		n++;
	}
}

両方ともこのコードにして、DTCM 内に局所変数を置きました。 こうすることで、純粋に命令キャッシュのかかり具合だけを見ることができます。

結果は、

・メインメモリ → 6473ms ・ITCM → 480ms

でした。 8000ms くらいになると予想してたのですが、 命令が単純化されたせいか、データ操作分で必要になりそうな 処理時間以上に実行が速くなりました。

参照

ARM 組み込みソフトウェア入門 (CQ出版社)

  • 13.2.3 領域のキャッシュとライト・バッファの設定

トップ   差分 バックアップ リロード   一覧 単語検索 最終更新   ヘルプ   最終更新のRSS
Last-modified: 2013-10-27 (日) 17:45:23 (3134d)