Small Demonstration Part IX


img
今回は少し趣旨を変えて開発環境についてです。
最近のクロス開発環境はソースコードが編集しやすくコンパイルも高速、エミュレータがあれば
デバッグも楽、と至れり尽くせりですが、言語環境をいろいろ変えられるのも利点の一つだと思います。
アセンブラだけでなく、探してみると Z80で C の環境というのも結構あるようです。

とりあえずお題として Demo V でやったレイトレーシングを、C で書き直して Mode5 の
15色環境に対応させてみることにしました。
少し敷居が高いですが、それに見合うメリットに期待しつつ試行錯誤してみます。




■ イントロダクション

そもそも C に期待できること、と初心者の筆者が言うのもおこがましいのですが
当面、以下のような点を挙げたいと思います。

・計算で楽が出来る

 sqrt(A * A + B * B + C * C) で A,B,C が浮動小数点数などという式が
 あったりすると、アセンブラで書くのは結構難儀です。
 DemoVで実際に自分で書いたソースを見ても訳が分かりませんでした。

・変数のアクセスが楽

 アセンブラだとレジスタのやりくり等で四苦八苦するのですが、そういったことから解放されます。
 メモリアクセス全般で細かいことを気にしなくて良くなるので配列の要素へのアクセスや
 関数ポインタを使った関数コール、引数の渡し方などがスマートに行えます。

で、反面気にしないといけないのが以下。

・メモリの管理が厳しい

 上で細かいことを気にしなくて良くなると書きましたが、気にしていないとあっという間に
 メモリが足りなくなります。変数を char から int にするだけで、それをアクセスする
 コードまで複雑化するので思った以上に膨らみます。

 また、どこにどのコードが配置されるのか、フリーエリアがどのくらいでスタックが
 どれくらい空いているか(どれくらい使いそうか)アセンブラほど直感的でないので
 出来上がったバイナリを見ても中々不安なものがあります。

なんというか (A)ssembler と (C)の中間を取って (B)ASIC でいいんじゃないかという
気もしてくるのですが、確かに複雑なことをしなければ BASICでも全然かまわないと思います。
が、BASIC だとターゲットマシンから離れられないというジレンマが。

C だと他の機種とのやりとりも比較的楽ですし、何より P6+C という環境が
筆者にとって未知なる理想郷に思えたのでした。以下へつづく。



■ SDCC を使ってみる

SDCC は Small Device C Compiler という名前が示すとおり、元々ワンチップマイコン等の小規模
デバイス用 C 環境が Z80 などのプロセッサまでサポートするようになったものらしいです。

http://sdcc.sourceforge.net/

左にある「Download」メニュー又は「Downloading SDCC」の項、「Sourceforge Download Page」に行き、
Windowsユーザーなら sdcc-win32 をダウンロードするとよいでしょう。現時点での最新は 2.8.0です。

これはインストーラーパッケージなので、ちょっと試したいだけの人は二の足を踏んでしまうかもしれません。
そういう人はインストーラーパッケージとは別に、zip圧縮された snapshot ビルドというものもありますので、
そちらをダウンロードするとよいと思います。snapshot ビルドは日々修正を取り込んで更新されています。
左の「Snapshots」のメニュー中の「Supported Windows Builds」から最新の日付の zip パッケージを入手。

インストーラーは環境変数の設定までやってくれていたような気がしますが(うろ覚え)、zip パッケージの方は
手動で設定しないといけません。これについては後述します。
とりあえず任意の場所に解凍してみます。

ライブラリなどはソースまで含んでいるので眺めてみるのも面白いかもしれません。
ドキュメント類もあります。沢山ありすぎて混乱しますが sdccman.pdf を押さえておけば良いかと。
snapshotビルド版の方は、Z80 に関係ない他のデバイスのコードやドキュメントまで作成されてしまいます。
いらないところはざっくり削除してかまわないと思います。

そうそう、MAKE を使ってビルドしたい方は、どこぞから入手して SDCC\bin に放り込んでおくとよいでしょう。

・実際に何か書いてみる

さあ、満を持して Hello,world! を書こう、と思ったら printf 関数が無いのですね。
といっても当たり前の話で、P6 用に作られた便利なライブラリなど無いので、全て自前で実装しないといけません。
どうしたものかと彷徨っていたところ Contiki for PC-6001 の Markn 氏によるユーティリティ hex2cas の
サンプルプログラムとして簡易 printf と Hello World プログラムが付いているのを発見。
いきなりやることが無くなってしまいました。

気を取り直して「画面に文字を表示する」プログラムを Mode 5 で作ってみます。
void main()
{
    unsigned char *ptr;
    ptr=(unsigned char*)0x4400; 

    *ptr=0x61;
}
適当にテキストエディタで書いて test.c と名前を付けて保存します。

いざコンパイルと行きたいのですが、SDCC コンパイラの本体は SCCC\bin にあるので、
そこにパスを通し、ついでに環境変数も設定しておいたほうが良いでしょう。
今回は MAKE を使わず手動でコンパイルしてみることにします。

set path=d:\develop\sdcc\bin
set SDCC_HOME=d:\develop\sdcc
set SDCC_INCLUDE=d:\develop\sdcc\include
sdcc -mz80 --no-std-crt0 --code-loc 0x9000 test.c 
バッチファイルにしておくと楽ですね。
筆者の環境では D:\develop の下に SDCC をインストールしてあるため上記のようになります。

sdcc がコンパイラ本体で、test.c をコンパイルせよと指示しているわけですがオプションがいくつか付いています。
-mz80 と --code-loc 0x9000 は大体分かりますね。ターゲット CPU を Z80 と指定して機械語を生成するのと
生成されたコードをアドレス 0x9000 番地から配置するようにせよ、という意味です。
とりあえずこれで P6 の EXEC &H9000 でプログラムを起動できるようになるわけです。

ワンチップマイコン等が主なターゲットの SDCC ではプログラムコードは ROM の 0番地から開始することが多いので
任意の RAM にプログラムを配置できるプロセッサでは明示的に指定しないといけない・・・らしいです。

問題は --no-std-crt0 です。これは SDCC\lib\z80 にある crt0.o をリンクしない、という意味です。
この crt0.o はソースコード crt0.s が SDCC\lib\src\z80 にあります。
アセンブラの文法がちょっとアレなので分かりづらいですが、どうやら初期化っぽいことを行っていますね。

先ほど書いた test.c にしても、そもそも main() からプログラムがスタートする、というのは C のお約束みたいな
ものなのですが、そのお約束を実現するのが crt0.s なのです。他にもスタックポインタの初期化など、
機械語レベルで考えると当然しておかなければならない初期化をここで行います。
P6 の場合 IOポートを操作して ROM バンクを RAM に切り替えたりしておきたい所。

先ほど EXEC &H9000で実行できると書きましたが、それはたまたま code 領域(0x9000-)に main() が先頭にあるから、
という理由なだけで、func() が main() より前にあると、func() が先に実行されてしまいます。
そこで crt0.o をコードの先頭にリンクするようにしておけば、そこから main() にジャンプさせることが
可能になるというわけです。

今回は main() しかないので問題が無いわけです。
で、 --no-std-crt0 を付けておく、と。
山のようにあるオプション類については前述 sdccman.pdf に書いてあります。筆者は途中で読むのを挫折しました。

コマンドコンソールで上の一連の作業を実行すると、test.lst / test.sym / test.ihx などずらずら出力されます。
お目当てのファイルは test.ihx です。これ自体が機械語の最終出力なのですが、インテルHEX形式になっているので
バイナリ形式に直さないといけません。SDCC には MAKEBIN というプログラムが付いているっぽいのですが
いまひとつ使い方が分からなかったので、こちら(piroyan's Lablog)で配布されている hex2bin を使わせて
いただくことにしました。記事自体も大変有用で助かりました。

hex2bin test.ihx 
これだけでOKです。test.bin が出力されます。

実は前述の Markn 氏作のユーティリティ hex2cas も同様の変換ツールなのですが、こちらは最終的に
テープイメージに加工するものなので、用途に合わせてツールを選択すると良いと思います。

※ 2008.08.02追記
hex2cas はバージョンアップを重ねて Hexameter として公開されています。
こちらからダウンロードできます。 Hexamter (SourceForge)
SDCCでのコンパイルから変換までの流れも解説されています。
バイナリに変換する際にも役に立つと思います。

それでは実行してみます。
エミュレータ(PC-6001VW)のモニターモードを使用します。
モード 5 ページ 2 で BASIC を起動後、ALT+F6で下の画面へ


setbin[enter]でバイナリ読み込みダイアログが出るので、test.bin を 9000 番地へ読み込みます。




G[enter]でモニターモードから復帰。EXEC &H9000で実行できます。


画面の左上(4400番地)に、'a'(0x61)が表示されましたね。
え、これだけ?


--no-std-crt0 を付けてスタートアップルーチンを省略するとは言っても、main()しかないプログラムで
ずっとやりくりする訳にもいかないので、やはり自前で crt0.o を作っておくべきです。
	;; crt0.S
        .module crt0
       	.globl	_main

 	.area	_HEADER (ABS)
	.org 	0x9000
	jp	init

	;; Ordering of segments for the linker.
	.area	_HOME
	.area	_CODE
	.area   _GSINIT
	.area   _GSFINAL
 	.area	_DATA
 	.area	_DATAFINAL
	.area   _BSS
	.area   _HEAP

    .area   _CODE
init::
    call    gsinit
	jp	_main

        .area   _GSINIT
gsinit::
        .area   _GSFINAL
        ret
	.area	_DATA
	.area	_DATAFINAL
デフォルトの crt0.s を少し弄っただけですが、必要に合わせて初期化コードを追加すれば良いでしょう。
.org で0x9000を指定しています。このスタートアップルーチン自体が 0x9000 から始まるという意味なので
コンパイラに指示する --code-loc は少し(最低 jp init の分 3バイト)ずらさないといけません。
test.c で書いたコードと被ってしまうからです。

as-z80 -los crt0.o crt0.s
とりあえずこんな感じで crt0.s から crt0.o を作っておいて、あとは使い回しをすると良いと思います。

さきほどの test.c を少し拡張して試してみます。
char str[] = {0x9a,0xfd,0xe6,0xe1,0xea,0x20,0xcf,0xb2,0xba,0xdd,0x00};
void main()
{
    int i = 0;
    char *ptr;
    ptr=(char *)0x4400;

    while(str[i]!=0){
        *ptr++ = str[i++];
    }

    _asm
        di
        halt
    _endasm;

}
初期化つきグローバル変数とインラインアセンブラを入れてみました。
コンパイルするためのバッチファイルも、全部まとめて以下のようにしてみます。

set path=d:\develop\sdcc\bin
set SDCC_HOME=d:\develop\sdcc
set SDCC_INCLUDE=d:\develop\sdcc\include
as-z80 -los crt0.o crt0.s
sdcc -mz80 -c test.c
sdcc -mz80 --code-loc 0x9003 --data-loc 0 --no-std-crt0 crt0.o -o test.ihx test.o
link-z80 -f test.lnk
hex2bin test.ihx
少しオプションが増えました。コンパイル後にできる test.map や test.sym を見ると
コードやデータがどのように配置されたか確認できると思います。



強制的に実行を止めているので BASIC に戻ってきません。
インラインアセンブラは「;」セミコロンを忘れがちなので注意が必要です。

これでおおよその準備は整ったでしょうか。

・Ray Trace Demo for PC-6001Mk2



まずは小手調べ。
背景が青いのは真っ黒で何も表示されないと不安だからです。
球が見切れているのは、うまくレイトレースできているか1ライン目で判別するため。
速度を稼ぐため 160x200 でレイトレースしているので誤差拡散も簡略化されています。

球オブジェクト2つと反射平面で合計3つです。右側は Windows上で再構成したもの(解像度同じ)。
反射まで実装してあります。再帰処理は4回行っていますが、遠目で見て分かるかどうかというところです。
平面上に赤と緑の球面が映り込む(球面にもそれ自身が映り込む)予定だったのですが、階調の乏しさがネックに。

描画にとてつもない時間がかかるので、何度も試すわけにいかないのが辛いです。
Windows 上でやると、精度が良すぎてあまりシミュレーションにならなかったり。

とりあえず、うまくいったところまでを置いておきます。
ソースコードとテープイメージ p6cray.zip
mode=5 page=2 で cload後、runでマシン語を読み込み実行します。
PC6001VWのテープイメージ変換機能とローダーを使用させていただきました。

※ コンパイルに関して
MAKE と hex2bin が必要です。compile.bat と makefile のパス設定は環境に合わせて書き換えてください。
コンパイル中に変数の Unreference Warning がずらっと出ますが、気にしなくて良いです。
出来上がるのは 0x4000 からはじまる bin ファイルなので読み込んで実行するためには、
事前にバンクを切り替えるなりローダーを作るなりしないといけません。

描画には Demo8 で用いた Floyd-Steinberg ディザを使っています。
実解像度は 160x200 ドットですが、レイトレースは 320x200 で行い、ディザも同じ解像度で誤差拡散を行います。
このために 322ドットx2ラインx3要素(RGB)x2バイト(int) の巨大なバッファが必要になり、これもメモリを圧迫します。
メモリは最初から最後まで苦労した部分で、下の方の未描画の VRAM をワークに使うことも真剣に考えました。

サイズが大きいので、事前計算に必要な行列関数群をごっそり省略したりしています(結果だけ即値で与えている)
オブジェクトの種類も、球と平面、ポリゴンと3種作ってみましたが、2種以上あるとかなりメモリを圧迫します。
そのため、シーン毎にインクルードするオブジェクト用関数を分ける必要がありました。全部は無理。



反射平面25枚のテスト。演算精度が低いので微妙にうすらぼんやりした市松模様ですね。
Arctan2 関数が問題なのかと思って、試しに余所から引っ張ってきたソースで Arcrtan2 を実装してみましたが
余計に酷くなってしまいました。
これをレンダリングするのに 10倍速でも 48時間以上かかるので、これ以上の実験は中止することにしました。
この市松模様に反射球を乗せて以下のような出力を得るつもりだったのですが…。
スタックがオーバーフローしていそうな気配がありありだったので、どのみち駄目だったかも。

右側は P6 っぽく15色ディザリングで出力した結果のシミュレーションです。

・反 省

一連の作業は CコンパイラにCソースを通す→アセンブリ言語に変換→アセンブラでアセンブリソースをコンパイル→
オブジェクト形式に変換→リンカがリンク情報を参照しながらオブジェクトやライブラリを結合→バイナリのできあがり
という感じです。Cコンパイラだけでなくアセンブラやリンカのオプションも知っておいた方が良いでしょう。

筆者は出力をバイナリファイルにしていますが、テープイメージに変換したい場合などは結構大変です。
PC-6001VW の bin2p6 コマンドをよく活用させてもらっているのですが、裏RAMに入れる場合など、それなりに
手間がかかります。スクリーンモード変更、割り込み禁止、ROM バンクを RAM に切り替えの各作業をローダー側で
行いました。

プログラム自体は1日も掛からずに出来たものの、SDCC の環境に合わせるのが非常に大変でした。
CRT0 の書き方や配置の仕方など、いまだによく分からないところもあり、方言なのか仕様なのか、どうなの?という
バグが出たりして、他の C 環境と平行してテストしながら SDCC では何日も動かずに悩むという有様。
Cの経験が浅いので良く飲み込めていないのが原因だと思いますが。

コードサイズが大きいのも悩みどころで、一時的に HI-TECH C 環境に移行しました(結局戻ってきましたが)。
そのときの記録も多少役に立つところもあるかと思うので残しておきます。


■ HI-TECH C を使ってみる

HI-TECH Z80 CP/M C Compiler は HI-TECH SOFTWARE 社が開発した CP/M 上で動くコンパイラです。
元は商用ソフトだったようですが、Freeware となったようです。
以下のファイルを揃えます。
  • debugman.txt デバッガのマニュアル
  • libsrc.exe ライブラリのソース
  • z80doc.exe コンパイラのマニュアル
  • z80read.me README
  • z80v309.exe コンパイラ一式
コンパイラ一式を適当なところに解凍しておきます。マニュアルは長大なテキストファイルですが、
コンパイラ・アセンブラ・リンカのマニュアルが一つにくっついているので編集して分割した方が無難かもしれません。

ライブラリのソースは拡張子 .HUF で、コンパイラと一緒に解凍された DEHUFF.COM で解凍すると思われるのですが、
どうにもうまくいきませんでした。それから、コンパイラ一式を解凍して出てくる実行形式のファイルは CP/M アプリなので、
Windows 上で直接実行したりしないようにします(一度やってしまった)。

これらはCP/M 上で動いて CP/M 用の実行ファイルを生成する為のアプリなので、まずは動作環境の方をどうにかしないといけません。

こちら(http://www1.interq.or.jp/~t-takeda/top.html)に CP/M Player という CP/M-80 エミュレータがあるのですが、
CP/M ほとんどそのままなので、コマンドの類を知らない私には敷居が高い感じでした。
単にコンパイルがしたいだけなので、あまり OS のことを意識せずに使えたらなあ、と。

で、こちらの CP/M program EXEcutor (http://hp.vector.co.jp/authors/VA000084/)を使ってみることにしました。
これは Windows の DOSプロンプトから cpm につづけて CP/M のコマンドやアプリケーションを実行できるすぐれもので
実行結果も DOS窓に表示されるので、バックログを見たりするのにも大変便利です。

・実際に使ってみる

CP/M アプリを動かす環境は整いました。次は C コンパイラで CP/M アプリケーション以外のバイナリを出力する方法です。
普通に test.c のコンパイルをすると test.com のような CP/M 実行形式が出来てしまうので、test.bin を作るには
特別にオプション指定を加える必要があります。

set path=d:\develop\c\hitechc;d:\develop\c\cpm
set cpmpath=d:\develop\c\hitechc
cpm c -V -C -Dz80 test.c -LF
cpm LINK -Z -C8100H -Ptext=8100H -Otest.bin crt0.O test.OBJ LIBF.LIB LIBC.LIB
いま、HI-TECH C コンパイラ本体(上記z80v309.exe)を D:\DEVELOP\C\HITECHC に、
CPM EXEcutor を D:\DEVELOP\C\CPM に解凍したものとします。

1行目はそのようにパスを通します。
2行目の set cpmpath は CPM EXEcutor にコンパイラの場所を教える環境変数です。
3行目と4行目は CP/M アプリケーションである Cコンパイラ(C.COM)とリンカ(LINK.COM)を実行します。
  普通のコマンドラインのように使えるのが素敵です。
3行目の -V は コンパイルメッセージを表示する、-C はオブジェクトファイルとして出力するオプションです。
-LF というのは浮動小数点演算ライブラリを参照するという意味で、不要なら外しておきます。
-Dz80 は #define z80 1 と同じ意味で、プリプロセッサに定義を渡します(デフォルトっぽいので不要かも)

オプションの類は z80doc.txt に書いてあるので、そちらを見た方が早いでしょう。

リンカ LINK の方のオプションもドキュメントに書いてありますが、-C -P オプションは SDCC で言う --code-loc などの
指定と同じく重要なので、必須のオプションです。この辺りの事は MSX での HI-TECH C を解説された以下のサイトが
大変参考になりました。

Tatsu's MSX情報局 (http://homepage3.nifty.com/Tatsu_syo/TMR/MSX.html)

で、リンカに crt0.O をファイル参照指定に加えている事からもわかると思いますが、こちらもスタートアップコードが要ります。
;	crt0 routine
;
psect	text
global	_main
_crt0:
	di
	ld	a,221
	out	(240),a
	out	(241),a
	ld	sp,65535  
	jp	_main

こんな感じで書きました。BASIC に戻ってくる気まったくありませんね。
10進数にしてあるのは16進数の書き方が分からなかったからです。
これを crt0.z80 というファイル名で保存、以下のようにアセンブラ ZAS.COM でコンパイルします。

cpm zas -ocrt0.o crt0.z80  
なんだか順番が前後していますね。crt0.o を作らないと上の test.c はコンパイルは通りません。

リンカに LIBF.LIB LIBC.LIB を指定していますが、それぞれ浮動小数点演算ライブラリとその他関数ライブラリです。
必要なければ外してOKです。リンカはオブジェクトを見つけきらないとエラーを返します。

・注意事項?

HI-TECH C は SDCC と違って Z80 で動くアプリケーションなので、それなりの『配慮』が必要です。
あまり無茶をさせるといけないということで・・・。
上記「Tatsu's MSX情報局」さんでも書かれていますので一読をお勧めします。

・Out of Memory エラーが結構出る。
 IX,IY などをポインタに使っているからだと思いますが、変数が多すぎると出ます。
 特に float で 4x4 行列などを作ってあれこれしていると息切れしがちです。

・浮動小数点フォーマットが独自形式っぽい
 まともに使えれば何でも良いのですが。

・コンパイラ(C.COM)のオプションで -O(最適化)をつけるとインラインアセンブラでエラーを吐く場合がある。
 具体的には out 命令なのですが、筆者が何か指定を間違えたのかもしれません。

他にも、配列を引数にすると warning が大量に出るとかいろいろあります。
出力されるバイナリは非常にコンパクトで実行速度も満足のいく出来でした。
SDCC で 30K だった時に HI-TECH C では 14KB で驚きました。速度も2倍以上出ていました。


最初のテスト

何故 SDCC に戻ったかというと、関数ポインタを使い始めたあたりでコンパイルエラーが
頻発し、DOS 窓ごと落ちるという妙な現象が出始めたからです。

OS,言語,環境 それぞれに慣れないと中々先に進まないので難しいですね。
速度とサイズが気に入っていたので残念なのですが、原因究明をあきらめて SDCCに戻ることにしました


・反 省

バイナリファイルを作っても、そこからテープイメージなりを作るのが一苦労だったりして
いっそのこと P6 に CP/M があればいいのにと思いました。
まあ CP/M とはいかないまでも簡単な DOS があると、とても便利そうな気がします。

C 自体の話では、他の機種との架け橋がまた一つ増えたかな、という感じでしょうか。
BASIC ではロジック自体の移植も結構むずかしかいように思いますが、C ならなんとかなりそうです。
描画部などをアセンブラで書いて、思考ルーチンが C というテーブルゲームとか良さそうですね。

レイトレースの大部分は下記の書籍をベースにしました。
・参考書籍(VisualBasic画像処理プログラミング:Softbank刊)


▲ TOP