NES(Famicom)のMPUである6502(のカスタムチップ)の解説とニーモニック表です。
NESに使用されているMPUはリコー製の6502プロセッサのカスタムチップ(RP2A03)です。 動作クロックは1.7897725MHzでした。(このクロックの数字は後で音源を操作するのに必要になります) MPUとはいわゆるCPUのことで、6502では慣習的にCPUではなくMPUと表記しています。 このカスタムチップではいくつかの機能の削除・追加がなされています。 具体的にはBCDモードが削除されI/Oがメモリにマップされる形に変更されています。 また矩形波2ch、三角波1ch、ノイズ1chとΔPCM1chからなるサウンド機能を内蔵しています。 その他NESではハードウェアの構成に特殊な部分が多数あります。これについては改めて紹介します。
6502などモトローラ系列のMPUのニーモニックでは16進数を表記する場合に数値の頭に"$"を付けます。16進数DEは$DEとなります。 6502はモトローラの系列ですが、メモリ内の16bit数値をリトルエンディアンで扱う珍しいMPUです。 (他のモトローラ系MPUでは基本的にビッグエンディアンで扱います)
このページではアセンブリ・ニーモニックをNESASMでの表記で記述しています。 NESASMでは2進数を"%"を付けて記述できるように拡張されています。
次の2つのコードは同一の内容です。
lda #$CD
lda #%11001101
表1は6502のレジスタをまとめたものです。PSRレジスタはIntel系のCPUでいうところのフラグレジスタに相当するもので、 演算結果により変化するレジスタです。その他、MPUの動作モードの設定も行います。 特徴的なのは6502はPC以外のレジスタすべてが8bitであることに加え、 演算に使用するアキュムレータレジスタがAレジスタ1つしかありません。 これに関してはページの説明と併せて後ほど説明します。
表2にはPSRレジスタの各ビットの名称と動作についてまとめてあります。 ネガティブフラグは演算結果の正負を現しますが要するにAレジスタの最上位ビットになります。 オーバーフローフラグは演算結果がオーバーフローした際にセットされます。 ブレイクフラグはBRK割り込み発生時セットされます。 6502ではIRQとBRKの割り込みが共通のアドレスとなっているため、 このフラグを見て判別します。 デシマルモードフラグはMPUをBCDモードに切り替える際セットしますが、 NESでは機能が削除されているため、セットされているとMPUが正常に動作しません。 電源投入時のフラグの状態が不定のためプログラムの最初にこのビットのクリアを行うのが定石となっています。 インタラプトフラグはIRQ/BRK割り込みを禁止する際セットします。 ゼロフラグは演算結果が0の時にセットされます。 キャリーフラグは桁上がり(キャリー)発生時にビットがセットされるのはごく普通の動作ですが、ボロー(桁下がり)発生時には逆にクリアされるという特殊な仕様になっていることに気をつけてください。 また、加算命令・減算命令ではこのキャリーフラグも併せて加算・減算される仕様になっています。
PC(プログラムカウンタ) | 16bit |
---|---|
A(アキュムレータレジスタ) | 8bit |
X(インデックスレジスタX) | 8bit |
Y(インデックスレジスタY) | 8bit |
S(スタックポインタレジスタ) | 8bit |
PSR(プロセッサステータスレジスタ) | 8bit |
PSRレジスタの各ビットの配置 | NV0BDIZC |
---|---|
N:ネガティブフラグ | 正負符号。Aレジスタの最上位ビット。 |
V:オーバーフローフラグ | 演算結果が127を超える、または-128を下回るとセットされる。 |
B:ブレイクフラグ | BRK割り込み発生時にセットされる。割り込みがBRKかIRQかを識別するために使用する。 |
D:デシマルモードフラグ | セットするとBCDモードで動作する。NESでは機能が削除されているのでセットしてはならない。 |
I:インタラプトフラグ | セットするとIRQ/BRK割り込みが禁止される。 |
Z:ゼロフラグ | 演算結果が0の時にセットされる。 |
C:キャリーフラグ | キャリー発生時およびボローが発生しなかった時にセットされる。 |
6502で扱うことができるメモリ空間は$0000から$FFFFまでの64KByteです。しかしながら、前述のようにレジスタが8bitのため これらのメモリをレジスタにより直接指定することができません。スタックについても同様の理由により、 固定された256Byteのみがスタックとして利用可能となっています。6502ではこのように8bitのレジスタで処理する ためにメモリを256Byteごとに区切ってページという概念を用いて表現します。
ページとは8bitのレジスタでメモリを操作するためにメモリを256Byte単位で区切ったものです。スタックは$0100から$01FFまでの1ページ256ByteをSレジスタを使ってアドレスの下位8Byteを管理します。スタックの動作はスタックを積む時Sレジスタをデクリメントしスタックから取り出す時Sレジスタをインクリメントします。そのためプログラム開始時にSレジスタを$FFに初期化するのが一般的です。
$0000から$00FFまでの256Byteは特に0ページと呼ばれ特別な意味を持ちます。 0ページへのアクセスは通常より命令の長さを短くすることができ通常より少ないクロックでアクセスできるため、 他のメモリより高速にアクセスすることができるようになっています。 また連続した2Byteをメモリ空間のポインタとして使う間接アドレッシング機能があり これにより8bitのレジスタで全メモリ空間をアクセスでき、 また0ページへの高速アクセスを使うことで8bitプロセッサでありながら高い性能を発揮することができます。
6502には豊富なアドレッシングモードが用意されています。6502でのプログラミングにはこのアドレッシングモードに関する理解が不可欠です。ここではそのアドレッシングモードについて紹介します。
lda #$80
Immediate addressing modeはオペランドに指定した値をそのまま指定します。
lda
はメモリの内容をAレジスタにロードする命令です。この場合ですとAレジスタに$80の値がロードされます。
Immediate addressing modeを使用する場合は値に"#"を付けて記述します。
このアドレッシングモードで記述された命令はオペコードに展開すると2Byteになります。
lda <$D0
オペランドの頭に"<"を付加するとZero-Page addressing modeになります。 オペランドには8bitの0ページアドレスを指定し、指定した0ページのアドレスの値を読むように指定します。 この場合Aレジスタに$00D0の内容がロードされます。 Zero-Page addressing modeでは次のAbsolute addressing modeよりもオペコードに展開した際の長さが短く 少ないクロック数で処理が行える利点があります。
展開されるオペコードは2Byteになります。
lda $8020
Absolute addressing modeでは16bitでアドレスを指定します。この例では$8020の内容がAレジスタにロードされます。
展開されるオペコードは3Byteになります。2,3Byte目がアドレスになりますが、リトルエンディアンになっています。
inx
Implied addressing modeは命令自体に処理対象が含まれているものです。
このアドレッシングモードの場合オペランドを指定しません。
inx
はインデックスレジスタXをインクリメントします。
展開されるオペコードは1Byteになります。
lsr a
Accumulator addressing modeはAレジスタのみを対象とする命令のアドレッシングモードです。
lsr
はAレジスタのみを対象として論理右シフトを行う命令です。
展開されるオペコードは1Byteになります。
lda $C080, x
sta $6000, y
Index Absolute X addressing mode / Index Absolute Y addressing modeは第1オペランドに記述したアドレスに
第2オペランドにxまたはyを記述することで指定したインデックスレジスタの値を第1オペランドのアドレスに
加算してそのアドレスを指定することができます。Xレジスタの値を$F0, Yレジスタの値を$20とすると
$C080+$F0=$C170となり、$C170の内容がAレジスタにロードされます。
sta
は指定したアドレスにAレジスタの内容をセットする命令で、
この場合$6000+$20=$6020となり、$6020にAレジスタの内容をセットします。
展開されるオペコードは3Byteになります。
sta <$D0, x
ldx <$D0, y
Index Zero-Page X addressing mode / Index Zero-Page Y addressing modeはAbsolute addressing modeに対する
Zero-Page addressing modeと同様にIndex Absolute addressing modeに対する0ページを対象とするアドレッシングモードです。
ただし、注意点があります。このアドレッシングモードではldx
とstx
の2命令に限り
インデックスレジスタYが使用できそれ以外の場合はインデックスレジスタXしか使用できません。
ldx
とstx
はそれぞれXレジスタを対象とするロード、セット命令であるため
代用としてインデックスレジスタYが使用できるようになっています。
展開されるオペコードは2Byteになります。
lda [$40, x]
Index Indirect addressing modeはインデックスレジスタXを使用する間接アドレッシングです。 間接アドレッシングとは2Byteの連続した0ページアドレスにセットされた値を実際の対象アドレスとして 処理するアドレッシングです。 この例では0ページアドレス$40にインデックスレジスタXを加算した0ページアドレスを間接アドレッシングに使用します。 Xレジスタの内容が$02の場合、$42, $43を間接アドレッシングに使用します。 $42が対象アドレスの下位8bit、$43が対象アドレスの上位8bitを表します。 $42, $43の内容がそれぞれ$F0, $DEだった場合、この例ではアドレス$DEF0の値をAレジスタにロードします。
展開されるオペコードは2Byteになります。
lda [$40], y
Indirect Index addressing modeはインデックスレジスタYを使用する間接アドレッシングです。 先ほどのIndex Indirect addressing modeとはインデックスレジスタを加算するタイミングが異なります。 こちらでは$40,$41を間接アドレッシングに使用します。 $40,$41の内容が$00,$C0なら$C000にインデックスレジスタYの値を加算しこれが対象アドレスになります。 インデックスレジスタYが$80だったとすると、この例の場合$C080の内容がAレジスタにロードされます。
展開されるオペコードは2Byteになります。
jmp [$0320]
Absolute Indirect addressing modeは唯一jmp
命令にのみ使用できるアドレッシングモードです。
この例では$0320,$0321にセットされたアドレスにPCを移動(ジャンプ)しています。
あらかじめ間接アドレッシングに使用するアドレス($0320,$0321)の値を書き換えておくことで
ジャンプ先を変えることができます。
このアドレッシングモードにはバグがあり、$03FFのような8bit境界を跨ぐような指定をすると 正しく動作しないようです。
展開されるオペコードは2Byteになります。
bne Label1
Label1:
Relative addressing modeは条件分岐系の命令で使用されるアドレッシングモードです。
条件分岐命令は条件が成立した場合にオペランドに指定されたアドレスにジャンプしますが、
Relative addressing modeではジャンプ先のアドレスを絶対アドレス(2Byte)ではなく現在のアドレスからの
相対値(1Byte)で指定します。このため離れたアドレスへのジャンプはできませんが、
jmp
命令よりやや速くジャンプできます。
展開されるオペコードは2Byteになります。
この例のように実際にはラベル名を指定しアドレス値はアセンブラが計算するので相対値を 計算しておく必要はありません。これまで紹介した他のアドレッシングモードでも同様です。 逆にラベルを使用せず実際の数値で記述することは、プログラムの可読性を低下させるだけでなく プログラムの修正の際に設定した値すべてを書き換える必要が出て作業効率の面でも 悪影響があります。
6502には以上の13種類の豊富なアドレッシングモードが提供されておりこれらを駆使することで、 少ないレジスタでも効率的な処理を可能にしています。
6502には4種類の割り込みがあり、それぞれNMI, RESET, BRK, IRQと呼ばれています。 これらの割り込みが発生すると$FFFAから始まる3Word(1Word=2Byte)のベクタアドレスに ジャンプします。アドレスは16bitなので割り込み4種類に対してアドレスが3箇所しか設定できません。 これはBRKとIRQが共通のアドレスを使用し、ジャンプ後に必要であればPSRレジスタのBフラグを見ることで 分離する仕様だからです。
割り込みでのジャンプは割り込みが発生した時点で処理を行っていたアドレスとPSRをスタックに積みます。
割り込み処理が終了したらrti
命令によりスタックからアドレスとPSRをもどして元の処理に戻ります。
他のA,X,Yレジスタはプログラマが必要であると判断したらスタックに積むようプログラムするという設計で
自動ではスタックに積まれないので注意が必要です。
NMI割り込みのベクタアドレスは$FFFAでRESET割り込みの次に優先順位の高い割り込みです。 NMIとはマスク禁止割り込みの意味でPSRレジスタを割り込み禁止に設定しても、 別に割り込みを禁止する処置がされていない限り割り込みが発生します。
NESではこの割り込みをVBlank割り込みに設定しています。VBlank割り込みは日本のテレビで使用されている NTSC信号では1/60秒ごとに定期的に発生します。NESでは画面の更新が基本的にVBlank中にしか行えないため、 このNMI割り込みが処理の中心になります。
RESET割り込みはベクタアドレス$FFFCで最も優先順位の高い割り込みです。 電源投入時やリセットボタンを押した場合などに発生する割り込みで、 プログラムの開始位置を指定する割り込みです。
BRK/IRQのベクタアドレスは$FFFEで共通です。PSRのIフラグをセットすると割り込みが禁止されます。
BRKはブレーク割り込みのことでソフトウェア割り込みとも呼ばれます。
プログラム内でbrk
命令を実行すると発生する割り込みです。
IRQはハードウェア割り込みでハードウェアにより発生する条件が異なります。
NESではマッパーによっては走査線カウンタによってこのIRQを発生させることができ、
これを使用して画面内分割表示などが行える場合があります。
BRK割り込みでは復帰時のアドレスが+1される仕様のようです。扱いがややこしいのであまり使用している場面を見かけません。
6502のニーモニックを表にまとめたものです。操作内容のA,X,Y,S,Pはレジスタを意味し、 Mはアドレッシングで指定されたメモリを意味します。Cはキャリーフラグです。 !Cと書いた場合はキャリーフラグを反転(NOT)して演算を行うという意味です。
ニーモニック | 変化するフラグ | 操作内容 | 備考 |
---|---|---|---|
lda | NZ | A <- M | |
sta | NZ | A -> M | |
ldx | NZ | X <- M | |
stx | NZ | X -> M | |
ldy | NZ | Y <- M | |
sty | NZ | Y -> M | |
tax | NZ | A -> X | Implied addressing |
txa | NZ | X -> A | Implied addressing |
tay | NZ | A -> Y | Implied addressing |
tya | NZ | Y -> A | Implied addressing |
txs | NZ | X -> S | Implied addressing |
tsx | NZ | S -> X | Implied addressing |
pha | NZ | A -> [S], S-- | AレジスタをスタックにPHSH。Implied addressing |
pla | NZ | A <- [S], S++ | AレジスタにスタックからPULL。Implied addressing |
php | NZ | P -> [S], S-- | PSRをスタックにPHSH。Implied addressing |
plp | NZ | P <- [S], S++ | PSRにスタックからPULL。Implied addressing |
sec | C | C <- 1 | Cフラグセット。Implied addressing |
clc | C | C <- 0 | Cフラグクリア。Implied addressing |
sei | I | I <- 1 | Iフラグセット。Implied addressing |
cli | I | I <- 0 | Iフラグクリア。Implied addressing |
sed | D | D <- 1 | Dフラグセット。Implied addressing |
cld | D | D <- 0 | Dフラグクリア。Implied addressing |
clv | V | V <- 0 | Vフラグクリア。Implied addressing |
inc | NZ | M <- M+1 | メモリ内容をインクリメント。 |
dec | NZ | M <- M-1 | メモリ内容をデクリメント。 |
inx | NZ | X <- X+1 | Xレジスタをインクリメント。Implied addressing |
dex | NZ | X <- X-1 | Xレジスタをデクリメント。Implied addressing |
iny | NZ | Y <- Y+1 | Yレジスタをインクリメント。Implied addressing |
dey | NZ | Y <- Y-1 | Yレジスタをデクリメント。Implied addressing |
adc | NVZC | A <- A+M+C | Aレジスタにメモリ内容とCレジスタを加算。通常adcの前にclcを行いCレジスタをクリアする。 |
sbc | NVZC | A <- A-M-!C | Aレジスタからメモリ内容とCレジスタのNOTを減算。通常sbcの前にsecを行いCレジスタをセットする。 |
asl | NZC | C<-A:bit7, A:bitN<-A:bitN-1, A:bit0<-0 | Aレジスタ左シフト。Accumulator addressing |
lsr | NZC | C<-A:bit0, A:bitN<-A:bitN+1, A:bit7<-0 | Aレジスタ論理右シフト。Accumulator addressing |
rol | NZC | C<-A:bit7, A:bitN<-A:bitN-1, A:bit0<-C | Aレジスタ左ローテーション。Accumulator addressing |
ror | NZC | C<-A:bit0, A:bitN<-A:bitN+1, A:bit7<-C | Aレジスタ右ローテーション。Accumulator addressing |
and | NZ | A <- A and M | 論理積演算。 |
ora | NZ | A <- A or M | 論理和演算。 |
eor | NZ | A <- A eor M | 排他論理和演算。 |
cmp | NZC | A-M | Aレジスタとメモリの比較。 |
cpx | NZC | X-M | Xレジスタとメモリの比較。 |
cpy | NZC | Y-M | Yレジスタとメモリの比較。 |
bit | NVZ | A and M, N=M:bit7, V=M:bit6 | 論理積の演算結果によりZフラグ変更 |
beq | Z=1で指定アドレスへ相対ジャンプします。 | ||
bne | Z=0で指定アドレスへ相対ジャンプします。 | ||
bcs | C=1で指定アドレスへ相対ジャンプします。 | ||
bcc | C=0で指定アドレスへ相対ジャンプします。 | ||
bvs | V=1で指定アドレスへ相対ジャンプします。 | ||
bvc | V=0で指定アドレスへ相対ジャンプします。 | ||
bmi | N=1で指定アドレスへ相対ジャンプします。 | ||
bpl | N=0で指定アドレスへ相対ジャンプします。 | ||
jmp | 指定アドレスへジャンプします。 | ||
jsr | 指定アドレスへサブルーチンジャンプします。 | ||
rts | サブルーチンから復帰します。 | ||
rti | 割り込みから復帰します。 | ||
nop | クロックを消費するだけで何もしません。 | ||
brk | BRK割り込みを発生させます。 |