2020-08-01 23:54 — asano
カテゴリー:
Universal MonitorのZ80版にR(egister)コマンド実装していてRレジスタの挙動について誤解していたことがわかりました。そこで今回はZ80のRレジスタについて書いてみることにします。
今さら役に立つ機会なんて無いでしょうけどね。
これまでのRレジスタについての理解はざっとこんなところでした。
- リフレッシュアドレスは7ビットなので7ビットのレジスタ(最上位ビットは常に0)
- 1命令実行のたびにインクリメントされる
LD A,R
,LD R,A
命令で値の出し入れができる
はっきり言ってあまり使い道はありません。ゲームなどの簡易乱数発生器として使うか、プロテクトのチェッカルーチンで解析妨害のために使うくらいしか思いつきません。あとは同じ値を代入し続けてソフト的にリフレッシュ動作を不全にするくらいでしょうか。
普通なら無かったことにしても構わないレジスタなのですが...
せっかくR(egister)コマンド作るなら対応してみたくなったのでやってみました。
まずブレーク(Z80ではRST 38H
を使用)命令で退避する必要がありますが、これはAレジスタを経由しなくてはならないので先にPUSH AF
が必要です。RST 38H
やこれらの処理の間にもインクリメントされてしまうので補正が必要です。
次にレジスタに値を戻すときにもLD R,A
の後にPOP AF
, RET
などが必要で、ここでも補正が必要になります。
さて補正量はいくつにすればよいのでしょう。まずは補正量をともに0にしておいていろいろ試してみます。
最初はブレーク時です。
START: XOR A
LD R,A
RST 38H
Rに0を入れた直後にブレークさせます。この場合R=00となるのが自然と考えられますから、試して表示された値が補正量になります。
これは実際に試すと05Hになりました。実測だけでは納得がいかないので以下のソース(関連部分のみ)を数えてみます。
40/ 38 : ORG 0038H
41/ 38 : C3 2D 08 JP RST38H
1309/ 82D : RST38H:
1310/ 82D : =>TRUE IF USE_REGCMD
1311/ 82D :
1312/ 82D : F5 PUSH AF
1313/ 82E : ED 5F LD A,R
1314/ 830 : 32 37 FF LD (REGR),A
RST 38H
で+1- 0038Hの
JP
で+1 - 082DHの
PUSH AF
で+1 - 082EHの
LD A,R
で+2
Rレジスタは厳密にはM1サイクルのたびにインクリメントされるので、LD A,R
命令のようにプレフィックス付きの命令ではプレフィックスの分余計にインクリメントされます。
これ知識としては知っていましたが、実際に試して確認したのは今回が初めてです。
合計+5となります。
次は実行開始(ブレークからの復帰)時です。
START: LD A,R
RST 38H
R=00に設定しておいて実行します。LD A,R
命令のデコード時にRは+2されるのでR=00の状態で実行すればA=02とならなくてはなりません。
この場合の補正値は02Hでした。同様に見てみます。
388/ 30D : 3A 37 FF LD A,(REGR)
389/ 310 : ED 4F LD R,A
390/ 312 : F1 POP AF
391/ 313 : C9 RET ; POP PC
Rレジスタへの代入後のインクリメントは次のようになります。
- 0310Hの
LD R,A
は+2された後に代入されるので+0 - 0312Hの
POP AF
で+1 - 0313Hの
RET
で+1
合計は+2で実測と一致することがわかります。
以上の補正を加えていろいろ確認していたときに不思議なことが起きました。Rレジスタの最上位ビットが立つことがあるのです。
仕様書(Z80 Family Product Specifications Handbook)を確認したところ、Rレジスタの説明として8ビット・「Provides user-transparent dynamic memory refresh. Lower seven bits are automatically incremented and all eight are placed on the address bus during each instruction fetch cycle refresh time.」とあります。
最上位ビットはLD R,A
で書き込んだものがそのまま保持されるようです。
「補正」処理を単純に引き算で行なったために最上位ビットを変化させてしまったようです。仕方ないので最上位ビットは元の値を保存するように変更です。
ここまでできたら以下を確認をしておきます。
START: XOR A
LD R,A
LD A,R
RST 38H
これをそのまま実行した場合と、LD A,R
の1バイト目の0EDHを0FFHに書き換えてブレークさせた後0EDHに書き戻して継続実行させたときで結果が同一になることを確認しておきます。ブレークが対象プログラムに影響を与えていないことを確かめるためです。
先ほどの仕様書の説明の後半も気になりますね。8ビット分がバスに出力されるということは割り込みか何かで定期的に最上位ビットを反転すれば8ビットリフレッシュできるということか。個人的に未確認ですが、言及している文書見つけました(下記リンク参照)。
というわけで次のようなムダ知識を得てしまったのでした。
- Z80のRレジスタの最上位ビットは書いた値が保存される
- Z80のRレジスタの最上位ビットはリフレッシュ時にA7に出力される
最後にZ80互換プロセッサの場合について書いておきます。
HD64180, Z180の場合
現物を動かせる環境が無いのでデータシートをもとに書いています。実機動作未確認です。
「the least significant seven bits of the R counter (R) serve to count the number of instructions executed by the HD64180. R is incremented for each CPU op-code fetch cycles (each LIR cycles).」とあるのでソフトウェアからはZ80と同等のようです(LIRはLoad Instruction RegisterのことでZ80のM1と同じ)。
ハードウェアの視点だと違ってきます。Z80ではM1サイクル毎にリフレッシュ(Rレジスタのインクリメントも)し仕様にもRレジスタの値をバスに出力すると書かれているのですが、HD64180はLIRでインクリメントするものの実際のリフレッシュ動作はLIRとは独立に実行されます。
どうやら簡易乱数などの互換性維持のためにソフトウェアから見えるRレジスタはZ80と同じ動作をさせ、実際のリフレッシュアドレスのカウンタは別に持っていると思われます。
Z280の場合
「The Refresh register is used by the Z80 CPU to indicate the current refresh address, but does not perform this function in the Z280 CPU; instead, it is another 8-bit register available for the programmer.」
Rレジスタは単なる(使いにくい)汎用の8ビットレジスタになりました。勝手にインクリメントされたりしないのでAレジスタの一時退避などに使用できるようになりましたが、逆に積極的に利用していたソフトウェアは困ることになります。まぁ前述のように使い道は限られているので問題になることはあまり無いと思いますが...
ただUniversal Monitorは困りますので、CPU判別でZ280と判定された場合には「補正」処理をスキップするようにしています。他のZ80互換プロセッサの対応時には要注意ポイントの一つです。
コメント
8bit refresh
言及も何も、公式Databook にそうしろと書いてあります。1992_Zilog_Microprocessors_and_Peripherals (bitsaver にあり) p.1186 Z80-CPU Q&A 参照。他の互換 CPU がどうなっているかの一端は私のとこでまとめてあります。
Re: 8bit refresh
情報ありがとうございます。
この資料 IFF2 が正常に読めないことがある件で読んだ記憶はありましたが、リフレッシュについては気付いていませんでした。
NSC800
NS の NSC800 では、R が 8bit でカウントアップするようになっていますね。
あと、I/O空間の 0xBB に割り込み制御レジスタ(ICR)が存在しますが、write only
なので判別には使えないかもしれません。
Rレジスタ
まさに簡易乱数発生器として使いましたが、値の変化が周期的なので、若干イマイチでしたね。
値の変化が周期なのは、Rレジスタの仕様としてごもっともなのですが。
Re: Rレジスタ
まぁ本来の用途ではないので仕方ないですね。
それで十分な用途なら使えばいいし、もっと良い乱数が必要ならM系列なんかを書くなりするしかないです。
コメントを追加