ここでは,6502というCPUを研究して行きます.さらに6502をプログラムで 完璧に模倣(エミュレーション)することを目指します.
まず,6502について,私が知る範囲で説明したいと思います. 国内で6502といえばファミコンですね.しかし,ファミコンに入っている 6502は,普通の6502とは少し違う部分があります.ここでは,普通の6502 とファミコンの6502で異なると思われる部分についても,私が確認した範囲で 書きたいと思います.
個人的には,Z80よりも6502の方が使いやすいCPUだと思います. 命令セットが少ない分,アセンブリ言語入門者にとっては,強いて必要でない命令に 惑わされることがなくて,ハードルが低いのではないかと思います.
ご注意:
$ で始まる数字は16進数表記であることを示します.C言語で
言うところの 0x です.また b で終わる文字は
2進数表記であることを示します.
(例:10010110b = $96 = 0x96 = 150).
1バイトは8ビットです.ここでは bit 0 〜 bit 7 と表現することにします. これは6502に限ったことではないですが,bit 0 は特に LSB (Least Significant Bit),または最下位ビットと呼ばれ,bit7 は MSB (Most Significant Bit) または最上位ビットと呼ばれます.符号付き の値の場合,MSB は符号ビットとなります.符号付き8ビットの値を考えると,ゼロは 00000000b,正の最大値(+127)は 01111111b,負の最大値(-128)は 10000000b になります. この場合,01111111b (+127) に1を加えると 10000000b となり,とたんに -128 になって しまいます.これは8ビットで表現できる符号付きの値の範囲を超えたために起こります. この現象をオーバーフローといいます.
CPUはメモリから値を取り出し,これを命令
(instruction)と解釈し,その命令が必要とするデータを逐次読み込んで
実行を進めます.この命令とデータの集合はマシン語または
機械語と呼ばれます.その中で特に命令(instruction)のことを
オペコード
例えば,オペランドを1つ使用する命令が続く場合,
6502のオペコード(命令)は全て1バイトで表現されます.1バイトで表現できる値は 0〜255の256個ありますから,命令は256種類定義できることになります. しかし6502の場合,実際にはその値に命令が定義されていなかったり,メーカーが 動作を公開していない未定義命令 (非公開命令, undocumented instructions ともいう)もあるので,その種類は 256よりも少なくなっています.
ここではCPU6502(以後6502)の特徴を簡単に説明します. 6502は国内では今後お目にかかることはまずないであろうというほどマイナーな CPUですが,海外では Apple コンピュータや Commodore 64,Atari のゲーム機 などで使用されており,結構メジャーな8ビットCPUのようです.
6502のアドレス線の数はバリエーションがあったと思いますが,ここではアドレス線が 16本のものについて説明します.この場合,6502は $0000 - $FFFF の64KBの メモリ空間を持っています.6502はメモリマップドI/O方式 を採用しているので,メモリとI/Oの区別がなく,メモリの読み書きとI/Oの読み書きには 同じ命令を使用します(STA, LDA, STX, LDX, STY, LDY 等).
6502はまた,ゼロページ機構を持っており,アドレス空間 $0000 - $00FF (ゼロページ)のメモリを少ない命令数で高速にアクセスすることができます.スタック領域は ゼロページに続く $0100 - $01FF に固定されており,プログラマはスタックポインタの 下位8ビットをXレジスタを介して操作することが可能です.
6502は,次の6種類のレジスタを持っており, プログラマはこれらのレジスタをアセンブリ言語で操作 することによってプログラミングを行います.
レジスタ名 | 主な用途 |
---|---|
アキュムレータ A | 演算用 |
インデックスレジスタ X | インデックス用途,カウンタ,スタックポインタへのアクセス |
インデックスレジスタ Y | インデックス用途,カウンタ |
スタックポインタ S | 256バイトのスタックページへのポインタ |
ステータスレジスタ P | 演算結果の(特殊な)状態を示す |
プログラムカウンタ PC | 現在実行中の位置 |
ステータスレジスタの内容は次のようになっています.
BIT | 記号 | 名称 | 説明 |
---|---|---|---|
bit 0 | C | キャリーフラグ | ADC 命令を実行したとき bit 8 への繰り上がりが発生した場合に1にセットされる. また SBC, CMP, CPX, CPY 命令を実行したとき bit 8 からの繰り下がりが 無い場合にセットされる. SEC 命令を実行すると1にセットされ,CLC 命令を実行すると0にクリアされる. |
bit 1 | Z | ゼロフラグ | 算術論理演算(ADC, SBC, AND, OR, EOR, etc)およびデータロード命令 (LDA, LDX, LDY, etc)を実行した後,結果がゼロにの場合に1にセットされ, ゼロにならなかった場合は0にクリアされる. |
bit 2 | I | 割り込み禁止フラグ | システムリセット後,割り込み(IRQ および NMI) が発生したとき, BRK 命令および SEI 命令を実行したときに1にセットされる. CLI 命令を実行すると0にクリアされる. |
bit 3 | D | デシマルモードフラグ | このフラグが1にセットされているとき,ADC 命令や SBC 命令がBCDで行なわれる. ファミコンのカスタム6502はこのフラグを使用しない. 値の変更のみ可能. |
bit 4 | B | ブレイクコマンドフラグ | BRK 命令を実行するとフラグレジスタがスタックに退避される際に このフラグが1にセットされる(IRQ, NMI のときは0). また,PHP ののち PLA を行った場合,bit 4 は常に1となる. |
bit 5 | R | 予約フラグ | 使用しない.常に1の値を保持する. |
bit 6 | V | オーバーフローフラグ | 演算前後のオペランドが $7F - $80 間をまたぐとセットされる. BIT 命令を実行するとメモリの bit 6 がオーバーフローフラグに セットされる. CLV 命令を実行するとオーバーフローフラグは0にクリアされる. |
bit 7 | N | ネガティブフラグ | 演算の結果,bit 7 が1にセットされていれば(符号付では負とされるから)ネガティブ フラグは1にセットされ,bit 7 が0であればネガティブフラグは0にクリアされる. また,BIT 命令を実行するとメモリの bit 7 がネガティブフラグにセットされる. |
次に,6502がメモリを参照する方法である13種類のアドレッシングモード を示します.
アドレッシングモード | 命令の構成 | 説明 |
---|---|---|
Accumulator | 命令 | アキュムレータに対する命令 |
Implied | 命令 | データを必要としない命令 |
Immediate | 命令 D8 | D8 は即値;データそのもの |
Zero Page | 命令 D8 | D8 はゼロページのアドレスを示す |
Zero Page Index X | 命令 D8 | D8+X はゼロページのアドレスを示す |
Zero Page Index Y | 命令 D8 | D8+Y はゼロページのアドレスを示す |
Absolute | 命令 AL AH | AH || AL は実効アドレス(effective address)を示す |
Absolute Index X | 命令 AL AH | AH || AL+X は実効アドレスを示す |
Absolute Index Y | 命令 AL AH | AH || AL+Y は実効アドレスを示す |
Indirect | 命令 AL AH | AH || AL が示すアドレスからの2バイトが実効アドレスを示す |
Index Indirect | 命令 D8 | D8+X が示すゼロページからの2バイトは実効アドレスを示す |
Indirect Index | 命令 D8 | D8 が示すゼロページからの2バイト+Yは実効アドレスを示す |
Relative | 命令 D8 | D8 は符号付き8ビット値(-128 <= D8 <= 127)で, PC+D8 が分岐先のアドレスを示す |
以下,各アドレッシングモードの詳しい説明です.
■アキュムレータ(Accumulator)このアドレッシングモードは,オペランドを必要としません. 演算の対象(オペランド)が A レジスタ,つまりアキュムレータである命令のアドレッシングモード は アキュムレータ です. このアドレッシングモードを持つ命令には,LSR, ROR, ASL, ROL があります.例えば, LSR A は,A レジスタの内容を1ビット右へシフトし,最下位ビットから はみ出たビットの値はキャリーフラグへ格納され,最上位ビットは 0 になります. 今,A = 01011010b (2進表記) があったとします.これは一度LSR A すると,A = 00101101b になり, さらに LSR A すると A = 00010110b になり,キャリーフラグ(C フラグ)が1にセットされます.このように,演算の対象が A レジスタ, つまりアキュムレータの命令のアドレッシングモードをアキュムレータと呼びます.
■インプライド(Implied)このアドレッシングモードは,オペランドを必要としません. 演算の対象が固定されていて,オペコード(命令)1つで実行可能な命令のアドレッシングモード を インプライド と呼びます.これには PHA, INX, INY, SEI, CLI 等の命令 があります.
■イミディエイト(Immediate)このアドレッシングモードは,オペランドを1つ必要とします.そのオペランドを そのままデータとして使用する命令のアドレッシングモードを イミディエイト と呼びます.例えば,A レジスタに $10 を加算させる命令はインプライドで, ADC #$10 と記述します.ここで書かれた # 記号は, そのままデータとして使用することを示し,この値はCPUに読まれて即加算されます. このことから,#$10 のことを 即値 と呼ぶことがあります.
■ゼロページ(Zero Page)このアドレッシングモードは,オペランドを1つ必要とします. これは6502の特徴的なアドレッシングモードで,オペランド1つでメモリ空間の $0000 - $00FF の256バイトをアクセスする命令がこれに属します.イミディエイト のように第1オペランドをそのままデータとしては使用せず,ゼロページへのアドレス として使用します.例えば ADC <$10(# が < になっている) は,ゼロページの $10 番地の値を A に加える命令になります.
■ゼロページ・インデックス X(Zero Page Index X)このアドレッシングモードは,オペランドを1つ必要とします. 第1オペランドが示すのゼロページへのアドレスに X レジスタの値を加算したものを最終的なメモリアドレスとします (実効アドレス(effective address)という). 例えば ADC <$10,X で,X の値が $20 の場合は, ゼロページの $30 番地の値を A に加える命令になります.X レジスタの値を加算した結果 実効アドレスが $FF を超えた場合,繰り上がった桁は無視されます.例えば ADC <$FF,X で,X が $01 だった場合は,実効アドレスは $00 となり,従って ゼロページの $00 番地の値が A レジスタに加算されます.
■ゼロページ・インデックス Y(Zero Page Index Y)このアドレッシングモードは,オペランドを1つ必要とします. このアドレッシングモードは,上のゼロページ・インデックス XのYレジスタ版です. つまり,第1オペランドが示すのゼロページへのアドレスに Y レジスタの値を加算した ものを最終的なメモリアドレスとします.例えば ADC <$10,Y で,Y の値が $20 の場合は,ゼロページの $30 番地の値を A に加える命令になります. ゼロページ・インデックス Xの場合と同様に,実効アドレスが $FF を超えた場合, 繰り上がった桁は無視されます.
■アブソリュート(Absolute)このアドレッシングモードは,オペランドを2つ必要とします. 第1オペランドは実効アドレスの下位バイト,第2オペランドは実効アドレスの 上位バイトとなります.例えば ADC $0200 は,CPUアドレス $0200 番地の値を A に加える命令です.アセンブリ言語で表記すればこのように なりますが,マシン語表記では,この命令は 6D 00 02 となり, アドレスの順番が逆になることに注意してください.
■アブソリュート・インデックス X(Absolute Index X)このアドレッシングモードは,オペランドを2つ必要とします. 第1オペランドは実効アドレスの下位バイト,第2オペランドは実効アドレスの 上位バイトとなりますが,最終的な実効アドレスはこれにXレジスタの値を加えた ものになります.例えば ADC $02FF,X は,CPUアドレス $02FF+X 番地の値を A に加える命令です.ここで,Xの加算は 16 ビットで 行われることに注意してください.つまり,$02FF+X で,X = $01 の場合,実効アドレス は(ゼロページ・インデックス X/Yのときのように) $0200 とはならず, $0300 となります.
■アブソリュート・インデックス Y(Absolute Index Y)このアドレッシングモードは,オペランドを2つ必要とします. このアドレッシングモードは,アブソリュート・インデックス X のYレジスタ版です. つまり,第1オペランドは実効アドレスの下位バイト,第2オペランドは実効アドレスの 上位バイトとなりますが,最終的な実効アドレスはこれにYレジスタの値を加えた ものになります.例えば ADC $02FF,Y は,CPUアドレス $02FF+Y 番地の値を A に加える命令です.ここで,Yの加算は 16 ビットで 行われることに注意してください.
■インダイレクト(Indirect)このアドレッシングモードは,オペランドを2つ必要とします. 第1オペランドはアドレスの下位バイト,第2オペランドはアドレスの上位バイト となりますが,この16ビットのアドレスが示す2バイトの値が実効アドレス になります.JMP [$0200] は,$0200 および $0201 番地が示す値を ジャンプ先のアドレスとする命令で,インダイレクトをアドレッシングモードとして 持つ命令は実はこの命令だけです.$0200 に $00 $0201 に $80 が格納されていた とすると,ジャンプ先のアドレス(実効アドレス)は $8000 になります.
■インデックス・インダイレクト(Index Indirect)このアドレッシングモードは,オペランドを1つ必要とします. 第1オペランドにXレジスタの値を加算した値(繰り上がりは無視する)は ゼロページのアドレスを示しますが,実効アドレスはこのゼロページの アドレスからの2バイトです.例えば ADC [$80,X] は, ゼロページの $80+X および $81+X 番地からの2バイトが示すアドレスの値 を A に加算する命令です.X = $80 だった場合,実効アドレスはCPUアドレス $0000 からの2バイトになります.
■インダイレクト・インデックス(Indirect Index)このアドレッシングモードは,オペランドを1つ必要とします. 第1オペランドはゼロページのアドレスを示しますが,実効アドレスはこの ゼロページのアドレスからの2バイトにYレジスタの値を加えた値になります. 例えば ADC [$80],Y は,ゼロページの $80 および $81 番地からの2バイトに Y レジスタの値 を加えたアドレスが示す値を A に 加算する命令です.
■リラティブ(Relative)このアドレッシングモードは,オペランドを1つ必要とします. 第1オペランドは,符号付き8ビット値と解釈され,現在のプログラムカウンタ(PC) の値からの相対アドレスを示します.例えば BPL $10 は, ネガティブフラグ(N フラグ)が0のとき BPL 命令のアドレス+2から 16 進んだ位置へ 分岐する命令で,BNE $80は,ネガティブフラグが1のとき BNE 命令の アドレス+2から 128 戻った位置へ分岐する命令です.6502の場合,全ての分岐 命令のアドレッシングモードはリラティブです(逆にいうと,-128 〜 +127 の範囲を 超える分岐は直接行えないということです).いずれも分岐命令から2だけ 進んだ位置から分岐先のアドレスを計算するのは,現在の命令を実行している時点で既に PCは次の命令の位置まで進んでいるからです.
6502はシステムリセット後,割り込み禁止フラグ(I フラグ)を1にセットし, ブレイクコマンドフラグ(B フラグ)を0にクリアします.その後メモリ空間の $FFFC 番地の1バイトをプログラムカウンタ(以後PC)の下位8ビットに,また $FFFD 番地の1バイトをPCの上位8ビットにロードし,プログラムの実行を 開始します.例えば,$FFFC 番地の値が $00,$FFFD 番地の値が $C0 であれば, リセット時のPCの値は $C000 になり,ここがシステムリセット直後に実行される プログラムの先頭になります.
6502は1つ命令を実行するごとに割り込み(以後IRQ)がリクエストされていないかを チェックします.IRQ がリクエストされていることを検知すると,6502はまず ステータスレジスタ(P レジスタ)の割り込み禁止フラグ(I フラグ)を調べ,割り込みの 実行が禁止されていないかをチェックします.割り込みが禁止されていなければ(I フラグ のビットがゼロであれば),6502はPCの値を上位8ビット,下位8ビットの順に スタックへ退避し,次にステータスレジスタPをスタックへ退避しますが, この時退避されるPのブレークコマンド(B フラグ)のビットは0にクリア されます.その後,6502はPのIフラグのビットを1にセットし,メモリ空間の $FFFE および $FFFF の2バイトの値をリセットの場合と同様にPCにロードし, このPCの位置へジャンプします.
BRK 命令を実行した場合も IRQ が検知された場合と同じように, $FFFE および $FFFF の2バイトの値をPCにロードしますが,割り込みが 禁止状態(I フラグのビットが1)であっても BRK 命令は遂行される点と,ステータス レジスタPをスタックへ退避する際のブレークコマンド(B フラグ)のビットが1にセット される点,またスタックに退避されるPCの値が BRK+2 になる点で IRQ の実行と異なります.
優先割り込み(以後 NMI)がリクエストされると,IRQ と同じ動作をしますが, 割り込みが禁止されていても割り込み処理は遂行される点と,PCの値を ロードする場所が $FFFA および $FFFB である点が異なります.
リセット,IRQ, BRK命令, および NMI の直後のPCが示すアドレスにある値は OPコードまたは命令を示すコードで,この値を読んで6502は どの命令を実行するのか,この命令のアドレッシングモードは どれなのか,また命令のパラメータであるオペランドが 何バイト必要なのかを知ります.
例えばアドレッシングモードが Absolute であった場合,この命令のオペランドは AL および AH の2つで,6502はOPコードを読んだ後オペランド AL および AH を続けて読み進め,PCの値を増加させます(PCをインクリメントさせる といいます).命令,AL,および AH を読み込んで命令を処理した後,PCが示す 場所は次の命令の位置になっています.
各種命令の解説はここではしません.6502の命令の情報は海外のサイトに たくさんありますので,Google などで 6502 と technical document 等のキーワードを併せて検索してください.ファミコン用のカスタム 6502 (RP2A03) について,注意点を記録しておきます. 以下は,ファミコンの6502にて確認していますが,私は汎用の6502を持っていない ため(国内では既に入手不可能と思われる),汎用6502での確認ができていません. 情報を持っている方がいましたら教えてください.
2.5.1 I フラグ (割り込みマスクフラグ)汎用6502 の I フラグ(割り込みマスクフラグ)の詳細はハッキリ知りませんが, I フラグは,RP2A03 が RESET 信号を受けると1(IRQ禁止状態)にセットされます. また,NMI 発生と共に1(IRQ禁止状態)にセットされます.
2.5.2 B フラグRP2A03 の B フラグのステータスは,PHP した後 PLA で読み出すと常に1になります. B フラグのステータスを調べるには,BRK / NMI / IRQ 発生時に,割込み処理の先頭で ステータスレジスタの待避場所 ($SP+1) を調べないと駄目です.
例:NMI および IRQ 発生直後に,
tsx inx lda $0100,x and #$10 ; FLAG Bとすると,bit 4 (B フラグのビット)はゼロですが,BRK 命令を実行した場合は, bit 4 は1になっています.これにより,割込みの要因が NMI / IRQ によるものか, BRK 命令によるものかを判別することができます. 2.5.3 D フラグ
RP2A03 の D フラグ(デシマルモードフラグ)は ADC 命令,および SBC命令に おいて使用されません.ただし CLD および SED 命令によって操作することは 可能です.
2.5.4 SEI と CLI 命令の調査記録Iフラグがセットされていて,/IRQ 信号線が "L" になっているとき これがトリガされるのは,CLI とその次の命令が実行された後です.
また,Iフラグがセットされていて,/IRQ 信号線が "L" になっているとき,
念のために,Iフラグがセットされていて,/IRQ 信号線が "L" になっているとき,
ところで,Iフラグがセットされていて,/IRQ 信号線が "L" になっているとき,