情報・生体工学実験I

機械語プログラミング

最終更新日:2021年6月30日(担当:渕田 fuchida@ibe.kagoshima-u.ac.jp)

目次

  1. 目的
  2. 実験方法
  3. 機械語とは
  4. コンピュータの仕組み
  5. レジスタによる計算
  6. メモリの利用
  7. 繰り返しと分岐
  8. 間接アドレッシング
  9. I/O処理
  10. レポート課題

目的

コンピュータの頭脳とも言われるCPUが直接実行できるプログラムは機械語と呼ばれています。

近年はC++やJavaのような高級言語が広く普及し簡単に利用できるため、直接機械語でプログラムを組む必要性はかなり少なくなりました。

しかし、ハードウェアに極めて近いソフトウェア(組み込み系のソフトや制御用のソフトなど)を作る場合は、まだ機械語でプログラムを組むような場面もないとは言えません。

また、高級言語でプログラムを組む場合でも、コンパイルされて最終的には機械語に変換されて実行されるわけですから、機械語の仕組みを理解しておくことは決して無駄というわけではありません。

この実験では、Z80という代表的な8ビットCPUをターゲットとして、機械語のプログラミングとはどんなものなのか、その基本を理解することを目的とします。

実験方法

この実験は個人で行います。

z80editというZ80 CPUのシミュレータを使用して、以下の説明に沿って実験を行うこと。(z80editの使い方は後述します)

※このソフトはMacOSやLinuxでは動作しません。その場合は大学の演習室を利用 してください。どうしてもz80editが利用できない場合はここを参照してください。

レポートについて

レポートはManabaから提出すること。

質問について

質問は基本的にメールで受け付けます。

機械語とは

CPU内部ではすべての信号は電圧が高いか低いかによって表現されるため、それは2進数の表現となります。したがって、機械語は、

00111110 11001101 00110010 00000000 00000001 01110110

のような形で表されるわけです。

しかし、このような表現は、CPUにとっては極めて実行しやすいものですが、人間にとってはかなり理解しにくい形です。

2進数よりは16進数の方がわかりやすい(かな?)ということで、上記のプログラムを16進数に変換すると、

3E CD 32 00 01 76

となります。(2進→16進の変換はできますか?、基本ですよ)

が、やっぱり意味はわかりませんね。

実は機械語とは数字の羅列に過ぎません。したがって、それ自体には意味はないのです。

意味は、そのプログラムをどのCPUが実行するのか、によって決まってきます。

CPUには、そのCPUで実行可能な命令のセットがあらかじめ準備してあり、それを組み合わせてプログラムを作ります。

もし、このプログラムがZ80というCPUの機械語だとすると、これには次のような意味がつけられています。(CPUが変われば意味も変わります。CPUが違うとプログラムが実行できないのはこのためです)

3E - 次の1バイト(ここではCD)をAレジスタに格納せよ
CD - データの数値
32 - 次の2バイト(ここでは0001)の番地にAレジスタの値を格納せよ
(ただし、Z80では2バイトの数値は「逆ワード」によって表現されるので、上の場合 0001 と書かれていれば 0100 番地に書き込まれることになります。)
00 - データの数値
01 - データの数値
76 - プログラムを停止せよ

数値はすべて16進数です。

青で示したのはZ80の命令として解釈される数値です。それ以外は単なるデータです。機械語では、命令とデータはどちらも単なる数値ですから、プログラムをどこから開始するかによって同じ数値が命令になるかデータになるかが変わります。もしCDが命令として解釈されたとすれば、CALLというサブルーチン呼出し命令になります。(Z80の主要命令表はこちら

ということで、このプログラムは、

CD(=10進数では205)をAレジスタに入れ、それを0100番地に入れよ

という意味になるのです。

Aレジスタ?、メモリの番地?、などと思った人は次も読んでください。

コンピュータの仕組み

CPUはコンピュータの頭脳である、と言われますが、CPUだけでコンピュータが動いているわけではありません。コンピュータがきちんと機能するためには、CPUの周りにある周辺装置も重要です。

詳しい話は「計算機工学」で勉強してもらうとして、ここではざっくりとコンピュータとCPUの仕組みを説明します。

コンピュータの構造

下の図は一般的なコンピュータの構造を示しています。

メモリとは「主記憶」と呼ばれる記憶装置であり、周辺機器はハードディスクやキーボード、マウス、グラフィックスカードなど、メモリ以外のさまざまなパーツのことです。周辺機器はI/O(Input/Output)とも呼ばれます。

CPUとメモリやI/Oとは、アドレスバスとデータバスによってつながっています。また、いくつかの制御信号もつながっています。

それらは基本的には、アドレスで番地を指定し、その番地のデータを読んだり、その番地にデータを書いたりします。

操作対象がメモリなのかI/Oなのかは制御信号によって指定しますが、機械語のプログラミングの場合は使用する命令が違いますから、どのようにして切り替わっているかを意識する必要はありません。

メモリはデータを記憶しておくための装置であり、通常は、実行中のプログラムやデータはここに記憶されています。

I/Oにはさまざまなタイプがあります。

入力デバイスとしてはキーボードやマウス、カメラなどがあります。このような機器に接続されたI/Oのアドレスを指定してデータを読むと、その入力装置からのデータが読み込めます。

出力デバイスとしてはディスプレイ(グラフィックスカード)やスピーカー(サウンドカード)などがあります。このような機器に接続されたI/Oのアドレスを指定してデータを書くと、その出力装置に何かが出力されるわけです。

またハードディスクやUSBメモリなどは入力にも出力にも使われるI/O機器です。このようなI/Oの場合、書き込みと読み込みの両方を行うことができます。

コンピュータは、メモリの上にプログラムをおき、それを読み書きして実行しながら、必要に応じてI/Oと通信して入出力デバイスからデータを読んだり書いたりしているのです。これによって、キーボードやマウスでコンピュータにいろいろな指示を出したり、結果をディスプレイやスピーカーで確認できるというわけです。

CPUの仕組み

次に、CPUの内部の仕組みを簡単に説明しましょう。

ここでは、機械語プログラミングに必要な部分に限って説明します。

上の図はZ80というCPUの内部構造を表しています。

この中で、機械語のプログラミングを行う上で扱わなければならないのは赤色で示した3つの部分です。

レジスタとはCPU内部の記憶装置のことであり、外部にあるメモリよりさらに高速にアクセスが可能です。メモリやI/Oから読み込んだデータはレジスタに記憶しておき、必要に応じてさまざまな処理に使われます。

レジスタセットはCPUによって異なっており、Z80の持つレジスタは全部で次の21個あります。

Aレジスタはアキュムレータとも呼ばれ、加算や論理積などの演算はこのレジスタの中のデータに対して行われます。言い換えれば、演算を行いたい場合は、Aレジスタに格納しておく必要があります。

Fレジスタはフラグレジスタと呼ばれ、演算結果によって状態が自動的に変化する特別なレジスタです。このレジスタの状態を使って処理を分岐させたりします。これは、いわゆるIF文の実現です。フラグレジスタは8ビットの大きさを持ち、各ビットは次の意味を持っています。

S : サイン(符号)フラグ。演算結果が正(プラス)であれば0に、負(マイナス)であれば1になる

Z : ゼロフラグ。演算結果が0ならば1に、そうでなければ0になる

X : 未使用

H : ハーフキャリーフラグ。演算時に下位4ビットから上位4ビットに桁上がりがあれば1に、なければ0になる

P/V : パリティ/オーバーフローフラグ。論理演算の結果では演算結果の中での1のビットの数(パリティ)が偶数なら1に、奇数なら0になる。算術演算の結果ではオーバーフローがあれば1に、なければ0になる。

N : サブトラクト(減算)フラグ。直前に実行された命令が減算命令ならば1に、そうでなければ0になる

C : キャリーフラグ。演算の結果、桁上がり(または桁借り)が発生すれば1に、そうでなければ0になる

この中で特に重要なフラグはゼロフラグとキャリーフラグです。この実験ではゼロフラグしか使いません。

BレジスタからLレジスタまでは汎用レジスタと呼ばれ、データの一時的な記憶やカウンタなど、さまざまな目的で利用されます。プログラムを実行する上での変数のようなものだと思えばよいです。BとC、DとE、HとLはそれぞれ組み合わせて16ビットレジスタとしても使用することができます。メモリのアドレスを指定する場合などに使われます。

Iレジスタはインタラプトレジストといって、割り込みを処理するときに使用されますが、ここでは省略。

Rレジスタはリフレッシュレジスタといって、DRAM(ダイナミックRAM)のリフレッシュに使用されますが、これも省略。

IXとIYレジスタはインデックスレジスタといって、アドレスを指定する際に便利なレジスタです。このレジスタにはディスプレースメントと呼ばれる-128から+127までの数値を指定することができ、ある番地を基準にして相対的なアドレス指定を行うことができます。が、この実験では使用しません。

SPはスタックポインタです。スタックとはちょうど生協食堂のトレイ置き場のような概念で、データを積んでおく場所と考えられます。PUSHとPOPという2つの命令がスタックを使用します。PUSH命令ではスタックにデータを積みます。POP命令でスタックからデータを取り出します。

プログラムカウンタ(PC)は、メモリ上の現在実行中のアドレスを保持しているレジスタです。(これもレジスタですが、構造上分けました)

ALUとはArithmetic Logical Unitの略で、日本語では算術論理演算装置と訳されます。CPU内部で必要となる論理演算と算術演算を実行します。可能な演算は、加算、減算、論理積、論理和、比較、シフト・ローテートなどたくさんあります。Z80では、アキュムレータのデータをその他のレジスタのデータを演算し、結果をアキュムレータに戻します。

このように、CPUは、外部のメモリやI/Oからデータを読み、それを必要に応じて演算して、メモリやI/Oに書き出す、という処理を行う装置であるといえます。

ニーモニックについて

機械語は、たとえ16進数で表したとしても、人間にはとても理解しにくい形をしています。

そこで、人間にわかりやすい形で表現するためにニーモニックという表記が使われます。

ニーモニックは、機械語の命令の1つ1つに名前をつけて、読んで意味がわかりやすくしたコードであり、機械語と1対1に対応します。

たとえば、機械語の3Eという命令は、ニーモニックでは LD A,n と記述されます。この意味は、

Aレジスタに、数値nをロード(LD)せよ

ということです。この命令を実行すると、Aレジスタに指定した値が設定されます。

ニーモニックで書かれた機械語プログラムは、16進数表記と比較すると格段に読みやすくなります。

しかし、ニーモニックは直接CPUが実行することはできないので、機械語に翻訳する必要があります。この作業をアセンプルといい、この作業をやってくれるプログラムをアセンプラといいます。

また、ニーモニックから手作業で機械語コードを導く作業はハンドアセンブルと呼ばれます。

Z80の主要な命令のニーモニックをここに示します。

また、より詳細なインストラクションセットの説明はここにあります。参考にしてください。

レジスタによる計算

まず最初の実験として、2つの整数の和を求める簡単な機械語のプログラムを作ります。

まず、作業用フォルダを準備します。Z:ドライブのどこかに(実験2のフォルダなど)、作業フォルダを作成します。名前は何でも良いですが、ここでは "z80" としましょう。

なおこれ以降、ニーモニックにおける16進数の数値には、最後に H をつけて10進数と区別することにします。H は Hexadecimal の頭文字です。また、16進数の最初の文字がアルファベットの場合、先頭に0を付加することにします。

ただし、機械語のコードには、この H はつけません。

Z80シミュレータ z80edit

z80editは、植木友浩氏が作成、公開をしているフリーのZ80シミュレータです。(現在は作成終了)

このソフトは内部にニーモニックエディタ、アセンプラ、シミュレータを備えたオールインワンな設計で、Z80の機械語を勉強するにはとても良い環境を提供してくれます。

上のリンクはVectorの配布サイトです。ここからインストーラーをダウンロードして適当なフォルダにインストールしてください。

Z80シミュレータを起動すると、次の画面が現れます。

残念なことに、このソフトには使用法の説明などが付随していません。(「ヘルプ」メニューはありますが機能しません)

使い方は実験を進めていく過程で説明します。

このソフトはMacOSやLinuxでは動作しません。この場合は、大学の演習室を利用してください。 どうしてもz80editが利用できない場合はここを参照してください。

z80editを使う上での注意

z80editは良くできたソフトウェアですが、個人がフリーソフトとして作っているものですので、動作がおかしくなる場合もあります。

現在までにわかっている誤動作とその対策について、ここにまとめておきます。

2つの数値の加算

Z80シミュレータ z80edit に慣れるため、機械語の簡単なプログラムの作成と実行をしてみます。

ここでは、レジスタを使って 13H + 28H = 3BH の足し算を行ってみましょう。

以下、z80edit の画面を示しながら説明をします。

ニーモニックコードの作成

z80editを起動し、「ファイル」→「新規作成」で新しいエディタ画面を開き、次のように入力します。

擬似命令とは、機械語の命令ではなくアセンブラに指示するための命令です。ORGはプログラムの開始番地を指定します。ここで指定した番地にプログラムがおかれます。またENDはプログラムの終端を示すための擬似命令です。

LD命令はLOADの略で、レジスタなどに数値(ここでは13H)を入れる命令です。

ADD命令は加算命令で、今の場合はAレジスタに数値(ここでは28H)を足して、結果をAレジスタに入れます。

HALT命令はCPUの動作を停止します。

また、セミコロン(;)より右はコメントであり、何を書いてあってもアセンブラは無視します。(ということは書かなくても大丈夫です)

入力が終わったら、「ファイル」→「保存」で名前をつけて保存します。ファイル名は "addnum1.asm" とします。

アセンブル

ニーモニックコードができたら、アセンブルします。

「編集」→「アセンブル」を選ぶと次の画面が現れます。

ここで、左のボタンを押すとアセンブルが開始され、エラーがなければ次のようにアセンブルされたコードが表示されます。

もしエラーが表示された場合は、ニーモニックコードをもう一度見直しましょう。

シミュレータで実行

実行には2種類があります。

ここでは、ステップ実行を行ってみます。

「シミュレーション」→「ステップ実行」を選びます。次の3つのウィンドウが現れます。

ステップ実行ウィンドウはプログラムを1行ずつ実行するためのウィンドウです。右矢印をクリックすると1行進み、左矢印をクリックすると1行戻ります。

メモリダンプウィンドウはプログラムを実行中のメモリの状態を表示します。ただし、このウィンドウはプログラムコードは表示されないようです。シミュレータ上のプログラムが使用可能なメモリ領域は8000H~8FFFHの4KBのみです。

レジスタウィンドウにはプログラム実行中のレジスタの状態を表示されます。これは2進数、10進数、16進数の3通りの表示形式が選べます。

さらにレジスタウィンドウの上側には、8セグメント8桁LEDと8個のLEDの表示部があり、プログラムからこれらの点灯状態を制御することができます。この方法については後から説明します。

さて、ステップ実行ウィンドウの右矢印をクリックして1行ずつ実行してみましょう。

次のようにレジスタの値が変化するかを確認してください。

アドレス 命令 実行後のAレジスタ
1   ORG 8000H 00000000 (00H)
2     00000000 (00H)
3 8000H LD A,13H 00010011 (13H)
4 8002H ADD A,28H 00111011 (3BH)
5 8004H HALT 00111011 (3BH)
6     00111011 (3BH)
7   END 00111011 (3BH)

ということで、Aレジスタに 13H + 28H = 3BH が計算されたことがわかります。(実際のステップ実行では5行目でCPUが停止するので6行目までしか実行されません)

実験1

1から10までの和をLD命令とADD命令を使って順次足していく方法で計算せよ。作成したニーモニックコードをアセンブルし、結果をレポートに示せ。それをステップ実行し、Aレジスタがどのように変化したかもレポートに示すこと。

メモリの利用

レジスタの値を変更しても、それは外部からは見えないので、あまり意味がありません。

そこで、計算結果をメモリに残すようにしてみましょう。ついでに、計算するデータもメモリから取り出すようにします。

2つの数値の加算(メモリ使用)

コンピュータのプログラムが実行中のとき、CPUは頻繁にメモリとデータのやり取りをします。

CPUの内部にあって高速にアクセス可能なのはレジスタですが、個数が限られているので何でもレジスタに覚えておくわけには行きません。

そこで、当面の計算に必要なものだけをレジスタに入れ、残りはメモリに記憶しておく方法が取られるのです。

したがって、CPUの命令の中にもメモリとレジスタのデータのやり取りを行うものがたくさんあります。

次のニーモニックは、メモリの8020H番地と8021H番地に書かれているデータを足して、結果を8022H番地に書き出します。 なお、このコードは後から「ラベル」を付与することを考えて行の最初に8個のスペース文字を入れています。

        ORG 8000H      ; プログラムの開始番地は8000H

        LD A,(8020H)   ; 8020H番地の値をAレジスタに
        LD B,A         ; Aレジスタの値をBレジスタに
        LD A,(8021H)   ; 8021H番地の値をAレジスタに
        ADD A,B        ; A+B->A
        LD (8022H),A   ; 8022H番地にAの値を書く
        HALT           ; CPUを停止

        END

ニーモニックでは、括弧に囲んで数値を書くと、それはメモリの番地を表すものと解釈されます。つまり、

LD A,12H    - Aレジスタに12Hを入れる
LD A,(12H)  - Aレジスタに12H番地の値を入れる

という違いがあります。上のコードではAレジスタの値は12Hになりますが、下のコードでは何になるかは12H番地の内容によって変わります。

また、LD A,(番地) という命令はありますが、LD B,(番地) という命令はありません。すべてのレジスタが平等に扱えれば楽なのですが、そうはなっていないので、この例のように、いったんAレジスタに入れてからBレジスタに移す、という処理が必要なることもあります。

参考:Z80の主要な命令のニーモニック表より詳細なインストラクションセットの説明

実験2

上のプログラムをステップ実行し、Aレジスタ、Bレジスタ、8020H~8022H番地の内容がどのように変化するかを記録し、レポートに説明せよ。

ただし、8020H番地と8021H番地にはあらかじめ13Hと28Hのデータを、メモリダンプウィンドウを使って入れておくこと。

実験3

8030H~8033H間での4バイトに書かれた数値の和を8040Hに格納するプログラムを、実験2のコードにならって作成せよ。実際に8030H~8034Hに次のような自分の誕生日のデータを格納して実行し、その様子をレポートに報告せよ。

例:1965年6月11日生まれの場合

※ この場合、19 65 06 11は16進数として格納されるので、10進数としてこの4つ数値を足した値にはならない。

プログラムをステップ実行する際は、まず「シミュレート」→「データリセット」を選んでメモリとレジスタをリセットし、その後、上記の設定をしてから開始すること。

繰り返しと分岐

これまでのプログラムでいくつかの数値の和を計算することができました。

しかし、10個の数値を足すのに10回のADD命令を書くのは効率が悪いですし、個数が100個や1000個になったらやってられません。

このような場合、繰り返しと分岐を使うと効率的なプログラムを書くことができます。

フラグレジスタの動作

Z80で分岐処理を行う上で避けて通れないのがフラグレジスタです。

コンピュータの仕組みのところで少し説明しましたが、フラグレジスタとは、演算を行った結果によって値が自動的に変化するようなレジスタです。Z80のフラグレジスタには全部で6つのフラグがありますが、ここではゼロフラグだけについて説明します。

ゼロフラグとは、直前の演算の結果がゼロになったことを表すフラグです。

LD A,10H    ; A←10H
LD B,10H    ; B←10H
SUB A,B     ; A←A-B

というプログラムを実行したとき、SUB A,B を行った直後にゼロフラグは1になります。

あるいは、

LD C,5H     ; C←5H
DEC C       ; C←C-1 (C=4)
DEC C       ; C←C-1 (C=3)
DEC C       ; C←C-1 (C=2)
DEC C       ; C←C-1 (C=1)
DEC C       ; C←C-1 (C=0)

のような場合、5つ目のDEC Cの直後はゼロフラグが1になります。それ以外のDEC Cの後はゼロフラグは0です。

演算命令以外の命令の後では、フラグは変化しないこともあります。詳しくはより詳細なインストラクションセットの説明を参照してください。この中で、「フラグ」の欄がフラグレジスタの変化を示しています。

分岐命令と条件付分岐命令

分岐命令は、プログラムの流れを変えるための命令です。

分岐するときは、分岐先のアドレスを指定します。

JP 8010H      ; 8010H番地へジャンプする

条件付分岐命令は、フラグレジスタの特定のフラグが立ったとき(あるいは立っていないとき)にジャンプを行うものです。

JP Z,8010H    ; ゼロフラグが立っていれば8010H番地にジャンプする
JP NZ,8010H   ; ゼロフラグが立っていなければ8010H番地にジャンプする

10回の繰り返し

以上を踏まえると、繰り返し処理を書くことができます。

次のニーモニックコードは、8020H番地のデータを10回、1ずつ増やして8030H番地に書き込みます。

        ORG 8000H      ; プログラムの開始番地は8000H

        LD A,(8020H)   ; 8020H番地の値をAレジスタに
        LD C,10        ; C←10 (カウンタ)
LOOP:   INC A          ; Aを1増やす
        DEC C          ; Cを1減らす
        JP NZ,LOOP     ; ゼロでないならLOOPへ
        LD (8030H),A   ; 答えを8030H番地に書く
        HALT           ; CPUを停止

        END

このコードでは、繰り返しのカウンタとしてCレジスタを使用しています。

まずCレジスタを(10進数の)10に初期化します。(10の後にHがない点に注意)

LOOP: というのは「ラベル」と呼ばれ、アセンブラの擬似命令です。ラベルは任意の行の先頭に書くことができ、コロン(:)で区切ります。ラベルを使うとアドレスを数値で指定する必要がなくなり、プログラムが書きやすく読みやすくなります。

その後、繰り返し処理の中でCレジスタを1つ減らし(DEC C命令)、その直後にJP NZ命令でラベルLOOPに分岐しています。これは、Cレジスタを減らした結果がゼロでないならLOOPに分岐することを意味しており、10回減らすと0になるので、結果としては10回繰り返すことになります。

8020H番地に13Hを書き込んでからステップ実行すると、次のようになります。ここでは5行目に着目します。

5行目の実行 Aレジスタ Cレジスタ
1回目 13H 0AH
2回目 14H 09H
3回目 15H 08H
9回目 1DH 01H
10回目 来ない 来ない

ステップ実行しながら5行目が来るごとに、回数とA,Cレジスタの値を記録してみます。

9回目の実行でCレジスタが1になりますから、次のDEC CでCレジスタは0になるので、もう分岐は起こらず、10回目の実行は起こりません。

最後のDEC Cの前にINC Aがありますから、Aレジスタは10回増えて1DHになって、それを8030H番地に格納します。

カウンタとしてはCレジスタが多く使われます(名前がCだから?)が、特にどのレジスタでもカウンタとして使うことができます。

実験4

繰り返しを用いて、1から10までの和を計算し、結果を8030H番地に格納するプログラムを作成せよ。実行の状況を上の例にならってレジスタの値の変化がわかるように示すこと。

間接アドレッシング

これまでのプログラムでは、メモリの番地を指定するのに数値を直接使っていました。(ラベルを書いても、数値を書いたのと同じ)

このような番地の指定の仕方を、直接アドレッシングと呼びます。

これに対して、レジスタを使って番地を指定することもできます。

このような番地の指定の仕方を、間接アドレッシングと呼びます。

次のコードは、8020H番地から3つの数値を足して、結果を8030H番地に格納します。

        ORG 8000H      ; プログラムの開始番地は8000H

        LD HL,8020H    ; HL←8020H
        LD A,(HL)      ; A←HL番地の値
        LD C,2         ; C←2
LOOP:   INC HL         ; HL←HL+1
        ADD A,(HL)     ; A←A+HL番地の値
        DEC C          ; C←C-1
        JP NZ,LOOP     ; ゼロでなければLOOPへ
        LD (8030H),A   ; 結果を8030H番地に格納する
        HALT           ; CPUを停止

        END

HLレジスタは16ビットの大きさを持つレジスタで、2バイトの数値を格納できます。

このプログラムでは、最初にHLに8020Hという値を入れておき、それを番地としてAレジスタにその番地の値を入れます。次に、HLを1つ増やします。これで、次の番地に進むことになります。そして、AレジスタにHL番地の値を足す、という処理をCレジスタが0になるまで繰り返します。Cレジスタは2に初期化していますから、最初の数値をAレジスタに入れた後、2回、HL番地の値を足すことで、合計3つの数値の和を計算しています。

8020H番地に11H,22H,33Hという3つの数値を入れて実行した結果を以下に示します。

実行前:

実行後:

11H + 22H + 33H = 66H が実行されていることがわかります。

実験5

間接アドレッシングを使って、8020H~802FHまでに格納してあるデータを1つずつずらして回転させるプログラムを作成せよ。たとえば次のようになる。

適当な16バイトのデータを入れてプログラムを実行し、正しくローテートしていることをレポートに示せ。

また、プログラムコードも示せ。

ヒント:

I/O処理

コンピュータのI/O空間には、通常はいろいろな周辺機器が接続されています。

I/O空間からデータを読んだり、I/O空間にデータを書き込むことは、それらの周辺機器を操作することになります。

I/Oポートの使い方

z80editでは、I/O空間として3つのポートA,B,Cが利用できるようになっており、それらのI/O空間でのアドレスは次のように割り当てられてられています。

I/O番地 ポート名
0F8H A
0F9H B
0FAH C

これらのポートは、「シミュレート」→「実行」で表示される実行ウィンドウで設定できます。

このウィンドウは、プログラムを連続実行します。

ポートの状態を示すチェックボックスは、チェックが入っているときが1、いないときが0を意味しますが、プログラムの実行中はAポートのチェックボックスは変更できず、Cポートの上位4ビットも変更できません。(バグかも?)

また「出力先」ラジオボタンは、レジスタウィンドウの右上のLEDにつながっており、どのポートの状態をLEDに出力するかを切り替えます。

次のニーモニックコードは、Bポートからデータを読んで、それをAポートに出力します。

        ORG 8000H      ; プログラムの開始番地は8000H

LOOP:   IN A,(0F9H)    ; BポートからAレジスタに読む
        OUT (0F8H),A   ; AレジスタをAポートに書く
        JP LOOP        ; 無限ループ

        END

IN A,(n) 命令はI/O空間のn番地からデータを読み、Aレジスタに格納します。

また、OUT (n),A 命令はAレジスタの値をI/O空間のn番地に出力します。

したがって、このプログラムは、I/O空間の0F9H番地(=Bポート)からデータを読んで、それを0F8H番地(=Aポート)に書く、という処理を無限に繰り返します。

「シミュレート」→「実行」で実行ウィンドウを表示しましょう。

「出力先」をAポートにして実行ボタンを押してください。

Bポートのチェックボックスを変更すると、同時にAポートのチェックボックスも変更になり、レジスタウィンドウのLEDがそれに合わせて点灯・消灯することを確認してください。

流れるLED

I/Oポートを使ってLEDを点灯させる方法がわかったので、LEDを右から左に流すプログラムを考えましょう。

LEDの点灯状態は、Aポートに書き込む値で制御できます。

したがって、

のように光らせるためには、Aポートに書き込む値を

01H → 02H → 04H → 08H → 10H → 20H → 40H → 80H

のように変化させればよいことがわかります。

これを踏まえて書いたプログラムが以下のコードです。

        ORG 8000H      ; プログラムの開始番地は8000H

INIT:   LD A,01H       ; Aを01Hに初期化する
LOOP:   OUT (0F8H),A   ; Aをポートに出力
        CALL WAIT      ; ちょっと待つ
        ADD A,A        ; Aを2倍する
        JP NZ,LOOP     ; 繰り返し
        JP INIT        ; 左端まで行ったら最初から

; ちょっと待つためのサブルーチン
;
WAIT:   LD C,50        ; 50回ループするためのカウンタ
WAIT1:  LD B,B         ; 無駄な作業
        DEC C          ; カウンタを減らす
        JP NZ,WAIT1    ; 0になるまでループ
        RET            ; 呼び出された場所に戻る

        END

ここでは、サブルーチンと呼ばれる機能を利用しています。

これは、C言語の関数呼び出しのようなもので、よく使う機能を1つの場所に書いておき、必要に応じて呼び出します。

呼び出すには、

CALL nn   (nnは番地)

処理が終わって戻るには、

RET

という命令を使います。

このサブルーチンは「ちょっと待つ」ということが目的ですから、無駄な作業(Bレジスタの値をBレジスタに入れる)を50回繰り返しています。50を変更すると、待ち時間を調節でき、LEDが流れるスピードが変わります。ただし、Cレジスタは8ビットなので0以上255以下で指定し、0がもっとも遅くなります。

また、LEDの点灯を左に移動させるために ADD A,A を使用しています。

この命令はAレジスタの内容とAレジスタの内容を足してAレジスタに格納する、という意味であり、要するにAレジスタを2倍することになります。2進数で値を2倍すると桁が1つ上がりますから(10進数を考えるとわかりますね)、2倍2倍になっていくと、1の位置が次々に左にずれていくことになります。

しかし、Aレジスタは8ビットレジスタですから、記憶できる数値には上限があります。

1から始めて2倍になっていくと、

01H → 02H → 04H → 08H → 10H → 20H → 40H → 80H → 100H → ・・・

と続いていきますが、16進数で2桁が8ビットで表すことができる上限なので、3桁になると桁上がりは捨てられてしまい、00H になります。これを利用して、演算結果がゼロでなければLOOPに戻り、ゼロならばINITに戻ってもう一度Aレジスタを01Hに初期化しています。

実験6

8020H番地から書かれているLEDへの出力パターンを繰り返して表示するプログラムを作成せよ。

パターンとは、たとえば、

8020H  81H  ●○○○○○○●
8021H  42H  ○●○○○○●○
8022H  24H  ○○●○○●○○
8023H  18H  ○○○●●○○○
8024H  18H  ○○○●●○○○
8025H  24H  ○○●○○●○○
8026H  42H  ○●○○○○●○
8027H  81H  ●○○○○○○●

のようなデータ列である。このデータ列を繰り返して表示すると、ちょうどナイトライダーのトレードマークのように動く。

自分でオリジナルのパターンを考えて表示させ、期待通りになったかをレポートに報告せよ。

レポート課題

以下の課題について考察し、レポートに回答せよ。

  1. Z80が2バイトの数値を扱うときは「逆ワード」で扱われる。この逆ワードとは何かをレポートに示せ。
  2. 「流れるLED」で示したコードで、最後のLEDだけ点灯させずに、また最初のLEDから流れるようにしたい。この場合、JP NZ 文の前に比較の判定を行う必要がある。このコードを考えて示せ。

(2のヒント)

「流れるLED」のプログラムは、01H → 02H → 04H ・・・ とビット1が右から左に移動することでLEDが流れる。最後まで流れきると A レジスタは 00H になるので、そのとき最初に戻っている(INITに分岐する)。ということは、左端のLEDを点灯しないようにするには、A レジスタが 00H になる前に最初に戻るようにしてやればよい。言い換えれば、Aレジスタが 80H の時に最初に戻ればよい。

そのためには、CP n という比較命令を使う。

参考として、以下に、A レジスタが 55H の時に INIT にジャンプするプログラムを示しておく。

	ORG 8000H

INIT:	LD A,00H         ; Aに00Hを入れる
LOOP:	INC A            ; Aを1つ増やす
	CP 55H           ; Aと55Hを比較する
	JP Z,INIT        ; 一致していればINITにジャンプする
	JP LOOP          ; そうでないならLOOPにジャンプする

	END

青字の部分が比較と分岐を行っている部分である。CP n という命令は A レジスタと指定した数値 n とを比較し、その結果が等しければ Z フラグを立てる。したがって上の例では、A レジスタと 55H を比較し、等しければ INIT へ、そうでなければ LOOP へ分岐する