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

●TK80のモニタプログラム(スタートルーチン)の説明です

前々回から、TK80のステップ動作を、ソフトウェアの面から説明しています。
前回の説明で、ステップ動作でRST7割込みを受けて、0151からの「ブレイク」処理ルーチンが実行されることをお話しました。
そのプログラムの最後は、0051からの、モニタスタートルーチンへのJMP命令で終わっていました。

あれ?
割込みプログラムの最後は、
EI
RET
だったはずですが、どうしてこんなところに来てしまうのでしょう?

普通の割込みプログラムならば、最後はEI、RETになるのですけれど、ステップ動作は、ちょいと普通の割込みプログラムとは異なっているのです。

割込みを利用してはいますけれど、ステップ動作の処理ルーチンは、実は「ブレイク」ルーチンなのだ、ということを前回書きました。
「ブレイク」ですから、モニタプログラムに戻って、そのあとは通常の「モニタモード」になるのです。
ステップ動作(ブレイク動作)でモニタプログラムに戻ったあとは、たとえばメモリにセーブされたレジスタの値を確認したりできるように、普通のキー操作が行えることが求められます。

ですから、普通のキー操作をする、モニタスタートルーチンにジャンプするようにしてあるのです。
それなら、ステップ動作の続きはどうなるの?
という疑問が出てきます。

その答えはじきにわかります。
まずはモニタスタートルーチンを見てみることにしましょう。


              ;
              ; MONITOR START
              ;
0051 3EF7     START:MVI A,F7
0053 D398       OUT 98;PIC reset
0055 31D1FF     LXI SP,MONSP
0058 CDC001     CALL SEGCG
005B CD1602     CALL KEYIN
005E 47         MOV B,A
005F E610       ANI 10
0061 CA8400     JZ DIGIT
0064 78         MOV A,B
0065 E60F       ANI 0F
0067 0600       MVI B,00
0069 87         ADD A
006A 4F         MOV C,A
006B 217400     LXI H,TABL
006E 09         DAD B
006F 7E         MOV A,M
0070 23         INX H
0071 66         MOV H,M
0072 6F         MOV L,A
0073 E9         PCHL
              ;
0074 CC00     TABL:DW GOTO
0076 F901       DW RESRG
0078 9400       DW ADSET
007A B800       DW ADDCX
007C 9D00       DW ADINX
007E C200       DW MEMW
0080 D500       DW SDATA
0082 0701       DW LDATA
              ;
0084 CDB501   DIGIT:CALL SHIFT
0087 3AECFF     LDA DATA
008A B0         ORA B
008B 32ECFF     STA DATA
008E CDA101     CALL RGDSP
0091 C35100     JMP START

0051からのモニタスタートルーチンは、その名の通り、モニタプログラムが0000から実行を開始すると、必要な初期設定を済ませたあと、必ずここからスタートします。
何をしているかと言いますと、ここではキーが押されるのを待っているのです。
005BのCALL KEYINがキー入力ルーチンです。

KEYINは0216番地にあるサブルーチンです。
キーが押されるまで待っています。
キーが押されると、そのキーに対応する「キーコード」をAレジスタに入れてリターンしてきます。

参考までに、キーコードとキーの対応表を下に示します。

●TK80のキーコード表

キーコード  キー
00
01
02
03
04
05
06
07
08
09
0A
0B
0C
0D
0E
0F
10 RUN
11 RET
12 ADRSSET
13 READ DECR
14 READ INCR
15 WRITE INCR
16 STOREDATA
17 LOADDATA

●数字入力処理

0〜Fの数字キーのコードは、「そのまんま」です。
入力されたキーに対応するキーコードが00〜0Fのとき(つまりビット4が1ではないとき、というチェックを005FのANI 10で行っています)、0084のDIGITへジャンプします。

余り詳しく説明していますと、なかなか先へ進めませんから、簡単に説明をします。
ここではLEDの右4桁の「データ表示部」に表示するためのデータを保持しているFFEC、FFEDの値を、まず左に4ビットシフトして(CALL SHIFT)、空いた最下位4ビットに、今入力された数値コードの4ビットを加えます(OR B)。
そして更新された表示データを、LEDに表示するためにRGDSPサブルーチンをCALLしてから、またキー入力処理に戻ります(JMP START)。

●命令キーの入力処理

キーコードが10〜17のときは、それぞれの処理ルーチンにジャンプします。
キーコードは数字キーのチェックでANIを使うためにBレジスタに退避されています。
そのBレジスタの値を、CMP命令で比較しながら、JZでそれぞれの処理ルーチンへジャンプする、というプログラムでもいけないことはないのですが、それではいかにも「稚拙」です。

今回のようにコードが序数(順に並んだ数)であるような場合には、テーブルを使います。
これもよく使われるテクニックです。

まず各命令へのJMP先アドレスのテーブル(表)データを用意します。
0074〜0083が命令ジャンプテーブルです。
たとえば最初の0074、0075にあるCC00はRUNキーの処理ルーチンへのジャンプアドレスです。
RUNキーの処理プログラムは00CCにありますが、アドレスの上位と下位が入れ替わる形になっています。
こうしなければいけないことはないのですが、8080の命令のルールでアドレスなど2バイトの値をメモリに格納するときは、下位バイト、上位バイトの順に格納するので、ここでもそういう配置になっているのだと思います。

その次の0076、0077にあるF901はRETキーの処理ルーチンへのジャンプアドレスです。
ここも上位と下位が逆になっています。
RETキーの処理プログラムは01F9から始まります。

なおニーモニックのDW(define word)はDB(define byte)と同じように、もともとのCPUの命令ではなくて、アセンブラ処理のために用意された「擬似命令」です。
DBは1バイトのコードをそのまま記述するときに使います。
これに対してDWは2バイトのデータを直接定義するときに用いられます。

さて、命令コードをもとに、テーブルを参照して、それぞれの処理ルーチンへジャンプするためのプログラムは、0064からの部分です。


0064 78         MOV A,B
0065 E60F       ANI 0F
0067 0600       MVI B,00
0069 87         ADD A
006A 4F         MOV C,A
Bレジスタに退避させてあったキーコードの上位4ビットを0にして、00〜07にします。
ADD Aでその2倍、00〜0Eにしたあと、それをCレジスタに入れます。
Bレジスタは00にします。
キーコードの10〜17をもとにして、BCレジスタに0000〜000Eが入りました。

006B 217400     LXI H,TABL
006E 09         DAD B
HLレジスタにテーブルトップのアドレス0074を入れて、DAD Bを実行すると、その結果HLレジスタは、キーコードに対応するジャンプテーブルのアドレスを示すことになります。

006F 7E         MOV A,M
0070 23         INX H
0071 66         MOV H,M
0072 6F         MOV L,A
0073 E9         PCHL
最後の仕上げです。
HLレジスタは目的とするジャンプ先アドレスの下位アドレスがあるテーブル番地を示しています。
MOV A,Mで、そのジャンプ先の下位アドレスがAレジスタに入れられます。
INX H
MOV H,M
で、ジャンプ先の上位アドレスがHレジスタに入ります。
そしてAレジスタに入れられていたジャンプ先の下位アドレスを、MOV L,AでLレジスタに入れます。

ここで、最初にいきなりMOV L,Mとやってしまうと、HLレジスタがジャンプテーブルを示していたのが、別の値に変化してしまって、次のMOV H,Mが正しく実行できません。
下位バイトを読み出すのにMOV A,Mとしているのは、その理由からです。
そして、最後にPCHLを実行します。
PCHLはHLの値をプログラムカウンタにセットする命令ですから、このPCHLを実行することで、ジャンプテーブルが示すジャンプ先へジャンプすることになります。

●RETキーの動作の説明です

やっと、ここまでたどりつきました。
ステップ動作の締めくくりです。

RETキーはステップ動作を再開するためのキーです。
RST7割込みによって、ブレイクしてモニタプログラムに戻ったあとは、普通にキー操作ができるようにするために、モニタスタートルーチンに行きました。

このままステップ動作を止めてしまうこともできます。
でも、もしステップ動作を続けるのならば、RETキーを押します。
RETキーを押すと、メモリに保存してあったブレイク直後のCPUレジスタやプログラムカウンタの値などを、元通りに戻したあと、ユーザープログラムに戻ります。

ちょっと考えると、どうするとそんな器用なことができるのだろう、と思いますが、そこでもテクニックが使われています。


              ;
              ; REGISTER RESTORE
              ;
01F9 2AE2FF   RESRG:LHLD SSAVE
01FC F9         SPHL
01FD 2AE0FF     LHLD PSAVE
0200 E5         PUSH H
0201 2AE4FF     LHLD LSAVE
0204 E5         PUSH H
0205 2AEAFF     LHLD FSAVE
0208 E5         PUSH H
0209 2AE8FF     LHLD CSAVE
020C 4D         MOV C,L
020D 44         MOV B,H
020E 2AE6FF     LHLD ESAVE
0211 EB         XCHG
0212 F1         POP PSW
0213 E1         POP H
0214 FB         EI
0215 C9         RET

これもまたみごとな職人芸のようなプログラムです。
SSAVEはブレイクしたときに、スタックポインタを保存したメモリアドレスです。
同様に、PSAVEはプログラムカウンタ、LSAVEはHLレジスタ、FSAVEはAレジスタとフラグレジスタ、CSAVEはBCレジスタ、ESAVEはDEレジスタをそれぞれ格納したメモリアドレスです。

最初に
LHLD SSAVE
SPHL

でスタックポインタにもとの値を戻します。
これは、まあ、わかりますね。

次の
LHLD PSAVE
PUSH H

になると、ええっ?なにをしているのだろう?
と思ってしまいますが、これは最後に利いてきます。
PSAVEには、ステップ動作でモニタプログラムに戻ったときに、次に実行するはずだったユーザープログラムのアドレス、つまりそのときのプログラムカウンタの値がセーブされています。
そのアドレスをPUSH Hでスタックに保存します。

その次の
LHLD LSAVE
PUSH H
LHLD FSAVE
PUSH H

は、HLレジスタと、Aレジスタおよびフラグレジスタをもとにもどしてからスタックに保存しています。
フラグレジスタは、こうしておいて、後でPOP PSW命令を使わなければもとにもどせませんから、ここでPUSHするのは理にかなっています。
しかしHLレジスタについては、はてな?です。

ここでメモリから読み込まなくても、最後に読み込めばよいはずです。
つまり、ここで
LHLD LSAVE
PUSH H

としないで、最後の
POP H
の代わりに、
LHLD LSAVE
を実行すれば、むだな
PUSH H
POP H
を省くことができます。

それはともかくとして、次はBCレジスタとDEレジスタの復帰です。
LHLD CSAVE
MOV C,L
MOV B,H
LHLD ESAVE
XCHG

これは、わかりますよね。

最後に、
EI
RET

を実行すると、割り込みが許可されて、それからRET命令によって、最初にスタックにPUSHしたユーザープログラムのアドレスがプログラムカウンタに戻されて、そしてユーザープログラムにリターンします。

いやあ、やっと、ステップ動作の説明が終わりました。
ながながと、モニタプログラムの説明を続けてきたのにはわけがあります。
それは、このあとにお話をする予定の、「とんでもないミスを見つけてしまった」というところのお話で、いままで説明してきたことを予備知識として理解しておいていただいたほうが、話を進め易いかなぁ、と思ったからです。
2009.6.16upload

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