hand-assemble

手作業でマシン語に変換する ハンド・アセンブルの実際6項

コンピュータを作ってもプログラムがなければ動作しない。しかしプログラムには様々な言語があり、初学者は何を学べばいいか迷ってしまいがちである。ここでは、アセンブリ言語を使って手作業でマシン語に変換する「ハンド・アセンブル」についてまとめ、コンピュータそのものに対する理解を深める。

1 プログラマから見たハードウェア

プログラムの作成時に必要なハードウェアの情報は、以下の7つである。

 

CPU(プロセサ)の情報

  • CPUの種類:CPUの種類によって使用されるマシン語が決まる。マシン語とは、0と1だけの2進数の数値で表されたプログラミング言語。プロセサが直接(ネイティブに)理解できるプログラミング言語という意味で「ネイティブ・コード」ともいう
  • クロック信号の周波数:クロック・ジェネレータからCPUに与えられる電気信号の周波数のこと。MHz(メガヘルツ=100万回/秒)を単位として表す。私たちのマイコンには、2.5MHzのクロック信号が使われている。マシン語は、命令の種類によって実行に要するクロック数が決まっている。プログラマは、クロック数の合計でプログラムの実行時間を見積もることができる。

メモリーの情報

  • アドレス空間:メモリーの中にあるデータの格納場所を指定するアドレスの範囲
  • 1つのアドレスに記憶できる情報のビット数:8ビット(=1バイト)

I/Oの情報

  • I/Oの種類:マイコンと周辺装置を接続するI/Oの種類
  • アドレス空間:I/Oのレジスタを指定するためのアドレスの範囲
  • 接続されている周辺装置:Aポート・データとBポート・データは、周辺装置と入出力するデータを記憶する場所

 

2 マシン語とアセンブリ言語

アセンブリ言語とは、マシン語のプログラムにニーモニック(ニックネーム)をつけてプログラミングするプログラミング言語のこと。マシン語のプログラムは0と1の数値の羅列(命令かデータを表す)だが、それが何を表しているかを判断することはできない。そこで、この0と1の羅列にその機能を表す英語に似たニックネームをつけてプログラムを作成する手法が考案された。そのため、マシン語とアセンブリ言語のどちらを使っても、数値で表すかニーモニックで表すかの違いだけでやっていることは同じである。

 

表1.アセンブリ言語のソースコード例

ラベル オペコード オペランド

     LD    A, 207
OUT   (2), A
LD    A, 255
OUT   (2), A
LD    A, 207
OUT   (3), A
LD    A, 0
OUT   (3), A
LOOP:  IN    A, (0)
OUT   (1), A
JP    LOOP

 

アセンブリ言語の文法はとても簡単で、1行に「ラベル」と「オペコード(命令)」と「オペランド(命令の対象)」を並べて記述するという構文しかない(表1)。

ラベルは、その行のメモリー・アドレスの数値に名前を付けるものである。必要な行だけに任意のラベルを書く。

オペコードは「○○せよ」という命令である。提供されているオペコードの種類がCPUにできることのすべてである。Z80 CPUの命令は全部で70種類程度ある。主な命令を以下に挙げる。

演算命令

  • ADD A, num:Aレジスタにnumを加算する
  • SUB reg:Aレジスタからにregを減産する
  • INC reg:regの値をインクリメントする

メモリーとCPUの入出力命令

  • LD reg, num:regにnumを書き込む
  • PUSH reg:regをスタックに書き込む

I/OとCPUの入出力命令

  • IN A, (num):numのアドレスからAレジスタに読み出す
  • OUT (num), A:Aレジスタの値をnumのアドレスに書き込む

プログラムの流れを制御する命令

  • JP num :numのアドレスにプログラムをジャンプする
  • CALL num :numのアドレスにあるサブルーチンを呼び出す

 

オペランドは命令の対象となる「△△を」を表すものである。オペランドには、CPUのレジスタ、メモリー・アドレス、I/Oアドレスまたは数値データを指定する。オペランドが複数の場合は、カンマで区切る。命令の種類によってオペランドの数が決まっている。

アセンブリ言語の構文は、英語の命令文の構文と同様である。つまり、動詞→目的語となる。

アセンブリ言語では数値データを記述するときに10進数または16進数を使う(マシン語は2進数)。123と単に数字を書いたときは10進数であるとみなされ、123Hのように末尾にH(Hexadecimall=16進数)をおいたときは16進数であるとみなされる。

 

3 Z80 CPUのレジスタ構成

レジスタとはCPUの中にあるデータを記憶する場所のことである。CPUのレジスタは単にデータを記憶するだけでなく、データを演算する機能がある。どのようなレジスタを持っているかはCPUの種類によって異なる。Z80 CPUが持つレジスタは、図1のようになっている。A、B、C、Dなどはレジスタの名前である。アセンブリ言語では、オペランドにレジスタの名前を指定する。

IX、IY、SP、PCのサイズは16ビットで、その他のレジスタのサイズは8ビットである。レジスタの種類によって用途が決まっており、命令の種類によって特定のレジスタしかオペランドに指定できないものもある。

例えば、Aレジスタは「アキュムレータ」とも呼ばれ、演算の中心となるものである。Fレジスタは「フラグ・レジスタ」とも呼ばれ、演算結果によって桁上がりが発生したことや、大小比較の結果などが記憶される。PCレジスタは「プログラム・カウンタ」とも呼ばれ、CPUが次に実行する命令のメモリー・アドレスが格納される。SPレジスタは「スタック・ポインタ」とも呼ばれ、メモリー上に「スタック」というデータの一次記憶領域を作成するために使われる。

図1.Wikipedia

 

4 プログラムの動きを追う

アセンブリ言語で記述されたプログラムは、そのままでは実行できない。CPUがネイティブに理解できる唯一のプログラミング言語であるマシン語に変換してから実行する必要がある(変更の方法は後述)。

命令の読み出し、命令の解釈・実行、PCレジスタの更新を繰り返してプログラムが動作する。プログラムの流れはPCレジスタで制御される。

 

5 ハンド・アセンブルの実際

ハンド・アセンブルとは、アセンブリ言語で記述したプログラムを手作業でマシン語に変換することである。CPUの資料に仕様できるすべてのマシン語のニーモニックとニーモニックをマシン語に変換した数値が明示されていることを利用するものである。以下に、必要な命令だけのニーモニックからマシン語への変換方法と、マシン語の実行に要するクロック数を示しておく(表2)。

表2.ニーモニックからマシン語への変換方法

ニーモニック   マシン語   クロック数

LD A, num   00111110 num   7
OUT (num), A   11010011 num   11
IN A, (num)    11011011 num   11
JP num    11000011 num   10

 

まず「LD A,207」は「LD A,num」というパターンのため「00111110 num」になる。numの部分には、10進数の207を8ビットの2進数で表した「11001111」を指定する(Windows添付の電卓を使うと便利)。これで「LD A,207」が「00111110 11001111」というマシン語に変換できた。

同様に「OUT (2),A」は「OUT(num),A」というパターンのため、「11010011 num」になる。numの部分には、10進数の2を8ビットの2進数で表した「00000010」を指定し、結果として「1101001100000010」というマシン語になる。「IN A,(0)」も「IN A,(num)」というパターンのため、結果として「11011011 00000000」というマシン語になる。

最後のJP LOOPは「JP num」というパターンのため、「11000011 num」となる。ここでは、numにメモリー・アドレスを16ビット(16桁)の2進数で指定することに注意が必要である。Z80 CPUにはメモリー・アドレスを指定するピンが16本あるので、マシン語では16ビットの2進数でアドレスを指定するようになっている。ジャンプ先のメモリー・アドレスは、LOOP:というラベルが指定された「LD A,0」の部分すなわち00010000番地である。これを16ビットで表すと00000000 00010000番地になる。上位8ビットをすべて0にしているだけである。

注意点として「リトル・エンディアン(little endian)」というデータ格納形式がある。これは2バイトのデータをメモリーに格納するときは、下位8ビットが先で上位8ビットが後になることである。一方、データの上位から下位の順でそのままメモリーに格納する形式を「ビック・エンディアン(big endian)」と呼ぶ。CPUの種類によって、ビッグ・エンディアンとリトル・エンディアンのどちらかが使われる。Z80 CPUでは、リトル・エンディアンを使うため、JP LOOPは「11000011 00010000 00000000」というマシン語になる。

 

6 プログラムの実行時間を見積もる

クロック数からプログラムの実行時間を見積もることができる。表2に示したクロック数を見てアセンブリ言語の各命令のクロック数を集計すると、LOOPラベルの前までの8行の処理は7+11+7+11+7+11+7+11=72クロックで、LOOPラベル以降の3行の処理は11+11+10=32クロックである。2.5MHzというクロック数は1秒間に250万クロックであるから、1クロックの実行時間は1/250万=0.0000004秒=0.4マイクロ秒となる。72クロック=72×0.4=28.8マイクロ秒で、32クロック=12.8マイクロ秒である。LOOPラベル以降で行われている「入力、出力、ジャンプ」という処理を1秒間に1秒/12.8マイクロ秒=78125回も繰り返している。コンピュータは本当にすごい速さで動作していることがわかるだろう。

 

最後に

C言語やBASICなどの言語よりアセンブリ言語の方が、簡単に習得できるかもしれない。文法が単純で、命令の数も少ないからである。しかし、現在ではアセンブリ言語を使う人は滅多にいない。なぜならば、この言語でプログラミングするとコンピュータの動作を細かく記述することになるため、プログラムが長くなってしまうからである。

アセンブリ言語、マシン語、そしてハンド・アセンブルを体験することで、よりコンピュータに近い動作を意識できるようになったと思う。今後JavaやPHPといったプログラミング言語を学ぶ上で、その知識が活かせるだろう。

次回はプログラムの流れについてまとめる。

コンピュータはなぜ動くのか~知っておきたいハードウエア&ソフトウエアの基礎知識~


コメントを残す

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

次のHTML タグと属性が使えます: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>