標準TTLだけ(!)でCPUをつくろう!(組立てキットです!)
(ホントは74HC、CMOSなんだけど…)
[第67回]
●スタックポインタ
[第65回]でLXI SPという命令が出てきました。
SP(スタックポインタ)は16ビットのレジスタでスタックアドレスを格納します。
スタックというのはメモリ上に置かれたレジスタの格納場所(一時記憶)です。
プログラムを書いていくと、大抵の場合は、CPUの持っているレジスタだけでは足りなくなってしまいます。
最も頻繁に使われるのがAレジスタで、その次に使われるのがHLレジスタです。
たとえば、加算(ADD)とか減算(SUB)などを異なった値に対して複数回、行わなければならないときなど、ADDやSUBはAレジスタしか使えない(厳密に言うと、計算結果はAレジスタにしか入れられない)ので、困ってしまいます。
たまたまB、C、D、E、H、Lの中で空いているレジスタがあれば、一時的にAレジスタの値をその空いているレジスタに移しておく、という方法もあります。
でも全てのレジスタを使ってしまっていて、ひとつも空いているレジスタが残っていない、となるとお手上げです。
おっと、最後の手段がありました。
M(メモリアドレス)を使うという手です。
しかし、MはHLレジスタをアドレスの指定に使います。
HLレジスタも別のデータの保存に使ってしまっている、としたら、Mも使うことができません。
残る方法は、メモリアドレスを直接指定する方法です。
メモリアドレスを直接指定して、Aレジスタの値を保存したり、読み出したりする命令は、STAとLDAでした。
たとえば、STA 8000 (Aレジスタの値をメモリの8000番地に格納する、とか LDA 8500 (メモリの8500番地の値をAレジスタに入れる)などと使うことができます。
メモリアドレスを直接指定する方法はやぼったいようですが、じつは、高級言語のBASICやCなどでは当たり前に使われている方法です。
BASICで、
ABC=123,XYZ=0
などとした場合には、ABCやXYZをメモリの特定アドレスに割り付けます。
そのあとで、KEKKA=ABC−XYZ
などという計算をさせると、CPUはABCやXYZの記憶場所として先に割り当てておいたメモリアドレスからデータを読んで来て、計算を行います。
このように、高級言語ではごく普通に行われる方法でも、マシン語プログラムでは少し事情が異なってきます。
BASICやCでは、特別の場合を除いて、変数をそれほど多く使うことはありませんし、もともと変数エリアはメモリ上に置くようになっていますから、メモリアドレスを直接参照するのは当たり前の手段です。
しかし、マシン語プログラムでは、なにしろ限られたレジスタしか使えませんし、メモリを使用できる命令も限られています。
また、マシン語プログラムでは、レジスタを特定のデータの保存場所としてずっと使うことはしないで、そのときそのときで必要なレジスタを「一時的」に使用する、という使い方が一般的です。
そのような条件のもとでも、すでに説明したように、Aレジスタについてはメモリアドレスを直接指定してデータを保存することが可能です(STA、LDA命令)。
しかし、これもすでに説明したように、その他のレジスタ(B,C,D,E,H,L)については、直接メモリアドレスを指定して、その値を保存するような命令は用意されていません。
HLレジスタを使って、メモリアドレスを指定する(間接アドレッシング)方法によって、MOV M,Bのようにすることしかできず、そのためにはHLレジスタを常に空けておく必要があります。
このことは、8080に対して多くの拡張命令を追加したZ80でも同じです。
LD A,(adrs) …これは8080のLDAと同じ
LD (adrs),A …8080のSTA
はありますが、
LD B,(adrs)や、LD (adrs),E という命令はありません。
もちろん、このような制約のもとでも、プログラムテクニックによって、B〜Lの各レジスタの値をメモリ上に保存することはできるのですが、それは煩雑な処理を必要とします。
B〜Lレジスタはテクニックによってメモリに保存することも可能ですが、フラグの状態を保存することは、プログラムテクニックによってもできません。
そのためには、どうしてもスタックという機能が必要になってきます。
なおZ80では、B〜Lレジスタについてメモリを直接指定する命令は用意されていませんが、16ビットCPUの8086になると、Aレジスタだけではなくて、その他のレジスタについても、メモリを直接指定する命令が用意されています。
しかし、8086でもスタックは必要不可欠なものとして、マシン語プログラムを作る上で重要な役割を担っています。
●スタック
スタック(stack)は、(整然と)積み重ねる、または積み重ねたもの、という意味です。
コンピュータ用語では、メモリ上に配置された一時記憶領域で、ランダムアクセスはできず、シーケンシャルアクセスのみ許されます。
イメージとしては狭いトンネル状の格納場所があって、そこにデータをひとつずつ詰め込んでいくのに似ています。
どんどん詰め込んでいくのにしたがって、手前の空きがふさがっていきます。常に最後に詰め込んだデータだけが、入り口から見えています。
先に詰め込んだデータを取り出すには、その手前にあるデータを全部順に取り出してしまわなければ、その奥に詰め込んだデータを取り出すことはできません。
最後に詰め込んだ(または最後に取り出した)ときの、メモリ上のスタックの入り口のアドレスを常に記憶しているのがスタックポインタです。
スタックはメモリの奥のアドレス(大きいアドレス)から手前のアドレス(小さいアドレス、若いアドレス)に向かって詰め込まれていきます。スタックはレジスタペア(16ビット、2バイト)単位で詰め込んだり、取り出したりします。
スタックポインタはデータを詰め込むごとに自動的に−2減算され、データを取り出すごとに+2加算されます。
プログラムの進み方とは逆の方向になります。
スタックの、このアクセス方法から、一般的にはメモリの後ろの方(大きいアドレス)にスタックを置くことが多いようですが、絶対にそうしなければならない、というものではありません。
いままで説明してきた内容を図に描いて整理してみます。
スタックは図のようにメモリの後ろのアドレスから前に向かって詰められていくので、通常はメモリアドレスの後ろの方に置くことが多いのですが、図右のようにメモリの途中のアドレスに置くこともできます。
8080やZ80の場合、指定できるメモリアドレスは16ビットですから、0000〜FFFF(1111 1111 1111 1111)になります。
プログラムは必ず0000からスタートします。
しかしスタックはメモリアドレスの任意の位置から始めることができます。
スタックアドレスはスタックポインタに保持されます。
スタックポインタにスタックアドレスを設定する命令が、[第65回]で説明した、そして今回の書き出しにもあげた、LXI SP命令です。
SP(スタックポインタ)とスタックの関係について説明します。
図ではSPにF800が入っています。
プログラムの実行途中の状態でこうなっている場合もありますし、LXI SP命令で、LXI SP,F800 を実行した結果こうなっている、ということにしてもよいでしょう。
F800から前のメモリアドレスの値が図のようになっていて、BCレジスタとDEレジスタの値がそれぞれ、BC=ABCD、DE=EF01であるとします。
レジスタの値をスタックに保存する命令はPUSH命令です。
PUSF B
PUSH D
PUSH H
PUSH PSW
の4つの命令があります。
PUSH命令の回路については、次回に説明します。
今回はどのような働きをするかについて、例をもって説明します。
上の図の状態で、
PUSH B
PUSH D
のように、2つのPUSH命令を続けて実行すると、メモリ上にとられたスタックはどのように変化するでしょうか。
PUSH B命令は、Bレジスタだけをスタックに入れるのではなくて、BレジスタとCレジスタをあわせた2バイトの値をスタックに入れます。他のレジスタに対するPUSH命令についても同様です。
PUSH Bを実行する前のSPの値はF800でした。
PUSH Bを実行すると、F800ではなくて、そこから−1したF7FFに、上位レジスタであるBレジスタの値が入れられ、次にさらにSPは−1されてF7FEになり、そのアドレスにCレジスタの値が入れられます。
続いてPUSH Dが実行されると、F7FDにDレジスタの値が入れられ、F7FCにEレジスタの値が入れられます。
そしてSPの値はF7FCになります。
PUSH命令によってスタックに入れられた値を取り出すにはPOP命令を使います。
POP命令についても、次回以降に説明をします。
PUSH、POPはプログラムによってスタックを操作する命令ですが、サブルーチンコール命令(CALL)やサブルーチンからメインルーチンに戻るときに実行するリターン命令(RET)でもスタックが使われます。
CALLやRETについても、もう少し先のところで説明をする予定です。
2008.9.14upload
前へ
次へ
ホームページトップへ戻る