CPUの動作を模擬で可視化

加算演算時のCPUのレジスタとメモリを観察

[スタート]ボタンでメモリのアドレス10,11の数値をレジスタに読み込んで和を計算、結果を12に書き込みます。

CPU メモリ
レジスタ名 内容 内容 アドレス
Pカウンタ 1 READ A,(10) 1
READ B,(11) 2
Aレジスタ ADDD A,B 3
WRITE (12),A 4
Bレジスタ STOP 5
3 10
5 11
12

使い方

[スタート]メモリ上のプログラムを次々に実行します
[ステップ実行]1ステップずつ実行します。
[リセット]データをクリアしプログラムカウンタを1に戻します。
[乱数]アドレス10,11の加算するデータを乱数で変化させます。

CPUの動作の解説

実際のCPUはもっとたくさんのレジスタを持っていますしフラグもあるのですが、実行中のアドレスの位置を記録するプログラムカウンタとレジスタが2つあれば、加算の動作をイメージできます。

メモリにはアドレスがあって、プログラムとデータが領域を分けて配置されています。

プログラムは READ A,(10) などと書かれていますが、実際にはデータと同じもの、つまりアドレス10,11,12にあるのと同様のものです。例えばZ80という8ビットCPUでは、16進法で 76 が、HALTという命令でした。これは整数として読めば、2進法で 01110110 10進法では118です。ASCIIの文字コードとして読むと v です。

ここに書いた READ A,(10) なども本当は 119 のような数値がふさわしいのですが、対応する命令で表現しています。

CPUの種類ごとに命令語の体系は異なります。ここに書いた命令は架空のもので、かなりいい加減ですが、ひと目で指示がわかるように考えました。

1.アドレス10のメモリからデータをAレジスタに読み込む

2.アドレス11のメモリからデータをBレジスタに読み込む

3.AレジスタにBレジスタの値を足す

4.Aレジスタの値をアドレス12のメモリに書き出す

5.止まる

というものです。

難をいうと READ A,(10) は1バイト命令でない可能性が高いですが、そもそも架空の命令体系なのでご容赦ください

プログラム解説

アドレスの1〜5まで5つの命令が入っています。まず、ステップ実行で一つずつの動作をしていくように作ります。READ A,(10) は、

(1)アドレス1の背景色を変更

(2)Aレジスタとアドレス10の背景色及びそれらをつなぐパイプの色をつける

(3)アドレス10内の数をAレジスタにコピー

というふうにいくつかの動作がありますが、それぞれをfunctionに書いてsetTimeout()を使って遅延させて次々に呼び出させます。

onloadでの初期化

試行錯誤を見越してレジスタやメモリにはそれぞれidをつけます。

データのコピーの動線を示す黄色の「パイプ」はtableセルの連続ですが、これはgetElementsByClassNameで配列にしておきます。class="m10toa" はアドレス10からAレジスタへのパイプの通り道です。タブっている場合は全部のクラス名をスペース区切りで列挙します。

ボタンは状況により使用不可にします。そのためにbtnxxxxとgoalを用意しておきます。

counterはプログラムカウンタの値。この値がgoalを超えたらボタンを使用可にします。この説明は後で。

function cpuinit(){
   m10toas = document.getElementsByClassName( "m10toa" );
   m11tobs = document.getElementsByClassName( "m11tob" );
   atom12s = document.getElementsByClassName( "atom12" );
   m1 = document.getElementById("m1");
   m2 = document.getElementById("m2");
   m3 = document.getElementById("m3");
   m4 = document.getElementById("m4");
   m5 = document.getElementById("m5");
   m10 = document.getElementById("m10");
   m11 = document.getElementById("m11");
   m12 = document.getElementById("m12");
   a = document.getElementById("a");
   b = document.getElementById("b");
   pc = document.getElementById("pc");
   actionadd = document.getElementById( "actionadd" );
   btnreset = document.getElementById( "reset" );
   btnstart = document.getElementById( "start" );
   btnstep  = document.getElementById( "step" );
   btnrand  = document.getElementById( "rand" );
   counter=1;
   goal=5;
}
window.onload = function(){
  cpuinit();
}

READ A, (10)

s1() カウンタの値(ここでは必ず1)をプログラムカウンタに入れ、アドレス1の背景色を変更。s1after()を1秒後に呼び出す

function s1(){
   pc.firstChild.nodeValue=counter;
   m1.setAttribute("style","background-color:#6ff;");
   setTimeout("s1after()",1000);
}

s1after() Aレジスタとアドレス10の背景色及びそれらをつなぐパイプの色をつける。s1afterafter()を1秒後に呼び出す

function s1after(){
   m10.setAttribute("style","background-color:#6ff;");
   a.setAttribute("style","background-color:#6ff;");
   for (i = 0; m10toas.length>i; i++) {
       m10toas[i].setAttribute("style","background-color:#ff9;");
   }
   setTimeout("s1afterafter()",1000);
}

s1afterafter() アドレス10内の数をAレジスタにコピー。カウンタの値を2にする。

activateButtons()はボタンを使用可にする関数です。この説明は後で。

function s1afterafter(){
   a.firstChild.nodeValue= m10.firstChild.nodeValue; //"3";
   counter=2;
   if (counter>goal) activateButtons();
}

READ B, (11)

s2() カウンタの値(ここでは必ず2)をプログラムカウンタに入れ、アドレス1,10,Aレジスタ、パイプの背景色を戻す。アドレス2の色を変更、s2after()を1秒後に呼び出す

function s2(){
   pc.firstChild.nodeValue=counter;
   m1.setAttribute("style","background-color:#cff;");
   m10.setAttribute("style","background-color:#cff;");
   a.setAttribute("style","background-color:#cff;");
   for (i = 0; m10toas.length>i; i++) {
       m10toas[i].setAttribute("style","background-color:#fff;");
   }
   m2.setAttribute("style","background-color:#6ff;");
   setTimeout("s2after()",1000);
}

s2after() Bレジスタとアドレス11の背景色及びそれらをつなぐパイプの色をつける。s2afterafter()を1秒後に呼び出す

function s2after(){
   m11.setAttribute("style","background-color:#6ff;");
   b.setAttribute("style","background-color:#6ff;");
   for (i = 0; m11tobs.length>i; i++) {
       m11tobs[i].setAttribute("style","background-color:#ff9;");
   }
   setTimeout("s2afterafter()",1000);
}

s2afterafter() アドレス11内の数をBレジスタにコピー。カウンタの値を3にする。

function s2afterafter(){
   b.firstChild.nodeValue= m11.firstChild.nodeValue;  //"5";
   counter=3;
   if (counter>goal) activateButtons();
}

ADDD A,B

s3() カウンタの値(ここでは必ず3)をプログラムカウンタに入れ、アドレス2,11,Bレジスタ、パイプの背景色を戻す。アドレス3の色を変更、s3after()を1秒後に呼び出す

function s3(){
   pc.firstChild.nodeValue=counter;
   m2.setAttribute("style","background-color:#cff;");
   m11.setAttribute("style","background-color:#cff;");
   b.setAttribute("style","background-color:#cff;");
   for (i = 0; m11tobs.length>i; i++) {
       m11tobs[i].setAttribute("style","background-color:#fff;");
   }
   m3.setAttribute("style","background-color:#6ff;");
   setTimeout("s3after()",1000);
}

s3after() AレジスタとBレジスタの背景色及びそれらをつなぐ加算の表示をする。s3afterafter()を1秒後に呼び出す

function s3after(){
   a.setAttribute("style","background-color:#6ff;");
   b.setAttribute("style","background-color:#6ff;");
   actionadd.setAttribute("style","background-color:#cfc;color:#060;text-align:right;");
   actionadd.firstChild.nodeValue="⇧+";
   setTimeout("s3afterafter()",1000);
}

s3afterafter() AレジスタとBレジスタの値を加えAレジスタに入れる。nodeValueは文字列なので、parseInt()が必要。

カウンタの値を4にする。

function s3afterafter(){
   var v=parseInt(a.firstChild.nodeValue)+parseInt(b.firstChild.nodeValue);
   a.firstChild.nodeValue=String(v);
   counter=4;
   if (counter>goal) activateButtons();
}

WRITE (12),A

s4() カウンタの値(ここでは必ず4)をプログラムカウンタに入れ、アドレス3,AレジスタとBレジスタの背景色及びそれらをつなぐ加算の表示を元に戻す。アドレス4の色を変更。s4after()を1秒後に呼び出す

function s4(){
   pc.firstChild.nodeValue=counter;
   m3.setAttribute("style","background-color:#cff;");
   a.setAttribute("style","background-color:#cff;");
   b.setAttribute("style","background-color:#cff;");
   actionadd.setAttribute("style","background-color:#fff;");
   actionadd.firstChild.nodeValue=" ";
   m4.setAttribute("style","background-color:#6ff;");
   setTimeout("s4after()",1000);
}

s4after() Aレジスタとアドレス12の背景色及びそれらをつなぐパイプの色をつける。s4afterafter()を1秒後に呼び出す

function s4after(){
   a.setAttribute("style","background-color:#6ff;");
   m12.setAttribute("style","background-color:#6ff;");
   for (i = 0; atom12s.length>i; i++) {
       atom12s[i].setAttribute("style","background-color:#ff9;");
   }
   setTimeout("s4afterafter()",1000);
}

s4afterafter() Aレジスタ内の数をアドレス12にコピー。カウンタの値を5にする。

function s4afterafter(){
   m12.firstChild.nodeValue= a.firstChild.nodeValue;  //"8";
   counter=5;
   if (counter>goal) activateButtons();
}

STOP

s5() カウンタの値(ここでは必ず5)をプログラムカウンタに入れ、アドレス4,12,Aレジスタとそれらをつなぐパイプの背景色を元に戻す。アドレス5の色を変更。

最後にボタンにつけられた無効化の指定を取り除く。これは無効化のAttributeをしていなくても害はない。これは「スタート」で一括実行するときのため

function s5(){
   pc.firstChild.nodeValue=counter;
   m4.setAttribute("style","background-color:#cff;");
   a.setAttribute("style","background-color:#cff;");
   m12.setAttribute("style","background-color:#cff;");
   for (i = 0; atom12s.length>i; i++) {
       atom12s[i].setAttribute("style","background-color:#fff;");
   }
   m5.setAttribute("style","background-color:#6ff;");
   activateButtons();
}

ボタン制御

動作中に追加でボタンを押されると辻褄が合わなくなることがあります。動作中はボタンを使用不可にします。

html4の時代には、属性としてdisabledと書きましたが、xhtmlの時代に属性は必ずkey="value"と書くべしということになってdisabled="disabled"と書くという不格好な状態でした。html5になって戻りましたがxhtml方式で書いても容認されます。

ということで、setAttribute("disabled","disabled")とします。

全部のボタンを使用不可にする関数を定義しておきます。

function disableButtons(){
   btnreset.setAttribute("disabled","disabled");
   btnstart.setAttribute("disabled","disabled");
   btnstep.setAttribute("disabled","disabled");
   btnrand.setAttribute("disabled","disabled");
}

使用可にするには、disabledと書かないということですから、removeAttribute("disabled")とすればいいということになります。

disabled="" という書き方も認められていますから、setAttribute("disabled","") でもいいはずですが、試していません。

function activateButtons(){
   btnreset.removeAttribute("disabled");
   btnstart.removeAttribute("disabled");
   btnstep.removeAttribute("disabled");
   btnrand.removeAttribute("disabled");
}

ステップ実行

最初に作ったのは全部を順番にするものでしたが、もっとゆっくり見たいということもあろうかとステップごとを作りました。必ず次のステップを実行します。どのステップを実行するかは選択できません。

説明はステップ実行が先のほうがわかりやすいでしょう。

まずボタンを使用不可にして、goalに現在のcounterの値を代入し、counterの値で分岐します。if, else if でも書けますが、caseの方が見通しが良いと思います。ただし、break を書き忘れると次のcaseも実行してしまいます。

function step(){
   disableButtons();
   goal = counter;
   switch (counter){
      case 1: s1(); break;
      case 2: s2(); break;
      case 3: s3(); break;
      case 4: s4(); break;
      case 5: s5(); break;
   }
}

counterの値は初期値は1にされていますからs1()に進み、s1afterafter()に行ってcounter=2となったあとcounter>goalがtrueになるのでボタンが使用可能になってステップ実行が終わります。

次のステップ実行はcounterが2の状態からスタートです。

スタート(連続実行)

counterが5でなければ、そのままスタートします。これはステップ実行の途中から、「あとは最後まで自動で」ということになります。1からスタートしたければ[リセット]ボタンを使います。

最後までボタンを使用不可にするためにgoal=5にします。

この先はちょっとトリッキーです。

まず、switchにbreakがないのに注目してください。counterが1のとき、case1からcase5まで全部実行します。counterが3ならcase3から実行します。

次がsetTimeout()の時間です。setTimeout()は時間が来たら実行されるように「セットする」だけで、すぐに次の命令に移ります。だから同じ待ち時間をセットすると「ほぼ同時に」実行してしまいます。それぞれのステップで2秒かけていますから、3秒ずつ遅らせてセットすることで、ちょうどよく次々に実行させることができます。

一つのステップが終わることで、次のステップがトリガーされるのではなく、最初に全部スケジュールを組んでしまうのです。

msecnowは途中から始める場合に、待ち時間を調整するためにあります。

function start(){
   if ( counter==5 ){resetstate();}
   goal = 5;
   disableButtons();
   msecnow=(counter-1)*3000;
   switch (counter){
      case 1:
         s1();
      case 2: 
         setTimeout("s2()",3000-msecnow);
      case 3: 
         setTimeout("s3()",6000-msecnow);
      case 4: 
         setTimeout("s4()",9000-msecnow);
      case 5: 
         setTimeout("s5()",12000-msecnow);
   }
}

setTimeout(somecomand,3000)は実行に3秒かかるのではなく、すぐに次の命令を実行します。これを忘れていると予想外のことが起こるかもしれません。たとえば、ボタンを使用可能にするのをswitch文の次に書くと使用不可になっている時間がほとんどないということになります。

リセット

全部の背景色を元も戻し、レジスタをクリア、プログラムカウンタを1にセットします。メモリの内容はアドレス12だけクリアします。本当はアドレス12はクリアしないのが現実に近いと思うのですが、わかりやすさを優先しました。

function resetstate(){
   m1.setAttribute("style","background-color:#cff;");
   m2.setAttribute("style","background-color:#cff;");
   m3.setAttribute("style","background-color:#cff;");
   m4.setAttribute("style","background-color:#cff;");
   m5.setAttribute("style","background-color:#cff;");
   m10.setAttribute("style","background-color:#cff;");
   m11.setAttribute("style","background-color:#cff;");
   m12.setAttribute("style","background-color:#cff;");
   a.setAttribute("style","background-color:#cff;");
   a.firstChild.nodeValue=" ";
   b.setAttribute("style","background-color:#cff;");
   b.firstChild.nodeValue=" ";
   m12.firstChild.nodeValue=" ";
   actionadd.setAttribute("style","background-color:#fff;");
   actionadd.firstChild.nodeValue=" ";
   for (i = 0; m10toas.length>i; i++) {
       m10toas[i].setAttribute("style","background-color:#fff;");
   }
   for (i = 0; m11tobs.length>i; i++) {
       m11tobs[i].setAttribute("style","background-color:#fff;");
   }
   for (i = 0; atom12s.length>i; i++) {
       atom12s[i].setAttribute("style","background-color:#fff;");
   }
   counter=1;
   pc.firstChild.nodeValue=counter;
}

乱数

足し算の値として使用するアドレス10,11の乱数で変更します。

function setRandomNum(){
     var v = Math.random() * 256;    //変数aを0以上~256未満の数にする
     var s = String(Math.floor( v )) ;
     m10.firstChild.nodeValue= s;
     v = Math.random() * 256;
     s = String(Math.floor( v )) ;
     m11.firstChild.nodeValue= s;
}

HTML

<div class="table">
<table id="desktop">
<tr>
<td colspan="2">CPU</td>
<td class="pipe"></td>
<td class="pipe"></td>
<td class="pipe"></td>
<td colspan="2">メモリ</td>
</tr>
<tr>
<td class="wleft regname">レジスタ名</td>
<td class="wright center">内容</td>
<td></td>
<td></td>
<td></td>
<td class="wleft center">内容</td>
<td class="wright"><small>アドレス</small></td>
</tr>
<tr>
<td class="wleft regname">Pカウンタ</td>
<td id="pc">1</td>
<td></td>
<td></td>
<td></td>
<td id="m1">READ A,(10)</td>
<td class="wright">1</td>
</tr>
<tr>
<td class="wleft"></td>
<td class="wright"></td>
<td></td>
<td></td>
<td></td>
<td id="m2">READ B,(11)</td>
<td class="wright">2</td>
</tr>
<tr>
<td class="wleft regname">Aレジスタ</td>
<td id="a"> </td>
<td class="m10toa atom12"></td>
<td class="m10toa atom12"></td>
<td></td>
<td id="m3">ADDD A,B</td>
<td class="wright">3</td>
</tr>
<tr>
<td class="wleft"></td>
<td id="actionadd" class="wright"> </td>
<td></td>
<td class="m10toa atom12"></td>
<td></td>
<td id="m4">WRITE (12),A</td>
<td class="wright">4</td>
</tr>
<tr>
<td class="wleft regname">Bレジスタ</td>
<td id="b"> </td>
<td class="m11tob"></td>
<td class="m10toa m11tob atom12"></td>
<td></td>
<td id="m5">STOP</td>
<td class="wright">5</td>
</tr>
<tr>
<td class="wleft wbottom"></td>
<td class="wright wbottom"></td>
<td></td>
<td class="m10toa m11tob atom12"></td>
<td></td>
<td class="wleft"></td>
<td class="wright"></td>
</tr>
<tr>
<td></td>
<td></td>
<td></td>
<td class="m10toa m11tob atom12"></td>
<td class="m10toa"></td>
<td id="m10">3</td>
<td class="wright">10</td>
</tr>
<tr>
<td></td>
<td></td>
<td></td>
<td class="m11tob atom12"></td>
<td class="m11tob"></td>
<td id="m11">5</td>
<td class="wright">11</td>
</tr>
<tr>
<td></td>
<td></td>
<td></td>
<td class="atom12"></td>
<td class="atom12"></td>
<td id="m12"> </td>
<td class="wright">12</td>
</tr>
<tr>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td class="wleft wbottom"></td>
<td class="wright wbottom"></td>
</tr>
</table>
</div>

<p>
<button type="button" onclick="resetstate()" id="reset">リセット</button>
<button type="button" onclick="start()" id="start">スタート</button>
<button type="button" onclick="step()" id="step">ステップ実行</button>
<button type="button" onclick="setRandomNum()" id="rand">乱数</button>
</p>

css

table{
  margin:1% 2%;
  white-space: nowrap;
  }
td,table{border:none;}
#pc,#a,#b{
  background-color:#cff;
  border:solid 1px #999;
  text-align:right;
  width:8em;
}
#m1,#m2,#m3,
#m4,#m5,
#m10,#m11,#m12
{
  background-color:#cff;
  border:solid 1px #999;
}
#m1,#m2,#m3,
#m4,#m5
{
  font-weight:bold;
  font-family:courier, monospace;
}
#m10,#m11,#m12
{
  text-align:right;
  width:8em;
}
td.pipe{
  width:1ex;
}
td[colspan],td.center{
  text-align:center;
  border-bottom:solid 1px #ccc;
}
td.wleft{
  border-left:solid 1px #ccc;
}
td.wright{
  border-right:solid 1px #ccc;
}
td.wbottom{
  border-bottom:solid 1px #ccc;
}
td.regname{
  text-align:right;
  font-size:80%;
}