マルチCPUの仕組み(2) ソフト編
動いているCPUが自分自身を停止させ、相手を動かせるハードの仕組みについては、
6809/Z80マシーンを発掘しました(3) マルチCPUの仕組み(1) ハード編 - 電音の歩み
を見てください。
とてもシンプルなハードですが、シンプルすぎてどのように使えば良いのか疑問にもたれた方もあるでしょう。
実際、このハードだけではCPUを交互に動かせるだけなので何もできません。
これを活かすソフト面での仕掛けが、「CPU Manager」です。
この図は、発掘したソースコードを見て、私が再発見しながら書いた「CPU Manager」の動作フローです。左半分は6809コード、右半分はZ80コードです。
仕組みはとてもシンプルですが、驚くほどパワフルです。
この「CPU Manager」の狙いは、
(1) 6809からZ80の関数を、Z80から6809の関数を引数付きでコールできる。
( ACCA, X ) は、 ( Areg , HLreg )に相互に引き継がれます。
(2)相互の関数コールは、多重にできる。
だけです。
これができれば、相手のCPUの関数も自在に利用できることになりますよね。
まず、基本動作を説明します。
■Z80側の初期化
Resetが掛かると最初に6809が動き出すので6809側の色々な初期化は自由にできますがZ80側の初期化も必要です。
話を簡単にするため、ここには書いていない6809側の初期化コードで、Z80をResetから起動しSPを設定し、< 0 >のルートでCHG_CPUのポイントまで導き、制御を6809の初期化コードに戻します。(Z80 のRESET後の例外的な動きのためハードでもRESETを記憶するD-FFが付いています)
これで Z80 側の準備もできました。
重要なのは、CHG_CPU(どちらのCPUからも切替ポートにダミーライトすると相手に制御が移る)のポイントは、どちらのCPUも1箇所しか無いことです。
■6809からZ80の関数をコールする動作
6809でZ80の関数(サブルーチン)を呼びたいときは、
CALLZ80 address
というマクロを記述します。
実際の命令は、
JSR CALZ80
FDB address
というコードが生成されます。
シーケンス< A >
JSR 命令の後ろに続くコーリングシーケンスのアドレスを取り出し、引数メモリの (PC) に格納し、" CFLG = 0"にします。これは、6809からZ80をコールすることを意味します。
さらに、 X レジスタを ( XREG )、 ACCA を( AREG )に書き込み、CHG_CPUポイントでCPUを Z80 に切り替えます。
シーケンス< 2 >< 4 >< 5 >
6809は停止し、Z80は CHG_CPUポイントから動き出します。
( XREG )を HL レジスタに、( AREG ) を A レジスタにロードし、CFLG をチェックします。
ここでは CFLG は 0 なので、( PC )のアドレスのサブルーチンをコールします。
関数から戻ってくると、今度は CFLG は 6809 に Z80 から帰ってきたことを示すために" CFLG = 0" に再設定し、HL と A レジスタを書き戻し、CHG_CPUポイントでCPUを 6809 切り替えます。
シーケンス< B >< C >
CHG_CPUポイントから戻ってきた6809は、レジスタの引き継ぎを行い、 CFLG をチェックし ここでは CFLGが 0 なので、Z80 の関数から戻ってきたことが分かり、リターンします。
ここが重要なポイントですが、もしこのとき CFLG が 1 であった場合、6809からコールしたZ80の関数から戻ってきたのでは無く、そのZ80の関数の中からさらに6809の関数をコールしたことになり、シーケンス< C >ではなくシーケンス< D >に向かいます。
このCFLG の仕組みと、6809 と Z80 それぞれのスタックの働きにより、何重にでも多重コールが実現できることになります。
話が飛びましたが、こんどはZ80から6809を呼ぶ動作を説明します。
■Z80から6809の関数をコールする動作
シーケンス< 1 >
CALL CALL6809
DW address
と記述すると、シーケンス < 1 >に入り、" CFLG = 1"として CHG_CPU に行き、制御を 6809に移します。
シーケンス< B >< D >< E >
6809はレジスタを引き継ぎ、 CFLG が 1 であることを確認し、addressの関数を間接アドレッシングのJSR命令でコールします。
戻ってくると6809関数からの復帰を知らせるため、CFLG を再度 1 に設定し<
CHG_CPUポイントからZ80に復帰します。
シーケンス< 2 >
レジスタを引き継ぎ、CFLGが 1 なので6809からの帰還と見なしRETします。
■6809からZ80の関数をコールし、その関数は6809の関数をコールしているときの動作
6809で書かれたアプリケーションソフトが CP/M で動作している様子を見てみましょう。アプリは既に動いており、スクリーンに文字を表示しようととしています。
6809コードのアプリですので、Z80コードのCP/M の API をコールするためには、CALLZ80機能を使います。
ここまでで、CFLG=0 でシーケンス < A><2 > < 4 >まで来ました。
CP/M の API は さらにZ80コードのBIOSをコールします。
ところが呼ばれたBIOS の大半のAPI はそのまま 6809 で書かれた ROM内ルーチンをコールするだけで済ませています。
即ち、CFLG=1 として、シーケンス< 1 >< B >< D >と進みます。
< D >で呼ばれたROM内の6809コードで文字表示を行い、CFLG = 1として< E >
を進み、CHG_CPUで< 2 >< 3 >と抜け6809関数コールを終えます。
この関数コールをしていたのは、< 4 >で6809から呼ばれていたZ80 のBIOSですので、CFLG = 0 として< 5 >に進み、CHG_CPU ポイントから、6809コードで書かれたアプリに戻ってきます。
万事うまくいくでしょう。
このような仕組みにより、FLEX09用に作られた6809コードのスクリーンエディタ " DUET " ですが、APIコールだけ CP/M 用に変更して、立派な CP/M アプリとして動いています。
アプリのコードも実際のBIOSの下請けコードも全部 6809 なので、このアプリの動作中はほとんど赤のLEDが点いています。