標準TTLだけ(!)でCPUをつくろう!(組立てキットです!)
(ホントは74HC、CMOSなんだけど…)
[第249回]

●TK80のステップ動作(モニタプログラム)の説明です

前回は、TK80のステップ動作について、ハードウェアから見た、動作の仕組みについて、説明をしました。

ステップ動作をするために、割込みが使われていることを説明しました。
その割込みは、フリップフロップによって、ユーザープログラムを1命令だけ実行したあとに、受け付けられるようにしてあります。
しかし、ただ割込みが受け付けられただけで、ワンステップ動作が行われるわけではありません。
当然そこには、割込み処理を含めた、モニタプログラムの仕組みがあって、はじめてワンステップ動作が可能になるわけで、プログラムがなければお話になりません。

今回はTK80のステップ動作について、ソフトウェア(モニタプログラム)の面から見ていくことにいたします。

「つくるCPU版」TK80のモニタプログラムリストは[第241回]でお見せしました。
そして[第242回]で、ステップ動作モードのときに、INT信号入力によってRST7割込みが発生して、そこで実行される割り込み処理プログラムがモニタプログラムの0151番地に書かれていることをお話しました。
そして、そのプログラムをたどっていくと、どういうわけか[RET]キーの処理ルーチンに行ってしまいます、という説明をしました。
しかしそのときは、さらに進んで、そのプログラムの説明までは、しないままで終わってしまいました。

いやあ、ここは、ちょっと難しいのです。
説明する側としましても、なかなか説明が難しいという感じがします。
でもまあ、ここを説明しないで素通りしてしまうのは、いかにも中途半端です。
ですから、まあ、ぼちぼち、少しずつ説明していくことにいたしましょう。

●割込み処理(RST7)プログラムです

ステップ動作の割込みは、INT信号だけで、データバスには何ものせませんから、RST7割込みになります。

ここでちょいとおさらいです。
8080の割込みは、INT信号を入力するとともに、データバスに8ビットの命令コード(通常はRST命令)を乗せることで行われます。
データバスに何ものせないで、INT信号だけを入力すると、データバスは通常は抵抗でプルアップしているため、命令コードFFの割込み命令が実行されることになります。
RST命令のうち、RST7は命令コードがFF(11111111)ですから、INT信号のみを入力すると、RST7割込みが実行されます。

RST7命令は1バイトのCALL命令です。
固定アドレス0038番地からのサブルーチンをCALLします。
普通は0038から、だらだらっと割込みプログラムを書いたりはしないで、割込みプログラムは特殊な処理ですから、割とプログラムの後ろの方に書くことが多いようです。

0038には、その後ろの方に書いた割込み処理プログラムへのジャンプ命令だけを書いておきます。

では、モニタプログラムを見てみることにしましょう。


                ORG $0038
0038 C35101     JMP BRENT

0038には、0151番地へのJMP命令が書いてあります。

それでは、その0151からの割込み処理プログラムを見てみましょう。
少しずつ細切れにして、説明しながら進むことにいたします。


              ;
              ; BREAK ENTRY 
              ; BREAK & ONE STEP OPERATION
              ;
                ORG $0151
              ;
0151 E3       BRENT:XTHL
0152 22E0FF     SHLD PSAVE
0155 F5         PUSH PSW
0156 210400     LXI H,$0004
0159 39         DAD SP
015A F1         POP PSW
015B 22E2FF     SHLD SSAVE
015E E1         POP H
015F 31ECFF     LXI SP,DATA
0162 F5         PUSH PSW
0163 C5         PUSH B
0164 D5         PUSH D
0165 E5         PUSH H

いきなり何がなんだかさっぱりわけがわからないプログラムが始まっています。
これはもう「職人芸」です。
昔の先輩プログラマの面目躍如というところでしょうか。

ここはユーザープログラムを1命令実行したあとに、割込みによって実行を開始する最初の部分です。
割込み処理プログラム自身が実行されることで、割込み直後のCPUの状態(レジスタの値など)はどんどん変化していってしまいます。
そうなってしまう前に、まずCPUのレジスタの値を全てメモリのそれぞれ決まった格納場所にセーブしなければなりません。
保存するのは汎用レジスタだけではありません。
フラグの状態や、スタックポインタ、そしてプログラムカウンタも保存します。

最初の
XTHL
SHLD PSAVE
が、プログラムカウンタを保存しているところです。
PSAVE(アドレスFFE0,FFE1)はプログラムカウンタの保存場所です。

RST7は1バイトのCALL命令です。
CALL命令ですから、プログラムカウンタの値(次に実行される命令のあるメモリアドレスを示しています)をスタックに保存してから、指定アドレスの命令を実行します。

わかりますでしょうか。
XTHLは、スタックトップの2バイトの値(最後にPUSHした値)をHLレジスタに入れ、代わりにHLレジスタの値をスタックトップに置きます。
スタックトップには割込み直前のプログラムカウンタの値が入っています。

XTHLについては[第100回]で説明をしました。
そのときには、「XTHLなんて、どこでつかうのでしょう。普通はまず出番はありません」なんて書きましたが、こんなところでしっかり役目を果たしています。

こうすることで、プログラムカウンタを保存しながら、HLレジスタもちゃんとスタックに保存しています。

つぎの
PUSH PSW
LXI H,$0004
DAD SP
POP PSW
SHLD SSAVE
はスタックポインタの保存です。

SSAVE(FFE2,FFE3)はスタックポインタの保存場所です。
スタックポインタの値を直接参照する命令はありませんが、DAD SP命令を使うことで、スタックポインタの値をHLに入れることができます。
DAD命令を実行すると、フラグが変化してしまいます(ここまでに実行した命令は、フラグを変化させない命令です)。
そこで、現在のフラグの状態を保存しておくために、DAD SPよりもさきにPUSH PSWを実行します。
ところがPUSH PSWの実行によって、スタックポインタの値が2バイトマイナスされてしまいます。
もともと、この割込み処理が実行される段階で、割込み直前のプログラムカウンタの値をスタックに保存していますから、ユーザープログラムを実行中のスタックポインタの値よりも2バイト少なくなっています。
ですから、今回のPUSH PSWの分を合わせると、本来のスタックポインタの値よりも4バイト減になっています。
HLレジスタに0004を入れた上で、DAD SPを実行することで、もとの、本来のスタックポインタの値がHLレジスタに入れられることになります。

その後の
POP H
LXI SP,DATA
PUSH PSW
PUSH B
PUSH D
PUSH H
は、Aレジスタとフラグ、BC、DE、HLレジスタをそれぞれ保存しているところですが、ここでもテクニックが使われています。さすが、みごとです。

まず
POP H
でスタックトップに保存してあったHLレジスタをもとのHLに戻します。
その次からがテクニックです。
連続したレジスタの保存場所がメモリ上に次のように割り当てられています。

FFEB Aレジスタ
FFEA フラグレジスタ
FFE9 Bレジスタ
FFE8 Cレジスタ
FFE7 Dレジスタ
FFE6 Eレジスタ
FFE5 Hレジスタ
FFE4 Lレジスタ

ここに、さきほどのプログラムカウンタやスタックポインタのときのように、SHLD命令を使うのではなくて、スタックポインタにFFECを与えておいて、つまり、この格納場所を仮のスタックにしておいて、そこに連続してPUSH命令を実行することで、一気にレジスタの値をセーブしてしまいます。
いやあ、じつにみごとなテクニックです。

ここまでが、CPUの現在の状態をセーブしているところです。
ここからあとが、モニタプログラムでの本来の処理部分になるのですけれど、ちょっと、疲れてしまいましたですね。
この続きはまた次回、ということにいたしましょう。
2009.6.13upload

前へ
次へ
ホームページトップへ戻る