| | \ S−OS講座・第7回「Z80:スタックとサブルーチン」 / ・光陰矢のごとし!恐るべきは時の流れ哉(このタイトル2回目)  約2年ぶりのごぶさたでしたが(2年も空けて当講座の実用性が疑わしいところでは ある),みなさん寒いなかいかがお過ごしでしょうか.私のほうは,精神性疾患で仕事 を休みはじめてはや1年と10か月,本当に時の過ぎ去るのは速いですね.  さて今回は,メモリの操作上重要な スタック操作命令 と,その応用である サブルーチン について学習します. ・スタックとは...  さて今まで,メモリの操作方法として,「直接アドレッシング」や「HLレジスタ間 接アドレッシング」などを学んできましたが,今回学ぶ「スタック」というのは SP(スタックポインタ)レジスタ をポインタとして使用し,LIFO(Last In First Out:後入れ先出し)バッファを実 現する仕組みです.LIFOとは,ある入れ物(コンピュータの場合は,メモリ)に数 値1,2,3を順番に入れ(PUSH)たとすると,取り出す(POP)ときは順序が 逆になって3,2,1の順で取り出すことができるという意味です.これをメモリとS Pレジスタで実現すると次のようになります. (1)空の状態 メモリ | | | | | | | | SP→  ̄ ̄ ̄ ̄ (底を指している) (2)1,2,3と入れる メモリ | | (PUSH) SP→| 3 |(数値「3」を指している) | 2 | | 1 |  ̄ ̄ ̄ ̄ (3)1つ取り出す メモリ | | (POP) | 3→→取り出し SP→| 2 | | 1 |  ̄ ̄ ̄ ̄ (4)もう1つ取り出す メモリ | | (POP) | | | 2→→取り出し SP→| 1 |  ̄ ̄ ̄ ̄ HLレジスタ間接では,このような操作を行なうには,INC HL や DEC HL を用いて指示 するアドレスの更新をしなければなりませんが,SPレジスタを使えば,データの出し 入れにしたがって,SPレジスタの値が自動的に上下するので便利です.  Z80のPUSH/POP命令では,レジスタペアの単位でメモリに出し入れします ので,次の命令のように出し入れするデータはレジスタペアで指定します. PUSH HL PUSH BC PUSH DE  Aレジスタの場合だけ特殊で,次のようにフラグレジスタ(CフラグやZフラグが入 っている8ビットのレジスタ)とのペアで出し入れします. PUSH AF ただし,SPレジスタは,システムでも使用されています(RET命令の戻り先とか) ので,内容を書き換える時は,メモリの適当な場所(または,他のレジスタ)に値を退 避しておいて,RET時に元に戻す必要があります. 【プログラム】スタックに値を積んでみる. ラベル ニモニック コメント -------+-------+----------------+----------------------------------------- ORG 8000H ; 8000Hより始めよ LD (SPWK),SP ; SPの内容を退避 XOR A ; Acc=0 , Cy=0 , Z=1 , P/V = 1 LD SP,8040H ; スタックの「底」を8040H番地とする LD A,12H LD BC,3456H LD DE,7890H LD HL,0ABCDH PUSH AF PUSH BC PUSH DE PUSH HL LD SP,(SPWK) ; SPの内容を復帰 RET SPWK: DEFS 2 ; SPレジスタの退避場所 -------+-------+----------------+----------------------------------------- 因みに,DEFS 命令は疑似命令の一種で,それが書かれているアドレスから数値分のスペ ース(バイト)を確保します.  では,実際にアセンブル,実行してみましょう. A>A OBJECT CODE END 80ID A>J8000 A>D8000 :8000 ED 73 1C 80 AF 30 40 80 : :8018 7B 1C 80 C9 nn mm .. .. : :8038 CD AB 90 78 56 34 44 12 ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ L H E D C B F A このように,メモリの上位から下位に向かってスタックが積み上げられているのが分か りますね.  余談になりますが,以前学んだCフラグやZフラグは,直接値を他のレジスタに入れ ることはできませんが,PUSH AFでスタック上に値を取り出すことができます. Fレジスタは8ビットのレジスタですが,うち6ビットが意味を持っていて,次のよう な配列になっています. +――+――+――+――+――+――+――+――+ | S | Z | -- | H | -- |P/V | N | C | +――+――+――+――+――+――+――+――+ ・S(サイン)フラグ  算術演算を実行した時の演算結果の符号(+:0,−:1)が入ります. ・Z(ゼロ)フラグ  演算結果が零ならば1,それ以外なら0が入ります. ・H(ハーフキャリ)フラグ  演算結果の下位4ビットで桁上がりが生じた場合,1となります. ・P/V(パリティ/オーバフロー)フラグ  論理演算を実行した時は,演算結果のビットが「1」の数によって,偶数な  らば1が,奇数ならば0が入ります.  算術演算を実行した時は,桁あふれが生じた場合に1,それ以外なら0が入  ります. ・N(減算)フラグ  減算命令を実行したとき1が,それ以外の演算命令を実行した時0が入りま  す. ・C(キャリ)フラグ  演算を実行した結果,桁上がりが生じた時に1,それ以外なら0が入ります。 プログラムでは,3行目のXOR命令がフラグに反映され,Fレジスタの内容が 44H = 0100 0100B …… ZフラグとP/Vフラグがセット となっていることがわかります(因みに XOR A は,Aレジスタをクリアする時に多用さ れます). 【プログラム】スタックから値を取り出してみる. ラベル ニモニック コメント -------+-------+----------------+----------------------------------------- _PRTHL EQU 1FBEH ; HLの内容を16進表示 ORG 8000H ; 8000Hより始めよ XOR A ; Acc=0 , Cy=0 , Z=1 , P/V = 1 LD A,12H LD BC,3456H LD DE,7890H LD HL,0ABCDH PUSH AF ; スタックに値を積む PUSH BC PUSH DE PUSH HL POP HL ; 1つ取り出し CALL _PRTHL ; 表示 POP HL CALL _PRTHL POP HL CALL _PRTHL POP HL CALL _PRTHL RET -------+-------+----------------+-----------------------------------------  実行結果は次のようになります. A>A OBJECT CODE END 8020 A>J8000 ABCD789034561244 A> とまあ,奇麗に逆順に表示されますね.  このプログラムのように,SPレジスタを退避しない場合は,S−OSのモニタが割 り当てたスタックを使うことになります.その場合,1つのプログラムで,PUSHと POPの数を同じにしてください.数が違うと,RET命令で暴走します.なぜなら, CALL命令やRET命令でも同じスタックを使用しているからです.くわしくは次の 項目で取り上げます.  動作的には,PUSH命令やPOP命令は,次のように書き表すことができるでしょ う.注意しておきたいのは,PUSHはSPを−1してから値を書く(プレデクリメン ト)のに対して,POPは値を読んでからSPを+1して(ポストインクリメント)い る点です. PUSH HL  →  DEC SP LD  (SP),H DEC SP LD  (SP),L POP  HL  →  LD  (SP),L INC SP LD  (SP),H INC SP ・サブルーチンの例  今までもS−OSのサブルーチンを呼び出すために,CALL命令を何となく使って きましたが,実はCALL命令ではスタックに戻り先アドレスを積み,RET命令では スタックから戻り先アドレスを取りだしているのです.動作的には CALL nn  →  DEC SP LD  (SP),PCH DEC SP LD (SP),PCL JP  nn RET →  LD  (SP),PCL INC SP LD  (SP),PCH INC SP と実行していることになります.  もちろん呼び出す相手はS−OSのサブルーチンだけでなく,自分の作ったプログラ ムでもよいわけです.それではプログラム例を見てみましょう. 【プログラム】HL←HL+Aを実行するサブルーチン. ラベル ニモニック コメント -------+-------+----------------+----------------------------------------- _PRTHL EQU 1FBEH ; HLの内容を16進表示 ORG 8000H ; 8000Hより始めよ MAIN: LD A,99H ; メインプログラム LD HL,7788H CALL HLADDA CALL _PRTHL RET ; モニタへ戻る HLADDA: ; サブルーチン PUSH AF ; Aレジの内容を退避 ;* ADD A,L ; 下位8ビットの計算 LD L,A LD A,H ; 上位   〃 ADC A,0 LD H,A POP AF ; Aレジの内容を復帰 RET ; メインへ戻る -------+-------+----------------+----------------------------------------- *印を通過した時のスタックの状態は, SP→| nm |……F | 99 |……A | 08 |……メインヘの戻り先アドレス. | 80 | | ?? |……モニタへの戻り先アドレス. | ?? | このようにスタックには,PUSH命令で退避されたデータやCALL命令によって積 まれたアドレスが混在していますので,PUSH命令でストアされたアドレスにRET したり,CALL命令でストアされたアドレスをPOPで取り出したりすることもでき ます(テクニックの一種).もちろん間違えると暴走の元となりますが.  またこのプログラムのサブルーチン部にあるように,サブルーチンの先頭でPUSH して末尾でPOPするというのは実際よく使います.「今Aレジには大事なデータが入 っているから内容を書き換えたくない」場合など,内容を一時的にスタックへ保存して Z80のレジスタを有効に使うために用いられます. ・サブルーチンのネスティング  実際のプログラミングではサブルーチンからサブルーチンを呼び出すことが頻繁にあ ります.そのような時でもスタックを使えば容易にプログラムを作成することができま す.例を見てみましょう. 【プログラム】HL←A*Bを実行するサブルーチン.(簡単型) ラベル ニモニック コメント -------+-------+----------------+----------------------------------------- _PRTHL EQU 1FBEH ; HLの内容を16進表示 ORG 8000H ; 8000Hより始めよ MAIN: LD A,34H ; メインプログラム LD B,56H CALL MULAB CALL _PRTHL RET MULAB: LD HL,0 ; サブルーチン PUSH AF LD A,B OR A ; B=0か? JR NZ,M0 POP AF RET M0: POP AF M1: CALL HLADDA DJNZ M1 RET HLADDA: ; サブルーチンのサブルーチン PUSH AF ; Aレジの内容を退避 ADD A,L ; 下位8ビットの計算 LD L,A LD A,H ; 上位   〃 ADC A,0 LD H,A POP AF ; Aレジの内容を復帰 RET -------+-------+----------------+----------------------------------------- MULAB では,Bレジが0ならHL=0で終わり.そうでなければBの回数分だけHLに Aを足しこんでいます(HLADDAの中身にはそれほど関知する必要はない).Bレジの値 を直接調べる命令はないので,一旦Aレジを退避させておいて,OR A で0かどうか調べ ています.OR A はAとAのANDを計算するという一見意味のなさそうな命令ですが, 演算結果がフラグに反映されますので,Aレジが0であるか調べたり,Cフラグをリセ ットしたりするのによく使われます.  このプログラムではBレジの大きさが大きい程,計算に時間がかかってしまいますが 次回には効率良く掛け算や割り算を実行できる,シフト命令を紹介したいと思います. (できれば,インデックスレジスタ関連やブロック転送,サーチなどもやりたいな) (EOF)