6502の研究部屋

ここでは,6502というCPUを研究して行きます.さらに6502をプログラムで 完璧に模倣(エミュレーション)することを目指します.

まず,6502について,私が知る範囲で説明したいと思います. 国内で6502といえばファミコンですね.しかし,ファミコンに入っている 6502は,普通の6502とは少し違う部分があります.ここでは,普通の6502 とファミコンの6502で異なると思われる部分についても,私が確認した範囲で 書きたいと思います.

個人的には,Z80よりも6502の方が使いやすいCPUだと思います. 命令セットが少ない分,アセンブリ言語入門者にとっては,強いて必要でない命令に 惑わされることがなくて,ハードルが低いのではないかと思います. まぁそんなことを言っている私もアセンブリ言語であまり大きなプログラムを作った ことはないので,あまりでしゃばったことは言わないようにしておきます(^_^;.

ご注意:
−以下の情報には一部私が調査した結果が含まれています.このため,内容の保証はできません
−このページの情報を使用して損害が発生しても,私は一切責任を負えません
ファミコンは任天堂株式会社の登録商標です.
−以下の内容に間違いがあれば教えてください.

トップへ戻る


○目次
  1. 予備知識
  2. CPU6502の概要
  3. CPU6502を用いた回路とそのエミュレーション

トップへ戻る


1.予備知識

$ で始まる数字は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)のことを オペコード(op-code: operational code の略) と呼び,オペコードが必要とするデータのことを オペランドといいます. CPUはオペコードを読み出し,これがオペランドを必要とする場合は,続いてこれを読み出します. 最初に読み出されるオペランドを第1オペランド,さらにオペランドが必要 な場合は続いて第2オペランドを読み出します.以後第3オペランド, 第4オペランド,...と続きます.ただし6502は第2オペランドまでしか使用しません.

例えば,オペランドを1つ使用する命令が続く場合,

オペコード,オペランド,オペコード,オペランド,...

とメモリに配置されます.オペランドを1つ使用する命令の後にオペランドを2つ使用する命令が あって,その後にオペランドを1つ使用する命令がある場合は,

オペコード,オペランド,オペコード,オペランド,オペランド,オペコード,オペランド

という配置になります.もしバグや何らかの原因でこれが守られなかった場合は, CPUはプログラマがオペランドのつもりで書いた値を命令として読み取ったり, オペコードのつもりで書いた値をデータとして使ったりするのでプログラムから 脱線してしまい,予測不能な動作をします.

6502のオペコード(命令)は全て1バイトで表現されます.1バイトで表現できる値は 0〜255の256個ありますから,命令は256種類定義できることになります. しかし6502の場合,実際にはその値に命令が定義されていなかったり,メーカーが 動作を公開していない未定義命令 (非公開命令, undocumented instructions ともいう)もあるので,その種類は 256よりも少なくなっています.

目次へ戻るトップへ戻る


2.CPU6502の概要

ここでは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レジスタを介して操作することが可能です.

目次へ戻るトップへ戻る


2.1 レジスタ

6502は,次の6種類のレジスタを持っており, プログラマはこれらのレジスタをアセンブリ言語で操作 することによってプログラミングを行います.

レジスタ名 主な用途
アキュムレータ A 演算用
インデックスレジスタ X インデックス用途,カウンタ,スタックポインタへのアクセス
インデックスレジスタ Y インデックス用途,カウンタ
スタックポインタ S 256バイトのスタックページへのポインタ
ステータスレジスタ P 演算結果の(特殊な)状態を示す
プログラムカウンタ PC 現在実行中の位置

目次へ戻る


2.2 フラグレジスタ

ステータスレジスタの内容は次のようになっています.

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 がネガティブフラグにセットされる.

目次へ戻るトップへ戻る


2.3 アドレッシングモード

次に,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 が分岐先のアドレスを示す

注1: D8 は8ビット値です.
注2: || は8ビット値 AL および AH を2つ連結させて16ビット値を得る演算子です.

以下,各アドレッシングモードの詳しい説明です.

■アキュムレータ(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つ必要とします.そのオペランドを そのままデータとして使用する命令のアドレッシングモードを イミディエイト と呼びます(英語では「イミディエット」と発音する。"immediately" = 「即座に」). 例えば,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は次の命令の位置まで進んでいるからです.

目次へ戻るトップへ戻る


2.4 リセットと割り込み

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 等のキーワードを併せて検索してください.

目次へ戻るトップへ戻る


2.5 ファミコンの6502について

ファミコン用のカスタム 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" になっているとき,

CLI
SEI

を実行すると IRQ はトリガされ,リターン先のアドレスは SEI 命令 の次の番地になることから,SEI 命令についても IRQ が実際に禁止されるのは SEI 命令とその次の命令が実行されてからであることが分かります.

念のために,Iフラグがセットされていて,/IRQ 信号線が "L" になっているとき,

CLI
SEI
BRK

を実行したところ,予想どおり割込みルーチンは2回呼び出され,リターン先の アドレスは $(BRK+2) となることから,SEI 命令実行直後に BRK が実行された ことが確認できます.また,この後 IRQ がトリガされることがないことから, BRK 命令実行直後に(SEIにより) IRQ が禁止されたことが分かります.

ところで,Iフラグがセットされていて,/IRQ 信号線が "L" になっているとき,

CLI
PHP
PLA

を実行したところ,A の bit2 (I フラグのビット) はクリアされているので, フラグ自体は命令実行時に即クリアされることが分かります.

目次へ戻るトップへ戻る


3. 6502を用いた回路とそのエミュレーション(書き直し中)

書き直し中です... m(_ _)m

目次へ戻るトップへ戻る
(C) Ki 2001, 2002