65C816とはアメリカ・ウェスタンデザインセンター(WDC)社の16ビットCPUのことであり、このページではその命令系を扱います。65C816はスーパーファミコンで使われているCPUなので、この命令系を用いればROMを1から作ることも出来ます。
ここでは機械語を列挙しているわけではなく、そのアセンブリ版、65C816アセンブリについてまとめました。実際にはTRASMやxkasなどでアセンブルしてROMに書き込みますが、それらの応用的な話はここでは扱いません。
一般的なCPUなどのハードウェアの知識を前提として書いていますので、分からない方は先に調べておくことをお勧めします。PICとか弄ったことある人ならきっとすんなり理解できると思います。前から順番に読むとスムーズに進められると思います。
目次
表記
コメント
; これはコメントです
「;」から行末まではコメント扱いになります。後で自分が見直したときに、どんな処理をしているか分かるように書いておくと便利です。もちろん、アセンブラによって仕様が異なりますので使うときは注意して下さい。
命令
アセンブリでは命令を1行に1つ書くのが基本です。
命令1 命令に関する値 命令2 命令に関する値 命令3 命令4 ...
上のように述語「命令」と主語「命令に関する値」で成り立つものと述語「命令」のみで成り立つものの2通りあります。いずれにしても1つの命令で1行使います。
レジスタ
アドレス$0DBFの値を変えると取得したコインの枚数が変化します。$0DBEの値を変えるとマリオの残機が変化します。このようにメモリ(RAM)を変化させることによって現在のシステムを維持しながら色々な処理をすることが出来ます。勿論、本格的な改造も出来るわけですが、弄っていることはメモリの値を変化させているだけです。
アドレス
65C816ではROM, RAM上のアドレスを$XX:YYYYと表現します。ここでX, Yなどは1文字8bitであり、16進数です。XXの部分はバンクと呼ばれています。バンクは00 ~ FFまであります。
ROMとは書き込みできない領域のことでバンク78~7Fを除いたバンクの各$XX:8000~$XX:FFFF(以下XX:は省略)からなる領域のことです。
RAMは逆に自由に書き込みの出来る領域で、バンク$00~$3Fの各アドレス$0000 ~ $1FFFとバンク$7E, $7Fの全てです。ところがバンク$00~$3Fの各アドレス$0000 ~ $1FFFとバンク$7Eの$0000 ~ $1FFFは共有しているため、実質使えるのは$7E, $7Fだけです。
レジスタ
レジスタとはRAMのように自由に書き込みの出来る領域です。こちらは頻繁に使うので高速に書き換えできるような構造になっています。全部で9つありますが、通常使うのはA, X, Yくらいです。
- Aレジスタ
- 演算に使ったりします。8/16bitの切り替えが可能です。
- X, Yレジスタ
- 基準値からの増減を保持するレジスタです。これも8/16bitの切り替えが出来ます。(*1)
- Pレジスタ
-
ステータスを保持しているレジスタです。よく使われるネガティブフラグ、オーバーフローフラグゼロフラグ、キャリーフラグは覚えておくと便利です。
7bit Aの最上位ビットが1のとき負として扱い1がセットされます。0のときは0です。ネガティブフラグ 6bit 演算結果のオーバーフローフラグ(Aが+$7Fを超えたり、-$80より小さくなったりした場合セットされます。) 5bit Aレジスタのサイズを1: 8bit / 0 : 16bitで指定 4bit X, Yレジスタのサイズを1: 8bit / 0 : 16bitで指定 3bit 1 : 2進化10進数モード / 0 : 通常(*2) 2bit 1 : 割り込み不許可 / 0 : 許可 1bit 演算結果が0のとき1, 他は0。ゼロフラグ 0bit 符号なしと見なしたキャリーフラグ (*3) - Dレジスタ
- $XX表記でのオフセットを16bitで指定します。つまりバンクは$00でD + XXのアドレスを参照するようになります。リセットすると0になります。
- DBレジスタ
- $YYYY表記でのバンクを指すレジスタです。リセットすると0になります。
- PBレジスタ
- プログラムバンク。次に実行される24bitアドレスの上位8bitです。つまりバンクです。
- PCレジスタ
- 次に実行される24ibitアドレスの内、下位16bitを指します。プログラムカウンタ。
- Sレジスタ
- スタックで次に書き込むアドレス16bitが格納されています。バンクは$00です。
エミュレーションモードとネイティブモード
実はスーファミはファミコンの上位互換であり、65C816は6502というCPUの上位互換です。65C816には6502をエミュレートするエミュレーションモードと、通常のネイティブモードが用意されており、実はPレジスタにはこのエミュレーションフラグが用意されています。
XCE
とするとキャリーフラグとエミュレーションフラグを入れ替えます。これによってエミュレーションフラグを変更することが出来ます。
フラグ操作
- CLC
- キャリーフラグのクリア
- SEC
- キャリーフラグのセット
- CLD
- デシマルフラグ(*4)のクリアS
- SED
- デシマルフラグのセット
- CLI
- インタラプトフラグ(*5)のセット
- SEI
- インタラプトフラグのクリア
- CLV
- オーバーフローフラグのクリア
- SEP #$XX
- Pレジスタで、指定した値で1の部分のbitを1に(セット)します。
- REP #$XX
- Pレジスタで、指定した値で1の部分のbitを0に(クリア)します。
例えばキャリーフラグをクリアするには、
CLC
でもいいわけですが、Pレジスタの0bit目をクリアすればいいので%00000001(2進数) = $01(16進数)を指定すれば同じ動作が出来ます。いっぺんにフラグを操作することも出来ます。
REP #$01
- REP #$20
- Aレジスタのみ16bitモードに切り替え
- REP #$30
- A, X, Yレジスタを16bitモードに切り替え
アドレッシングモード
ROM, RAMのアドレスを指定する方法がいくつかあります。ところが、全ての命令で使えるわけではなく、命令によって表記によるアドレッシングモードが変わってしまいます。(*6)以下の表記は大体有効ですが命令によっては有効でないかも知れませんのでお気を付け下さい。
- #$XX
- XX自体を指します。(*7)
- $XXYYYY
- そのアドレスに入っている値を指します。また、$YYYYと表記すると上位1バイトはDBレジスタの値となります。
- ($XXYYYY)(*8)
- と表記することで、そのアドレスに入っている値をアドレス2と見なし、そのアドレス2に入っている値を指します。
- $XXYYYY,x
- $XXYYYY + xに入っている値を指します。バンクを指定する場合、yはありません。また、xは負の数も扱えます。
- $YYYY,x
- または$YYYY,yと表記することで、$YYYY + x(またはy)に入っている値を指します。
- $XX(*9)
- (Dレジスタの値 + XX)に入っている値を指します。
- $XX(*10)
- (現在の実行されているアドレス + XX)に入っている値を指します。
他にもたくさんありますがよく利用するのは上記だけでしょう。
レジスタに書き込む
Aレジスタに値を書き込むには、
LDA 値
とします。値には#$XXや$YYYYなどを指定します。同様にX, Yレジスタにも
LDX 値 LDY 値
として書き込みます。例えば20(16進数)をAレジスタに書き込みたいときは、
LDA #$20
とします。
LDA | #$XX $XX(Dレジスタ+XXを参照) $XX,x $YYYY $YYYY,x $YYYY,y $XXYYYY $XXYYYY,x |
LDX | #$XX $XX,y $YYYY $YYYY,y |
LDY | #$XX $XX,x $YYYY $YYYY,x |
レジスタから読み込む
レジスタに格納されているデータを他のメモリにコピーするには、
STA アドレス STX アドレス STY アドレス
とします。例えば、
LDA #$1000 STA $0010
とすれば$0010に1000が入ることになります。また、
STZ アドレス
とすることでアドレスに入っている値を0にすることができます。
STA | $XX$XX,x $YYYY $YYYY,x $YYYY,y $XXYYYY $XXYYYY,x |
STX | $XX $XX,y $YYYY |
STY | $XX $XX,x $YYYY |
STZ | $XX $XX,x $YYYY $YYYY,x |
レジスタ間で値をコピーする
TXA
でXレジスタの値をAレジスタにコピーします。同様に
TYA TAX TAY TXY TYX
はYからAへ、AからXへ,AからYへ、XからYへ、YからXへコピーします。
他にも
- TDC
- Dレジスタの値をAレジスタへコピー
- TCD
- Aレジスタの値をDレジスタへコピー
- TSC
- Sレジスタの値をAレジスタへコピー
- TCS
- Aレジスタの値をSレジスタへコピー
- TXS
- Xレジスタの値をSレジスタへコピー
- TSX
- Sレジスタの値をXレジスタへコピー などがあります。
Aレジスタの上位下位交換
XBA
とするとAレジスタの上位8bitと下位8bitが交換されます。
8bitモード
8bitモードではAレジスタは下位8bitしか使いません。しかし、LDA #$00などとクリアしたつもりでも上位ビットは残っているのでTCDなどの指令を行うと上位ビットまでコピーされて不具合が起きる場合があります。従ってクリアするにはXBAで交換を行ってから再度#$00にするか、($0000であるはずの)TDCでするかなどが考えられます。
LDA #$00 XBA LDA #$00
TDC
スタック
PUSHとPULL
一般的なデータ管理と同じで、データを積んでいくイメージです。スタック領域とはその積んでいくデータのある領域です。$7E010Bから$7E01FFまでの245バイト分確保されています。
データA, B, CをPUSHしていくとスタックはA → AB → ABCとなり、PULLしていくとスタックはABC → AB → Aとなります。即ち、上から積んでいき(PUSH)、上から取っていく(PULL)処理をします。
データは後尾の$7E01FFからPUSHされているようで、順に下がっていきます。このとき、PUSHするとSレジスタが減少し、PULLすると増加します。Sレジスタは次にPUSHするアドレスを指します。TCSなどでSレジスタを変更することが出来ますが、しないほうが安全です。
PULL, PUSH命令
PHA PHX PHY PHP
はそれぞれA, X, Y, Pレジスタの値をPUSHします。
PLA PLX PLY PLP
はそれぞれA, X, Y, PレジスタにPULLした値を書き込みます。
他のレジスタに関しても操作できます :
- PHB
- DBレジスタの値をPUSH
- PHD
- Dレジスタの値をPUSH
- PHK
- PBレジスタの値をPUSH
- PEA $XXXX
- 2バイトのデータXXXXをPUSH(*11)
- PER $XXYYYY
- 指定アドレスの後のPCレジスタの値をPUSH
- PEI ($XX)
- $XXの指すアドレスに入っている値をPUSH
- PLB
- DBレジスタにPULL
- PLD
- DレジスタにPULL
PUSHしたらスタックにデータが残ってしまってフリーズの元なので必ず自分で積んだデータは最終的にPULLして空にしましょう。というのも、後に述べるルーチン移動の際にスタックにその情報が蓄積されるのでルーチン終了の際にスタックで取り出される値が異なると誤動作の元となるからです。
加算と減算
インクリメントとデクリメント
INC DEC
これらはAレジスタの値を+1, -1します。同様に
INX INY DEX DEY
があり、X, Yレジスタの値を+1, -1します。
8bitモードでは$FFのときINCすると$00に戻ります。16bitモードのでは$FFFFのときINCすると$0000に戻ります。
INC アドレス DEC アドレス
とすることでアドレスにある値を+1, -1します。例えば、
LDA #$05 STA $0101 LDX #$02 INC $0101,x
とすると、
- Aレジスタを05にセット
- $0101へAレジスタの値05をセット
- Xレジスタを02にセット
- $(0101 + 02) = $0103に格納されてある値を1増加させる という処理を書くことが出来ます。
INC | $XX $XX,x $YYYY $YYYY,x |
DEC | $XX $XX,x $YYYY $YYYY,x |
Aレジスタの増減
ADC 値 SBC 値
とすると、Aレジスタの値に加算、Aレジスタの値から減算できます。値でなくアドレスを指定した場合は、そのアドレスに入っている値で加算、減算します。
Pレジスタの7bit目をキャリーフラグといったりしますが、これが1のときはADCを行っても1増加するだけです。逆に、これが0のときはSBCで減算を行ったら更に1減算されます。つまり、50引いたつもりが51引くことになるので注意が必要です。1のときはこの限りではありません。
従って、指定した数だけしっかり加減算したい場合、キャリーフラグをセット、クリアしなければなりません。
- 加算の場合
-
CLC ; クリア ADC #$50
- 減算の場合
-
SEC ; セット SBC #$50
ADC | #$XX $XX $XX,x $YYYY $YYYY,x $YYYY,y $XXYYYY $XXYYYY,x |
SBC | #$XX $X$XX,x $YYYY $YYYY,x $YYYY,y $XXYYYY $XXYYYY,x |
分岐
比較
CMP 値 CPX 値 CPY 値
でA, X, Yレジスタと値とを比較します。比較と行っても中では引き算が実行されているだけで、(レジスタの値) - (指定した値)が実行されます。この計算によってオーバーフローを起こすことはありませんが、他のフラグは健在です。
状態 | ゼロフラグ | キャリーフラグ |
値 > A | 0 | 0 |
値 = A | 1 | 1 |
値 < A | 0 | 1 |
となります。等しいときでもキャリーフラグも1になることに注意が必要です。(*12)
CMP | #$XX $XX $XX,x $YYYY $YYYY,x $YYYY,y $XXYYYY $XXYYYY,x |
CPX | #$XX $XX $YYYY |
CPY | #$XX $XX $YYYY |
ラベル
適当な地点にラベルを付けることで、その地点のアドレスを参照できます。例えば、
LABEL01: ~LABEL01の処理~ LABEL02: ~LABEL02の処理~
とすることで、「LABEL01」と他の場所で書くとその場所からの相対位置として$XXに変換されます。(*13)下の分岐やジャンプと一緒に多用されます。
なお、ラベルについては65C816の仕様ではありませんので各アセンブラに応じてかき分けて下さい。尤も、殆どのアセンブラでは[ラベル名]:と書くのが一般的ですから気にする必要はないかも知れませんが;
分岐
BEQ $XX
と書くと直前に行った演算などによってフラグが変化したことを利用して相対アドレス$XXへジャンプします。つまり、この命令が終わった後前後どれだけジャンプするかを指定します。なお。指定した場所は -$F9 ~ +$80以内になければいけません。というのも、上のラベルは$XX形式なので1バイトしか情報がないからです。
- BEQ
- ゼロフラグが1のとき、指定した場所までジャンプ ( == )
- BNE
- ゼロフラグが0のとき、指定した場所までジャンプ ( != )
- BCS
- キャリーフラグが1のとき、指定した場所までジャンプ (比較対象 < レジスタ)
- BCC
- キャリーフラグが0のとき、指定した場所までジャンプ (比較対象 > レジスタ)
- BMI
- ネガティブフラグ(*14)が1のとき、指定した場所までジャンプ
- BPL
- ネガティブフラグが0のとき、指定した場所までジャンプ
- BVS
- オーバーフローフラグが1のとき、指定した場所までジャンプ
- BVC
- オーバーフローフラグが0のとき、指定した場所までジャンプ
基本的には演算してフラグが変化するものならなんでも比較できますが、CMPという便利な命令があるのでそれとセットで使われることが多いです。
CMP #$20 BEQ TEST
とするとAレジスタが#$20と等しければTESTラベルへ移動できます。
各命令 | $XX(PCレジスタの値 + XXを参照) |
ジャンプ
JMP $YYYY JML $XXYYYY
特定のアドレスまでジャンプします。
JMP ($YYYY) JML ($XXYYYY)
$YYYYや$XXYYYYYに入っている値をアドレスとみて、そのアドレスにジャンプします。
JMP ($XX,x) JML ($XX,x)
バンクがDBレジスタ、下位16bitアドレスが$XX + Xレジスタにジャンプします。
サブルーチン
JSR $YYYY JSL $XXYYYY
で指定したアドレスへジャンプします。ここまではJMP, JMLと変わらないのですが、その後RTS, RTLで元の場所に戻ってくることが出来る点がJMP, JMLと異なります。
処理1 JSR LABEL01 処理3 JMP END LABEL01: 処理2 RTS END: 処理4
JSRでジャンプした場合RTSで戻り、JSLでジャンプした場合RTLで戻ります。例のようにラベルも用いることが出来ます。なお、JSR, JSLでルーチンに入ったら、必ずJSRの場合はRTSで、JSLの場合はRTLでリターンしないとスタックにたまってしまいバグの元です。
論理演算
論理演算
AND 値 ORA 値 EOR 値
それぞれAレジスタとの論理積、論理和、排他的論理和を計算し、Aレジスタに書き込みます。
BIT アドレス
とするとANDの計算によるフラグの変化だけ起こります。つまりAレジスタの値は変化しません。
TRB アドレス
とするとAレジスタの値を反転させ、その結果と指定したアドレスに入っている値との論理積を取り、そのアドレスに書き込みます。
TSB アドレス
とするとAレジスタの値を反転させ、その結果と指定したアドレスに入っている値との論理和を取り、そのアドレスに書き込みます。
AND | #$XX $XX $XX,x $YYYY $YYYY,x $YYYY,y $XXYYYY $XXYYYY,x |
ORA | #$XX $XX $XX,x $YYYY $YYYY,x $YYYY,y $XXYYYY $XXYYYY,x |
BIT | $XX $YYYY |
TRB | $XX $YYYY |
TSB | $XX $YYYY |
ビットシフト
ASL アドレス LSR アドレス
指定したアドレスに入っている値を左シフト、右シフトします。一般的なビットシフト演算と同じです。左シフトASLでは最上位ビットが1のときに行うとキャリーフラグが立ちます。右シフトLSRでは最下位ビットが1のとき行うとキャリーフラグが立ちます。
ROL アドレス ROR アドレス
は論理左シフト、論理右シフトです。キャリーフラグを用いて循環するように演算されます。
アドレスを省略するとAレジスタの値をシフト、論理シフトします。
ASL | $XX $XX,x $YYYY $YYYY,x |
LSR | $XX $XX,x $YYYY $YYYY,x |
ROL | $XX $XX,x $YYYY $YYYY,x |
ROR | $XX $XX,x $YYYY $YYYY,x |
割り込み処理
- WAI
- 割り込みがあるまで待機します。
- BLK
- 割り込みを起こします。
- RTI
- 割り込みから復帰します。