#author("2023-05-29T23:58:43+09:00","","")
#author("2023-05-30T00:01:27+09:00","","")
* CPU [#a7fd7735]
GBAには''ARM7TDMI(16.78MHz)''と呼ばれる省電力に特徴的なCPUが使われています。開発元はイギリスの[[ARM社:https://www.arm.com/ja/]]です。基本的にアセンブラを知らなくてもゲームは作れるので、予備知識程度に知っておくのがいいと思います。偉そうなこといってますが、ぶっちゃけるとこんな項目を書いた自分も実はさっぱりなのです、わっはっは。(^^;

** ARM7TDMI [#f8b6e3fc]
ARMには様々な種類のチップが存在していて区別は名称(ARM7)以降の''TDMI''の1文字ごとです。ここで特に注意しておきたいのは''T''のThumb機能(命令セット)です。~

| T | Thumb機能                |
| D | デバッグ機能             |
| M | 乗算機能強化型           |
| I | リアルタイムトレース機能 |

** ARMステートとThumbステート [#z0ca63c2]
windowsでプログラムしている人ならご存知のとおり、PC互換機上ではx86命令セットの1種類で動いています。ところがARM7TDMIは''ARM''と''Thumb''の2種類の命令セットが存在します。違いについては以下のとおりです。

| 命令セット | 命令サイズ | 特徴                                 |
| ARM        | 4バイト   | サイズが大きい分命令に幅がある。     |
| Thumb      | 2バイト   | サイズが小さい分命令が限られている。 |

このことについて、Dev'rs GBA Dev FAQsでは次のようなアドバイスをしています。

 Should I use a C compiler that generates ARM or Thumb instructions?
 
 Thumb code is able to provide up to 65% of the code size of ARM,
 and 160% of the performance of ARM when accessing 16 bit memory.
 (Source: ARM DDI 0029E) Since most code is usually run out of
 ROM(which is 16 bit) it makes sense to use a Thumb compiler.
 For the times when you need the extra performance of ARM,
 you can always code these subroutines in ARM assembly language
 and place them in internal WRAM.

メモリ領域(IWRAM, EWRAM, ROM)のサイズ、バス幅、アクセス時間との兼ね合いもあるので、基本的には以下の表を指針にさせてください。特にIWRAMはArmにするかThumbにするかで悩みます。ただ、どちらかというと容量の少なさにThumbを選択してしまうことが多い印象です。どうしてもという時はArmを選択します。

| 領域  | おすすめステート | 理由              |
| IWRAM | ARM or Thumb     | サイズが少ない為  |
| EWRAM | Thumb            | バス幅が16bitの為 |
| ROM   | Thumb            | バス幅が16bitの為 |

** ステートの切り替えについて [#h5951034]
C言語でソースコードを書く分には切り替えを意識する必要はなく、コンパイラがすべてやってくれています。ただ、問題はアセンブラやインラインアセンブラでの話ですが・・・勉強不足なので省略させて頂きます(爆。割り込みについては正直日記さんに詳しく書かれていましたので引用させていただきました。thumbで書くとハマるので注意してください。

 割り込みハンドラがthumbで動かない訳
 
 IRQ例外が発生すると現在の状態に関わらずARMステートに移行した後、BIOS
 の0x00000018に飛ぶわけですよ。
 
 んで、BIOSから割り込みハンドラへと飛ぶのですが、そのときにbx(ARM←→
 THUMBステートの移行が出来る)ではなく ldr(ARM←→THUMBステートの移行
 が出来ない)で飛んでいるんです。
 
 実際はTHUMBコードで書かれているのに、CPUはARMステートのままなのでARM
 コードとして解釈されるわけです。だから割り込みハンドラを-mthumbでコン
 パイルするとうまく動かないわけ。

割り込み関数については必ずARMで書かなくてはいけません。Cのソースコードで指定したい場合はファイル名で決まります。

 irq.arm.c
 irq.arm.h
 intr.arm.c
 intr.arm.h

という様に、xxx.arm.cと書けばARMになります。このファイルの関数からthumbコードを呼び出した時はコンパイラがうまく切り替えてくれるので気にしなくて大丈夫です。詳しいコンパイルの記述については以下のファイルを参照してください。

 C:\devkitPro\devkitARM\gba_rules
 C:\devkitPro\devkitARM\base_rules

** 速さの基準 [#j4533f70]
速さの基準にMIPSやベンチマークなどが存在します。とはいうものの、プログラマに必要なのは自分のコードが軽いか重いか、体感という大雑把な基準でいいと思っています。重ければアルゴリズムの選定をやりなおしたり、根本的に書き直します。数値的な基準としてはVCOUNTやタイマーを利用するとテストコードが作れます。他にもfpsを常時表示しておくのもいいかもしれません。

** 割り算、除算の罠 [#e8e6b829]
ARM7TDMIの命令セットには割り算、除算がないためlibgccを暗黙に呼び出しています。内部的に処理を行っているのは大変ありがたいのですけれど、コードはROM領域(0x08000000)に固定されています。つまりとんでもなく低速です。使わないことにはこしたことはないけれど、どうしても必要な場合はBIOS機能を使いましょう。以下に除算ベンチマークをしてみた比較結果を記載しました。

 #include "lib/gba.h"
 
 //---------------------------------------------------------------------------
 void WaitForVsync(void)
 {
 	while (*(volatile u16*)0x4000006 >= 160) {};
 	while (*(volatile u16*)0x4000006 <  160) {};
 }
 //---------------------------------------------------------------------------
 void WaitForVCount(u32 c)
 {
 	while (*(volatile u16*)0x4000006 != 0) {};
 }
 //---------------------------------------------------------------------------
 u32 GetVCount(void)
 {
 	return *(volatile u16*)0x4000006;
 }
 //---------------------------------------------------------------------------
 #define LOOP	100
 
 int main(void)
 {
 	volatile s32 i, r1, r2, c1, c2;
 	volatile s32 m = 1234567;
 
 
 	// libc --------------------------------
 	c1 = 0;
 	WaitForVCount(0);
 
 	for(i=0; i<LOOP; i++)
 	{
 		r1 = m % 13;
 	}
 	c1 = GetVCount();
 
 	// bios --------------------------------
 	c2 = 0;
 	WaitForVCount(0);
 
 	for(i=0; i<LOOP; i++)
 	{
 		r2 = Mod(m, 13);
 	}
 	c2 = GetVCount();
 
 	// result ------------------------------
 	TRACE("result: %d %d\n", r1, r2);
 	TRACE("libc: %d\n", c1);
 	TRACE("bios: %d\n", c2);
 
 	for(;;)
 	{
 		WaitForVsync();
 	}
 }

- 除算テスト(カードリッジ内、THUMBコード。動作確認:VisualBoyAdvance-1.8.0-beta3)
|ループ回数|5|10|20|30|40|50|100|
|libgcc|4|8|17|26|35|43|87|
|BIOS|1|3|5|8|11|14|29|

表からわかるとおり100回ループするとVcountで87!という驚異の数値になります。VBLANKが68以内ということを考えたのなら、この数値は非常に重たいと言わざる負えません。とはいえ全面的にlibgccを使うのはやめようといっているのではなく、自分の作りたいものを想像して許容するかどうかのトレードオフを考えてほしいと思っています。カレンダーアプリなら別にいいかとか、シューティングゲームならやめようとか。
表からわかるとおり100回ループするとVcountで87!という驚異の数値になります。VBLANK期間が68ということを考えたのなら、この数値は非常に重たいと言わざる負えません。とはいえ全面的にlibgccを使うのはやめようといっているのではなく、自分の作りたいものを許容するか、トレードオフを考えてほしいと思っています。カレンダーアプリなら別にいいかとか、シューティングゲームならやめようとか。

- test.map
 Archive member included to satisfy reference by file (symbol)
 
 c:/devkitpro/devkitarm/bin/../lib/gcc/arm-none-eabi/12.2.0\libgcc.a(_divsi3.o)
                               obj/main.o (__aeabi_idivmod)
 c:/devkitpro/devkitarm/bin/../lib/gcc/arm-none-eabi/12.2.0\libgcc.a(_dvmd_tls.o)
                               c:/devkitpro/devkitarm/bin/../lib/gcc/arm-none-eabi/12.2.0\libgcc.a(_divsi3.o) (__aeabi_idiv0)
 
 (中略)
 
  *(EXCLUDE_FILE(*.iwram*) .text*)
  .text.startup  0x080001f8       0xb8 obj/main.o
                 0x080001f8                main
  .text          0x080002b0      0x148 c:/devkitpro/devkitarm/bin/../lib/gcc/arm-none-eabi/12.2.0\libgcc.a(_divsi3.o)
                 0x080002b0                __divsi3
                 0x080002b0                __aeabi_idiv
                 0x080003d8                __aeabi_idivmod
  .text          0x080003f8        0x4 c:/devkitpro/devkitarm/bin/../lib/gcc/arm-none-eabi/12.2.0\libgcc.a(_dvmd_tls.o)
                 0x080003f8                __aeabi_idiv0
                 0x080003f8                __aeabi_ldiv0

何気なく普通に書いたソースコードだったとしても、コンパイラは勝手にlibgccなどを入れてきます。make後に生成されたmapファイルを見て、意図しない関数が入っていないかチェックしてください。そもそも入れるのが嫌ならばコンパイルオプションで無効化しましょう。以下のように-nostdlibすると問答無用でエラー表示してくれるようになります。

 LDFLAGS = -Map $(MAPFILE) -Wall -specs=gba.specs -nostartfiles
 #LDFLAGS = -Map $(MAPFILE) -Wall -specs=gba.specs -nostartfiles -nostdlib

** 履歴 [#e04f2feb]
- 2023/04/14
- 2007/10/16

トップ   差分 履歴 リロード   一覧 検索 最終更新   ヘルプ   最終更新のRSS