Z80ユーザーに贈るH8マシン語入門
レジスタ構成を知ろう
レジスタは、以下のように特殊なレジスタと汎用レジスタに分けられます。
・特殊なレジスタ
以下の特殊なレジスタは、ビット数こそ違うもののZ80とほぼ同じ働きをします。
H8のレジスタ
|
相当するZ80のレジスタ
|
PC(24ビット)
|
PC
|
ER7(32ビットだが実質24ビットしか使わない)
|
SP
|
CCR(8ビット)
|
フラグレジスタ
|
・汎用レジスタ
汎用レジスタは、ER0〜ER6の、32ビット×7本です。これらは上下16ビットづつに分割して、E0〜E6、R0〜R6の14本の独立した16ビットレジスタとして使うこともできます(ここが80386や68000などと比べて便利な点です。たとえば80386などは、EAXの上位16ビットを独立したレジスタとして扱えません)。さらに、R0〜R6の7本の16ビットレジスタは、上位バイトと下位バイトに分けてR0H〜R6H、R0L〜R6Lという14本の独立した8ビットレジスタとして使うこともできます。その辺の関係をER0を例にとって表にすると、以下のようになります。
32ビットレジスタ
|
ER0
|
16ビットレジスタ
|
E0(上位)
|
R0(下位)
|
8ビットレジスタ
|
|
R0H
|
R0L
|
基本的にすべての汎用レジスタは対等です。使用できるレジスタが固定されているのはEEPMOV命令(LDIR相当)だけです。Z80のようにAやHLがひいきされていたりしません。
アドレッシングモード
H8は結構68000と似たCPUですが、68000のような複雑怪奇なアドレッシングモードはありません。Z80のアドレッシングモードが理解できた人なら問題ないでしょう。ただ、アセンブラの表記は異なります。以下はZ80とほぼ同じアドレッシングモードです。
アドレッシングモード
|
H8
|
Z80
|
イメーディエート
|
MOV.B #H'12,R0L
|
LD A,012H
|
レジスタ直接
|
MOV.B R1H,R0L
|
LD A,B
|
絶対
|
MOV.B @H'001234,R0L
|
LD A,(01234H)
|
レジスタ間接
|
MOV.B @ER3,R0L
|
LD A,(HL)
|
ディスプレースメント(オフセット)つきレジスタ間接
|
MOV.B @(10,ER3),R0L
|
LD A,(IX+10)
|
プログラムカウンタ相対
|
BRA label
|
JR label
|
目新しいアドレッシングモードとしてはポストインクリメント、プリデクリメントのレジスタ間接があります。これは、メモリアクセスとポインタのインクリメント/デクリメントを1つの命令でやってくれるのです。
ポストインクリメントレジスタ間接
|
MOV.B @ER3+,R0L
|
LD A,(HL)
INC HL
|
プリデクリメントレジスタ間接
|
MOV.B R0L,@-ER3
|
DEC HL
LD (HL),A
|
なお、気を付けなければいけないのは、ポストインクリメントは「メモリ→レジスタ」のみ、プリデクリメントは「レジスタ→メモリ」のみ、と転送方向が限られています。これらのアドレッシングモードは「連続データ転送」などよりも「ソフトウエアスタック」を目的として設けられたようです。
あと、メモリ間接というのもありますが、データのアクセスには使えず、ジャンプ、コール(JSR)のみで使えます。8086のINT命令みたいな感じです。
移植の際の注意
・メモリーレジスタ間の演算はできない
Z80と違い、メモリーレジスタ間の演算命令がありません。具体的には ADD A,(HL) のようなことはできません。が、H8はZ80より圧倒的にレジスタが多いのでそれほど困らないでしょう。たとえば ADD A,(HL) なら
MOV.B @ER3,R0H
ADD.B R0H,R0L
・転送しただけでゼロフラグ、符号フラグが変わる
たとえば、MOV.B @H'123456,R0L を実行します。当然123456番地の内容がR0Lにロードされるのですが、同時にその値によってゼロフラグおよび符号フラグが変化します。
最初、そんなに簡単にフラグが変わってよいのか、変わってほしくないときに勝手に変えられてしまうのではないか、と不安に思うのですが、実際プログラムを組んでみると、ゼロフラグとか符号フラグなんてそんなにいつまでもしつこく保持する必要もないことに気づきます。むしろ、Z80のようにフラグを立てるために AND A などを実行する必要がありません。特に、2バイト数値や4バイト数値の転送でもフラグが立つので、データのゼロチェック、符号チェックが簡単になって便利です。
・交換命令がない
H8にはZ80のEX命令のような交換命令がありません。しかし、レジスタ数の多さと直交性の高さのおかげでほとんど苦になりません。むしろ、交換命令はレジスタの直交性の低いCPUが苦肉の策としてつけたものであることに気づきます。たとえば EX DE,HL なんてのは、DEがはじめからHLと同じ機能を持っていれば必要ないはずです。
・条件コール、条件リターンがない
これは、コール(JSR,BSR)やリターン(RTS)命令を条件分岐ですっ飛ばすしかないですね。
・EEPMOVについて
Z80のLDIRに相当する命令としてEEPMOV命令があるのですが、この命令の実行中は割り込みを受け付けません(EEPMOV.Wの場合はNMIのみ受け付ける)。ですから調子に乗って大量のデータを転送していると、割り込み応答が思いっきり遅れます。
もうひとつ問題点としてはLDDR相当の命令がないので、その場合はループを組まなければいけません。
それらの問題点を解決する方法としては、「DMAコントローラで転送する」という手もあるのですが、転送するデータが数十バイト程度の場合わざわざDMAコントローラを設定するのも気が引けます。また、入出力用にDMAコントローラを常時フルに使っているようなシステムではその手は使えません。
・ポインタのインクリメント・デクリメントにはADDS/SUBSを使おう
注意と言うほどのものでもないのですが。最初、私はADDS/SUBS命令の存在に気づかず、ポインタを4バイト進めるのに
INC.L #2,ER3 ; ER3に2を足す
INC.L #2,ER3 ; ER3に2を足す
なんてやってました。「どうせなら4も足せるようにしてくれ!」などと怒りながら。ところが、ある日ふと何気なく命令表をみていると、ADDSなんて命令があるじゃないですか!
ADDS #4,ER3
とやればよかったのですね。
ところで、INC/DECはフラグに影響を与えるのですが、ADDS/SUBSはフラグに全く影響を与えません。したがってループのカウントなどにはSUBSではなくDECを使わないと、無限ループになってしまうので気を付けましょう。
こりゃ楽でいいや
H8はZ80よりはるかにすぐれている点があります。そういった点を積極的に使いましょう。
・どのレジスタでも論理演算できる、しかも、2バイト、4バイトの演算も1命令でできる!
以下は16ビットのAND演算の例です。32ビット演算になると比べる気もしません。
Z80
|
H8
|
LD A,E
AND C
LD E,A
LD A,D
AND B
LD D,A
|
AND.W R1,R2
|
・2バイト数値、4バイト数値のイメーディエイト演算ができる!
これも、もう涙がでるほどありがたいです。
Z80
|
H8
|
PUSH DE ; DEを壊してはいけない場合
LD DE,01234H
ADD HL,DE
POP DE ; DEを壊してはいけない場合
|
ADD.W #H'1234,R3
|
・2バイト数値、4バイト数値のレジスタ間接アクセスが1命令でできる!
LD E,(HL)
INC HL
LD D,(HL)
INC HL
とかの嵐なんですよね。一応マクロ定義して一行で書いているんですけど。そういったものはH8だと
MOV.W @ER3+,R2