X68000 Human68Kを使わずにプログラムを実行する
はじめに
X68000のゲームでモトスや超人をフロッピーディスクから起動したことがある人は、他のゲームとは違う起動の仕方に驚いたことがあるかと思います。
X68000用の多くのソフトは、フロッピーディスクから起動すると、Human68K、色々なデバイスドライバ、シェルであるCommand.x、バッチファイル等々が順に読み込まれ、システムが起動していく様子を見ることができるのですが、モトスや超人は、そういった起動画面を表示することなく、いきなり、それも高速にゲームが始まるように作られています。
X68000で何かをする時、起動デバイスがFDDでもHDDであっても、まずは何かしらのOSを起動するというのが一般的ですが、独特な周辺回路を持つX68000も、68000を搭載したコンピュータであることには変わりませんし、そもそもHuman68KといったOSもプログラムなのですから、OSを使わずにプログラムを動かすことが出来るはずです。
フロッピーディスクからHuman68Kを使わずにいきなりゲームプログラムが起動したら、読込み時間の短縮になりますし、メモリの節約にもなるし[1]、なによりいきなりゲームや起動してタイトルロゴがバーンと表示されたらかっこいいですよね[2]。
Human68Kの呪縛から解放されたい・・・!
そこで、Human68KといったOSを使わずに、純粋な68000のプログラムをX68000で動かすにはどうしたらよいかを調べてみました。
ざっくりとしたメモリマップ
図はInsideX68000から引用させていただきました。
いきなりメモリマップの説明から始まるのですが、要するに、Human68Kを使わずに自作のプログラムを動かすというのは、電源投入後にメモリ上で68000のマシンコードを実行できればよいという話です。
メモリ後半はI/O領域だったりROM領域だったりするので使えません。VRAMはプログラム実行領域としても使うことが出来ます。また、メモリマップの先頭部分は、68000の割込みベクタとして使われるので$000000-$0003FCあたりは使えないと思った方がよさそうです。
そもそも68000は起動時に$000000からSSP、$000004からPCを読み取って・・・という、一般的な68000の動きの説明は省略します。このあたりはX68000に限った話ではありませんし、X68000の場合、この後に説明する理由から知らなくても困りません。
IPL-ROM
X68000が起動すると、IPL-ROMに書かれているプログラムが実行されますが、その時、IPL-ROMのプログラムはX68000の様々な周辺回路を初期化してくれます。MFCやらDMAやらFDCやらを初期化するだけでも、デバイスの分厚いマニュアルを読み解かないといけないわけですからありがたい話です。ただ、X68000はIPL-ROMから起動するという呪縛から逃れることが出来ないといことでもあります。[3]
また、IPL-ROMにはIOCSが入っています。Human68Kを使わないプログラムであっても、IOCSが利用できるという事です。ただし、IPL-ROMのIOCSは、HIOCS.Xで置き換える前のオリジナルのIOCSなので速度が遅いといった課題はありますが、厄介な周辺デバイスの制御にIOCSを利用できるというのはありがたい話です。
IPL-ROMにはもう一つ重要な役割があります。それは、ユーザのプログラムを起動デバイスから読み取り、RAM領域に書き込み、SSPを設定し、実行する仕組みです。
具体的には、フロッピーディスクの論理セクタ0番[4]から1024Byteをメモリの$002000にロードして、$002000から実行開始するという仕組み(ルール)になっています。また、ここで読み込まれるプログラムの最初の命令はBRA.BかBRA.Wでなければならないようです。
フロッピーディスクではなく、SASI/SCSIデバイスから起動する場合は少し違うのですが、ここでは省略します。あと、この手の実験をする時はHDDを外しておいた方がよいと思います。
実践
仕組みが分かったのでプログラムを書きます。
.include \include\iocscall.mac
.cpu 68000
.text
boot:
bra.w entry
dc.b "FDD IPL"
.even
entry:
lea (stack_end,pc),a7
move.w #$0,d1
move.w #$0,d2
IOCS _B_LOCATE
IOCS _OS_CURON
lea.l (TXT_MSG_PUSHKEY,pc),a1
IOCS _B_PRINT
; KEY WAIT
IOCS _B_KEYINP
lea.l (TXT_MSG_LOADING,pc),a1
IOCS _B_PRINT
loop:
bra loop
.data
.even
TXT_MSG_PUSHKEY:
.dc.b "original boot loader",$0d,$0a
.dc.b "PUSH ANY KEY",$0d,$0a,$00
.even
TXT_MSG_LOADING:
.dc.b "DONE",$0d,$0a,$00
.bss
.even
stack:
ds.b 256
stack_end:
.end
最初のbra命令後のテキストは無くても構いません。プログラムの大半はIOCS呼び出しで文字を表示しているだけです。
これをアセンブラを使ってバイナリを出力してフロッピーディスクに書き込めば完成です。
出来ました。エミュレータで試したので、実機でも動くかどうかは不明です。
アセンブルしてフロッピーに書き込むまで
先ほどのアセンブリコードをバイナリにしてフロッピーに書き込む方法なのですが、色々と面倒でした・・・。手軽そうなツールが見つからなかったのであれこれ組み合わせています。
手順
- has.xで、オブジェクト出力
- hlk.xで、.x形式の実行ファイルを作る
- hcv.xで.z形式の実行ファイルに変換する
- 実行ファイルのヘッダを削る
- ディスクに書き込む
1と2でX形式の実行ファイルが出来上がるところまでは皆さんご存知の通りです。X形式はHuman68K上で実行されるファイルなので、hcv.xを使って、絶対アドレスで実行されるZ形式に変換します。(完全リロケータブルなバイナリであれば任意の絶対アドレスに配置して動かせるのかもしれませんが試していません)
Z形式の実行ファイル
Z形式の実行ファイルは、指定された絶対アドレスで動くプログラムです。XやR形式は実行アドレスに依存しないのですが、今回は$002000から実行されるプログラムなので、X形式をZ形式に変換しておくと楽そうです。
X形式をZ形式に変換するツールがcv.xで、C Compiler PRO-68Kに同梱されているのですが、なんでもバグがあるらしく、フリーウェアのhcv.xを使った方がよさそうです。
他にも、X68k Programming Series #1 X680x0 Develop.に付属しているcvm.x(ソースコード付き)でもZ形式に変換できるようです。
hcv -z2000 FD_ipl.x
これでZ形式の実行ファイルが出来上がります。
ただ、Z形式は、ファイルの先頭に実行情報のヘッダが付いています。
offset | size | 内容 |
---|---|---|
$00 | 1.w | 識別子$601a |
$02 | 1.l | Text Section size |
$06 | 1.l | DATA Section size |
$0a | 1.l | BS Section size(.comm .stackを含む) |
$0e | 8.b | 予約 |
$16 | 1.l | 実行開始アドレス |
$1a | 1.w | 識別子$FFFF |
これは不要になるので、なんらかの方法で、Zファイルの先頭から$1Cバイトを削ればOKです。雑にC言語でプログラムを書きました。
#include <stdio.h>
#include <stdlib.h>
#include <sys/dos.h>
#include <sys/param.h>
#include <ctype.h>
int main(int argc, char *argv[])
{
FILE *fp;
int siz;
char *adr;
if (argc != 3) {
printf("cutheader.x INfile OUTfile\n");
return 0;
}
fp = fopen(argv[1], "rb");
if (fp == NULL) {
perror(argv[1]);
exit(1);
}
fseek(fp, 0, SEEK_END);
siz = ftell(fp);
siz = siz - 0x1c;
adr = malloc(siz);
if (adr == NULL) {
perror("malloc");
exit(1);
}
fseek(fp, 0x1c, SEEK_SET);
fread(adr, 1, siz, fp);
fclose(fp);
fp = fopen(argv[2], "wb");
if (NULL == fp) {
perror(argv[2]);
exit(1);
}
fwrite(adr, 1, siz, fp);
fclose(fp);
free(adr);
return 0;
}
探せばX68000で動くバイナリ編集ツール(ddみたいなの)があるのかもです。
もっとスマートなやり方で68000のコードだけをhlkやhcvで出力できればよさそうなのですが、やり方がわかりませんでした。(hcvのドキュメントをみるとROM化対応しているようなことが書かれているのですが...)
追記:最初の識別子$601aはbra命令っぽいので、ヘッダを削らずにディスクに書き込んでもよいのかも?
ディスクに書き込む
作成した68000プログラムをディスクに書き込む方法ですが、okiさんのrawriteを使いました。Vectorさんは令和の時代になってもソフト配信を続けてくれていてありがたいですね。
rawriteにはソースコードも付属していたので、私はちょっと改変して使っています。
一応の注意ですが、rawriteで指定するドライブを間違えてHDDを指定してしまうと、上記のプログラムが正しく起動するだけのHDDになってしまいますので気をつけてください。
ここまでのまとめ
ここまでの一連の流れでわかるように、実はX68000上で開発する必要がなく、Linux/Windows/Mac上で開発が可能だと思います。
- 68000バイナリ(絶対アドレス指定)を出力するアセンブラやC言語を使う
- XDFディスクイメージファイルを作成して書き込む
m68Kコードを書きだせるCコンパイラならどこかにありそうですね(GNU CやGASで出来るのでは説)。C言語を使う場合、ライブラリをどうするのか問題はありそうです。
あと、XDFのファイルフォーマットが謎ですが、ex68に付属しているX6_UTIL.LZHのREADFD.Cをみると、単にセクタデータを並べただけっぽいですね。
応用編
せっかくなので、IPL-ROMから起動される1024バイト以内の起動プログラムから、別のプログラムをフロッピーディスクから読み取って実行するようにしてみます。
ここでやっていることはIOCSのB_READを使って、フロッピーディスクの表面のTrack1, Sector1から8Sector分を$003000に読み取ってから、jmp命令で実行開始しています。読み込せるプログラムですが、何かないか探してみたところ、68000で動作するTINY BASICを作られていた方がいらっしゃったのでコードを流用させてもらいました。
- モニタやキーボードの入出力部分をIOCSに書き換えています
- アセンブラの文法違い
- ORG疑似命令を使うとHAS?HLK?が書き出すバイナリがおかしくなるので消しています
- シリアル入出力部分は変えていないので、セーブもロードも出来ません(え)
- BASICの終了処理を書き換えていません(そもそも戻り先のOSがいないですし)
こうしてHuman68Kを使わずにプログラムを動かしてみると、X68000がパーソナルワークステーションではなくマイコンマシンに思えてくるので、私がX68000っぽさを感じるポイントというのはHuman68KからのCommand.XやVS.XやSX-WindowといったShellの起動なのかなと思ったりしました。
更なる発展?
ここでは単純な単体のプログラムを動かしてみましたが、もう少し規模の大きなプログラムを動かすとなると、データ部分を別のファイルとして分割したくなります。その場合、そのファイルをどのように管理するのかという話になるので、なんらかのファイルシステムが欲しくなってきます。他にも、例えば音を出すのであれば、サウンド周りは別のプログラムとした方が開発も利用もしやすいので、着脱可能なデバイスドライバとして実装した方がよさそうですから、結局のところ、Human68Kってありがたいよねというオチになるのでした。
ここまできたら、あとはOS-9を移植したり(もうある)、CP/M-68Kを移植したり(もうある)、MINIXを移植したり(もうある)、MacOSを移植したり(もうある)、Palm OSを移植したり、オレオレOSを自作するなんてことも出来ると思いますので、チャレンジしてみるといいのかもしれません。
参考:OSの墓場 68000編
Discussion