ARMの開発環境 (1) ARM 社のもの ARM Software Development Toolkit(アカデミック版) http://cis.k.hosei.ac.jp/~nakata/lectureMachine/202u_w32_v1.zip ARM Developer Suite(評価版 45日間) S. Furber著/アーム(株)監訳「改訂ARMプロセッサ」CQ出版社 (2) DevKitAdv http://devkitadv.sourceforge.net/index.html (3) GCC Linux/GBA用 西田亙著「Linuxから目覚める僕らのゲームボーイ」 ソフトバンク パブリッシング Linux/Zaurus用 http://www.nk.rim.or.jp/~jun/slasm/arm00.html Linux Zaurusでアセンブリプログラミング ARM Software Development Toolkit のインストール (1) 202u_w32_v1.zipを解凍し、その中のARM202U を C:\ARM202U に置く。 C:\ARMProjectというディレクトリを作る。 (2) C:\ARM202U\BIN\APM.EXEをダブルクリックして立ち上げる。 Project -> New... で、たとえば C:\ARMProject\test1.apj を指定すると、TEST1.APJというプロジェクトがC:\ARMProject\ にできる。 (「マイドキュメント」には入れないこと) Options -> Directories... で C:\ARM200 となっているところ(3箇所ある)を C:\ARM202U に変更する(たとえば、 C:\ARM200\BIN\ を C:\ARM202U\BIN\ に変更)。 インストールした 場所の指定 ARM Software Development Toolkit を使う(プログラム作成) APM(ARM Project Manager)で File -> new でUntitled1という名前のファイルが出来てその画面(最初は空)がでるから、 そこにアセンブリ言語のプログラムを書いていく。 X Y Z AREA ENTRY LDR LDR ADD STR DCD DCD DCD END test,CODE R0, X R1, Y R0, R0, R1 R0, Z 15 21 0 ; “タブAREAタブtest,CODE”と叩く ; “タブENTRY”と叩く。タブはスペースでも可 ; “タブLDRタブR0, X”と叩く。 R0 = X; ; R1 = Y; LDRはLoad Register ; R0 = R0 + R1; ; STRはStore Register, Z = R0 ; X番地に定数(DCD: Define Constant)15 ; Y番地に定数21 ; Z番地に定数0 X,Y,Zはラベルと呼ばれ ; end of program 行の先頭に書くことで その値が定義される。 これを test1.s という名前でARMProjectに保存する。 ARM Software Development Toolkit を使う(プロジェクト作成) APM(ARM Project Manager)で Project -> edit... でtest1.sをプロジェクトTEST1.APJに付け加える。 (プロジェクトについて何か行うときは、そのプロジェクトのウィンドウを選んでおく。 質問のウィンドウが出たら、大体は「はい」と答えればよい。) 次に Project -> Build TEST1.APJ を行う。ビルドでは、そのプロジェクトのアセンブラ言語のプログラムから実行形式の プログラムを作る。 Building... Linking (C:\ARMP`ROJ\TEST1) ... Build Complete. と表示されたら成功である。 プロジェクト作成に成功した時 ARM Software Development Toolkit を使う(実行1) APM(ARM Project Manager)で Project -> Debug TEST1.APJ を選ぶ。そうすると、ARM Debuggerの画面が作られる。 Execution Window (8008番地に色が付いている。すべて16進表示) Console Window などがある。次に View -> Registers -> User Mode を選んで、User Registersを表示する。その下から2番目のPCはレジスタ15番であ り、プログラムカウンタである。次に実行される命令は8008番地の命令である。 Execute -> Step を選ぶ、あるいは、Stepボタンをクリックすると、 800c番地で止まる。そこには bl test という命令がある(testはプログラムの中に書いておいた名前)。 今度は、 Execute -> Step In を選ぶ、あるいは、Step Intoボタンをクリックする。すると、testに飛ぶ(ブランチす る)。Execution Windowの内容はtest1.sに変わる。 User RegistersのPCを見ると、 その値は8080であるから、このプログラムの先頭番地は8080である Project -> Debug TEST1.APJ を選んだ結果 View -> Registers -> User Mode Execute -> Step Execute -> Step In を選んだ結果 ARM Software Development Toolkit を使う(実行2) View -> Memory... を選んで、8080番地を指定すると、その番地以降の内容がMemory Windowに表 示される。それを見ると、 LDR R0, X は機械語では 0xe59f0008 8090番地に10進の15(16進ではf) 8094番地に10進の21(16進では15)。 ここで、 Execute -> Step In を選ぶ(Execute -> Stepを選んでも同じ)と8080番地の命令が実行される。 User Registersで見るとX番地(8090番地)の値がr0レジスタにロードされている。 さらにステップ実行を続けていくと、 r1に0x15がロードされ、 それとfとを加えた0x24がr0に入り、 それがZ番地(8098番地)にストア(格納)される 。 X、Y、Zが実際に何番地になっているかは、 View -> Low Level Symbols のウィンドウで調べることもできる。 View -> Memory... で8080を指定 Execute -> Step In で8080番地の命令を実行 STR命令まで実行 View -> Low Level Symbols で名前と番地の対応がわかる ARM Software Development Toolkit を使う(実行3) そこでさらに、ステップ実行をしたらどうなるか。 計算機は次の番地にある 0x0000000f を命令であると思って実行しようとする。 それがどんな命令であるかを調べるためには View -> Disassembly... ( Disassemblyはアセンブラの逆のことをする) を選んで、8080または8090番地を指定すればよい。 それで見ると、 0x0000000f は命令語としては andeq r0, r0, pc であることが分かる。それを実行するとr0の値が0になる。 コンピュータは「プログラム内蔵方式」 プログラム内蔵方式ではプログラムはメモリに内蔵されている。メモリに内蔵されている ものはデータとして扱うことも出来るし、命令語として扱うことも出来る。コンピュータは PCの指すところにあるものを命令語と見なし、その命令語の実行によって取り出された り格納されたりするものをデータと見なすだけである。 View -> Disassembly... 例題2 AREA ENTRY LDR LDR LDR LDR test2, CODE add str ADD CMP BNE SWI DCD DCD DCD DCD AREA % END R0, R0, R1 R0, [R3] ; Out[0] ], Out[1],…,にストア。 R3, R3, #4 ; R3 = Out[i]番地。i=1,2,3,... R0, R2 ; compare R0 and R2 L1 ; if (R0 != R2) go to L1; 0x11 ; Software Interrupt: 0x11 はHalt 0 ; 定数0 1 ; 定数1 10 ; 定数10 Out ; A_Out番地には Outの値(番地) block, DATA, READWRITE 40 ; "%": 値0のバイト、それを40個 ; Out[0], Out[1],…, Out[9], L1 M0 M1 M10 A_Out Out R0 = 0; R1 = 1; R2 = 10; i = 0; do { R0 = R0 + R1; Out[i] = R0; i = i + 1; } while (R0 != R2) R0, M0 R1, M1 R2, M10 R3, A_Out ; Out[0]の番地 str命令を最初に実行した直後 CMP命令を10回目に実行した直後 大文字と小文字の使い分け AREA ENTRY LDR LDR LDR LDR test2, CODE R0, M0 R1, M1 R2, M10 R3, A_Out L1 M0 M1 M10 A_Out Out add str ADD CMP BNE SWI DCD DCD DCD DCD AREA % END R0, R0, R1 R0, [R3] R3, R3, #4 R0, R2 L1 0x11 0 1 10 Out block, DATA, READWRITE 40 AREA,CODE,ENTRY,END などの疑似命令は大文字 ADD, STR などの命令は add, strのように小文字でもよい。 R0,R1などのレジスタ名は r0, r1のように小文字でもよい。 L1,M0,A_Outなどのラベルは 大文字と小文字が混在してもよい。 ただし、定義と使用は合っていなけ ればならない 例題3 1から9までの数をコンソールに出力する プログラムは次のようにすればよい。 小さな定数は命令の中に 入れておくことができる。 WriteC EQU 0x0 AREA test3, CODE ENTRY MOV R1, #0 L1 ADD R1, R1, #1 ADD R0, R1, #0x30 SWI WriteC CMP R1,#10 BNE L1 SWI 0x11 END R1 = 0; do { R1 = R1 + 1; R0 = R1 + 0x30; WriteC(R0); } while (R1 != 10) ; WriteCは定数0(0x0)を表す ; R1 = 0; 命令の中の定数nは'#n'の形 ; R1 = R1 + 1; ; 数値1=00000001から文字コード00110001へ ; WriteC(R0); ; compute R1 - 10 and set Condition Code ; if (Z==0 すなわちR1 != 10) go to L1; JISの文字コード 上位4ビット 下位4 ビット ASCIIの場合は「\」が「\」 SWI 0を最初に実行した直後 ARMアーキテクチャ ARM: Advanced RISC Machine RISC: Reduced Instruction Set Computer (CISC:Complex Instruction Set Computer) RISCの特徴 ・ロード/ストア アーキテクチャ メモリにアクセスする命令はロード/ストア だけで ADD R0, M1 のような命令はない ・命令は固定長 命令語の長さは一定。 ARMでは固定長の32ビット命令 データ型 ・バイト:8ビット 符号付き(signed): -128 ~ 127 符号なし(unsigned) : 0 ~ 255 ・ハーフワード:16ビット 符号付き: -32768 ~ 32767 符号なし: 0 ~ 65535 2バイト境界でアライン(偶数番地から) ・ワード:32ビット 符号付き: -2147483648 ~ 2147483647 符号なし: 0 ~ 4294967295 4バイト境界でアライン(4の倍数番地から) そのほかに、浮動小数点型(実数型)のデータがあるが、 ここではとりあげない。 レジスタ 32ビットのレジスタが16個ある(ユーザモードで使える)。 r0~r15 r15: pc (program counter) r14: lr(link register) r13: sp(stack pointer) CPSR: Current Program Status Register) 条件コードフラッグを変更する命令を実行した結果により N: 負(Negative)。 負のとき(最上位ビットが'1'のとき)'1'、 その他の時'0'にリセット Z: ゼロ(Zero)。ゼロのとき'1'をセット、その他の時'0'にリセット C: 桁上げ(Carry)。加減算で、最上位ビットからの桁上げが あったときにセット、またはシフトによるキャリアウトをセット V: あふれ(oVerflow)。加減算で、符号付きオーバフロー が生じたときにセット、その他の時リセット メモリシステム メモリは0から231-1までのバイトの線形配列と考えられる。 ワード、ハーフワードとバイトとの位置関係は little endian bi t 31 bi t 0 bi t 31 bi t 0 23 22 21 20 20 21 22 23 19 18 17 16 16 17 18 19 word16 15 14 13 word16 12 12 half-word14 half-word12 11 10 9 6 8 2 15 8 9 10 11 word8 5 4 byte6 half-word4 3 14 half-word12 half-word14 word8 7 13 1 byte address 0 byte3 byte2 byte1 byte0 (a) Little-endian memory organization 4 5 6 7 byte5 half-word6 0 1 2 byte address 3 byte0 byte1 byte2 byte3 (b) Big-endian memory organization ARMアセンブリ言語 以下はARM社のアセンブリ言語で、gccのARM用のものとは違う。 アセンブラの疑似命令あるいはディレクティブ AREA: ENTRY: END: ORG: IMPORT: EXPORT: ALIGN: データやコードのひとまとまりの始まり 実行される最初の命令 プログラムの終わりを示す 以下のプログラムの先頭番地を指定する 他のファイルで定義されている名前を使う 名前を他のファイルで使えるようにする 次の番地をワード境界にあわせる 命令語 命令語の形 [ラベル] WS 命令語 [WS][ ; コメント] [ ]で囲まれたものはなくてもよい WS(white space)ではスペースまたはタブ ラベルは行の先頭から始まり、命令語の前にはWSが必要である 例: L1 ADD R1,R1,#1 ; R1 = R1 + 1; ADD R0,R1,#0x30 ; 数値1から文字コードへ 定数名 定数名の定義 ラベル WS EQU 式 EQUはequate(同等視する)を意味する疑似命令 この形で、「式」の値を「ラベル」の値とする。 例: Abc EQU 100 以後100と書く代わりにAbcと書くことが出来る データ データは以下の形で書く [ラベル] WS (DCB|DCW|DCD|%) WS データ DCB(バイト)、DCW(ハーフワード)、DCD(ワード)、% いずれも疑似命令 DCはDefine Constantの意味 %の後にはゼロバイト(1バイトでその値が0)の数を書く 例: DCB4 DCB 'a', 0xff, 5, 0 DCW6 DCW 'a', 0xff, 5, 0, 12345, 0xffff DCD5 DCD 'a', 0xff, 12345, 0xffff, 0xffffff DCB "C_string",0 最後は DCB 'C', '_', 's', 't', 'r', 'i', 'n', 'g', 0 と同じ JISの文字コード 上位4ビット 下位4 ビット ASCIIの場合は「\」が「\」 データ処理命令(1) 算術演算: ADD、SUB、ADC(キャリ付き加算)、SBC(キャリ付き減算)、 RSB(逆減算)、RSC(キャリ付き逆減算) add r0, r1, r2 ; r0 = r1 + r2 sub r0, r1, r2 ; r0 = r1 - r2 rsb r0, r1, r2 ; r0 = r2 - r1 論理演算: AND 両方1なら1。マスク演算。共通集合 01010101 AND 00001111 = 00000101 (下4ビットを取り出す) ORR どちらかが1なら1。和集合 01010101 ORR 00001111 = 01011111 EOR(Exclusive OR): 両者が異なれば1。ビットごとの加算。 01010101 EOR 00001111 = 01011010 BIC 第2オペランドが1なら0。 ANDの逆のマスク演算 BIC r0,r1,r2 ; r0 = r1 and not r2 01010101 BIC 00001111 = 01010000 (下4ビットを0) データ処理命令(2) レジスタ転送 MOV(転送)、MVN(否定して転送)。 MOV r0, r2 ; r0 = r2 MVN r0, r2 ; r0 = not r2 比較演算(結果でcc(条件コード)をセット) CMP(比較)、CMN(符号反転して比較)、TST、TEQ CMP r1, r2 ; compare: set cc on r1 - r2 CMN r1, r2 ; compare negative: set cc on r1 + r2。 TST r1, r2 ; bit test: set cc on r1 and r2 TEQ r1, r2 ; bit test equal: set cc on r1 xor r2 即値オペランド(immediate operand) 演算対象の2番目のレジスタの代わりに定数を使う。 8ビットで表現できるデータに限る。たとえば#0xff*4、#0xff*64 ADD r3, r3, #1 ; r3 = r3 + 1 MOV r4, #9 ; r4 = 9 AND r8,r7,#0xff ; r8 = r7の下位8ビット データ処理命令の形式 31 28 27 26 25 24 cond 00 # opcode 21 20 19 S 16 15 Rn 12 11 0 operand 2 Rd destination register first operand register set condition codes arithmetic/logic function 25 11 8 7 #rot 1 0 8-bit immediate immediate alignment 11 7 0 5 Sh #shift 25 6 4 3 0 Rm 0 immediate shift length shift type second operand register 11 8 Rs register shift length 7 0 6 Sh 5 4 1 3 0 Rm シフトしたオペランド データ処理命令(シフト) LSL: Logical Shift Left 論理左シフト。 空いたビットには0 LSR: Logical Shift Right 論理右シフト。 空いたビットには0 ASR: Arithmetic Shift Right 算術右シフト。空いたビットには 符号ビットと同じもの ROR: Rotate Right 右回転。右端から左端 RRX: Rotate Right Extended (Cビットを先頭につけ 1ビット右回転) データ処理命令(シフト例) シフト操作を指定するのは、命令語の形式のoperand2 ADD r3, r2, r1, LSL #3 ; r3 = r2 + r1*23 ADD r5, r5, r3, LSL r2 ; r5 = r5 + r3*2r2 logical shift(論理シフト)の例: 1110 0000 1001 0000 0011 0000 0101 0010 を「LSR #4」で右に4ビットシフトすると 0000 1110 0000 1001 0000 0011 0000 0101 さらに「LSL #4」で左に4ビットシフトすると 1110 0000 1001 0000 0011 0000 0101 0000 arithmetic shift(算術シフト)の例: 右にmビットシフトしたら、ほぼ2mで割った数に。符号は不変 1110 0000 1001 0000 0011 0000 0000 0010 を右に4ビット算術シフトすると 1111 1110 0000 1001 0000 0011 0000 0000 乗算、命令の実行時間(サイクル) 乗算 MUL r4,r3,r2; r4 = r3 * r2の下位32ビット 即値オペランドは使えず、結果レジスタが第2ソースレジスタと同じにはできない 乗算には時間がかかるから別の命令で MOV r0, r1, LSL #16 ; r0 = r1 * 65536 ADD r0, r1, r1, LSL #2 ; r0 = r1 * 5 RSB r0, r1, r1, LSL #3 ; r0 = r1 * 7 View -> Debugger Internals のウィンドウの中のclockの項を見ればよい。 Options -> Debugger Configuration -> ARMulator でclock speedを1MHzとすると、 クロック値=サイクル数 各命令の実行時間(サイクル数) ALU 1 演算命令:ADD, SUB, MOV; B, BL 3 分岐命令 LDR 3 ロード命令: +2 if Rd is pc. STR 2 ストア命令 MUL 1+M 乗算命令: M = 1~4(オペランドによる) データ転送命令 LDR/STRの命令形式 31 28 27 26 25 24 23 22 21 20 19 cond 01 # P U BW L 16 15 Rn 12 11 0 offset Rd sou rce/destination regis ter bas e reg iste r loa d/store write-b ack (au to-in dex) uns igne d byte /word up/down pre-/post-inde x 25 11 0 12-bit immediate 0 25 11 7 6 5 4 3 #shift 1 imm edia te s hift len gth shi ft typ e offse t reg iste r Sh 0 0 Rm データ転送命令のアドレッシング(1) レジスタ間接アドレッシング(Rnでレジスタ指定、offset=0) LDR r0, [r1] ; r0 = mem[r1] = r1の値をnとして、n番地のワード STR r0, [r1] ; mem[r1] = r0 PC相対アドレッシング(Rn=pc、offset=その命令からの距離(12ビット以内)) LDR R2, M10 LDR R3, A_Out ; M10はアセンブラがPC相対にする ; A_OutはPC相対。Out番地は近くないかも ... M10 DCD 10 A_Out DCD Out AREA block, DATA Out % 40 ; address of Out(32ビット) このように書くのはわずらわしいから LDR R3, =Out と書けば、アセンブラがA_Outに相当するものを作り出してくれる データ転送命令のアドレッシング(2) ベース+オフセット(Rnでベース指定、offsetでベースからの距離) pre-indexedアドレッシング LDR r0, [r1,#4] ; r0 = mem [r1 + 4] 自動アドレッシング LDR r0, [r1,#4]! ; r0 = mem[r1 + 4] ; r1 = r1 + 4 post-indexedアドレッシング LDR r0, [r1], #4 ; r0 = mem[r1] ; r1 = r1 + 4 loop LDR LDR LDR STR CMP BNE r1, =TABLE1 r2, =TABLE2 r0, [r1], #4 r0, [r2], #4 r0, #0 loop i = 0; j = 0; do { r0 = TABLE2[j++]; TABLE1[i++] = r0; } while (r0 != 0) データ転送命令のアドレッシング(3) ベース+レジスタオフセット(offsetにもレジスタ指定、シフトも) LDR r1, =TABLE1 LDR r2, =TABLE2 MOV r3, #0 loop LDR r0, [r1, r3, LSL #2] STR r0, [r2, r3, LSL #2] ADD r3, r3, #1 CMP r0, #0 BNE loop i = 0; do { r0 = TABLE2[i]; TABLE1[i] = r0; i++; } while (r0 != 0) バイト・データの転送 データ転送命令の命令コードに"B"をつければ、 バイト・データが転送される LDRB r0, [r1] ; r0 = mem8[r1] r1の示す番地の1バイトのデータをr0レジスタの下位8ビット に入れ、その他のビットはすべて0になる。 AREA Copy, CODE ENTRY LDR r1, =srcstr LDR r0, =dststr strcopy LDRB r2, [r1], #1 STRB r2, [r0], #1 CMP r2, #0 BNE strcopy SWI 0x11 文字列データを 1バイトずつコピーする AREA Strings, DATA srcstr DCB "First string",0 dststr DCB "Second string",0 END 分岐命令 分岐命令の形式 31 28 27 cond 25 24 23 101 0 24-bit signed word offs et L (24-bit signed word offset)*4 + この命令のアドレス+8 が飛び先(分岐先)のアドレス(PC相対アドレッシング) 4倍する:飛び先の命令のアドレスは4の倍数に決まっているから 8を足す:この命令の実行時にはPCはそれだけ進んでいるから 1 2 3 instruc tion fetc h dec ode execute fetc h dec ode execute fetc h dec ode execute time 条件分岐命令 Branch B BAL BEQ BNE BPL BMI BCC BLO BCS BHS BVC BVS BGT BGE BLT BLE BHI BLS Interpretation Unconditional Always Equal Not equal Plus Minus Carry clear Lower Carry set Higher or same Overflow clear Overflow set Greater than Greater or equal Less than Less or equal Higher Lower or same Normal uses Always take this branch Always take this branch Comparison equal or zero result Comparison not equal or non-zero result Result positive or zero Result minus or negative Arithmetic operation did not give carry-out Unsigned comparison gave lower Arithmetic operation gave carry-out Unsigned comparison gave higher or same Signed integer operation; no overflow occurred Signed integer operation; overflow occurred Signed integer comparison gave greater than Signed integer comparison gave greater or equal Signed integer comparison gave less than Signed integer comparison gave less than or equal Unsigned comparison gave higher Unsigned comparison gave lower or same 条件実行 分岐命令はハードウェアのパイプラインを乱す 次の命令のデコードや次の次の命令のフェッチの時間が無駄 この無駄を解消するための工夫 遅延分岐:分岐命令の次の命令を実行してから分岐 分岐予測:予測したほうの命令をフェッチ 条件実行:条件が成り立つときだけ実行。分岐しないですむ CMP BEQ ADD SUB BYPASS … r0, #5 BYPASS r1, r1, r0 r1, r1, r2 = if ((r0==r1) && (r2==r3)) r4++; CMP r0, #5 ADDNE r1, r1, r0 SUBNE r1, r1, r2 BYPASS … = CMP r0, r1 CMPEQ r2, r3 ADDEQ r4, r4, #1 条件実行のコード Opcode [31:28] 0000 0001 0010 0011 0100 0101 0110 0111 1000 1001 1010 1011 1100 1101 1110 1111 Mnemonic extension EQ NE CS/HS CC/LO MI PL VS VC HI LS GE LT GT LE AL NV Interpretation Equal / equals zero Not equal Carry set / unsigned higher or same Carry clear / unsigned lower Minus / negative Plus / positive or zero Overflow No overflow Unsigned higher Unsigned lower or same Signed greater than or equal Signed less than Signed greater than Signed less than or equal Always Never (do not use!) Status flag state for execution Z set set = 1 Z clear clear = 0 C set C clear N set N clear V set V clear C set and Z clear C clear or Z set N equals V N is not equal to V Z clear and N equals V Z set or N is not equal to V any none SWI(Software Interrupt)命令 SWI_WriteC (SWI 0) r0の1バイトを出力 SWI_Write0 (SWI 2) r0が指す文字列(0が終わりを示す)を出力 SWI_ReadC (SWI 4) キーボードから1バイト読んでr0に入れる SWI_Exit (SWI 0x11) ユーザプログラムを終了しデバッガに戻る SWI_Clock (SWI 0x61) 実行時間を1/100秒単位でr0に返す SWI_Open (SWI 0x66) r0が指すファイル名のファイルをオープン r1がモードを示す整数:0 (read), 4 (write), 8 (apend) そのファイルのハンドルがr0に返される。 オープンに失敗すると0がr0に返される SWI_Close (SWI 0x68) ファイルをクローズする。 ファイルのハンドルはr0に与えておく。成功すれば0がr0に返る SWI_Write (SWI 0x69) バッファからファイルに書き出す r0=ファイルのハンドル、r1=バッファの先頭番地、r2=書くバイト数 SWI_Read (SWI 0x6a) ファイルからバッファに読み込む r0=ファイルのハンドル、r1=バッファの先頭番地、r2=読むバイト数 入出力プログラム(1) AREA hello1,CODE SWI_WriteC EQU 0x0 SWI_Exit EQU 0x11 ENTRY START LDR r1, =TEXT LOOP LDRB r0, [r1], #1 CMP r0, #0 SWINE SWI_WriteC BNE LOOP SWI SWI_Exit TEXT DCB "Hello World" DCB 0x0d, 0x0a, 0 END r0の1バイトが0と等しくなければそれを出力 r0の1バイトが0と等しくなければループ "Hello World"と改行を出力 0x0aがLine Feed(改行)、 0x0dがCarriage Return(復帰) (unixのファイルでは0x0aだけ、 マックのファイルでは0x0dだけが使われる) 入出力プログラム(2) write_only EQU 4 AREA hello2,CODE ENTRY start LDR r0, =filename MOV r1, #write_only SWI SWI_Open MOV r5, r0 LDR r1, =String MOV r2, #14 SWI SWI_Write MOV r0, r5 SWI SWI_Close SWI SWI_Exit filename DCB "hello.txt",0 String DCB "Hello World!" DCB 0x0d, 0x0a, 0 END r0にファイル名hello.txtのアドレス r1にファイルの属性「書き込みのみ」 ファイルをオープン、ハンドルをr0へ ファイルhello.txtのハンドルをr5へ 出力する文字列のアドレスをr1へ 出力する文字列の長さをr2へ 出力する(r0のハンドルのファイルへ) r0は壊れるから入れなおす 最後にファイルをクローズ Game Boy Advance(GBA)-1 CPUはARM7 アドレス 外部RAM 内部RAM I/Oレジスタ VRAM 容量 アクセス(サイクル) 8/16/32ビット 0200 0000 - 0203 ffff 256KB 3/3/6 0300 0000 - 0300 7fff 32KB 1/1/1 0400 0000 - 0400 03ff 1KB 1/1/1 0600 0000 - 0601 7fff 96KB 1/1/2 ・外部RAMと内部RAMにプログラムとデータ ・I/Oレジスタに書き込むことによって入出力が行われる ・VRAMの内容が画面に表示される。画面は縦160ドット×横240ドット 画面上の1つのドットは2バイトで表現され、RGB形式の色情報は各5ビットで 0BBBBBGGGGGRRRRR 0111110000000000 = 7C00 = 1F/0/0 ブルー 0000001111100000 = 03E0 = 0/1F/0 グリーン 0000000000011111 = 03E0 = 0/0/1F レッド 0111111111111111 = 7FFF = 1F/1F/1F 白 0000000000000000 = 0000 = 0/0/0 黒 Game Boy Advance(GBA)-2 画面の左上に、黒点、白点、黒点、白点、黒点、白点、黒点、白点、 を表示するプログラム mov r1, #0x04000000 ldr r2, =0xF03 str r2, [ r1 ] mov r3, #0x06000000 ldr r4, =0x7FFF str r4, [ r3 ] str r4, [ r3, #4 ] str r4, [ r3, #8 ] str r4, [ r3, #12 ] loop b loop ; I/O base address ; video mode 3 ; display control ; VRAM address ; 0x00007FFF 黒点と白点 ; 最初の黒点と白点 ; 2番目の黒点と白点 ; 3番目の黒点と白点 ; 4番目の黒点と白点 ; 無限ループ ウィンドウズ上のGBAのエミュレータVisualBoyAdvanceで試す、 GBAの画面を赤、緑、青の三色で塗りつぶす、 のは「ARMの説明.doc」を見よ サブルーチン プログラムの1単位はルーチン(routine)と呼ばれる サブルーチン(subroutine) 他のルーチンから呼び出される。Javaのメソッドに相当する。 メイン・ルーチンまたは主ルーチン 最初に実行されるルーチン(Javaのmainメソッドに相当)。 サブルーチンを実現するためには、どこからそれが呼び出されても、 呼び出した元の場所へ戻る仕掛けが必要。 サブルーチンに飛ぶ(ブランチする)と同時に戻り番地を渡せばよい。 その命令がBL(Branch with Link)命令である。 BL m を実行すると、そのときのPCの値(次の命令の番地)を レジスタr14に入れてm番地へ飛ぶ。サブルーチンの最後で MOV PC, r14 ; MOV r15, lr を実行すると「BL m」命令の次の命令に戻る。 "r14"の代わりに"lr"または"LR"(Link Register)と書いてもよい。 サブルーチンの例 start MOV MOV BL MOV MOV BL SWI r0, #15 r1, #20 addsubr r0, #30 r1, #54 addsubr 0x11 addsubr ADD r0, r0, r1 MOV pc, lr ; Set up parameters ; Call subroutine ; Set up parameters ; Call subroutine ; terminate ; Subroutine addsubr ; r0 = r0 + r1 ; Return from subroutine ; with result in r0 サブルーチンgcd(最大公約数) start MOV r0, #10 MOV r1, #4 BL gcd ; r0 = gcd(10, 4) STR r0, result SWI 0x11 result DCD 0 int gcd(int r0, int r1){ gcd do { CMP r0, r1 if (r0 > r1) r0 -= r1; SUBGT r0, r0, r1 if (r0 < r1) r1 -= r0; SUBLT r1, r1, r0 } while(r0 != r1) BNE gcd return r0; MOV pc,lr } サブルーチン:文字列コピー main LDR LDR BL SWI r0, =srcstr r1, =dststr strcopy 0x11 ; pointer to first string ; pointer to second string ; copy the first into second ; and exit srcstr DCB "This is my first (source) string",0 dststr DCB "This is my second (destination) string",0 ALIGN strcopy LDRB r2, [r0], #1 STRB r2, [r1], #1 CMP r2, #0 BNE strcopy MOV pc, lr ; realign address to word boundary ; load byte, then update address ; store byte, then update address ; check for zero terminator ; keep going if not ; return サブルーチン呼び出しのネストの問題 start RA0 MOV MOV BL SWI r0, #15 r1, #20 firstfunc 0x11 ; Set up parameters ; Call firstfunc. lr = RA0 ; terminate firstfunc ADD r0, r0, r1 BL secondfunc RA1 MOV pc, lr ; Subroutine firstfunc ; r0 = r0 + r1 ; Call secondfunc. lr = RA1 ; Return to RA1 !! (not RA0) secondfunc ADD r0, r0, #45 MOV pc, lr ; Subroutine secondfunc ; r0 = r0 + 45 ; Return to RA1 firstfucから戻れない 戻り番地をスタックに積む main(){ f(){ g(){ (1) f(); (2) g(); f1: m1: (6) } スタック h(){ (3) h(); g1: (5) } (4) } } g1 f1 f1 m1 m1 m1 (1)で積む (2) で積む (3) で積む (6)で降ろす (5) で降ろす (4) で降ろす 戻るときはスタックのトップを降ろし、そこへ飛べばよい。 サブルーチン呼び出しのネストの解決 start RA0 MOV MOV BL SWI firstfunc STR ADD BL RA1 LDR r0, #15 r1, #20 firstfunc 0x11 ; Set up parameters ; Call firstfunc. lr = RA0 ; terminate ; Subroutine firstfunc lr, [sp], #4 ;スタックに戻り番地(RA0)を積む r0, r0, r1 ; r0 = r0 + r1 secondfunc ; Call secondfunc. lr = RA1 pc, [sp, #-4]! ;スタックから RA0を降ろしてそこへ戻る secondfunc STR lr, [sp], #4 ADD r0, r0, #45 LDR pc, [sp, #-4]! ; Subroutine secondfunc ;スタックのRA0の上にRA1を積む ; r0 = r0 + 45 ; スタックから RA1を降ろしてそこへ戻る 再帰的サブルーチンfactorial static int fact(int n){ if (n == 1) return 1; return n * fact(n-1); } factからfactを呼ぶときは引数 が変わっている。戻ったときに 以前の引数が必要。 引数もスタックに積めばよい。 STR lr, [sp], #4 ; 戻り番地をスタックへ積む STR R0, [sp], #4 ; R0にある引数nをスタックへ積む SUBS R0, R0, #1 ; 引数n - 1をR0へ BEQ fact1 ; if (n == 1) goto fact1 BL fact ; else fact(n - 1) factn LDR R1, [sp, #-4]! ; 引数nを降ろしてR1へ MUL R0, R1, R0 ; R0 = n * fact(n - 1) LDR pc, [sp, #-4]! ; 戻り番地を降ろして戻る fact1 LDR R0, [sp, #-4]! ; 引数1=fact(1)を降ろしてR0へ LDR pc, [sp, #-4]! ; 戻り番地を降ろして戻る fact fact STR lr, [sp], #4 STR R0, [sp], #4 SUBS R0, R0, #1 BEQ fact1 r0 3 BL fact factn LDR R1, [sp, #-4]! MUL R0, R1, R0 r13(sp) LDR pc, [sp, #-4]! r14(lr) main1 fact1 LDR R0, [sp, #-4]! r15(pc) fact LDR pc, [sp, #-4]! main LDR sp, =stackBegin MOV R0, #3 BL fact main1 STR R0, result ; result = fact(3) factorialの呼び出し(1) AREA stack,DATA,READWRITE stackBegin % 1000 スタック mainのBL fact命令実行直後 fact STR lr, [sp], #4 STR R0, [sp], #4 SUBS R0, R0, #1 BEQ fact1 r0 2 BL fact factn LDR R1, [sp, #-4]! MUL R0, R1, R0 r13(sp) LDR pc, [sp, #-4]! r14(lr) factn fact1 LDR R0, [sp, #-4]! r15(pc) fact LDR pc, [sp, #-4]! main LDR sp, =stackBegin MOV R0, #3 BL fact main1 STR R0, result ; result = fact(3) factorialの呼び出し(2) AREA stack,DATA,READWRITE stackBegin % 1000 3 main1 スタック factのBL fact命令の最初の実行直後 fact STR lr, [sp], #4 STR R0, [sp], #4 SUBS R0, R0, #1 BEQ fact1 r0 1 BL fact factn LDR R1, [sp, #-4]! MUL R0, R1, R0 r13(sp) LDR pc, [sp, #-4]! r14(lr) factn fact1 LDR R0, [sp, #-4]! r15(pc) fact LDR pc, [sp, #-4]! main LDR sp, =stackBegin MOV R0, #3 BL fact main1 STR R0, result ; result = fact(3) factorialの呼び出し(3) AREA stack,DATA,READWRITE stackBegin % 1000 2 factn 3 main1 スタック factのBL fact命令の2回目の実行直後 fact STR lr, [sp], #4 STR R0, [sp], #4 SUBS R0, R0, #1 BEQ fact1 r0 0 BL fact factn LDR R1, [sp, #-4]! MUL R0, R1, R0 r13(sp) LDR pc, [sp, #-4]! r14(lr) factn fact1 LDR R0, [sp, #-4]! r15(pc) fact1 LDR pc, [sp, #-4]! main LDR sp, =stackBegin MOV R0, #3 BL fact main1 STR R0, result ; result = fact(3) factorialの呼び出し(4) AREA stack,DATA,READWRITE stackBegin % 1000 1 factn 2 factn 3 main1 スタック factのBEQ fact1命令の実行直後 fact STR lr, [sp], #4 STR R0, [sp], #4 SUBS R0, R0, #1 BEQ fact1 r0 1 BL fact factn LDR R1, [sp, #-4]! MUL R0, R1, R0 r13(sp) LDR pc, [sp, #-4]! r14(lr) factn fact1 LDR R0, [sp, #-4]! r15(pc) factn LDR pc, [sp, #-4]! main LDR sp, =stackBegin MOV R0, #3 BL fact main1 STR R0, result ; result = fact(3) factorialの呼び出し(5) AREA stack,DATA,READWRITE stackBegin % 1000 1 factn 2 factn 3 main1 スタック fact STR lr, [sp], #4 STR R0, [sp], #4 SUBS R0, R0, #1 BEQ fact1 r0 2 BL fact r1 2 factn LDR R1, [sp, #-4]! MUL R0, R1, R0 r13(sp) LDR pc, [sp, #-4]! r14(lr) factn fact1 LDR R0, [sp, #-4]! r15(pc) factn LDR pc, [sp, #-4]! main LDR sp, =stackBegin MOV R0, #3 BL fact main1 STR R0, result ; result = fact(3) factorialの呼び出し(6) AREA stack,DATA,READWRITE stackBegin % 1000 1 factn 2 factn 3 main1 スタック factのLDR pc命令の実行直後 r0 = 1 fact STR lr, [sp], #4 STR R0, [sp], #4 SUBS R0, R0, #1 BEQ fact1 r0 6 BL fact r1 3 factn LDR R1, [sp, #-4]! MUL R0, R1, R0 r13(sp) LDR pc, [sp, #-4]! r14(lr) factn fact1 LDR R0, [sp, #-4]! r15(pc) main1 LDR pc, [sp, #-4]! main LDR sp, =stackBegin MOV R0, #3 BL fact main1 STR R0, result ; result = fact(3) factorialの呼び出し(7) AREA stack,DATA,READWRITE stackBegin % 1000 1 factn 2 factn 3 main1 スタック factのLDR pc命令の最後の実行直後 r0 = 2 レジスタの使い方のルール 独立に開発したサブルーチンがお互いに呼び合ってもうまく機能する ためには、サブルーチン間でのレジスタの使い方の約束が必要 一般に、calling conventionといわれる。 ARMの場合は、ARM Procedure Call Standard といわれ r0: 第1引数、整数の結果、スクラッチ r1: 第2引数、スクラッチ r2: 第3引数、スクラッチ r3: 第4引数、スクラッチ r4~r8: グローバル(r9~r13は特別な役割) スクラッチ: 使い捨て。サブルーチンは自由に使える。 caller save register: callerが、呼び出す前にsaveする グローバル: サブルーチンは勝手には使えない。 callee save register: 呼ばれたcalleeがsaveする サブルーチンの入口・出口で退避・回復 サブルーチンfactorial ルールに従ってレジスタの退避・回復を行う lr(戻り番地: callee save)、r4(callee save)を使うから fact STMIA sp!,{r4,lr} MOV r4, r0 SUBS r0, r0, #1 BEQ fact1 BL fact factn MUL r0, r4, r0 LDMDB sp!, {r4, pc} fact1 MOV r0, #1 LDMDB sp!, {r4, pc} ; 戻り番地とr4をスタックへ退避 ; r4 = n ; 引数n - 1をr0へ ; if (n == 1) goto fact1 ; else fact(n - 1) ; r0 = n * fact(n - 1) ; スタックから回復してreturn ; r0 = 1 ( = fact(1) ) ; スタックから回復してreturn 複数ファイルで構成するプロジェクト ファイルfactsub.s ファイルfactmain.s IMPORT fact AREA factmain,CODE ENTRY main LDR sp, =stackBegin MOV r0, #3 BL fact main1 STR r0, result SWI 0x11 result DCD 0 AREA stack,DATA stackBegin % 1000 END EXPORT fact AREA factsub, CODE fact STMIA sp!,{r4,lr} MOV r4, r0 SUBS r0, r0, #1 BEQ fact1 BL fact factn MUL r0, r4, r0 LDMDB sp!, {r4, pc} fact1 MOV r0, #1 LDMDB sp!, {r4, pc} END 別のファイルでの定義名を使うときは、 定義側でEXPORT、使う側でIMPORT ARMの命令(1) 算術演算 ADD{c}{S} Rd, Rn, op2 ADC{c}{S} Rd, Rn, op2 SUB{c}{S} Rd, Rn, op2 SBC{c}{S} Rd, Rn, op2 RSB{c}{S} Rd, Rn, op2 RSC{c}{S} Rd, Rn, op2 MUL{c}{S} Rd, Rm, Rs 論理演算 AND{c}{S} Rd, Rn, op2 ORR{c}{S} Rd, Rn, op2 EOR{c}{S} Rd, Rn, op2 BIC{c}{S} Rd, Rn, op2 op2 Rd = Rn + op2 Rd = Rn + op2 + Carry Rd = Rn – op2 Rd = Rn + op2 – Not(Carry) Rd = op2 – Rn Rd = op2 – Rn – Not(Carry) Rd = Rm * Rs (Rd != Rm) Rd = Rn AND op2 Rd = Rn OR op2 Rd = Rn ROR op2 Rd = Rn AND NOT op2 Rm #32bit_imm(5bit_non_0) Rm, LSL #5bit_imm Rm, LSR #5bit_imm Rm, ASR #5bit_imm Rm, ROR #5bit_imm Rm, LSL Rs Rm, LSR Rs Rm, ASR Rs Rm, ROR Rs Rm, RRX 命令コードにSをつけると、実行結果によって条件コード(フラグ:NZCV)をセット 命令コードにcをつけると、その条件コードがセットされているときだけ実行 c: EQ (Equal), NE (Not Equal), MI (Minus), PL (Plus, Positive or zero) GE (Greater or Equal), LT (Less Than), GT (Greater Than), LE (Less than or Equal) ARMの命令(2) レジスタ転送 MOV{c}{S} Rd, op2 Rd = op2 MVN{c}{S} Rd, op2 Rd = 0xFFFFFFFF EOR op2 比較演算 CMP{c}{S} Rn, op2 cc = Rn – op2 CMN{c}{S} Rn, op2 cc = Rn + op2 TST{c}{S} Rn, op2 cc = Rn AND op2 TEQ{c}{S} Rn, op2 cc = Rn EOR op2 分岐命令 B{c} label pc = label address BL{c} label lr = pc, pc = label address 命令コードにSをつけると、実行結果によって条件コードをセットする 命令コードにcをつけると、その条件コードがセットされているときだけ実行 c: EQ (Equal), NE (Not Equal), MI (Minus), PL (Plus, Positive or zero) GE (Greater or Equal), LT (Less Than), GT (Greater Than), LE (Less than or Equal) ARMの命令(3) データ転送 LDR{c} Rd, addr LDR{c}B Rd, addr LDM{c}DB Rd{!}, regs STR{c} Rd, addr STR{c}B Rd, addr STM{c}IA Rd{!}, regs ソフトウェア・インタラプト SWI 24bit_value Rd = [addr] Rd0-7 = [byte from addr], Rd8-31=0 [Rd]からregsへロード。regsは{ }で囲む [addr] = Rd [addr] = Rd0-7 regsを[Rd]へストア。regsは{ }で囲む ARMの命令(4) アドレッシング addr label =value [Rn] labelのアドレス( PC相対) valueがあるアドレス(PC相対) Rnの値がアドレス(レジスタ間接) [Rn, #+/-12bit_offset] [Rn, +/-Rm] [Rn, +/-Rm, LSL #5bit] ベース[Rn] +オフセット(定数)がアドレス [Rn] +オフセット(レジスタ)がアドレス [Rn] +オフセット(レジスタをシフト) [Rn, #+/-12bit_offset]! [Rn, +/-Rm]! [Rn, +/-Rm, LSL #5bit]! ベース+オフセットと同じだが 実行後ベースオフセットの 値を加える [Rn], #+/-12bit_offset [Rn], +/-Rm [Rn], +/-Rm, LSL #5bit アドレスは[Rn]だが 実行後Rnに加える
© Copyright 2024 ExpyDoc