たまりば

パソコン・インターネット パソコン・インターネット三鷹市 三鷹市

ファミコン命令表
2011年05月08日 01:36

最近ファミコンプログラミングにはまっているので、ちょっとファミコンのCPUである6502の命令セットについてまとめてみる。
いろんなところに命令表はあるけど結構大事な情報が載ってなかったりするので、探すのに苦労した情報を重点的に。あと自分なりに解説も付けておく。

▶レジスタ

A アキュムレータ
基本的に演算結果はここに入る。
X インデックスレジスタX
Y インデックスレジスタY
汎用レジスタ。メモリの連続アクセスのために使いやすい設計。
S スタックポインタ
これが1バイトなのでスタックの場所が$0100~$01FFの256バイトに制限されている。
TXS/TSX命令を介して読み書きできる。
P プロセッサステータスレジスタ
いわゆるフラグレジスタ。素直にFって名前にしといたほうが分かりやすいと思うんだが。
重要なビットはSE*/CL*系命令で1つづつ制御できる。PHP/PLPでスタックを介した読み書きも可能。
PC プログラムカウンタ
普通のプログラムカウンタ。6502唯一の16bitレジスタ。
読み書きはできずジャンプ系命令で制御するのみ。

▶フラグ

Pレジスタの中身。上位bitから順に、
N Negative : 結果が負の時セット。
V oVerflow : 演算でオーバーフローが発生した時セット。
(R Reserved : つねにセット)
B Break : BRK発生時はセット、IRQ発生時はクリア。
D Decimal : BCD(Binary Coded Decimal)モードを表すがファミコンではこの機能は削除されている
I Interrupt disable : セットされていればIRQ禁止。
Z Zero : 結果がゼロの時セット。
C Carry : 加減算ではキャリーが発生した/ボローが発生しなかったときセット。シフトではみ出したビットが入る。

※「セット」とは1を書き込むことであり、「クリア」(「リセット」とも)は0を書き込むことである。なお冗長なので書いていないが、セットされなければ何もしないのではなくクリアされる。

▶アドレッシングモード

アドレッシングモードに応じて命令のバイト数が決まる。[~]の形で示す。
・Implied 黙示
演算対象は黙示的に決まっているため、オペランドは無い。[1バイト]
・Accumulator アキュムレータ
演算対象はアキュムレータ。Impliedの一種だが、アセンブラ上の表記ではオペランドとして「A」をつける。シフト系命令のみにあるモード。
機械語としてはオペランドは無いので、[1バイト]。
・Immediate 即値
即値を演算対象にとる。即値は8bitCPUなので8bit。機械語とオペランド1バイトづつで計[2バイト]。
・Absolute 絶対
メモリのアドレスをオペランドにとる。6502のアドレスは16bitなので、[3バイト]。
・Absolute,X
・Absolute,Y
オペランドにX(Y)レジスタの値を加算した値をメモリのアドレスとして使う。[3バイト]
Absolute,XとAbsolute,Yは機能的には同じ。Absolute,Yを使える命令は少ない。
・Relative 相対
Absoluteではオペランドはメモリの絶対アドレスを表すが、こちらは現在のPCの位置からの相対アドレス。範囲は1バイトで表せる-128~+127の範囲。[2バイト]
このモードがあるのはブランチ系命令のみ。
・Zeropage
・Zeropage,X
・Zeropage,Y
6502特有のモード。Absolute系と機能は同じだが、使えるメモリが0000~00FF番地(ゼロページ)に限られている。(Zeropage,X(Y)で加算の結果繰り上がっても無視される)
そのためオペランドが1バイトで済み、[2バイト]。実行時間も大抵の命令で1クロック少ない。
Zeropage,YはXレジスタを演算対象に使う命令(LDX,STX)にのみZeropage,Xの代わりに存在するモード。
・(Indirect) 間接
オペランドで示された番地(からの2バイト、リトルエンディアン)のメモリで示された番地を使う。[3バイト]
このモードがあるのはJMP命令のみ。
なお、オペランドで示された番地の下位バイトがFFだった場合、上位バイトの番地を求める際に繰り上がらず、結果として256バイト前のメモリを参照するバグが存在する。
・(Indirect,X) indexed indirect
オペランドにXを加えた番地のメモリで示された番地を使う。
注意点として、オペランドは1バイトで、つまり最初に指すメモリはゼロページに限られる(加算の繰上りも無視される)。[2バイト]
・(Indirect),Y indirect index
上との違いに注意。
こちらは、オペランドで示された番地のメモリで示された値にYを足した番地を使う。括弧の付き方の違いは間接の取り方の違いを反映している。[2バイト]

(Indirect,X)と(Indirect),Yの違いはXやYを変えながら連続してアクセスすることを考えると分かりやすい。
(Indirect,X)と(Indirect),Yの違い

▶命令

・凡例
LDALoaD to AN,Z
ニモニックニモニックの由来(予想)命令実行で書き込まれるフラグ

◆転送命令
メモリやレジスタの間でデータをコピーする命令。
6502ではメモリ→レジスタはLD*、レジスタ→メモリはST*、レジスタ間はT**と単語が使い分けられている。

・ロード
オペランドで指定されたメモリの内容やオペランドの即値をレジスタにロードする。
LDA: LoaD to A: N,Z
A(アキュムレータ)にロードする。フラグは、ロードした値が負ならN、ゼロならZがセットされる。
LDX: LoaD to X: N,Z
LDY: LoaD to Y: N,Z
それぞれXレジスタ・Yレジスタにロードする。フラグはLDAと同様。

・ストア
レジスタから、オペランドで指定された場所にデータをストアする。
STA: STore from A: なし
Aからストアする。
STX: STore from X: なし
STY: STore from Y: なし
説明不用。

・トランスファー(だと思う。Tで始まってそれっぽい単語が他に思いつかない。)
レジスタ間転送。
TAX: Transfer A to X: N,Z
TXA: Transfer X to A: N,Z
TAY: Transfer A to Y: N,Z
TYA: Transfer Y to A: N,Z
TSX: Transfer S to X: N,Z
TXS: Transfer X to S: なし
TXSだけフラグ変化なしとか、SはXにしか入れられないとか罠だと思う。
転送した値に応じてNとZをセット。

・スタック操作
Pushはいいのだが、Pullというのは普通POPと呼ぶと思う。
スタックに入れられるのはアキュムレーターとPレジスタだけ。
PHA: Push A: なし
アキュムレータの内容をスタックにプッシュする。
すなわち、スタックポインタの示すメモリ番地にアキュムレータの内容を書き込み、スタックポインタを1つ進める。進めるというか、スタックはマイナス方向に伸びていくので、デクリメントすることになる。
PHP: Push P: なし
Pを(以下略)
PLA: PuLl to A: N,Z
スタックからアキュムレータにポップする。その値に応じてNとZをセット。
すなわち、スタックポインタを1インクリメントし、スタックポインタの示すメモリ番地の内容をアキュムレータに書き込む。
PLP: PuLl to P: N,Z,C,I,D,V
スタックからPにポップする。P、すなわちフラグレジスタに書き込むということで当然全フラグが影響される…かと思いきやBのみは例外です。

◆演算
値に変化がある命令。

・加減算
加減算は常にキャリーを計算に入れる。ちゃんとニモニックにも「C」がついている。
ADC: ADd with Carry: N,Z,C,V
オペランドをアキュムレータに加算。キャリーがあればさらに1加算。
結果が負ならN、ゼロならZ、演算でキャリーが出ればC、演算で正/負が変化したらVがセット。
SBC: SuBtract with Carry: N,Z,C,V
注意点1: 減算の方向はA=A-オペランド。
注意点2: 6502のキャリーフラグの立ち方は減算の時は加算の逆。ボローがあればキャリーフラグは立たず、ボローのないときにフラグが立つ。
逆といったが、キャリーフラグがあれば1余計に加算するという意味では働きは同じである。
PICもそうだったが、この仕組みの方が回路が簡単(*あとで書く)なので、低コストなCPUでは好まれるのだろう。
他のフラグはADCと同様。

・ビット演算
AND: AND: N,Z
EOR: Exclusive OR: N,Z
ORA: OR A: N,Z
ごく普通のビット演算。アキュムレータとオペランドで演算した結果をアキュムレータに入れる。
ただ、ニモニックがちょっと妙。
EORって略し方は見たことがない。普通XORだよなあ。
ORAは3文字統一で一番無理が出ているところ。まあこの命令のおかげでビット演算はAに対してしかできないのを覚えられるが。余談だがPICのOR演算はInclusive ORの略でIOR**である。
あとNOTは無い。
フラグは結果によってNとZ。

・インクリメント/デクリメント
INC: INCrement: N,Z
DEC: DECrement: N,Z
INX: INcrement X: N,Z
INY: INcrement Y: N,Z
DEX: DEcrement X: N,Z
DEY: DEcrement Y: N,Z
普通のインクリメント・デクリメント。
INC/DECはオペランドのメモリに対しての演算。
XとYに対しては別命令になっているのと、アキュムレーターに対しては演算できないことに注意。
フラグは結果によってNとZ。

・シフト
ASL: Arithmatic Shift Left: N,Z,C
LSR: Logical Shift Right: N,Z,C
えーと、左シフトに算術も論理もないと思います。
6502のシフトは論理シフト、ってことでいいと思う。

ROL: ROtate Left: N,Z,C
ROR: ROtate Right: N,Z,C
シフトとローテートが分かれているのはちょっと嬉しい。
ビットがシフトして空いたところに0が入ってくるのがシフト、Cのビットが入ってくるのがローテート。シフトしてはみ出したビットはどちらもCに入る。
ちなみにキャリーを使わない8ビットのローテートはない。
フラグは結果によってNとZ、あふれたビットがCに入る。

◆テスト
値に変化がなくフラグだけ操作する命令。

CMP: CoMPare: N,Z,C
CPX: ComPare X: N,Z,C
CPY: ComPare Y: N,Z,C
コンペア。比較。A・X・Yとオペランドの大小を比較する。
言い方を変えると、A・X・Yからオペランドを引いてみて結果は捨ててフラグだけ立てる。フラグの立ち方はVが変化しない以外はSBCと同じ。
CPYがcopyにしか見えません。

BIT: BIT test: N,Z,V
特殊なフラグの立ち方をする命令。
Nフラグがオペランドの7ビット目、Vフラグがオペランドの6ビット目の値になり、オペランドとアキュムレーターのANDの結果がゼロならZフラグが立つ。
最初意味不明でした。
この命令が分かりにくい原因は2つの操作を同時にやっているからだと思います。
分けて考えれば、
1. A and オペランドを計算し、結果は捨ててZフラグを立てる
使い方は例えば、Aに#%00000001を入れれば、オペランドのメモリの最下位ビットがゼロかどうかが分かる。
2. 6ビット目と7ビット目はフラグにそのまま入れる
Aを使わなくとも6、7ビット目は判断できてお得。
ということです。

◆ジャンプ
プログラムの実行位置が変わる命令。

JMP: JuMP: なし
無条件ジャンプ。アドレッシングモード(Indirect)はこの命令だけにあるレアなモード。
JSR: Jump to Sub Routine: なし
サブルーチンコール。プログラムカウンタをスタックに積み(上位→下位の順)、ジャンプ。

RTS: ReTurn from Sub routine: なし
サブルーチンから返る。すなわちスタックから(下位→上位の順に)取得したプログラムカウンタ+1の場所にジャンプ。
RTI: ReTurn from Interrupt: N,Z,C,I,D,V
割り込みから返る。割り込みの時にはPレジスタもスタックに乗っているので、P→PC下位→PC上位の順にスタックから降ろす。

BCC: Branch if C is Clear: なし
BCS: Branch if C is Set: なし
BEQ: Branch if EQual: なし
BNE: Branch if Not Equal: なし
BMI: Branch if MInus: なし
BPL: Branch if PLus: なし
BVC: Branch if V is Clear: なし
BVS: Branch if V is Set: なし
無条件なのがジャンプで条件付きがブランチ。
もう一つJMPとの大きな違いとして、ブランチ命令は全て共通でアドレッシングモードがrelative(相対)のみである。
これにより現在の位置から-128~+127の場所にしか飛べない制限がある。
命令名はBCC,BCS,BVC,BVSはそのまんま。
BPLとPMIはN(ネガティブ)フラグの有無=負/正に対応。
BEQとBNEはZ(ゼロ)フラグの有無に対応している。これは減算して結果がゼロならEqualという意味での命名。

BRK: BReaK: I,B
ソフトウェア割り込みを発生させる。
具体的にはPC上位→PC下位→Pレジスタの順にスタックに積み$FFFF:$FFFEの示す場所にジャンプ。

◆フラグ操作
フラグをセットしたりクリアしたり。

CL* (C,D,I,V): CLear *: 各
SE* (C,D,I): SEt *: 各
CLVはあってSEVはない。まあオーバーフローフラグは本当にオーバーフローを知らせる以外の役割がないからあえてセットする必要はないんだろう。
対してキャリーフラグは加減算の時いじったりシフト系で使われるから操作出来なきゃいけない。
SEDとCLDは、D(デシマル)フラグに対応する機能がCPU自体から削除されているので使えない。よって最初にCLDでクリアした後は使う機会は無い。

◆NOP
NOPはNOPである。

NOP: No OPeration: なし
NOPである。

▶おまけ

命令ごとの使用可能なアドレッシングモードと必要クロック数を表にまとめてみた。
転送LDA (nz)Im 2Zero 3ZeroX 4Abs 4AbsX, Y 4+(IndX) 6(Ind)Y 5+
LDX, LDY (nz)Im 2Zero 3ZeroX 4Abs 4AbsX 4+
STAZero 3ZeroX 4Abs 4AbsX, Y 5(IndX) 6(Ind)Y 6
STX, STYZero 3ZeroX 4Abs 4
TAX, TXA, TAY, TYA, TSX (nz), TXS 2
PHA, PHP 3
PLA(nz), PLP(全) 4
演算ADC, SBC (nzcv), AND, EOR, ORA (nz)Im 2Zero 3ZeroX 4Abs 4AbsX, Y 4+(IndX) 6(Ind)Y 5+
INC, DEC (nz)Zero 5ZeroX 6Abs 6AbsX 7
INX, INY, DEX, DEY (nz) 2
ASL, LSR, ROL, ROR (nzc)Acc 2Zero 5ZeroX 6Abs 6AbsX 7
テストBIT (nzv)Zero 3Abs 4
CMP (nzc)Im 2Zero 3ZeroX 4Abs 4AbsX, Y 4+(IndX) 6(Ind)Y 5+
CPX, CPY (nzc)Im 2Zero 3Abs 4
ジャンプJMP(Ind) 5Abs 3
JSRAbs 6
RTI(全), RTS 6
BCC, BCS, BEQ, BNE, BMI, BPL, BVC, BVSRel 2, 3+
BRK (ib) 7
フラグCLC, CLD, CLI, CLV, SEC, SED, SEI (各) 2
NOP 2
凡例:
・アドレッシングモード
Im: Immediate
Zero: Zeropage
ZeroX: Zeropage,X (LDX,STXはZeropage,Y)
Abs: Absolute
AbsX: Absolute,X (LDXはAbsolute,Y)
AbsX,Y: Absolute,XとAbsolute,Y両方使用可能
(Ind): (Indirect)
(IndX): (Indirect,X)
(Ind)Y: (Indirect),Y
Acc: Accumulator
Rel: Relative
表記無し: Implied
・クロック数
「+」付きはアドレス計算時に上位バイトに繰り上がりが発生する場合1サイクル追加。
ブランチ系はブランチしない時2、ブランチするとき3+。
・書き込まれるフラグ
命令をまとめた後に括弧つきで表記。
「ADC, SBC (nzcv), AND, EOR, ORA (nz)」なら、ADCとSBCがN,Z,C,V。AND、EOR、ORAがN,Z。
(全)=N,Z,C,I,D,V。Bは除く。

▶さらにおまけ

それを携帯待ち受けサイズの画像にしてみた。
ファミコン命令表
見方は基本的に上の表と同じ。AbsX,Y欄の色付きはXY両方、色なしはXのみを表す。SBCの引く向きがよく分からなくなるので余白に書いておいた。他にも覚えられないことがあれば余白に書くとよいだろう。


  • Post time : 2011年05月08日 01:36│Comments(0)
    URL欄を実験的に消してる間に廃止されてしまいました。まあいいか。
     
    <ご注意>
    書き込まれた内容は公開され、ブログの持ち主だけが削除できます。
    削除
    ファミコン命令表
      コメント(0)