リアルタイムモニタ(ZealMon)の製作 [Z80]
Twitter(X)のタイムラインで Z80 のスタックを使ったタスク管理の話題(スタック上にタスクアドレスを並べて順次呼び出すもの)を見てリアルタイムモニタ(リアルタイムOS)を作ってみたくなりました。
リアルタイムOSに関してはネット上にも多くの情報があり、例えばここや "リアルタイム処理の基礎知識" 等に簡潔にまとめられています。
とは言ってもあまり本格的なものではなく、ライトウェイトである程度高速なものを作りたいと思います。今回作成した Z80 用のリアルタイムモニタ(以降、ZealMon と記す)の特徴を列挙すると
イベント管理、セマフォ、タスク間のメッセージ管理などの機能はまだありませんが、タイマー管理と同様にすることで追加実装は割合楽だと思います。
タスクコントロールブロック(TCB)の構造は下記のようにしました。
最近動かしている手持ちの Z80 ボードではタイマー割込みを自由に設定できるものが無かったのでネット上で公開されている MSX のエミュレータである MSXPen で試すことにしました。
周期割込みとして MSX の垂直帰線割り込み(1/60秒周期)を使用します。割込みモードは mode1(RST 38H 実行) です。
リアルタイムモニタが動作中の時は割込みでタスク切替が発生しているのでディバッガでブレークしてもディバッガ自身がブレークしたタスクと同じタスクの扱いになり、他のタスクが文字表示等のシステムコールをしている場合、リエントラントにコールすることになるので直ぐにハングしてしまいます。
結局ディバッガはあまり役に立たずに机上ディバッグ(若しくは割込み機能を停止した上のでブレーク確認)がメインになります。仕事であればICEが使えるのですが、個人としてはそんな高価なものは持っていません。
下図は MSXPen でデモプログラムを実行した際の画面です。
表示が競合し易くなるように1文字表示毎にタイマーウェイトを入れています。
下図はディスパッチが最初に実行される直前とタスク3が実行開始した時点でのワークエリアのダンプ表示です。割込みの影響を無くすため、フックした新たな割込み処理の先頭をリターンコードに書き換えています。
画面中央のダンプ情報からディスパッチ前はタスク1~3がプライオリティ 0 のレディキューにつながっているのが判ります。タスク0は前述のアイドルタスクでプライオリティ3のレディキューにつながっています。下側のダンプはタスク3実行開始時のメモリで実行タスク番号が3でタイマーウェイトキューにタスク1とタスク2が繋がっています。
まだ最適化はしていませんが、ソースも貼っておきます。無いとは思いますが今後、Z80 でリアルタイム制御したい場合に役に立つかもしれませんw
Twitter(X)に投稿した動画付きメッセージを貼っておきます。
本確認で実施した割込み試験プログラムのリストを下記に示します。割込みが入った場合、リターンアドレスとコール元のコードを保存するようにしました。
★追記 2023/10/06
上記の様にシステムコール内で割込み許可していることが判ったので、システムコール時は割込みは許可状態のままにしてタスクをディスパッチしない様にしてみました(システムコール毎のタイマーウェイトを無くし、ラウンドロビンコールを追加)。
その結果、下図のように表示が競合する場合には互いに1文字ずつ表示するようになりました。割込み処理の空振りが無いのでタイマー管理も正確になったはずです。
MSX-DOS 上で GAME 言語を使って BIOS 内の EI(0FBH)を検索した結果を貼っておきます。38H の割込みのエントリーが 0DDA9H なので割込み処理も含まれているのでしょうが結構あるものですね。
ざっと見た感じ、最後の二つは相対ジャンプのオフセット値ですが、他は EI 命令のコードのようですね。
[TOP] [ 前へ ] 連載記事一覧 [ 次へ ]
リアルタイムOSに関してはネット上にも多くの情報があり、例えばここや "リアルタイム処理の基礎知識" 等に簡潔にまとめられています。
とは言ってもあまり本格的なものではなく、ライトウェイトである程度高速なものを作りたいと思います。今回作成した Z80 用のリアルタイムモニタ(以降、ZealMon と記す)の特徴を列挙すると
- 実装機能は必要最小限
- 実行中のタスクの中断(プリエンプト)可能
- タイマーウェイト機能
- プライオリティは4レベル(拡張は容易)
- ラウンドロビンタスク切替え要求機能
- ライトウェイト
モニタ本体のコードは約 300 バイト
- タスクの状態遷移はキューで管理
TCB(タスクコントロールブロック)内にタスク状態情報を持たず状態書換え処理を省略し軽量化
- アイドルタスクの実装省略
最低プライオリティでウェイト状態にならないタスクを持ついことで、レディ状態のタスクが無い時に実行するアイドルタスクの実装を省きました。番兵的な発想ですね。
イベント管理、セマフォ、タスク間のメッセージ管理などの機能はまだありませんが、タイマー管理と同様にすることで追加実装は割合楽だと思います。
タスクコントロールブロック(TCB)の構造は下記のようにしました。
name | offset | bytes | mearn |
---|---|---|---|
TcList | 0 | 2 | address for TCB link |
TcTskNo | 2 | 1 | high nibble:priority(0:highest) low nibble:task No. |
TcTim | 3 | 1 | timer count |
TcEvCnt | 4 | 1 | how many waiting event except timer |
TcSP | 5 | 2 | stack pointer save area |
TcStat | 7 | 1 | task status !! not used !! |
最近動かしている手持ちの Z80 ボードではタイマー割込みを自由に設定できるものが無かったのでネット上で公開されている MSX のエミュレータである MSXPen で試すことにしました。
周期割込みとして MSX の垂直帰線割り込み(1/60秒周期)を使用します。割込みモードは mode1(RST 38H 実行) です。
リアルタイムモニタが動作中の時は割込みでタスク切替が発生しているのでディバッガでブレークしてもディバッガ自身がブレークしたタスクと同じタスクの扱いになり、他のタスクが文字表示等のシステムコールをしている場合、リエントラントにコールすることになるので直ぐにハングしてしまいます。
結局ディバッガはあまり役に立たずに机上ディバッグ(若しくは割込み機能を停止した上のでブレーク確認)がメインになります。仕事であればICEが使えるのですが、個人としてはそんな高価なものは持っていません。
MSXPen 上では動作が中々安定せず悩みましたが、割込み禁止(DI)中でも割込みが入るらしく、DI と共に 38H をリターンコードに書き換えるようにしたら安定して動作するようになりました。※1
※1)原因判明(2023/10/05 の追記部分を参照)下図は MSXPen でデモプログラムを実行した際の画面です。
表示が競合し易くなるように1文字表示毎にタイマーウェイトを入れています。
MSXPen でデモプログラム実行時の様子 |
|
下図はディスパッチが最初に実行される直前とタスク3が実行開始した時点でのワークエリアのダンプ表示です。割込みの影響を無くすため、フックした新たな割込み処理の先頭をリターンコードに書き換えています。
画面中央のダンプ情報からディスパッチ前はタスク1~3がプライオリティ 0 のレディキューにつながっているのが判ります。タスク0は前述のアイドルタスクでプライオリティ3のレディキューにつながっています。下側のダンプはタスク3実行開始時のメモリで実行タスク番号が3でタイマーウェイトキューにタスク1とタスク2が繋がっています。
ZealMon 実行時のメモリ状態 |
|
まだ最適化はしていませんが、ソースも貼っておきます。無いとは思いますが今後、Z80 でリアルタイム制御したい場合に役に立つかもしれませんw
リアルタイムモニタ(ZealMon)とデモのソース(Z80アセンブラ) |
|
Twitter(X)に投稿した動画付きメッセージを貼っておきます。
タイムラインでZ80のスタックを使ったタスク管理の話題を見かけリアルタイムモニタを作ってみたくなりました
— skyriver (@wcinp) October 3, 2023
必要最小限の機能の実装ですがデモをMSXPenで動かしてみました
詳細は下記urlを参照して下さいhttps://t.co/mB0lGvejzS#ZealMon #Z80 #RealTimeMonitor pic.twitter.com/1gC0Ji3ZHg
★追記 2023/10/05
上記の DI 中に割込みが入っている現象の原因が判りました。
下記の割込みテストプログラムで確認した結果が下の画面です。繰り返し行っても同様の結果でした。BIOS の1文字表示処理の ROM 内のコードで EI を実施しているため、割込みが入っているようです。
上記の DI 中に割込みが入っている現象の原因が判りました。
下記の割込みテストプログラムで確認した結果が下の画面です。繰り返し行っても同様の結果でした。BIOS の1文字表示処理の ROM 内のコードで EI を実施しているため、割込みが入っているようです。
割込み試験実施結果 |
|
本確認で実施した割込み試験プログラムのリストを下記に示します。割込みが入った場合、リターンアドレスとコール元のコードを保存するようにしました。
割込み試験プログラムのソース(Z80アセンブラ) |
|
★追記 2023/10/06
上記の様にシステムコール内で割込み許可していることが判ったので、システムコール時は割込みは許可状態のままにしてタスクをディスパッチしない様にしてみました(システムコール毎のタイマーウェイトを無くし、ラウンドロビンコールを追加)。
その結果、下図のように表示が競合する場合には互いに1文字ずつ表示するようになりました。割込み処理の空振りが無いのでタイマー管理も正確になったはずです。
割込み対処後のデモ画面例 |
|
MSX-DOS 上で GAME 言語を使って BIOS 内の EI(0FBH)を検索した結果を貼っておきます。38H の割込みのエントリーが 0DDA9H なので割込み処理も含まれているのでしょうが結構あるものですね。
ざっと見た感じ、最後の二つは相対ジャンプのオフセット値ですが、他は EI 命令のコードのようですね。
MSX-DOS BIOS 内の 0FBH コードのサーチ結果 |
|
[TOP] [ 前へ ] 連載記事一覧 [ 次へ ]
FizzBuzz問題(独自解法) [Z80]
今回は Twitter(X) のタイムラインで見かけた Fizz Buzz 問題の解法について書いてみたいと思います。ウィキペディアにも書いてあるように Fizz Buzz 問題とは1から100までを言い合う遊びで3の倍数の場合は数字の代わりに Fizz と言い、5の倍数の場合は Buzz、両者の倍数の場合は Fizz Buzz と言うゲームです。
FizzBuzz 問題を解くプログラムを作ること自体は容易なのですが、面白くするために高速化という観点から剰余算を使わないという制約を設けます。また、FizzBuzz の間にはスペースを入れず、それぞれの数字や 単語の後にはスペースを1個入れることとします。
素数を求める時によく使われる「エラトステネスの篩(ふるい)」でのフラグ設定のように3周期のカウンタと5周期のカウンタを持つことでそれぞれの倍数であることを判断できます。
最初に考えたC言語のソースは次のようなものでした。
ネット上のコンパイラ環境サービスで実行結果を確かめることもできます(便利な世の中になったものですね)。
上述のように3と5のカウンタを持っているので全体は15周期の繰り返しになり、4ビットでカウントできます。ならば15周期のカウンタだけを使って処理できないものでしょうか?
ここで15以内の3と5の倍数について考えてみます。両者の倍数である15を除外して列挙すると
更に面白いことに相互を XOR してみると他のパターンに推移するので、これらの3の倍数と5の倍数は2進数の観点で見ると密接な関係があるかのように思えてきます。
この特徴を利用して Z80 のアセンブリ言語で FizzBuzz 問題を解くプログラムを作ってみることにしました。上記は独自の解析なので恐らくこの手法による FizzBuzz プログラムは世界初だと思いますw
1のビットの数が2個だった場合、上述のように3か5の倍数ということになるのですが、どちらに属するかの判断方法は簡単そうに見えて中々思いつきませんでした。
最も単純なのは
CP 5
JR Z,Buzz
CP 10
JR Z,Buzz
のような8バイトものコードになってしまいます。漸く思い付いたのが次の方法です。
SUB 5
OR A
JP PE,Buzz
如何でしょう。引き算後のビット1の総数(パリティ)で判断できました(ほかに良い方法があればコメント下さい)。
★追記 2023/09/23 {
もっと判り易く、簡潔な判定方法を思い付きました^^
AND 5
JP PE,Buzz
}
★追記 2023/09/23 {
ビット数の判定をループ処理方式
L1: LD BC,4 * 256 + 0
XOR A ; clear bit counter
LD L,D
L2: RRC L
ADC A,C
DJNZ L2
CP 2
JR NZ,DspNum
から下記のループ処理無しの方式に改善してみました。
LD A,D
OR A
JP PO,DspNum
CP 0FH
JR Z,Both
}
できたソースを下記に示します。CP/M用に作成していますがDOSコールしているのは1文字出力サービスのみですので他の環境にも移植は容易だと思います。メインループ処理の簡潔さを堪能してください。
上のプログラムの出力結果は次のようになります。
1 2 Fizz 4 Buzz Fizz 7 8 Fizz Buzz 11 Fizz 13 14 FizzBuzz 16 17 Fizz 19 Buzz Fizz 22 23 Fizz Buzz 26 Fizz 28 29 FizzBuzz 31 32 Fizz 34 Buzz Fizz 37 38 Fizz Buzz 41 Fizz 43 44 FizzBuzz 46 47 Fizz 49 Buzz Fizz 52 53 Fizz Buzz 56 Fizz 58 59 FizzBuzz 61 62 Fizz 64 Buzz Fizz 67 68 Fizz Buzz 71 Fizz 73 74 FizzBuzz 76 77 Fizz 79 Buzz Fizz 82 83 Fizz Buzz 86 Fizz 88 89 FizzBuzz 91 92 Fizz 94 Buzz Fizz 97 98 Fizz Buzz
上記のソースよりは少し前のバージョンですが、ネット上のサービスとして公開されている MSX のエミュ環境である MSXPenでも実行できました(重ねて便利な世の中に・・)。
FizzBuzz 問題を解くプログラムを作ること自体は容易なのですが、面白くするために高速化という観点から剰余算を使わないという制約を設けます。また、FizzBuzz の間にはスペースを入れず、それぞれの数字や 単語の後にはスペースを1個入れることとします。
素数を求める時によく使われる「エラトステネスの篩(ふるい)」でのフラグ設定のように3周期のカウンタと5周期のカウンタを持つことでそれぞれの倍数であることを判断できます。
最初に考えたC言語のソースは次のようなものでした。
FizzBuzz 問題の解答プログラム(C言語) |
|
ネット上のコンパイラ環境サービスで実行結果を確かめることもできます(便利な世の中になったものですね)。
上述のように3と5のカウンタを持っているので全体は15周期の繰り返しになり、4ビットでカウントできます。ならば15周期のカウンタだけを使って処理できないものでしょうか?
ここで15以内の3と5の倍数について考えてみます。両者の倍数である15を除外して列挙すると
- 3の倍数
0011,0110,1100,1001
- 5の倍数
0101,1010
更に面白いことに相互を XOR してみると他のパターンに推移するので、これらの3の倍数と5の倍数は2進数の観点で見ると密接な関係があるかのように思えてきます。
この特徴を利用して Z80 のアセンブリ言語で FizzBuzz 問題を解くプログラムを作ってみることにしました。上記は独自の解析なので恐らくこの手法による FizzBuzz プログラムは世界初だと思いますw
1のビットの数が2個だった場合、上述のように3か5の倍数ということになるのですが、どちらに属するかの判断方法は簡単そうに見えて中々思いつきませんでした。
最も単純なのは
CP 5
JR Z,Buzz
CP 10
JR Z,Buzz
のような8バイトものコードになってしまいます。漸く思い付いたのが次の方法です。
SUB 5
OR A
JP PE,Buzz
如何でしょう。引き算後のビット1の総数(パリティ)で判断できました(ほかに良い方法があればコメント下さい)。
★追記 2023/09/23 {
もっと判り易く、簡潔な判定方法を思い付きました^^
AND 5
JP PE,Buzz
}
★追記 2023/09/23 {
ビット数の判定をループ処理方式
L1: LD BC,4 * 256 + 0
XOR A ; clear bit counter
LD L,D
L2: RRC L
ADC A,C
DJNZ L2
CP 2
JR NZ,DspNum
から下記のループ処理無しの方式に改善してみました。
LD A,D
OR A
JP PO,DspNum
CP 0FH
JR Z,Both
}
できたソースを下記に示します。CP/M用に作成していますがDOSコールしているのは1文字出力サービスのみですので他の環境にも移植は容易だと思います。メインループ処理の簡潔さを堪能してください。
FizzBuzz 問題の解答プログラム(Z80アセンブリ) |
|
上のプログラムの出力結果は次のようになります。
1 2 Fizz 4 Buzz Fizz 7 8 Fizz Buzz 11 Fizz 13 14 FizzBuzz 16 17 Fizz 19 Buzz Fizz 22 23 Fizz Buzz 26 Fizz 28 29 FizzBuzz 31 32 Fizz 34 Buzz Fizz 37 38 Fizz Buzz 41 Fizz 43 44 FizzBuzz 46 47 Fizz 49 Buzz Fizz 52 53 Fizz Buzz 56 Fizz 58 59 FizzBuzz 61 62 Fizz 64 Buzz Fizz 67 68 Fizz Buzz 71 Fizz 73 74 FizzBuzz 76 77 Fizz 79 Buzz Fizz 82 83 Fizz Buzz 86 Fizz 88 89 FizzBuzz 91 92 Fizz 94 Buzz Fizz 97 98 Fizz Buzz
上記のソースよりは少し前のバージョンですが、ネット上のサービスとして公開されている MSX のエミュ環境である MSXPenでも実行できました(重ねて便利な世の中に・・)。
シリアルEEPROMでCP/M [Z80]
SDカードは使用制限があるので自作基板で使用した場合、公開し辛い面がありました。SDカードの中でも使用されているであろうEEPROMを使って CP/M を起動できないか実験しました。
秋月さんで販売している1MbitのシリアルEEPROM(S-24CM01CIーJ8T1U4)を購入していましたが、今回はより容量が大きな海外の通販で見つけた 32MBit EEPROM(W25Q32JVSIQ) を使って実験してみました。後者の方が書込み速度が遅く、許容している書込み回数も少ないのですが容量の大きさを優先しました。
最近、秋月さんで 4Mbit のフラッシュメモリ(IS25LP040E) を販売開始したようです。
今回使用した 32MBit EEPROM(W25Q32JV) のブロック図が下図になります。消去時の単位は 4KBytes/32KByte/64KByte/chiperase と柔軟性があり、最小単位は 4KByte です。
EEPROM とのインターフェースは基本的には SPI で今回は GAL を使って Z80 側のソフトウェアで対応することにしました。この EEPROM はデータ線が 2bit や 4bit の拡張インターフェースの機能もあるのでソフト対応しておくことで後日高速化できる余地を残すという意図もあります。
まずはデータ線が 1bit の SPI インターフェースでの接続実験を行いました。
下図は今回の EEPROM 評価時の回路です。上述のように GAL(GAL22V10D)を使用して EEPROM を接続しています。
実験環境は下図のように Z80GalCompact の CPU ソケットを拡張して EEPROM を実装しています。
下図はデバイスの ID を読込んだ際のロジアナ波形です。ソフト対応での転送ビットレートは送信/受信でそれぞれ 770KB/590KB 程です。Z80GalCompact は Z80 が 20MHz で動作していますが、シリアル通信用のタイマー割込みのため実質速度は 16MHz 程度です。
下図はステータス1~3の書込み後に読み出した際のロジアナ波形です。今回使用した EEPROM は型名の最後の Special Optoins が'Q'で Status2 の QE bit が 1 に固定になっています。
下図はデータリード時の波形で先頭の 0x03 がリードコマンドで次の 3 バイトがリード対象のアドレスでその後にリードデータが続きます。複数個所でクロックに隙間が発生しているのはシリアル通信のためのタイマー割込みのためです(この波形はCP/M起動時のもので割込み許可状態で動作している)。
リードデータ長に関しては自由にリードできるのですが、ライトの場合は、事前にデータを消去する必要があり、消去の最小単位が前述したように4Kバイトなので CP/M で使う場合のブロック長は最小でも4Kバイトということになります。
CP/M で使用する場合のブロック長は前述のように消去の最小単位である4Kバイトにし、ディスクパラメータを新規に作成し、ブロッキング/デブロッキング処理を実装しました。
下図は CP/M を立ち上げて STAT コマンドで DSK: のパラメータを表示した際の画面キャプチャーです。モニタで CP/M の HEX ファイルをロード後、GO コマンドで起動していますが、 シリアル送受信の割込みルーチンをBIOS 内の処理へ切替えている関係でスタートメッセージの先頭が文字化けしていますね。4KB のバッファを BIOS 内に持っているため 59K CP/M です。
アクセス速度に関しては SD カード時との差異を殆ど感じずに違和感の無い速度でした(消去に時間が掛かるので大量書込み時は遅く感じるかもしれません)。
冒頭で書いたように SD カードを利用して開発した CP/M 基板を公開し辛かったのですが、今回シリアル EEPROM で CP/M を起動できたので色々応用できそうです。
★追記 2023/06/06 {
HITECH-C で hello.c をコンパイルするのに要する時間を比較してみました。下の画面キャプチャで前半がSDカードでブートした従来の CP/M で後半が EEPROM でブートした CP/M です。コンパイル時間を TeraTerm のマクロで計測した結果が "Start=" と書かれた行になります。特にリンク時間(=書込み量が多い)が遅い感じで EEPROM の場合、1.75倍程度の時間が掛かっています。
}
Twitterに投稿した動画付きメッセージも貼っておきます。
秋月さんで販売している1MbitのシリアルEEPROM(S-24CM01CIーJ8T1U4)を購入していましたが、今回はより容量が大きな海外の通販で見つけた 32MBit EEPROM(W25Q32JVSIQ) を使って実験してみました。後者の方が書込み速度が遅く、許容している書込み回数も少ないのですが容量の大きさを優先しました。
最近、秋月さんで 4Mbit のフラッシュメモリ(IS25LP040E) を販売開始したようです。
今回使用した 32MBit EEPROM(W25Q32JV) のブロック図が下図になります。消去時の単位は 4KBytes/32KByte/64KByte/chiperase と柔軟性があり、最小単位は 4KByte です。
EEPROM(W25Q32JV)のブロック図 |
|
EEPROM とのインターフェースは基本的には SPI で今回は GAL を使って Z80 側のソフトウェアで対応することにしました。この EEPROM はデータ線が 2bit や 4bit の拡張インターフェースの機能もあるのでソフト対応しておくことで後日高速化できる余地を残すという意図もあります。
まずはデータ線が 1bit の SPI インターフェースでの接続実験を行いました。
下図は今回の EEPROM 評価時の回路です。上述のように GAL(GAL22V10D)を使用して EEPROM を接続しています。
EEPROM 評価用回路図 |
|
実験環境は下図のように Z80GalCompact の CPU ソケットを拡張して EEPROM を実装しています。
Z80GalCompact に接続した EEPROM 基板 |
|
EEPROM 部のZOOM |
|
下図はデバイスの ID を読込んだ際のロジアナ波形です。ソフト対応での転送ビットレートは送信/受信でそれぞれ 770KB/590KB 程です。Z80GalCompact は Z80 が 20MHz で動作していますが、シリアル通信用のタイマー割込みのため実質速度は 16MHz 程度です。
デバイスID読込み時のロジアナ波形例 |
|
下図はステータス1~3の書込み後に読み出した際のロジアナ波形です。今回使用した EEPROM は型名の最後の Special Optoins が'Q'で Status2 の QE bit が 1 に固定になっています。
ステータスリード/ライト時のロジアナ波形例 |
|
下図はデータリード時の波形で先頭の 0x03 がリードコマンドで次の 3 バイトがリード対象のアドレスでその後にリードデータが続きます。複数個所でクロックに隙間が発生しているのはシリアル通信のためのタイマー割込みのためです(この波形はCP/M起動時のもので割込み許可状態で動作している)。
リードデータ長に関しては自由にリードできるのですが、ライトの場合は、事前にデータを消去する必要があり、消去の最小単位が前述したように4Kバイトなので CP/M で使う場合のブロック長は最小でも4Kバイトということになります。
シリアルEEPROMリード時のロジアナ波形例 |
|
CP/M で使用する場合のブロック長は前述のように消去の最小単位である4Kバイトにし、ディスクパラメータを新規に作成し、ブロッキング/デブロッキング処理を実装しました。
下図は CP/M を立ち上げて STAT コマンドで DSK: のパラメータを表示した際の画面キャプチャーです。モニタで CP/M の HEX ファイルをロード後、GO コマンドで起動していますが、 シリアル送受信の割込みルーチンをBIOS 内の処理へ切替えている関係でスタートメッセージの先頭が文字化けしていますね。4KB のバッファを BIOS 内に持っているため 59K CP/M です。
アクセス速度に関しては SD カード時との差異を殆ど感じずに違和感の無い速度でした(消去に時間が掛かるので大量書込み時は遅く感じるかもしれません)。
DSK:パラメータ表示画面例 |
|
冒頭で書いたように SD カードを利用して開発した CP/M 基板を公開し辛かったのですが、今回シリアル EEPROM で CP/M を起動できたので色々応用できそうです。
★追記 2023/06/06 {
HITECH-C で hello.c をコンパイルするのに要する時間を比較してみました。下の画面キャプチャで前半がSDカードでブートした従来の CP/M で後半が EEPROM でブートした CP/M です。コンパイル時間を TeraTerm のマクロで計測した結果が "Start=" と書かれた行になります。特にリンク時間(=書込み量が多い)が遅い感じで EEPROM の場合、1.75倍程度の時間が掛かっています。
HITECH-Cでのhello.cコンパイル時間比較 |
|
★追記 2023/06/13 {
今回使用している EEPROM は読出しに関しては任意のアドレスから任意長のリードができるので EEPROM バッファにヒットしない場合には EEPROM からデータを読込んで DMA 領域に直に書き込むようにしました。このようにブロッキング/デブロッキング処理をハイブリッド化することで読込み時のバッファリング処理を極力省き高速化しました。
この変更後の HITECH-C で hello.c をコンパイルするのに要した時間を測定した結果が下図になります。前半が SD ブートで後半が EEPROM ブートになります。なんと SD カード上でのコンパイルより高速になりました^^
}
今回使用している EEPROM は読出しに関しては任意のアドレスから任意長のリードができるので EEPROM バッファにヒットしない場合には EEPROM からデータを読込んで DMA 領域に直に書き込むようにしました。このようにブロッキング/デブロッキング処理をハイブリッド化することで読込み時のバッファリング処理を極力省き高速化しました。
この変更後の HITECH-C で hello.c をコンパイルするのに要した時間を測定した結果が下図になります。前半が SD ブートで後半が EEPROM ブートになります。なんと SD カード上でのコンパイルより高速になりました^^
EEPROM上でのhello.cコンパイル時間(改善版) |
|
Twitterに投稿した動画付きメッセージも貼っておきます。
32MbitのシリアルEEPROM(W25Q32JVS)を使ってCP/Mを起動できました
— skyriver (@wcinp) June 4, 2023
体感的にはアクセスがSDカードより遅いという感じはありませんでした
消去の最小単位が4Kバイトなのでディスクパラメータを新設しブロッキング/デブロッキング処理を実装しています#Z80GalCompact #CPMhttps://t.co/eYbvvMhUSV pic.twitter.com/N11V0VYHYD
Z80でのDAA命令によるHEX変換テクニック [Z80]
Z80 等で BCD 演算のための補正命令である DAA を使ってバイナリ(00H..0FH)をヘキサの文字コード('0'..'9','A'..'F')に巧妙に変換する処理が知られています。
今回はこのバイナリ to HEX 変換処理でヘキサ文字を小文字にしたい場合の処理を考えたのでブログに記録しておきたいと思います。
最初に結論から書いてしまうと今回考えた下記のコードで 00H..0FH のバイナリを '0'..'9','a'..'f' に変換できます。
それでは上記のコードに辿り着いた経緯について以下に書きます。
★追記 2023/05/26
その後も少し気になったので下記のパターンで自動スキャンしてみました。xxとyyは 00H..FFH の範囲でフルスキャンし、ニーモニックの部分はそれぞれのパタンの全組合せを自動チェックしています。
CP/OR/ADD/SUB xx; NOP/DAA; ADC/SBC yy; NOP/DAA
結果は下図の通りで上記で既に記述したもの以外はありませんでした。「*** Find!!」以降の部分が見つけた解を表示している部分でそれ以外の表示はスキャンの進捗が判るように表示しているものです。
尚、各ニーモニックのオペコードは
CP:FE,OR:F6,ADD:C6,SUB:D6,NOP:00,DAA:27,ADC:CE,SBC:DE
です。
2バイト分のフルスキャンとオペコード切替えなので全チェックパターンは 210 万(=4*256*2*2*256*2)程ですが、上記の画面で最後に表示されているようにほぼ4分でスキャンが完了しました。環境は Z80 20MHz で GAME コンパイラを使用しています。ソースはこれです。
★追記 2023/05/26
ヘキサ文字への変換は最小単位としては1バイトを2桁のヘキサ文字に変換するケースが殆どなのではないかと思います。下位のニブルを取り出す時に AND 0FH を使用するとハーフキャリーが立ってしまうので厄介なのですが、OR 0F0H であればハーフキャリーが立たない(当然キャリーもゼロ)ので最初から DAA が使えます。入力データが F0H、FAH の場合、DAA 後は期待通りにそれぞれが 50H、60H になります。後は下記の様にキャリーを立てて加算/減算で調整すれば実質5バイトのコードで変換できます。
以上より、現時点ではヘキサ文字に変換する5バイトコードは3通りあるということが判りました。
OR 0F0H method 1
OR 0F0H method 2
★追記 2023/08/18
Twitter(X?)で関連するメッセージを見かけたのでこのページを紹介して、自分でも再度見てみました。最初に OR する方式で AND 方式の様に CP から始めるパターンもできることに気が付きました。
OR 0F0H method 3
今回はこのバイナリ to HEX 変換処理でヘキサ文字を小文字にしたい場合の処理を考えたのでブログに記録しておきたいと思います。
最初に結論から書いてしまうと今回考えた下記のコードで 00H..0FH のバイナリを '0'..'9','a'..'f' に変換できます。
|
---|
それでは上記のコードに辿り着いた経緯について以下に書きます。
- DAA 使用の一般的なヘキサ変換
例えば居酒屋ガレージ日記の「プログラムテクニック「DAA」」の記事に書かれているように
ADD A,90H DAA ADC A,40H DAA
の6バイトのコードで 00H..0FH を '0'..'9','A'..'F' に変換できます。
入力が 0AH 以上の場合は 最初の DAA で上位ニブルに桁上りし、更に上位ニブルが桁上りして 0 になり、キャリーが立つので2回目の DAA で上位ニブルは 4 となり、下位ニブルには キャリーも加算されるので最初の値から 9 が引かれた値になります。
入力が 09H 以下の場合は最初の DAA では上位ニブルが 9 となることで 2回目の DAA で上位ニブルが 4-1=3 になります。
BCD の桁上りとキャリーの効果を巧妙に組み合わせた処理ですね。
★追記 2023/05/24 {
このような6バイトの変換コードは他にも存在します。例えば先頭の"ADD A,90H"は"OR 90H"でもいいし、"ADC A,40H"が"ADC A,3AH"でも結果は同じです。
下記は今回考えたコードで引き算方式にしたもので、ここ以外では多分見かけないのではないかと思います。
my idea 1
SUB 0AH DAA SBC A,59H DAA
my idea 2 }
※追記 2023/05/27 CCFではハーフキャリーが不定なので上記は環境依存性があります
SUB 0AH SBC A,25H CCF DAA
★追記 2023/05/26 {
最初に OR を実行してハーフキャリーをクリアすれば普通に変換できます。
my idea 3
OR A DAA CP 10 SBC 0CFH
my idea 4 }
OR A DAA ADD A,0F0H ADC A,40H
- 更に短いコード
上記の様なヘキサ変換で必要な処理の要素を列挙すると
- 下位ニブルの桁上り
入力が 0AH 以上の場合は下位ニブルから上位ニブルに桁上りが発生するようにして上位ニブルを +1 する。
- 下位ニブルへの+1
入力が 0AH 以上の場合は下位ニブルに +1 されるようにする('A'のアスキーコードは 41H なので xAH を x1H にする)
例えば Twitterのタイムラインで見つけた
CP 10 SBC A,69H DAA
の5バイトのコードでもヘキサ変換が可能です。SBC を絶妙に組み合わせることで上記の2つの必要な要素が実現されています。
キャリーを加算することで同じようなことが出来ないか考えてみました。入力が 0AH 以上の場合にキャリーを発生させる必要があるので
ADD A,0F6H ADC A,3AH DAA
としてみましたが、ADC 後も入力が 0AH以上の場合と 09H 以下の場合でキャリーの値が異なるので DAA の作用が不統一になり破城しましたw
CP 命令は減算処理はするが A-reg は変更しないという命令ですが加算でも同様にフラグは更新するが A-reg を変更しない命令があればいいのですが・・・
1バイト増えてしまいますが下記の様にキャリーの操作を加えると上手く行きました(処理内容も判り易いですね)。
CP 10 CCF ADC A,30H DAA
- 下位ニブルの桁上り
- 小文字のヘキサ変換
上記の様に ADD や SUB でキャリーを操作した場合、次の加算または減算で 入力が 0AH 以上の場合と 09H 以下の場合でどちらかがバイトのバウンダリーを超えるので、キャリーが不一致な状態となり、DAA で不要に +6 や -6 されるので、5バイトでのヘキサ変換処理は上記の方法以外は無いのではないかと推測します。
と言うわけで趣向を変えて小文字のヘキサ変換について挑戦してみました。DAA 命令の複雑な挙動に関しては「Zilog Z80 DAA実行結果」にまとめられているのを見つけたので、これを参考にして辿り着いたのが冒頭に書いたコードになります。
下記のような検証用のプログラムで全てのケースについて確認を行いました。
1バイトの小文字ヘキサ変換試験プログラム(Z80アセンブラ) ;+++++++++++++++++++++++++++++++++++++++++++++++ ; translate 0x00-0x0f to hex ascii code '0'-'f' ; Ver 0.01 2023/05/22 by skyriver ;+++++++++++++++++++++++++++++++++++++++++++++++ 000D CR EQU 13 000A LF EQU 10 0001 SMALL EQU 1 ; lower case ToHex macro if SMALL EQ 0 CP 10 SBC A,069H DAA else SUB 10 DAA DAA DAA SBC A,09FH endif endm 0000' ASEG ORG 0100H 0100 AF XOR A 0101 F5 LOOP: PUSH AF 0102 CD 0116 CALL TRAN 0105 3E 20 LD A,' ' 0107 CD 0135 CALL PUTC 010A F1 POP AF 010B 3C INC A 010C F5 PUSH AF 010D E6 0F AND 0FH 010F CC 013C CALL Z,NEWLN 0112 F1 POP AF 0113 20 EC JR NZ,LOOP 0115 C9 RET ; translate to hex ascii code ; A <- data(00-0f) 0116 F5 TRAN: PUSH AF 0117 0F RRCA 0118 0F RRCA 0119 0F RRCA 011A 0F RRCA 011B E6 0F AND 0FH ToHex 011D D6 0A + SUB 10 011F 27 + DAA 0120 27 + DAA 0121 27 + DAA 0122 DE 9F + SBC A,09FH 0124 CD 0135 CALL PUTC 0127 F1 POP AF 0128 E6 0F AND 0FH ToHex 012A D6 0A + SUB 10 012C 27 + DAA 012D 27 + DAA 012E 27 + DAA 012F DE 9F + SBC A,09FH 0131 CD 0135 CALL PUTC 0134 C9 RET ; put chara on screen ; A <- data 0135 PUTC: 0135 5F LD E,A 0136 0E 02 LD C,2 0138 CD 0005 CALL 0005H 013B C9 RET ; new line 013C 3E 0D NEWLN: LD A,CR 013E CD 0135 CALL PUTC 0141 3E 0A LD A,LF 0143 CD 0135 CALL PUTC 0146 C9 RET END Macros: TOHEX Symbols: 000D CR 000A LF 0101 LOOP 013C NEWLN 0135 PUTC 0001 SMALL 0116 TRAN No Fatal error(s)
実機の CP/M-80 環境での確認結果が下図になります。
CP/M-80 環境での確認結果(OK)
ところが Windows の DOS 窓での CPM エミュ環境では下図のように NG でした。尚、プロンプトで表示されているパス情報はわざとぼかしています。
CP/M エミュ環境での確認結果(NG)
CP/M エミュでは DAA を実行した後のフラグの挙動が Z80 と違うことが原因の様です。下図は Z80 の実機(Z84C0020PEC)を使って CP/M 環境で ZSID でステップ動作した結果です。
Z80 実機でのステップ動作
下図が CP/M エミュでのステップ動作になります。DAA 命令での 'I'フラグの挙動が上記の実機と違いますね。尚、'I'フラグはハーフキャリーです。
CP/M エミュでのステップ動作(NG)
GAME 言語を使って 00H..0FH のどの範囲が NG なのか確認してみた結果が下図になります。上記の結果からも判っていましたが、00H..09H の範囲がNGの様です。
CP/M エミュ環境での確認結果(NG)
エミュということで試しに MSXPen 環境で確認した結果が下図になります。MSXPen 環境では流石に問題無いようです。
MSXPen 環境での確認結果(OK)
★追記 2023/05/26
その後も少し気になったので下記のパターンで自動スキャンしてみました。xxとyyは 00H..FFH の範囲でフルスキャンし、ニーモニックの部分はそれぞれのパタンの全組合せを自動チェックしています。
結果は下図の通りで上記で既に記述したもの以外はありませんでした。「*** Find!!」以降の部分が見つけた解を表示している部分でそれ以外の表示はスキャンの進捗が判るように表示しているものです。
尚、各ニーモニックのオペコードは
です。
変換コードパターンのスキャン結果 |
|
2バイト分のフルスキャンとオペコード切替えなので全チェックパターンは 210 万(=4*256*2*2*256*2)程ですが、上記の画面で最後に表示されているようにほぼ4分でスキャンが完了しました。環境は Z80 20MHz で GAME コンパイラを使用しています。ソースはこれです。
★追記 2023/05/26
ヘキサ文字への変換は最小単位としては1バイトを2桁のヘキサ文字に変換するケースが殆どなのではないかと思います。下位のニブルを取り出す時に AND 0FH を使用するとハーフキャリーが立ってしまうので厄介なのですが、OR 0F0H であればハーフキャリーが立たない(当然キャリーもゼロ)ので最初から DAA が使えます。入力データが F0H、FAH の場合、DAA 後は期待通りにそれぞれが 50H、60H になります。後は下記の様にキャリーを立てて加算/減算で調整すれば実質5バイトのコードで変換できます。
以上より、現時点ではヘキサ文字に変換する5バイトコードは3通りあるということが判りました。
|
---|
|
---|
★追記 2023/08/18
Twitter(X?)で関連するメッセージを見かけたのでこのページを紹介して、自分でも再度見てみました。最初に OR する方式で AND 方式の様に CP から始めるパターンもできることに気が付きました。
|
---|
CP/M-80でのBIOSエミュレートによるMBASICの起動 [Z80]
Twitter のタイムラインで CP/M-80 版 MBASIC は BIOS のコンソール入出力機能のみでも基本動作はするという情報を見かけたので確認して見ました。
最近使っている 20MHz 動作の Z80PicCompactの開発機は CP/M が自動起動する設定にしているので、「CP/M用hexローダーの製作」の記事で紹介した HexLoader を使ってヘキサファイルをロードして実験しました。
MBASICを起動するためにはBIOS のコンソール関連の実装以外に下記の項目への対応が必要でした。
上記の項目を対応することで無事 MBASIC が起動するようになりました。
殆ど処理が無いですが、ソースは下記になります。
Twitter に投稿した動画付きメッセージを貼っておきます。上記の BIOS エミュレータ+ MBASIC 本体のHEXファイルをロードすることで MBASIC を起動しています。
BIOS エミュレータの WBOOT は HALT するようにしてあるので CP/M に戻るために元々の WBOOT(0xfa03)をコールしています。
最近使っている 20MHz 動作の Z80PicCompactの開発機は CP/M が自動起動する設定にしているので、「CP/M用hexローダーの製作」の記事で紹介した HexLoader を使ってヘキサファイルをロードして実験しました。
MBASICを起動するためにはBIOS のコンソール関連の実装以外に下記の項目への対応が必要でした。
- BDOS コールアドレスの設定
MBASIC は BDOS コール(0005H)部分の BDOS アドレスを参照して、BDOSCCP 領域も使用するので BDOS コール用のジャンプ命令を設定する必要がある。
★修正 2023/05/17
MBASIC はディスクアクセス機能が必要なので BDOS 領域は壊さないことを確認しました。
- コマンドパラメータ無しの設定
MBASIC は起動時に 0080H 部分にあるコマンドパラメータを参照してパラメータがある場合、ソースをロードしようとします。0080H を 00H に設定することでパラメータが無い状態にします(この部分は上記の HexLoader が使用するメモリなのでこのために HexLoader をアップデートしました)
上記の項目を対応することで無事 MBASIC が起動するようになりました。
殆ど処理が無いですが、ソースは下記になります。
CP/M BIOS エミュレータのソース |
|
Twitter に投稿した動画付きメッセージを貼っておきます。上記の BIOS エミュレータ+ MBASIC 本体のHEXファイルをロードすることで MBASIC を起動しています。
BIOS エミュレータの WBOOT は HALT するようにしてあるので CP/M に戻るために元々の WBOOT(0xfa03)をコールしています。
CP/M-80版MBASICはBIOSのコンソール入出力機能のみでも基本動作はするようなので実験してみました
— skyriver (@wcinp) May 16, 2023
BIOSのソースが手元にあるので環境構築は容易です
BDOSコールアドレスを参照して再利用している点と0080Hを00Hにしてアーギュメント無し状態にする必要があることに対応することで立ち上がりました pic.twitter.com/e60gH9sgE6