カテゴリー別アーカイブ: apple2pi

6800FLEX Basic と ASCIIART

Lambda-2022 マシンで FLEX 2.0 が動くようになりました。6800 / 6809 用 DOS である FLEX はマイコン DOS としては比較的初期の製品になりますが、アセンブラをはじめ各種言語が利用できて便利です。

「DOS」といっても物理フロッピーディスクならびにドライブをいまさら使う必要は感じられないので、シリアルサーバ Apple2pi のコードを改変して FLEX ディスクドライブをエミュレートしました。Apple ProDOS が 1 ブロック 512 バイトなのに対して FLEX は 1 セクタ 256 バイトが読み書きの最小単位であるため、Apple2pi のディスクエミュレーションコードの該当部分を変更します。FLEX のセクタ数とトラック数はディスク TOC に書かれたパラメータに応じて可変となるので、その処理も追加しました。6800 用クライアントコードは 6502 クライアントコード(a2pi.s)を参考にして新規に書きました。

FLEX ブートディスクイメージの作り方については Old68fun 様のページの情報が大変参考になりました。

FLEXシステムの作成方法(その3)・・・6800FLEXの場合

さて DOS が起動するとまず BASIC を使いたいところです。最初の手習いは当然、ASCIIART.BAS です。FLEX Basic で ASCIIART 画面をきれいに出力するには FLEX の TTYSET コマンドを使って 80 桁改行、ページャなしに設定する必要があります。

1 MHz 6802 / Flex 2.0 Basic:05:05.37

同じ 1 MHz 6800(6802)で動く Altair680 Basic と比較すると若干高速であることに気が付きます。

1 MHz 6802 / Altair68 Basic:07:35

同一プロセッサ、同一クロック数でこの処理速度の違いは?思って調べると、FLEX Basic はどうも浮動小数点の精度が低めなようです。試しに 10 進での有効桁数を確認しました。

1.00001 が表現できて 1.000001 が表現できないということは10進数有効桁数は7未満です。FLEX Basic マニュアルには「10 進 6 桁精度 (“six decimal digit accuracy”)」と記載されています [1]。

2^16 < 10^5 <  2^17  < 2^19 < 10^6 < 2^20

という比較が成り立つので、FLEX Basic 浮動小数点数の仮数部は 17-19 ビットの範囲のいずれかであるようです。例の「ケチビット」を勘定に入れると仮数部は 16-18 ビットで表現可能でしょう。また指数部については検証を怠りましたがマニュアルによると +/-38(10進)ですから 2 進換算で 7 ビット、これに加えて符号 1 ビットが必要となります。

FLEX Basic はおそらく 24 ビット精度の浮動小数点を使用していると推測されます。Aitair680 Basic その他初期 MS Basic は多くの場合 40 ビットまたは 32 ビット浮動小数点です。24 ビット浮動小数点の FLEX Basic で ASCIIART を実行した場合、計算量が少ない分速くなると言えそうです。

[補注]よく数えたら仮数部が 16-18 ビット、指数部(指数符号を含むオフセット表現)が 8 ビット、符号が 1 ビットとなり全体では最低でも 25 ビット必要になります。8 ビットマシンにしては半端なビット幅なので何か計算違いしている可能性があります。

参照

  • 1. TSC Basic Users Manual, 1979, Technical System Consultants, Inc.

8 ビットコンピュータ用シリアルサーバの構成

「シリアルサーバ」というのは今考えた言葉で、世間で通用する用語ではありません。どういう意味かというと、8 ビットレトロコンピュータに装備されている最低限の通信機能を利用して最近のハイエンドマイコンボード(Raspberry Pi、ESP32 等)ないし PC へ接続し、端末接続のみならず様々なサービス、例えばディスクエミュレーションやネットワーク機能などをレトロコンピュータに提供しようというアイディアです(注 1)。

レトロコンピュータ界隈ではこのアイディアに基づいて次のデバイスが発表されています。

レトロパソコンは数多く存在するのでこれ以外にも同様のデバイスがあるかもしれませんが、私が多少なりとも調べてみたことがあるのは上記二つだけです(注 2)。

ポイントは、レトロコンピュータ側は処理能力の限界上あまり複雑なプロトコルを喋れないということです。AppleTalk などの汎用ネットワークプロトコルは 16/32 ビットプロセッサの時代になってようやくサポートされました(注 3)。

そういう理由で、シリアルサーバでは接続相手のレトロコンピュータがなるべく少ないオーバーヘッドでサービス呼び出し処理を行えるように配慮されています。レトロコンピュータに搭載されている既存のモニタプログラムにはそのような機能がないので、専用のクライアントソフトウェア(ドライバ)を新たに書く必要があります。なお文字端末機能をシリアルサーバに載せる場合は基本的に 1 文字送受信を 1 パケットに対応させるため、元の回線速度の数分の 1 程度に遅くなることは避けられません。

***

と、ここまでさも全容を知っているかのように説明してきましたが、実のところ各システムの細部について十分理解できていません。そこで本稿では Apple2Pi のソースを読み、どのような構成になっているのかを調べていこうと思います。要は学習ノートです。あわよくば自分でも同様のサーバやクライアントプログラムを書こうという考えです。

Apple2Pi ソースコード

Apple2Pi ソースコードは Github で公開されています。docs ディレクトリに格納されているドキュメントを参照したところ、システム構成について簡単な説明がみつかりました。さしあたって次のソースコードが参考になりそうです。

  • src/a2pid.c: Raspberry Pi 上で実行されるデーモン(サーバプログラム)
  • src/a2lib.c、src/a2pidcmd.c、src/a2term.c: Linux 側から a2pid を利用するためのライブラリとクライアントプログラム群。
  • client/BUILD/a2pi.s: Apple II(ProDOS)側から a2pid を利用するための、シリアル回線経由のクライアントプログラム。マウス・ジョイスティックイベントをサポートしているため単純な文字入出力処理に比べると結構複雑になる。

注意することは、Apple2pi では Apple II の入出力ストリームを Linux のデバイスに接続するだけではなく、Apple II のキーボードとジョイスティックないしマウスを Linux の入力デバイスとして利用するという、いわば双方向のサービスが提供されている点です。今回想定している目的では後者の機能に深入りする必要がないので、興味のない部分はなるべく読み飛ばすようにします。

src/a2pid.c

シリアルサーバのサービスを提供するコードです。とりあえず main() から読んで概要を把握します。と思ったらファイルの半分超、約 550 行が main() 内のコードでした。くじけず読んでいくと次のような流れがわかります。

  • ポート 6551 で TCP ソケットをオープン(#653-)
  • Apple II に接続されたシリアル回線もソケット管理に含める(#676)
  • Apple II とサーバのコンソール入出力、ブロックデータ転送、仮想ディスクファイルの読み書き、IIe/IIc のマウスイベントなどの情報を通信する(#682-)

いったんソケットがオープンされると a2pid は TCP ソケットのクライアントおよびシリアル回線を監視し(#691 select())、パケットを受信するとコマンドを解釈して適切な応答を返すか関連するクライアントに転送します。a2pid プログラムの実行中はこのパケット処理ループから脱出しません。

なおソケット通信でやり取りされるデータはパケットと称されますが、パケット管理のためのフィールドは必要最低限に抑えられています。Apple II シリアル回線上での通信の場合パケット長は最小 3 バイトで、1 バイト目は必ずコマンド値です。主要なコマンドについて、将来流用できそうなものを中心として以下のように読んでいきます。

a. afd(Apple II シリアル回線)でやり取りされるコマンド

  • 0x80 sync:Apple II からの同期チェック。Apple II への応答(ack)は 0x81。
  • 0x82 a2d keybd:Apple II から送られるキーボードイベント。2 バイト目がモディファイアコード、3 バイト目がキーコード。
  • (マウス関連は割愛)
  • 0x90 ack read bytes, 0x92 ack write bytes: Apple II と a2pid 間のバイナリデータ転送
  • 0x96 send input char:Apple II へキーボード入力文字を送信する。Apple II からの ack コマンドは 0x97
  • 0x98 get output char:Apple II から文字出力を受信して印字担当クライアントへ転送する。
  • 0xa0、0xa2 drive 1/2 status call:ドライブ 1、ドライブ 2 のステータスを Apple II へ送信する。
  • 0xa4、0xa6 dirve 1/2 read call:ドライブ 1、ドライブ 2 の指定ブロックを読み出して Apple II へブロック転送(512 バイト)する。その後結果コード 1 バイトを Apple II へ送信する。
  • 0xa8、0xaa drive 1/2 write call:Apple II からのブロック転送(512 バイト)をドライブ 1、ドライブ 2 の指定ブロック位置へ書き込む。その後結果コード 1 バイトを Apple II へ送り返す。

b. srvfd(Linux TCP ソケット)でやり取りされるコマンド

Linux 上のクライアントプログラムと通信します。TCP ソケットなので a2pid が走っているマシンとは別のマシンでもクライアントを実行できます。

  • 0x90 read bytes:Apple II のメモリ内容を読み出す。addreq() を用いて転送リクエストをキューに追加する。
  • 0x92 write bytes:Apple II へデータを書き込む。addreq() を用いて転送リクエストをキューに追加する。
  • 0x94, 0x9a call:指定されたアドレスの Apple II ルーチンを実行する
  • 0x96 send input char:Apple II のキーボード入力へ 1 文字送る
  • 0x98 get output char:Apple II の 1 文字印字を取得する
  • 0xc0 reconnect vdrvs:仮想ドライブを再起動する(仮想ドライブファイルのクローズと再オープン)
  • 0xc2 disconnect vdrvs:仮想ドライブを閉じる
  • 0xff close:クライアントをクローズする

***

a2pid.c には仮想ドライブファイル読み書きのためのインターフェイス(vdrvopen(), vdrvclose(), vdrvread(), vdrvwrite() 等)が用意されています。ここは少し詳しく関数単位で見ていきます。

int vdrvopen(char *path)

Apple2Pi の仮想ドライブ 1/2 をオープンします。仮想ドライブのディスクイメージファイル名は “A2VD1.PO” および “A2VD2.PO” で固定ですが、これらのファイルがあるディレクトリパスは引数 path で指定できます。双方のファイルのオープンに失敗すると 0 を返し、それ以外の場合は 0 以外の値を返します。

void vdrvclose(void)

Apple2Pi の仮想ドライブ 1/2 をクローズします。

int vdrvtime(int afd)

ProDOS 形式の日付時刻データ(4 バイト)をファイルディスクリプタ afd に書き込みます。現在使用されていません。

int vdrvstat(int afd, int drive)

ドライブ drive に割り当てられているディスクイメージファイルのブロック数(=ファイルサイズ/512、2 バイト値)をファイルディスクリプタ afd へ書き出します。

int vdrvread(int afd, int drive, int block)

仮想ドライブ drive に対応するディスクイメージファイルのブロック番号 block の位置から 512 バイト(ProDOS 1 ブロック分のデータ)を読み出して、ファイルディスクリプタ afd へ書き出します。成功すると 0、失敗すると 0x27 を返します。

int vdrvwrite(int afd, int drive, int block)

ファイルディスクリプタ afd から 512 バイト(ProDOS 1 ブロック分のデータ)を読み取り、仮想ドライブ drive のブロック番号 block へ書き込みます。 完了後 fsync() で Linux ファイルシステムへの書き込みを確定します。成功すると 0 を返します。ファイルシステムへの書き込みに失敗すると 0x27 を、それ以外の失敗では 0x28 を返します。

***

Apple2Pi には vdrv* 系の読み書きインターフェイスとは別に Linux ファイルシステムから ProDOS ボリュームを操作するための fusea2pi.c も含まれますが、この部分は今回は読みません。

a2pid.c 以外のソースは簡単に眺めるだけに止めます。

src/a2lib.c

Linux クライアントから a2pid への接続と通信を扱うライブラリです。ソケットのオープン/クローズ、データブロック送受信、Apple II 内ルーチン(ProDOS システムコール等)の呼び出しが行なえます。

src/a2term.c

Linux コンソールを Apple II の入出力に接続するためのプログラムです。以下のコマンドを送受信します。

  • 0x96 を送信: Apple II へのキーボード入力送信。
  • 0x98 を受信:Apple II からの印字リクエスト。MSBをマスクする、改行文字を変更するなどの変換を行ってから Linux 端末上に表示する。
  • 0x9E を受信:キーボード入力を Apple II が受信したことの確認応答。a2term から送信した文字と一致しなかった場合はエラーとみなし、再同期はせずそのまま a2term 終了。

src/a2setvd, src/a2pidcmd.c

a2pidcmd は a2pid 経由で Apple II シリアル回線へ任意のコマンドパケットを送信し、応答として返される ack 値を受信します。なお Apple II が受け取った 1 バイト(かならず偶数値)のコマンドに対して返す ack 値は、コマンド値+1(奇数値)となります。

a2setvd は仮想ドライブにディスクイメージファイルを割り当てるスクリプトです。a2picmd を経由して a2pid へコマンド 0xc0(マウント)、0xc2(アンマウント)を送ります。アンマウント→イメージファイルを再リンク→マウントの順に実行することで指定のファイルを割り当てます。

利用する機能

以上、Apple2Pi のコードをかなり恣意的に拾い読みしてきました。ピックアップした内容を押さえることで次の処理が可能になります。

  1. シリアル回線を経由した文字の送受信
  2. シリアル回線を経由したディスクイメージファイルへのブロック単位の読み書き

つまり単一のシリアル回線を利用してコンソールとディスクエミュレータの 2 つの機能を実行できます。今後この機能を実装していきます。

注:

  1. 1980 年代の ASCII 誌で「Planet」という汎用パソコン間ネットワークのシステムが紹介されていました。京大 KMC のプロジェクトだったと思います。シリアル回線経由かどうかはわかりません。
  2. 2023 年 6 月時点で Raspberry Pi Pico ベースの 8 ビットサーバといえるシステムが複数発表されています。Raspberry Pi Pico には高速 PIO 機能があり、レトロマシンとの接続は必ずしもシリアルに限定する必要はありません。PIO は 1〜4 MHz クロックの 8 ビットシステムバスに接続するのにぴったりだな、と考えていたら RP2040 データシートそのものに用途例として “8080 and 6800 parallel bus” と書いてありました。
  3. 実際のところ Apple IIe 用の AppleTalk アダプタが発売されていたようです(参考)。また 1990 年代に入ってから Ethernet カードなども発売されていました。