最終更新日:2015年3月19日
コンピュータの頭脳とも言われるCPUが直接実行できるプログラムは機械語と呼ばれています。
近年はC++やJavaのような高級言語が広く普及し簡単に利用できるため、直接機械語でプログラムを組む必要性はかなり少なくなりました。
しかし、ハードウェアに極めて近いソフトウェア(組み込み系のソフトや制御用のソフトなど)を作る場合は、まだ機械語でプログラムを組むような場面もないとは言えません。
また、高級言語でプログラムを組む場合でも、コンパイルされて最終的には機械語に変換されて実行されるわけですから、機械語の仕組みを理解しておくことは決して無駄というわけではありません。
この実験では、任天堂ゲームボーイ(DSじゃないですよ)にも用いられた代表的な8ビットCPUのZ80をターゲットとして、機械語のプログラミングとはどんなものなのか、その基本を理解することを目的とします。
この実験は個人で行います。
電算機演習室のPCを使用してZ80シミュレータを起動し、以下の説明に沿って実験を行うこと。
レポートの提出方法は担当教員の指示に従うこと
CPU内部ではすべての信号は電圧が高いか低いかによって表現されるため、それは2進数の表現となります。したがって、10進数の「16 205 50 0 1 118」は、機械語では、
00010000 11001101 00110010 00000000 00000001 01110110
のような形で表されるわけです。
しかし、このような表現は、CPUにとっては極めて実行しやすいものですが、人間にとってはかなり理解しにくい形です。
2進数よりは16進数の方がわかりやすい(かな?)ということで、上記の数値を16進数に変換すると、
3E CD 32 00 01 76
となります。
が、やっぱり意味はわかりませんね。
(2進←→10進←→16進の変換は次の表を参考にして下さい。よく分からない場合はこのウェブページも読んで下さい。)
2進数 | 10進数 | 16進数 |
---|---|---|
0000 | 0 | 0 |
0001 | 1 | 1 |
0010 | 2 | 2 |
0011 | 3 | 3 |
0100 | 4 | 4 |
0101 | 5 | 5 |
0110 | 6 | 6 |
0111 | 7 | 7 |
1000 | 8 | 8 |
1001 | 9 | 9 |
1010 | 10 | A |
1011 | 11 | B |
1100 | 12 | C |
1101 | 13 | D |
1110 | 14 | E |
1111 | 15 | F |
実は機械語とは数字の羅列に過ぎません。したがって、それ自体には意味はないのです。
意味は、そのプログラムをどの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というサブルーチン呼出し命令になります。
ということで、このプログラムは、
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の内部の仕組みを簡単に説明しましょう。
ここでは、機械語プログラミングに必要な部分に限って説明します。
上の図は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が実行することはできないので、機械語に翻訳する必要があります。この作業をアセンプルといい、この作業をやってくれるプログラムをアセンプラといいます。
また、ニーモニックから手作業で機械語コードを導く作業はハンドアセンブルと呼ばれます。
詳細な説明(命令動作表)はここにあります。参考にしてください。
まず最初の実験として、2つの整数の和を求める簡単な機械語のプログラムを作ります。
まず、作業用フォルダを準備します。Z:ドライブのどこかに(実験2のフォルダなど)、作業フォルダを作成します。名前は何でも良いですが、ここでは "z80" としましょう。
なおこれ以降、ニーモニックにおける16進数の数値には、最後に H をつけて10進数と区別することにします。H は Hexadecimal の頭文字です。また、16進数の最初の文字がアルファベットの場合、先頭に0を付加することにします。
ただし、機械語のコードには、この H はつけません。
なお、10進数(Decimal Number)の場合は最後にD、2進数(Binary Number)の場合は最後にBをつけて表現する場合があります。
z80editは、植木友浩氏が作成、公開をしているフリーのZ80シミュレータです。(現在は作成終了)
このソフトは内部にニーモニックエディタ、アセンプラ、シミュレータを備えたオールインワンな設計で、Z80の機械語を勉強するにはとても良い環境を提供してくれます。
電算機演習室で使用する場合、デスクトップの「コンピュータ」アイコンをクリック→上のアドレス欄をクリック→「\\nas\share\exp1\Z80edit」を入力→「Z80edit.exe」を実行してください。
Z80シミュレータを起動すると、次の画面が現れます。
z80editは良くできたソフトウェアですが、個人がフリーソフトとして作っているものですので、動作がおかしくなる場合もあります。
現在までにわかっている誤動作とその対策について、ここにまとめておきます。
Z80シミュレータ z80edit に慣れるため、機械語の簡単なプログラムの作成と実行をしてみます。
ここでは、レジスタを使って 13H + 28H = 3BH の足し算を行ってみましょう。
(10進数では、13H=(1x16+3)=19D、28H=(2x16+8)=40D、3B=(3x16+11)=59D)
以下、z80edit の画面を示しながら説明をします。
z80editを起動し、「ファイル」→「新規作成」で新しいエディタ画面を開き、次のように入力します。
擬似命令とは、機械語の命令ではなくアセンブラに指示するための命令です。ORGはプログラムの開始番地を指定します。ここで指定した番地にプログラムがおかれます。またENDはプログラムの終端を示すための擬似命令です。
LD命令はLOADの略で、レジスタなどに数値(ここでは13H)を入れる命令です。
ADD命令は加算命令で、今の場合はAレジスタに数値(ここでは28H)を足して、結果をAレジスタに入れます。
HALT命令はCPUの動作を停止します。
また、セミコロン(;)より右はコメントであり、何を書いてあってもアセンブラは無視します。(ということは書かなくても大丈夫です)
入力が終わったら、「ファイル」→「保存」でZドライブを選択し、名前をつけて保存します。ファイル名は "addnum1.asm" とします。
今後も新しいプログラムを作成するたびに"自分でわかるファイル名.asm"としてZドライブに保存してください。
また、以前に保存したプログラムを呼び出す場合は、「ファイル」→「開く」→ファイルの種類を「アセンブラファイル(*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行目までしか実行されません)
01Hから0FHまでの和をLD命令とADD命令を使って順次足していく方法で計算せよ。作成したニーモニックコードをレポートに示せ。
また、ステップ実行して、Aレジスタ(16進数)がどのように変化したかもレポートに示すこと。(Fレジスタの値を書かないように気をつけること。)
新たにプログラムをステップ実行する際は、まず「シミュレート」→「データリセット」を選んでメモリとレジスタをリセットすること。
レジスタの値を変更しても、それは外部からは見えないので、あまり意味がありません。
そこで、計算結果をメモリに残すようにしてみましょう。ついでに、計算するデータもメモリから取り出すようにします。
コンピュータのプログラムが実行中のとき、CPUは頻繁にメモリとデータのやり取りをします。
CPUの内部にあって高速にアクセス可能なのはレジスタですが、個数が限られているので何でもレジスタに覚えておくわけには行きません。
そこで、当面の計算に必要なものだけをレジスタに入れ、残りはメモリに記憶しておく方法が取られるのです。
したがって、CPUの命令の中にもメモリとレジスタのデータのやり取りを行うものがたくさんあります。
次のニーモニックは、メモリの8020H番地と8021H番地に書かれているデータを足して、結果を8022H番地に書き出します。
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レジスタに移す、という処理が必要なることもあります。
参考:命令の意味(命令動作表)
上のプログラムをステップ実行し、Aレジスタ、Bレジスタ、8020H〜8022H番地の内容がどのように変化するかを記録し、レポートに説明せよ(16進数で示すこと)。
ただし、8020H番地と8021H番地にはあらかじめ13Hと28Hのデータを、メモリダンプウィンドウを使って入れておくこと。
8030H〜8033H間での4バイトに書かれた数値の和を8040Hに格納するプログラムを、実験2のコードにならって作成せよ。実際に8030H〜8034Hに次のような自分の誕生日のデータを格納して実行し、その様子をレポートに報告せよ(16進数で示すこと)。
例:1965年6月11日生まれの場合
※ この場合、19 65 06 11は16進数として格納されるので、10進数としてこの4つの数値を足した値にはならない。
(10進数の場合、19H=(10x16)+9=25D、65H=(6x16)+5=101D、06H=(0x16)+6=6D、11H=(1x16)+1=17D)
これまでのプログラムでいくつかの数値の和を計算することができました。
しかし、10個の数値を足すのに10回のADD命令を書くのは効率が悪いですし、個数が100個や1000個になったらやってられません。
このような場合、繰り返しと分岐を使うと効率的なプログラムを書くことができます。
Z80で分岐処理を行う上で避けて通れないのがフラグレジスタです。
コンピュータの仕組みのところで少し説明しましたが、フラグレジスタとは、演算を行った結果によって値が自動的に変化するようなレジスタです。Z80のフラグレジスタには全部で6つのフラグがありますが、ここではゼロフラグだけについて説明します。
ゼロフラグとは、直前の演算の結果がゼロになったことを表すフラグです。
(sub A,B は「AからBを引きなさい」という命令)
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 A は「Aから1を引きなさい」という命令)。
それ以外のDEC Cの後はゼロフラグは0です。
演算命令以外の命令の後では、フラグは変化しないこともあります。
分岐命令は、プログラムの流れを変えるための命令です。
分岐するときは、分岐先のアドレスを指定します。
JP 8010H ; 8010H番地へジャンプする
条件付分岐命令は、フラグレジスタの特定のフラグが立ったとき(あるいは立っていないとき)にジャンプを行うものです。
JP Z,8010H ; ゼロフラグが立っていれば8010H番地にジャンプするJP NZ,8010H ; ゼロフラグが立っていなければ8010H番地にジャンプする
以上を踏まえると、繰り返し処理を書くことができます。
次のニーモニックコードは、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だから?)が、特にどのレジスタでもカウンタとして使うことができます。
繰り返しを用いて、10進数の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 が実行されていることがわかります。
間接アドレッシングを使って、8020H〜802FHまでに格納してあるデータを1つずつずらして回転させるプログラムを作成せよ。たとえば次のようになる。
メモリダンプに適当な16バイトのデータを入れてプログラムを実行し、正しくローテートするプログラムコードを示せ。
ヒント:
周辺器機(Input/Output: 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がそれに合わせて点灯・消灯することを確認してください。
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に初期化しています。
8020H番地から8027H番地までのデータを繰り返して表示することで、下記のように左右のLEDがそれぞれ逆側に流れるようなパターンを表示するコードを示せ。(データはあらかじめメモリダンプに格納しておいて良い。)
8020H 81H ●○○○○○○● 8021H 42H ○●○○○○●○ 8022H 24H ○○●○○●○○ 8023H 18H ○○○●●○○○ 8024H 18H ○○○●●○○○ 8025H 24H ○○●○○●○○ 8026H 42H ○●○○○○●○ 8027H 81H ●○○○○○○●
以下の課題について考察し、レポートに回答せよ。
(ヒント)
「流れる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 へ分岐する