↑の記事のおまけ。
SRで、バスリクエストOFFにして、速くなるかを確認してみました。
test3p6sr.zip
test3p6 から、BASICの75行を足しただけです。
やっぱりSRは速いです。
SRのWAIT関連もちゃんと調べないとなぁ...
2018年07月01日
動作速度算出(4)
P6月間特別企画...居残り
処理時間を求める、の続きです。
5.スクロールの速度を上げる
処理時間を求める事と直接は関係ないですが...
画面スクロールの処理速度は、
メモリ読み出し→メモリ書き込み→ポインタを進める
が一連の流れで、この部分をどれだけ高速に出来るかになります。
最初のLDIRバージョンでは、1バイト当たり、23クロック
次のLDIバージョンでは、1バイト当たり、18クロック
です。
今のままでは、結構限界に近い速度です。
私が思いついたのは以下の2つです。
1)M1サイクル時のWAIT挿入を止める
通常では本体改造をしない限り、M1サイクルのWAIT挿入を止める事ができません。
ですが、外部RAM(拡張RAMパック)に関しては、M1サイクルのWAITを挿入しても動くようです。
新しいRAMだけでなく、当時のPC-6006などで試しても問題なく動作しました。
これで、1バイト当たり、16クロックです。

2)PUSH-POPを使い、さらに力技にする
PUSH/POP命令はメモリアクセスに関しては最速です。
ただ、自由に書き込み/読みだし位置(SP)を操れないので、その辺りで悩んだ結果、力技にしました(^^;)
1)のM1サイクルWAIT挿入なしとも併せて、1バイト当たり、12.5クロックになっています。

↓これまでのを合わせたプログラムです。ちょっと読みにくいプログラムになってしまいましたが。
test3p6.zip
0:(23clk/1byte)LDIR
1:(18clk/1byte)LDI
2:(16clk/1byte)M1時、WAIT挿入なし
3:(12.5clk/1byte)PUSH-POP
で、0〜3を繰り返し実行します。
途中でSTOPキーを押した時は、PAGEキーを押すと元に戻ります。
80行でFOR-NEXTしていますので、適当に修正してみて下さい。
処理時間を求める、の続きです。
5.スクロールの速度を上げる
処理時間を求める事と直接は関係ないですが...
画面スクロールの処理速度は、
メモリ読み出し→メモリ書き込み→ポインタを進める
が一連の流れで、この部分をどれだけ高速に出来るかになります。
最初のLDIRバージョンでは、1バイト当たり、23クロック
次のLDIバージョンでは、1バイト当たり、18クロック
です。
今のままでは、結構限界に近い速度です。
私が思いついたのは以下の2つです。
1)M1サイクル時のWAIT挿入を止める
通常では本体改造をしない限り、M1サイクルのWAIT挿入を止める事ができません。
ですが、外部RAM(拡張RAMパック)に関しては、M1サイクルのWAITを挿入しても動くようです。
新しいRAMだけでなく、当時のPC-6006などで試しても問題なく動作しました。
これで、1バイト当たり、16クロックです。

2)PUSH-POPを使い、さらに力技にする
PUSH/POP命令はメモリアクセスに関しては最速です。
ただ、自由に書き込み/読みだし位置(SP)を操れないので、その辺りで悩んだ結果、力技にしました(^^;)
1)のM1サイクルWAIT挿入なしとも併せて、1バイト当たり、12.5クロックになっています。

↓これまでのを合わせたプログラムです。ちょっと読みにくいプログラムになってしまいましたが。
test3p6.zip
0:(23clk/1byte)LDIR
1:(18clk/1byte)LDI
2:(16clk/1byte)M1時、WAIT挿入なし
3:(12.5clk/1byte)PUSH-POP
で、0〜3を繰り返し実行します。
途中でSTOPキーを押した時は、PAGEキーを押すと元に戻ります。
80行でFOR-NEXTしていますので、適当に修正してみて下さい。
2018年06月30日
動作速度算出(3)
P6月間特別企画(その30)
処理時間を求める、の続きです。
4.実際に処理時間を求めてみる
まず各命令のサイクル数を横に書いてみます。

ループ/分岐/サブルーチンコールがあるので、それを考慮してサイクル数を算出します。
・10〜24行目まで、DJNZでループするので、96回繰り返しになる。
CALL先以外の合計サイクル数 = 132
→ 132x96 = 12672
・25行目のDJNZは、95回ジャンプ、1回スルー
→ 14x95+9 = 1339
・34行目のLDIRは、31回ジャンプ、1回スルー
→ 23x31+18 = 731
・37行目のLDIRは、6111回ジャンプ、1回スルー
→ 23x6111+18 = 140571
・42行目のLDIRは、31回ジャンプ、1回スルー
→ 23x31+18 = 731
・31行から44行までのサイクル数の合計
→ 142122
・1行目〜27行目までのサイクル数の合計
→ 13+12672+1339+16+142122*96*2 = 27301464
MODE2なので、平均クロックは 1.96MHz
処理時間は、27301464 * 1 / 1.96MHz = 13.929s
128x192のSCREEN3の画面を1ドットずつスクロールさせるだけなんですが、1画面分スクロールするのに大体14秒かかります。
サイクル数の計算は結構面倒なのですが、大抵は繰り返し数が多い所だけの概算で事足ります。
ちょっと遅いので、速くなる工夫をしてみます。
38行の LDIR の部分が少ないサイクル数で動かすと速くなるので、こんな感じ↓に変更してみます。
(CPY_LDIRの中身のみを変更)

LDIR → LDI×32 に展開しています。
この変更で、通常は1バイト当たり、23クロック掛かっていたのが、18 + 0.34 クロックで済みます。
LDIの個数を多くすると、+0.34クロックの部分を減らす事が出来ます。
二つのプログラムを連続するものを作りました↓
目に見えて速度が違うと思います。
test2p6.zip
mk2以降で test2_p6.d88 をセットして起動させて下さい。
画像データは、前回と同じ、Hashiさんの所のデータを使用させて頂きました。感謝です>Hashiさん
さらに速度を上げるには...いろいろ考えましたが、まだ出来そうですが........
続きます(^^;)
処理時間を求める、の続きです。
4.実際に処理時間を求めてみる
まず各命令のサイクル数を横に書いてみます。

ループ/分岐/サブルーチンコールがあるので、それを考慮してサイクル数を算出します。
・10〜24行目まで、DJNZでループするので、96回繰り返しになる。
CALL先以外の合計サイクル数 = 132
→ 132x96 = 12672
・25行目のDJNZは、95回ジャンプ、1回スルー
→ 14x95+9 = 1339
・34行目のLDIRは、31回ジャンプ、1回スルー
→ 23x31+18 = 731
・37行目のLDIRは、6111回ジャンプ、1回スルー
→ 23x6111+18 = 140571
・42行目のLDIRは、31回ジャンプ、1回スルー
→ 23x31+18 = 731
・31行から44行までのサイクル数の合計
→ 142122
・1行目〜27行目までのサイクル数の合計
→ 13+12672+1339+16+142122*96*2 = 27301464
MODE2なので、平均クロックは 1.96MHz
処理時間は、27301464 * 1 / 1.96MHz = 13.929s
128x192のSCREEN3の画面を1ドットずつスクロールさせるだけなんですが、1画面分スクロールするのに大体14秒かかります。
サイクル数の計算は結構面倒なのですが、大抵は繰り返し数が多い所だけの概算で事足ります。
ちょっと遅いので、速くなる工夫をしてみます。
38行の LDIR の部分が少ないサイクル数で動かすと速くなるので、こんな感じ↓に変更してみます。
(CPY_LDIRの中身のみを変更)

LDIR → LDI×32 に展開しています。
この変更で、通常は1バイト当たり、23クロック掛かっていたのが、18 + 0.34 クロックで済みます。
LDIの個数を多くすると、+0.34クロックの部分を減らす事が出来ます。
二つのプログラムを連続するものを作りました↓
目に見えて速度が違うと思います。
test2p6.zip
mk2以降で test2_p6.d88 をセットして起動させて下さい。
画像データは、前回と同じ、Hashiさんの所のデータを使用させて頂きました。感謝です>Hashiさん
さらに速度を上げるには...いろいろ考えましたが、まだ出来そうですが........
続きます(^^;)
2018年06月29日
動作速度算出(2)
P6月間特別企画(その29)
処理時間を求める、の続きです。
3.各命令の実行時間とM1サイクル
それぞれの命令に掛かる時間は、命令ごとによって違います。Z80 のドキュメントを参考にします。
Z80 USER MANUAL で検索すると、Zilog社のpdfファイルが見つかると思います。
http://www.zilog.com/appnotes_download.php?FromPage=DirectLink&dn=UM0080&ft=User%20Manual&f=YUhSMGNEb3ZMM2QzZHk1NmFXeHZaeTVqYjIwdlpHOWpjeTk2T0RBdlZVMHdNRGd3TG5Ca1pnPT0=
それぞれの命令の説明の、T states(ステート数)というのが、その命令を実行するのに掛かるクロック数です。
一番短いクロックは、4クロック(LD A,B や NOP など)です。
T states(ステート数)は、それぞれの命令について全部明記されているので、それを合計していけば全体の処理クロック数になる...といいのですが、P6の場合、さらに M1サイクルを考慮する必要があります。なぜ M1サイクルを考慮する必要があるかというと、P6では M1サイクルに 1WAIT 挿入されるからです。
Z80が命令を処理する際、
オペコードを読み込む(M1サイクル)
メモリから読み出す (M2サイクル)
メモリに書き込む (M3サイクル)
の順序で処理が行われます。
M2、M3サイクルは、命令によっては存在しないのですが、M1サイクルは全ての命令に存在します。
ややこしい事に、命令によってはM1サイクルが1命令内で2つあるものがあります。
オペコードで表した時に、0xCB、0xED、0xDD、0xFD で始まる命令は、M1サイクルが2つあります。
0xCB:ビット演算系
0xED:Z80で拡張された命令
0xDD:IX系
0xFD:IX系
『M1サイクルに 1WAIT 挿入』というのは、M1サイクル時にメモリから読み出す際に、1クロック分の待ち時間を挿入するという意味です。WAIT(待ち時間)の挿入というのは、CPUに対してメモリ動作が遅いために取られている処置です。
WAITが挿入されるのは以下の通りです。
○初代機:
M1サイクルで、1WAIT
ROM領域(0x0000〜0x7FFF)へのメモリリード/ライトで、1WAIT
○mk2/66:
ポート0xF3:bit7-5の設定に従う。
ポート0xF3:bit7:M1 サイクルでのWAIT (0:OFF 1:ON)
ポート0xF3:bit6:ROMブロックでのWAIT (0:OFF 1:ON)
ポート0xF3:bit5:RAMブロックでのWAIT (0:OFF 1:ON)
(bit4-0は割り込み関連の設定)
デフォルトは 0xC2 で、M1サイクルとROM領域のみ、1WAIT挿入されます(初代機と同じ)。
WAIT挿入を無くせればCPUの動作が速くなるのですが、やっかいな事に普通ではこのWAIT挿入を無くす事が出来ません。
詳しくはこちらを参照して下さい→http://sbeach.seesaa.net/article/387861448.html
結局、通常はM1サイクルが1or2あるものとして計算する事になります。
例えば、
LD A,B :ステート数が4なので、4+1クロック
ADC HL,HL:ステート数が15で 0xED から始まるので、15+2クロック
という感じで計算できます。
まだ続きます。
処理時間を求める、の続きです。
3.各命令の実行時間とM1サイクル
それぞれの命令に掛かる時間は、命令ごとによって違います。Z80 のドキュメントを参考にします。
Z80 USER MANUAL で検索すると、Zilog社のpdfファイルが見つかると思います。
http://www.zilog.com/appnotes_download.php?FromPage=DirectLink&dn=UM0080&ft=User%20Manual&f=YUhSMGNEb3ZMM2QzZHk1NmFXeHZaeTVqYjIwdlpHOWpjeTk2T0RBdlZVMHdNRGd3TG5Ca1pnPT0=
それぞれの命令の説明の、T states(ステート数)というのが、その命令を実行するのに掛かるクロック数です。
一番短いクロックは、4クロック(LD A,B や NOP など)です。
T states(ステート数)は、それぞれの命令について全部明記されているので、それを合計していけば全体の処理クロック数になる...といいのですが、P6の場合、さらに M1サイクルを考慮する必要があります。なぜ M1サイクルを考慮する必要があるかというと、P6では M1サイクルに 1WAIT 挿入されるからです。
Z80が命令を処理する際、
オペコードを読み込む(M1サイクル)
メモリから読み出す (M2サイクル)
メモリに書き込む (M3サイクル)
の順序で処理が行われます。
M2、M3サイクルは、命令によっては存在しないのですが、M1サイクルは全ての命令に存在します。
ややこしい事に、命令によってはM1サイクルが1命令内で2つあるものがあります。
オペコードで表した時に、0xCB、0xED、0xDD、0xFD で始まる命令は、M1サイクルが2つあります。
0xCB:ビット演算系
0xED:Z80で拡張された命令
0xDD:IX系
0xFD:IX系
『M1サイクルに 1WAIT 挿入』というのは、M1サイクル時にメモリから読み出す際に、1クロック分の待ち時間を挿入するという意味です。WAIT(待ち時間)の挿入というのは、CPUに対してメモリ動作が遅いために取られている処置です。
WAITが挿入されるのは以下の通りです。
○初代機:
M1サイクルで、1WAIT
ROM領域(0x0000〜0x7FFF)へのメモリリード/ライトで、1WAIT
○mk2/66:
ポート0xF3:bit7-5の設定に従う。
ポート0xF3:bit7:M1 サイクルでのWAIT (0:OFF 1:ON)
ポート0xF3:bit6:ROMブロックでのWAIT (0:OFF 1:ON)
ポート0xF3:bit5:RAMブロックでのWAIT (0:OFF 1:ON)
(bit4-0は割り込み関連の設定)
デフォルトは 0xC2 で、M1サイクルとROM領域のみ、1WAIT挿入されます(初代機と同じ)。
WAIT挿入を無くせればCPUの動作が速くなるのですが、やっかいな事に普通ではこのWAIT挿入を無くす事が出来ません。
詳しくはこちらを参照して下さい→http://sbeach.seesaa.net/article/387861448.html
結局、通常はM1サイクルが1or2あるものとして計算する事になります。
例えば、
LD A,B :ステート数が4なので、4+1クロック
ADC HL,HL:ステート数が15で 0xED から始まるので、15+2クロック
という感じで計算できます。
まだ続きます。
2018年06月28日
動作速度算出(1)
P6月間特別企画(その28)
問題です。
○次のプログラムは何をするものでしょう?(MODE=2/4、PAGE=4)

答えは...実際に動かしてみて下さい。実際に動く形にしたものが↓です。
testp6.zip
画像データも必要なので、FDで動作させるようにしました。
mk2以降で test_p6.d88 をセットして起動させて下さい。
画像データは、Hashiさんの所のデータを使用させて頂きました。感謝です>Hashiさん
では、本題です。
○上のプログラムを1度動かした場合、どれくらいの時間が掛かるでしょうか?
という問題を答えるために、動作速度の計算が出来る必要があります。
P6で、動作速度について言及しているものを見たことがないような気がして、とりあえずまとめてみる事にしました。
(注意)
今回は、初代/mk2/66について、になります。
mk2SR/66SR に関しては、ちゃんと調査できていないので、また後日にまとめる事にします。
ただ、mk2SR/66SR 上で、MODE=1〜5で動作させる場合は、初期設定時では初代/mk2/66と同等の速度になるように設計されていますので、参考になるかと思います。
プログラムのある処理がどれだけの時間が掛かるかを算出するためには、
・CPUの1クロックが何MHzで動作するか(=1クロックは、何秒か)
・処理の合計が何クロックで動くか
が分かれば、その積で時間が算出できますが、P6特有の事情によりちょっと変わってきます。
1.CPUクロック
CPUは、入力されるクロックを基準として動作しています。
初代機:3.9936MHz
mk2:3.9936MHz
66:4.0000MHz
計算の際は面倒なので全部4MHzで構わないと思います。
4MHzなので1クロックは 1/4MHz = 250ns になります。
2.バスリクエスト(nBASREQ)
初代/mk2/66では、VRAMがメインRAMでもあります。
そのため両方からのアクセスがぶつからないように、画面描画をする時にCPUを止めています。
CPUに対して、アクセスしないように要求する信号がバスリクエスト(nBASREQ:負論理入力)で、実際にCPUが止まっているのを示しているのが、バスアックノリッジ(nBASACK:負論理出力)です。
mk2で実際に波形を表示させたものが↓です(MODE=2)。

真ん中やや下(下から5本目)が nBASACK です。"H"の時はCPUが動作、"L"の時は停止します。
nBASACK を見ると、H の区間がちょっと続いた後、縞模様(=L/Hの繰り返し)があり、また H の区間が続き...となっているのが分かると思います。
この H の区間は、画面に描画していない区間になります。長く H が続いている区間は、画面の一番下を表示し終わって、次フレームの画面の一番上を表示するまでの間になります。
縞模様(=L/Hの繰り返し)を拡大したものが↓です(MODE=2)。

L の区間、H の区間、…と続きますが、L の区間がちょっと長いです。
この L の区間の間に、画面描画を行っています。
MODE5の場合は、L と H の割合が変わります↓

MODE5の方が、描画するデータが多いため、CPUが止まっている時間が長くなります。
停止している時間を図示すると、こんな感じです。

で、CPUが止まっている事を考慮に入れて、平均のクロック速度を算出しておきます。
MODE1〜4:
1フレーム時間のクロック数 = 912×262
1フレーム時間のCPU停止クロック数 = 608×200
平均クロック速度 = 4MHz × (912×262 - 608×200) / 912×262 = 1.96MHz
MODE5:
1フレーム時間のクロック数 = 912×262
1フレーム時間のCPU停止クロック数 = 736×200
平均クロック速度 = 4MHz × (912×262 - 736×200) / 912×262 = 1.54MHz
かなーり画像表示のために、CPU停止を食らっていますね。
続きます。
問題です。
○次のプログラムは何をするものでしょう?(MODE=2/4、PAGE=4)

答えは...実際に動かしてみて下さい。実際に動く形にしたものが↓です。
testp6.zip
画像データも必要なので、FDで動作させるようにしました。
mk2以降で test_p6.d88 をセットして起動させて下さい。
画像データは、Hashiさんの所のデータを使用させて頂きました。感謝です>Hashiさん
では、本題です。
○上のプログラムを1度動かした場合、どれくらいの時間が掛かるでしょうか?
という問題を答えるために、動作速度の計算が出来る必要があります。
P6で、動作速度について言及しているものを見たことがないような気がして、とりあえずまとめてみる事にしました。
(注意)
今回は、初代/mk2/66について、になります。
mk2SR/66SR に関しては、ちゃんと調査できていないので、また後日にまとめる事にします。
ただ、mk2SR/66SR 上で、MODE=1〜5で動作させる場合は、初期設定時では初代/mk2/66と同等の速度になるように設計されていますので、参考になるかと思います。
プログラムのある処理がどれだけの時間が掛かるかを算出するためには、
・CPUの1クロックが何MHzで動作するか(=1クロックは、何秒か)
・処理の合計が何クロックで動くか
が分かれば、その積で時間が算出できますが、P6特有の事情によりちょっと変わってきます。
1.CPUクロック
CPUは、入力されるクロックを基準として動作しています。
初代機:3.9936MHz
mk2:3.9936MHz
66:4.0000MHz
計算の際は面倒なので全部4MHzで構わないと思います。
4MHzなので1クロックは 1/4MHz = 250ns になります。
2.バスリクエスト(nBASREQ)
初代/mk2/66では、VRAMがメインRAMでもあります。
そのため両方からのアクセスがぶつからないように、画面描画をする時にCPUを止めています。
CPUに対して、アクセスしないように要求する信号がバスリクエスト(nBASREQ:負論理入力)で、実際にCPUが止まっているのを示しているのが、バスアックノリッジ(nBASACK:負論理出力)です。
mk2で実際に波形を表示させたものが↓です(MODE=2)。
真ん中やや下(下から5本目)が nBASACK です。"H"の時はCPUが動作、"L"の時は停止します。
nBASACK を見ると、H の区間がちょっと続いた後、縞模様(=L/Hの繰り返し)があり、また H の区間が続き...となっているのが分かると思います。
この H の区間は、画面に描画していない区間になります。長く H が続いている区間は、画面の一番下を表示し終わって、次フレームの画面の一番上を表示するまでの間になります。
縞模様(=L/Hの繰り返し)を拡大したものが↓です(MODE=2)。
L の区間、H の区間、…と続きますが、L の区間がちょっと長いです。
この L の区間の間に、画面描画を行っています。
MODE5の場合は、L と H の割合が変わります↓
MODE5の方が、描画するデータが多いため、CPUが止まっている時間が長くなります。
停止している時間を図示すると、こんな感じです。
で、CPUが止まっている事を考慮に入れて、平均のクロック速度を算出しておきます。
MODE1〜4:
1フレーム時間のクロック数 = 912×262
1フレーム時間のCPU停止クロック数 = 608×200
平均クロック速度 = 4MHz × (912×262 - 608×200) / 912×262 = 1.96MHz
MODE5:
1フレーム時間のクロック数 = 912×262
1フレーム時間のCPU停止クロック数 = 736×200
平均クロック速度 = 4MHz × (912×262 - 736×200) / 912×262 = 1.54MHz
かなーり画像表示のために、CPU停止を食らっていますね。
続きます。
2018年03月27日
Z80の命令表のバグ?
SD6031関連でS/Wを変更した時に遭遇したZ80の問題です。
多分普通では遭遇しないかと思います...
その時は、INI 命令を使っていたプログラムなんですが、どうも挙動不審でした。
実機で動作を調べてみると、命令前後で Cflagが変化していました。
公式のドキュメントでは、Cflagは変化しないようになっています(unknown ではなく、not affected)。また、大抵の文献でも変化なし、になっています。
非公式のドキュメント(The Undocumented Z80 Documented:http://www.z80.info/zip/z80-documented.pdf)では、しっかりCflagが変化する事が書かれています(条件がややこしい)。
同様に、INI/IND/INIR/INDR、OUTI/OUTD/OTIR/OTDR、はすべて Cflag が変化する事があるようです。
ほとんど使わない命令ですが、参考まで。
多分普通では遭遇しないかと思います...
その時は、INI 命令を使っていたプログラムなんですが、どうも挙動不審でした。
実機で動作を調べてみると、命令前後で Cflagが変化していました。
公式のドキュメントでは、Cflagは変化しないようになっています(unknown ではなく、not affected)。また、大抵の文献でも変化なし、になっています。
非公式のドキュメント(The Undocumented Z80 Documented:http://www.z80.info/zip/z80-documented.pdf)では、しっかりCflagが変化する事が書かれています(条件がややこしい)。
同様に、INI/IND/INIR/INDR、OUTI/OUTD/OTIR/OTDR、はすべて Cflag が変化する事があるようです。
ほとんど使わない命令ですが、参考まで。
2015年11月21日
メモリ割り当て
前回の続きです。
参考文献:Mr.PCテクニカルコレクション 秀和システムトレーディング
○ポート0x60:CPUアドレス:0x0000〜0x1FFFの読み出し設定
○ポート0x61:CPUアドレス:0x2000〜0x3FFFの読み出し設定
○ポート0x62:CPUアドレス:0x4000〜0x5FFFの読み出し設定
○ポート0x63:CPUアドレス:0x6000〜0x7FFFの読み出し設定
○ポート0x64:CPUアドレス:0x8000〜0x9FFFの読み出し設定
○ポート0x65:CPUアドレス:0xA000〜0xBFFFの読み出し設定
○ポート0x66:CPUアドレス:0xC000〜0xDFFFの読み出し設定
○ポート0x67:CPUアドレス:0xE000〜0xFFFFの読み出し設定
○ポート0x68:CPUアドレス:0x0000〜0x1FFFの書き込み設定
○ポート0x69:CPUアドレス:0x2000〜0x3FFFの書き込み設定
○ポート0x6A:CPUアドレス:0x4000〜0x5FFFの書き込み設定
○ポート0x6B:CPUアドレス:0x6000〜0x7FFFの書き込み設定
○ポート0x6C:CPUアドレス:0x8000〜0x9FFFの書き込み設定
○ポート0x6D:CPUアドレス:0xA000〜0xBFFFの書き込み設定
○ポート0x6E:CPUアドレス:0xC000〜0xDFFFの書き込み設定
○ポート0x6F:CPUアドレス:0xE000〜0xFFFFの書き込み設定
・SR以降のみに存在するI/Oポートです。
・SRモード(ポート0xC8:bit0=0)以外では動作しません。
・書き込みのみ。読み出しはできません(読み出すと0xFFになる)。
書き込むデータは次の通り。
bit7-4:割り当てるメモリの種別
bit7-4=0000:内部RAM(VRAM)
bit7-4=0010:外部RAM(拡張RAM)
bit7-4=1011:外部ROM1(拡張ROM(nCS2):通常、0x6000〜0x7FFFに割り当てる)
bit7-4=1100:外部ROM2(拡張ROM(nCS3):通常、0x4000〜0x5FFFに割り当てる)
bit7-4=1101:CGROM
bit7-4=1110:SYSTEMROM2(音声合成/漢字)
bit7-4=1111:SYSTEMROM1(BASIC-ROM)
それ以外は未使用
bit3-1:割り当てるメモリの先頭アドレス(アドレスバスA15-13に出力する値)
bit0:常に"0"
◎解説
CPUからメモリなどにアクセスする際に、8Kバイトごとに自由に割り当てる事が出来る機能です(内部RAMは制限あり。後述)。
SRモード専用の機能で、SRモード以外は使用できません。mk2/66モードのポート0xF0〜0xF2に似たような機能がありますが、ポート0xF0〜0xF2はバンク切り替えのイメージです。
設定値の与え方がちょっと分かりにくい(特にbit3-1)ですので、具体的な例を挙げてみます。
SYSTEMROM1(BASIC-ROM)の0x8000〜0x9FFFを、0x0000〜0x1FFFから読み出したい場合は、
・設定するポートは、0x0000から読み出すので、ポート0x60に書き込む。
・設定値bit7-4=SYSTEMROM1(BASIC-ROM)、1111
・設定値bit3-0=エリアの先頭に0x8000を割り当てるから、0x8=1000
となって、ポート0x60に0xF8を書き込む。
外部RAM(拡張RAM)の0x6000〜0x7FFFを、0x4000〜0x5FFFから書き込みたい場合は、
・設定するポートは、0x4000から書き込むので、ポート0x6Cに書き込む。
・設定値bit7-4=外部RAM(拡張RAM)なので、0010
・設定値bit3-0=エリアの先頭に0x6000を割り当てるから、0x6=0110
となって、ポート0x6Cに0x26を書き込む。
このメモリ割り当て機能ですが、回路的にはCPUアドレスのA15-13を入力として、新しくアドレスRA15-13を生成する回路と、チップセレクトを生成する回路になります。
A15-13からRA15-13を生成する回路は、こんな感じです。

メモリアクセスに関しては、すべてRA15-13を使うのですが、内部RAM(VRAM)だけは、RA15-14とA13を使っています。多分バグだと思われます。

そのため内部RAM(VRAM)に関しては、以下のような動作になります。
○ポート0x60:CPUアドレス:0x0000〜0x1FFFの読み出し設定
○ポート0x62:CPUアドレス:0x4000〜0x5FFFの読み出し設定
○ポート0x64:CPUアドレス:0x8000〜0x9FFFの読み出し設定
○ポート0x66:CPUアドレス:0xC000〜0xDFFFの読み出し設定
○ポート0x68:CPUアドレス:0x0000〜0x1FFFの書き込み設定
○ポート0x6A:CPUアドレス:0x4000〜0x5FFFの書き込み設定
○ポート0x6C:CPUアドレス:0x8000〜0x9FFFの書き込み設定
○ポート0x6E:CPUアドレス:0xC000〜0xDFFFの書き込み設定
内部RAM(VRAM)を割り当てる場合は、以下の値しか取れない。
0x0000〜0x1FFF:書き込むデータbit7-0=00000000
0x4000〜0x5FFF:書き込むデータbit7-0=00000100
0x8000〜0x9FFF:書き込むデータbit7-0=00001000
0xC000〜0xDFFF:書き込むデータbit7-0=00001100
○ポート0x61:CPUアドレス:0x2000〜0x3FFFの読み出し設定
○ポート0x63:CPUアドレス:0x6000〜0x7FFFの読み出し設定
○ポート0x65:CPUアドレス:0xA000〜0xBFFFの読み出し設定
○ポート0x67:CPUアドレス:0xE000〜0xFFFFの読み出し設定
○ポート0x69:CPUアドレス:0x2000〜0x3FFFの書き込み設定
○ポート0x6B:CPUアドレス:0x6000〜0x7FFFの書き込み設定
○ポート0x6D:CPUアドレス:0xA000〜0xBFFFの書き込み設定
○ポート0x6F:CPUアドレス:0xE000〜0xFFFFの書き込み設定
内部RAM(VRAM)を割り当てる場合は、以下の値しか取れない。
0x2000〜0x3FFF:書き込むデータbit7-0=00000010
0x6000〜0x7FFF:書き込むデータbit7-0=00000110
0xA000〜0xBFFF:書き込むデータbit7-0=00001010
0xE000〜0xFFFF:書き込むデータbit7-0=00001110
また、書き込むデータbit3-1が、RA15-13に出力されますが、内部RAM(VRAM)使用時はRA13は未接続のために、bit1は無視されます。
上記制限のため、例えば
CPUアドレス 0x0000〜0x1FFF に、内部RAM(VRAM)0x4000〜0x5FFF を割り当て
CPUアドレス 0x2000〜0x3FFF に、内部RAM(VRAM)0x2000〜0x3FFF を割り当て
CPUアドレス 0x4000〜0x5FFF に、内部RAM(VRAM)0x0000〜0x1FFF を割り当て
CPUアドレス 0x6000〜0x6FFF に、内部RAM(VRAM)0x2000〜0x3FFF を割り当て
はOK(複数から同じRAMに割り当ててもOK)なのですが、
CPUアドレス 0x0000〜0x1FFF に、内部RAM(VRAM)0x2000〜0x3FFF を割り当て
CPUアドレス 0x2000〜0x3FFF に、内部RAM(VRAM)0x0000〜0x1FFF を割り当て
はNGです。
また、内部RAMはVRAMとして使用されていますが、メモリ割り当てではあくまでもCPUからのアクセスアドレスが変わるだけなので、画面表示への影響はありません。
さらにVRAMでビットマップモードを使用した場合は、メモリ割り当てでアドレス変換された後に、ビットマップ変換がされます。詳しくはSRグラフィックの説明の際に記載します。
参考文献:Mr.PCテクニカルコレクション 秀和システムトレーディング
○ポート0x60:CPUアドレス:0x0000〜0x1FFFの読み出し設定
○ポート0x61:CPUアドレス:0x2000〜0x3FFFの読み出し設定
○ポート0x62:CPUアドレス:0x4000〜0x5FFFの読み出し設定
○ポート0x63:CPUアドレス:0x6000〜0x7FFFの読み出し設定
○ポート0x64:CPUアドレス:0x8000〜0x9FFFの読み出し設定
○ポート0x65:CPUアドレス:0xA000〜0xBFFFの読み出し設定
○ポート0x66:CPUアドレス:0xC000〜0xDFFFの読み出し設定
○ポート0x67:CPUアドレス:0xE000〜0xFFFFの読み出し設定
○ポート0x68:CPUアドレス:0x0000〜0x1FFFの書き込み設定
○ポート0x69:CPUアドレス:0x2000〜0x3FFFの書き込み設定
○ポート0x6A:CPUアドレス:0x4000〜0x5FFFの書き込み設定
○ポート0x6B:CPUアドレス:0x6000〜0x7FFFの書き込み設定
○ポート0x6C:CPUアドレス:0x8000〜0x9FFFの書き込み設定
○ポート0x6D:CPUアドレス:0xA000〜0xBFFFの書き込み設定
○ポート0x6E:CPUアドレス:0xC000〜0xDFFFの書き込み設定
○ポート0x6F:CPUアドレス:0xE000〜0xFFFFの書き込み設定
・SR以降のみに存在するI/Oポートです。
・SRモード(ポート0xC8:bit0=0)以外では動作しません。
・書き込みのみ。読み出しはできません(読み出すと0xFFになる)。
書き込むデータは次の通り。
bit7-4:割り当てるメモリの種別
bit7-4=0000:内部RAM(VRAM)
bit7-4=0010:外部RAM(拡張RAM)
bit7-4=1011:外部ROM1(拡張ROM(nCS2):通常、0x6000〜0x7FFFに割り当てる)
bit7-4=1100:外部ROM2(拡張ROM(nCS3):通常、0x4000〜0x5FFFに割り当てる)
bit7-4=1101:CGROM
bit7-4=1110:SYSTEMROM2(音声合成/漢字)
bit7-4=1111:SYSTEMROM1(BASIC-ROM)
それ以外は未使用
bit3-1:割り当てるメモリの先頭アドレス(アドレスバスA15-13に出力する値)
bit0:常に"0"
◎解説
CPUからメモリなどにアクセスする際に、8Kバイトごとに自由に割り当てる事が出来る機能です(内部RAMは制限あり。後述)。
SRモード専用の機能で、SRモード以外は使用できません。mk2/66モードのポート0xF0〜0xF2に似たような機能がありますが、ポート0xF0〜0xF2はバンク切り替えのイメージです。
設定値の与え方がちょっと分かりにくい(特にbit3-1)ですので、具体的な例を挙げてみます。
SYSTEMROM1(BASIC-ROM)の0x8000〜0x9FFFを、0x0000〜0x1FFFから読み出したい場合は、
・設定するポートは、0x0000から読み出すので、ポート0x60に書き込む。
・設定値bit7-4=SYSTEMROM1(BASIC-ROM)、1111
・設定値bit3-0=エリアの先頭に0x8000を割り当てるから、0x8=1000
となって、ポート0x60に0xF8を書き込む。
外部RAM(拡張RAM)の0x6000〜0x7FFFを、0x4000〜0x5FFFから書き込みたい場合は、
・設定するポートは、0x4000から書き込むので、ポート0x6Cに書き込む。
・設定値bit7-4=外部RAM(拡張RAM)なので、0010
・設定値bit3-0=エリアの先頭に0x6000を割り当てるから、0x6=0110
となって、ポート0x6Cに0x26を書き込む。
このメモリ割り当て機能ですが、回路的にはCPUアドレスのA15-13を入力として、新しくアドレスRA15-13を生成する回路と、チップセレクトを生成する回路になります。
A15-13からRA15-13を生成する回路は、こんな感じです。
メモリアクセスに関しては、すべてRA15-13を使うのですが、内部RAM(VRAM)だけは、RA15-14とA13を使っています。多分バグだと思われます。
そのため内部RAM(VRAM)に関しては、以下のような動作になります。
○ポート0x60:CPUアドレス:0x0000〜0x1FFFの読み出し設定
○ポート0x62:CPUアドレス:0x4000〜0x5FFFの読み出し設定
○ポート0x64:CPUアドレス:0x8000〜0x9FFFの読み出し設定
○ポート0x66:CPUアドレス:0xC000〜0xDFFFの読み出し設定
○ポート0x68:CPUアドレス:0x0000〜0x1FFFの書き込み設定
○ポート0x6A:CPUアドレス:0x4000〜0x5FFFの書き込み設定
○ポート0x6C:CPUアドレス:0x8000〜0x9FFFの書き込み設定
○ポート0x6E:CPUアドレス:0xC000〜0xDFFFの書き込み設定
内部RAM(VRAM)を割り当てる場合は、以下の値しか取れない。
0x0000〜0x1FFF:書き込むデータbit7-0=00000000
0x4000〜0x5FFF:書き込むデータbit7-0=00000100
0x8000〜0x9FFF:書き込むデータbit7-0=00001000
0xC000〜0xDFFF:書き込むデータbit7-0=00001100
○ポート0x61:CPUアドレス:0x2000〜0x3FFFの読み出し設定
○ポート0x63:CPUアドレス:0x6000〜0x7FFFの読み出し設定
○ポート0x65:CPUアドレス:0xA000〜0xBFFFの読み出し設定
○ポート0x67:CPUアドレス:0xE000〜0xFFFFの読み出し設定
○ポート0x69:CPUアドレス:0x2000〜0x3FFFの書き込み設定
○ポート0x6B:CPUアドレス:0x6000〜0x7FFFの書き込み設定
○ポート0x6D:CPUアドレス:0xA000〜0xBFFFの書き込み設定
○ポート0x6F:CPUアドレス:0xE000〜0xFFFFの書き込み設定
内部RAM(VRAM)を割り当てる場合は、以下の値しか取れない。
0x2000〜0x3FFF:書き込むデータbit7-0=00000010
0x6000〜0x7FFF:書き込むデータbit7-0=00000110
0xA000〜0xBFFF:書き込むデータbit7-0=00001010
0xE000〜0xFFFF:書き込むデータbit7-0=00001110
また、書き込むデータbit3-1が、RA15-13に出力されますが、内部RAM(VRAM)使用時はRA13は未接続のために、bit1は無視されます。
上記制限のため、例えば
CPUアドレス 0x0000〜0x1FFF に、内部RAM(VRAM)0x4000〜0x5FFF を割り当て
CPUアドレス 0x2000〜0x3FFF に、内部RAM(VRAM)0x2000〜0x3FFF を割り当て
CPUアドレス 0x4000〜0x5FFF に、内部RAM(VRAM)0x0000〜0x1FFF を割り当て
CPUアドレス 0x6000〜0x6FFF に、内部RAM(VRAM)0x2000〜0x3FFF を割り当て
はOK(複数から同じRAMに割り当ててもOK)なのですが、
CPUアドレス 0x0000〜0x1FFF に、内部RAM(VRAM)0x2000〜0x3FFF を割り当て
CPUアドレス 0x2000〜0x3FFF に、内部RAM(VRAM)0x0000〜0x1FFF を割り当て
はNGです。
また、内部RAMはVRAMとして使用されていますが、メモリ割り当てではあくまでもCPUからのアクセスアドレスが変わるだけなので、画面表示への影響はありません。
さらにVRAMでビットマップモードを使用した場合は、メモリ割り当てでアドレス変換された後に、ビットマップ変換がされます。詳しくはSRグラフィックの説明の際に記載します。
2013年06月02日
タイマ割り込みの違い
オフで話題になった件です。
コナミのスクランブル(カートリッジ版)を実行させた場合、開始時に鳴る音楽のテンポが違うというものです。
確かに、PC-6001初代機と、mk2ではテンポが違って、初代機に比べてmk2のテンポがかなり遅く感じます。
BASIC-ROM の違いかとも思いましたが、エミュレータでは同じテンポで鳴ります(mk2と同じ、遅い方のテンポ)。
FPGA版ではどうかと調べると、実機と同じように鳴りました。
ただ、心当たりがないので、調べてみました。
結論は、2ms タイマの割り込み間隔が違う(時がある)のが原因です。
初代機で、スクランブルを動かした場合、通常 2ms 周期の割り込みタイマが 1ms 周期になります(多分、意図的に 1ms にしているんだと思います)、
では、なぜこうなるか、ですが...
実は、PC-6001初代機と、PC-6001mk2以降では、タイマOFF からタイマON にした直後の、最初の割り込みタイミングが違います。

mk2以降では、タイマON にした直後でも 2ms 後に割り込みが発生します。
しかし、PC-6001初代機では、タイマON にした直後は、1ms 後に割り込みが発生します。
回路のバグというか仕様というか...ディスクリート部品で回路を組んでいる関係上、簡略化した副作用です。
スクランブルですが、割り込みルーチンは以下のようになっています。
4AE0 PUSH AF
4AE1 PUSH BC
:
4B1B LD A,03H
4B1D OUT (0B0H),A
4B1F DEC A
4B20 OUT (0B0H),A
4B22 EI
4B23 POP AF
4B24 RET
割り込みの最後で、タイマON → OFF → ON として、タイマの再起動をしています。
このため、大体1msの間隔で割り込みが入るようになっています。
(実際には、割り込みルーチンの最後の方でタイマOFF/ON をしているため、もうちょっと長いです)
mk2以降で同じようにするには、ポート0xF6 に、03H の代わりに 01H or 02H を出力すると、大体同じになるはずです(ソフトによります。スクランブルなら、02H ぐらいが適当なようです)。
コナミのスクランブル(カートリッジ版)を実行させた場合、開始時に鳴る音楽のテンポが違うというものです。
確かに、PC-6001初代機と、mk2ではテンポが違って、初代機に比べてmk2のテンポがかなり遅く感じます。
BASIC-ROM の違いかとも思いましたが、エミュレータでは同じテンポで鳴ります(mk2と同じ、遅い方のテンポ)。
FPGA版ではどうかと調べると、実機と同じように鳴りました。
ただ、心当たりがないので、調べてみました。
結論は、2ms タイマの割り込み間隔が違う(時がある)のが原因です。
初代機で、スクランブルを動かした場合、通常 2ms 周期の割り込みタイマが 1ms 周期になります(多分、意図的に 1ms にしているんだと思います)、
では、なぜこうなるか、ですが...
実は、PC-6001初代機と、PC-6001mk2以降では、タイマOFF からタイマON にした直後の、最初の割り込みタイミングが違います。
mk2以降では、タイマON にした直後でも 2ms 後に割り込みが発生します。
しかし、PC-6001初代機では、タイマON にした直後は、1ms 後に割り込みが発生します。
回路のバグというか仕様というか...ディスクリート部品で回路を組んでいる関係上、簡略化した副作用です。
スクランブルですが、割り込みルーチンは以下のようになっています。
4AE0 PUSH AF
4AE1 PUSH BC
:
4B1B LD A,03H
4B1D OUT (0B0H),A
4B1F DEC A
4B20 OUT (0B0H),A
4B22 EI
4B23 POP AF
4B24 RET
割り込みの最後で、タイマON → OFF → ON として、タイマの再起動をしています。
このため、大体1msの間隔で割り込みが入るようになっています。
(実際には、割り込みルーチンの最後の方でタイマOFF/ON をしているため、もうちょっと長いです)
mk2以降で同じようにするには、ポート0xF6 に、03H の代わりに 01H or 02H を出力すると、大体同じになるはずです(ソフトによります。スクランブルなら、02H ぐらいが適当なようです)。
2013年01月12日
uPD780未定義命令(1)
これもツイッターで見つけた発言から。
>>TAPE版ロードランナーのローダーをちょっと読んでみた。唐突にEDH,C4Hとか現れるけど,それがなくてもプログラムとして成立してるっぽい。ウェイト?よくわかんね。
未定義命令に関しては、ちゃんと解析する必要があるとは思います。
昔と違ってロジアナが使えるので、解析はだいぶラクだとは思いますが。
今の音声合成の解析が終わってからだな(^^;
>>TAPE版ロードランナーのローダーをちょっと読んでみた。唐突にEDH,C4Hとか現れるけど,それがなくてもプログラムとして成立してるっぽい。ウェイト?よくわかんね。
未定義命令に関しては、ちゃんと解析する必要があるとは思います。
昔と違ってロジアナが使えるので、解析はだいぶラクだとは思いますが。
今の音声合成の解析が終わってからだな(^^;
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 では、他に割り込み系の命令にもバグがあったと思います。また詳しく調べたいと思います。
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 では、他に割り込み系の命令にもバグがあったと思います。また詳しく調べたいと思います。
2012年08月23日
mk2のウエイト
WAIT 関連ですが、やはりバグがありました。
仕様は以下の通りです。
I/Oポート 0xF3(初期値:0xC2)
bit7: M1 サイクルで 1WAIT 挿入(1 で WAIT 挿入)
bit6: ROMブロックで 1WAIT 挿入(1 で WAIT 挿入)
bit5: RAMブロックで 1WAIT 挿入(1 で WAIT 挿入)
bit4-0 は、割り込み関連です。
初期値が、0xC2 なので、M1サイクルとROMブロックで 1WAIT 挿入されます。
仕様がちょっとあいまいなんですが、bit7=1 の場合は、ROMかRAMかに関わらず、M1サイクルで 1WAIT 挿入されます。
また、bit6=1 の場合は、ROM のプログラムのM1 サイクル、オペランドのリード、ROM からのリードなどで、1WAIT挿入されます。
例えば、bit7-5 = 110 の場合、
3A 00 FA LD A,(FA00H)
この命令では、4+3+3+3 ですが、このプログラムが ROM の場合は、(4+1)+(3+1)+(3+1)+3 になります。このプログラムが RAM の場合は、(4+1)+3+3+3 になります(M1 のみ 1WAIT)
で、バグです。
1つ目は、bit7-5 = 001 つまり、RAMブロックのみ1WAIT を挿入した場合に、ROMブロックからリードすると、なぜか1WAIT されます(M1 サイクルに関しては確認出来ていません)。
2つ目が問題なんですが、M1 サイクルを 0WAIT に出来ません。
詳しく言うと、bit7-5 = 000、RAM領域での bit7-5 = 001 などを設定すると暴走します。
ROM に対しては、遅いのでダメというのは分かるんですが、RAM はそれなりに速いはずなので、ちょっとヘンです。
で、ロジアナで調べてみました。
wait.zip
調査したプログラムです。内容は、ROMの内容0x0000-0x7FFF の値を足し算するだけです(それを16回繰り返します)。
このプログラムでも、bit7-5 = 000、001 と設定すると、暴走します。
結論から言うと、RAMへのアクセスが適切ではないため、WAIT なしでは動作しない状態になっていました。

ロジアナで取った波形です。上が 1WAIT、下が0WAIT です。それぞれ左の線が M1サイクルの始まり、右の線が M1サイクルの終わり(=RAMからのデータの取り込みタイミング)になります。
RAM(DRAM)は、RAS が L になって、その後に CAS が L になってから、データが出力されます。
上の 1WAIT では、CAS が L になってから、4MHz で 1.5CLK 、つまり375ns あります。
下の 0WAIT では、CAS が L になってから、4MHz で 0.5CLK 、つまり125ns あります。
0WAIT の場合、RAMの出力遅延とZ80のセットアップ時間の和が、125ns 以下になる必要がありますが、これが不足しています。
そのため、0WAIT では、正常なデータを取り込めなくなり、暴走しているようです。

ここで、回路図の左上のFFを見てみると、CAS 信号は4MHz の立下りで叩かれているため、この時間だけ遅れている事がわかります。
では、ここを何とかすると動くのでは?
という事でやってみました。

これは、16MHz から4MHz を作っている回路ですが、4MHz の立下りで叩くのではなく、この16MHz の立下りで叩くと、16MHz で 1clk分、62.5ns ほど速く出せるようになります。
DRAMの仕様を確認してみても、RAS に対して、CAS が速すぎる事もなさそうです(50ns ほどあればいいらしい)。
回路としては、IC2 の 3pin に、現在入力されている 4MCLK の代わりに 16MCLK を入力します。16MCLK は、IC44 の 12pin から取ります。
上記は、mk2 の時で、66 の場合は、IC16 の 1pin に、IC60 の 10pin を入力します。
この改造のためには、1箇所パターンカット(もしくはICの足上げ)が必要になります。
回路図としては、4MHz と書かれている所を 16MHz と変更するだけなんですけどね(^^;
改造した結果です。

同じプログラムを動かした時のロジアナの波形(1WAIT挿入)です。上が改造前、下が改造後です。
MEMC-DR-nCAS が少し速くなっているのがわかると思います。

同じプログラムを動かした時のロジアナの波形(WAIT挿入なし)です。上が改造前、下が改造後です。
同じようにMEMC-DR-nCAS が少し速くなっているのがわかると思います。
RAMからの出力の波形を取ってなかったんですが、かなり余裕があるのがわかると思います。
実際のプログラムでどれだけ違うのか、AX-9 のフライトシミュレータで試してみました。
まず、何もしない状態です(M1、ROM にウエイトあり)。
次に、WAIT を ROM のみにしました(OUT&HF3,&H42)。
ちょっと速くなってる?
通常は、ROM内ルーチンよりユーザプログラム上を走っている事が多いので、これでも十分効果があるはずです。
ROM のWAIT をなくす事は出来ないので、ROM のデータをRAMにコピーして、RAMで動かしてみました。
romcpy.zip
ちなみにこれが ROM のデータをRAMにコピーするプログラムです。
特定の箇所で、描画時間を測定してみました(14フレーム分)。
何もしない状態:14.1 秒
ROMのみWAIT挿入:12.2 秒
WAITなし:12.2 秒
手動でストップウオッチなので、多少の誤差はありますが、ROM のみWAIT挿入でも速くなっています。
M1サイクルのWAIT が無くなるので、当然の結果ではあります。
ちなみに改造をしても、0xF3 のWAIT関連ビットを変えなければ、(当然)今まで通りの動作をします。
※改造は自己責任で行って下さい。
仕様は以下の通りです。
I/Oポート 0xF3(初期値:0xC2)
bit7: M1 サイクルで 1WAIT 挿入(1 で WAIT 挿入)
bit6: ROMブロックで 1WAIT 挿入(1 で WAIT 挿入)
bit5: RAMブロックで 1WAIT 挿入(1 で WAIT 挿入)
bit4-0 は、割り込み関連です。
初期値が、0xC2 なので、M1サイクルとROMブロックで 1WAIT 挿入されます。
仕様がちょっとあいまいなんですが、bit7=1 の場合は、ROMかRAMかに関わらず、M1サイクルで 1WAIT 挿入されます。
また、bit6=1 の場合は、ROM のプログラムのM1 サイクル、オペランドのリード、ROM からのリードなどで、1WAIT挿入されます。
例えば、bit7-5 = 110 の場合、
3A 00 FA LD A,(FA00H)
この命令では、4+3+3+3 ですが、このプログラムが ROM の場合は、(4+1)+(3+1)+(3+1)+3 になります。このプログラムが RAM の場合は、(4+1)+3+3+3 になります(M1 のみ 1WAIT)
で、バグです。
1つ目は、bit7-5 = 001 つまり、RAMブロックのみ1WAIT を挿入した場合に、ROMブロックからリードすると、なぜか1WAIT されます(M1 サイクルに関しては確認出来ていません)。
2つ目が問題なんですが、M1 サイクルを 0WAIT に出来ません。
詳しく言うと、bit7-5 = 000、RAM領域での bit7-5 = 001 などを設定すると暴走します。
ROM に対しては、遅いのでダメというのは分かるんですが、RAM はそれなりに速いはずなので、ちょっとヘンです。
で、ロジアナで調べてみました。
wait.zip
調査したプログラムです。内容は、ROMの内容0x0000-0x7FFF の値を足し算するだけです(それを16回繰り返します)。
このプログラムでも、bit7-5 = 000、001 と設定すると、暴走します。
結論から言うと、RAMへのアクセスが適切ではないため、WAIT なしでは動作しない状態になっていました。
ロジアナで取った波形です。上が 1WAIT、下が0WAIT です。それぞれ左の線が M1サイクルの始まり、右の線が M1サイクルの終わり(=RAMからのデータの取り込みタイミング)になります。
RAM(DRAM)は、RAS が L になって、その後に CAS が L になってから、データが出力されます。
上の 1WAIT では、CAS が L になってから、4MHz で 1.5CLK 、つまり375ns あります。
下の 0WAIT では、CAS が L になってから、4MHz で 0.5CLK 、つまり125ns あります。
0WAIT の場合、RAMの出力遅延とZ80のセットアップ時間の和が、125ns 以下になる必要がありますが、これが不足しています。
そのため、0WAIT では、正常なデータを取り込めなくなり、暴走しているようです。
ここで、回路図の左上のFFを見てみると、CAS 信号は4MHz の立下りで叩かれているため、この時間だけ遅れている事がわかります。
では、ここを何とかすると動くのでは?
という事でやってみました。
これは、16MHz から4MHz を作っている回路ですが、4MHz の立下りで叩くのではなく、この16MHz の立下りで叩くと、16MHz で 1clk分、62.5ns ほど速く出せるようになります。
DRAMの仕様を確認してみても、RAS に対して、CAS が速すぎる事もなさそうです(50ns ほどあればいいらしい)。
回路としては、IC2 の 3pin に、現在入力されている 4MCLK の代わりに 16MCLK を入力します。16MCLK は、IC44 の 12pin から取ります。
上記は、mk2 の時で、66 の場合は、IC16 の 1pin に、IC60 の 10pin を入力します。
この改造のためには、1箇所パターンカット(もしくはICの足上げ)が必要になります。
回路図としては、4MHz と書かれている所を 16MHz と変更するだけなんですけどね(^^;
改造した結果です。
同じプログラムを動かした時のロジアナの波形(1WAIT挿入)です。上が改造前、下が改造後です。
MEMC-DR-nCAS が少し速くなっているのがわかると思います。
同じプログラムを動かした時のロジアナの波形(WAIT挿入なし)です。上が改造前、下が改造後です。
同じようにMEMC-DR-nCAS が少し速くなっているのがわかると思います。
RAMからの出力の波形を取ってなかったんですが、かなり余裕があるのがわかると思います。
実際のプログラムでどれだけ違うのか、AX-9 のフライトシミュレータで試してみました。
ダウンロードは🎥こちら
まず、何もしない状態です(M1、ROM にウエイトあり)。
ダウンロードは🎥こちら
次に、WAIT を ROM のみにしました(OUT&HF3,&H42)。
ちょっと速くなってる?
通常は、ROM内ルーチンよりユーザプログラム上を走っている事が多いので、これでも十分効果があるはずです。
ダウンロードは🎥こちら
ROM のWAIT をなくす事は出来ないので、ROM のデータをRAMにコピーして、RAMで動かしてみました。
romcpy.zip
ちなみにこれが ROM のデータをRAMにコピーするプログラムです。
特定の箇所で、描画時間を測定してみました(14フレーム分)。
何もしない状態:14.1 秒
ROMのみWAIT挿入:12.2 秒
WAITなし:12.2 秒
手動でストップウオッチなので、多少の誤差はありますが、ROM のみWAIT挿入でも速くなっています。
M1サイクルのWAIT が無くなるので、当然の結果ではあります。
ちなみに改造をしても、0xF3 のWAIT関連ビットを変えなければ、(当然)今まで通りの動作をします。
※改造は自己責任で行って下さい。
2012年06月08日
ディスク関連(8)
外付けドライブが異常に遅いのは、データ転送時の問題のようでした。
でもまだ遅い...
というわけで、速度を比較してみました。
比較用に、Bookwormさんに作ってもらった速度計測用のプログラムを一部修正して使いました。
フロッピーからの読み出しですが、P66内蔵ドライブだと以下のような流れになります。
1)CPUから、モータONのコマンドを出す
↓
2)モータON
↓
3)CPUから、μPD765Aに SEEK コマンドを発行
↓
4)μPD765A は、FDDに対して、目的のシリンダ(≒トラック番号)にヘッドを動かすように指示する
↓
5)CPUから、μPD765Aに SEEK コマンドの実行結果を聞く(SENSE INTERRUPT STATUSコマンド発行)
↓
6)μPD765A が、実行結果を報告する
↓
7)CPUから、μPD765Aに 目的のセクタの読み出しを指示する(READ DATA コマンドを発行)
↓
8)μPD765A は、FDD から入力される「フロッピーのデータ」から、目的のセクタを探す
↓
9)μPD765A は、リード用バッファに対して、目的のセクタのデータを書き出す
↓
10)書き出しが完了したら、FDINT を出力してCPUに通知する
↓
11)CPUは、リード用バッファに入っているデータをメインメモリに転送する
となります。
最初の1)2)は、実機では時間がかなり掛かりますが、FPGA版では無視です。
3)〜6)も、実機では時間が掛かりますが、FPGA版では一瞬です。
また、1)〜6)は、連続で同一シリンダ(トラック)を読む場合では、この時間は無視できます。
速度測定プログラムで測定したのは、7)〜11)の時間になります。
元のプログラムや、ROM内ルーチンでは、途中に割り込み禁止が入っていたのですが、これは外しました。
(割り込み禁止が入っている理由ですが、μPD765A の仕様で、何μS までに処理を完了させなければならないとかいうのがあるみたいです)
で、測定した結果です。
・実機
手で何度も測定した場合、100〜270ms ぐらいと数字がばらけます。
BASIC でループさせた場合、106〜112ms になりました。
また、セクタ番号を1と16をやってみましたが、傾向は同じでした。
・実機の考察
手で測定した時に数字がばらけるのは、その時のヘッドの位置と読みたい場所の関係が毎回違うからだと思われます。
たまたま、ヘッドと読みたい場所が近い場合は時間が短く、遠い場合は時間が長いようです。
BASIC でループさせた場合は、この距離が一定になるから、数字がばらけなかったようです。
ヘッドと読みたい場所が最悪の時の時間は、ディスクが1周する時間と同じですので、
1/ 300rpm = 1/(300/60) = 200ms
で、ディスクの場所により、0〜200ms の時間待たされる事になります。
(300rpmは、もしかしたら600rpmかも)
実測値と比較すると、リードするのに100ms、読み出すのを待っている時間0〜200ms で大体合っているような感じです。
・FPGA版
構造上、セクタ番号で掛かる時間が違うようになっています
セクタ1が一番速く、セクタ16が一番遅くなります。
セクタ1で、56〜60ms、セクタ16で140〜154msでした。
遅いのは遅いんですが、実機と比べると速いので、ヨシとする事にします。
でもまだ遅い...
というわけで、速度を比較してみました。
比較用に、Bookwormさんに作ってもらった速度計測用のプログラムを一部修正して使いました。
フロッピーからの読み出しですが、P66内蔵ドライブだと以下のような流れになります。
1)CPUから、モータONのコマンドを出す
↓
2)モータON
↓
3)CPUから、μPD765Aに SEEK コマンドを発行
↓
4)μPD765A は、FDDに対して、目的のシリンダ(≒トラック番号)にヘッドを動かすように指示する
↓
5)CPUから、μPD765Aに SEEK コマンドの実行結果を聞く(SENSE INTERRUPT STATUSコマンド発行)
↓
6)μPD765A が、実行結果を報告する
↓
7)CPUから、μPD765Aに 目的のセクタの読み出しを指示する(READ DATA コマンドを発行)
↓
8)μPD765A は、FDD から入力される「フロッピーのデータ」から、目的のセクタを探す
↓
9)μPD765A は、リード用バッファに対して、目的のセクタのデータを書き出す
↓
10)書き出しが完了したら、FDINT を出力してCPUに通知する
↓
11)CPUは、リード用バッファに入っているデータをメインメモリに転送する
となります。
最初の1)2)は、実機では時間がかなり掛かりますが、FPGA版では無視です。
3)〜6)も、実機では時間が掛かりますが、FPGA版では一瞬です。
また、1)〜6)は、連続で同一シリンダ(トラック)を読む場合では、この時間は無視できます。
速度測定プログラムで測定したのは、7)〜11)の時間になります。
元のプログラムや、ROM内ルーチンでは、途中に割り込み禁止が入っていたのですが、これは外しました。
(割り込み禁止が入っている理由ですが、μPD765A の仕様で、何μS までに処理を完了させなければならないとかいうのがあるみたいです)
で、測定した結果です。
・実機
手で何度も測定した場合、100〜270ms ぐらいと数字がばらけます。
BASIC でループさせた場合、106〜112ms になりました。
また、セクタ番号を1と16をやってみましたが、傾向は同じでした。
・実機の考察
手で測定した時に数字がばらけるのは、その時のヘッドの位置と読みたい場所の関係が毎回違うからだと思われます。
たまたま、ヘッドと読みたい場所が近い場合は時間が短く、遠い場合は時間が長いようです。
BASIC でループさせた場合は、この距離が一定になるから、数字がばらけなかったようです。
ヘッドと読みたい場所が最悪の時の時間は、ディスクが1周する時間と同じですので、
1/ 300rpm = 1/(300/60) = 200ms
で、ディスクの場所により、0〜200ms の時間待たされる事になります。
(300rpmは、もしかしたら600rpmかも)
実測値と比較すると、リードするのに100ms、読み出すのを待っている時間0〜200ms で大体合っているような感じです。
・FPGA版
構造上、セクタ番号で掛かる時間が違うようになっています
セクタ1が一番速く、セクタ16が一番遅くなります。
セクタ1で、56〜60ms、セクタ16で140〜154msでした。
遅いのは遅いんですが、実機と比べると速いので、ヨシとする事にします。

