MSXのアプリ開発環境を構築する方法（macOS向け）

# 概要

Z80 のプログラミング修行用に、macOS で MSX のアプリ開発環境（※オールマシン語）を構築する方法を紹介します。

- 言語: Z80アセンブリ言語
- メディア形式: 32KB ROM (標準ROM)
- 実行環境: [WebMSX](https://webmsx.org)

> 恐らく、Linux でもほぼ同じ手順でできます。

# 1. アセンブラを準備

アセンブラは [z88dk](https://z88dk.org/site/) 付属の `z80asm` を使います。

> [z88dk](https://z88dk.org/site/) は、MSX 以外にもゲームギア、ゲームボーイ、X1、PC-8801 など、 [Z80 系の色々なゲーム機やパソコン](https://github.com/z88dk/z88dk/wiki/Platform) に対応した SDK です。とても便利。

> 各種プラットフォームの C ライブラリが充実しているので、どちらかというと C言語 で組む場合に使うケースが多いかもしれませんが、今回は C コンパイラやライブラリは一切使わず、オールマシン語で組みます。ハンドアセンブルでも良かったのですが流石にニーモニックは使います。（要するに、必ずしも z88dk である必要はありません）

## 1-1. インストール

```bash
mkdir ~/msx-work
cd ~/msx-work
curl -O http://nightly.z88dk.org/z88dk-osx-latest.zip
tar xvf z88dk-osx-latest.zip
export PATH=${PATH}:${HOME}/msx-work/z88dk/bin
```

## 1-2. チェック

`z80asm` コマンドが実行できれば準備 OK です。

```
% z80asm
Z80 Module Assembler 16923-cc7a9072f-20200910
(c) InterLogic 1993-2009, Paulo Custodio 2011-2020
```

# 2. メモリマップ

8bit系、16bit系のプラットフォームでアプリ開発をする時に **一番重要な情報はメモリマップ**（と、I/Oマップ）と言っても過言では有りません。

という訳で、プログラムを組む前に今回作る **32KB 標準 ROM カートリッジで MSX が動作する時のメモリマップ** について理解しておきましょう。

MSX のメモリマップはかなり自由度の高い構造になっています。

> 実のところ、MSX や X1 などの PC 系はメモリマップが複雑で面倒くさいので、初学者向けにはゲーム系（マスターシステムやゲームギアあたり）の方がオススメかもしれません。

## 2-1. スロット構成

MSX は 16KB 区切り 4 ページのメモリレイアウトになっています。
（スロットと呼ばれています）

32KB の ROM を MSX の スロット1 に挿入した状態で電源を入れると、BIOS のブート処理が完了した時、以下のようなメモリレイアウトになり、ROM ヘッダに書き込まれているスタートアドレスからプログラムが動き始めます。

| アドレス | 内容 |
|:-:|:-|
| `0x0000 ~ 0x3FFF` | BIOS (ブート処理、割り込み処理、BASIC) |
| `0x4000 ~ 0x7FFF` | ROM の前半 16KB |
| `0x8000 ~ 0xBFFF` | ROM の後半 16KB |
| `0xC000 ~ 0xFFFF` | RAM |

> メガロム（32KBより大きいサイズのROM）の場合もこのメモリレイアウトになります。メガロムには、ROM 領域を 8KB 区切りでバンク切り替えするものと、16KB 区切りでバンク切り替えするものが存在します。（ROM 領域への書き込み (LD) でカセット内蔵のバンクコントローラにバンク切り替えのリクエストを行う形になっています）

> なお、MSX 実機で動作できるメガロムを開発することは可能ですが、MSX エミュレータにメガロムの種別を認識させる手段がないので、メガロム対応の MSX アプリ開発は基本的にできません。（例えば、WebMSX の場合は[このような形](https://github.com/ppeccin/WebMSX/blob/654e3aa303e84404fba4a89d5fa21fae32753cf5/src/main/msx/rom/ROMDatabaseUncompressed.js)で ROM ファイルのチェックサムのようなものからメガロムの種別を認識しています）

BIOS は、ROM のヘッダ領域（先頭 16 バイト）の先頭 2 バイトの内容を見て、ROM カセットが挿入されていることを認識します。

今回、ROM のスタートアドレスを `0x4010` にするので、ヘッダの内容は次のようにします。

- 1バイト目: `A (0x41)` BIOS に ROM と認識させるプレフィクス
- 2バイト目: `B (0x42)` BIOS に ROM と認識させるプレフィクス
- 3バイト目: `0x10` スタートアドレスの下位8bit
- 4バイト目: `0x40` スタートアドレスの上位8bit
- 5バイト目 ~ 16バイト目: `0x00`

## 2-2. RAM について

前項のスロット構成だと 16KB の RAM が使用できる形になっています。

MSX の搭載 RAM サイズは、8KB、16KB、32KB、64KB (※MSX2以降は標準装備）など色々なバリエーションがありますが、32KB 以上の RAM を使用する場合、BIOS 処理の呼び出し（インタースロットコール）でスロット構成を切り替えて使用します。

MSX の最低 RAM サイズは 8KB なので、その想定でプログラムを開発すればより多くの実機環境で動作できるメリットがあります。

そのため、大手が開発した MSX 用の市販ゲームソフトは 8KB RAM で動作するものが大半です。

8KB RAM を搭載している機種としては [カシオの PV-7](https://ja.wikipedia.org/wiki/PV-7) あたりが有名です。

8KB RAM の場合、

- `0xC000 ~ 0xDFFF` : RAM
- `0xE000 ~ 0xFFFF` : `0xC000 ~ 0xDFFF` のミラー

というメモリマップになります。

8bit ゲーム機の大半が、RAM は多くても 8KB 程度しか搭載していないので、8KB もあれば十分かと思われます。

> 参考:
> - SG-1000: 1KB
> - ファミコン: 2KB（※拡張RAM搭載のマッパーなら+8KB）
> - マスターシステム: 8KB
> - ゲームボーイ: 8KB


# 3. VDP (TMS9918A)

MSX は TMS9918A と呼ばれるテキサス・インスツルメンツ社の VDP; _Video Display Processor（映像表示装置）_ を搭載しています。

> 一部、MSX2 に搭載されている VDP (V9938) を搭載している MSX も存在します。（例: [Yamaha YIS-503II](https://www.msx.org/wiki/Yamaha_YIS-503II)）

TMS9918A は、MSX 以外にも SG-1000、コレコビジョンなど、様々なコンピュータで使われています。

TMS9918A は、テキサス・インスツルメンツ社の TI-99/4 に搭載された TMS9918 を改良したもので、後継機の TI-99/4A に最初に搭載されました。

> TMS9918 と TMS9918A の大きな違いは、mode 2 と呼ばれる画面モードの有無です。

TI-99/4 は、1979年 に発売された世界初の 16bit CPU (TMS9900) を搭載したホームコンピュータ（パソコン）として知られていますが、殆ど売れなかった（出荷台数: 2万台以下）ようです。しかし、1981年 に発売された後継機のTI-99/4A はヒットした（出荷台数: 280万台）ので、TMS9918A は MSX が発売された当時（1983年）は、比較的安価に入手できたものと考えられます。

> 8bit ~ 16bit の SEGA ハードの VDP は、TMS9918A をベースに機能強化（mode 4 や mode 5 を追加）する形で進化しているので、TMS9918A を扱えるようになれば、比較的カンタンに SEGA ハードのゲーム開発ができるようになると思います。
>
> MSX2 の VDP V9938 (MSX-VIDEO) も TMS9918A をベースに機能強化されていますが、どちらかといえばビットマップ系の機能に注力しているので、ゲームよりもグラフ表示などの実用用途向けに進化したものといえます。

TMS9918A には色々な画面モードがありますが **mode 2 だけ覚えれば大丈夫** です。

> 私の知る限り、MSX の市販ゲームソフトは全て mode 2 で作られています。「これなら mode 0 で作れるんじゃないか？」と思われるゲームですら、mode 2 で作られています。
>
> mode 1 や mode 3 を使っている MSX か SG-1000 のゲームソフトが存在するのかが謎...（mode 3 の鬼のように荒いビットマップでゴリゴリ動くゲームとかがあるなら一見の価値があるかも？）

なお、TMS9918A の詳しい使い方は、本記事では省略します。

以下のドキュメントを参考にしてください。

https://www.smspower.org/uploads/Development/tms9918a.txt

> 若干意訳混じりですが、以下に上記を翻訳したものがあります。
> https://github.com/suzukiplan/tinymsx/blob/master/doc/TMS9918A.md

MSX の BIOS のブート処理が完了してスタートアドレスが実行される時点の TMS9918A の状態は、以下のようになっています。

- 画面モード: `mode 0`
- VRAMサイズ: `16KB`

（メモリマップ）

|用途|アドレス|補足事項|
|:-:|:-:|:-|
|キャラクタパターン|`0x0000 ~ 0x07FF`|mode 2 なら `0x0000 ~ 0x17FF`|
|ネームテーブル|`0x1800 ~ 0x1AFF`||
|スプライト属性（OAM）|`0x1B00 ~ 0x1B7F`||
|カラーテーブル|`0x2000 ~ 0x27FF`|mode 2 なら `0x2000 ~ 0x37FF`|
|スプライトパターン|`0x3800 ~ 0x3FFF`||

> mode 0 と mode 2 の違いは、キャラクタパターン数です。
>
> - mode-0: 全部で 256 パターン
> - mode-2: 全部で 768 パターン
>   - ネームテーブル上部 8 行 に 256 パターン
>   - ネームテーブル中間 8 行 に 256 パターン
>   - ネームテーブル下部 8 行 に 256 パターン
>
> 256パターン以下で問題無ければ、mode 2 ではなく mode 0 でも OK です。


キャラクタパターンは、ASCII コードに対応する文字の画像が設定された状態になっているので、テキストベースの MSX アプリを開発するのであれば、キャラクタパターンは未設定でも大丈夫です。 _（この辺がゲーム機でアプリを開発する場合よりも楽で良いですね）_

MSX BIOS が設定しているデフォルトの VRAM マップは、mode 0 と mode 2 のどちらでも使える形になっているので、VRAM のメモリマップを VDP レジスタで変更する必要は特に無さそうです。

# 4. Hello, World!

それでは `Hello, World!` を表示する ROM を作ってみます。

## 4-1. プログラム本体 (hello.asm)

```z80
org $4000

.Header
    ; MSX の ROM ヘッダ (16 bytes)
    defb 'A', 'B', $10, $40, $00, $00, $00, $00
    defb $00, $00, $00, $00, $00, $00, $00, $00

.Start
    ; スタックポインタを初期化
    ; 参考: https://qiita.com/suzukiplan/items/9d4b814d53ce96e4ea35
    ld sp, $F380

    ; VRAM の アクセスアドレス を NameTable の先頭 ($1800) に書き込みモードで設定
    ; ※VDP アドレスポート を書き込む時は di ~ ei で割り込みを禁止
    ld a, ($0007)
    inc a
    ld c, a
    di
    ld a, $00
    out (c), a
    ld a, $18 + $40
    out (c), a
    ei

    ; 画面を ' ' で埋める
    dec c
    ld a, ' '
    ld b, $00
    ld d, $03
ClearLoop:
    out (c), a
    djnz ClearLoop
    dec d
    jnz ClearLoop

    ; VRAM の アクセスアドレス を NameTable の $1989 に書き込みモードで設定
    ; (ココから Hello, World! を書き込めばだいたい中心に描画される)
    inc c
    di
    ld a, $89
    out (c), a
    ld a, $19 + $40
    out (c), a
    ei

    ; NameTable に Hello, World! を書き込む
    dec c
    ld hl, Hello
    ld b, 13
    otir 

    ; プログラム終了 (無限ループしておく)
.End
    jmp End

.Data

Hello:
    defb "Hello, World!"
```

## 4-2. アセンブル & ROM 出力

```bash
# hello.asm をアセンブルして hello.bin を出力
z80asm -b hello.asm

# dd で hello.bin を 32KB にした hello.rom を作成
dd bs=32k conv=sync if=hello.bin of=hello.rom
```

`hello.rom` のダンプはこんな感じです。

```
% hexdump -C hello.rom
00000000  41 42 10 40 00 00 00 00  00 00 00 00 00 00 00 00  |AB.@............|
00000010  31 80 f3 3a 07 00 3c 4f  f3 3e 00 ed 79 3e 58 ed  |1..:..<O.>..y>X.|
00000020  79 fb 0d 3e 20 06 00 16  03 ed 79 10 fc 15 c2 29  |y..> .....y....)|
00000030  40 0c f3 3e 89 ed 79 3e  59 ed 79 fb 0d 21 47 40  |@..>..y>Y.y..!G@|
00000040  06 0d ed b3 c3 44 40 48  65 6c 6c 6f 2c 20 57 6f  |.....D@Hello, Wo|
00000050  72 6c 64 21 00 00 00 00  00 00 00 00 00 00 00 00  |rld!............|
00000060  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
*
00008000
```

## 4-3. WebMSX で実行

以下の手順で実行できます。

1. https://webmsx.org を開く
2. `Cartridge 1` の　`Load ROM Image` で `hello.rom` を読み込む

こんな感じで表示されます。

![スクリーンショット 2021-10-13 23.39.59.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2161417/4b9888c0-90f9-1782-ef5c-253dd334b68c.png)