【rubyでcomet2を作る】④プログラムを実行する

### はじめに
[【rubyでcomet2を作る】③CPUオブジェクト、メモリオブジェクトを定義する2](https://qiita.com/223yuta/items/dc5e95c6be7982e1f74f)の続きです。
今回は、前回までに作成したCPUオブジェクト、メモリオブジェクトを使って、下記4つのプログラムを実行していきます。
- 単純加算処理
- 条件分岐処理
- 繰返処理
- サブルーチン処理

普段簡単に書いているこのような処理をCPUではどのように処理しているのか見ていきたいと思います。

[完成コード](https://github.com/223yu/virtual_pc)

###実行状況を見やすくする

プログラムの実行状況を見やすくするために、下記変更を加えます。
- 命令読出(フェッチ)後、実行する命令を表示する
- 命令実行後、レジスタの状況を表示する

```ruby:cpu.rb
      # 命令の読み出し（フェッチ）
      ir = memory.send("m#{pr}")
      self.pr += 1

      # 実行する命令を表示する
      if ir[2].instance_of?(Array)
        puts "#{ir[0]} #{ir[1]} #{ir[2].join(',')}"
      elsif ir[2].nil?
        puts "#{ir[0]} #{ir[1]}"
      else
        puts "#{ir[0]} #{ir[1]} #{ir[2]}"
      end
```

```ruby:cpu.rb
  # レジスタの状況を表示する
  def observe_register
    puts "PR:#{self.pr}, SP:#{self.sp}, FR[ZF,SF,OF]:#{self.fr}"
    puts "gr0:#{self.gr0}, gr1:#{self.gr1}, gr2:#{self.gr2}, gr3:#{self.gr3}"
    puts "-------------------------------------------------------"
  end
```

### プログラムを実行する
例として4つのプログラムを実行していきます。実行するプログラムはどこに書いてもいいのですが、今回はmain.rbに書いていくことにします。

#### ①加算結果を取得する
定数Aと定数Bの加算処理を行い、結果をANSに保存します。

```ruby:main.rb
require './lib/cpu'
require './lib/memory'

memory = Memory.new
cpu = CPU.new

# 例①加算結果を取得する
#              ラベル    オペコード　   オペランド
memory.m0 =   ['',      'START'                   ]
memory.m1 =   ['',      'LD',     ['gr0','A']     ] # GR0にAの値を格納
memory.m2 =   ['',      'LD',     ['gr1','B']     ] # GR1にBの値を格納
memory.m3 =   ['',      'ADDA',   ['gr0','gr1']   ] # GR0とGR1の値を加算
memory.m4 =   ['',      'ST',     ['gr0','ANS']   ] # GR0の値をANSに格納
memory.m5 =   ['A',     'DC',     '123'           ] # 定数Aを定義
memory.m6 =   ['B',     'DC',     '456'           ] # 定数Bを定義
memory.m7 =   ['ANS',   'DS',     '1'             ] # 領域ANSを確保
memory.m8 =   ['',      'END'                     ]

# m40-m49をスタック領域として確保
memory.m40 =  ['']
memory.m41 =  ['']
memory.m42 =  ['']
memory.m43 =  ['']
memory.m44 =  ['']
memory.m45 =  ['']
memory.m46 =  ['']
memory.m47 =  ['']
memory.m48 =  ['']
memory.m49 =  ['']

cpu.control(memory)

puts "参考：memory7の状況 #{memory.m7}"
```

実行結果は以下の通りです。

```terminal
 START
PR:1, SP:49, FR[ZF,SF,OF]:[0, 0, 0]
gr0:, gr1:, gr2:, gr3:
-------------------------------------------------------
 LD gr0,A
"gr0に123を読み込みました。"
PR:2, SP:49, FR[ZF,SF,OF]:[0, 0, 0]
gr0:123, gr1:, gr2:, gr3:
-------------------------------------------------------
 LD gr1,B
"gr1に456を読み込みました。"
PR:3, SP:49, FR[ZF,SF,OF]:[0, 0, 0]
gr0:123, gr1:456, gr2:, gr3:
-------------------------------------------------------
 ADDA gr0,gr1
"gr0にgr0のデータ+gr1のデータを格納しました。"
PR:4, SP:49, FR[ZF,SF,OF]:[0, 0, 0]
gr0:579, gr1:456, gr2:, gr3:
-------------------------------------------------------
 ST gr0,ANS
"ANSにgr0のデータを格納しました。"
PR:5, SP:49, FR[ZF,SF,OF]:[0, 0, 0]
gr0:579, gr1:456, gr2:, gr3:
-------------------------------------------------------
A DC 123
PR:6, SP:49, FR[ZF,SF,OF]:[0, 0, 0]
gr0:579, gr1:456, gr2:, gr3:
-------------------------------------------------------
B DC 456
PR:7, SP:49, FR[ZF,SF,OF]:[0, 0, 0]
gr0:579, gr1:456, gr2:, gr3:
-------------------------------------------------------
ANS DS 579
PR:8, SP:49, FR[ZF,SF,OF]:[0, 0, 0]
gr0:579, gr1:456, gr2:, gr3:
-------------------------------------------------------
 END
参考：memory7の状況 ["ANS", "DS", "579"]
```
意図した通りに動いています。普段であれば下記の1行で済むことですが、CPUはこの計算をこれだけの工程を踏んで実行しているということが分かります。

```ruby
ans = 123 + 456
```

#### ②絶対値を取得する（条件分岐）
定数Aの絶対値を取得し、結果をANSに保存します。プログラム領域のコードのみ記載します。

```ruby:main.rb
# 例②絶対値を取得する（条件分岐）
#              ラベル    オペコード　   オペランド
memory.m0 =   ['ABS',   'START'                   ]
memory.m1 =   ['',      'LAD',    ['gr0','0']     ] # GR0に0を格納
memory.m2 =   ['',      'LD',     ['gr1','A']     ] # GR1にAの値を格納
memory.m3 =   ['',      'CPA',    ['gr0','gr1']   ] # GR0とGR1を比較
memory.m4 =   ['',      'JMI',    ['LABEL']       ] # GR0 < GR1ならLABELへジャンプ
memory.m5 =   ['',      'XOR',    ['gr1','65535'] ] # GR1のビットの並びを反転
memory.m6 =   ['',      'ADDA',   ['gr1','1']     ] # GR1の値に1を加算
memory.m7 =   ['LABEL', 'ST',     ['gr1','ANS']   ] # GR1の値をANSに格納
memory.m8 =   ['A',     'DC',     '-123'          ] # 定数Aを定義
memory.m9 =   ['ANS',   'DS',     '1'             ] # 領域ANSを確保
memory.m10 =  ['',      'END'                     ]

```

実行結果
m5,m6にて絶対値に変換しています。

```terminal
ABS START
PR:1, SP:49, FR[ZF,SF,OF]:[0, 0, 0]
gr0:, gr1:, gr2:, gr3:
-------------------------------------------------------
 LAD gr0,0
"gr0に0を格納しました。"
PR:2, SP:49, FR[ZF,SF,OF]:[0, 0, 0]
gr0:0, gr1:, gr2:, gr3:
-------------------------------------------------------
 LD gr1,A
"gr1に-123を読み込みました。"
PR:3, SP:49, FR[ZF,SF,OF]:[0, 0, 0]
gr0:0, gr1:-123, gr2:, gr3:
-------------------------------------------------------
 CPA gr0,gr1
"gr0のデータとgr1のデータの算術比較を行いました。"
PR:4, SP:49, FR[ZF,SF,OF]:[0, 0, 0]
gr0:0, gr1:-123, gr2:, gr3:
-------------------------------------------------------
 JMI LABEL
PR:5, SP:49, FR[ZF,SF,OF]:[0, 0, 0]
gr0:0, gr1:-123, gr2:, gr3:
-------------------------------------------------------
 XOR gr1,65535
"gr1にgr1のデータと65535の排他的論理和を格納しました。"
PR:6, SP:49, FR[ZF,SF,OF]:[0, 0, 0]
gr0:0, gr1:122, gr2:, gr3:
-------------------------------------------------------
 ADDA gr1,1
"gr1にgr1のデータ+1を格納しました。"
PR:7, SP:49, FR[ZF,SF,OF]:[0, 0, 0]
gr0:0, gr1:123, gr2:, gr3:
-------------------------------------------------------
LABEL ST gr1,ANS
"ANSにgr1のデータを格納しました。"
PR:8, SP:49, FR[ZF,SF,OF]:[0, 0, 0]
gr0:0, gr1:123, gr2:, gr3:
-------------------------------------------------------
A DC -123
PR:9, SP:49, FR[ZF,SF,OF]:[0, 0, 0]
gr0:0, gr1:123, gr2:, gr3:
-------------------------------------------------------
ANS DS 123
PR:10, SP:49, FR[ZF,SF,OF]:[0, 0, 0]
gr0:0, gr1:123, gr2:, gr3:
-------------------------------------------------------
 END
参考：memory9の状況 ["ANS", "DS", "123"]
```

実行結果（A=123、元から整数の時）
m3の結果を元に、m4の処理でm7にジャンプしています。（m5,m6を飛ばしています）

```terminal
ABS START
PR:1, SP:49, FR[ZF,SF,OF]:[0, 0, 0]
gr0:, gr1:, gr2:, gr3:
-------------------------------------------------------
 LAD gr0,0
"gr0に0を格納しました。"
PR:2, SP:49, FR[ZF,SF,OF]:[0, 0, 0]
gr0:0, gr1:, gr2:, gr3:
-------------------------------------------------------
 LD gr1,A
"gr1に123を読み込みました。"
PR:3, SP:49, FR[ZF,SF,OF]:[0, 0, 0]
gr0:0, gr1:123, gr2:, gr3:
-------------------------------------------------------
 CPA gr0,gr1
"gr0のデータとgr1のデータの算術比較を行いました。"
PR:4, SP:49, FR[ZF,SF,OF]:[0, 1, 0]
gr0:0, gr1:123, gr2:, gr3:
-------------------------------------------------------
 JMI LABEL
"PRを7に変更しました。"
PR:7, SP:49, FR[ZF,SF,OF]:[0, 1, 0]
gr0:0, gr1:123, gr2:, gr3:
-------------------------------------------------------
LABEL ST gr1,ANS
"ANSにgr1のデータを格納しました。"
PR:8, SP:49, FR[ZF,SF,OF]:[0, 1, 0]
gr0:0, gr1:123, gr2:, gr3:
-------------------------------------------------------
A DC 123
PR:9, SP:49, FR[ZF,SF,OF]:[0, 1, 0]
gr0:0, gr1:123, gr2:, gr3:
-------------------------------------------------------
ANS DS 123
PR:10, SP:49, FR[ZF,SF,OF]:[0, 1, 0]
gr0:0, gr1:123, gr2:, gr3:
-------------------------------------------------------
 END
参考：memory9の状況 ["ANS", "DS", "123"]
```

#### ③1からNUMまでの合計を求める（繰返処理）
1からNUMに定義した定数までの合計を取得し、ANSに保存します。条件を満たすまで、m3~m7の処理が繰り返し実行されています。今回は合計を求めているだけですが、m6が繰り返しの中身になりますので、m6を調整すると色々なことができるようになります。

```ruby:main.rb
# 例③1からNUMまでの合計を求める（繰返処理）
#              ラベル    オペコード　   オペランド
memory.m0 =   ['SUM',   'START'                   ]
memory.m1 =   ['',      'LAD',    ['gr0','0']     ] # 0をGR0に格納
memory.m2 =   ['',      'LAD',    ['gr1','0']     ] # 0をGR1に格納
memory.m3 =   ['LABEL1','ADDA',   ['gr0','1']     ] # GR0に1を加算
memory.m4 =   ['',      'CPA',    ['gr0','NUM']   ] # GR0とNUMを算術比較
memory.m5 =   ['',      'JPL',    ['LABEL2']      ] # GR0 > NUM の場合LABEL2にジャンプ
memory.m6 =   ['',      'ADDA',   ['gr1','gr0']   ] # GR1にGR0を加算
memory.m7 =   ['',      'JUMP',   ['LABEL1']      ] # 無条件にLABEL1へジャンプ
memory.m8 =   ['LABEL2','ST',     ['gr1','ANS']   ] # ANSにGR1を格納
memory.m9 =   ['NUM',   'DC',     '3'             ] # 定数NUMを定義
memory.m10 =  ['ANS',   'DS',     '1'             ] # 領域ANSを確保
memory.m11 =  ['',      'END'                     ]
```

実行結果

```terminal
SUM START
PR:1, SP:49, FR[ZF,SF,OF]:[0, 0, 0]
gr0:, gr1:, gr2:, gr3:
-------------------------------------------------------
 LAD gr0,0
"gr0に0を格納しました。"
PR:2, SP:49, FR[ZF,SF,OF]:[0, 0, 0]
gr0:0, gr1:, gr2:, gr3:
-------------------------------------------------------
 LAD gr1,0
"gr1に0を格納しました。"
PR:3, SP:49, FR[ZF,SF,OF]:[0, 0, 0]
gr0:0, gr1:0, gr2:, gr3:
-------------------------------------------------------
LABEL1 ADDA gr0,1
"gr0にgr0のデータ+1を格納しました。"
PR:4, SP:49, FR[ZF,SF,OF]:[0, 0, 0]
gr0:1, gr1:0, gr2:, gr3:
-------------------------------------------------------
 CPA gr0,NUM
"gr0のデータと3の算術比較を行いました。"
PR:5, SP:49, FR[ZF,SF,OF]:[0, 1, 0]
gr0:1, gr1:0, gr2:, gr3:
-------------------------------------------------------
 JPL LABEL2
PR:6, SP:49, FR[ZF,SF,OF]:[0, 1, 0]
gr0:1, gr1:0, gr2:, gr3:
-------------------------------------------------------
 ADDA gr1,gr0
"gr1にgr1のデータ+gr0のデータを格納しました。"
PR:7, SP:49, FR[ZF,SF,OF]:[0, 0, 0]
gr0:1, gr1:1, gr2:, gr3:
-------------------------------------------------------
 JUMP LABEL1
"PRを3に変更しました。"
PR:3, SP:49, FR[ZF,SF,OF]:[0, 0, 0]
gr0:1, gr1:1, gr2:, gr3:
-------------------------------------------------------
LABEL1 ADDA gr0,1
"gr0にgr0のデータ+1を格納しました。"
PR:4, SP:49, FR[ZF,SF,OF]:[0, 0, 0]
gr0:2, gr1:1, gr2:, gr3:
-------------------------------------------------------
 CPA gr0,3
"gr0のデータと3の算術比較を行いました。"
PR:5, SP:49, FR[ZF,SF,OF]:[0, 1, 0]
gr0:2, gr1:1, gr2:, gr3:
-------------------------------------------------------
 JPL LABEL2
PR:6, SP:49, FR[ZF,SF,OF]:[0, 1, 0]
gr0:2, gr1:1, gr2:, gr3:
-------------------------------------------------------
 ADDA gr1,gr0
"gr1にgr1のデータ+gr0のデータを格納しました。"
PR:7, SP:49, FR[ZF,SF,OF]:[0, 0, 0]
gr0:2, gr1:3, gr2:, gr3:
-------------------------------------------------------
 JUMP LABEL1
"PRを3に変更しました。"
PR:3, SP:49, FR[ZF,SF,OF]:[0, 0, 0]
gr0:2, gr1:3, gr2:, gr3:
-------------------------------------------------------
LABEL1 ADDA gr0,1
"gr0にgr0のデータ+1を格納しました。"
PR:4, SP:49, FR[ZF,SF,OF]:[0, 0, 0]
gr0:3, gr1:3, gr2:, gr3:
-------------------------------------------------------
 CPA gr0,3
"gr0のデータと3の算術比較を行いました。"
PR:5, SP:49, FR[ZF,SF,OF]:[1, 0, 0]
gr0:3, gr1:3, gr2:, gr3:
-------------------------------------------------------
 JPL LABEL2
PR:6, SP:49, FR[ZF,SF,OF]:[1, 0, 0]
gr0:3, gr1:3, gr2:, gr3:
-------------------------------------------------------
 ADDA gr1,gr0
"gr1にgr1のデータ+gr0のデータを格納しました。"
PR:7, SP:49, FR[ZF,SF,OF]:[0, 0, 0]
gr0:3, gr1:6, gr2:, gr3:
-------------------------------------------------------
 JUMP LABEL1
"PRを3に変更しました。"
PR:3, SP:49, FR[ZF,SF,OF]:[0, 0, 0]
gr0:3, gr1:6, gr2:, gr3:
-------------------------------------------------------
LABEL1 ADDA gr0,1
"gr0にgr0のデータ+1を格納しました。"
PR:4, SP:49, FR[ZF,SF,OF]:[0, 0, 0]
gr0:4, gr1:6, gr2:, gr3:
-------------------------------------------------------
 CPA gr0,3
"gr0のデータと3の算術比較を行いました。"
PR:5, SP:49, FR[ZF,SF,OF]:[0, 0, 0]
gr0:4, gr1:6, gr2:, gr3:
-------------------------------------------------------
 JPL LABEL2
"PRを8に変更しました。"
PR:8, SP:49, FR[ZF,SF,OF]:[0, 0, 0]
gr0:4, gr1:6, gr2:, gr3:
-------------------------------------------------------
LABEL2 ST gr1,ANS
"ANSにgr1のデータを格納しました。"
PR:9, SP:49, FR[ZF,SF,OF]:[0, 0, 0]
gr0:4, gr1:6, gr2:, gr3:
-------------------------------------------------------
NUM DC 3
PR:10, SP:49, FR[ZF,SF,OF]:[0, 0, 0]
gr0:4, gr1:6, gr2:, gr3:
-------------------------------------------------------
ANS DS 6
PR:11, SP:49, FR[ZF,SF,OF]:[0, 0, 0]
gr0:4, gr1:6, gr2:, gr3:
-------------------------------------------------------
 END
参考：memory10の状況 ["ANS", "DS", "6"]
```


#### ④平均値を取得する（サブルーチン）
DATA1とDATA2の平均値を取得し、AVEに保存します。CALL命令,RET命令を使ってサブルーチンに一部処理を渡しています。

```ruby:main.rb
#              ラベル    オペコード　   オペランド
memory.m0 =   ['MAIN',  'START'                   ] # メインルーチンの先頭
memory.m1 =   ['',      'LD',     ['gr0','DATA1'] ] # DATA1をGR0に格納
memory.m2 =   ['',      'LD',     ['gr1','DATA2'] ] # DATA2をGR1に格納
memory.m3 =   ['',      'CALL',   ['SUB']         ] # サブルーチン呼出
memory.m4 =   ['',      'ST',     ['gr0','AVE']   ] # 処理結果をAVEに格納
memory.m5 =   ['DATA1', 'DC',     '100'           ] # 定数DATA1を定義
memory.m6 =   ['DATA2', 'DC',     '200'           ] # 定数DATA2を定義
memory.m7 =   ['AVE',   'DS',     '1'             ] # 領域AVEを確保
memory.m8 =   ['',      'END'                     ] # メインルーチンの末尾
memory.m9 =   ['SUB',   'START'                   ] # サブルーチンの先頭
memory.m10 =  ['',      'ADDA',   ['gr0','gr1']   ] # GR0とGR1を加算しGR0に格納
memory.m11 =  ['',      'SRA',    ['gr0','1']     ] # GR0を1ビット右シフト(1/2にする)
memory.m12 =  ['',      'RET'                     ] # メインルーチンに戻る
memory.m13 =  ['',      'END'                     ] # サブルーチンの末尾
```

実行結果
サブルーチンに処理を渡していますので、下記の順番でPRが動いています。
0→1→2→3→9→10→11→12→4→5→6→7→8
また、CALL命令とRET命令でSPの値を変更することで、この処理を実現していることが分かります。

```terminal
MAIN START
PR:1, SP:49, FR[ZF,SF,OF]:[0, 0, 0]
gr0:, gr1:, gr2:, gr3:
-------------------------------------------------------
 LD gr0,DATA1
"gr0に100を読み込みました。"
PR:2, SP:49, FR[ZF,SF,OF]:[0, 0, 0]
gr0:100, gr1:, gr2:, gr3:
-------------------------------------------------------
 LD gr1,DATA2
"gr1に200を読み込みました。"
PR:3, SP:49, FR[ZF,SF,OF]:[0, 0, 0]
gr0:100, gr1:200, gr2:, gr3:
-------------------------------------------------------
 CALL SUB
"スタック領域:m48に4を格納しました。"
"PRを9に変更しました。"
PR:9, SP:48, FR[ZF,SF,OF]:[0, 0, 0]
gr0:100, gr1:200, gr2:, gr3:
-------------------------------------------------------
SUB START
PR:10, SP:48, FR[ZF,SF,OF]:[0, 0, 0]
gr0:100, gr1:200, gr2:, gr3:
-------------------------------------------------------
 ADDA gr0,gr1
"gr0にgr0のデータ+gr1のデータを格納しました。"
PR:11, SP:48, FR[ZF,SF,OF]:[0, 0, 0]
gr0:300, gr1:200, gr2:, gr3:
-------------------------------------------------------
 SRA gr0,1
"gr0のデータを1 左シフトしました。"
PR:12, SP:48, FR[ZF,SF,OF]:[0, 0, 0]
gr0:150, gr1:200, gr2:, gr3:
-------------------------------------------------------
 RET
PR:4, SP:49, FR[ZF,SF,OF]:[0, 0, 0]
gr0:150, gr1:200, gr2:, gr3:
-------------------------------------------------------
 ST gr0,AVE
"AVEにgr0のデータを格納しました。"
PR:5, SP:49, FR[ZF,SF,OF]:[0, 0, 0]
gr0:150, gr1:200, gr2:, gr3:
-------------------------------------------------------
DATA1 DC 100
PR:6, SP:49, FR[ZF,SF,OF]:[0, 0, 0]
gr0:150, gr1:200, gr2:, gr3:
-------------------------------------------------------
DATA2 DC 200
PR:7, SP:49, FR[ZF,SF,OF]:[0, 0, 0]
gr0:150, gr1:200, gr2:, gr3:
-------------------------------------------------------
AVE DS 150
PR:8, SP:49, FR[ZF,SF,OF]:[0, 0, 0]
gr0:150, gr1:200, gr2:, gr3:
-------------------------------------------------------
 END
参考：memory7の状況 ["AVE", "DS", "150"]
```

以上4つのサンプルプログラムを実行しました。

### おわりに
COMET2を作ってみようと思ったのは、次の2点からでした。
1. CPUの理解を深める
2. railsではなく、rubyの理解を深める

動画や書籍を見て分かったつもりになっていた機能も実際にコードに落とし込んでみると、理解が浅いところが多々あり、CPUの理解はとても深まったのではないかと思います。また、一からオブジェクトを作るという作業を初めて行なったのでrubyについても学習を進めることができたと思っています。

今後の学習については、

- 作成したシミュレータをより厳密にする（ビットの次元で考える）
- プロセス管理や割込処理、周辺機器やOSなど理解を広げる

などを考えていますが、ソフトウェアのエンジニアになりますので、どちらかというと後者の学習を進め、広く知識を習得していきたいかなと思っています。

### 参考資料

- [放送大学/コンピュータの動作と管理　全15回](https://v.ouj.ac.jp/view/ouj/#/navi/vod?ca=807)
- [日経XTECH/アセンブラで学ぶコンピュータ基礎のキソ　全5回](https://xtech.nikkei.com/it/article/COLUMN/20070404/267385/)
- [Arcanum/CASL2](https://arcanum.work/summary?id=3&category=CASL2&page=0)
- [キャッスルシミュレータ](https://www.chiba-fjb.ac.jp/fjb_labo/casl/COMET2spec.html)