Z80 アセンブリ入門

ようこそ、Z80 アセンブリ言語の世界へ!この記事では、かつて多くのパーソナルコンピュータやゲーム機で活躍し、今なお組み込みシステムなどで利用される伝説的な 8 ビット CPU「Z80」のアセンブリプログラミングの基本を解説します。レトロコンピューティングに興味がある方、CPU の動作原理を深く理解したい方、組み込みシステム開発の基礎を学びたい方におすすめです。さあ、一緒に Z80 の魅力を探求しましょう!

Z80 とは?

Z80 は、1976 年にアメリカのザイログ (Zilog) 社によって開発・発表された 8 ビットマイクロプロセッサです。インテル (Intel) 社の 8080 CPU の開発者の一部がザイログに移籍して設計したため、8080 とソフトウェアレベルで高い互換性を持っています。

8080 と比較して、Z80 は +5V の単一電源で動作可能、DRAM(ダイナミックRAM)のリフレッシュ機能内蔵、より豊富な命令セット、インデックスレジスタや裏レジスタ(後述)の追加といった改良が施されていました。これにより、回路設計が簡略化でき、より高性能なプログラムを作成しやすくなったため、1970 年代後半から 1980 年代にかけて、ホビーパソコン(日本ではシャープ MZ シリーズ、NEC PC-8000/8800 シリーズなど)、ビジネス用コンピュータ、アーケードゲーム(パックマンなど)、家庭用ゲーム機(セガ SG-1000/マスターシステムなど)、さらには組み込みシステムまで、非常に幅広い分野で採用され、一時代を築きました。

クロック周波数は、初期の 2.5MHz から、Z80A (4MHz)、Z80B (6MHz)、Z80H (8MHz)、CMOS 版の高速なもの (最大 20MHz) まで様々なバリエーションが存在します。また、ザイログだけでなく、モステック (Mostek)、シャープ、NEC、東芝、ローム (ROHM) など多くのセカンドソースメーカーによって互換チップが製造されたことも、普及を後押ししました。

21 世紀に入っても、組み込み用途や、FPGA/ASIC 上の IP コアとして利用され続けています。残念ながら、ザイログによるオリジナルの Z80 チップは 2024 年 4 月に生産終了が発表されましたが、流通在庫やライセンス品はまだ存在するため、当面は入手可能です。

Z80 のアーキテクチャ

アセンブリプログラミングを始める前に、Z80 が内部にどのような記憶領域(レジスタ)を持っているかを知る必要があります。レジスタは CPU 内部にある高速な記憶場所で、計算やデータの移動に直接使われます。

主要なレジスタ

Z80 の主要なレジスタは以下の通りです。

レジスタ名 サイズ 役割
A (アキュムレータ) 8 ビット 算術演算や論理演算の中心となるレジスタ。演算結果は通常ここに格納されます。
F (フラグレジスタ) 8 ビット 演算結果の状態(ゼロか、負か、桁上げが発生したかなど)を示すフラグを格納します。条件分岐などで使われます。
B, C, D, E, H, L 各 8 ビット 汎用レジスタ。データの一時的な保存などに使えます。
BC, DE, HL 各 16 ビット 上記の汎用レジスタを 2 つ組み合わせた 16 ビットレジスタペアとしても利用できます。特に HL ペアはメモリアドレスの指定(ポインタ)によく使われます。
IX, IY 各 16 ビット インデックスレジスタ。ベースアドレスにオフセットを加えたメモリアドレスを指定するのに便利です。配列や構造体のアクセスに使われます。
SP (スタックポインタ) 16 ビット メモリ上のスタック領域の先頭アドレスを指します。サブルーチン呼び出し時の戻りアドレスや、レジスタの一時退避に使われます。
PC (プログラムカウンタ) 16 ビット 次に実行する命令が格納されているメモリアドレスを指します。命令が実行されるたびに自動的にインクリメントされます。
I 8 ビット 割り込みベクタレジスタ。割り込みモード 2 で使用され、割り込み処理ルーチンのアドレスの一部を指定します。
R 8 ビット メモリリフレッシュカウンタ。DRAM のリフレッシュ動作に使われます。通常、プログラマが直接操作する必要はありません。

裏レジスタ (Alternate Registers)

Z80 の特徴的な機能として、「裏レジスタ」があります。これは、主要なレジスタ群 (AF, BC, DE, HL) と全く同じ構成のもう一つのレジスタセット (AF’, BC’, DE’, HL’) です。EX AF, AF'EXX という命令を使うことで、表レジスタと裏レジスタの内容を瞬時に入れ替えることができます。これにより、割り込み処理などでレジスタの値を一時的に退避・復帰させる手間を大幅に削減でき、高速な処理が可能になります。

フラグレジスタ (F) の詳細

フラグレジスタ F は 8 ビットですが、各ビットが特定の意味を持っています。

ビット 記号 名称 意味 (1 のとき)
7 S サインフラグ 演算結果が負 (最上位ビットが 1)
6 Z ゼロフラグ 演算結果がゼロ
5 (未使用)
4 H ハーフキャリーフラグ 演算結果の下位 4 ビットから上位 4 ビットへの桁上げ (またはボロー) が発生 (BCD 演算用)
3 (未使用)
2 P/V パリティ/オーバーフローフラグ 演算結果のパリティが偶数 (論理演算) または 演算結果がオーバーフローした (算術演算)
1 N 減算フラグ 直前の演算が減算だった (BCD 演算用)
0 C キャリーフラグ 演算結果で桁上げ (またはボロー) が発生

これらのフラグは、算術演算 (ADD, SUB など) や論理演算 (AND, OR, XOR など) の結果に応じて自動的にセットまたはリセットされます。そして、条件ジャンプ命令 (JP, JR) や条件コール命令 (CALL)、条件リターン命令 (RET) が、これらのフラグの状態を見て処理の流れを分岐させます。

アセンブリ言語の基本

アセンブリ言語は、CPU が直接理解できる「機械語 (マシン語)」と一対一に近い対応を持つ低水準言語です。機械語は単なる数値 (0 と 1 の羅列) ですが、アセンブリ言語ではそれを人間が理解しやすいように、命令に「ニーモニック (Mnemonic)」と呼ばれる短い英単語 (例: LD, ADD, JP) を割り当て、操作対象となるデータやメモリアドレス (オペランド) を記述します。

例えば、「レジスタ B に値 10 を入れる」という操作は、以下のように書きます。

LD B, 10

ここで、LD がニーモニック (Load = 読み込む、代入する)、B が第一オペランド (操作対象のレジスタ)、10 が第二オペランド (代入する値) です。

アセンブリ言語で書かれたプログラム (ソースコード) は、「アセンブラ」と呼ばれるツールによって機械語のプログラム (オブジェクトコード) に変換 (アセンブル) されて、初めて CPU で実行できるようになります。

Z80 の主な命令

Z80 には多数の命令がありますが、ここでは基本的なものをいくつか紹介します。

分類 ニーモニック例 機能 簡単な説明
データ転送 LD r, r' レジスタからレジスタへ 例: LD A, B (レジスタ B の内容を A へコピー)
LD r, n 即値をレジスタへ 例: LD C, 55h (レジスタ C に 16 進数の 55 を代入) ※hは16進数を示す
LD r, (HL) メモリからレジスタへ HL レジスタペアが指すメモリアドレスの内容をレジスタ r へ読み込む
LD (HL), r レジスタからメモリへ レジスタ r の内容を HL レジスタペアが指すメモリアドレスへ書き込む
算術演算 ADD A, r 加算 A レジスタの内容にレジスタ r の内容を加算し、結果を A へ格納
SUB r 減算 A レジスタの内容からレジスタ r の内容を減算し、結果を A へ格納
INC r インクリメント レジスタ r の内容を 1 増やす
DEC r デクリメント レジスタ r の内容を 1 減らす
論理演算 AND r 論理積 A レジスタの内容とレジスタ r の内容のビットごとの論理積をとり、結果を A へ格納
OR r 論理和 A レジスタの内容とレジスタ r の内容のビットごとの論理和をとり、結果を A へ格納
XOR r 排他的論理和 A レジスタの内容とレジスタ r の内容のビットごとの排他的論理和をとり、結果を A へ格納
比較 CP r 比較 A レジスタの内容とレジスタ r の内容を比較し、結果をフラグレジスタに反映 (A の内容は変化しない)
CP n 即値と比較 A レジスタの内容と即値 n を比較し、結果をフラグレジスタに反映
CP (HL) メモリの内容と比較 A レジスタの内容と HL が指すメモリの内容を比較し、結果をフラグレジスタに反映
分岐 (ジャンプ) JP nn 無条件ジャンプ 指定したアドレス nn へジャンプ
JP cc, nn 条件付きジャンプ 指定した条件 cc (例: Z=ゼロ, NZ=非ゼロ, C=キャリー) が満たされた場合、アドレス nn へジャンプ
JR e 相対ジャンプ 現在の場所から相対的に e バイト (-128~+127) だけ離れた場所へジャンプ
JR cc, e 条件付き相対ジャンプ 指定した条件 cc が満たされた場合、相対的に e バイト離れた場所へジャンプ
サブルーチン CALL nn サブルーチン呼び出し 戻りアドレスをスタックに保存し、アドレス nn へジャンプ
RET リターン スタックから戻りアドレスを取り出し、そこへジャンプしてサブルーチンから戻る
スタック操作 PUSH qq プッシュ レジスタペア qq (AF, BC, DE, HL, IX, IY) の内容をスタックに保存
POP qq ポップ スタックからデータを取り出し、レジスタペア qq に格納
入出力 IN A, (n) 入力 I/O ポートアドレス n からデータを読み込み、A レジスタへ格納
OUT (n), A 出力 A レジスタの内容を I/O ポートアドレス n へ出力
注意点:
  • r は 8 ビットレジスタ (A, B, C, D, E, H, L) を表します。
  • n は 8 ビットの即値 (0~255 または 00h~FFh) を表します。
  • nn は 16 ビットのアドレスまたは即値 (0~65535 または 0000h~FFFFh) を表します。
  • (HL) は HL レジスタペアが示すメモリアドレスの内容を表します (間接アドレッシング)。
  • qq は 16 ビットレジスタペア (BC, DE, HL, AF, IX, IY, SP) を表します (命令によって使えるペアが異なります)。
  • e は相対ジャンプのオフセット値 (-128~+127) を表します。
  • cc は条件コード (Z, NZ, C, NC, PE, PO, M, P など) を表します。
  • 数値の後ろの h は 16 進数 (Hexadecimal) を示す慣例的な記法です。

簡単なプログラム例: 2つの数値の加算

実際に簡単な Z80 アセンブリプログラムを見てみましょう。ここでは、メモリ上の 2 つの 8 ビット数値を読み込んで加算し、結果を別のメモリ場所に書き込む例を示します。

; Z80 アセンブリ プログラム例: 2つの数値の加算

ORG 8000h     ; プログラムの開始アドレスを 8000h に指定

; --- データ定義 ---
DATA1: DB 12h       ; メモリアドレス DATA1 に 16進数 12 を格納 (DB = Define Byte)
DATA2: DB 34h       ; メモリアドレス DATA2 に 16進数 34 を格納
RESULT: DB 0        ; 結果を格納するメモリ場所 RESULT を確保し 0 で初期化

; --- メインプログラム ---
START:
    LD HL, DATA1    ; HL レジスタに DATA1 のアドレスをロード
    LD A, (HL)      ; HL が指すメモリ (DATA1) の内容 (12h) を A レジスタにロード

    LD HL, DATA2    ; HL レジスタに DATA2 のアドレスをロード
    LD B, (HL)      ; HL が指すメモリ (DATA2) の内容 (34h) を B レジスタにロード

    ADD A, B        ; A レジスタの内容 (12h) に B レジスタの内容 (34h) を加算
                    ; 結果 (46h) は A レジスタに格納される

    LD HL, RESULT   ; HL レジスタに RESULT のアドレスをロード
    LD (HL), A      ; A レジスタの内容 (46h) を HL が指すメモリ (RESULT) に書き込む

    HALT            ; プログラムの実行を停止

END START       ; プログラムの終了 (アセンブラへの指示)

コード解説

  • ; (セミコロン): これ以降の行末まではコメントとして扱われ、プログラムの動作には影響しません。
  • ORG 8000h: プログラムがメモリ上のどこから始まるかをアセンブラに指示する疑似命令です (Origin)。ここでは 16 進数で 8000 番地を指定しています。
  • DATA1:, DATA2:, RESULT:, START:: これらはラベルです。メモリ上の特定のアドレスやプログラム中の特定の場所に名前を付けます。これにより、アドレスを直接数値で指定する代わりに、分かりやすい名前で参照できます。
  • DB: Define Byte の略で、1 バイトのデータを定義する疑似命令です。DB 12h は、その場所に 16 進数の 12 を格納します。
  • LD HL, DATA1: HL レジスタペアに、ラベル DATA1 が示すメモリアドレスそのものを代入します。
  • LD A, (HL): HL が指しているアドレス (ここでは DATA1 のアドレス) に格納されている内容 (12h) を A レジスタに読み込みます。() が間接アドレッシングを示します。
  • ADD A, B: A レジスタと B レジスタの内容を加算し、結果を A レジスタに格納します。
  • LD (HL), A: A レジスタの内容を、HL が指しているアドレス (ここでは RESULT のアドレス) に書き込みます。
  • HALT: CPU の動作を停止させる命令です。
  • END START: プログラムのソースコードの終わりと、プログラムの実行開始点をアセンブラに伝える疑似命令です。

このプログラムをアセンブルして実行すると、メモリの RESULT 番地には 12h + 34h の結果である 46h が格納されることになります。

開発環境

Z80 アセンブリプログラミングを行うには、以下のものが必要になります。

  1. テキストエディタ: ソースコードを書くためのエディタ。
  2. Z80 アセンブラ: ソースコードを機械語に変換するツール。
    • 例: ZASM, z80asm (z88dk に付属), ASxxxx Cross Assemblers (asl), pasmo など。
    • OS (Windows, macOS, Linux) 上で動作するクロスアセンブラを使うのが一般的です。
  3. エミュレータまたは実機: 作成したプログラムを実行し、デバッグ (間違い探しと修正) するための環境。
    • PC 上で Z80 搭載マシンを再現するエミュレータが多数存在します (例: OpenMSX, Fuse (ZX Spectrum), WinApe (Amstrad CPC), MAME (アーケード) など)。
    • あるいは、実際の Z80 搭載コンピュータや自作の Z80 マイコンボードを使用することもできます。

具体的な環境構築方法は、対象とするシステム (MSX, ZX Spectrum など) や使用する OS によって異なります。Web 上で「Z80 開発環境 構築」や「(対象システム名) アセンブラ 使い方」などのキーワードで検索すると、多くの情報が見つかります。

最近では、Visual Studio Code などの高機能エディタに Z80 アセンブリ用の拡張機能を入れることで、シンタックスハイライトやデバッガ連携などが可能になり、より快適に開発を進められる環境もあります。

学習リソースと次のステップ

Z80 アセンブリの世界は奥深く、この記事で紹介したのはほんの入り口にすぎません。さらに学習を進めるためのリソースをいくつか紹介します。

  • 書籍: Z80 アセンブリに関する入門書や解説書が過去に多数出版されています。古書店やオンラインで探してみると良いでしょう (例: 「Z80 アセンブラ入門」「Z-80 アセンブラプログラミング入門」など)。
  • Web サイト: Z80 の命令セットやプログラミングテクニックを解説している Web サイトが多数あります。
  • コミュニティ: レトロコンピューティングや Z80 に関するフォーラムや SNS グループに参加して、情報交換や質問をしてみるのも良いでしょう。

次のステップとしては、以下のようなことに挑戦してみてはいかがでしょうか。

  • 簡単な計算プログラム (乗算、除算など) を書いてみる。
  • 画面に文字や図形を表示させてみる。
  • 簡単なゲームを作ってみる。
  • エミュレータだけでなく、実機や FPGA 上で Z80 を動かしてみる。

アセンブリ言語の学習は、コンピュータがどのように動作しているかを根本から理解する上で非常に役立ちます。時間はかかるかもしれませんが、一つ一つの命令を理解し、プログラムが動いた時の達成感は格別です。ぜひ、Z80 アセンブリプログラミングを楽しんでください!

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です