MCS6502

1970年代末期、電源を入れるとBASICがすぐに使えるようなオールインワンパッケージの8 bitコンピュータがいくつも台頭してきました。それ以前は、S-100 busシステムの時代で、筐体、CPUボード、メモリボード、I/Oボード、入出力装置、外部記憶装置などの各モジュールを寄せ集めて、その上でBASICとかアセンブラとか他の高級言語なんかでプログラミングを行うのが、一般的なパーソナルコンピュータのイメージでした(特にアメリカでは)。しかし1970年代末期にはCPUもメモリも電源もキーボードやCRT表示用の回路まで、単一の、比較的コンパクトなタイプライタ風の筐体の中に納められたコンピュータがいくつも発売され、主流になりつつありました。SOLとかTRS-80のように80系のプロセッサを搭載したモデルもありましたが、Apple社のAPPLE IIやCommodore社のPET-2001などはグラフィック性能や外観以外にBASICが他より高速で、注目をあびました。BYTE誌なんかではパーソナルコンピュータ内蔵BASICのベンチマークテスト結果なんかがよく掲載されていましたが、それまで主流だったZ80 CPUなどを採用したコンピュータのBASICよりもApple/PETのBASICの方が高速だったのです。Apple/PETで共通に使われていたのはMCS6502というマイクロプロセッサでした。
パーソナルコンピュータという形態になる以前から、ICレベルからコンピュータを組み上げて遊んでいる連中の間で、どのマイクロプロセッサが優れているのか(ま、自分の使っているプロセッサ自慢なんだが)、議論されていました。古くは8080A対MC6800ですか。他にもSC/MP IIとかCOSMACとか、いろいろありましたが。
パーソナルコンピュータという形態ができあがりつつある時期にも、まだプロセッサ論争が続いていました。しかし、そんな中、6502を採用したパーソナルコンピュータがBASICベンチマークで上位を占めているという事実は、輸入品で高価で機能も高くある種あこがれの的でもあったApple/PETのユーザの勢いを後押ししました。
そんなMCS6502ですが、アセンブリ言語レベルでプログラミングしてみるとアクが強く、特にBASICプログラムから呼び出して使用するルーチンとか、あるいはBASICインタプリタの内部ルーチンを利用するプログラミングなんかの場合には、システム標準のBASICインタプリタがゼロページ領域を食い荒らしてユーザが利用可能なゼロページメモリが少なかったりして、なかなかつらいものがありました。

MCS6502の特徴としては次のようなものがあります。8 bitプロセッサにしては珍しかった各種間接アドレッシングを使えたこと。CPU内のレジスタはほとんど8 bit化して、スタックポインタまで8 bitにしてしまい、$0100から$01FFのアドレス範囲にしかスタックを配置できないというような思い切ったハードウェアの簡略化(ここでは16進数表記に$の記号を前置する)。相対分岐命令で飛び先を下位8 bitから順番に計算する際、上位8 bitに桁上がりの影響がないと判定した時点で先に進める(後で詳細を書きます)といった高速化の工夫が行われていること。ADD命令がなく、ADC命令で代用するなど、命令の思い切った簡略化。

MPS6502, SY6502
上はMOS Technology社のMPS6502、下はSynertek社のSY6502。SY6502はこのほかに林義信様よりいただいたものがあります。MOS Technology社はパッケージ種別をプリフィックスの第2文字で表示していて、セラミックパッケージがMCS、プラスチックパッケージはMPSとなります。

CMOS化された65C02にはビット操作命令など、追加された命令がいくつも存在します。そのため、MCS6502の未定義命令を使用していたプログラムは動作しません。

レジスタ構成は、このようになっています。
MCS6502 registers

MC6800と異なり、アキュムレータが1本になっている代わりにインデックスレジスタが2本になっています。このXとYのインデックスレジスタはアドレッシングにおいて異なる機能を持ちますから、注意してください。詳細はアドレッシングモードの解説で。プログラムカウンタPCが16 bit幅なのはよいとして、インデックスレジスタもスタックポインタも8 bit幅です。インデックスレジスタはポインタとして使用することはできません。16 bitのアドレス定数を修飾するレジスタくらいの役割になります。問題はスタックポインタで、8 bit幅しかないので、16 bitアドレスの全メモリ空間の任意の場所を指すわけにはいきません。スタックは1ページ内に限定されています。つまり、スタック領域の上位8 bitは$01に限定されています。16進数で$0100から$01FFまでの間の空間だけがスタックとして使用できるのです。
フラグ類を集めたレジスタをMC6800ではコンデションコードレジスタ(CCR)と呼びましたが、MCS6502ではプロセッサステータスレジスタ(PSR)と呼びます。このレジスタのフラグのようすもMC6800とは異なります。ただ、NはネガティブでVがオーバーフロー、Iが割り込みマスクでZがゼロフラグというあたりは、ビット位置が異なっても意味はMC6800と同じです。Cはキャリーフラグで、一見MC6800と同じですが、減算のときには桁借りがあったときに0で桁借りがなかったときに1になります。これはMC6800と逆になっていますから、注意しなくてはなりません。比較命令も減算の一種ですから、条件分岐命令の使い方がMC6800と異なります。減算のときにキャリーフラグの意味が逆になるCPUには、他にもMicrochip社のPICシリーズなんかがあります。ややこしそうですが、まぁ慣れですね。BはBRKフラグで、割り込み原因がソフトウェア割り込みのBRK命令であったかIRQ割り込み信号によるものかの識別に使います。この2種類の割り込みは、割り込みベクタが異なれば問題なかったのですが、なぜか同じ割り込みベクタを共用しているので、識別用のフラグが新設されたというわけです。最後に残ったDフラグは10進数演算モードを表すデシマルフラグです。MC6800にあったハーフキャリーフラグがないことに気付いているかもしれませんが、さらにMCS6502にはDAA命令もありません。それではBCD演算ができないみたいですが、あっと驚く方法でBCD演算を可能にしています。それが、このDフラグです。Dフラグが0のときには、加減算を行うADC命令やSBC命令で2進数演算が普通に行われます。ところがDフラグを1にセットしてあると、自動的にADC命令やSBC命令でBCD演算が行われるようになっているのです。減算でBCD演算が可能というだけでMC6800より強力ですが、たとえば複数バイトの加算サブルーチンがあったとして、そのサブルーチンをDフラグを1にセットしてから呼び出すだけで、複数桁のBCD加算サブルーチンとして利用可能になってしまうということになります。なかなか強力そうな機能です。しかし、リセット時の状態は不定ですし、割り込みサービスルーチンの中の演算も注意しないとメインルーチンで設定されているDフラグの状態によって影響を受けますから、充分に注意が必要な機能です。

アドレッシングモードは相当に増えています。イミディエートアドレッシング、ゼロページアドレッシング(MC6800のダイレクトアドレッシング)、アブソリュートアドレッシング(MC6800のエクステンドアドレッシング)、インプライドアドレッシングとアキュムレータアドレッシング(どちらもMC6800ではインプライドアドレッシングに分類)はMC6800と名称は異なっていても同一です。ただし、アブソリュートアドレッシングの16 bitアドレス指定はMC6800がオペコードの後にアドレス上位、アドレス下位と並んで配置されていたのに対し、MCS6502ではオペコードの後にアドレス下位、アドレス上位の順になっていて、8080Aなどと同一の並びになっています。
インデックスアドレッシングはMC6800では16 bitのインデックスレジスタを8 bitオフセットで修飾するモードしかありませんでしたが、MCS6502ではずっと複雑になっています。まずアブソリュートXとアブソリュートYは16 bitのオペランドアドレスを8 bitのインデックスレジスタで修飾するモードです。3 Byte命令になるし、インデックスレジスタは255までのオフセット分しか修飾できません。次はゼロページXとゼロページYアドレッシングモード。これはオペランドの内容とインデックスレジスタの内容を加えたゼロページ内のメモリにアクセスするモードです。かりに9 bit目にキャリーが発生しても、$100以上のアドレスにアクセスするわけではなく、アドレス上位1 Byteは0に固定されたままです。一般にゼロページXしか使えませんが、LDXとSTX命令に限っては、Xの内容を操作するのにゼロページXでは不便ですから、ゼロページXの代わりにゼロページYアドレッシングが使えるようになっています。この後の二つの間接アドレッシングは複雑になっています。ひとつはインデックス修飾の間接アドレッシングで、ゼロページXアドレッシングをまず行い、そのアドレスから始まるメモリ2 Byteの内容の値を操作対象アドレスと解釈します。ゼロページX間接とも表現します。ゼロページに複数のポインタを用意しておき、それをXレジスタで切り替えることができるモードです。もうひとつが間接アドレッシングのインデックス修飾です。まずゼロページアドレッシングを行い、そのゼロページメモリから2 Byteの値を取得します。それをYレジスタでインデックス修飾します。こちらは簡単にはゼロページ間接Yと表現します。ゼロページ領域内に複数のインデックスレジスタを用意しておきたい場合に使えます。ただし、特定の命令で使用するインデックスレジスタはゼロページアドレッシングで固定される点がXレジスタを使用するゼロページX間接と異なります。
条件分岐系の命令ではMC6800と同じように相対アドレッシングを使用します。JMP命令とJSR命令はアブソリュートアドレッシングが基本ですが、JMP命令にだけは間接アドレッシングが使えて、オペランドとして16 bitアドレスを使用し、そのアドレスから始まる2 Byteのデータが示すアドレスにジャンプします。
概して、チップ上からは16 bitポインタが消えてしまいましたが、その分の16 bitポインタをゼロページ上に多数用意できるアドレッシングモードが用意されているのが特徴です。MC6800では16 bitポインタ(インデックスレジスタ)が1本しかなく、その退避や復旧回数がやたら多くなってしまってプログラム速度が低下してしまったのと比べると、チップ面積を小さくできる上に高速化できます。もう少し具体的に考えてみましょうか。2ヶ所以上のメモリロケーションに取られた連続データへの操作(たとえばメモリコピーや文字列比較など)の場合、同じインデックスレジスタを続けて2回使用するなんてことはあまりありません。1ヶ所のデータにアクセスしたら、すぐに別のメモリロケーションのデータを参照して何かの作業を行うというほうが普通です。すると、MC6800ではインデックスアドレッシングを使用するたびに、どこかにインデックスレジスタを保存して、別のポインタをインデックスレジスタにロードしなくてはなりません。そんなことなら、MCS6502でゼロページ間接Yアドレッシングを使用した方が、インデックスレジスタのストアやロード命令が不要になる分、コードが短く高速になってしまうというわけです。

命令の名称や雰囲気は、比較的MC6800に似ています。
以下にアキュムレータを中心とした演算命令を並べます。
表のMNE.はニーモニック、IMMはイミディエートアドレッシングモード、ABSはアブソリュートアドレッシング、ZEROはゼロページ、ACCはアキュムレータ、I.XはゼロページX間接、I.Yはゼロページ間接Y、Z.XはゼロページX、A.XはアブソリュートX、A.YはアブソリュートYの各アドレッシングモードを意味します。NZCVはそれぞれフラグ変化を示すもので、命令実行によって変化するものには*を、変化しないものには空白を書き込んであります。命令の各カラムの16進数2桁と数字1桁は、16進数2桁が機械命令のバイナリコードを、数字1桁が実行に要するクロック数を表します。ただし、インデックス修飾によってアドレス下位8 bitから上位に桁上がりがある場合は、さらにもう1クロックが必要とされます。これは、桁上がりの有無に関らず下位8 bit分のアドレス計算を行った時点で、そのアドレスから内容を読み出しておき、桁上がりがなければそのデータを使用して演算を続け、桁上がりがあった場合には先に読み取ったデータを破棄して次のクロックで正しいアドレスからデータを読み込むという、一種の投機的先読みを行っているためです。

MNE. IMM  ABS  ZERO ACC  I.X  I.Y  Z.X  A.X  A.Y  NZCV 動作
ADC  69 2 6D 4 65 3      61 6 71 5 75 4 7D 4 79 4 **** A + M + C -> A
AND  29 2 2D 4 25 3      21 6 31 5 35 4 3D 4 39 4 **   A & M -> A
BIT       2C 4 24 3                               ** * A & M
CMP  C9 2 CD 4 C5 3      C1 6 D1 5 D5 4 DD 4 D9 4 ***  A - M
EOR  49 2 4D 4 45 3      41 6 51 5 55 4 5D 4 59 4 **   A eor M -> A
LDA  A9 2 AD 4 A5 3      A1 6 B1 5 B5 4 BD 4 B9 4 **   M -> A
ORA  09 2 0D 4 05 3      01 6 11 5 15 4 1D 4 19 4 **   A | M -> A
SBC  E9 2 ED 4 E5 3      E1 6 F1 5 F5 4 FD 4 F9 4 **** A - M - not C  -> A
STA       8D 4 85 3      81 6 91 6 95 4 9D 5 99 5      A -> M
ROR       6E 6 66 5 6A 2           76 6 7E 7      ***
ASL       0E 6 06 5 0A 2           16 6 1E 7      ***
LSR       4E 6 46 5 4A 2           56 6 5E 7      ***
ROL       2E 6 26 5 2A 2           36 6 3E 7      ***
INC       EE 6 E6 5                F6 6 FE 7      **   M + 1 -> M
DEC       CE 6 C6 5                D6 6 DE 7      **   M - 1 -> M
MC6800だと、このグループに属する命令は27種類ありますが、MCS6502では15種類と、ほぼ半分になっています。アキュムレータのインクリメントやデクリメントもできず、キャリーを無視した加減算もありません。クリアやビット反転、符号反転などの命令も省略されています。BCD補正命令もありませんが、これはもちろん先に説明したようにDフラグによってADC命令とSBC命令の動作モードが変化するためです。
ADC命令は前述の通りキャリー付きの加算です。MC6800のADD命令と同じことを行いたい場合は、後述のCLC命令と組み合わせて、ADC命令に先立ってキャリーをクリアしなくてはなりません。SBC命令も同じ扱いです。ただし、SBC命令とCMP命令の減算系の命令は、前述の通りに桁借りがなかったときにキャリーが1になり、桁借りのときには0になります。したがって、MC6800のSUB命令と同じことをするには、SEC命令で先にキャリーをセットしておかなくてはなりません。CMP命令は命令実行前のキャリーフラグの状態に無関係に8 bitの減算を行いますから、SECは不要です。
AND, EOR, ORAの各命令は論理演算です。ビット反転を行うMC6800のCOM命令はEOR #$FFという形でEOR命令で実現するのが普通です。
BIT命令はAND演算でメモリの内容をテストする命令で、Zフラグは通常どおりですが、Nフラグにはメモリの内容のビット7が、Vフラグにはメモリの内容のビット6が入ります。この二つのフラグは演算結果に影響を受けません。メモリのビット7とアキュムレータのビット7をANDした結果ではないので、注意してください。この命令ではアキュムレータの内容は変化しませんから、メモリやI/Oポートのビット7やビット6がどんな値か、アキュムレータの内容とは無関係にテストすることができます。特にI/Oポートのフラグ検査が楽になります。
LDA命令とSTA命令はアキュムレータへのロードとストアで、特に別のプロセッサと異なる点はありません。
ROR、ASL, LSR, ROL命令もMC6800と同じ動作をします。この4命令はアキュムレータを操作することができますが、間接アドレッシングやアブソリュートYアドレッシングモードが使用できません。
INC命令とDEC命令はおなじみのインクリメント・デクリメントですが、アキュムレータを操作することはできません。アキュムレータをインクリメントしたい場合、CLC/ADC #1という3 Byteの命令列が必要になります。INC命令とDEC命令では、キャリーフラグに影響を与えません。また、メモリ内に確保された16 bitポインタをいっぺんにインクリメントしたりする命令はありませんし、インデックスレジスタは8 bit幅なので、MCS6502では257 Byte以上の配列を操作するのに少々長い命令列が必要となります。ポインタをインクリメントするサブルーチンを作成すればいいんですけどね。
次はインデックスレジスタ回りですが、まずはメモリ参照のあるものから。
MNE. IMM  ABS  ZERO Z.X  A.X  A.Y  Z.Y  NZCV 動作
LDX  A2 2 AE 4 A6 3           BE 4 B6 4 **   M -> X
LDY  A0 2 AC 4 A4 3 B4 4 BC 4           **   M -> Y
CPX  E0 2 EC 4 E4 3                     ***  X - M
CPY  C0 2 CC 4 C4 3                     ***  Y - M
STX       8E 4 86 3                96 4      X -> M
STY       8C 4 84 3 94 4                     Y -> M
間接系のアドレッシングモードがありません。Z.YはゼロページYアドレッシングモードを意味します。XのロードやストアにはXを使ったアドレッシングモードではなく、Yを使ったアドレッシングモードになるというのは、合理的でしょう。比較系の命令では、インデックスレジスタでアドレス修飾を行うモードが使えません。
では同じインデックス関係でもインプライドアドレッシング、つまりオペランドがニーモニック表現に内在されている命令を示します。
MNE. IMP  NZCV 動作
INX  E8 2 **   X + 1 -> X
INY  C8 2 **   Y + 1 -> Y
DEX  CA 2 **   X - 1 -> X
DEY  88 2 **   Y - 1 -> Y
TAX  AA 2 **   A -> X
TAY  A8 2 **   A -> Y
TSX  BA 2 **   S -> X
TXA  8A 2 **   X -> A
TXS  9A 2      X -> S
TYA  98 2 **   Y -> A
INX, INY, DEX, DEYはインデックスレジスタのインクリメントとデクリメント。きちんとZフラグも変化しますから、ループの終了条件にも使えます。
Tで始まるのが内部レジスタ間の転送命令です。お気付きかもしれませんが、スタックポインタSに値を直接ロードする命令がありませんから、LDX命令の後TXS命令でSに値を設定します。スタックポインタはリセット直後に一度設定したら変更する機会などほとんどありませんから、これも合理化の一環なんでしょう。また、インデックスレジスタに比較以上の複雑な操作を行う場合には、まずアキュムレータへ転送してから必要な操作を行うことになります。5 Byteおきのデータ参照など面倒そうですが、そもそもそんな参照が必要なデータ構造の場合には(C言語でいう構造体の配列なんかかな)全体が256 Byteを越えてしまうでしょうから、どうせゼロページに確保したポインタを操作しなくてはならないでしょう。
上記以外のインプライドアドレッシングの命令は、次の11種類です。
MNE. IMP  動作
CLC  18 2 0 -> C
CLD  D8 2 0 -> D
CLI  58 2 0 -> I
CLV  B8 2 0 -> V
SEC  38 2 1 -> C
SED  F8 2 1 -> D
SEI  78 2 1 -> I
PHA  48 3 A -> M(S--)
PHP  08 3 P -> M(S--)
PLA  68 4 M(++S) -> A
PLP  28 4 M(++S) -> P
CL*という命令はフラグのクリアで、SE*命令がフラグのセットです。キャリーのセットやリセットはMCS6502では加減算の際に必要となりますし、DレジスタもBCD演算と2進数演算を切り替えるためにセットやリセットが必要です。割り込みマスクのIレジスタも命令によって値を変更する必要がありますね。ところが、Vフラグに関してはクリアするCLV命令があるのにセットするSEV命令というのがありません。これは、ハードウェアで説明するSO端子を使用するために必要な命令で、クリアは必要だけれどもセットする必要が無いからです。SO端子のネガティブエッジでオーバーフローフラグが強制的にセットされます。これを検出して条件分岐できますが、次のエッジを検出するためには前もってリセットしておかなくてはなりません。そのための命令です。
PHAはアキュムレータのプッシュで、PHPはPSRのプッシュです。それぞれ対応するPLA命令とPLP命令があります。インデックスレジスタを直接スタックへ退避したり復帰したりすることはできません。後述しますが、割り込み時には少し不便です。
これから、投機的動作で有名な条件分岐命令について説明しましょう。
MNE. REL  条件
BCC  90 2 C = 0
BCS  B0 2 C = 1
BEQ  F0 2 Z = 1
BMI  30 2 N = 1
BNE  D0 2 Z = 0
BPL  10 2 N = 0
BVC  50 2 V = 0
BVS  70 2 V = 1
条件判定用のフラグがPSRに4種類ありましたから、それぞれが0か1かという条件で、計8種類の条件分岐命令があります。MCS6502では相対アドレッシング(表ではRELと表記)を使用する命令が、この条件分岐命令しかありません。無条件分岐とか、サブルーチン呼び出しに相対アドレッシングを使うことはできないのです。
さて、表では、すべての条件分岐命令のクロック数が2になっています。2クロックでは、オペコードと相対アドレス1 Byteを読み込む最低限のクロック数になってしまいます。MC6800では相対アドレッシングの分岐命令に常に4クロック消費していました。本当にMCS6502で2クロックサイクルで実行可能なのでしょうか。実はそうなっていません。条件が満たされない場合のみ、2クロックで実行するのです。MCS6502では命令を解釈して2 Byte目のオフセットを読み込みながら条件判定を行います。条件が満たされなければ、次のアドレスから直後の命令をフェッチして、それを実行してしまうから2クロックとなるのです。条件が満たされていた場合、ただちに読み込んだオフセットとプログラムカウンタPCの下位8 bitを加算して、そのアドレスから命令をフェッチしはじめます。加算に1クロック必要で、計3クロックで条件分岐を実行できます。あ、PCの下位8 bitと加算するのでしたね。仮に下位8 bitとの加算で、上位8 bitの方へ桁上がりがなければ、3クロックで計算が済んで、4クロック目でフェッチした命令をそのまま実行し続けます。桁上がりがあれば、4クロック目で次の命令ではないかと思われたアドレスからフェッチしつつ、PCの上位8 bitへの桁上がりの加算を行います。計算中に先読みした命令候補を破棄して、5クロック目で正しい分岐先アドレスから命令のフェッチを始めます。要約すれば、条件分岐しない場合には2クロック、分岐してPCの下位8 bitとだけの計算で分岐先アドレスが求まる場合には3クロック、分岐してPCの上位8 bitまで変化する場合には4クロックの時間を消費します。条件分岐は小さなループに使われる頻度が高く、その場合にはPCの上位8 bitが影響を受けることは少ないでしょうから、MC6800と比べて同程度のハードウェア規模で3/4以下の時間で条件分岐を実行する能力が得られます。合理的ですね。以前はこの処理のことをMCS6502のパイプライン処理と言っていましたが、今では投機的命令フェッチと表現した方が正確ではないかと思います。
このようなクロックサイクルの有効利用は、分岐命令の他にもインデックスレジスタを用いた修飾時にも行われています。ここでひとつだけ注意することがあります。I/Oポートの中には、単に読み出しただけでも状態が変化してしまうフラグ類を用いているものがあります。偶然、上位8 bitが変化する前のアドレスがI/Oポートだったら、そのI/Oポートを(命令の存在するアドレスだとして)誤って読み出してしまう可能性があり、検出しにくいトラブルの原因になるかもしれません。まぁ、これも簡単な解決方法があって、I/Oは特定の1ページ、つまりアドレス上位8 bitが決まっている256 Byteのアドレス空間に割り当てると定めてしまえば問題ありません。そうすれば、I/O空間からフェッチした命令で条件分岐を行わない限り、I/O空間へと上位8 bit無修正のアドレスで命令フェッチを行うことはなくなります。これは暴走しない限り、満たされます。また、インデックスレジスタ修飾時の問題も、こうしておけばI/Oポートをインデックスレジスタを用いたアドレッシングモードでアクセスしさえしなければ、影響がなくなります。常にアブソリュートアドレッシングでアクセスしてしまえば良いわけです。この条件は、よほど同一仕様のI/Oポートを並べて並列処理したい場合でなければ、悪影響はないと思われます。たとえばシリアルインターフェースを5回線備えたシステムで、デバイスドライバを用いてほとんど同じように通信したい場合には、単一のデバイスドライバを作成して、I/Oアドレスをインデックスレジスタやポインタでアクセスしたくなりますが、それをあきらめさえすれば実現可能でしょう。とはいえ、そんな多数の端末を使用する汎用コンピュータと同じような要求が存在するかを考えれば、8 bitマイクロプロセッサの応用面の制約としては厳しいものとはいえません。この先読みに伴ったトラブルの種を理解していれば、命令実行クロック数を切り詰めた分だけプログラムの高速化に役立つことでしょう。
さて、最後に残った6命令について。条件分岐以外のプログラムカウンタ関係の命令です。
MNE. IMP  ABS  IND  動作
NOP  EA 2           PC++
JMP       4C 3 6C 5 Addr -> PC
JSR       20 6      PC -> (SP--), Addr -> PC
RTI  40 6           (++SP) -> PSR, (++SP) -> PC
RTS  60 6           (++SP) -> PC
BRK  00 7           1 -> B, PSR -> (SP--), PC -> (SP++), (VEC)  -> PC
NOP命令には説明が不要でしょう。ただし、半端なコードが割り当てられています。JMP命令は16 bitアドレス空間の任意の位置にジャンプできますし、16 bitアドレスで示されたメモリから2 Byteを読み込んで、そのアドレスにジャンプする間接アドレッシングモード(IND)も使用できます。MCS6502では無条件のジャンプはJMP命令しか存在しません。相対アドレッシングの無条件分岐はありません。そこは少々不便かもしれませんが、ADD命令すら省略する設計ですから、しょうがないでしょうね。JSR命令もおきまりのサブルーチンジャンプで、リターンアドレスをスタック上にプッシュしてからジャンプするものです。アブソリュートアドレッシングしか使えません。JSR命令と対になって使用されるリターン命令がRTS (Return from Subroutine)命令です。
RTI命令は割り込みからの復帰を行うための命令です。割り込みメカニズムは後でまとめますが、MC6800と異なり、プログラムカウンタとフラグ類のPSRしかスタックに自動で退避させません。アキュムレータやインデックスレジスタの退避はプログラマの責任です。そのため、RTI命令でもPSRとPCを復元するだけです。BRK命令はMC6800のSWI命令に相当するもので、プログラムカウンタとPSRを退避して、特定のベクトルアドレスから割り込みハンドラの開始アドレスを読み込んで、そこに分岐します。詳細は割り込みの項で説明することにします。

ソフトウェアの観点からの類似性ばかりでなく、ハードウェア自身もMC6800の類似が指摘されています。というより、ハードウェアの方の類似性の方が高いといえるでしょう。ほぼ同じタイミングの同期バスを採用していて、メモリや入出力デバイスの設計を簡単に流用できたりしますが、その上にピン配置までMC6800と似ています。
具体的にはピン配置はこうなっています。MC6800と比べてみてください。

GND    1      40 RES*
RDY    2      39 CK2
CK1    3      38 SO
IRQ*   4      37 CK0
NC     5      36 NC
NMI*   6      35 NC
SYNC   7      34 R/W*
VCC    8      33 DB0
AB0    9      32 DB1
AB1   10      31 DB2
AB2   11      30 DB3
AB3   12      29 DB4
AB4   13      28 DB5
AB5   14      27 DB6
AB6   15      26 DB7
AB7   16      25 AB15
AB8   17      24 AB14
AB9   18      23 AB13
AB10  19      22 AB12
AB11  20      21 GND
Motorola社との表記の違いでVSSがGNDになっていたり、アドレスやデータのA0やD7がAB0やDB7なんてふうになっていますが、アドレスバスとデータバス、電源、割り込み信号や制御信号のかなりの部分が同一であることがわかります。違いがあるのは、クロック信号の発振回路が内蔵になっているためクロック関係の信号と、DMAの仕組みがなくなっている(RDY信号で外部回路付加によって実現可能)部分、SO入力の部分だけです。SO入力信号はSet Overflow Flagという意味で、立ち下がりでオーバーフローフラグがセットされます。1 bitの入力ポートとして使えますが、まぁ良い子は使わずにプルアップしておくでしょうねぇ。SYNCは8080やZ80 CPUのM1と同じ意味の信号で、命令のオペコードをフェッチしていることを表します。
VMA信号が存在しないのもMC6800と比べると変わっているところでしょうか。他のプロセッサに慣れ親しんだ方からすると相当に気持ち悪いでしょうが、MCS6502ではクロックに同期して、必ずメモリアクセスを行います。仮に命令を内部で解釈実行している時間ですら、メモリ側から見ればクロックに同期してまったく同じ読み書きの信号が出力され続けています。無効なメモリアクセスサイクルであることを示す信号が存在しないのですから、区別が付きません。無効な書き込みが行われるのは、INCやROR命令などのリードモデファイ形式、つまりあるメモリアドレスからデータを読み込んで加工を行い、同一アドレスに書き戻すような命令で、正しく加工されたデータを書き込む直前に、同じアドレスにでたらめなデータを書き込む場合にしか、行われません。対象がメモリなら最終的に書き込まれた値しか残りませんから影響はないというわけです。他の不要なメモリアクセスは読み込みばかりで、命令実行中にプログラムカウンタやインデックスレジスタ操作が不完全なままのアドレスから読み出すものです。命令の解説に少々注意した通りです。
相手がメモリならどこを何回読み込もうと問題はありませんが、I/Oポートでアクセスに応じてフラグが変化するようなものに不要なアクセスが行われると、困ってしまいます。対策としては、I/Oポートを特定のページに集めて、I/Oと同じページにメモリを配置しないようにハードウェア設計しておき、I/Oポートへのアクセスにはアブソリュートアドレッシングを必ず用いることとリードモデファイ形式の命令を用いてI/O操作を行わないというソフトウェア面での注意を徹底することが必要です。

割り込み関係の信号はMC6800と同じようにRES*, NMI*, IRQ*の3本があり、ソフトウェア割り込みを起こすMC6800のSWI命令に相当するBRK命令も備えています。
これらの要因で割り込みが発生すると、割り込みベクタを参照します。ベクタは要因ごとに次のようなアドレスに格納されることになっています。
割り込み要因 ベクタアドレス
IRQ, BRK FFFE, FFFF
RES FFFC, FFFD
NMI FFFA, FFFB

ベクタはMCS6502のアドレスの最上位に位置しますが、細かい位置や順序はMCS6502とMC6800とで異なります。また、16 bitデータの格納順序の規則も異なっていますから、注意してください。MCS6502では下位バイトが常に小さなアドレスに配置されます。
RES*信号が有効にされてから規定の時間が経過後にHに戻されると、$FFFCから2 Byteのデータを読み込んでPCにロードします。そして、そのPCを用いて命令をフェッチしてプログラムの実行を始めます。IフラグはセットされてIRQ入力が禁止されますが、他のレジスタ類の初期化は行われません。値が不定の中にはDフラグもあって、リセット後に命令で初期設定しない限りADC命令やSBC命令を正しく実行できませんから、注意してください。
NMI*はエッジトリガの割り込み信号で、HからLへの遷移によって割り込みが発生します。Lレベルが長時間続いても、CPUの実行状態になんの影響も与えません。NMIが発生すると、Bフラグを0にしてPCとPSRをスタックにつみ、Iフラグを1にセットしてIRQを禁止して$FFFAから2 Byteのデータを取り出してPCに格納し、割り込みサービスルーチンの実行を開始します。アキュムレータやインデックスレジスタの退避は行われず、プログラマが保存や復帰の管理を行う必要があります。
IRQ*はレベルトリガの割り込み入力信号で、Iフラグが0のときにIRQ*がLレベルになっていれば割り込みが発生します。Bフラグを0にしてPCとPSRをスタックにつみ、Iフラグを1にセットしてそれ以上のIRQ割り込みの発生を禁止してから$FFFEからのデータをPCに格納して割り込みサービスルーチンに分岐します。
BRK命令はソフトウェア割り込みですが、Iフラグの状態に影響されないことと、PSRをスタックにつむ前にBフラグを1にセットすることだけがIRQと異なります。それ以外はまったくIRQと同じシーケンスで実行されます。割り込みベクタまで共通なので、IRQとBRK命令による割り込みは同一の割り込みサービスルーチンが呼び出されることになります。IRQとBRK命令の、どちらが原因で割り込みサービスルーチンが呼ばれたか、PSRのBフラグを調べて判定しなくてはなりません。直接PSRのBフラグによって条件分岐を行ったり、PSRのビットの状態を調べたりする命令は存在せず、アキュムレータにPSRの内容を転送してから判断しなければなりませんが、その転送命令もないので、PHP命令とPLA命令を続けて実行したりしてからAND命令でBフラグだけ残すようにして、やっと条件分岐することができるわけで、ちょっと面倒ですけどね。BRK命令については、さらに注意する点があります。BRK命令発生時にスタックにプッシュされるPCの値は、BRK命令の次のアドレスではなくて、さらにもうひとつ次のアドレスです。BRK命令実行後に割り込みサービスルーチンがRTI命令を実行して元の命令シーケンスを実行しはじめる際、BRK命令の次の1 Byteは無視されてしまいますから、気をつけてください。BRK命令の直後にはNOP命令でも挿入しておかないと、思わぬトラブルのもとになります。
RTI命令はスタックからPSRとPCを取り出して割り込み発生前の命令シーケンスの実行を継続する命令です。PLP命令とRTS命令でも同じようなことができそうですが、MCS6502ではJSR命令とRTS命令に変なクセというか、ある種の実装上の都合というか、ちょっと事情があって、PLP命令とRTS命令でRTI命令を置き換えることができないようになっています。3 ByteのJSR命令でスタックにプッシュされるアドレスは、JSR命令のアドレスに3を加えたものではなく、2を加えたアドレスになっています。RTS命令では、PCにスタックから取り出した値をロードしてから、余分に1回だけインクリメントして、正しいアドレスから命令が読み込めるようにしています。RTI命令ではその余分な1回のインクリメントが行われません。変な仕様ですが、JSR命令とRTS命令、割り込み類とRTI命令を、必ず対にして使用すれば不整合が起こり得ませんから問題ないといえば問題ないのですけどね。

MCS6502はRockwellによってCMOS化されましたが、同時に命令を追加し、ハードウェア的な問題点にも修正が入っています。

R65C02
RockwellのR65C02の高速版。

まずは追加された命令です。既存の命令にアドレッシングモードが追加されているものと、新設された命令があります。アキュムレータ演算には、ゼロページ間接アドレッシングが使えるようになっています。また、BIT命令には待望のイミディエートアドレッシングが追加されています。INCとDECにはアキュムレータを対象とすることができるようになりました。以下では、追加した命令のオペコードとサイクル数の間にアスタリスク*を入れてあります。

MNE. IMM  ABS  ZERO ACC  I.X  I.Y  Z.X  A.X  A.Y  ZIND NZCV 動作
ADC  69 2 6D 4 65 3      61 6 71 5 75 4 7D 4 79 4 72*5 **** A + M +  C -> A
AND  29 2 2D 4 25 3      21 6 31 5 35 4 3D 4 39 4 32*5 **   A & M -> A
BIT  89*2 2C 4 24 3                34*4 3C*4           ** * A & M
CMP  C9 2 CD 4 C5 3      C1 6 D1 5 D5 4 DD 4 D9 4 D2*5 ***  A - M
EOR  49 2 4D 4 45 3      41 6 51 5 55 4 5D 4 59 4 52*5 **   A eor M  -> A
LDA  A9 2 AD 4 A5 3      A1 6 B1 5 B5 4 BD 4 B9 4 B2*5 **   M -> A
ORA  09 2 0D 4 05 3      01 6 11 5 15 4 1D 4 19 4 12*5 **   A | M -> A
SBC  E9 2 ED 4 E5 3      E1 6 F1 5 F5 4 FD 4 F9 4 F2*5 **** A - M -  not C -> A
STA       8D 4 85 3      81 6 91 6 95 4 9D 5 99 5 92*5      A -> M
INC       EE 6 E6 5 1A*2                F6 6 FE 7      **   M + 1 -> M
DEC       CE 6 C6 5 3A*2                D6 6 DE 7      **   M - 1 -> M
ZINDと表記したゼロページ間接アドレッシングモードでは2 Byte命令となり、2 Byte目の8 bit値で指示されたゼロページ内のメモリから16 bitデータを取り出して、その値を実際のオペランドアドレスとして解釈します。
分岐命令にも重要な追加があります。
MNE. REL  ABS  IND  IA.X 動作
BRA  80*2                Addr -> PC
JMP       4C 3 6C 5 7C*6 Addr -> PC
無条件相対分岐命令であるBRA命令が新設されています。以前になかったのが不思議なくらいでした。また、JMP命令には特別なアドレッシングモードが追加されています。表ではIA.Xと表記していますが、アブソリュートX間接とでも表現すべきモードで、まずはアブソリュートXアドレッシングと同じようにアドレス計算を行い、そこから16 bitのデータを読み出して、その値をPCへとロードします。ジャンプテーブルを実装するのに便利なモードです。
新設された命令のうち、インプライドアドレッシングモードのものを次に示します。
MNE. IMP  動作
PHX  DA 3 X -> M(S--)
PHY  5A 3 Y -> M(S--)
PLX  FA 4 M(++S) -> X
PLY  7A 4 M(++S) -> Y
インデックスレジスタ類のプッシュプルでした。これでサブルーチン内とか割り込みサービスルーチン内でのインデックスレジスタ退避が楽になります。
メモリアクセスする新設命令もいくつかありますが、ビット操作をのぞいて、メモリのゼロクリアと論理演算テスト命令を先に示します。
MNE. ABS  ZERO Z.X  A.X  NZCV 動作
STZ  9C 4 64 3 74 4 9E 5      0 -> M
TRB  1C 6 14 5            *   not A & M -> M
TSB  0C 6 04 5            *   A | M -> M
STZ命令はSTore Zeroの略ですね。TRB命令はTest and Reset Bits、TSB命令はTest and Set Bitsの略になっていて、アキュムレータで1が入っているビットに対応するメモリの複数のビットを操作します。
残るはビット操作命令です。ゼロページ上の特定のビットを操作します。アセンブラニーモニックでは、BBR0とかBBR3のように操作対象のビット位置をニーモニックの4文字目に付け加えることになっていますが、ここでは表の都合で、BBR[0-7]というように表記します。同様にオペコードも[0-7]Fという形で表記しますが、これはBBR0なら0F、BBR1なら1Fという意味です。
MNEMONIC  ZERO      動作
BBR[0-7] [0-7]F 4  if M<b> = 0, branch
BBS[0-7] [8-F]F 4  if M<b> = 1, branch
RMB[0-7] [0-7]7 5  0 -> M<b>
SMB[0-7] [8-F]F 5  1 -> M<b>
これらの命令はフラグに影響を与えません。BBRとBBS命令はBranch if Bit ResetとBranch if Bit Setという意味で、オペランドにゼロページと分岐先のラベルを取り、条件が満たされたときに相対アドレッシングで分岐します。RMB命令とSMB命令はReset Memory BitとSet Memory Bitの略で、特定のビットを1にしたり0にしたりするものです。
以上で65C02で追加された命令は終わりです。他に、問題点の修正が以下のように行われているようです。

  1. リセット時と割り込み発生時にDフラグをクリアして状態を確定させた。
  2. 間接ジャンプ命令でページ境界をまたぐアドレスを参照するときにアドレス上位バイトに桁上がりを行わずに誤ったアドレスからフェッチしていたのを修正した。
  3. BRK命令実行中に割り込みが発生した場合にBRK命令が無視されていたのをBRK命令実行後に割り込み処理を行うように修正した。
  4. アドレス計算途中のアドレスから無効な読み出しを行っていたのを、命令の最終アドレスを出力して命令を再読み込みすることによってI/Oポートなどをアクセスしないようにした。
  5. リードモデファイ形式の命令のとき、データを1回読み込んでから書き込みを2回行っていたのを読み込み2回に書き込み1回となるようにタイミングを修正した。
などの修正が行われています。

ところで6502アーキテクチャは今でも受け継がれている流れがあります。たとえば、これ。
M37450
三菱のM37450の開発用窓付き8 KByte UVEPROM内蔵版のM37450E4SSと、少量生産用の窓なし8 KByte PROM版のM37450E4SP。

組み込み制御用の1チップマイクロコンピュータですが、6502にビット操作命令と乗算命令などを加えて強化したCPUアーキテクチャです。こちらの方が65C02より強化された命令セットになっています。アドレッシングモードも細かく分ければ17種類と増加しています。残念ながら、追加された命令パターンは65C02とは互換性がありません。同じようなビットのセット・リセット命令のコードも違えば、INC A, DEC Aのコードもしっかり異なっています。ちなみに、ほかに256 ByteのRWMにシリアルやパラレルポート、A/DやD/A、タイマに他のマイクロプロセッサがアクセスできるスレーブポートなど、至れり尽せりの入出力機能がセットになって、(少し古めのこの石でも)2.5 MHzクロックで動作し、わずかな消費電力と、なかなか使い出のあるチップでした。なお、これはすでに製造中止。ROMがぐんと増えて高速になったものなら手に入るかもしれません。でも、最初に紫外線消去可能なパッケージのを見たときには、ダイの大きさに驚いたなぁ。これで採算あってるのかな。

Return to IC Collection