このドキュメントは http://edu.net.c.dendai.ac.jp/ 上で公開されています。
アセンブリ言語はテキストファイルに記述します。 アセンブラのユーザーズマニュアル AVR Assembler Assembler source に示されているように記述します。 「ラベル: ニモニック オペランド」 を空白で区切って記述します。 ラベルの前に空白があってはいけません。 また、セミコロン(;)の後ろはコメントとしてアセンブル時に無視されます。 ニモニックの位置には他にディレクティヴ、マクロなどを置くことができます。
ラベル ニモニック オペランド コメント
.org 0x0013
reset:
ldi r16,low(RAMEND)
out SPL,r16
アセンブリ言語中で定数の演算式を書くことが出来ます。 アセンブラのユーザーズガイドの AVR Assembler Expressions にあるように C 言語で許されて いるような演算子がそのまま使えます。 但し、レジスタの内容は演算の対象ではありません。
アセンブラとはニモニックとオペランドから機械語を生成するものですが、 利便性を向上するためのアセンブラの機能があります。 ディレクティヴとはアセンブラに対する指示をする命令です。 ディレクティヴはピリオド(.)で始めます。
良く使うディレクティヴを紹介します。
マクロを定義します。 macro のオペランドにマクロ名を指定します。 マクロ定義の中で引数として @0 から @9 の 10 個の仮引数が使えます。 なお、AVR のアセンブラのマクロ中のラベルはローカルにしか使用できません ので、マクロ中に定義したラベルに外部からアクセスすることはできません。 なお、グローバルなラベルを使用するには #define 疑似命令を使用する手が ありますが、こちらこちらで基本的に一行で記述しなければならず、複数行を 書くには(\)を使用した継続行を使用する必要があります。
EQU などで使用できる関数を下記に示します。 なお、これらは一部なので、詳しくは アセンブラマニュアル Expressions を参照してください。
ここでは、 AVR Instruction Set Manual を解説します。 AVR Assembler の Instruction も関連しますが、載ってない情報もあります。
mnemonic | operand | comment |
---|---|---|
ld | r0,X | ; X レジスタの示す番地のメモリの値を r0 レ ジスタへ入れる |
ld | r0,X+ | ; X レジスタの示す番地のメモリの値を r0 レジスタへ入れた後、 |
; X を 1加算する | ||
ld | r0,-Y | ; Y を 1減算した後、 |
; Y レジスタの示す番地のメモリの値を r0 レジスタ へ入れる | ||
ld | r0,Y+5 | ; Y レジスタの値+5番地のメモリの値を r0 レジスタへ入れる |
AVR Instruction Set Manual の pp.3-9 という巻頭にページを割いて、図解で解説してある 「アドレッシングモード」とはなんでしょうか? 命令の内容として、「データをレジスタに入れる」など、基本的な動作は同じ であるけど、動作の対象が異なるような別々の命令に対して、同じニモニック を与える一方で、オペランドの指定の仕方を変えて区別するというものです。
AVR Instruction Set の pp.11-15 には命令が分類されています。
以下、特徴的な命令を分類しながら説明していきます。
アセンブラ言語では、オペランドに数値が書かれていた場合は、基本的にはア ドレスと解釈します。
一方で、コンピュータ用語、あるいはアセンブリ言語用語として、「即値 (immediate)」という言葉があります。 これは演算のデータとしてオペランドに含まれる定数を言います。 なお、ジャンプ命令や、コール命令のような分岐命令にも定数が付きますが、 演算に使用されないので、分岐先のアドレスは即値とは呼ばれないようです。
機械語には任意の論理式による IF 文はありません。その代わり、 演算結果が 0 かどうか、又は桁あふれ(オーバーフロー)したかどうかは常に 監視されていて、Status Register というレジスタに結果が入ります(データ シート pp.26-29)。 これはそれぞれ 1bit で状態を表しますが、このように状態を表す bit をフラグと呼びます。 フラグを 1 にすることを、「フラグを立てる」「フラグをセットする」のよ うに呼びます。一方、フラグを 0 にすることを「フラグを降ろす」「フラグ をクリアする」などと呼びます。
演算結果が 0 になったら Z (Zero)フラグがセットされます。一方、桁溢れをした場合 は C (Carry)フラグがセットされます。 この他、符号付き演算をサポートする N, V, S フラグや、8bit を 10 進2桁 で表現して演算をする時に使用する H フラグなどがあります。
これらのフラグに対して、フラグが立っていたり降りていたりする場合に分岐 する命令が Conditional Branch 命令です。 Conditional Branch 命令の一覧は AVR Instruction Set Manual p. 21 にあります。
一方、フラグを変化させるだけが目的で、結果を残さない引き算命令 CP, CPC, CPI があります。 また、レジスタが0やマイナスであることを検査する TST 命令もあります(但 し、こ れは実際は AND Rd,Rd を TST Rd と呼んでいるだけ)。 これらを組み合わせて、特定の論理式が成立した時に特定の処理を行うという、 高級言語で実装されている IF 文と同等の機能を実現します。
この他、条件分岐として使用できる命令として、スキップ命令があります。こ れは、特定の条件が成立する時に次の命令を飛ばす命令です。 スキップ命令は条件判断と分岐命令が一体になっていて1命令で完結します。 但し、このようなスキップ命令で「飛ばされる次の命令」のサイズは1命令で す。 AVR は RISC ですが、命令は1ワードまたは2ワードの可変長です。それにも関 わらず、次の命令を解釈して1命令スキップするようになっています。 スキップ命令には次のものがあります。
前述したように、条件分岐命令は、プログラムを連続して実行せずに、指定し た場所に制御を移します。 この他に、「無条件分岐」とも呼ばれる、単に何の条件にも関わらずに指定した場 所に制御を移すジャンプ命令があります。 さらに、サブルーチンを呼び出すコール命令と、呼び出された後で、呼び出し たコール命令の次の番地に戻るリターンがあります。 但し、条件分岐を含め、これらのすべてが相対ジャンプと呼ばれ、オペランド がプログラムカウンタに足される方式のジャンプ命令です。したがって、ジャンプで きる先が、現在のプログラム番地の近傍に限られます。 BRxx 命令は近傍として7bitのオペランドを -64から63 までの間の値として指 定できます。
一方、無条件分岐 rjmp とサブルーチンコール rcall は 12bit を -2048 か ら 2047 までの間の値を指定できます。 サブルーチンから戻るときは範囲を気にせずに ret のみで戻れます。
さらに、 ATmega328Pには 4Mword 内を行き来できる jmp 命令もあります。
この他に、 Z レジスタの値の番地に移動、または呼び出しを行う、 IJMP, ICALL 命令があります。
通常の、8bitレジスタ同士や、8bitレジスタと即値との演算の他に、 特殊かつ有用な算術命令として次のようなものがあります。
AVR Instruction SET マニュアルには AVR シリーズの全ての命令の解説が載っ ています。 しかし、これは全てのAVRマイコンで使用できるわけではありません。 それぞれのマイコンで使用可能な命令は、それぞれのデータシートに載ってい ます。 ATmega328P では pp. 431-434 に載っています。 ここでは AVR Instruction set マニュアルの命令のページの読み方を 最初の命令 ADC を例に説明します。
Adds two registers and the contents of the C Flag and places the result in the destination register Rd.
Syntax: | Operands: | Program Counter: | |
---|---|---|---|
(i) | ADC Rd,Rr | 0≤d≤31, 0≤r≤31 | PC ← PC + 1 |
0001 | 11rd | dddd | rrrr |
I | T | H | S | V | N | Z | C |
---|---|---|---|---|---|---|---|
- | - | ⇔ | ⇔ | ⇔ | ⇔ | ⇔ | ⇔ |
Rd3⋅Rr3+Rr3⋅R3+ R3⋅Rd3
Set if there was a carry from bit 3; cleard otherwise
N⊕V, For signed tests.
Rd7⋅Rr7⋅R7+ Rd7 ⋅ Rr7 ⋅R7
Set if two's complement overflow resulted from the operation; cleard otherwise.
R7
Set if MSB of the result is set; cleared otherwise.
R7⋅ R6⋅ R5⋅ R4⋅ R3⋅ R2⋅ R1⋅ R0
Set if the result is $00; cleard otherwise.
Rd7⋅Rr7+ Rr7⋅R7+ R7 ⋅Rd7
Set if there was carry from the MSB of the result; cleard otherwise.
R(Result) equals Rd after the operation.
; Add R1:R2 to R3:R2
add r2,r0 ; Add low byte
adc r3,r1 ; Add with carry high byte
1 (2 bytes)
1
Adds an immediate value (0 - 63) to a register pair and places the result in the register pair. This instruction operates on the upper four register pairs, and is well suited for operations on the pointer registers.
This instruction is not available in all devices. Refer to the device specific instruction set summary.
Syntax: | Operands: | Program Counter: | |
---|---|---|---|
(i) | ADIW Rd+1:Rd,K | d ∈ {24, 26, 28, 30}, 0≤K≤63 | PC ← PC + 1 |
1001 | 0110 | KKdd | KKKK |
I | T | H | S | V | N | Z | C |
---|---|---|---|---|---|---|---|
- | - | - | ⇔ | ⇔ | ⇔ | ⇔ | ⇔ |
N⊕V, For signed tests.
Rdh7 ⋅R15
Set if two's complement overflow resulted from the operation; cleard otherwise.
R15
Set if MSB of the result is set; cleared otherwise.
R15⋅ R14⋅ R13⋅ R12⋅ R11⋅ R10⋅ R9⋅ R8⋅ R7⋅ R6⋅ R5⋅ R4⋅ R3⋅ R2⋅ R1⋅ R0
Set if the result is $0000; cleard otherwise.
R15 ⋅Rdh7
Set if there was carry from the MSB of the result; cleard otherwise.
R(Result) equals Rdh:Rdl after the operation (Rdh7-Rdh0 = R15-R8, Rdl7-Rdl0=R7-R0).
adiw r25:r24,1 ; Add 1 to r25:r24
adiw ZH:ZL,63 ; Add 63 to the Z-pointer(r31:r30)
1 (2 bytes)
2
Loads an 8 bit constant directly to register 16 to 31.
Syntax: | Operands: | Program Counter: | |
---|---|---|---|
(i) | LDI Rd,K | 16≤d≤31, 0≤K≤255 | PC ← PC + 1 |
1110 | KKKK | dddd | KKKK |
I | T | H | S | V | N | Z | C |
---|---|---|---|---|---|---|---|
- | - | - | - | - | - | - | - |
clr r31 ; Clear Z high byte
ldi r30,$F0 ; Set Z low byte to $F0
lpm ; Load constant from Program
; memory pointed to by Z
1 (2 bytes)
2
Relative jump to an address within PC - 2K +1 and PC + 2K (words). For AVR microcontrollers with Program memory not exceeding 4K words (8K bytes) this instruction can address the entire memory from every address location. See also JMP.
Syntax: | Operands: | Program Counter: | Stack: | |
---|---|---|---|---|
(i) | RJMP k | -2K≤k<2K | PC ← PC + k + 1 | Unchanged |
1100 | kkkk | kkkk | kkkk |
I | T | H | S | V | N | Z | C |
---|---|---|---|---|---|---|---|
- | - | - | - | - | - | - | - |
cpi r16,$42 ; Compare r16 to $42
brne error ; Branch if r16 <> $42
rjmp ok ; Unconditional branch
error: add r16,r17 ; Add r17 to r16
inc r16 ; Increment r16
ok: nop ; Destination for rjmp (do nothing)
1 (2 bytes)
2
Relative call to an address within PC - 2K + 1 and PC + 2K (words). The return address (the instruction after the RCALL) is stored onto the Stack. See also CALL. For AVR microcontrollers with Program memory not exceeding 4K words (8K bytes) this instruction can address the entire memory from every address location. The Stack Pointer uses a post-decrement scheme during RCALL.
Syntax: | Operands: | Program Counter: | Stack: | ||
---|---|---|---|---|---|
(i) | RCALL k | -2K≤k<2K | PC ← PC + k + 1 STACK ← PC + 1 | SP ← SP -2 | (2 bytes, 16 bits) |
(ii) | RCALL k | -2K≤k<2K | PC ← PC + k + 1 STACK ← PC + 1 | SP ← SP - 3 | (3 bytes, 22 bits) |
1101 | kkkk | kkkk | kkkk |
I | T | H | S | V | N | Z | C |
---|---|---|---|---|---|---|---|
- | - | - | - | - | - | - | - |
rcall routine ; Call subroutine
...
routine: push r14 ; Save r14 on the Stack
...
pop r14 ; Restore r14
ret ; Return from subroutine
1 (2 bytes)
3, devices with 16 bit PC
4, devices with 22 bit PC
2, devices with 16 bit PC
3, devices with 22 bit PC
4
Returns from subroutine. The return address is loaded from the STACK. The Stack Pointer uses a pre-increment scheme during RET.
Syntax: | Operands: | Program Counter: | Stach: | ||
---|---|---|---|---|---|
(i) | RET | None | See Operation | SP ← SP + 2 | (2 bytes, 16 bits) |
(ii) | RET | None | See Operation | SP ← SP + 3 | (3 bytes, 22 bits) |
1001 | 0101 | 0000 | 1000 |
I | T | H | S | V | N | Z | C |
---|---|---|---|---|---|---|---|
- | - | - | - | - | - | - | - |
call routine ; Call subroutine
...
routine: push r14 ; Save r14 on the Stack
...
pop r14 ; Restore r14
ret ; Return from subroutine
1 (2 bytes)
4 devices with 16-bit PC
5 devices with 22-bit PC
Conditional relative branch. Tests the Zero Flag (Z) and branches relatively to PC if Z is set. If the instruction is executed immediately after any of the instructions CP, CPI, SUB or SUBI, the branch will occur if and only if the unsigned or signed binary number represented in Rd was equal to the unsigned or signed binary number represented in Rr. This instruction branches relatively to PC in either direction (PC - 63 ≤ destination ≤ PC + 64). The parameter k is the offset from PC and is represented in two’s complement form. (Equivalent to instruction BRBS 1,k).
Syntax: | Operands: | Program Counter: | |
---|---|---|---|
(i) | BREQ k | -64 ≤ k ≤ +63 | PC ← PC + k + 1
PC ← PC + 1, if condition is false |
1111 | 00kk | kkkk | k001 |
I | T | H | S | V | N | Z | C |
---|---|---|---|---|---|---|---|
- | - | - | - | - | - | - | - |
cp r1,r0 ; Compare registers r1 and r0
breq equal ; Branch if registers equal
...
equal: nop ; Branch destination (do nothing)
1 (2 bytes)
1 if condition is false
2 if condition is true
直前の演算の結果が0になったら(Z フラグがセットされていたら)プログラム カウンタに k+1 を加えます。そうで無い時は、次の命令に制御を移します。 なお、 k は -64 から 63 までの値なので、近傍にしか移れません。
なお、このような分岐命令は各フラグごとにセットとクリアで二つずつ用意さ れています。 但し、「ゼロフラグセット」のような名前ではなく、「イコール」のようなフ ラグの意味や使用される状況を意味した言葉が使われていることもあります。
This instruction stores the contents of register Rr on the STACK. The Stack Pointer is post-decremented by 1 after the PUSH. This instruction is not available in all devices. Refer to the device specific instruction set summary.
Syntax: | Operands: | Program Counter: | Stack: | |
---|---|---|---|---|
(i) | PUSH Rr | 0 ≤ r ≤ 31 | PC ← PC + 1 | SP ← SP - 1 |
1001 | 001d | dddd | 1111 |
I | T | H | S | V | N | Z | C |
---|---|---|---|---|---|---|---|
- | - | - | - | - | - | - | - |
call routine ; Call subroutine
...
routine: push r14 ; Save r14 on the Stack
push r13 ; Save r13 on the Stack
...
pop r13 ; Restore r13
pop r14 ; Restore r14
ret ; Return from subroutine
1 (2 bytes)
2
1
レジスタの内容をスタックに入れます。 逆に、POP 命令でスタックから取り出せます。 レジスタの内容を、領域をわざわざ定義せずに退避させたいときなどに使用し ます。 特に、割り込み時など、レジスタの内容を一切破壊できないときに有用です。
Loads one byte pointed to by the Z-register into the destination register Rd. This instruction features a 100% space effective constant initialization or constant data fetch. The Program memory is organized in 16-bit words while the Z-pointer is a byte address. Thus, the least significant bit of the Z-pointer selects either low byte (ZLSB = 0) or high byte (ZLSB = 1). This instruction can address the first 64K bytes (32K words) of Program memory. The Z-pointer Register can either be left unchanged by the operation, or it can be incremented. The incrementation does not apply to the RAMPZ Register.
Devices with Self-Programming capability can use the LPM instruction to read the Fuse and Lock bit values. Refer to the device documentation for a detailed description.
The LPM instruction is not available in all devices. Refer to the device specific instruction set summary.
The result of these combinations is undefined:
LPM r30, Z+
LPM r31, Z+
Operation: | Comment: | |
---|---|---|
(i) | R0 ← (Z) | Z: Unchanged, R0 implied destination register |
(ii) | Rd ← (Z) | Z: Unchanged |
(iii) | Rd ← (Z) Z ← Z + 1 | Z: Post incremented |
Syntax: | Operands: | Program Counter: | |
---|---|---|---|
(i) | LPM | None, R0 implied | PC ← PC + 1 |
(ii) | LPM Rd, Z | 0 ≤ d ≤ 31 | PC ← PC + 1 |
(iii) | LPM Rd, Z+ | 0 ≤ d ≤ 31 | PC ← PC + 1 |
(i) | 1001 | 0101 | 1100 | 1000 |
---|---|---|---|---|
(ii) | 1001 | 000d | dddd | 0100 |
(iii) | 1001 | 000d | dddd | 0101 |
I | T | H | S | V | N | Z | C |
---|---|---|---|---|---|---|---|
- | - | - | - | - | - | - | - |
ldi ZH, high(Table_1<<1) ; Initialize Z-pointer
ldi ZL, low(Table_1<<1)
lpm r16, Z ; Load constant from Program
; Memory pointed to by Z (r31:r30)
...
Table_1:
.dw 0x5876 ; 0x76 is addresses when ZLSB = 0
; 0x58 is addresses when ZLSB = 1
1 (2 bytes)
3
プログラム領域をレジスタに読み込みます。 プログラム領域は 16bit なので、上位 8bit と下位 8bit をアドレスで区別 するため、(実アドレス)*2 で下位8bit,(実アドレス)*2+1 で上位8bitのデー タを取得できます。 但し、直接アドレスを指定する仕組みは無く、必ず Z=r31:r30 レジスタを使用する必 要があります。 通常、この命令で呼び出すデータは dw や db ディレクティヴで定義します。
Shifts all bits in Rd one place to the left. The C Flag is shifted into bit 0 of Rd. Bit 7 is shifted into the C Flag. This operation, combined with LSL, effectively multiplies multi-byte signed and unsigned values by two.
Syntax: | Operands: | Program Counter: | |
---|---|---|---|
(i) | ROL Rd | 0 ≤ d ≤ 31 | PC ← PC + 1 |
(ii) | LPM Rd, Z | 0 ≤ d ≤ 31 | PC ← PC + 1 |
(iii) | LPM Rd, Z+ | 0 ≤ d ≤ 31 | PC ← PC + 1 |
0001 | 11dd | dddd | dddd |
I | T | H | S | V | N | Z | C |
---|---|---|---|---|---|---|---|
- | - | ⇔ | ⇔ | ⇔ | ⇔ | ⇔ | ⇔ |
Rd3
N⊕V, For signed tests.
N⊕ C (For N and C after the shift)
R7 Set if MSB of the result is set; cleared otherwise.
Rd7 Set if, before the shift, the MSB of Rd was set; cleared otherwise.
R (Result) equals Rd after the operation.
lsl r18 ; Multiply r19:r18 by two
rol r19 ; r19:r18 is a signed or unsigned two-byte integer
brcs oneenc ; Branch if carry set
...
oneenc: nop ; Branch destination (do nothing)
1 (2 bytes)
1
Rd と C フラグを合わせて、左にビットを回転させます。 Rd の値は2倍され、最下位ビットに C フラグの内容が入れられます。 オーバーフローしたかどうかが C フラグに入ります。
なお、この命令は ADC Rd,Rd と同じです。
Shifts all bits in Rd one place to the right. Bit 7 is held constant. Bit 0 is loaded into the C Flag of the SREG. This operation effectively divides a signed value by two without changing its sign. The Carry Flag can be used to round the result.
Syntax: | Operands: | Program Counter: | |
---|---|---|---|
(i) | ASR Rd | 0 ≤ d ≤ 31 | PC ← PC + 1 |
1001 | 010d | dddd | 0101 |
I | T | H | S | V | N | Z | C |
---|---|---|---|---|---|---|---|
- | - | ⇔ | ⇔ | ⇔ | ⇔ | ⇔ | ⇔ |
Rd3
N⊕V, For signed tests.
N⊕ C (For N and C after the shift)
R7 Set if MSB of the result is set; cleared otherwise.
Rd0 Set if, before the shift, the LSB of Rd was set; cleared otherwise.
R (Result) equals Rd after the operation.
ldi r16,$10 ; Load decimal 16 into r16
asr r16 ; r16=r16 / 2
ldi r17,$FC ; Load -4 in r17
asr r17 ; r17=r17/2
1 (2 bytes)
1
符号付きの数を 1/2 するために、最上位の符号付きビットはそのままにし、 右シフトをします。 但し、上位に埋め込まれる数は符号ビットと同じになります。
さて、この節では実際にプログラムを作成する手順を説明します。
アセンブルしたプログラムはシミュレータで動作を観察できます。 「プロジェクト→ プロジェクト名 のプロパティ」を選ぶと 設定画面が出ます。 Tool の設定画面で、 Simulator を選ぶと ソフトウェアシミュレータ を使えるようになります。 シミュレータはプログラムを一命令ずつ動かしたり、指定した所まで動かして 止めたり出来ます。
シミュレータはテープレコーダのボタンのようなボタンで操作をします。 なお、 右側の画面でマイコンの状態を見ることができます。
プログラムリストの行をダブルクリックすると左側に赤い丸が付きます。 これを ブレークポイントと言います。 矢印一つの Run ボタンを押すと、ブレークポイントで停止します。 これにより、プログラムの途中のレジスタの値などをチェックできます。
その他、一命令だけ実行する Step Into, Call 命令に関しては戻って来るま で一括で実行する Step over, サブルーチン中から抜け出すまで実行する Step out があります。
なお、実行中にI/Oのボタンを押すと、その値に変化させることができます。
以下のプログラムを program.bat というテキストファイルに収めます。
SET dude="C:\Program Files (x86)\Arduino\hardware\tools\avr\bin\avrdude.exe"
set conf="C:\Program Files (x86)\Arduino\hardware\tools\avr\etc\avrdude.conf"
rem %dude% -C %conf% -p atmega328p -cstk500v1 -P %1 -b 57600 -D -U flash:w:%2%:i
%dude% -C %conf% -p m328p -c arduino -P %1 -D -U flash:w:"%2":i
サンプルプログラムflashuno.asm の各ディレクティヴがどのような意味か、全て調べなさい。
サンプルプログラムflashuno.asm を機能毎に区分し、各機能を説明しなさい。
サンプルプログラムswitchtraining.asm を動作させなさい。
また、プログラムを読み、どのような動きをするのか、説明しなさい。
サンプルプログラムswitchtraining.asm の各ディレクティヴがどのような意味か、全て調べなさい。
サンプルプログラムswitchtraining.asm を機能毎に区分し、各機能を説明しなさい。
; switchtraining.asm
;
; Created: 2018/09/17 4:39:20
; Author : sakamoto
;
.cseg
.org 0x0000
rjmp reset
.org INT_VECTORS_SIZE
reset:
ldi r16,high(RAMEND)
out SPH,r16
ldi r16,low(RAMEND)
out SPL,r16
.equ onb = 0b00111111
.equ ond = 0b11000000
.equ pullup = 0b00110000
.equ onc = 0b00001111
ldi r16,onb
out ddrb, r16
ldi r16,ond
out ddrd, r16
ldi r16,pullup
out portd, r16
ldi r16,onc
out ddrc, r16
.def now = r17
.def prev = r18
.def cond = r19
clr prev
ser cond
.macro clrport
in r16,@0
andi r16,~@1
out @0,r16
.endmacro
.macro setport
in r16,@0
ori r16,@1
out @0,r16
.endmacro
main:
in now,pind
sbrc prev,pind5
rjmp pressed
sbrs now,pind5
rjmp notpressed
nowpressed:
ldi r16,1<<pind4
eor cond,r16
notpressed:
pressed:
andi prev,~(1<<pind5)
or prev,now
eor now, cond
sbrc now,pind4
rjmp swon
swoff:
clrport portb,onb
clrport portd,ond
clrport portc,onc
rjmp main
swon:
setport portb,onb
setport portd,ond
setport portc,onc
rjmp main
.exit