2017-04-11 14:01 — asano
ふとZ80ボード 3号機で遊んでいたときに考えたルーチンを思い出しました。
1: 8000 ORG 8000H
2: 8000 check:
3: 8000 7E LD A,(HL)
4: 8001 AE XOR (HL)
5: 8002 C9 RET
ルーチンと呼ぶのもおかしいほどの短いコードです。何をするものでしょうか?
Z80のコードがわからない人のために簡単に解説するとこんな感じです。
- 1,2行目は開始番地とエントリポイントのラベルです。
- 3行目でHLレジスタの指すメモリを読んでAレジスタに入れます。
- 4行目でもHLレジスタの指すメモリを読み、Aレジスタの内容とXOR演算をします。結果はAレジスタに入ります
- 5行目でメインルーチンへ帰ります。
簡単ですね。
3行目と4行目で読むメモリのアドレスは一緒です。それのXORを取るわけですから結果は常に0になります。これでは単なる無駄ですね。C言語等で同様のコードを書いたら最適化で取り除かれてしまうでしょう。
最適化されないためにはCではvolatileを使いますね。それはどんなときでしょうか?
メモリマップされたI/Oや共有メモリなど、ソフトウェアの知らないところで変化する可能性があるときですね。でもZ80にはI/O空間があるのであまりメモリ空間にI/Oを配置しません。もちろん配置しても良いのですが、私のボードではしていません。共有メモリもありません。
ちょっとヒントを書くと、このルーチンから帰るときのAレジスタの内容は00Hか0D0Hになります。ちなみに0D0Hがどこから来るかというと、7EH xor 0AEH = 0D0Hです。
7EH と 0AEH どこかで見た値です。そう、3行目と4行目のオペコードです。
オペコードがどうして演算に登場するのでしょうか?
もう少しハードウェアの動きも含めて解説するとこうなります。
- 1,2行目は開始番地とエントリポイントのラベルです。
エントリポイントに飛んできた時点でPC(プログラムカウンタ)には8000Hが入っています。 - 3行目ではまずPCレジスタの指すメモリを読み、7EHが読まれます。
- これを命令として解釈し、HLレジスタの指すメモリを読んでAレジスタに入れます。
- 4行目でもまずPCレジスタの指すメモリを読み、0AEHが読まれます。
- これを命令として解釈し、HLレジスタの指すメモリを読み、Aレジスタの内容とXOR演算をします。結果はAレジスタに入ります
- 5行目もまずPCレジスタの指すメモリを読み、C9Hが読まれます。
- これを命令として解釈し、スタックから戻り番地を2回に分けて読み、PCに代入することでメインルーチンへ帰ります。
スタックから戻り番地を読むときに上下どちらから読むか(アドレスではなく順序)は確かめたことはありません。
さてここでHLレジスタの指すアドレスにメモリが無かったらどうなるでしょうか? ここからはハードウェアの話になってきます。
直前に命令を読む(フェッチといいます)ためにメモリが7EHや0AEHでデータバスをドライブした後は誰もデータバスをドライブしないまま読み込みが実行されます。この時に何が読み込まれるでしょうか?
データバスはGNDとの間に静電容量を持っています。これは接続されているデバイスの持つ容量と配線自体の容量の合計です。プルアップ抵抗や接続されているデバイスの入力抵抗は比較的大きいのでデータバスはしばらくその状態を保持しています。そのため直前に載っていたデータ(ここでは7EHや0AEH)が残っていてそのまま読み込まれてしまうのです。
つまりこれは、そのアドレスにアクセスしたときにデータバスをドライブするデバイス(つまりメモリ)が存在するかを判定するものなのです。書き込む必要が無いので内容を保持したまま、しかもROMも判定できます。
実際にはどれだけの時間データが保持されているかは様々な条件で異なってくるので実用にはならないでしょう。でも、こういう現象が起きるということは知っておいても良いのではないかと思いますね。
コメントを追加