z88dkによるクロスビルド
z88dk はZ80マシン向けのコンパイラスイートです。アセンブラ、C言語、による開発ができます。
環境構築
こちらにならってNightly Buildを落として展開し、 パスを通し、ZCCCFG 環境変数を設定します。OSXでは下記のように実行すればOkです。
cd ${HOME}
wget http://nightly.z88dk.org/z88dk-osx-latest.zip
tar -xzf z88dk-latest.tgz
export PATH=${PATH}:${HOME}/z88dk/bin
export ZCCCFG=${HOME}/z88dk/lib/config
Windowsでも同様に、 z88dk-win32-latest.zipをダウンロードして展開し、PATH および ZCCCFG 環境変数を設定すればOkです。
下記の例はすべてgithubにあげてあります。こちらを参照ください。一括ですべてをビルドできるMakefileも添付してあります。
C言語開発
下記内容のhello.cを作成します。
#include <stdio.h>
#include <conio.h>
void main()
{
    clrscr(); // clear screen
    printf("Hello World !\n");
}
下記コマンドでビルドすると、hello.ihexができます。
zcc +g800 -create-app -clib=g850b -ohello.ihex hello.c
| オプション | 説明 | 
|---|---|
| +g800 | g800シリーズをターゲットに | 
| -create-app | ビルドしたバイナリをターゲットに応じたアプリケーション形式に変更する | 
| -clib=g850b | G850のCライブラリをリンクする。g850とg850bがあり、前者は一画面8ラインで後者は一画面6ライン | 
g800 hello.ihx
で実行すると、下記のようになります。

対応ライブラリはプラットフォーム依存なので、g800プラットフォーム向けに全てが対応しているわけではないですが、 こちらのヘッダファイルにあるようなライブラリが 使用できます。
C言語開発(グラフィック)
z88dkのg800用グラフィックライブラリは私が作成しました。z88dk開発チームから丁寧な指導をいただいて、プルリクを採用してもらいました。感謝です。ライブラリの詳細はmonograhics libraryに記載があります。
仮想VRAMは使っておらず直接LCD内のVRAMに書きこんでいます。LCDのリフレッシュレートに同期する方法が不明なため、高速に描画をするとチラツキが出ます。
sin関数を描画してみましょう。sin.cです。
#include <graphics.h>
#include <math.h>
#include <stdio.h>
void main() {
  int x, y;
  clg();
  plot(0,24);
  for (x = 0; x < 144; x++) {
    y = (int)(24 * (1.0 - sin(x / 144.0 * 3.14 * 8)));
    drawto(x, y);
  }
  while (getk() != 10) {};
}
最後のgetk()の下りですが、機械語モニタに戻って描画が上書きされてしまうことを防ぐため キー入力待ちの無限ループを入れています。改行キーのキーコードは、z88dkのg800ポートでは 10に定義されているようです。
zcc +g800 -lm -create-app -clib=g850b  -osin.ihx sin.c
g800 sin.ihx
で、ビルド&実行すると下記のようになります。

アセンブラ開発
下記内容のhello2.asmを作成します。
ORG 0x100
ld hl,hello_text    ; 文字
ld b,12             ; 文字数
ld d,3              ; yカーソル座標
ld e,0              ; xカーソル座標
call 0xbff1         ; 文字列表示
ret
hello_text:
defb "Hello, World"
下記コマンドでビルドすると、hello.ihexができます。
zcc +g800 --no-crt -create-app -Cz"--ihex --org=0x100" -o hello2.ihex hello2.asm
| オプション | 説明 | 
|---|---|
| +g800 | g800シリーズをターゲットに | 
| --no-crt | Cランタイムルーチンをリンクしない | 
| -create-app | ビルドしたバイナリをターゲットに応じたアプリケーション形式に変更する | 
| -Cz"--ihex --org=0x100" | 0x100から始まるIntelHexを作成 | 
g800 hello2.ihx
で実行すると、下記のようにテキストが表示されます。

C/アセンブラ混載開発
インラインアセンブラは下記のように記載します。
#asm
....
#endasm
Cの変数とアセンブラのレジスタのやりとりは関数経由で行います。
標準的な関数コール手順
z88dkのC関数コールの手順は、
- 引数を「前から」スタックに積んでいく (SDCCとは向きが違うので注意!)
- リターンアドレスをスタックに積む
- 関数のエントリポイントにジャンプ
という手順のようです。関数に入った直後のスタックは上から下記のようになります。
| アドレス | データ | 
|---|---|
| (SP) | リターンアドレス(L) | 
| (SP)+1 | リターンアドレス(H) | 
| (SP)+2 | 引数N | 
| (SP)+2+引数Nのサイズ | 引数N-1 | 
| ... | ... | 
| ... | 引数1 | 
なので、関数内で引数を取り出す際には、SP+2を起点として取り出していけばよいです。関数から抜けるときHLに入っている値が戻り値となります。
下記のようなinline1.cが一例です。
#include <stdio.h>
#include <conio.h>
int add(int x,int y){
#asm
    ld ix,2
    add ix, sp
    ld h, (ix+3)  ;x_H
    ld l, (ix+2)  ;x_L
    ld b, (ix+1)  ;y_H
    ld c, (ix)    ;y_L
    add hl,bc
    ret
#endasm
}
int main() {
  clrscr(); // clear screen
  printf("%d\n", add(300,500));
}
最初にSP+2をIXレジスタにコピーしています。その後、順番に1バイトずつ取り出しています。 charが1バイト、intが2バイト、であることに注意しましょう。下記でビルドできます。
zcc +g800 -lm -create-app -clib=g850b -oinline1.ihx inline1.c
g800 inline1.ihx

fastcall関数コール
引数が1個の場合はよりオーバヘッドの少ない関数呼び出しが可能です。
下記inline2.cのように__z88dk_fastcallという修飾子をつけて関数定義すると、 ポインタpがHLレジスタに渡されます。HLレジスタの内容をintの戻り値として返すこともできますが、 この例ではポインタ先の値を直接書き換えています。(いわゆる参照渡し)
__nakedは関数のコンパイル結果を、純粋にインラインアセンブラで書いた内容だけに限定したい場合に指定します。
#include <stdio.h>
#include <conio.h>
int inc(unsigned char *p) __z88dk_fastcall __naked {
#asm
    ld a,(hl)
    inc a
    ld (hl),a
    ret
#endasm
}
int main() {
  unsigned char a=10;
  clrscr(); // clear screen
  inc(&a);
  printf("%d\n", a);
}
解説すると下記のようになります。
- 変数aのポインタ&aを関数inc()に渡す
- 関数inc()の引数p(つまり&a)がHLレジスタに渡される
- HLレジスタの指すアドレスに格納されているunsigned charの値(変数aの値)をAレジスタに取り出す
- Aレジスタをインクリメントする
- レジスタAの値をHLレジスタの指すアドレスに書き戻す(変数aの値を更新)

a=10に1が足されて11が表示されています。
その他の関数呼び出し規約
関数の呼び出し規約はこちらやこちらで詳しく説明されています。
低レベル関数
stdlib.hに基本的な低レベル関数がいくつか定義されています。便利!
| ジャンル | 命令 | コメント | 
|---|---|---|
| I/O制御 | inp(address) | 入力 | 
| I/O制御 | outp(address,data) | 出力 | 
| メモリ読み書き | bpoke(address,byte) | バイト書き込み | 
| メモリ読み書き | wpoke(address,word) | ワード書き込み | 
| メモリ読み書き | bpeek(address) | バイト読み出し | 
| メモリ読み書き | wpeek(address) | ワード読み出し | 
| ウェイト | t_delay(tstates) | ステート単位のウェイト(141以上) | 
| ウェイト | sleep(seconds) | 秒単位のウェイト | 
| ウェイト | csleep(centiseconds) | 10ミリ秒単位のウェイト | 
| ウェイト | msleep(miliseconds) | ミリ秒単位のウェイト |