cocoaの隠れ処

更新ページ

取得中です。

人気ページ

TOTAL : -

65C816


※上記の広告は60日以上更新のないWIKIに表示されています。更新することで広告が下部へ移動します。

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

とすると、

  1. Aレジスタを05にセット
  2. $0101へAレジスタの値05をセット
  3. Xレジスタを02にセット
  4. $(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
割り込みから復帰します。