最終更新日

Z88DK を使って MSX のゲームをつくるための環境構築メモ

はじめに

この文書は、Z80 を CPU に持つコンピュータ向けの C コンパイラ・アセンブラツールチェーンである Z88DK を使って MSX のゲームをつくるための環境構築メモです。

Z88DK

The development kit for over a hundred z80 family machines - c compiler, assembler, linker, libraries.

手順では Ubuntu 20.04 LTS もしくは Ubuntu 22.04 LTS を用いています。Z88DK の準備については Windows WSL2 でも同様の手順です。

Z88DK で実装したゲーム

筆者が Z88DK で実装した MSX ゲームです。GitHub からソースコードも見ることができます。

PONPON

🎮 WebMSX でこのゲームを遊んでみる

🔗 https://github.com/h1romas4/z88dk-msx-template

NOBORUNOCA

🎮 WebMSX でこのゲームを遊んでみる

🔗 https://github.com/h1romas4/noborunoca

Z88DK ツールチェイン

Z88DK のビルド

Z88DK の最新版を使うためソースコードからビルドします。(ここでは master ブランチの d0c5ff4 版で確認しています)

Installation

依存関係の導入(Z88DK の Github Actions build-on-ubuntu.yml を参考にしてください)

sudo apt install -y ragel re2c dos2unix texinfo texi2html gdb curl cpanminus ccache libboost-all-dev libmodern-perl-perl libyaml-perl liblocal-lib-perl libcapture-tiny-perl libpath-tiny-perl libtest-differences-perl libtext-table-perl libdata-hexdump-perl libregexp-common-perl libclone-perl

Perl の依存関係をローカルホーム(~/.perl5)に導入

cpanm App::Prove Capture::Tiny::Extended CPU::Z80::Assembler Data::HexDump File::Path List::Uniq Modern::Perl Object::Tiny::RW Test::Cmd Test::Cmd::Common Test::Harness Test::HexDifferences Text::Table YAML::Tiny

ビルド前に Perl のライブラリーパスを設定

eval $(perl -I ~/perl5/lib/perl5/ -Mlocal::lib)

ビルド(submodule を使っているので --recursive を必ずつける)

git clone --recursive https://github.com/z88dk/z88dk.git
cd z88dk
git checkout 9ffe204 # v2.2 を指定
./build.sh

ターゲットが MSX だけの場合は次のようにするとビルド時間が短縮できます。

make clean
./build.sh -p msx

ビルド確認(zcc がコンパイラフロントエンドコマンドになります)

$ ls -laF bin/
合計 13764
drwxrwxr-x  2 hiromasa hiromasa    4096  6月 23 17:20 ./
drwxrwxr-x 18 hiromasa hiromasa    4096  6月 23 17:19 ../
-rwxr-xr-x  1 hiromasa hiromasa 3447096  6月 23 17:20 z80asm*
-rwxr-xr-x  1 hiromasa hiromasa 1376688  6月 23 17:19 z88dk-appmake*
-rwxr-xr-x  1 hiromasa hiromasa   15657  6月 23 17:20 z88dk-asmpp*
-rwxr-xr-x  1 hiromasa hiromasa    6789  6月 23 17:20 z88dk-asmstyle*
-rwxr-xr-x  1 hiromasa hiromasa  260568  6月 23 17:20 z88dk-basck*
-rwxr-xr-x  1 hiromasa hiromasa  185104  6月 23 17:19 z88dk-copt*
-rwxr-xr-x  1 hiromasa hiromasa  444432  6月 23 17:20 z88dk-dis*
-rwxr-xr-x  1 hiromasa hiromasa   28128  6月 23 17:20 z88dk-dzx0*
-rwxr-xr-x  1 hiromasa hiromasa   17184  6月 23 17:20 z88dk-dzx7*
-rwxr-xr-x  1 hiromasa hiromasa   35016  6月 23 17:20 z88dk-font2pv1000*
-rwxr-xr-x  1 hiromasa hiromasa 1061816  6月 23 17:20 z88dk-gdb*
-rwxr-xr-x  1 hiromasa hiromasa   47264  6月 23 17:20 z88dk-lib*
-rwxr-xr-x  1 hiromasa hiromasa  834616  6月 23 17:19 z88dk-sccz80*
-rwxr-xr-x  1 hiromasa hiromasa 1132312  6月 23 17:20 z88dk-ticks*
-rwxr-xr-x  1 hiromasa hiromasa  311624  6月 23 17:19 z88dk-ucpp*
-rwxr-xr-x  1 hiromasa hiromasa 3447096  6月 23 17:20 z88dk-z80asm*
-rwxr-xr-x  1 hiromasa hiromasa  335840  6月 23 17:20 z88dk-z80nm*
-rwxr-xr-x  1 hiromasa hiromasa  101464  6月 23 17:20 z88dk-z80svg*
-rwxr-xr-x  1 hiromasa hiromasa  121312  6月 23 17:19 z88dk-zcpp*
-rwxr-xr-x  1 hiromasa hiromasa  383360  6月 23 17:20 z88dk-zobjcopy*
-rwxr-xr-x  1 hiromasa hiromasa   45344  6月 23 17:20 z88dk-zpragma*
-rwxr-xr-x  1 hiromasa hiromasa   48304  6月 23 17:20 z88dk-zx0*
-rwxr-xr-x  1 hiromasa hiromasa   21448  6月 23 17:20 z88dk-zx7*
-rwxr-xr-x  1 hiromasa hiromasa  329480  6月 23 17:20 zcc*

Z88DK の設定

ZCCCFG 環境変数を z88dk/lib/config に設定して、PATHz88dk/bin に通します。 また、この後 VS Code のソース編集時に include path を通すため Z88DK_HOME (任意の名前)を設定しておきます。

.bashrc の再下部に追加

# z88dk
export Z88DK_HOME=/home/hiromasa/devel/msx/z88dk
export ZCCCFG=${Z88DK_HOME}/lib/config
export PATH=${Z88DK_HOME}/bin:${PATH}

zcc の起動確認

$ zcc +msx
zcc - Frontend for the z88dk Cross-C Compiler - v18586-be7c8763a-20210901

Usage: zcc +[target] {options} {files}

Options:

   -v -verbose                  Output all commands that are run (-vn suppresses)
   -h -help                     Display this text
      -o                        Set the basename for linker output files
      -specs                    Print out compiler specs
# ..snip..

Z88DK のコアやライブラリーソースなどを VS Code で開いて参照する場合は、ビルド後に多数生成される .o オブジェクトファイルで VS Code が遅くならないよう .vscode/settings.json を追加し以下の files.watcherExclude 設定をすると良いです。

.vscode/settings.json

{
    "files.watcherExclude": {
        "**/.git/objects/**": true,
        "**/.git/subtree-cache/**": true,
        "**/obj/**": true,
    }
}

テンプレートプロジェクト

テンプレートプロジェクトの clone

導入したツールチェーンの動作を確認するため、筆者がつくったサンプルプログラムをセットしたテンプレートプロジェクトを git clone します。

  • cmake によるビルドが組まれています。
  • ソースコード編集やビルドに必要な VS Code の設定が入っています。
  • サンプルソースと .git/ を削除して、自分のプログラムを入れるとそのままプロジェクトディレクトリとして使えると思います。
  • GitHub Actions による自動ビルドが組まれています。タグを push すると .rom を GitHub のコンテナでビルドしてリリースできます。

Z88DK MSX build template with sample game

Use this template

cmake の導入

apt install cmake

テンプレートプロジェクトのビルド(サンプルソースをコンパイルして .rom をビルド)

  • cmake/z88dk.cmake 内で ${Z88DK_HOME} 環境変数を利用している。
git clone https://github.com/h1romas4/z88dk-msx-template.git
cd z88dk-msx-template
mkdir build
cd build
cmake -DCMAKE_TOOLCHAIN_FILE=../cmake/z88dk.cmake ..
make

cmake/z88dk.cmake

set(CMAKE_C_COMPILER $ENV{Z88DK_HOME}/bin/zcc)
set(CMAKE_ASM_COMPILER $ENV{Z88DK_HOME}/bin/zcc)

src/msx 配下のサンプルプログラムがコンパイルされ、dist/ 配下に MSX で起動可能な .rom ファイルが生成されます。(任意の MSX エミュレータにセットすることで起動可能です)

$ ls -laF dist/
合計 128
drwxrwxr-x  2 hiromasa hiromasa  4096  9月  9 23:26 ./
drwxrwxr-x 10 hiromasa hiromasa  4096  9月  9 18:04 ../
-rw-rw-r--  1 hiromasa hiromasa 94546  9月  9 17:02 example.map
-rw-rw-r--  1 hiromasa hiromasa 16384  9月  9 17:02 example.rom
-rw-rw-r--  1 hiromasa hiromasa   245  9月  9 17:02 example_BSS.bin
-rw-rw-r--  1 hiromasa hiromasa    16  9月  9 17:02 example_DATA.bin

テンプレートプロジェクトの CMakeLists.txt の構成

プロジェクトのビルドは CMakeLists.txt に従って行われます。プロジェクトに合わせて修正します。

コンパイラのパス指定(${Z88DK_HOME} 環境変数を利用している)

set(CMAKE_C_COMPILER $ENV{Z88DK_HOME}/bin/zcc)
set(CMAKE_ASM_COMPILER $ENV{Z88DK_HOME}/bin/zcc)

dist/example.rom の出力 ROM 名の設定

project(example.rom C ASM)

ビルド対象ソースファイルの追加削除(.c 及び .asm に対応)

add_source_files(
    ./src/msx/example.c
    ./src/msx/chars.asm
)

CMakeLists.txt を修正した後は、一度 cmake をし直します。

# キャッシュがあるので build ディレクトリごと削除
rm -Rf build
# build ディレクトリをつくってカレントを移動
mkdir build && cd build
# CMakeLists.txt の場所を指定して cmake
cmake -DCMAKE_C_COMPILER=${Z88DK_HOME}/bin/zcc ..
# Makefile が生成されるので make
make

通常のソースコード変更だけの場合は make だけで OK です。なお、アセンブラで include を使っている場合、include 先の更新までは make が追従しませんので、更新がある場合はいったん make clean してください。

その他のコンパイルオプション

The compiler frontend: ZCC

The frontend of z88dk is called zcc, it is this that you should call if you want to do any compilations. To invoke the frontend use the command:

C 向け(-m が後述の example.map シンボルマップファイルを出力する指定)

add_compile_flags(C
    +msx
    -vn
    -llib3d
    -lm
    -lndos
    -lmsxbios
    -m
    # -debug
    # https://github.com/z88dk/z88dk/wiki/Classic-allocation#automatic-heap-configuration
    -DAMALLOC
)

アセンブラ向け

add_compile_flags(ASM
    +msx
)

リンカー向け

  • -subtype=rom0x4000 からの ROM 版をつくることを指定。
  • もし 0xc0000 からの RAM 版をつくる場合は -subtype=default (.cas 形式) もしくは -subtype=wav (カセットテープのピーガー形式) を指定。
    • 実機 BASIC の BLOAD "CAS:",R でロード・ランできる。
add_compile_flags(LD
    -create-app
    -subtype=rom
)

cmake が生成した Makefile の zcc コマンドをデバッグする場合は CMakeLists.txt の以下の部分をコメントアウトする。

# for debug
# set(CMAKE_VERBOSE_MAKEFILE 1)

VS Code

テンプレートプロジェクトは C/C++ for Visual Studio Code 拡張用の .vscode/settings.json を含んでいます。Z88DK への include path の設定がされているので、拡張を有効にした後、ディレクトリを VS Code で開くことで、ソースコード編集中の定義の参照やインテリセンスによる補完ができます。

C/C++ for Visual Studio Code

The C/C++ extension adds language support for C/C++ to Visual Studio Code, including features such as IntelliSense and debugging.

.vscode/settings.json${env:Z88DK_HOME} 環境変数で Z88DK へのパスをたどっている)

{
    "configurations": [
        {
            "name": "z88dk",
            "includePath": [
                "${workspaceFolder}/src/msx/*",
                "${env:Z88DK_HOME}/include/*",
                "${env:Z88DK_HOME}/include/**/*"
            ],
            "defines": [],
            "compilerPath": "${env:Z88DK_HOME}/bin/zcc",
            "cStandard": "c11",
            "intelliSenseMode": "gcc-x86"
        }
    ],
    "version": 4
}

また、.vscode/tasks.json で F1 キー押下後の Run Tasks メニューに、ビルド系のコマンドを追加しています。

.vscode/tasks.json

{
    // See https://go.microsoft.com/fwlink/?LinkId=733558
    // for the documentation about the tasks.json format
    "version": "2.0.0",
    "tasks": [
        {
            "label": "Clean CMake directory",
            "type": "shell",
            "linux": {
                "command": "rm -Rf ${workspaceFolder}/build && mkdir ${workspaceFolder}/build"
            }
        },
        {
            "label": "Run CMake",
            "type": "shell",
            "dependsOn": "Clean CMake directory",
            "linux": {
                "command": "(cd ${workspaceFolder}/build && cmake -DCMAKE_C_COMPILER=${Z88DK_HOME}/bin/zcc ..)"
            }
        },
        {
            "label": "Run Build",
            "type": "shell",
            "linux": {
                "command": "(cd ${workspaceFolder}/build && make)"
            }
        },
    ]
}

MSX のエミュレーション実行環境

openMSX のビルド

ビルドした .rom の動作確認を行うため openMSX をビルドします。(ここでは master ブランチの 5841294 版で確認しています)

openMSX

the MSX emulator that aims for perfection

依存関係の導入

sudo apt-get install libgl1-mesa-dev

ビルド

  • -j6 はコンパイルに使う CPU コア数。マシンに合わせて設定すると速くビルドできます。
  • 依存を static にするため staticbindist を指定しています。
    • リビルド時に SDL の include でエラーになる場合は derived ディレクトリを削除のこと。
git clone https://github.com/openMSX/openMSX.git
cd openMSX
make -j6 OPENMSX_TARGET_CPU=x86_64 OPENMSX_TARGET_OS=linux OPENMSX_FLAVOUR=opt staticbindist

# ...snip...
Creating binary package:
  Executable...
  Data files...
  Documentation...
  C-BIOS...
  Creating symlinks...

インストール

derived/x86_64-linux-opt-3rd/bindist/install/bin に生成された openmsx をパスが通っている適当な場所にコピー。

cp -p derived/x86_64-linux-opt-3rd/bindist/install/bin/openmsx ~/.local/bin

~/.openMSX にコンフィグをコピー

mkdir ~/.openMSX
cp -Rfp derived/x86_64-linux-opt-3rd/bindist/install/share/ ~/.openMSX

起動確認(テンプレートプロジェクトの example.rom による)

$ cd /home/hiromasa/devel/msx/z88dk-msx-template/dist
$ ls -laF
合計 128
drwxrwxr-x  2 hiromasa hiromasa  4096  9月  9 23:26 ./
drwxrwxr-x 10 hiromasa hiromasa  4096  9月  9 18:04 ../
-rw-rw-r--  1 hiromasa hiromasa 94546  9月  9 17:02 example.map
-rw-rw-r--  1 hiromasa hiromasa 16384  9月  9 17:02 example.rom
-rw-rw-r--  1 hiromasa hiromasa   245  9月  9 17:02 example_BSS.bin
-rw-rw-r--  1 hiromasa hiromasa    16  9月  9 17:02 example_DATA.bin
$ openmsx example.rom

SDL init failed になる場合

もし openmsx 起動時に次のエラーが出力され、音声の発音などに問題がある場合は、SDL を static のものから OS 標準のものに変更してビルドしてみます。

SDL init failed (16)

依存関係のライブラリー devel を導入。

sudo apt-get install libsdl2-dev libpng-dev tcl-dev libglew-dev libsdl2-ttf-dev libvorbis-dev libtheora-dev libogg-dev libao-dev libfreetype6-dev

openMSX を clone したディレクトリにて再ビルド。(staticbindist を付与しない)

$ pwd
/home/hiromasa/devel/msx/openMSX
$ make clean
$ make -j6 OPENMSX_TARGET_CPU=x86_64 OPENMSX_TARGET_OS=linux OPENMSX_FLAVOUR=opt

できたバイナリーをパスが通っている位置にコピー。

$ ls -laF ./derived/x86_64-linux-opt/bin/openmsx
-rwxrwxr-x 1 hiromasa hiromasa 7675296 10月 23 15:30 ./derived/x86_64-linux-opt/bin/openmsx*
$ cp -p ./derived/x86_64-linux-opt/bin/openmsx ~/.local/bin

コンフィグレーションファイルをコピー。

$ mkdir -p ~/.openMSX/
$ cp -Rfp share/ ~/.openMSX/
$ ls -laF ~/.openMSX/
drwxr-xr-x 13 hiromasa hiromasa 4096 10月 23 01:50 share/
$ ls -laF ~/.openMSX/share
drwxr-xr-x  2 hiromasa hiromasa    4096 10月 23 01:12 extensions/
drwxr-xr-x  2 hiromasa hiromasa    4096 10月 23 01:12 icons/
-rw-r--r--  1 hiromasa hiromasa    7321 10月 23 01:12 init.tcl
drwxr-xr-x  3 hiromasa hiromasa   16384 10月 23 01:12 machines/
drwxr-xr-x  2 hiromasa hiromasa    4096 10月 23 01:12 nettou_yakyuu/
drwxr-xr-x  2 hiromasa hiromasa    4096 10月 23 01:12 playball/
drwxr-xr-x  2 hiromasa hiromasa    4096 10月 23 01:12 scripts/
-rw-r--r--  1 hiromasa hiromasa     317 10月 23 15:38 settings.xml
drwxr-xr-x  2 hiromasa hiromasa    4096 10月 23 01:12 shaders/
drwxr-xr-x 10 hiromasa hiromasa    4096 10月 23 01:12 skins/
drwxr-xr-x  2 hiromasa hiromasa    4096 10月 23 01:12 software/
-rw-r--r--  1 hiromasa hiromasa 1208129 10月 23 01:12 softwaredb.xml
-rw-r--r--  1 hiromasa hiromasa     301 10月 23 01:12 softwaredb1.dtd
drwxr-xr-x  3 hiromasa hiromasa    4096 10月 23 01:12 systemroms/
drwxr-xr-x  3 hiromasa hiromasa    4096 10月 23 01:12 unicodemaps/

起動確認。

$ which openmsx
~/.local/bin/openmsx
$ openmsx -v
openMSX 18.0-102-g953affe10
flavour: opt
components: ALSAMIDI CORE GL LASERDISC
$ openmsx

openmsx-debugger のビルド

openMSX 上で起動した .rom をアセンブルデバッグするために openmsx-debugger を導入します。(ここでは master ブランチの 1bac8ea 版で確認しています)

openmsx-debugger The openMSX debugger is a separate program that interfaces with openMSX and controls its debugger from within a graphical user interface.

依存関係の導入(Ubuntu 20.04 LTS)

sudo apt install qtbase5-dev qttools5-dev-tools qt5-default

依存関係の導入(Ubuntu 21.04 | Ubuntu 22.04 LTS)

sudo apt install qtbase5-dev qtbase5-dev-tools qtchooser qt5-qmake

ソースコードの clone とパッチ

Ubuntu 22.04 LTS(Qt 5.14) の場合:

git clone https://github.com/openMSX/debugger
cd debugger
git checkout bc999831fb14ce7d4505e06ad75a821842049ab4 # master ブランチでパッチが通らない場合
wget https://gist.githubusercontent.com/h1romas4/9fdbcd45c4d6bdd87312dc3ca83059be/raw/122ec08b62df3a285b6c710e05a3e33a24c45364/0001-support-z88dk-map-symble-file.patch
patch -p1 < 0001-support-z88dk-map-symble-file.patch
make -j6

Ubuntu 20.04 LTS(Qt 5.12) の場合:

git clone https://github.com/openMSX/debugger
cd debugger
git checkout bbbc33c1fc07a66ea80d38251fdd078d0242abd8 # 少し前のバージョン
wget https://gist.githubusercontent.com/h1romas4/c851be9afa16ff2ebb3a967548ed92ad/raw/54030a10567ec2fe6148a47f2dffd8b8ab81e81c/0001-add-z88dk-symbol-read-hack.patch
patch -p1 < 0001-add-z88dk-symbol-read-hack.patch
make -j6

ビルド確認と適当なパスの通った位置にコピー

$ ls -laF derived/bin/
-rwxrwxr-x 1 hiromasa hiromasa 20408664  9月 10 00:58 openmsx-debugger*
$ cp -p derived/bin/openmsx-debugger ~/.local/bin
$ openmsx-debugger

openMSX で dist/example.rom を起動した状態で、openmsx-debugger から System -> Symbol Manager -> Add で dist/example.map を読ませる。

System -> Connect で openMSX に接続すると、ソースコード内のシンボル名付きでアセンブラコードが表示され、デバッグブレイクなどができる。

MAME と z88dk-gdb でデバッグを行う。

Z88DK の master ブランチには z88dk-gdb コマンドが含まれており、MAME の gdbstub と通信してデバッグを行うこともできます。

  • MSX ゲーム開発を MAME/C-BIOS で行うメモ
  • CMakeLists.txt の CFLAGS-m -debug コンパイルオプションを有効にしてビルドすると C のレベルでデバッグできます。(プログラムは遅くなります)
    • 2022/7 リリースの Z88DK v2.2 からの機能です。
$ (cd ${MAME_HOME} && ./cbios cbios example -window -resolution 800x600 -debugger gdbstub -debug)
gdbstub: listening on port 23946
$ cd dist/ # .map ファイル内に出力されたソースコードのパス(../src/msx/example.c)と z88dk-gdb を起動するカレントディレクトリを合わせます。(v2.2 からソースマップがフルパスになったのでこの操作は不要)
$ z88dk-gdb -h 127.0.0.1 -p 23946 -x example.map

また、Native Debug 拡張を使い、gdb/mi2 に VS Code からアタッチすることで VS Code デバッガから操作することもできます。

.vscode/launch.json

{
    "version": "0.2.0",
    "configurations": [
        {
          "name": "Attach to MAME gdbserver",
          "type": "gdb",
          "request": "attach",
          "target": "127.0.0.1:23946",
          "remote": true,
          "cwd": "${workspaceRoot}",
          "gdbpath": "${env:Z88DK_HOME}/bin/z88dk-gdb",
          "debugger_args": [
            "-x",
            "${workspaceRoot}/dist/example.map" // or appropriate .map of your project (-m -debug needed!)
          ],
          "autorun": [
          ]
      }
    ]
}

なお、2022/7 現在、Step Over すると動作が戻ってこなくなるようです。Continue と Step into はうまく動作しますので使い分けて活用すると良いと思います。

アセンブラデバッグをする場合

VS Code 上でアセンブラデバッグをする場合は以下の文書を参考にしてください。

DeZog + Z88DK + MAME で MSX アセンブリーをデバッグする手順

この文書は、VS Code の Z80 デバッガーである DeZog 拡張と Z80 ツールチェイン Z88DK、及び MSX エミュレータ・gdb スタブとして MAME を使い、VS Code 上の Z80 アセンブリーをデバッグする手順です。

開発周辺ツール

タイルマップエディタ nMSXtiles の導入

MSX 向けのグラフィックを描くため nMSXtiles を導入してみます。(ここでは master ブランチの e4db1b8 版で確認しています)

nMSXtiles

Editor de Baldosas (Tiles) y Sprites para MSX en modo gáfico SC2 y SC4

依存関係の導入

sudo apt install qtcreator

ソースコードの clone

git clone https://github.com/pipagerardo/nMSXtiles.git

依存関係で導入した Qt Creator を起動して ファイル/プロジェクトを開くから src/nmsxtiles.pro を指定する。

ビルドからプロジェクトのビルドを実行する。

build/ 下に nMSXtiles 実行バイナリが作成されるので、適当なパスの通ったディレクトリにコピーして起動する。

$ ls -laF build/
合計 452
drwxrwxr-x 4 hiromasa hiromasa   4096  9月  9 21:40 ./
drwxrwxr-x 9 hiromasa hiromasa   4096  9月  9 21:40 ../
drwxrwxr-x 2 hiromasa hiromasa   4096  9月  9 21:21 doc/
drwxrwxr-x 2 hiromasa hiromasa   4096  9月  9 21:52 examples/
-rwxrwxr-x 1 hiromasa hiromasa 445792  9月  9 21:40 nMSXtiles*
$ cp -p nMSXtiles ~/.local/bin
$ nMSXtiles

起動して build/examples 下にあるサンプルプロジェクトを開くと使い方が分かります。

Z88DK MSX プログラミング

Z88DK 標準ライブラリー

Z88DK で +msx で MSX をターゲットに指定した場合は C の標準ライブラリーは Classic Library になります。(もうひとつは New Library)

Classic Overview

The classic library can be used with both sccz80 and zsdcc and the final binary can be made of object files generated by either compiler (though care will need to be taken around calling conventions and floating point usage).

  • C 標準ライブラリーにより sprintf などが使える(便利)。

src/example.c

#include <stdio.h>
#include <stdlib.h>

/**
 * スコア等表示
 */
void print_state()
{
    uchar score_string[VRAM_WIDTH + 1];
    sprintf(score_string, "SCORE %06u  HISCORE %06u  %02u", game.score, game.score_hi, game.remein_clear);
    vwrite(score_string, VPOS(0, 0), VRAM_WIDTH);
}

Z88DK gfx ライブラリー

MSX の TMS9918a/TMS9928a VDP にアクセスする場合は msx/gfx.h を include します。

  • set_colorset_mode set_mangled_mode などで VDP の初期化ができる。
  • vwritefillvpeek vpoke などで VRAM へのアクセスができる。
#include <msx/gfx.h>

/**
 * グラフィックス初期化
 */
void init_graphics()
{
    // スクリーンモード
    set_color(15, 1, 1);
    set_mangled_mode();

    // スプライトモード
    set_sprite_mode(sprite_default);

    // キークリックスイッチ(OFF)
    *(uint8_t *)MSX_CLIKSW = 0;

    // 画面クリア
    fill(VRAM_START, VRAM_NONE, VRAM_WIDTH * VRAM_HEIGHT);

    // PCG 設定(3面に同じデーターを転送)
    vwrite(chars, 0x0000, 0x800);
    vwrite(chars, 0x0800, 0x800);
    vwrite(chars, 0x1000, 0x800);

    // 色設定(0000|0000 = 前|背景)
    set_char_color('=', 0x54, place_all);
    set_char_color('$', 0xa0, place_all);
    set_char_color('>', 0x6d, place_all);
    set_char_color('?', 0x60, place_all);
}
  • get_rnd seed_rnd 関数で乱数が使える。
uint8_t x = get_rnd() % 31 + 1;
uint8_t y = get_rnd() % 21 + 1;
  • get_stick get_trigger でコントローラ、キーボードの入力ができる。
// 入力取得
uint8_t stick = st_dir[get_stick(0)];
uint8_t trigger = get_trigger(0);

ブートストラップ

Z88DK のブートストラップの解説は以下にドキュメントがあります。

CRT

The crt is the startup code that runs before calling main(). It is responsible for setting the memory map, instantiating device drivers on stdin/stdout/stderr, initializing the bss and data sections and calling any initialization code prior to calling main(). On return from main() it is responsible for closing open files, resetting the stack and preparing to return to the host.

+msx subtype=rom 指定の場合は、lib/config/msx.cfg より設定され、ブートストラップは次のアセンブラになります。

lib/target/msx/classic/rom.asm

;
;  Main Code Entrance Point
;
IFNDEF CRT_ORG_CODE
    defc  CRT_ORG_CODE  = $4000
ENDIF
    org   CRT_ORG_CODE

; ROM header
    defm    "AB"
    defw    start
    defw    0		;CallSTMT handler
    defw    0		;Device handler
    defw    0		;basic
    defs    6

start:
    di
    INCLUDE "crt/classic/crt_init_sp.asm"
    ei
;...snip...
  • 標準で 0x4000 からの 16KB ROM。ROM ヘッダーがここでつけられる。
  • スタックポインタ初期化。
  • プログラムの大きさにより 32KB ROM にも初期化してくれる。(NOBORUNOCA は 32KB ROM)
    • コナミのメガロムマッパーなどにも対応されていそう。(未検証)
  • RAM は 0xc000 から。

.asm をエントリーポイントとしてプログラムを構成する場合は _mainPUBLIC にする。

; code_user is for read-only code
; called by the crt as entry to program
SECTION code_user
; _main という名前でラベルを PUBLIC にする
PUBLIC _main

; メイン
_main:
    ; ここからプログラム
    LD BC,VALUE1
    LD A,(BC)
    LD IX,WORK1
    LD (IX),A
    RET

; rodata_user if for constant data
; kept in rom if program is in rom
SECTION rodata_user
; ROM に書き込むデーター
VALUE1:      DB 10

; bss_user is for zeroed ram variables
; zeroed by crt when program started
SECTION bss_user
; RAM に配置するワークエリア(CRT が 0 に初期化してくれる)
WORK1:       defs 1

; data_user is for initially non-zero ram variables
; initialized in ram by crt if the program is in rom
SECTION data_user
; RAM に配置するデーター(CRT が指定値で初期化してくれるらしい)
WORK2:       DB 10

.asm へのバイナリデータリンク

.asm ファイルにデータセクション rodata_user(ROM に配置される)をつくり、_ アンダーバー付きラベルをパブリック指定することで .c から extern で、アンダーバーなしのラベル名で参照できる。

src/chars.asm

; rodata_user
; https://github.com/z88dk/z88dk/blob/master/doc/overview.md#a-quick-note-for-asm-code
; rodata_user if for constant data
; kept in rom if program is in rom
SECTION rodata_user
PUBLIC _chars, _colors

_chars:
DB $fe,$c2,$8a,$92,$aa,$86,$fe,$00,$fe,$c2,$8a,$92,$aa,$86,$fe,$00
DB $fe,$c2,$8a,$92,$aa,$86,$fe,$00,$fe,$c2,$8a,$92,$aa,$86,$fe,$00

src/example.c

// chars.asm::_chars ラベルのアドレス参照(VRAM PCG 転送用)
extern unsigned char chars[];

ABI を合わせることで .asm.c 間で関数呼び出しや call が可能。(本サンプルではアセンブラでかかれたサウンドドライバーの呼び出し部分で使用)

The Stack Frame

The z88dk parameter passing mechanism normally relies on the stack: local variables declared in a C program are allocated on the stack as a function is entered and are popped off the stack as functions are exited. z88dk supports multiple calling conventions so care should be taken when writing assembler that interfaces with C.

.asm でかかれたプログラム領域は SECTION code_user に配置する。.asm の規定セクション名については、以下のドキュメントに記載があります。

A Quick Note for ASM Code

Z88DK uses z80asm as a section-aware linking assembler. What this means is placement of code and data in memory is controlled by section assignment rather than ORG. This is an essential feature of a modern development environment.

ABI について(呼び出し規則と関数デコレータ)

本項「呼び出し規則と関数デコレータ」は Z88DK Wiki の DeepL 日本語訳です。


Z88DK には、関数を呼び出すためのいくつかの異なる規約があります。これらを理解することで、プログラムとアセンブラの機能をリンクさせたり、デバッグの際に役立つでしょう。

規約や修飾子は、例えば、関数のプロトタイプにサフィックスとして追加することで、コンパイラに示されます。

int function(long val) __z88dk_callee;

Z88DK は以下の呼び出し規則をサポートしています。

修飾子 Stack cleanup 説明
__smallc 呼出し元 sccz80 が使用するデフォルトの呼び出し規則です。パラメータは、左から右に向かってスタックに押し込まれます。sccz80 の場合、char は word としてスタックにプッシュされます。注意 zsdcc のバグにより、__smallc 関数にアクセスすると、chars は 1 バイトとしてプッシュされます。戻り値は hl, または dehl です。
__stdc 呼出し元 パラメータは逆順(右から左)にプッシュされます。char は word としてスタックにプッシュされます。戻り値は hl または dehl です。
__z88dk_sdccdecl 呼出し元 zsdcc のデフォルトの規約です。パラメータは、右から左に向かってスタックにプッシュされます。char は、1 バイトとしてスタックにプッシュされます。戻り値は、l、hl、または dehl に保持されます。

Calling convention modifiers

修飾子 説明
__z88dk_callee 呼び出された関数(callee)は、スタックをクリーンアップする責任があります。
__z88dk_fastcall レジスターには最大で1つのパラメータが渡されます。smallc では、一番右のパラメータになります。また、__stdc__z88dk_sdccdecl では、唯一のパラメータとなります。使用されるレジスタは、パラメータのビット幅に応じて、常に DEHL のサブセットとなります。 sccz80 の浮動小数点/倍精度は 48 ビットなので、少し扱いが異なります。 これらは「一次浮動小数点アキュムレータ」を介して渡されます。 従来の C ライブラリでは、これは "fa"と名付けられた6バイトのスタティックメモリです。 new c library では、これはexxセットのレジスタ BCDEHL です。 sdcc の 64 ビット long long 型は、fastcall リンケージを使用して渡すことができません。
__z88dk_saveframe sccz80 で生成されたコードにのみ有効です。sdccフレームポインタ(ix)は、この関数への入力時に保存されます。これはこの関数が sdcc コンパイルコードから呼び出されることが予想され、長さまたは浮動小数点を使用する場合に必要です。
__critical 割り込みの状態は、関数への入力時に保存され、終了時に復元されます。
__interrupt(n) 入力時にプライマリ・レジスタとインデックス・レジスタを保存し、終了時にリストアします。必要に応じてreti/retnして返します。z88dk で採用されている動作 はsdccと同じで「かなり紛らわしい」です。
__naked アセンブラ関数に使用され、関数のプロローグとエピローグが生成されないようにします。
__interrupt __critical インタラクション:
  • void func() __critical __interrupt - レジスタをセーブし、retn で返します(NMIの場合など)
  • void func() __interrupt - eiし、レジスターを保存し、reti で返します (im2 の場合)
  • void func() __critical __interrupt(0) - レジスターを保存し、ei; reti で返します (im1 の場合)

バンクコール規約修飾子

修飾子 使用方法
__banked ターゲット固有の banked_call 関数を使用して、関数を呼び出すことができます。呼び出しの後には、その関数の32ビットのアドレスが続きます。最初の2バイトがアドレスで、後の2バイトがバンクです。この呼び出し規則の関数例は、クラシックな +zx、+msx、+gbポートに見られます。
__z88dk_shortcall(RR, VV) rst RR トランポリンで関数を呼び出すことができます。VV < 256 の場合、生成されるコードは rst RR; defb VV であり、そうでない場合は rst RR; defw VV となります。これは、非ページ化されたメモリバンクにある関数にアクセスするために使用できます。
__z88dk_shortcall_hl(RR, VV) * 生成されたトランポリンが次のようになることを除けば、上記と同じです。: ld hl, VV; rst RR
__z88dk_hl_call(VV1, VV2) * spectranet スタイルトランポリンです。: ld hl, VV1; call VV2
__z88dk_params_offset(VV) トランポリンを介して呼び出された場合、関数のパラメータがsp+2を起点に配置されていないことがあります。このアノテーションでは、パラメータに到達するために必要な追加のオフセットを定義します。

*: __z88dk_fastcall と組み合わせた場合、fastcall に必要なコンテンツや HL は BC にコピーオーバーされ、後に置き換えられます。トランポリンが受信側で処理されたときにHLの内容を復元するのは、ユーザーの責任です。

__z88dk_shortcall() では、直接呼び出しのみがrstトランポリンを使用し、関数ポインタを介した呼び出しは通常通り呼び出します。したがって、ライブラリを配布したり、インターフェイスを提供したりする際には、rst トランポリンを介して呼び出すことができるマッチした関数スタブを提供する必要があります。

その他の修飾子

修飾子 利用方法
__preserves_regs(r1,r2...) 指定されたレジスタが関数によって保持されることを sdcc に示します。この情報は、sdcc が生成するコードの品質を向上させるために使用されます。

sccz80 ライブラリーアノテーション

慣習的に、sccz80が使用するライブラリ関数は、型の後に__LIB__と記されています。

void __LIB__ mylibrary_function(int param);

つまり、sccz80 は mylibrary_function を、sdcc は _mylibrary_function を呼び出すことになります。これらの異なるエントリーポイントは、呼び出し規則を調整するのに役立ちます。(vaargs関数では必須)

戻り値

戻り値は、戻り値のビット幅に応じて、DEHLのサブセットに保持されます。 sccz80 の 48 ビット float/double は、クラシックライブラリではアドレス "fa"の 6 バイトのスタティックメモリである "primary floating point accumulator" に値を返し、new c library では exx セットのレジスタBCDEHL に値を返すなど、扱いが異なります。 なお、DEHL を介して1つのパラメータが関数に渡される fastcall リンケージと同じルールが適用されます。

64-bit long long 型の戻り値は特別に処理されます。 コンパイラは、関数呼び出しの最初のパラメータとして、戻り値のメモリへのポインタを渡します。 このパラメータは、関数のプロトタイプには記載されていません。 呼び出された関数は、そのポインタを使って、返された64ビットの値を格納しなければなりません。

[翻訳ここまで]


インラインアセンブラ

C 言語ソースコード中に #asm #endasm もしくは、__asm __endasm; を挟むことでインラインアセンブラを使うことができます。スタックは __smallc にて、char は word のリトルエンディアンで積まれるので、C言語関数の引数は次のように取得できます。

void write_psg(uint8_t reg, uint8_t dat)
{
#asm
    LD   HL ,2     ;
    ADD  HL, SP    ; リターンアドレスをスキップ
    LD   E, (HL)   ; WRTPSG(E)
    INC  HL        ; WORD2byte 目をスキップ
    INC  HL        ;
    LD   A,(HL)    ; WRTPSG(A)
    CALL $0093     ; call WRTPSG(A, E)
#endasm
}

/**
 * 特殊効果音
 */
void sound_jump()
{
    // ..snip..
    // ボリューム設定
    write_psg(0xa, 0x0);
    // ..snip..
}