【rubyでcomet2を作る】②CPUオブジェクト、メモリオブジェクトを定義する1

### はじめに

[【rubyでcomet2を作る】①概要](https://qiita.com/223yuta/items/bf3ce3882c7199f97230)の続きです。
今回はCPUオブジェクト、メモリオブジェクトを定義していきたいと思います。

### CPUオブジェクトを定義する
CPUクラスを作成し、プロパティとメソッドを定義していきます。

```ruby:cpu.rb
class CPU
  # プロパティ

  # メソッド
  
end
```

#### 必要となるプロパティ
前回の記事で説明した通りCOMET2のCPUは下記4種類のレジスタを持ちます。

- プログラムレジスタ
- スタックポインタ
- フラグレジスタ
- 8つの汎用レジスタ

本来のCOMET2ではフラグレジスタのみ3ビット、それ以外は16ビットの容量を持ちます。
命令によっては16ビット命令と32ビット命令がありますが、今回はビット数まで考えることはせず、全ての命令が16ビット命令(１語命令、１メモリ番地を使用する）という前提で話を進めます。そのためCPUについてもビット数は無視して考え、フラグレジスタは[ZF,SF,OF]という配列、それ以外は任意のデータが入るということにします。

```ruby:cpu.rb
  attr_accessor :pr, :sp, :fr, :gr0, :gr1, :gr2,
                :gr3, :gr4, :gr5, :gr6, :gr7
  # プロパティ
  def initialize(pr=0, sp=0, fr=[0,0,0], gr0='', gr1='', gr2='',
                gr3='', gr4='', gr5='', gr6='', gr7='')
    @pr = pr
    @sp = sp
    @fr = fr
    @gr0 = gr0
    @gr1 = gr1
    @gr2 = gr2
    @gr3 = gr3
    @gr4 = gr4
    @gr5 = gr5
    @gr6 = gr6
    @gr7 = gr7
  end
```

#### 必要となるメソッド
CPUの機能は大きく制御（命令デコーダ[^1]）と演算（ALU[^2]）に分かれます。

[^1]: 命令の内容を解読して実行の準備をする装置。機械語の命令を解読して、他の装置へ制御信号を出す。
[^2]: Arithmetic and Logic Unitの略。四則演算や論理演算などの処理を、加速器や論理演算器などの演算回路を用いて行う。
##### CPUの制御機能
CPUが命令を実行する手順は以下の通りです。

1. 命令の読み出し（フェッチ）
1. オペコードの解読
1. オペランドのアドレス計算、データの読み出し
1. 命令実行（演算処理）

命令によっては、3.は省略されることもあります。

```ruby:cpu.rb
  # 制御部
  def control(memory)

  end
```

###### 命令の読み出し（フェッチ）
プログラムレジスタと命令レジスタ(IR)[^3]を用います。
①プログラムレジスタに保持されているメモリアドレスのプログラムを取り出し、命令レジスタに保存します。
②プログラムレジスタの値を一つ進めます。

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

[^3]: 取り出した命令を一時的に記憶するためのレジスタ（Instruction register）。この概念がCOMET2に存在するのか分かりませんが、処理の流れを分かりやすくするためにもローカル変数を定義します。

###### オペコードの解読
命令レジスタに保存したオペコードを命令デコーダにより解読し、必要な装置に制御信号を送ります。
メモリオブジェクトを定義するにて説明しますが、今回は命令を[ラベル,オペコード,オペランド]の配列で与えますので、下記のように命令デコーダを定義します。それぞれの命令（インスタンスメソッド）の中身は次の記事で記述します。

```ruby:cpu.rb
    # オペコードの解読
    case ir[1]
    when 'LD' then
      read_data(ir[2], memory)
      ld(ir[2])
    when 'LAD' then
      lad(ir[2])
    when 'POP' then
      pop(ir[2], memory)

      ・
      ・ #(全部で28パターン記述。省略）
      ・

    when 'RET' then
      ret(memory)
    when 'SVC' then
      self.svc
    end
```

###### オペランドのアドレス計算、データの読み出し
COMET2におけるオペランドに含まれる情報は下記3つです。
- レジスタ名（例：'gr0'など）
- 即値（例：'100'など）
- メモリアドレス（ラベルで表される、例：'label1'など、/^gr[0-7]$/を除く）

この工程では、情報がメモリアドレスで与えられた場合、即値に置き換える処理を行います。

```ruby:cpu.rb
  # オペランドのアドレス計算、データの読み出し
  def read_data(operand, memory)
    operand.each_with_index do |o, i| #オペランドは複数与えられる可能性がある
      unless /(^[0-9]+$|^gr[0-7]$)/.match(o) #即値と汎用レジスタを除く
        (0..49).each do |n| #メモリ番地分繰り返す
          # メモリに命令が格納されていて、ラベルとオペランドが一致した際に実行
          if !memory.send("m#{n}").nil? && memory.send("m#{n}")[0] == o
            operand[i] = memory.send("m#{n}")[2] # オペランドを即値に更新する
          end
        end
      end
    end
  end
```

このメソッドをデータの読み出しを行う命令(LDなど)に対して実行します。

###### 命令実行（演算処理）
各命令の実行（演算）は、次の記事にて記述します。

###### 繰り返し処理
現段階では最初の命令を実行したら処理が終了してしまいます。そこで制御部全体をループし、ループからの脱出は後述する擬似命令のENDにて行います。

```ruby:cpu.rb
  def control(memory)
    while true
      case ir[1]
      ・
      ・ #(省略）
      ・
      when 'END'
        break
      end
    end
  end
```

#### 擬似命令とマクロ命令
CASL2には擬似命令[^4]とマクロ命令[^5]も用意されています。

##### 擬似命令
- START・・・プログラムの始まりを定義する
- END・・・プログラムの終わりを定義する
- DC・・・メモリ上に定数を定義する（Define Constant)
- DS・・・メモリ上に領域を確保する(Define Strage)

##### マクロ命令
- IN・・・I/O[^6]からデータを入力する
- OUT・・・I/Oへデータを出力する
- RPUSH・・・全てのレジスタの内容をスタックに退避する（Register PUSH)
- RPOP・・・スタックの内容をレジスタに復帰する(Register POP)

[^4]: CPUに直接的に解釈・実行させるのではなく、アセンブラに指示を与える命令のこと。見かけ上はCPUへの命令と同じ構文で記述するので擬似命令と呼ぶ。

[^5]: 複数の命令を並べて実現した処理全体に名前をつけたもの。擬似命令と同様に、見かけ上はCPUへの命令と同じ構文で記述できる。

[^6]: 入出力装置。Input/Output

#### ここまでのCPUオブジェクト
演算部の処理は次回の記事で記述しますので、今回の記事でのCPUオブジェクトの定義はここまでとします。

<details>
<summary>ここまでのCPUオブジェクト</summary><div>

```ruby:cpu.rb
class CPU
  attr_accessor :pr, :sp, :fr, :gr0, :gr1, :gr2,
                :gr3, :gr4, :gr5, :gr6, :gr7
  # プロパティ
  def initialize(pr=0, sp=0, fr=[0,0,0], gr0='', gr1='', gr2='',
                gr3='', gr4='', gr5='', gr6='', gr7='')
    @pr = pr
    @sp = sp
    @fr = fr
    @gr0 = gr0
    @gr1 = gr1
    @gr2 = gr2
    @gr3 = gr3
    @gr4 = gr4
    @gr5 = gr5
    @gr6 = gr6
    @gr7 = gr7
  end

  # メソッド
  # 制御部
  def control(memory)
    while true
      # 命令の読み出し（フェッチ）
      ir = memory.send("m#{pr}")
      self.pr += 1
  
      # オペコードの解読
      case ir[1]
      when 'LD' then
        read_data(ir[2], memory)
        ld(ir[2])
      when 'LAD' then
        lad(ir[2])
      when 'POP' then
        pop(ir[2], memory)
      when 'ST' then
        st(ir[2], memory)
      when 'PUSH' then
        push(ir[2], memory)
      when 'ADDA' then
        read_data(ir[2], memory)
        adda(ir[2])
      when 'ADDL' then
        read_data(ir[2], memory)
        addl(ir[2])
      when 'SUBA' then
        read_data(ir[2], memory)
        suba(ir[2])
      when 'SUBL' then
        read_data(ir[2], memory)
        subl(ir[2])
      when 'AND' then
        read_data(ir[2], memory)
        self.and(ir[2])
      when 'OR' then
        read_data(ir[2], memory)
        self.or(ir[2])
      when 'XOR' then
        read_data(ir[2], memory)
        xor(ir[2])
      when 'CPA' then
        read_data(ir[2], memory)
        cpa(ir[2])
      when 'CPL' then
        read_data(ir[2], memory)
        cpl(ir[2])
      when 'SLA' then
        read_data(ir[2], memory)
        sla(ir[2])
      when 'SRA' then
        read_data(ir[2], memory)
        sra(ir[2])
      when 'SLL' then
        read_data(ir[2], memory)
        sll(ir[2])
      when 'SRL' then
        read_data(ir[2], memory)
        srl(ir[2])
      when 'NOP' then
        self.nop
      when 'JPL' then
        jpl(ir[2], memory)
      when 'JMI' then
        jmi(ir[2], memory)
      when 'JNZ' then
        jnz(ir[2], memory)
      when 'JZE' then
        jze(ir[2], memory)
      when 'JOV' then
        jov(ir[2], memory)
      when 'JUMP' then
        jump(ir[2], memory)
      when 'CALL' then
        call(ir[2], memory)
      when 'RET' then
        ret(memory)
      when 'SVC' then
        self.svc

      # 擬似命令
      when 'START' then
        # 何もしない。プログラム開始の宣言。
      when 'END' then
        # 本来何もしない。プログラム終了の宣言。
        break
      when 'DC' then
        # 何もしない。メモリ上に定数を定義する宣言。
      when 'DS' then
        # 何もしない。メモリ上に領域を確保する宣言。

      # マクロ命令
      when 'IN' then
        # 入出力装置からデータを入力する。
      when 'OUT' then
        # 入出力装置へデータを入力する
      when 'RPUSH' then
        # 全てのレジスタの内容をスタックに退避する
      when 'RPOP' then
        # スタックの内容をレジスタに復帰する
      end
    end
  end

  # オペランドのアドレス計算、データの読み出し
  def read_data(operand, memory)
    operand.each_with_index do |o, i|
      unless /(^[0-9]+$|^gr[0-7]$)/.match(o)
        (0..49).each do |n|
          if !memory.send("m#{n}").nil? && memory.send("m#{n}")[0] == o
            operand[i] = memory.send("m#{n}")[2]
          end
        end
      end
    end
  end

  # 演算部

end
```

</div></details>

### メモリオブジェクトを定義する
メモリはメソッドを持たないプロパティのみの記憶装置として定義します。
本来のCOMET2では命令によって１語命令と2語命令が存在します。今回は簡易的に全て1語命令（1メモリ番地を使用する）として考えます。また、本来65536個の番地を持ちますが、シミュレーションするプログラムはそこまでメモリを必要としないため、0~49番地までを用意することとします。また、命令は[ラベル,オペコード,オペランド]の配列で与えられることとします。

```ruby:memory.rb

class Memory
  attr_accessor :m0, :m1, :m2, :m3, :m4,
                :m5, :m6, :m7, :m8, :m9,
                :m10, :m11, :m12, :m13, :m14,
                :m15, :m16, :m17, :m18, :m19,
                :m20, :m21, :m22, :m23, :m24,
                :m25, :m26, :m27, :m28, :m29,
                :m30, :m31, :m32, :m33, :m34,
                :m35, :m36, :m37, :m38, :m39,
                :m40, :m41, :m42, :m43, :m44,
                :m45, :m46, :m47, :m48, :m49,

  def initialize(m0=[], m1=[], m2=[], m3=[], m4=[],
                m5=[], m6=[], m7=[], m8=[], m9=[],
                m10=[], m11=[], m12=[], m13=[], m14=[],
                m15=[], m16=[], m17=[], m18=[], m19=[],
                m20=[], m21=[], m22=[], m23=[], m24=[],
                m25=[], m26=[], m27=[], m28=[], m29=[],
                m30=[], m31=[], m32=[], m33=[], m34=[],
                m35=[], m36=[], m37=[], m38=[], m39=[],
                m40=[], m41=[], m42=[], m43=[], m44=[],
                m45=[], m46=[], m47=[], m48=[], m49=[],)
    @m0 = m0
    @m1 = m1
    @m2 = m2
    @m3 = m3
    @m4 = m4
    @m5 = m5
    @m6 = m6
    @m7 = m7
    @m8 = m8
    @m9 = m9
    @m10 = m10
    @m11 = m11
    @m12 = m12
    @m13 = m13
    @m14 = m14
    @m15 = m15
    @m16 = m16
    @m17 = m17
    @m18 = m18
    @m19 = m19
    @m20 = m20
    @m21 = m21
    @m22 = m22
    @m23 = m23
    @m24 = m24
    @m25 = m25
    @m26 = m26
    @m27 = m27
    @m28 = m28
    @m29 = m29
    @m30 = m30
    @m31 = m31
    @m32 = m32
    @m33 = m33
    @m34 = m34
    @m35 = m35
    @m36 = m36
    @m37 = m37
    @m38 = m38
    @m39 = m39
    @m40 = m40
    @m41 = m41
    @m42 = m42
    @m43 = m43
    @m44 = m44
    @m45 = m45
    @m46 = m46
    @m47 = m47
    @m48 = m48
    @m49 = m49
  end
end
```

### 終わりに
今回はCPUオブジェクトの制御部、メモリオブジェクトの定義を行いました。
次回の記事ではCPUオブジェクトの演算部の定義を行っていきたいと思います。

### 参考資料

- [放送大学/コンピュータの動作と管理　全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)