2012年11月10日

入出力命令のバグ?

  Z80の入出力命令は、以下のようなものがあります。

IN A,(n) :A <- port (n)
IN r,(C) :r <- port (C)
INI :(HL) <- (C) , HL<-HL+1 , B<-B-1
INIR :(HL) <- (C) , HL<-HL+1 , B<-B-1 , B!=0なら繰り返す
IND :(HL) <- (C) , HL<-HL-1 , B<-B-1
INDR :(HL) <- (C) , HL<-HL-1 , B<-B-1 , B!=0なら繰り返す

OUT (n),A :port (n) <- A
OUT (C),r :port (C) <- r
OUTI :(C) <- (HL) , HL<-HL+1 , B<-B-1
OTIR :(C) <- (HL) , HL<-HL+1 , B<-B-1 , B!=0なら繰り返す
OUTD :(C) <- (HL) , HL<-HL-1 , B<-B-1
OTDR :(C) <- (HL) , HL<-HL-1 , B<-B-1 , B!=0なら繰り返す


  大抵の本には、こう書かれているはずです。

  ですが、Z80 のバグというか仕様で、一部の命令が上記の記載とは違う動きをします。


  その命令は4つで、動作は以下の通りです。

OUTI :B<-B-1 , (C) <- (HL) , HL<-HL+1
OTIR :B<-B-1 , (C) <- (HL) , HL<-HL+1 , B!=0なら繰り返す
OUTD :B<-B-1 , (C) <- (HL) , HL<-HL-1
OTDR :B<-B-1 , (C) <- (HL) , HL<-HL-1 , B!=0なら繰り返す


  Breg のデクリメントが、ポートへ出力するよりも速いんですね。


  別に影響がないように思えますが、実は大有りです。


  Z80 のシステムは、I/O領域へのアクセスは0x00〜0xFF の8ビットが普通なんですが、
 16ビット単位で行う事も可能です。

  上記命令でいうと、Creg を使って入出力をしているものに関しては、アドレスバスには Breg と Creg の内容の両方が出ています。上位8ビットが Breg で、下位8ビットが Creg です。

  通常のシステム(例えばP6)では、下位8ビットしか使わずに、8ビットでのI/O領域を構成しています。


  ただし、66 では、16ビットI/O を使っている箇所が一箇所だけあります。それは、ディスク用バッファの入出力です。


  ディスク用バッファは1Kバイトで、0x000〜0x3FF までのアドレスがあります。
  このアドレスにアクセスするためには、ポート0xD0〜0xD3 を使います。

  0xD0:0x000〜0x0FF
  0xD1:0x100〜0x1FF
  0xD2:0x200〜0x2FF
  0xD3:0x300〜0x3FF

  この時、バッファのアドレスの下位8ビットを表すのに、アドレスバスの上位8ビットが使われます。


  具体的には、

LD BC,012D0H
IN (C),A

  とすると、バッファアドレス0x0012 の内容を Areg に読み出すことができます。



  で、最初の話に戻ると、上記の OUTI/OTIR/OUTD/OTDR を使うと、Breg が先にデクリメントされるため、読み書きするアドレスが一つずれるという現象が起こります。


  Z80 では、他に割り込み系の命令にもバグがあったと思います。また詳しく調べたいと思います。


posted by えすび at 22:52| Comment(14) | P6解析:CPU周り | このブログの読者になる | 更新情報をチェックする
この記事へのコメント
この件,Bernieさんと一緒に悩んでおりました。
挙動は把握できたんですが釈然としないな〜と。
詳細な解説感謝です。すっきりしました!

改めて "Z80 Family CPU User Manual" を見てみると
下記の記載があるため「仕様」の様ですね。
何か実装上の制約があって止む無くこうなった感じがします。

Then, after the byte counter (B) is decremented,the contents of register C are placed on the bottom half (A0 through A7) of the address bus to select the I/O device at one of 256 possible ports.
 Register B may be used as a byte counter, and its decremented value is placed on the top half (A8 through A15) of the address bus.
Posted by ゆみたろ at 2012年11月11日 09:28
 すっきりして何よりです。

 私も詳細に調べてみるまで、勘違いしていました。

 ちなみに調べ方は、CPUデータバスとFDDバッファのアドレスをロジアナで観測しています。
Posted by えすび at 2012年11月11日 20:53
これはハマるところですよね。
私も 66disk 弄っているときに気が付いて慌てて他のエミュレーターの Z80 ソース等々確認した記憶が。。。
ただでさえ P6 は I/O 弄る箇所少ないのに 16bit ブロック転送とか何に使うの??って感じでしたので。

最近では djnz のループ終端 (b=0) でゼロフラグが変化しないと知ってエーという感じです。

>Z80 では、他に割り込み系の命令にもバグがあったと思います。

これは気になります。
Posted by bookworm at 2012年11月12日 07:17
DJNZ のゼロフラグも引っ掛かりますね。

後は、INC B とINC BC のフラグの動きの違いとか。


割り込み関連は、LD A,I と LD A,R の際の P/V フラグの挙動についてです。
このフラグを見ると、割り込み状態なのかそうでないのかが判別できるというものです。

普通は、まあ使わないでしょうけど。




Posted by えすび at 2012年11月12日 08:32
はじめまして。
Z80の割り込み状態取得時のバグについては、この文書↓が分かりやすかったです。
http://rd.vector.co.jp/soft/data/hardware/se056089.html

ブロックI/O命令のBレジスタの挙動に関しては、16ビットI/OアドレスがデフォルトのX1ユーザーにとっては常識に違いない…と思ってX1エミュレータのソースを昔確認したことがあります(^^;
Posted by youkan at 2012年11月12日 12:49
>youkanさん

コメントありがとうございます。

実は割り込みの件は、挙げてもらっていたものから情報を仕入れたものです(^^;
この情報が、P6に当てはまるかは、調べる必要があるかと思っています。面倒ですが...

I/O命令の16ビットの件は、X1では確かに常識ですけど、NEC系はほとんど使ってないんじゃないかと思います。


関係ないですが、ZEPLIS II は X1 のを遊んだかなぁ・・・


Posted by えすび at 2012年11月12日 19:43
どうも、この件ではお世話になりました。
ところで、メールの例でも気になっていたのですが、

LD BC,0D012H
IN A,(C)

の部分は

LD BC,012D0H
IN A,(C)

ですよね?

まさか、IN r,(C) を使うとBregとCregがひっくり返って使用されるということでは無いでしょうし…。
Posted by bernie at 2012年11月13日 22:06
 おっとっと。

 ご指摘ありがとうございます。

LD BC,012D0H
IN A,(C)

 が正解です。


 ブログの本文の方は修正しておきます。

Posted by えすび at 2012年11月13日 22:11
Z80の割り込み関連のバグはnMOS品全般にありますが、ZiLOGのCMOS品では修正
されています。しかしCMOSでも東芝製は修正されていません。
HD64180ではR0マスク(64A180/64B180等)にはあり、次のR1マスク(64180R)で修正された
そうです。

NECのμPD780はもともと非ライセンス品でしたが、この辺のバグは引き継がれた(マネ
した?)ようですね。μPD70008Aではいかがでしょうか?

> rd.vector.co.jp/soft/data/hardware/se056089.html

について筆者にメールした記憶が。

I/OでのBレジスタの挙動は、ゆみたろさんの通り、マニュアルで上位にBレジスタの値が
出ることがドキュメントになっているので隠し機能ではなく仕様ですね。
なぜOUT (C),r8なのかは例のヶ所に関連するような?この件がしっかりしていたら正式に
OUT (BC),r8としていたのかも知れません。
でも繰り返しの際にも16ビットI/Oとして使うことまでは当時想定されていなかった気は
しますが。
(ループの残り回数をデバイスに伝える用途には使えそうですが、これ自体(変化する値)を
アドレスに使うのはちょっと)
セカンドソースではない互換CPUではどうなんでしょうね?64180とかR800とかKC80とか
興味あるところです。

16ビットアドレスについてはZ80で拡張された機能(8080ではOUT (n),Aにて上位・下位とも
同値(n)を出力)ですから、それ自体が仕様になって継承された可能性はありますね。
ブロックI/Oについては8080どころか8086にもなかった機能(80186/V30にて実装)ですし。


ところで、FDDといえば、PC-6031の代用としてPC-8031やPC-80S31が使える(逆は不可)
そうですね。3.5インチの代用にはなりませんが。3.5インチの代用にするには
レア(?)なPC-8831-MWのFDDを3.5インチに載せ替えたものが必要になりそうです。
(動作の保証はできません)

66の場合は素直に2Dや2DD(SR)にするのが近道のような気がします。
2D/2DD FDDにした場合は、両面FDを片面2枚分として使えるようにもできますし。
DiskChangeをReadyにする回路と、Stepを2倍にする回路を設計したので、部品を手配次第
試してみます。これがあれば入手が(比較的)容易なPC用FDDが使えるようになるので。

ここでで恐縮ですが、ゆみたろさん、NDittにサイド1のアンフォーマットと、
(1DD/2DDでの)奇数シリンダのアンフォーマット機能をつけていただければ幸いです。
目的は片面ディスクにサイド1は不要(NDitt/1DDitt以外のツールでは、不要なサイド1も
イメージ化されるため)という点と、1D/2D FDDでの安定性向上のためです。
現在はPC-98実機上のWizard98でEraseしています。2回実行するので面倒です。
1) start 1 step 2
2) start 2 step 4
3) start 3 step 4
1DDには1を、2Dには2と3を、1Dには1と2を実行します。


追伸 シリアルポートの件は投げたわけではないので念のため。でも8251に乗り気でないのは
確かです。カートリッジスロットを使って16550を載せた方が遥かに高性能なので。
TARIOさんの「ピーッ」では16550でのアプローチになっています。
www.d1.dion.ne.jp/~tario/pc6601sr.html

折衷案として、I/Fは8251のままでマイコンで通訳をし、16550を使用する案もあります。
8251モードと16550モードはジャンパで切り替えできるようにするといいでしょう。
(P6からは8251として見えるようにするため、マイコンとしては使えない)
Posted by かかっくん at 2012年11月14日 01:00
ゆみたろさん指摘のZ80 Family CPU User Manualを改めて確認しましたが、
(www.zilog.comから参照できます)
OUTI/OTIR/OUTD/OTDRだけで、INI/INIR/IND/INDRではこうならないんですね。
ますます謎です

他に16ビットI/Oを積極的に使っているのはSMC-777とかですか。上位と下位を逆に
割り当ててあるとか?
xxyyh→yyxxh
Posted by かかっくん at 2012年11月14日 22:38
>かかっくんさん

NDittの件,機能追加自体は簡単にできそうなので考えてみますね。
いつになるかわかりませんが...(忘れちゃったらゴメンナサイ)

奇数シリンダのアンフォーマットは,2DD or 2HDでアンフォーマットしてから書き込めばとりあえずOKな気もします。
Posted by ゆみたろ at 2012年11月15日 20:36
ゆみたろさんダメモトで要望してみましたが、レスありがとうございます。

調子に乗ってもうひとつ。書き込みコマンド時のオプションで不要トラックの
アンフォーマット(消去)も同時にできるようにしていただければ。

> 奇数シリンダのアンフォーマットは,2DD or 2HDでアンフォーマットしてから書き込めばとりあえずOKな気もします。

単純な書き込み時は事前のアンフォーマットで事足りますが、1D/2Dを書き換え
た場合の問題は解決できないので。どちらかというとこちらを重視しています。

でもよく考えたら66SRで書き換えた1DはPC-6601ではエラーになり得ますが、
そのような報告があまりないのは...?まさかFDDが1DDとも考えにくいですし。
PC-6031SRなら1DDですから判りますが。
FM-77AVの後期機種も2DDですから従来機の2Dとの問題も生じるはずですし?
この報告は...?AV系の最後だからユーザも少ないのかも
PC-88/PC-98と比べてユーザが少ないからかな?
PC-88のM系(88VA/98DO含)やPC-98ユーザは圧倒的に多いですからね
Posted by かかっくん at 2012年11月19日 17:50
HD64180のデータシートを参照してみましたが、詳細はありませんでした。が、気になる
記載が。

---
The contents of Register B is output to A0 - A7. The contents of Register C is output to A8 - A15
---
逆になっていますね。他に上位0・下位CのOUTもあるので、これはただの誤植でしょう。

OTIR
---
Q{
(HL)M -> (BC)I
HLR+1 -> HLR
Br-1 -> Br
}

Repeat Q until Br=0
Cr -> A0 - A7
Br -> A8 - A15
---
OUTがループ内にないのが謎です。実際にはループしますが。
結局、データシートからはどのタイミングでBをDECするかは解りませんでした。

64180は後年は組み込みに多用されましたが、元々はCP/M向けのチップで、未定義
命令トラップで0番地にコール(CP/Mではコマンドラインに戻る)したりするのが
特徴ですね。
Posted by かかっくん at 2012年11月20日 02:21
>かかっくんさん

NDittの件ですが,忘れないうちにやってみました。
不要トラックのみアンフォーマットする機能と書込み時に不要なトラックをアンフォーマットする機能を追加しました。(Uオプション)
使い勝手がいいんだか悪いんだかよくわからない感じですが,説明書を見て使ってみてください。バックアップは忘れずに。
本題から随分それてしまいましたね...
Posted by ゆみたろ at 2012年11月21日 23:47
コメントを書く
お名前: [必須入力]

メールアドレス:

ホームページアドレス:

コメント: [必須入力]

認証コード: [必須入力]


※画像の中の文字を半角で入力してください。