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

●前回のテストプログラムの説明です

前回はSPHL、PCHL命令のテストプログラムを紹介しました。
「どういう動作をするのか、考えてみてください」
ということで、宿題といたしました。
皆さん、わかりましたでしょうか?

もう一度、前回のプログラムを再掲いたします。

0000 3E00     MVI A,00
0002 47       MOV B,A
0003 4F       MOV C,A
0004 57       MOV D,A
0005 5F       MOV E,A
0006 210001   LXI H,0100
0009 F9       SPHL      (4)
000A E9       PCHL      (4)

00FE 00       DB 00
00FF 00       DB 00
0100 3B       DCX SP    (4)or(6)
0101 3B       DCX SP    (4)or(6)
0102 F1       POP PSW   (4)
0103 3C       INR A      (4)
0104 F5       PUSH PSW  (4)
0105 C20901   JNZ 0109   (6)or(4)
0108 03       INX B      (4)or(6)
0109 24       INR H      (4)
010A E5       PUSH H    (4)
010B 210900   LXI H,0009 (4)
010E E3       XTHL       (8)
010F C9       RET        (4)

01FE 00       DB 00
01FF 00       DB 00
0200 3B       DCX SP    (4)or(6)
0201 3B       DCX SP    (4)or(6)
0202 F1       POP PSW   (4)
0203 3C       INR A      (4)
0204 F5       PUSH PSW  (4)
0205 C20902   JNZ 0209   (6)or(4)
0208 13       INX D      (4)or(6)
0209 25       DCR H      (4)
020A C30A01   JMP 010A   (6)

命令が実行されていく順にしたがって、説明をしていきましょう。

最初に、A、B、C、D、Eの各レジスタを00クリアします。
これはカウンタを00からスタートさせるためです。

HLレジスタには初期値として0100を入れています。
この0100は「BCA」カウンタルーチンのエントリアドレスであるとともに、スタックポインタのアドレスでもあります。

次の命令、SPHLで、SP(スタックポインタ)に、この0100がセットされます。
そして、さらにその次の、PCHL命令によって、PC(プログラムカウンタ)にも同じ0100がセットされます。
PCの値が0100になりますから、つまり、これはJMP 0100が実行されるのと同じことです。
したがって、処理の流れは、0100に移ります。

0100と0101にはDCX SP命令があります。
DCX SPを2回実行しますから、SPの値は0100−2で00FEになります。
本当は、SPには00FEを入れたいのですが、PCHLでジャンプ先アドレスとして0100を使うので、SPにもそれと同じ0100を入れるしか仕方がないのです。

次にいきなりPOP PSWを実行しています。
実はAレジスタ(およびフラグレジスタ)用の保存場所として、00FEと00FFをリザーブしておいたのです。
そのためあらかじめ初期値として00を入れておきました(プログラムの先頭のMVI A,00は、0100以降のカウンタとしてのAレジスタに対しては役に立ちません)。
SP(スタックポインタ)に00FEをセットしておいて、POP PSWを実行することで、00FE〜00FFのメモリ内容がAレジスタとフラグレジスタに入れられます。

このように、今回のプログラムでは、プログラム領域内に、RAMにしか置けないスタックを置いています。
ということは、今回のプログラムは、RAMに書かなければならない、ということになります。
前回、このプログラムはROMでは実行できません、と書いたのは、そういう理由からです。

POP PSWのあと、INR Aを実行して、Aレジスタを+1(インクリメント)します。
そして、PUSH PSW命令で、+1したあとのAレジスタの値をまたスタックに保存します。

この部分は、Aレジスタの値を保存するために、アドレス00FFのメモリエリアを利用しています。フラグレジスタの値も00FEに保存されますが、それは、いわば「おまけ」です。
こういう場合に、ふつうは、LDA 00FF、STA 00FFのようにします。
そうする代わりに、SPHLをうまく利用することでスタックをレジスタ保存用のメモリエリアとして使うことができる、というプログラム例です。

ここでは、Aレジスタだけですから、それほど有難味は感じませんが、これが、Aレジスタだけではなくてフラグレジスタの保存も必要であるとか、さらにそのほかの、BC、DE、HLレジスタも保存しなければならない、ということになると、SPHLとPUSH、POPを使う方法の優位さは歴然としてきます(職人芸といいますか、SPHLという命令があるからこそできる、ウラワザです)。

プログラムの説明に戻ります。
前にもいくつか作ったテストプログラムのように、Aレジスタが256カウントされて、00になるたびに、その上位カウンタのBCレジスタを+1します。
Aレジスタを+1した結果が00でないときは、BCレジスタを+1する命令(INX B)はジャンプします。
それが、JNZ 0109です。

0109には、INR H命令があります。
このテストプログラムでは、HLレジスタはカウンタではありません。
では、何をしているのでしょう?

HLレジスタには何が入っていたか、思い出してください。
そうです。
「BCA」カウンタルーチンを実行するために、そのエントリアドレスの0100が入っていました。
その、Hレジスタを+1するということは?

HLレジスタの値は、0200になります。
実は、0200はもうひとつのカウンタ「DEA」をインクリメントするためのルーチンのエントリアドレスなのです。
0200〜のメモリアドレスには、0100〜と同じ動作をするプログラム(対象になるレジスタだけが異なっている)が書かれています。

0100〜のプログラムを実行したあとで、次に実行するプログラムのエントリアドレスである0200をHLレジスタに入れて、それでどうするか、というと、PUSH Hを実行したあとで、RETを実行します。
なぜ、こんなことをするのでしょう?

HLレジスタに0200を入れておいて、0200にジャンプするためです。
ええ?じゃあPUSH、RETなどという面倒なことをしないで、PCHLを実行すればよいじゃないの?

その通りです。
010AにSPHLを書いて、次の010BにPCHLを書けば、それで正解になります。

今回はテストプログラムですから、BCレジスタとDEレジスタをカウントアップするだけで、他には何もすることがありません。
ですから、もっと簡単なプログラムにしてしまうこともできてしまいます。
そのあたりのことについては、前回もちょっとお話をしたところです。
いろいろな命令の使い方をテストしたりするために、わざと面倒なことをしています。

実は、0009のあたりに少し長いプログラムがあるのだけれど、プログラムサンプルなので、そこのところが省略されている、と考えてみてください。
カウント処理後は必ず、0009に戻る必要がある、という設定です。
それから、0009以後の処理のあと、HLレジスタの値によって0100か0200にジャンプしたいので、HLレジスタの値はつねに保持していたい、ということにしましょう。
カウント処理後に0009に戻るにも条件があって、HLレジスタには0100か0200を保持したままで、なおかつHLレジスタに0009を入れて、つまりHLレジスタによる間接アドレッシングによって、0009にジャンプしたい、というむちゃくちゃな条件をクリアしなければならない、ことにします。

HLレジスタに0009を入れておいて、PCHL命令を使えばよいのですけれど、そうするとそれ以前にHLレジスタに入れた、0100または0200を保持しておくことができません。
XCHG命令で一時的にDEレジスタを使うことにすると、DEレジスタのカウント値を保持することができません。

では、PUSH Hでスタックに保存したあとで、HLレジスタに0009を入れて、PCHL命令を実行したらどうか?
この場合は0009でPOP Hを実行することになりますから、それを前提にして、0009よりも前(0008よりも後ろ)にPUSH Hを置きます。それもひとつの解決法です。

しかし、もしも、他のいろいろなルーチンから、この0009に戻ってくる、という条件がさらに加わるとなると、POP Hを0009に置くのはきわめて危険になります。
HLレジスタに何らかの値を保持して、0009に戻る、という設定はノーマルなものですが、スタックに何らかの値をPUSHした状態で0009に戻る、という設定は、よほどシステム設計を厳密に行うようにしないと、プログラムの変更や追加を繰り返しているうちに、スタックが食い違ってしまい、暴走の原因となってしまいます。

ここでは、そういうときに、もっとスマートに条件をクリアする「ウラワザ」として、PUSH H、XTHL、RETの組み合わせを使っています。
CALL、RETもPUSH、POPもスタックの使い方は全く同じです。
ですから、このプログラムのように、リターン先のアドレスをHLレジスタに入れた上でPUSH Hを実行し、そのあとでRET命令を実行することで、PCHLと全く同じ動作をさせることができるのです。
なお、PUSH命令とRET命令の組み合わせでは、HLレジスタとRETだけに限らず、PUSH B、RETでもPUSH D、RETでも構いません。

今回のプログラムでは、さらにXTHLを使うことで、HLレジスタの値の保存という目的も達成しています。
010Aで先にPUSH Hを実行して、そのときのHLレジスタの値(0200)をスタックにまず保存します。
そしてLXI H,0009でリターン先アドレスを設定した上で、XTHLを実行します。
XTHL命令によって、スタックトップの値(0200)がHLレジスタに戻り、代わりにリターン先アドレス0009がスタックトップに収まります(ここで重ねてPUSH Hを使わず、XTHLを使うところがポイントです)。

0200以降のルーチンも、0100以降のルーチンと全く同じ動作です。
0100〜のプログラムではBCレジスタを+1していますが、0200〜のプログラムでは、DEレジスタを+1しているという違いがあるだけです。
それと、0100〜のプログラムでは、カウントアップ後に、次に0200〜のルーチンを実行するために、INR Hを実行しますが、0200〜のルーチンではHLレジスタの値を0100に戻すために、DCR Hを実行します。

もうひとつ、0100〜のプログラムではAレジスタの値の保存にメモリの00FF番地を使っていますが、0200〜のプログラムでは、Aレジスタの保存に、01FF番地を使っています。
0100〜と0200〜のプログラムで、別のメモリアドレスを、Aレジスタの値の保存に使っています。
そのため、0100〜と0200〜のプログラムで、ともに同じAレジスタを下位カウンタとして使っていながら、それぞれのプログラムでお互いに独立した別のカウンタとして、使うことができるのです。

今回はプログラムの説明が長くなってしまいました。
次回は、このテストプログラムの実行時間の計算と、それを実際に実行したときの写真をお見せすることにいたしましょう。
2009.2.17upload

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