この界隈?でのZ80用の実数 BASICといえばGRANT’s BASICですよね。

日本でも多くの方が、彼がまとめたNASCOM BASICのソースコードを利用して、自身のボードにBASICを移植されていますよね。

Z80だけでなく、6809や6502でも同じように、ハードもソフトもコンパクトにバランスよくまとめられていて、敬服の限りです。

私も、彼のZ80,6809,6502のBASICを一通り味わわせていただきました。BASIC、しかもフルアセンブラなんて何十年かぶりなので本当に勉強になりました。

ソースコードの取得

ここでは、彼の移植バージョンではなく、そのもととなったソースファイルを使用して、Z80-COREに移植したのでその作業の概要を示します。移植に使用したソースファイルは以下のサイトから入手しました。NASCOM関係の情報を集約したサイトです。

先に移植成果のお披露目

移植したNASCOM BASICの起動画面を以下に示します。1枚目の画面が今回移植したNASCOM BASICで、2枚目の画面がGRANT’s BASICです。GRANT’s BASIC は起動タイトルがZ80 BASICとなっていますね。

なお、1枚目の画像のCコマンドは、メモリをすべてRAMに切り替える指示です。Z80-COREは、起動時には0000H-3FFFHがROMになっており、0000H-0FFFHにモニタが常住しています。この状態では、1000H-3FFFHは空いていますが、ROMなのでコードを書き込むことができません。そこで、CコマンドでメモリをすべてRAMに変更した上で、1000HからにNASCOM BASICをロードしています。HEXファイルの読み込み時に、モニタが管理するPCに実行開始アドレスが自動的に設定されます。

NASCOM ROM BASIC
GRANT’s Z80 BASIC

移植手順

以下のような感じでソースコードの移植作業を進めていきました。

使用したアセンブラは、TASMではなくアークピットのXZ80です。

先に示したように、モニタでダウンロードして使用するので、BASICの初期化時にあらためてSIOの初期化は必要ないし、コンソール入出力はモニタの機能を使用する。

BASICの移植を行う際には、ほとんどのマイクロプロセッサのほとんどのBASICの実装で以下の3つのルーチンがあればよいことが示されており、それらはモニタのルーチンを利用する。

  • 一文字の入力ルーチン
  • 一文字の出力ルーチン
  • キー文字が入力されたかどうかを確認するルーチン

マイクロソフトのBASICではメモリサイズの自動検出機能(MLOOP)が組み込まれているが、以下のような理由で必要ないのでメモリ領域は決め打ちとする。

  • Z80-COREはメモリ空間フルにメモリを実装している。
  • また、68系とは異なり、I/O関係のマッピングでメモリの一部に穴が開くこともない。
  • メモリエラーを検出できるという考えもあるが、それはこのクラスのマシンでは別の話。

BASICの作業用の変数エリアの変数一つ一つにEQUでアドレスが書かれていたので、メモリ配置を変更しやすいように、変数エリアの先頭のアドレスの指定をかえれば、すべての変数のアドレスがそれに合わせて変更されるように書き換えたりもしました。

Z80-COREを含め、XENESISのボード群に移植したプログラムは、使い方に応じて、メモリ構成が変わったりROMに焼いたりで、状況に応じてコードやデータのメモリ配置を様々に変更する必要があるので、このような事前変更は非常に重要です。

ソースコードの初めの部分で定義されているNASCOMのハードウェアやモニターに依存するシンボルをコメントアウトして、アセンブルし、そこで(大量に)出てきたエラーに関連するコードを片っ端から切り取り・修正していく。。。ということで、移植?切除?を行っていきました。

他にもこまごまと作業を行いました。特に困って覚えていることは:

POINT()関数の様に、通常の関数とは異なる特別な処理が行われている関数などもあり、その対処などを行った。

他の移植でも遭遇したBASICのキーワード処理のあるあるとして、以下のように括弧のあるなしで、アセンブラの出力データが変わるのはやめてほしい。もともとソースコードに括弧がなかったので、すべてに括弧つけました。

  317 1134 CE455854                DB       ("N"+80H),"EXT"
  318 1138 4480415441              DB       "D"+80H,"ATA"

この問題の発見の経緯もどう言ったらいいのやら。。。BASICの移植は完了し、起動してプログラムも入力できるのに、コマンドモードでもシンタックスエラー、RUNしてもシンタックスエラーで、ほとんどなにをやってもシンタックスエラー。シンタックスエラーとは言ってるけれどもそれ以前の、コマンドやステートメントが認識できていないようなのでアセンブラのリストを見ると、お約束の状況が発生していました。

これを解決して、次こそはと思ってプログラムを実行させるとめでたく動いた。

とおもったら、マンデルブロ集合の表示プログラムを動かすと、またエラー。PRINT文のセミコロンがダメみたい。

そこで、PRINT文の処理のセミコロンのところを見ると以下のような感じ。問題はなさそうに見えるが。。。

        CP      ","             ; Comma?
        JP      Z,DOCOM         ; Yes - Move to next zone
        CP      ";"             ; Semi-colon?
        JP      Z,NEXITM        ; Do semi-colon routine

アセンブル結果は?

 1741 1A38 FE2C                    CP       ","         ; Comma?
 1742 1A3A CA9E1A                  JP       Z,DOCOM     ; Yes - Move to next zo
 1743 1A3D FE00                    CP       "           ;"             ; Semi-c
 1744 1A3F CAD81A                  JP       Z,NEXITM    ; Do semi-colon routine

。。。勘弁してくれ、そのセミコロンはコメントじゃないんだよ。

そもそも元のソースコードで文字列ではなく文字リテラルをダブルクオートで括っていることもどうかと思うが。。。

シングルクオートで括ると、アセンブラ様の許可が下りました。

 1741 1A38 FE2C                    CP       ","         ; Comma?
 1742 1A3A CA9E1A                  JP       Z,DOCOM     ; Yes - Move to next zo
 1743 1A3D FE3B                    CP       ';'         ; Semi-colon?
 1744 1A3F CAD81A                  JP       Z,NEXITM    ; Do semi-colon routine

何度目かの今度こそ終わりと思って、プログラムを入力してリストを表示させると、リスト表示が少ない行数で止まったり、出力文字が、画面の中央を過ぎたところで自動的に折り返したり、いろいろとトラブル。

画面の幅とLIST行数のデフォルトを変更。利用者からはWIDTH文とLINES文で変更できるみたい。

計時機能の追加

さて、移植作業がおおよそ片付いたので、NASCOM BASICにも、Z80-COREが持つ計時機能を利用できるようにUPTIME()関数を追加しました。この追加には、関数の入出力が同じ形態となっているDPEEKで、引数や返り値の処理方法を参考にして組み込みを行いました。

計時機能も組み込み、UPTIME()関数で、ボードの起動後の時間を秒単位で取得できます。この機能を使用すると、プログラムの実行時間を計測することができます。

早速マンデルブロ集合の表示プログラムの実行時間を計測してみました。プログラムは以下の通りで、最初と最後の行に、計時用の処理を追加しています。

早速マンデルブロ集合の表示プログラムの実行時間を計測してみました。

(動作保証外の)8MHzのクロックで、実行時間は155秒:2分35秒となりました。

また、4MHzのクロックでは、実行時間はちょうど倍の310秒:5分10秒となりました。

ちなみにSDCCでは、8MHzのクロックで、実行時間は107秒:1分47秒となりました。両者の計算精度はほぼ一緒です。実数の内部表現の詳細は別にして、それぞれ4バイトです。C言語での実行は速くなりましたが、驚くほどではないですね。このプログラムは、実行制御にではなく、実数計算にほとんどの時間を取られているのでしょうね。

まあ、何はともあれ、Z80-COREで、計時機能付きでメモリが53Kバイト程度使えるNASCOM BASICが使えるようになりました。めでたい。