UbuntuでZ80アセンブラを書く

選択肢は色々あるようですが、次のサイトの zasm というZ80専用アセンブラを使うことにしました。

オプションでCPUサイクル付きのリストが出せるのが面白そうです。導入方法は、Archiveから実行バイナリをダウンロードして動くならそれが一番簡単です。私の環境(Ubuntu 16.04.1 LTS 64bit)は zasm-4.0.15-Linux64.zip が問題なく使えそうですが、一応ビルドも試したので、手順をメモしておきます。

$ cd ~/workshed/gitprojects /* 適当な作業フォルダ */
$ git clone http://k1.spdns.de/Git/zasm-4.0.git
$ git clone http://k1.spdns.de/Git/Libraries.git
$ cd zasm-4.0
$ ln -nfs ../Libraries Libraries
$ cd Linux
$ make
$ sudo cp zasm /usr/local/bin

早速、簡単なプログラムをアセンブルしてみます。

        org     0000h
        ld      a,0
INC:    inc     a
        jp      nz,INC
LOOP:   jp      LOOP

これを test.asm として保存して…

$ zasm -uwy test.asm
assemble: 6 lines
time: 0.0202 sec.
zasm: no errors
$ 

問題なさそうです。test.lst と test.rom ができています。.rom が機械語バイナリで、これをROMに焼いてZ80に実行させます。使ったオプションの意味は次の通り(作者のオススメ組み合わせっぽい)

  • -u ... オブジェクトコード(機械語を16進数表記したもの)を .lst に含める
  • -w ... ラベルのリストを .lst に含める
  • -y ... CPUのクロックサイクルを .lst に含める

では test.lst がどうなっているか見てみましょう。

$ cat test.lst
                        ; --------------------------------------
                        ; zasm: assemble "test.asm"
                        ; date: 2016-11-03 03:26:01
                        ; --------------------------------------


0000:                           org     0000h
0000: 3E00     [ 7]             ld      a,0
0002: 3C       [ 4]     INC:    inc     a
0003: C20200   [14|14]          jp      nz,INC
0006: C30600   [10]     LOOP:   jp      LOOP


; +++ segments +++

#CODE :        start=0     len=9

; +++ global symbols +++

INC     = $0002 =      2          test.asm:3
LOOP    = $0006 =      6          test.asm:5


total time: 0.0199 sec.
no errors
$

例えば ld a,03E 00 という機械語アセンブルされています。間にある数字がその行の命令の実行時間(クロック数)のようです。これ、Z80を手動クロックで動かす実験をしたおかげでよく理解できます。まず、各命令は必ずM1サイクルから始まります。ここでオペコード(命令の種類を示す)をメモリから読み出して命令の実行準備を整えるのですが、基本4クロックかかります。その後、命令によって次のどれかのサイクルに進みます。

  • M1のうちに実行完了(5クロック以上かかることもある)
  • もう1回M1サイクル(Z80で追加された新しい命令)
  • メモリ・リード・サイクル(3クロック)
  • メモリ・ライト・サイクル(3クロック)
  • I/O・リード・サイクル(4クロック)
  • I/O・ライト・サイクル(4クロック)
    • メモリ・I/Oサイクルは必要数繰り返す

ld a,0 なら、3E で「アキュムレータ(Aレジスタ)に値をロードする」という命令を判別するのがM1サイクルで4クロック、ロードする値 00 をメモリ・リード・サイクルで取ってくるのに3クロック使って計7クロック。inc a3C だけで「アキュムレータの値を1増やす」と判別して実行するのでM1サイクルの4クロックで終了、という具合です。

jp LOOP ではジャンプ先アドレスを2バイトで指定するため、メモリ・リード・サイクルが2回必要です。つまり4+3+3=10クロック必要。残った jp nz,INC[14|14] は、分岐ジャンプでは分岐する時としない時でクロック数が変わる場合があるためこのような表記になっているのだと思いますが、14という数字自体が不思議です。手元の本でもググってみても jp nz,nn[10|10] となっています。なにか必要な設定が抜けてるのだろうか…。とりあえず、吐かれるバイナリ自体は間違ってないようなのでよしとしますか。この点も確認しときましょう。

$ od -t x1 test.rom
0000000 3e 00 3c c2 02 00 c3 06 00
0000011
$

オッケー!

というわけで、遂にZ80のシステムを実際に組んでいきます! まずは、CPU・ROM・PIOの最小構成を試す予定。

追記

jp nz,INC のクロックサイクルは [10|10] じゃないの?問題について。test.asm にちょっと手を加えてアセンブルしてみたら [14|14] の理由がわかりました。以下、リストファイルの抜粋

0000:                           org     0000h
0000: 3E00     [ 7]             ld      a,0
0002: 3C       [ 4]     INC:    inc     a
0003: 3C       [ 8]             inc     a
0004: 3C       [12]             inc     a
0005: 3C       [16]             inc     a
0006: 3C       [20]             inc     a
0007: 3C       [24]             inc     a
0008: 3C       [28]             inc     a
0009: 3C       [32]             inc     a
000A: C20200   [42|42]          jp      nz,INC
000D: C30D00   [10]     LOOP:   jp      LOOP

カッコ内の数字はその行の実行サイクルではなくて、ラベル間の実行サイクルを足し合わせながら出力される値のようです。