ワンボードマイコンをつくろう!(パソコンの原点はここから始まった)
TK80ソフトコンパチブル!8080、Z80マシン語からBASICまでこれ1台でこなせます
当記事は2009年11月から「TTLでCPUをつくろう!」というタイトルの もとにほとんど毎日連載をしてきたものを再編集したものです。 2011.6.30
前へ
次へ
目次へ戻る
ホームページトップへ戻る
☆ND80ZVでBASICを。
とんでもない機能追加を思いついてしまいました。
ND80ZVでBASICを実行できるようにしようというのです。
それも浮動小数点演算ができて、三角関数や対数計算までできる本物のBASICです!

[第56回]

●FOR NEXTのデバッグ

前回は/saveコマンドのデバッグ作業をしていて、listコマンドでの表示がおかしいことに気が付きました。
/saveコマンドでは受信バッファのデータをそのまま表示しているのですが、listコマンドでは受信バッファにデータがあっても表示しないで、最後にプロンプトマーク>(コード3E)と入力要求コード01を受信したときにはじめて受信バッファにたまっている全データを表示するようになっていました。

このままではたとえば非常に長いプログラムをlist表示させるときなどに困ったことになります。
なにしろ全データを受信し終わるまで、全く表示されないわけですから。
それは、困ります。

なので、そこのところは、受信する都度表示するように、C++のプログラムを直しました。
前回最後にお見せしたログファイルはデバッグのための受信データも一緒に表示されています。
下はプログラムの修正後に、デバッグ用の受信データを表示しないようにしてから、listコマンドと/saveコマンドを実行した画面です。


ひょんなことから、listコマンドの表示がまずいことに気が付いて直したのですが、それでやっとテストプログラムが何も表示しないでハングアップしてしまう理由がわかりました。
listコマンドだけではなくて、そのほかの多くのコマンドやBASICプログラムのPRINT文の表示なども同じ表示方法になっていたことがその理由でした。

どうも実数型のFOR NEXT文にバグがあるらしくて(まだこの時点では原因を追求中なのですが)そこまでくると無限ループになってしまうらしく、BASICプログラムから抜け出せないようなのです。
するとどこまでいっても、プロンプトマーク>(コード3E)と入力要求コード01が送られてこないことになりますから、受信バッファにはそれ以前の表示データは溜まっていたのですけれど、それが全然表示されなくて、ハングアップしたように見えていたのです。

これで不可解に思えた現象もかなりはっきりと見えるようになってきました。
解決まであと1歩です。



とりあえず/saveコマンドでテストプログラムは保存しましたので、newコマンドでBASICプログラムをクリアしました。
newコマンドはそれまでそこに書かれていたプログラムを全消去するコマンドです。
と言っても本当にメモリクリアしてしまうのではなくて、プログラムの開始アドレス、終了アドレスや変数エリアの情報を初期化するだけですから、newコマンドを実行しただけで、まだ新たなプログラムを一行も入力していないときでしたら、helpコマンドで元通りにプログラムを復活させることができます。

さてここでは新規にプログラムを書くために、まずnewコマンドを実行しました。
もうlistコマンドを入力しても何も表示されなくなりました。
ちょっとわかりにくいのですが、newの下でlistと入力したその下にはプロンプトマーク>しか表示されていません。
10のfor文と20のnext文は、その後でキーボードから新たに入力したものです。

実数型のFOR NEXTがおかしい、ということはほぼ確実になりましたから、余計な部分は省いてしまって、画面のようにFORとNEXTだけにして実行してみました。
これは予想通りハングアップしてしまいました。

そこでいよいよ実数型のFORとNEXTのデバッグです。
FOR NEXTループのカウンタに使う変数名としてA%のように整数型変数を使うと、整数型のFOR NEXT文になります。
変数名の後ろに%をつけると、その変数は整数型になります。
何もつけないで、AとかABCとだけ書いた場合、その変数は実数型になります。

上で新たに書いたテストプログラムではFOR NEXT文の変数に実数型変数のAを使いましたから、このFOR NEXTは実数型で実行されます。


FOR文のエントリアドレスは256Dです。
いきなりサブルーチンをコールしていますから、まずはそのCALL FGOSの次のアドレスにブレイクポイントを置いて、テストプログラムを実行してみます。
bp 2570
run
です。

アドレス2570でブレイクしました。
ユーザープログラムのアドレスを保持しているDEレジスタの値は…。
あれ?
4020になっています。

これはいけません。
とんでもないバグです。
実はこのZ80BASICシステムのもとになったZBK開発セット用のBASICでは、ユーザープログラムの開始アドレスは4000でした。
そこのところは、8000になるように直したつもりだったのですが、変更していないところがあったようです。
今回のZ80BASICシステムでは4000番地台にはシステムプログラムがありますから、モロ破壊されてしまっています(汗)。

この時点ではまだそのことに気が付いていないようです。
なんと恐ろしいこと。
しかしFOR NEXTの動作には幸い影響は及んでいないようです。
システムプログラムを破壊してテストプログラムが書かれてしまっているわけですけれど、とりあえずは動作しているようですし、せっかくの画面コピーですから、それを使ってしばらくは説明を続けていくことにいたします。

アドレス256Dから2580までのところで、FOR文で使っている変数のアドレスをFORVLに保存し、その変数の型をFORVTに保存しています。
変数のアドレスはDFFCで変数の型はF0(実数型)です。

[10.7.4追記ここから]
この値はアドレス2576でブレイクしたときのレジスタダンプに表示されています。
HLレジスタに変数AのアドレスDFFCが、そしてAレジスタに変数Aの型(実数型)を示すF0が入っています。
サブルーチンFGOSとLETSの実行によって、FOR A=0の部分が解読されて、実行された結果です。

テストプログラムのアドレスを保持しているDEレジスタは4020から4027に進みました。
そういえば、テストプログラムのメモリダンプリストがありませんねえ。
うっかりしていて、それを表示させるのを忘れてしまったようです。

でもここで同じ内容を再現しようとしますと、またシステムプログラムを破壊してしまいます。
もう今はバグを修正してアドレス8000からユーザープログラムが格納されるようになっていますから、この時点ではアドレスが4000からになっていたところを、アドレス8000からになってしまいますが、テストプログラムを再入力して、そのメモリダンプリストを表示させてみました。

logfile nd80zlog\07032039.txt open

ND80ZVに接続しました
0001 0000 - Z1000 00C3 - *** nd80z3 basic ****
>
>10FOR A=0 TO 10
>20NEXT A
>LIST
    10 FOR A=0 TO 10
    20 NEXT A
>HELP
TEXT 8004-8036
ヘンスウ DFFB-DFFF
>DM,8000,8036
8000  04 80 10 80 00 80 FF DF 10 80 0C 00 B3 53 44 94  .......゚....ウSD.
8010  FC DF 41 00 00 00 00 85 0A 00 04 00 0A 00 0D 84  .゚A.............
8020  F0 0C 00 9A FA 00 00 98 FA 0A 00 0D 10 14 00 05  ................
8030  85 F0 0C 00 0D 08 00 0D 10 14 00 0B F1 0C 00 9A  ................
>/EXIT

ndremote.exeを終了しました
logfile closed at Sat Jul 03 20:39:44 2010

デバッグしたときと同じテストプログラム(と言ってもたった2行ですけれど)を入力しましたから、その格納アドレスが4000から8000に平行移動しただけです。
さきほど2570でブレイクしたときのDEレジスタの値は4020でした。それを8020に読み替えます。

ユーザープログラムは8004から書き込まれていきます。
8000〜8003はシステムのエリアです。
ユーザープログラムそのものは、801Cから格納されています。
8004から800Fは、やはりシステムのエリアです。
その後ろ8010〜801Bに変数Aの情報が書かれています。
このテストプログラムでは、変数Aだけしか使いませんから、このようなメモリ内容になっています。

ここにさらにプログラムを書き加えていって、新たな変数が登場する度に、この変数領域は後ろに拡張されていきます。
それに伴ってプログラムそのものが書き込まれている領域も、ここでは801Cから始まっていたものが、どんどん後ろにずらされていきます。

つまりプログラムを作成中に新しい変数が使われると、その時点で、その変数のための領域を確保するために、プログラム本体の領域がその分だけ後ろにずらされる(メモリ間移動)のです。
なぜそのような面倒なシステムになっているかと言いますと、実はZ80BASICのもとになったZBK開発システム用のBASICはプログラムをROM化して、ターゲットボードをスタンドアローンで実行することを前提にして作られたBASICだったからです。

その考え方からすると、今はRAMエリアになっている8000〜のエリアは、プログラムが完成した後はそのままROMになってしまうことになります。
通常考えられるパソコン上でのBASICでしたら、変数エリアだけではなくて、その変数のための情報もRAM上にあって構わないのですから、たとえばプログラムのエリアは8000から始まって、変数のエリアと変数名などの情報のエリアはRAMの後ろの方に置く、というようにするのが、ごくまっとうな考え方になります。

しかしプログラムをROM化するとなると事情は異なってきます。
変数そのもののデータエリアはRAM上になければいけませんが、その変数のための情報はプログラムで参照する基本情報ですから、そこが変わってしまうことは許されません。
そのために、変数の基本情報もプログラムと一緒にROM化できるようにプログラム本体の前に置いてあるのです。

わずか1行のプログラムを追加したり、ちょっと内容を書き換えたりするだけでも、それを中間コードに翻訳してメモリに格納したり、メモリ内容を更新するために、けっこう複雑なシステムが動いているのです。

またまた余談になってしまいました。
そのように、今回のテストプログラムは中間コードに直されて801Cから後ろに置かれています。
801C、801Dは行番号です。0A 00ですが、これは16ビット2バイトの2進数を、下位、上位の順に置いたものです。
000Aですから、行番号10です。
その1バイト置いた次801Fは84になっています。
84はFOR命令の中間コードです。

最初にブレイクした2570の直前に実行された、サブルーチンFGOSは、この84を読んで必要な初期設定をするためのルーチンです。
FOR文とGOSUB文はプログラムの流れを一時的に変えて実行されるために、もとの戻り先の情報をスタックに保存しておかなければなりません。
さらにFOR文もGOSUB文も、その中で別のFOR文やGOSUB文を多重に使用することが考えられます。
それを間違い無く実行するためには、厳格なスタック管理が必要になってきます。
そのためのお膳立てをするのがFGOSサブルーチンなのです。

その次にブレイクした2576の直前で実行されたサブルーチンLETSは、数式を実行するルーチンです。
通常BASICでの数式表現は、
A=0
とか
B=C+D
のように書きますが、
正式には、
LET A=0、
LET B=C+D
のように表記します。
LETは「以下の数式を実行せよ」というコマンドです。

ですから数式を実行するために、LETS(LETサブルーチンの意味)をCALLしているのです。
LETSサブルーチンの実行によって、アドレス8020〜8026までが解読され、実行されました。

メモリダンプリストが上の方にいってしまいましたので、8020〜8026の内容を下に再記いたします。

F0 0C 00 9A FA 00 00 

最初の3バイトは変数Aを示しています。
F0は実数型変数であることを示す中間コードです。その次の2バイトは変数の情報が置かれている場所を示しています。
プログラムの先頭アドレス8004に、この値000Cを加算して求まる8010に変数Aの情報があります([第50回]参照)。
その次の9Aは=を示す中間コードです。
LET文の中では=は等号ではなくて、右辺の計算結果を左辺の変数に代入せよ、という演算命令として使われます。

その後ろの3バイトは、定数0を示しています。
それならただ0と書いた方が簡単じゃないのさ?
とお思いかもしれませんが、そうはいかないのです。
ここではいとも簡単に解読されていくように見えますが、実際に1バイトずつ解読しながら進んでいく過程では、新たに出現するコードはすべてルールに合ったコードである必要があります。
突然00が出現して、これは定数のゼロだから、そう思え、などとCPUに言うのは無理というものです。

ですからさきほどの変数AをF0で示したように、ここも定数であることをFAで示しているのです。
FAは整数型定数を示す中間コードです。
その後ろの2バイトは整数型定数(16進数2バイト)の0を示します。0000です。

LETSサブルーチンはここまでを解読して変数Aのメモリエリアに値0を格納します。
プログラムに書かれている定数0は、今説明しましたように整数型2バイトの値ですけれど、変数Aは実数型で4バイトの値を持ちます。
LETSはそこのところを判断して、整数型定数の0を実数型に変換して変数Aのメモリエリアに格納します。
たったA=0のところを解読して実行するだけでも、そら恐ろしい作業が必要なのです。
[追記ここまで]



その次の25ACから25B2までのところでは何をしているかといいますと、
FOR A=0 TO 10
のTOの値(ここでは10)をFTOに保存しています。
FTOは実数型データを格納しますから4バイトのエリアになります。
アドレスはF0F1〜F0F4の4バイトです。
そこに、アドレス25ACのCALL CALCによって計算した結果を、APKというサブルーチンを使って書き込んでいます。

ただの10を計算するだの、保存するためにわざわざサブルーチンをCALLするだの、いきなりわけのわからないことを書いてしまいました。
ここはもう少していねいに説明しないといけませんねえ。

あ。
本日はちょっと時間が無くなってしまいましたから、その説明は次回、ということにいたします。
CPUをつくろう!第541回(2010.7.3upload)を再編集


ワンボードマイコンをつくろう![第56回]
2011.6.30upload

前へ
次へ
目次へ戻る
ホームページトップへ戻る