CPUの構造

まず始めに、CPUの構造をおさらいする。下図は、H8/300H CPUの構造を模式的に表したものである。



各部を簡単に説明すると、

(1) レジスタ
CPUが演算や処理を進めるために、内部に持っているメモリを「CPUの内部レジスタ」(以後、レジスタ)と呼ぶ。レジスタには、

  1.  演算結果を保存したりするための「汎用レジスタ」
  2.  スタックの先頭アドレスを保持する「スタックポインタ」
  3.  演算結果の状態を保持する「状態レジスタ」
  4.  次に実行する命令が保管されているメモリアドレスを保持する「プログラムカウンタ」

などがある。

(2)ALU(演算器)
  ALU((Arithmetic Logic Unit)とは、算術演算(H8/300Hの場合は、加減乗除)や比較、bit演算などを行うハードウエア。レジスタやメモリに保管されたデータに対して演算処理を行い、その結果をレジスタやメモリに返す。

(3) コントロールロジック
 CPUの状態に応じて制御バスの信号を制御したり読み取ったり(例えば割り込みなど)、CPU全体の制御をおこなうハードウエア。

(4) 命令デコーダ
  メモリから読み取った機械語命令を解釈して、必要な操作を行う。例えば、加減乗除などの演算を行ったり、メモリにデータを書き込んだりする命令などを解釈して、ALU、各種のバスを制御する。


CPUは、(1)~(4)を順次制御しながら演算処理を行う。処理は、おおよそ以下の流れで実行される。

  1.  プログラムカウンタの指示するアドレスをアドレスバスに出力する。
  2.  データバスを通じて、機械語命令(およびデータ)をメモリから読み込む。(1,2の処理を命令フェッチとかフェッチサイクルと言ったりする)
  3.  命令デコーダが命令を解釈する。解釈が完了すると、その内容に従って、汎用レジスタやALU、各種のバス等を制御する。
  4.  結果を保存するアドレスをアドレスバスに出力し、データバスを通じてメモリに保管する。
  5.  以下、繰り返し


具体例: レジスタR0の値に1を加算する



  機械語命令とは、どのようなものかと言うと、例えば、以下のC言語のソースをコンパイルする。
(内容については、説明するほどではないが、変数cの内容を1ずつ増やしてゆくというもの。変数cの宣言にvotatileが付加されているが、これはコンパイラの最適化を抑止するためのものである。)

#include "monitor.h"
int main()
{
    volatile int c = 0;

    while (1)
    {
        c++;
    }
    return 0;
}


 以下に示すような、機械語命令によるプログラムが作成される。

 ※アセンブリコードとは、機械語命令と一対一の対応がとれているプログラムの事。
  機械語命令は、下図のように数値の羅列として表現されるため、人がコーディングするには向いていない。(というかはっきり言
  って無理。でも、昔のプログラマは、機械語命令を諳んじていた人も少なからず存在していた。まさに、人間アセンブラである
      が、現在では、そんな事が出来ても自己満足にしかならない。)少しでもプログラマの負担を軽減する目的で、アセンブリコード
     (アセンブラ)が考案された。

 ※今時の人々にとっては、アセンブラでコーディングすることだって、十分、苦痛である。Cのソースで数百万行のプログラムをアセ  
  ンブラでコーディングしろって言われたら、気が遠くなる。Cなどの高級言語は、この様なシステムの大規模化に対応するため
  に生まれたと言ってもよい。スクリプト言語など、ここ、最近は、その傾向が更に顕著であるが。
   (アセンブラや機械語は低級言語と呼ばれる事もある。あまり、一般的な呼び方ではないかもしれないが)


   ※アセンブラとは、アセンブリコードで記述されたプログラムを、機械語に変換するソフトウエアのこと。


  上の例で、

  INC.W #1, R0

 というアセンブリ・コードがある。これは、「レジスタR0に1を加算して、その結果をR0に保管せよ」、という意味になり、これを機械語命令に変換する(アセンブルする)と、機械語命令は 0B50 (16進)となる。

 FFE244    0B50       // いずれも16進数で表現

 となる。 また、この命令は、メモリアドレスFFE244に保管されている。

 CPUはこのプログラムを、どのように実行するかと言うと、

① ER7レジスタの中身を-2する。(意味がわからん && もっと知りたいという勉強熱心な人はこちら
②R0レジスタ=変数のc。
 R0レジスタの内容からR0レジスタの内容を減算する。(結局、R0レジスタの値は0になる。R0に0を代入すればよさそうなものだが、この方が高速に処理できるため。 アセンブラでは、この様な記述が良く見られるので、意味を深読みしてハマらないように。)
③ R0の内容を、ER7が示しているメモリアドレスに保管する。(つまりER7がポインタとして動作しているということ)
④ メモリアドレスFFE242の命令(つまり⑤)にジャンプする。(これも、一見して無意味に見える。所詮、機械がやることなんて、こんなもんだ。...っ言うか、人がそういう風にコンパイラをプログラミングしたってことで、コンピュータには、何の責任もない。テンプレートを使って機械語に展開してるとこういうことになるんだろう。
⑤ ER7が示しているメモリに保管されたデータを読み取って、R0にコピーする。(③との兼ね合いで、これも何だか無駄っぽい処理だが、コンパイラは、Cのソースを律儀に機械語に変換している事の現れである。機械には、人間の意図なんて読み取れないのである。       つまり、コンパイラの理解し易いようにコーディングを行うと、"より、高速な(またコンパクトな)プログラムが作成できる"、ということになる...と思う。 いまどきのコンパイラは人間様の我儘な命令に対応できるよう、相当賢くなってはいるが。
⑥ R0の値を+1する。つまりc++。
⑦ R0の値をER7が示すメモリアドレスに保管する。CPUのレジスタは演算のための一時記憶装置なので、演算が終わったら、その内容をメモリに保管する必要がある。
⑧ メモリアドレスFFE242の命令(つまり⑤)にジャンプ。(whileによる無限ループ)

 メモリアドレスFFE242とは、変数cの実体ということになる。(スタックに保管されているので、グローバル変数やstatic宣言された変数の様に、恒久的なものではないことに注意)
 

最終更新:2014年09月12日 11:27