標準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 | 0 |
01 | 1 |
02 | 2 |
03 | 3 |
04 | 4 |
05 | 5 |
06 | 6 |
07 | 7 |
08 | 8 |
09 | 9 |
0A | A |
0B | B |
0C | C |
0D | D |
0E | E |
0F | F |
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にします。
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
最後の仕上げです。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
前へ
次へ
ホームページトップへ戻る