CPU
Zilog Z80 のアーキテクチャとISAについて
ZilogのZ80といえば、8bitCPUでは日本一有名・・なのではないだろうか。
Z80は8080のISAを元に、CB/EDプリフィックスで命令を追加し、DD/FDプリフィックスでインデックスレジスタ関連の命令を追加、さらにDD-CBやFD-CBでエスケープして追加命令のインデックスレジスタ版を追加、さらに難解だった(らしい)Intelのニモニックを一掃して初心者に覚えやすい(らしい)ニモニックに変更している。
追加された命令は、そのほとんどが8080で「こんなときに、こんな命令があったらなぁ」というものであり、とても便利な8bitCPUになっている。
その反面、8080が元来持っていた哲学に反した命令拡張、ニモニックが変更されたことでアドレッシングモードがはっきりしない、プリフィックスによる命令拡張といった点で、明らかな“継ぎ足し感”を醸し出している。
ここではZ80のアーキテクチャとISAを、8080と比較しながら書いてみたいと思う。
元になった8080のアーキテクチャとISAについてはこちらに記してある。
8080に加えて、いくつかの命令とインデックスレジスタ・裏レジスタが追加されている。
Z80はアキュムレータマシンなので、8bit演算はすべてアキュムレータ(A)と何かの間で行われるのだが、16bit演算がかなり強化されていて、HLレジスタを16bitのアキュムレータとして使用できるようになっている上、ADD命令だけはIX/IYレジスタまでアキュムレータとして使用できるようになっている。
一応、Z80のアドレッシングモードを一覧しておく。
8080のアドレッシングモードは非常に少なかったが、Z80ではそれにインデックスレジスタを使ったアドレッシングが追加された程度であり、やはりアドレッシングモードはとても少ないCPUといえる。
1. レジスタ (レジスタ直接指定。8bitと16bitがある。(HL)もこれに含む)
2. イミディエイト (即値。8bitと16bitがある)
3. レジスタインダイレクト (レジスタペアBCとDEによる間接指定。HLはこれに含まない。演算命令では使えない)
4. エクステンド (2バイトでのメモリアドレス直接指定。指定先のサイズは8bitと16bitがある。演算命令では使えない)
5. インデックス(インデックスレジスタIX/IYと8bitのディスプレースメントによる間接指定)
ニモニックは2~4文字をとり、オペランドは0~2を記述する点は8080と同じだが、演算命令のオペランドが1つから2つに変更されているものがあるほか、ニモニックやレジスタの表記法が一新されている。
8080のニモニックには、以下のような特徴があった。
1. 第1オペランドが1種類に限られるものは、ニモニック内に第1オペランドも記述する。(“ANA”や“LHLD”など)
2. 第2オペランドも限られるものは、オペランドのみのニモニックとなる。(“PCHL”や“SPHL”など)
3. アドレッシングモードもニモニック内に記述する。(“INX”、“LHLD”、“ANI”など)
しかし、Z80のニモニックではこの特徴を排除し、以下のような記述になった。
1. ニモニック内にオペランドやアドレッシングモードを記述しない。
2. レジスタ/イミディエイト/エクステンドアドレッシングの8bitと16bitは同じニモニックに統合する。
3. Mレジスタ表記を排除し、“(HL)”とする。
4. 第1オペランドが1種類であることが自明なものは第1オペランドを記述しない。(“AND r”や“SUB r”など)
5. 第2オペランドも限られるものは、より理解のしやすい別の命令に置き換える。(“JP (HL)”や“LD SP,HL”など)
このため本来異なる命令やアドレッシングであるものが、あたかも「同じ命令・アドレッシング」であるかのような錯覚を生んでしまうアセンブリとなった。
元来、8080では「HLレジスタが指し示す先のメモリ内容は“Mレジスタ”である」という哲学があり、「メモリと汎用レジスタに扱いの差異がない(同じアドレッシングモードが使える)」という特徴があった。
しかしZ80のニモニックからは、このMレジスタが消えてしまっている。
すなわち、Mレジスタはご存じの「(HL)」という表記に取って代わった。(しかし“JP (HL)”に関してだけは、Mレジスタの内容へジャンプという意味ではなく、HLの値そのものを示す)
表記が変われど8080の完全上位互換CPUなので、この哲学は変わらないし、オペコードもそのままだ。
ld a,(hl) ;7e(01 111 110)
ld a,(bc) ;0a(00 001 010)
ld a,(de) ;1a(00 011 010)
ld a,(0a000h) ;3a 00 a0(00 111 010 / 00000000 10100000)
ld (hl),a ;77(01 110 111)
ld (bc),a ;02(00 000 010)
ld (de),a ;12(00 010 010)
ld (0a000h),a ;32 00 a0(00 110 010 / 00000000 10100000)
このように、ニモニックからは同じLD命令に見えるがオペコードは(HL)だけが別物になっている。(bit7とbit6に注目)
本来(HL)は「レジスタアドレッシング」なのだが、ニモニック表記上からは「レジスタインダイレクトアドレッシング」に見えてしまって、「覚えやすいのだが、アドレッシングモードが分かりにくい」ニモニックとなっている。
add a,e ;83(10 000 011)
add hl,de ;19(00 011 001)
8080では別の命令だった8bitの加算と16bitの加算は、Z80では同じADD命令表記となっているが、やはりオペコードは別物であり、元々異なる命令だったということが分かる。
このような「ニモニック表記の単純化」が生んだ弊害として、
ld d,(bc)
ld (de),h
ld l,(0c000h)
ld sp,bc
ld hl,de
add a,(bc)
add a,(de)
jp (de)
といったような、いかにも「書けそうで書けないコード」が多数存在し、そのため命令とオペランドの組み合わせに存在するものとしないものは丸暗記するしかないという羽目に陥ってしまうことが多く、私も当時は例外ではなかった。
こういった特徴があるので、Z80「だけ」しかアセンブリ経験が無いと、長年続ければ続けるほど“アドレッシングモード”という概念があまりよく分からないままコーディング技術だけが向上してしまい、他のプロセッサに移った際に「アドレッシングモードって何!?」という状況に陥ってしまう・・ような気がしてならない。
インデックスレジスタIX/IY関連の命令は、HLレジスタ関連の命令の前に1バイトのプリフィックスを置くことで実装している。
IXはDDを、IYはFDを前置する。
ld hl,(0c000h) ;2a 00 c0
add hl,de ;19
inc hl ;23
ld d,(hl) ;56
add a,(hl) ;86
といったHLレジスタ関連の命令にDD/FDを前置すれば、IX/IY関連の同じ命令となる。
ld ix,(0c000h) ;dd 2a 00 c0
add ix,de ;dd 19
inc ix ;dd 23
ld d,(ix+5) ;dd 56 05
add a,(ix+10) ;dd 86 0a
ld iy,(0c000h) ;fd 2a 00 c0
add iy,de ;fd 19
inc iy ;fd 23
ld d,(iy+5) ;fd 56 05
add a,(iy+10) ;fd 86 0a
この仕様から分かるように、インデックスレジスタとHLレジスタは一つの命令内で同時に使えない。
そのためインデックスレジスタ関連でも下のような「書けそうで書けないコード」が存在する。
add hl,ix
sbc hl,iy
ld (hl),(ix+5)
そしてこの「HLレジスタ関連の命令の裏にIX/IY関連の命令を置いた」という仕様のため、8080にあった「HLレジスタが指し示す先のメモリ内容は“Mレジスタ”である」という哲学が揺らいでしまって、「ではIX/IYレジスタが指し示す先のメモリ内容は何レジスタなんだ」という疑問が生じてしまうのだが、「これはインデックスアドレッシングだ」ということで片付いてしまったようだ。
ちなみに、このDD/FDをHレジスタやLレジスタの命令の前に置くと、IX/IYレジスタを8ビット単位で利用できる。
これは命令表に無いので「隠し命令」と呼ばれていた。
A,B,C,D,E,H,Lの7本のレジスタと、Fレジスタ(フラグレジスタ)の合計8本が2セットあり、これはEX命令やEXX命令で交換することができる。
2セット目のレジスタを「裏レジスタ」と呼び、これを含めるとZ80にはアキュムレータが2本、汎用レジスタが12本、スタックポインタが1本、インデックスレジスタが2本、フラグレジスタが2本、リフレッシュレジスタが1本、割り込みベクタレジスタが1本と、当時の8bitCPUとしては異例のレジスタ本数を持っている。
ちなみにフラグレジスタは2本あるのだが、スタックポインタは1本しかない。
裏が存在するのは8bitレジスタだけなので、システムスタックとユーザースタックを使い分けるといったことはできない。
この裏レジスタの挙動には僅かながら癖があり、EX DE,HL命令で表レジスタのDEとHLを交換しても、裏レジスタでは交換されていない。
EX DE,HL命令は、ワークメモリを使って実際に中身をスワップしているわけではなく、内部回路を切り替えているだけなのだが、裏と表はその切り替えが連動せず、独立して動くようになっている。
インデックスレジスタ関連以外で追加された命令は全部で54個あり、1バイト命令と2バイト命令に分けられる。
1バイト命令は、以下の8個。
裏表レジスタ交換と、相対ジャンプ命令、Bレジスタをカウンタとしたループ命令だ。
EX AF,AF' / EXX / JR e / JR NZ,e / JR Z,e / JR NC,e / JR C,e / DJNZ e
プリフィックスを前置しての2バイト命令は、以下の46個。(rはMレジスタも含む。Z80表記だと(HL)のこと)
HLレジスタ以外のエクステンドアドレッシングによるメモリアクセス、16bitのADC/SBC命令、アキュムレータ以外でのローテート、シフト命令、ビット操作、それにブロック転送やブロックサーチ、ブロック入出力など。
プリフィックスはCBとEDの2通りがあり、CBの内HLレジスタ関連コードの前にさらにDD/FDを前置すると「Z80で追加されたコードのインデックスレジスタ版」となり、これがZ80では最長・最遅の命令となる。
EDの前にDD/FDを前置して、インデックスレジスタ関連の命令にすることはできない(例:SBC IX,BC など)
LD BC,(nn) / LD DE,(nn) / LD SP,(nn) / LD (nn),BC / LD (nn),DE / LD (nn),SP / LD A,I / LD I,A / LD A,R / LD R,A / ADC HL,rr / SBC HL,rr / NEG / RLC r / RRC r / RL r / RR r / RLD / RRD / SLA r / SRA r / SRL r / BIT b,r / SET b,r / RES b,r / RETI / RETN / IN r,(C) / OUT (C),r / IM x / LDI / LDD / LDIR / LDDR / CPI / CPD / CPIR / CPDR / INI / IND / INIR / INDR / OUTI / OUTD / OTIR / OTDR
命令もアドレッシングモードも多くはないが、必要十分にあるレジスタ本数とツボを押さえた追加命令とによって、本当に使いやすいプロセッサだった。
最低限の理解と暗記でとりあえずコーディングでき、本気で内部を理解すれば1クロック単位でクロック削りができる、そんな石だったと思える。
8080同様、アセンブラを使わず、16進の命令コードを暗記してコーディングする人が多くいたが、こんなことが可能だったのは、アドレッシングモードが非常に少なかったからということに尽きるような気がする。
|