見出し画像

MSXの開発環境を準備しよう

前回はいきなりVDPとVRAMの説明を行いましたが、多少はMSXの画面仕様について理解いただけたでしょうか?え?わからない?でも、それで良いんです。前提知識を頭に入れておくとプログラミング時に反復してイメージできるようになっていきます。ご安心ください。
さて、今回はMSXの開発環境の準備について説明します。少々長くなりますがおつきあいください。

さて、いちばん最初の記事でさらっと紹介していますが、MSXのマシン語プログラミングでは以下を必要とします。


Z88DK(Z80系マシンのCコンパイラとアセンブラ)

マシン語のプログラミングは以下のようなコードになります。代表例としてAレジスタに数値の10を代入(マシン語では転送と呼びます)するコードです。このように人間でもわかるようなコードをアセンブリ言語と呼びます。

アセンブリ言語でコードを書く→アセンブラでマシン語にする→動かす

という流れです。

; セミコロンから書き始めるとコメント欄となる
Main: ; ラベルをつけれる。ラベルはループやジャンプ、サブルーチン名として使用する
  LD A,10  ; Aレジスタに数値の10を転送
Loop:
  JR Loop ; ラベルLoopにジャンプするラベルを指定するときにはコロンは付けない

LD…という箇所が命令(オペコード)です。LD A, n という命令があります。もちろん命令は他にもたくさんあります。このコードをアセンブル(マシン語に変換)すると次のような数値の連続になります。数値は全て16進表記としています。

3E 0A 18 FE

最初の3EHは LD A, n をマシン語にした結果です。LDが命令ではなく"LD A, n "が命令と思ってください。n の部分は10なので3EHの次に0AHと変換されています。次の18HはコードのJRの部分です。コードとしてはラベルのLoopに戻っているので無限ループのコードです。18Hの次にFEHをつけると無限ループになります。
「はあ〜ん?」という声が聞こえてきそうですが、CPUは数値のON/OFFでしか物事を判別できないため、アセンブリ言語を数値に変換する必要があるのです。なお、命令セットは他にもたくさんあります。(山本様のサイトと渕田様のサイトをご紹介させていただきます)

命令セットがこんなに多岐に渡っていたらオペコードの数値を覚えるのはそれは大変な労力になります。また、ものによっては126バイト前のアドレスにしかジャンプ出来ないといった制約のある命令もあるため、いろんなことを考慮してプログラミングしなければいけません。その労力を減らすためにアセンブラが必要となるわけです。ちなみに私の少年時代は命令とそれに対応する数値をにらめっこしながら手作業でアセンブルしていました。そのしんどさとVRAMが理解できないこととがあいまって勉強を諦めてしまいました・・。

昔話は横において、それではさっそくZ88DKをダウンロードしてアセンブラを手に入れましょう!

Z88DKのインストール

以下のサイトから最新のZ88DKをダウンロードします。

Windowsの場合:

z88dk-win32-latest.zip

macOSの場合:

z88dk-osx-latest.zip

ダウンロードしたらZIPファイルを展開して適当なディレクトリに配置しましょう。WindowsであればC:¥z88dk、macOSなら ~/z88dk などといったディレクトリを作ってそこに展開後のファイル群をポイと置くだけで良いです。
ファイル群を置いたら環境変数を設定しましょう。
WindowsであればPATH環境変数に、C:¥z88dk¥bin を追加。
macOSであれば.zshrcに export PATH=${PATH}:${HOME}/z88dk/bin を追加。
といった感じです。筆者はmacOSですがインストール後はこんな感じになっています。より詳しくはz88dkのInstallationのページを参照してください(英語)

z88dk % ls 
LICENSE			changelog.txt		libsrc			testsuite
Makefile		doc			set_environment.sh	vsbuild.cmd
README.md		examples		snap			win32
azure-pipelines.yml	ext			src			z88dk.Dockerfile
bin			include			support			z88dk_prompt.bat
build.sh		lib			test

プログラムを書いてアセンブルする

といっても・・まだ全然説明していないのでいったんは以下のコードをコピペしてsample001.asmというファイル名で保存してください。

; BIOSルーチン
REDVRM:equ $004A ; VRAMの内容をAレジスタに読み込む
WRTVRM:equ $004D ; VRAMのアドレスにAレジスタの値を書き込む
SETRED:equ $0050 ; VRAMからデータを読み込める状態にする
SETWRT:equ $0053 ; VRAMにデータを書き込める状態にする
FILVRM:equ $0056 ; VRAMの指定領域を同一のデータで埋める
LDIRMV:equ $0059 ; VRAMからRAMにブロック転送する
LDIRVM:equ $005C ; RAMからVRAMにブロック転送する
CHGMOD:equ $005F ; SCREENモードを変更する
SETGRP:equ $007E ; VDPのみをGRAPHIC2モードにする
ERAFNK:EQU $00CC ;ファンクションキーを非表示にする
GTSTCK:equ $00D5 ; JOY STICKの状態を調べる
GTTRIG:equ $00D8 ; トリガボタンの状態を返す
CHGCLR:equ $0111 ; 画面の色を変える
KILBUF:equ $0156 ; キーボードバッファをクリアする

; ワークエリア
LINWID:equ $F3AF ; WIDTHで設定する1行の幅が格納されているアドレス
RG0SAV:equ $F3DF ; VDPレジスタ#0の値が格納されているアドレス
FORCLR:equ $F3E9 ; 前景色が格納されているアドレス
BAKCLR:EQU $F3EA ;背景色のアドレス
BDRCLR:equ $F3EB ; 背景色が格納されているアドレス
CLIKSW:equ $F3DB ; キークリック音のON/OFFが格納されているアドレス
INTCNT:equ $FCA2 ; MSX BIOSにて1/60秒ごとにインクリメントされる値が格納されているアドレス

;--------------------------------------------
; 初期処理(お約束コード)
;--------------------------------------------
; プログラムの開始位置アドレスは0x4000
org $4000

Header:

    ;--------------------------------------------
    ; 初期処理(お約束コード)
    ;--------------------------------------------
    ; MSX の ROM ヘッダ (16 bytes)
    ; プログラムの先頭位置は0x4010
    defb 'A', 'B', $10, $40, $00, $00, $00, $00
    defb $00, $00, $00, $00, $00, $00, $00, $00

Start:

    ;--------------------------------------------
    ; 初期処理(お約束コード)ここから
    ;--------------------------------------------
    ; スタックポインタを初期化
    ld sp, $F380

    ; 画面構成の初期化
    ld a, $0F
    ld (FORCLR), a   ;白色
    ld a, $01
    ld (BAKCLR), a   ;黒色
    ld (BDRCLR), a   ;黒色

    ;SCREEN1,2
    ld a,(RG0SAV+1)
    or 2
    ld (RG0SAV+1),a  ;スプライトモードを16X16に

    ld a, 1          ;SCREEN1
    call CHGMOD      ;スクリーンモード変更

    ld a, 32         ;WIDTH=32
    ld (LINWID), a

    ;ファンクションキー非表示
    call ERAFNK

    ;カチカチ音を消す
    ld a, 0
    ld (CLIKSW), a

    ;--------------------------------------------
    ; 初期処理(お約束コード)ここまで
    ;--------------------------------------------

    ; VRAMを使って画面に文字を表示する
    ; HLレジスタにメモリ(RAM)のアドレス
    ; DEレジスタにVRAMの先頭アドレス
    ; BCレジスタに転送サイズ(バイト)
    ; VRAMにメモリのデータを転送するにはBIOSのLDIRVMを使う

    ld hl, MESSAGE
    ld de, $1800    ; 数値の先頭に$をつけると16進として解釈する
    ld bc, 26       ; メッセージは26バイト
    call LDIRVM
    
End:
    jr End

MESSAGE:
    defm "Let's make an arcade game!"

<大文字?小文字?>
冒頭で説明したコードではアセンブリ言語を大文字で書いてますが、今回のソースコードは小文字です。これ、z88dk的にはどっちでもいいんです。伝統的な書き方は大文字です。情報処理試験のCASLも大文字で表記します。ですが私はShiftキー押しながらタイプするのが面倒なのと最近の言語はほとんど小文字なので小文字で書いてます。あえていうならアドレスや定数のラベルは全部大文字、処理中のラベルは大文字小文字混在って感じで書いてますね。まあ、すきずきです。自分で読みやすいほうでコーディングしてください。

ファイルが保存できたら以下のコマンドでアセンブルします。sample001.asmというファイルをアセンブルするよー。という意味です。
アセンブルに成功するとsample001.binというバイナリファイルが作成されます。

z80asm -b sample001.asm

筆者の環境はmacOSなのですが、macOSにはhexdumpというコマンドが標準でついているのでsample001.binがどうなってるか確認できます。

% hexdump -C sample001.bin
00000000  41 42 10 40 00 00 00 00  00 00 00 00 00 00 00 00  |AB.@............|
00000010  31 80 f3 3e 0f 32 e9 f3  3e 01 32 ea f3 32 eb f3  |1.?>.2??>.2??2??|
00000020  3a e0 f3 f6 02 32 e0 f3  3e 01 cd 5f 00 3e 20 32  |:???.2??>.?_.> 2|
00000030  af f3 cd cc 00 3e 00 32  db f3 21 48 40 11 00 18  |????.>.2??!H@...|
00000040  01 1a 00 cd 5c 00 18 fe  4c 65 74 27 73 20 6d 61  |...?\..?Let's ma|
00000050  6b 65 20 61 6e 20 61 72  63 61 64 65 20 67 61 6d  |ke an arcade gam|
00000060  65 21                                             |e!|
00000062

正常にアセンブルできてるっぽいですね!
アセンブルに失敗するとエラーが出るのでそのエラーの内容を確認してはコードを修正してアセンブルを繰り返す作業になります。
Windowsの場合はcertutilコマンドで内容を確認できるかと思います。

Windowsの場合)
> certutil -encodehex sample001.bin sample001.hex & type sample001.hex

WebMSXで動作を確認する

それでは上記の説明で作成したsample001.binを動かしてみましょう!
当noteではWebMSXを使って説明します。
以下のURLにブラウザでアクセスしてください。

次に画面下の「Cartridge 1」→「Load ROM Image」を選択します。
そうするとファイルを選択するダイアログが表示されるので先ほど作成したsample001.binを選択して「開く」を押してください。
当noteでは常にROMイメージとしてファイルを作成します。MSXカートリッジを作ってる気分でなんだかちょっとわくわくしますね。

「Cartridge 1」→「Load ROM Image」を選択、その後sample001.binを選択して「開く」

動きましたか?

以下のような画面が表示されたら正常に動作しています。
なお、今回のソースコードは画面モードをSCREEN1にして、前景色・背景色を設定するなど初期処理をしたうえで、この文章を表示して無限ループしているだけです。ソースコード上にVRAMという文字があることを意識してください。VRAMのアドレスに文章のデータを転送することで画面に文章が表示されている。ということです。これ重要。

実行結果

以上、かけあしでしたがMSXの開発環境はこれで準備完了です。

アセンブリ言語でプログラムを書いてアセンブラでマシン語にアセンブルしてMSXのROMイメージを作成し、WebMSXで動かす。ということができるようになったはずです。

sample001.asmはGitHubでも公開しているのでコピペするなりなんなりどうぞ。

https://github.com/sailorman-msx/games/blob/main/src/sample001.asm

次回はどちらかというとマシン語よりな説明になると思いますが、今回のコードを題材に何をしているかについて説明します。

では、また!


マシン語講座:マシン語はかく語りき(レジスタとアドレス)

マシン語では LD A, n  がAレジスタへの値の転送であると冒頭で説明しました。レジスタはA、B、C、D、E、H、Lという8ビットの値を転送可能なレジスタとBC、DE、HL、IX、IY、SPという16ビットの値を転送可能なレジスタがあります。それ以外に転送は出来ないけれど「演算結果の状態」を持つFレジスタ(フラグレジスタ)というものもあります。その中でもAレジスタはアキュムレータと呼ばれるものでけっこうなんでもできるレジスタです。
HLレジスタもけっこうなんでも出来るのですがアキュムレータとの違いは私にはわかりません・・。これまた、さらに付け加えると私もまったく用途の判らないRレジスタ(リフレッシュレジスタ)というものもあります。
Rレジスタ以外はプログラミングにおいて頻繁に登場します。
FはRead Onlyなレジスタですが条件分岐とかで使います。このレジスタの役割はとても重要。使いかたは後の記事で説明します。

8ビットは00HからFFHまでの数字なのでそれを超える数値を8ビットレジスタに転送することは出来ません。
16ビットレジスタにはFFFFHまでの値であれば転送することが出来ます。

どんなときに8ビットと16ビットを使い分けるのか?という点ですが、アドレスを指定するときに16ビットレジスタを使います。16ビットレジスタは0000HからFFFFHまで使えます。RAMのデータはアドレス(番地)と呼ばれる場所に1バイトずつ入っています。RAMのアドレスは0000HからFFFFHまでです。箱がずらーっと並んでいてそれが0000HからFFFFHまでの65536個並んでる、とイメージしてください。そして、たとえばRAMの1800Hに値をセットする場合には次のように書きます。(1800H番地に値を転送するとも言います)

LD HL, 1800H   ; 1800Hという16ビットの値をHLレジスタにセット
LD (HL), 10 ; HLレジスタが指し示すアドレスの中に10という数値をセット

ただし、次のようには書けません。

a. LD A, 1800H ; Aレジスタには8ビットの値しかセットできないからNG
b. LD (1800H), 10 ; アドレスの中に値を転送するときにはレジスタを経由しないといけないのでNG
c. LD 1800H, 10 ; アドレスを書き換えることはできないのでNG

(nn)は「nnが指し示すアドレスの中の値」です。
単なる nn はアドレスそのものになります。
上記cはアドレスを書き換えようとしているようなコードです。こういうことは出来ません。

レジスタの値は書き換えできる。
アドレスは書き換え出来ない。
アドレスの中の値(箱の中)は書き換え出来る。
ただし、アドレスの中の値はレジスタ経由でのみ書き換え出来る。

だいたい、そんなイメージを持ってもらえればOKです。
おおよそ暴走するのはアドレス操作まわりが原因となることがほとんどです。これ、わかってるつもりでも慣れるまでそこそこ時間がかかりますのでご注意ください。

セーラー服が似合うおじさんです。猫好き、酒好き、ガジェット好き、楽しいことならなんでも好き。そんな「好き」をつらつらと書き留めていきます。