Web Analytics Made Easy - StatCounter

工業大学生ももやまのうさぎ塾

4年間+2年間の工業大学・大学院で学んだ知識やためになることを投稿していきます

うさぎでもわかる計算機システム Part07 プロセッサの動き

こんにちは、ももやまです。
今回もプロセッサの動きについてまとめたいと思います。

 

 

1.プロセッサの部品

前回の復習ですが、プロセッサにはどんな部品があるのかを簡単におさらいしておきましょう。

前回の復習はこちらに載せたのでまだプロセッサにはどんな要素があるのかよくわかっていない人はこちらをご覧ください。

www.momoyama-usagi.com

プログラムカウンタ(PC):次に実行する命令のアドレスを記憶
命令レジスタ(IR):実行中の命令の内容を格納
汎用レジスタ:データを記憶するだけのレジスタ
アキュームレータ(ACC):計算結果を格納
ALU:計算装置

今回はこれらの部品が機械語命令によって実際にどのように動くかを見ていきましょう。

2.機械語命令の種類

(1) プロセッサの仕様書

機械語には様々な命令があります。

どんな命令があるかをこちらの仕様書を持ってきました。

PDFでほしい人はこちらからダウンロードできます。

f:id:momoyama1192:20190711194651j:plain

f:id:momoyama1192:20190711194628g:plain

f:id:momoyama1192:20190711194633g:plain

今回はこちらの仕様書にかかれている主な命令を解説していきましょう。

 

 

(2) オペコード・オペランドとは

機械語(アセンブリも含む)の命令は「XXXX YYYY しなさい」という命令内容(動詞)目的語の2つで成り立っています。

例えば、「700代入しなさい」・「2番レジスタの値加算しなさい」・「304番地のメモリの値加算しなさい」・「ACCが0ならば334番地移動しなさい」などの命令があります。

 

どんな命令を実行するかは、オペコードオペランドと呼ばれるもので決まります。

上の仕様書の場合は、最上位4bit(16進数で1桁)がオペコードとなります。

例えば、0x8931 の場合は、オペコードは8となります。

 

オペコードは命令内容そのものに値します。

プログラムでいう式や関数に当たります。

 

オペランドは、命令の目的語を表します。

命令の種類によってオペランドの内容は異なりますが、「代入・加算される値」・「代入・加算される値があるレジスタ」・「代入・加算される値があるメモリの番地」などがオペランドとなっております。

プログラムでいう変数、数字に当たります。

 

(3) サインフラグ(SF)・ゼロフラグ(ZF)

サインフラグ(SF)・ゼロフラグ(ZF)がプロセッサ内にあり、ACCの値を監視しています。

SFは、値が正か(0以上か)負かを判定します。負であれば1を、そうでなければ0を判定します。

16進数の正負の判定は2進数と同様に2の補数表現を使います。
2進数の場合は最上位ビットが1のとき、負でしたね。最上位ビットが1ということは、"1xxx xxxx xxxx…" の形になっていますね。なので、16進数の最上位の桁が8以上のとき、2進数の最上位ビットは1となり、値が負であることがわかります。

ZFは、値が0かどうかを判定します。0であれば1を、そうでなければ0を返します。

 

今のフラグを2つまとめると、

ZF SF 状態
0 0 値は正(1以上)
0 1 値は負(-1以下)
1 0 値は0
1 1 ありえない

となります。

(4) 命令内容(全16種) 

(1) の仕様書には命令内容が16個書かれていましたね。
今回は仕様書の16種の命令内容を解説していきましょう。

解説するにあたって、各プロセッサの値は以下のように設定します。

f:id:momoyama1192:20190711194641g:plain

メモリの値

f:id:momoyama1192:20190711194637g:plain

Code: 0 ACCに即値代入

最初は即値を代入するシリーズです。
これは命令内容に書かれてる数字(オペランド)をそのままACCに入れてあげます。

オペランドが12bit(16進数だと3桁)なので、このオペランドをそのままACCに入れれば終わりです。

では実際に1つ即値を代入する命令の動きを見てみましょう。

f:id:momoyama1192:20190711194646g:plain

いまのプログラムカウンタの値は 0x0200ですね。

なので、0x0200番地の内容が 0x0710 となっているのがわかりますね。

0x0710 (0710hと同様)の場合はオペコードは0なのでこの命令が実行されます。

オペランドは710なので710を代入します。

すると、ACCの値は0x0710になりますね。さらにACCの値が0ではなくなったのでZFも偽となり、0となります。

命令が実行し終わったら、プログラムカウンタの値を1つ進めます。

メモリのアクセス単位は16ビット(つまり2バイト)なので、1つPCを進めると値は2増えます。

f:id:momoyama1192:20190711194403g:plain

Code: 1 ACCに即値加算

こちらも即値を使った命令です。
先程と違うのは、代入ではなく加算になった点です。

こちらは、オペランドの値を加算します。

f:id:momoyama1192:20190711194408g:plain

PCの値の番地 0x0202 を見ると、命令内容は 0x1A82 となっていますね。

早速、この命令を処理していきましょう。

 

オペコードは1なので確かにこの命令が実行されます。

オペランドはA82なのでA82を加算します。

 

加算するときに16進数の加算になり、計算ミスが多発するので注意して計算するか(16進数が計算できる)電卓やサイトで計算しましょう。

0x0710 + 0x0A82 = 0x1192

と計算できるので、ACCの値は0x1192となります。

命令が終わったのでPCの値を1つ(2だけ)すすめます。

 

f:id:momoyama1192:20190711194413g:plain

Code: 2 ACCにレジスタ値加算

レジスタを使った命令です。
命令コードに使うレジスタの番号が書かれてあり、そのレジスタ番号の値だけ加算を行います。

イメージとしては、配列の番号(添字)が書かれてあって、該当する番号のデータを加算する感じです。

f:id:momoyama1192:20190711194416g:plain

PCの値は 0x0204 なので 0x0204 番地の命令コードを見てみます。

コードは 0x2003 なので、3番目のレジスタの値 0x0810 をACCに加算します。

0x1192 + 0x0810 = 0x19A2

と計算できるので、ACCの値は 0x19A2 となります。

PCの値を2進めます。

f:id:momoyama1192:20190711194421g:plain

 

Code: 3 直接参照されたメモリ上の値を加算

ここら辺から命令内容が難しくなってきます。

この命令は、命令コードに書かれたオペランドの値のアドレスの番地を見て、その番地にかかれてある値を加算する命令です。

イメージとしては、命令コードに住所が書かれていて、その住所に書いてある数値を加算する感じです。

 

f:id:momoyama1192:20190711194425g:plain

 

PCの値は 0x0206 なので 0x0206 番地の命令コードを見てみます。

コードは 0x3332 で、オペコードは3なのでこの命令ですね。

 

オペランドは332なので、0x0332番値の数値(命令コード)を確認します。すると、0x0AED と書かれてあるので、 0x0AED を加算します。

0x19A2 + 0x0AED = 0x248F

と計算できるので、ACCの値は 0x248F となります。

PCの値を2進める。

 

f:id:momoyama1192:20190711194429g:plain

Code: 4 間接参照されたメモリ上の値を加算

思わせぶりか()と思うような命令です。

この命令は、命令コードに書かれたオペランドの値のアドレスの番地を見て、その番地にかかれてある値の番地にかかれている値を加算する命令します。

イメージとしては、命令コードに住所が書いてあって、その住所に書かれている値は答えではなく別の住所であり、別の住所にいった先にかかれている値を加算する感じです。

 

f:id:momoyama1192:20190711194433g:plain

PCの値は 0x0208 なので 0x0208 番地の命令コードを見てみます。

コードは 0x4334 で、オペコードは4なのでこの命令ですね。

オペランドは334なので、0x0334番値の数値(命令コード)を確認します。すると、0x0330 と書かれてあるので、今度は0x0330 番地の数値(命令コード)を確認します。すると、0x2BAD と書かれてあるので、0x2BAD を加算します。

0x248F + 0x2BAD = 0x503C

と計算されるのでACCの値は 0x503C となります。

 

f:id:momoyama1192:20190711194440g:plain

PCの値を(ry

Code: 5 インデックスレジスタ修飾でメモリ上の値を加算

今回紹介する命令の中で、理解するのが一番むずかしい命令です。

この命令は、オペランド部が2つの部分に分かれており、最初の上位4bit(16進数で1桁)がレジスタ番号、残りの8bit(16進数で2桁)が値(変数と同じ)となります。

レジスタ番号に書かれている値と残りの8bitの値をまずは加算します。計算結果の値が番地を示しているので、その番地にかかれている値を確認して、その値を加算します。

 

f:id:momoyama1192:20190711194444g:plain

PCの値は 0x020A なので 0x020A 番地の命令コードを見てみます。

コードは 0x5202 で、オペコードは5なのでこの命令ですね。

 

この命令の16進数上位1桁は2なのでレジスタNo.2の値を確認します。すると、0x0334となっています。残りの2桁は02なので02(つまり2)を加算します。すると、0x0336となりますね。

0x0336番地を見ると、0xCAFEと書かれていますね。これを加算すればOKです。

0x503C + 0xCAFE = 0x1B3A(溢れた5桁目は無視)

と計算されるのでACCの値は 0x1B3A となります。

 PCの(ry

f:id:momoyama1192:20190711194448g:plain

Code: 6 レジスタの値の2の補数の値をACCへ

レジスタの代入命令ですが、レジスタの値を2進数にしてから代入します。

簡単に言うと、レジスタの値の正負を入れ替えてからACCに代入するイメージと思ってください。

f:id:momoyama1192:20190711194453g:plain

PCの値は 0x020C なので 0x020C 番地の命令コードを見てみます。

コードは 0x6003 で、オペコードは6なのでこの命令ですね。

3番目のレジスタの値は、 0x0810 ですね。なので、0x0810 の2の補数を取ってあげます。

2の補数に直す方法は、

  1. 2進数に直し、0と1を入れ替え、1を加算してから16進数に戻す
  2. 10000h - 810h の計算をする。 

の2パターンがあります。1のやり方だと少し計算がめんどくさいので、2でやることをおすすめします(上限の桁+1を用意、用意した桁に1、それ以外を0としたものから減算)。

10000h - 810h = F7F0h

となるので、ACCの値には 0xF7F0 が代入されます。

ここで、最上位の桁が8以上(2進数の最上位ビットが1に相当)になりましたね。
最上位の桁が8以上ということは、ACCの値は負になっているね。

そのため、SFが0から1に変わります。

PC(ry

 

f:id:momoyama1192:20190711194456g:plain

また、レジスタ番号を5とするとACCの値を使うことができますね。

レジスタ番号を5とし、この命令を適用する(命令コード:0x6005)ことで、ACCの値の正負を入れ替えることができます。 

 

Code: 7 レジスタの値の否定をACCへ

こちらもレジスタの代入の命令ですが、レジスタの値の否定をACCに代入します。

値の否定とは、2進数の0と1を入れ替える操作を表します。16進数で表されているものは一旦2進数に直し、0と1を入れ替えてから16進数に戻すとレジスタの値の否定を求めることができます。

 

f:id:momoyama1192:20190711194503g:plain

PCの値は 0x020E なので 0x020E 番地の命令コードを見てみます。

コードは 0x7005 で、オペコードは7なのでこの命令ですね。

5番目のレジスタは、今回の仕様ではACCとなるので、ACCの値を見ます。すると、0xF7F0 なので、0xF7F0 を2進数になおしてから否定を取り、16進数に戻します。すると、

 

f:id:momoyama1192:20190711194606g:plain

となるので 0x080F がACCに代入されます。

最上位の桁が7以下(2進数の最上位ビットが0)になりましたね。
なので、値は正に戻り、SFも0になります。

P(ry(もうこれ以降表記しませんが、2足すのを忘れないようにしましょう)

f:id:momoyama1192:20190711194507g:plain

Code: 8 レジスタの値とACCの論理和を計算、代入

レジスタの値代入命令はしばらく続きます。
これは、レジスタの値とACCの値の論理和を計算し、結果を代入する命令です。

論理和の計算は、一旦両者を2進数に直しビットごとに計算し、それぞれの桁ごとにどちらか片方でも1であれば(or演算)1、両方とも0であれば0を計算していき、最後に計算結果を16進数に戻すことで計算できます。

 

f:id:momoyama1192:20190711194513g:plain

PCの値は 0x0210 なので 0x0210 番地の命令コードを見てみます。

コードは 0x8000 で、オペコードは8なのでこの命令ですね。

0番目のレジスタの値を確認すると、0x1126 とわかるので、0x1126 とACCの値である 080F の論理和をとってあげます。

それぞれを2進数に直す→ビットごとのOR演算→計算結果を16進数に戻す

ことで計算結果が出せます。

 

f:id:momoyama1192:20190711194611g:plain

となるので 0x192F がACCに代入されます。

f:id:momoyama1192:20190711201759g:plain

 

Code: 9 レジスタの値とACCの論理積を計算、代入

Code: 8の論理積バージョンです。
これは、レジスタの値とACCの値の論理積を計算し、結果を代入する命令です。

論理積の計算は、論理和と同じく、一旦両者を2進数に直しビットごとに計算します。
つぎに、それぞれの桁ごとにどちらか片方でも0であれば(and演算)0、両方とも1であれば1を計算していき、最後に計算結果を16進数に戻すことで計算できます。

f:id:momoyama1192:20190711194521g:plain

PCの値は 0x0212 なので 0x0212 番地の命令コードを見てみます。

コードは 0x9001 で、オペコードは9なのでこの命令ですね。

1番目のレジスタの値を確認すると、0x0206 とわかるので、0x0206 とACCの値である 192F の論理積をとってあげます。

それぞれを2進数に直す→ビットごとのAND演算→計算結果を16進数に戻す

ことで計算結果が出せます。

 

f:id:momoyama1192:20190711194615g:plain

となるので 0x0006 がACCに代入されます。 

 

f:id:momoyama1192:20190711201805g:plain

 

Code: A レジスタの値-1

レジスタの値を1引くだけの命令です。
5番レジスタを利用してACCから1減算することも可能です。

 

f:id:momoyama1192:20190711194532g:plain

PCの値は 0x0214 なので 0x0214 番地の命令コードを見てみます。

コードは 0xA000 で、オペコードはAなのでこの命令ですね。

 

0番レジスタの値を1減らすと、0x1126 → 0x1125 となります。

これだけです。

 

f:id:momoyama1192:20190711194536g:plain

Code: B レジスタのコピー

レジスタの値をコピーすることができる命令です。

コピー元を上位4bit(16進数の1桁)、コピー先を残りの8bitで指定します。

コピー元やコピー先に5番レジスタを使うとACCの値をレジスタから読みこんだり、レジスタに書き込みすることもできます。

f:id:momoyama1192:20190711194540g:plain

PCの値は 0x0216 なので 0x0216 番地の命令コードを見てみます。

コードは 0xB305 で、オペコードはBなのでこの命令ですね。

 

コピー元は3番レジスタ、コピー先は5番レジスタ、つまりACCなので、3番レジスタの値をACCにコピーする命令となります。

3番レジスタの値は、0x0810 なので、ACCの値は 0x0810 となります。

f:id:momoyama1192:20190711194546g:plain

 

Code: C ACCの値をメモリに書き出し

ACCの値をメモリに書き出す命令です。

命令コードにかかれている番地にACCの値を書き出します。

 

f:id:momoyama1192:20190711194550g:plain

コードは 0xC330 で、オペコードはCなのでこの命令ですね。

 

オペランドが330なので、330番地にACCの値である 0x0810 を書き込みます。

f:id:momoyama1192:20190711194554g:plain

 

Code: D 分岐命令1 ZFが1(真:ACCが0)でジャンプ

ここから下3つは分岐命令です。

ZFが1のとき(つまりACCが0x0000のとき)にオペランドで指定されたアドレスにジャンプ(PCの値を変更)します。

例えば、0xD302 という命令であれば、オペコードはDなのでこの命令、オペランドが302なので、ACCが0であれば0x0302番地にジャンプ(PCを書き換え)します。

ACCが0でないとき(条件に該当しないとき)は、なにもせず、普段どおりPCの値を1段階(この仕様書のメモリは2バイト単位なのでPCは2増える)進めます。

 

Code: E 分岐命令2 ZFが0(真:ACCが0以外)でジャンプ

ZFが0のとき(つまりACCが0x0000以外のとき)にオペランドで指定されたアドレスにジャンプ(PCの値を変更)します。

例えば、0xE334 という命令であれば、オペコードはEなのでこの命令、オペランドが334なので、ACCが0でなければ0x0334番地にジャンプ(PCを書き換え)します。

ACCが0のとき(条件に該当しないとき)は、なにもせず、普段どおりPCの値を1段階(この仕様書のメモリは2バイト単位なのでPCは2増える)進めます。

 

Code: F 分岐命令3 SFが0(真:ACCが負)でジャンプ

SFが1のとき(つまりACCの16進数の最上位桁が8以上)にオペランドで指定されたアドレスにジャンプ(PCの値を変更)します。

例えば、0xF400 という命令であれば、オペコードはFなのでこの命令、オペランドが400なので、ACCが負であれば0x0400番地にジャンプ(PCを書き換え)します。

ACCが0以上のとき(条件に該当しないとき)は、なにもせず、普段どおりPCの値を1段階(この仕様書のメモリは2バイト単位なのでPCは2増える)進めます。

 

3.練習問題

では、いつもように練習をしていきましょう。

練習1

つぎの文章の中で誤っている文章を1つ選びなさい。

  1. プログラムカウンタ(PC)の値は、減少しない。
  2. 機械語はどんな命令を実行するかが書かれているオペコードと命令の対象が書かれているオペランドの2つの部分から構成されている。
  3. サインフラグ(SF)・ゼロフラグ(ZF)の値がともに1(真)になることは絶対にない。
  4. ACCの値を  x とする。 x -= y (x \lt y) に相当する命令を実行すると必ずSFは1、ZFは0となる。

 

練習2

それぞれのプロセッサ(メモリ・ACC・汎用レジスタ)の値が図のように設定されているとする。このとき、つぎの(1)〜(5)の命令を実行したときのACCの値がどのようになるかを答えなさい。

f:id:momoyama1192:20190711194558g:plain

それぞれの小問は独立していることに注意!!

(1) つぎの命令が 0x1100 のとき
(2) つぎの命令が 0x2002 のとき
(3) つぎの命令が 0x3108 のとき
(4) つぎの命令が 0x410E のとき
(5) つぎの命令が 0x501C のとき

 

練習3

それぞれのプロセッサ(メモリ・ACC・汎用レジスタ)の値が図のように設定されているとする。PCが0x020Aを超えるまでのそれぞれの命令開始時のPCの値・それぞれの命令終了時のACCのの値を答えなさい。

 

f:id:momoyama1192:20190711194602g:plain

 

4.練習問題の答え

解答1

 

  1. 誤り。2の補数を使えばプログラムカウンタの値を減らすこともできるし、減算命令もちゃんとある。
  2. 正しい。命令の主語を表すオペコードと命令の目的語を表すオペランドから機械語の命令は構成されている。
  3. 正しい。ゼロフラグはACCが0のとき、サインフラグはACCが負のときに真となるが、値が0かつ負なんてことはありえない。
  4. 正しい。自分の数より大きい数を引くと必ずマイナスとなる。また、

解答2

(1)

解答:0x0100  (0100h)

1100のオペコードは1、オペランドは100。
オペコード1の命令は、オペランドをそのまま加算する命令。

なのでACCの値に 0x0100を加算すればOK。

0x0000 + 0x0100 = 0x0100

よりACCの値は 0x0100 となる。

 

(2)

解答:0x00F2

2002のオペコードは2、オペランドは002。
オペコード2の命令は、オペランドに示されたレジスタ番号を加算する命令。

レジスタNo.2の値は0x00F2なのでACCの値に 0x00F2を加算すればOK。

0x0000 + 0x00F2 = 0x00F2

よりACCの値は 0x00F2 となる。

 

(3)

解答:0x0100

3108のオペコードは3、オペランドは108。
オペコード3の命令は、オペランドに示されたメモリ番地の値を加算する命令。

0x0108番地の値は0x0100なのでACCの値に 0x0100を加算すればOK。

0x0000 + 0x0100 = 0x0100

よりACCの値は 0x0100 となる。

 

(4)

解答:0x0810

410Eのオペコードは4、オペランドは10E。
オペコード4の命令は、オペランドに示されたメモリ番地に書かれたメモリ番地を加算する思わせぶり命令。

0x010E番地の値は0x0106、0x0106番地の値は0x0810なので、ACCに0x0810を加算すればOK。

0x0000 + 0x0810 = 0x0810

よりACCの値は 0x0810 となる。

 

(5)

解答:0xDADA

501Cのオペコードは5、オペランドは016。
オペコード5の命令は、最初の上位1桁(16進数で)がレジスタ番地に書かれた値、残りの下位2桁が値を表しており、この2つの和が示すメモリ番地の値を加算する命令。

016のうち、レジスタ番地は0、値は1Cを表している。
No. 0 レジスタの値は 0x00F0 なので、 0x00F0 + 0x001C の和を計算します。すると、0x010C となるので、0x010C番地の値を見ます。すると、0xDADAなので

0x0000 + 0xDADA = 0xDADA

よりACCの値は 0xDADA となる。

 

解答3

解答

f:id:momoyama1192:20190711201756g:plain

(赤色部分は SF = 1)

初期値のPCの値に要注意!!

PCが0x0202となっているため、スタートは0x0202となる。
(思わず一番上に書いてあるからといって0x0200からスタートしないように!)

 

命令ID 1: 開始時のPCの値:0x0202 終了時のACCの値0xFACE

命令内容は0x4404。

オペコードが4なので間接参照(思わせぶり命令)

オペランドが404なので、まずは0x0404番地を確認。すると0x0400となっているのでつぎは0x0400番地を確認すると0xFACE となっているため、0xFACEを加算すればよい。
(ここでZFが0、SFが1に変更される)

 

命令ID 2: 開始時のPCの値:0x0204 終了時のACCの値0xFACE

命令内容は0xF208。

オペコードがFなのでACCが負(SFが1)なら指定されたオペランドの番地に移動する命令。

0xFACEは16進数での最大桁がFと8以上のため、値が負ということがわかる。

オペランドが208なので、PCは 0x0208 となる。(ACCの値は変わらない)

 

命令ID 3: 開始時のPCの値:0x0208 終了時のACCの値0x7244

命令内容は0x9003。

オペコードが9なので指定されたレジスタとACCのビットごとの論理積を取る。

 

オペランドが0003なので3番レジスタの値(0x7364)との論理積となる。

 

論理積を取ると、0x7244となる。
(SFは0に戻る)

 

命令ID 4: 開始時のPCの値:0x0208 終了時のACCの値0x8DBC

命令内容は0x6005。

オペコードが6なので指定されたレジスタの2の補数を代入。

 

オペランドが0005なので5番レジスタの値(ACC:0x7244)との2の補数を取る。

10000h - 7244h = 8DBCh

と計算できるので、2の補数を取ると 0x8DBC となる。
(最上位の桁が8以上なのでSFは1なる)

 

5.さいごに

今回は実際に機械語の命令にはどのようなものがあるのか、そして機械語の命令リストを見ながら命令がどのように動くかを簡単にですがまとめてみました。

今回紹介した機械語の命令はほんの一部で、他にもまだまだたくさんの命令があります。気になる人は調べてみましょう。