1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

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

Last updated at Posted at 2021-11-25

はじめに

【rubyでcomet2を作る】②CPUオブジェクト、メモリオブジェクトを定義する1の続きです。
今回はCPUオブジェクトの演算部を定義していきます。本来2進数でビット計算が行われているところを、無理矢理10進数で表現しているため、厳密には計算が正しくないところがあります。だいたいこういうもの程度に見ていただけると嬉しいです。

CPUオブジェクトを定義する

前回の記事でCPUオブジェクトのプロパティ、制御部のメソッドの定義を行いました。今回はCPUの演算機能(各命令の処理)について定義していきます。

CPUの演算機能

COMET2では28個の命令セットが用意されていますので各命令を定義していきます。

各命令の定義を行う前に、命令の中には汎用レジスタやメモリの値を変更するものがいくつかありますので、最初に汎用レジスタやメモリを更新するためのメソッドを作成します。

cpu.rb
  # GRの更新
  def update_gr(name, value)
      instance_eval("self.#{name} = #{value}.to_s")
  end

  # memoryの更新
  def update_memory(address, value, memory)
    memory.instance_eval("self.m#{address}[-1] = #{value}.to_s")
  end
入力系命令

LD, LAD, POPを定義します。

LD

LoaDの略。汎用レジスタから汎用レジスタ、メモリから汎用レジスタにデータを読み出す命令です。
オペランドのアドレス計算、データの読み出しの工程で、メモリのデータは読み出され、即値に変換されていますので、オペランドには下記3つが与えられることとします。

  • [汎用レジスタ, 汎用レジスタ]
  • [汎用レジスタ, 即値]
  • [汎用レジスタ, 即値, 汎用レジスタ]
cpu.rb
  # LD(Load)
  def ld(operand)
    # オペランドが[GR,即値,GR]の場合
    if operand.length == 3
      update_gr(operand[0], (operand[1].to_i + send("#{operand[2]}").to_i).to_s)
      p "#{operand[0]}#{operand[1]}+#{operand[2]}のデータを読み込みました。"
    # オペランドが[GR,GR]の場合
    elsif /^gr[0-7]$/.match(operand[1])
      update_gr(operand[0], send("#{operand[1]}"))
      p "#{operand[0]}#{operand[1]}のデータを読み込みました。"
    # オペランドが[GR,即値]の場合
    else
      update_gr(operand[0], operand[1])
      p "#{operand[0]}#{operand[1]}を読み込みました。"
    end
  end
LAD

Load ADdressの略。メモリアドレス(即値)を汎用レジスタに格納する命令です。保存したアドレスは即値データとして扱うことができますので、メモリのデータ領域に一度値を設定する必要がなくなります。
オペランドには下記2つが与えられます。

  • [汎用レジスタ, 即値]
  • [汎用レジスタ, 即値, 汎用レジスタ]
cpu.rb
  # LAD(Load ADdress)
  def lad(operand)
    # オペランドが[GR,即値,GR]の場合
    if operand.length == 3
      update_gr(operand[0], (operand[1].to_i + send("#{operand[2]}").to_i).to_s)
      p "#{operand[0]}#{operand[1]}+#{operand[2]}のデータを格納しました。"
    # オペランドが[GR,即値]の場合
    else
      update_gr(operand[0], operand[1])
      p "#{operand[0]}#{operand[1]}を格納しました。"
    end
  end
POP

メモリのスタック領域に格納されたデータを汎用レジスタに戻す命令です。スタックポインタに保存されているメモリアドレスのデータを読み出し、スタックポインタの値を一つ増やします。
オペランドには下記が与えられます。

  • 汎用レジスタ
cpu.rb
# POP
  def pop(operand, memory)
    update_gr(operand, memory.send("m#{self.sp}")[0])
    self.sp += 1
    p "#{operand}にスタックポインタのデータを読み込みました。"
  end
出力系命令

ST, PUSHを定義します。

ST

SToreの略。CPUの汎用レジスタからメモリにデータを格納する命令です。
オペランドには下記2つが与えられます。

  • [汎用レジスタ, アドレス]
  • [汎用レジスタ, ラベル]
cpu.rb
  # ST(STore)
  def st(operand, memory)
    # オペランドが[GR,アドレス]の場合
    if /^[0-9]+$/.match(operand[1])
      update_memory(operand[1], send("#{operand[0]}"), memory)
      p "m#{operand[1]}#{operand[0]}のデータを格納しました。"
    # オペランドが[GR,ラベル]の場合
    else
      (0..49).each do |n|
        if !memory.send("m#{n}").nil? && memory.send("m#{n}")[0] == operand[1]
          update_memory(n.to_s, send("#{operand[0]}"), memory)
        end
      end
      p "#{operand[1]}#{operand[0]}のデータを格納しました。"
    end
  end
PUSH

メモリのスタック領域にデータを格納するための命令です。
スタックポインタの値を一つ減らして、スタックポインタに保存されているメモリアドレスにデータを書き込みます。
オペランドには下記2つが与えられます。

  • [アドレス]
  • [アドレス,汎用レジスタ]
cpu.rb
  # PUSH
  def push(operand, memory)
    self.sp -= 1
    # オペランドが[アドレス]の場合
    if operand.length == 1
      update_memory(self.sp.to_s, operand[0], memory)
      p "m#{self.sp}#{operand[0]}を格納しました。"
    # オペランドが[アドレス,GR]の場合
    else
      update_memory(self.sp.to_s, (operand[0].to_i + send("#{operand[1]}").to_i).to_s, memory)
      p "m#{self.sp}#{operand[0]}+#{operand[1]}のデータを格納しました。"
    end
  end
演算系命令

ADDA, ADDL, SUBA, SUBL, AND, OR, XOR, CPA, CPL, SLA, SRA, SLL, SRL, NOPを定義します。
CASL2での加算命令、減算命令にはそれぞれ「算術」と「論理」の2種類が用意されています。どちらも似たような計算を行いますが、データの扱い方が異なります。

この「算術」と「論理」の違いを説明するために、まずはCOMET2における負数の表現について簡単に説明します。
COMET2では負の数を2の補数で表しています。
(例1)-1 ⇨ 1111 1111 1111 1111
(例2)-2 ⇨ 1111 1111 1111 1110

では、上の2つの例にて、2進数の数字をシンプルに10進数に直すとどうなるでしょうか。
(例1)1111 1111 1111 1111 ⇨ 65535
(例2)1111 1111 1111 1110 ⇨ 65534

また、上の2つの例が、2の補数で表されているとしたら、
(例1)1111 1111 1111 1111 ⇨ -1
(例2)1111 1111 1111 1110 ⇨ -2
となります。

今回の記事では機械語の段階まで考えず、表現を曖昧にしていましたが、実際のCPUは2進数の世界で計算を行なっています。そのため(例1)を65535と捉えるのか、-1と捉えるのか混乱してしまいます。この問題を解決するのが「算術」と「論理」の2つの命令になります。

  • 算術 ⇨ (例1)は-1と考える
  • 論理 ⇨ (例1)は65535と考える

16ビットでは65536個の数値を表すことが出来ますが、「算術」と「論理」では下記の範囲で認識されます。

  • 算術 ⇨ -32768 ~ 32767
  • 論理 ⇨ 0 ~ 65535

計算結果がこの範囲を出る場合にオーバーフローフラグはONになります。

この前提で、まずはフラグレジスタを更新するメソッドを定義していきます。

cpu.rb
  def update_fr(result, pattern)
      # FR初期化
      self.fr[0] = 0
      self.fr[1] = 0
      self.fr[2] = 0
      # サインフラグ
      if result < 0
        self.fr[1] = 1
      elsif pattern == 'arithmetic' && result > 32767
        self.fr[1] = 1
      end
      # オーバーフローフラグ
      if pattern == 'arithmetic' && result > 32767
        self.fr[2] = 1
        result -= 65536
      elsif pattern == 'arithmetic' && result < -32768
        self.fr[2] = 1
        result += 65536
      elsif pattern == 'logical' && result > 65535
        self.fr[2] = 1
        result -= 65536
      elsif pattern == 'logical' && result < 0
        self.fr[2] = 1
        result += 65536
      end
      # ゼロフラグ
      if result == 0
        self.fr[0] = 1
      end
      result
    end
ADDA

ADD Arithmeticの略。汎用レジスタやメモリに保存されているデータに対して、算術加算を行う命令です。
オペランドのアドレス計算、データの読み出しの工程で、メモリのデータは読み出され、即値に変換されていますので、オペランドには下記3つが与えられます。

  • [汎用レジスタ, 汎用レジスタ]
  • [汎用レジスタ, 即値]
  • [汎用レジスタ, 即値, 汎用レジスタ]

addaメソッドを作成する前に、算術加算or論理加算を行うメソッドを作成します。

cpu.rb
  # 算術論理加算
  def arithmetic_logical_addition(array, pattern)
    result = 0
    array.each do |a|
      a = a.to_i
      if pattern == 'arithmetic' && a > 32767
        a -= 65536
      elsif pattern == 'logical' && a < 0
        a += 65536
      end
      result += a
    end
    result = update_fr(result, pattern).to_s
  end
cpu.rb
  # ADDA
  def adda(operand)
    # オペランドが[GR,即値,GR]の場合
    if operand.length == 3
      array = [send("#{operand[0]}"), operand[1], send("#{operand[2]}")]
      answer = arithmetic_logical_addition(array, 'arithmetic')
      update_gr(operand[0], answer.to_s)
      p "#{operand[0]}#{operand[0]}のデータ+#{operand[1]}+#{operand[2]}のデータを格納しました。"
    # オペランドが[GR,GR]の場合
    elsif /^gr[0-7]$/.match(operand[1])
      array = [send("#{operand[0]}"), send("#{operand[1]}")]
      answer = arithmetic_logical_addition(array, 'arithmetic')
      update_gr(operand[0], answer.to_s)
      p "#{operand[0]}#{operand[0]}のデータ+#{operand[1]}のデータを格納しました。"
    # オペランドが[GR,即値]の場合
    else
      array = [send("#{operand[0]}"), operand[1].to_i]
      answer = arithmetic_logical_addition(array, 'arithmetic')
      update_gr(operand[0], answer.to_s)
      p "#{operand[0]}#{operand[0]}のデータ+#{operand[1]}を格納しました。"
    end
  end
ADDL

ADD Logicalの略。汎用レジスタやメモリに保存されているデータに対して、論理加算を行う命令です。
ただし、オペランドのアドレス計算、データの読み出しの工程で、メモリのデータは読み出され、即値に変換されていますので、オペランドには下記3つが与えられます。

  • [汎用レジスタ, 汎用レジスタ]
  • [汎用レジスタ, 即値]
  • [汎用レジスタ, 即値, 汎用レジスタ]
cpu.rb
  # ADDL
  def addl(operand)
    # オペランドが[GR,即値,GR]の場合
    if operand.length == 3
      array = [send("#{operand[0]}"), operand[1], send("#{operand[2]}")]
      answer = arithmetic_logical_addition(array, 'logical')
      update_gr(operand[0], answer.to_s)
      p "#{operand[0]}#{operand[0]}のデータ+#{operand[1]}+#{operand[2]}のデータを格納しました。"
    # オペランドが[GR,GR]の場合
    elsif /^gr[0-7]$/.match(operand[1])
      array = [send("#{operand[0]}"), send("#{operand[1]}")]
      answer = arithmetic_logical_addition(array, 'logical')
      update_gr(operand[0], answer.to_s)
      p "#{operand[0]}#{operand[0]}のデータ+#{operand[1]}のデータを格納しました。"
    # オペランドが[GR,即値]の場合
    else
      array = [send("#{operand[0]}"), operand[1]]
      answer = arithmetic_logical_addition(array, 'logical')
      update_gr(operand[0], answer.to_s)
      p "#{operand[0]}#{operand[0]}のデータ+#{operand[1]}を格納しました。"
    end
  end
SUBA

SUBtract Arithmeticの略。汎用レジスタやメモリに保存されているデータに対して、算術減算を行う命令です。
ただし、オペランドのアドレス計算、データの読み出しの工程で、メモリのデータは読み出され、即値に変換されていますので、オペランドには下記3つが与えられます。

  • [汎用レジスタ, 汎用レジスタ]
  • [汎用レジスタ, 即値]
  • [汎用レジスタ, 即値, 汎用レジスタ]

加算命令と同じく、まずは算術減算or論理減算を行うメソッドを作成します。

cpu.rb
  # 算術論理減算
  def arithmetic_logical_subtraction(array,pattern)
    result = 0
    array.each_with_index do |a, i|
      a = a.to_i
      if pattern == 'arithmetic' && a > 32767
        a -= 65536
      elsif pattern == 'logical' && a < 0
        a += 65536
      end
      if i == 0
        result += a
      else
        result -= a
      end
    end
    result = update_fr(result, pattern).to_s
  end
cpu.rb
  # SUBA
  def suba(operand)
    # オペランドが[GR,即値,GR]の場合
    if operand.length == 3
      array = [send("#{operand[0]}"), operand[1], send("#{operand[2]}")]
      answer = arithmetic_logical_subtraction(array, 'arithmetic')
      update_gr(operand[0], answer)
      p "#{operand[0]}#{operand[0]}のデータ-#{operand[1]}-#{operand[2]}のデータを格納しました。"
    # オペランドが[GR,GR]の場合
    elsif /^gr[0-7]$/.match(operand[1])
      array = [send("#{operand[0]}"), send("#{operand[1]}")]
      answer = arithmetic_logical_subtraction(array, 'arithmetic')
      update_gr(operand[0], answer)
      p "#{operand[0]}#{operand[0]}のデータ-#{operand[1]}のデータを格納しました。"
    # オペランドが[GR,即値]の場合
    else
      array = [send("#{operand[0]}"), operand[1]]
      answer = arithmetic_logical_subtraction(array, 'arithmetic')
      update_gr(operand[0], answer)
      p "#{operand[0]}#{operand[0]}のデータ-#{operand[1]}を格納しました。"
    end
  end
SUBL

SUBtract Logicalの略。汎用レジスタやメモリに保存されているデータに対して、論理減算を行う命令です。
ただし、オペランドのアドレス計算、データの読み出しの工程で、メモリのデータは読み出され、即値に変換されていますので、オペランドには下記3つが与えられます。

  • [汎用レジスタ, 汎用レジスタ]
  • [汎用レジスタ, 即値]
  • [汎用レジスタ, 即値, 汎用レジスタ]
cpu.rb
  # SUBL
  def subl(operand)
    # オペランドが[GR,即値,GR]の場合
    if operand.length == 3
      array = [send("#{operand[0]}"), operand[1], send("#{operand[2]}")]
      answer = arithmetic_logical_subtraction(array, 'logical')
      update_gr(operand[0], answer)
      p "#{operand[0]}#{operand[0]}のデータ-#{operand[1]}-#{operand[2]}のデータを格納しました。"
    # オペランドが[GR,GR]の場合
    elsif /^gr[0-7]$/.match(operand[1])
      array = [send("#{operand[0]}"), send("#{operand[1]}")]
      answer = arithmetic_logical_subtraction(array, 'logical')
      update_gr(operand[0], answer)
      p "#{operand[0]}#{operand[0]}のデータ-#{operand[1]}のデータを格納しました。"
    # オペランドが[GR,即値]の場合
    else
      array = [send("#{operand[0]}"), operand[1]]
      answer = arithmetic_logical_subtraction(array, 'logical')
      update_gr(operand[0], answer)
      p "#{operand[0]}#{operand[0]}のデータ-#{operand[1]}を格納しました。"
    end
  end
AND

汎用レジスタやメモリに保存されているデータに対して論理積演算を行う命令です。
論理積演算について4ビットを例に説明します。AとBの2つの入力が与えられたとき、論理積演算を行うと、ビット毎に比較して共に1の場合にだけ、1を返します。

(例1)A:3⇨0011, B:2⇨0010, F:0010⇨2
(例2)A:3⇨0011, B:4⇨0100, F:0000⇨0

rubyにはビット演算子が用意されていますので、今回はそのままビット演算子を利用したいと思います。
オペランドのアドレス計算、データの読み出しの工程で、メモリのデータは読み出され、即値に変換されていますので、オペランドには下記3つが与えられます。

  • [汎用レジスタ, 汎用レジスタ]
  • [汎用レジスタ, 即値]
  • [汎用レジスタ, 即値, 汎用レジスタ]

加算、減算と同じくまずは論理積を求めるメソッドを作成します。

cpu.rb
  # 論理積算
  def logical_and(array)
    result = 65535
    array.each do |a|
      a = a.to_i
      if a < 0
        a += 65536
      end
      result &= a
    end
    result = update_fr(result, 'logical').to_s
  end
cpu.rb
  # AND
  def and(operand)
    # オペランドが[GR,即値,GR]の場合
    if operand.length == 3
      array = [send("#{operand[0]}"), operand[1], send("#{operand[2]}")]
      answer = logical_and(array)
      update_gr(operand[0], answer)
      p "#{operand[0]}#{operand[0]}のデータと#{operand[1]}#{operand[2]}のデータの論理積を格納しました。"
    # オペランドが[GR,GR]の場合
    elsif /^gr[0-7]$/.match(operand[1])
      array = [send("#{operand[0]}"), send("#{operand[1]}")]
      answer = logical_and(array)
      update_gr(operand[0], answer)
      p "#{operand[0]}#{operand[0]}のデータと#{operand[1]}のデータの論理積を格納しました。"
    # オペランドが[GR,即値]の場合
    else
      array = [send("#{operand[0]}"), operand[1]]
      answer = logical_and(array)
      update_gr(operand[0], answer)
      p "#{operand[0]}#{operand[0]}のデータと#{operand[1]}の論理積を格納しました。"
    end
  end
OR

汎用レジスタやメモリに保存されているデータに対して論理和演算を行う命令です。
論理和演算について4ビットを例に説明します。AとBの2つの入力が与えられたとき、論理和演算を行うと、ビット毎に比較してどちらかが1の場合に、1を返します。

(例1)A:3⇨0011, B:2⇨0010, F:0011⇨3
(例2)A:3⇨0011, B:4⇨0100, F:0111⇨7

オペランドのアドレス計算、データの読み出しの工程で、メモリのデータは読み出され、即値に変換されていますので、オペランドには下記3つが与えられます。

  • [汎用レジスタ, 汎用レジスタ]
  • [汎用レジスタ, 即値]
  • [汎用レジスタ, 即値, 汎用レジスタ]

これまでと同様にまずは論理和を求めるメソッドを作成します。

cpu.rb
  # 論理和算
  def logical_or(array)
    result = 0
    array.each do |a|
      a = a.to_i
      if a > 32767
        a -= 65536
      end
      result |= a
    end
    result = update_fr(result, 'arithmetic').to_s
  end
cpu.rb
  # OR
  def or(operand)
    # オペランドが[GR,即値,GR]の場合
    if operand.length == 3
      array = [send("#{operand[0]}"), operand[1], send("#{operand[2]}")]
      answer = logical_or(array)
      update_gr(operand[0], answer)
      p "#{operand[0]}#{operand[0]}のデータと#{operand[1]}#{operand[2]}のデータの論理和を格納しました。"
    # オペランドが[GR,GR]の場合
    elsif /^gr[0-7]$/.match(operand[1])
      array = [send("#{operand[0]}"), send("#{operand[1]}")]
      answer = logical_or(array)
      update_gr(operand[0], answer)
      p "#{operand[0]}#{operand[0]}のデータと#{operand[1]}のデータの論理和を格納しました。"
    # オペランドが[GR,即値]の場合
    else
      array = [send("#{operand[0]}"), operand[1]]
      answer = logical_or(array)
      update_gr(operand[0], answer)
      p "#{operand[0]}#{operand[0]}のデータと#{operand[1]}の論理和を格納しました。"
    end
  end
XOR

汎用レジスタやメモリに保存されているデータに対して排他的論理和演算を行う命令です。
排他的論理和演算について4ビットを例に説明します。AとBの2つの入力が与えられたとき、排他的論理和演算を行うと、ビット毎に比較してAとBの値が異なる場合、1を返します。

(例1)A:3⇨0011, B:2⇨0010, F:0001⇨1
(例2)A:3⇨0011, B:4⇨0100, F:0111⇨7

オペランドのアドレス計算、データの読み出しの工程で、メモリのデータは読み出され、即値に変換されていますので、オペランドには下記3つが与えられます。

  • [汎用レジスタ, 汎用レジスタ]
  • [汎用レジスタ, 即値]
  • [汎用レジスタ, 即値, 汎用レジスタ]

これまでと同様にまずは排他的論理和を求めるメソッドを作成します。

cpu.rb
  # 排他的論理和算
  def logical_or(array)
    result = 0
    array.each do |a|
      a = a.to_i
      if a > 32767
        a -= 65536
      end
      result ^= a
    end
    result = update_fr(result, 'arithmetic').to_s
  end
cpu.rb
  # XOR
  def xor(operand)
    # オペランドが[GR,即値,GR]の場合
    if operand.length == 3
      array = [send("#{operand[0]}"), operand[1], send("#{operand[2]}")]
      answer = logical_xor(array)
      update_gr(operand[0], answer)
      p "#{operand[0]}#{operand[0]}のデータと#{operand[1]}#{operand[2]}のデータの排他的論理和を格納しました。"
    # オペランドが[GR,GR]の場合
    elsif /^gr[0-7]$/.match(operand[1])
      array = [send("#{operand[0]}"), send("#{operand[1]}")]
      answer = logical_xor(array)
      update_gr(operand[0], answer)
      p "#{operand[0]}#{operand[0]}のデータと#{operand[1]}のデータの排他的論理和を格納しました。"
    # オペランドが[GR,即値]の場合
    else
      array = [send("#{operand[0]}"), operand[1]]
      answer = logical_xor(array)
      update_gr(operand[0], answer)
      p "#{operand[0]}#{operand[0]}のデータと#{operand[1]}の排他的論理和を格納しました。"
    end
  end
CPA

ComPare Arithmeticの略。汎用レジスタやメモリに保存されているデータに対して、算術比較を行う命令です。
オペランドのアドレス計算、データの読み出しの工程で、メモリのデータは読み出され、即値に変換されていますので、オペランドには下記3つが与えられます。

  • [汎用レジスタ, 汎用レジスタ]
  • [汎用レジスタ, 即値]
  • [汎用レジスタ, 即値, 汎用レジスタ]

cpaメソッドを作成する前に、算術比較or論理比較を行うメソッドを作成します。
CPUで2つの値A,Bの比較を行う場合、減算を行い、フラグレジスタの符号を見ることでA,Bの大小比較を行なっています。

  • A < B の場合 [ZF,SF,OF]=[0,1,0]
  • A = B の場合 [ZF,SF,OF]=[1,0,0]
  • A > B の場合 [ZF,SF,OF]=[0,0,0]
cpu.rb
      # 算術論理比較
    def arithmetic_logical_comparison(array,pattern)
      # FR初期化
      self.fr[0] = 0
      self.fr[1] = 0
      self.fr[2] = 0
      
      array.each_with_index do |a, i|
        a = a.to_i
        if pattern == 'arithmetic' && a > 32767
          a -= 65536
        elsif pattern == 'logical' && a < 0
          a += 65536
        end
        array[i] = a
      end
      if array.length == 3
        if array[0] < array[1] + array[2]
          self.fr[1] = 1
        elsif array[0] == array[1] + array[2]
          self.fr[0] = 1
        end
      else
        if array[0] < array[1]
          self.fr[1] = 1
        elsif array[0] == array[1]
          self.fr[0] = 1
        end
      end
    end
cpu.rb
  # CPA
  def cpa(operand)
    # オペランドが[GR,即値,GR]の場合
    if operand.length == 3
      array = [send("#{operand[0]}"), operand[1], send("#{operand[2]}")]
      arithmetic_logical_comparison(array, 'arithmetic')
      p "#{operand[0]}のデータと#{operand[1]}+#{operand[2]}のデータの算術比較を行いました。"
    # オペランドが[GR,GR]の場合
    elsif /^gr[0-7]$/.match(operand[1])
      array = [send("#{operand[0]}"), send("#{operand[1]}")]
      arithmetic_logical_comparison(array, 'arithmetic')
      p "#{operand[0]}のデータと#{operand[1]}のデータの算術比較を行いました。"
    # オペランドが[GR,即値]の場合
    else
      array = [send("#{operand[0]}"), operand[1]]
      arithmetic_logical_comparison(array, 'arithmetic')
      p "#{operand[0]}のデータと#{operand[1]}の算術比較を行いました。。"
    end
  end
CPL

ComPare Logicalの略。汎用レジスタやメモリに保存されているデータに対して、論理比較を行う命令です。
オペランドのアドレス計算、データの読み出しの工程で、メモリのデータは読み出され、即値に変換されていますので、オペランドには下記3つが与えられます。

  • [汎用レジスタ, 汎用レジスタ]
  • [汎用レジスタ, 即値]
  • [汎用レジスタ, 即値, 汎用レジスタ]
cpu.rb
  # CPL
  def cpl(operand)
    # オペランドが[GR,即値,GR]の場合
    if operand.length == 3
      array = [send("#{operand[0]}"), operand[1], send("#{operand[2]}")]
      arithmetic_logical_comparison(array, 'logical')
      p "#{operand[0]}のデータと#{operand[1]}+#{operand[2]}のデータの論理比較を行いました。"
    # オペランドが[GR,GR]の場合
    elsif /^gr[0-7]$/.match(operand[1])
      array = [send("#{operand[0]}"), send("#{operand[1]}")]
      arithmetic_logical_comparison(array, 'logical')
      p "#{operand[0]}のデータと#{operand[1]}のデータの論理比較を行いました。"
    # オペランドが[GR,即値]の場合
    else
      array = [send("#{operand[0]}"), operand[1]]
      arithmetic_logical_comparison(array, 'logical')
      p "#{operand[0]}のデータと#{operand[1]}の論理比較を行いました。。"
    end
  end
SLA,SLL

Shift Left Arithmetic, Shift Left Logicalの略。汎用レジスタに保存されているデータに対して、算術左シフト、論理左シフトを行う命令です。本来2つの命令は若干異なる処理を行いますが、数値を10進数のまま考えている状態で、違いを表現することが難しいので、今回はどちらもrubyに定義されている左シフトを行う命令とします。
オペランドには下記2つが与えられます。

  • [汎用レジスタ,即値]
  • [汎用レジスタ,即値,レジスタ]
cpu.rb
  # SLA
  def sla(operand)
    # オペランドが[GR,即値,GR]の場合
    if operand.length == 3
      update_gr(operand[0], send("#{operand[0]}").to_i << (operand[1].to_i + send("#{operand[2]}").to_i))
      p "#{operand[0]}のデータを#{operand[2]}のデータ+#{operand[1]} 左シフトしました。"
    # オペランドが[GR,即値]の場合
    else
      update_gr(operand[0], send("#{operand[0]}").to_i << operand[1].to_i)
      p "#{operand[0]}のデータを#{operand[1]} 左シフトしました。"
    end
  end

  # SLL
  def sll(operand)
    sla(operand)
  end
SRA,SRL

Shift Right Arithmetic, Shift Right Logicalの略。汎用レジスタに保存されているデータに対して、算術右シフト、論理右シフトを行う命令です。本来2つの命令は若干異なる処理を行いますが、数値を10進数のまま考えている状態で、違いを表現することが難しいので、今回はどちらもrubyに定義されている右シフトを行う命令とします。
オペランドには下記2つが与えられます。

  • [汎用レジスタ,即値]
  • [汎用レジスタ,即値,レジスタ]
cpu.rb
  # SRA
  def sra(operand)
    # オペランドが[GR,即値,GR]の場合
    if operand.length == 3
      update_gr(operand[0], send("#{operand[0]}").to_i >> (operand[1].to_i + send("#{operand[2]}").to_i))
      p "#{operand[0]}のデータを#{operand[2]}のデータ+#{operand[1]} 左シフトしました。"
    # オペランドが[GR,即値]の場合
    else
      update_gr(operand[0], send("#{operand[0]}").to_i >> operand[1].to_i)
      p "#{operand[0]}のデータを#{operand[1]} 左シフトしました。"
    end
  end

  # SRL
  def srl(operand)
    sra(operand)
  end
NOP

No OPerationの略。この命令は何もしないことを明示的に記述する際に使用します。何もしない命令を何のために定義するんだという話ですが、下記のような用途で利用されます。

  • 他の装置やプログラムとタイミングを合わせる
  • あとで命令を追加する場所(パッチエリア)を確保するためにとりあえず置いておく

今回作成している上では特に意識していませんが、本来CPUはクロック周波数に基づいて命令を実行しています。今回は単一のプログラムの動作ですので、関係ありませんが、複数のプログラムを並行して実行している場合、タイミングを合わせるために数クロックを消費するNOP命令が利用されます。
また、場合によっては、一度プログラムのアドレスが決まると後から変更できないことがあります。その場合、後ほどプログラムの挿入を行うことはできませんので、NOPでパッチエリアを用意しておき、こちらを書き直すことでバグの修正を行います。

cpu.rb
  # NOP
  def nop
    p "何もしませんでした。"
  end
制御系命令

JPL, JMI, JNZ, JZE, JOV, JUMP, CALL, RET, SVCを定義します。
通常プログラムは上から順番に実行されます。CPUではプログラムレジスタの値を順にインクリメントしていくことで、この動きを実現していますが、プログラムレジスタの値を変更することで、この流れを変えることができるのが制御形命令です。

各命令で、フラグレジスタの値によって、プログラムレジスタの値を変更するかどうかが決まりますが、まずは強制的にプログラムレジスタの値を変更するJUMP命令の定義を行います。

JUMP

unconditional JUMPの略。フラグレジスタの値に関係なくプログラムレジスタの値を変更します。
オペランドには下記2つが与えられます。

  • [ラベル]
  • [即値,レジスタ]
cpu.rb
  # JUMP
  def jump(operand, memory)
    # オペランドが[ラベル]の場合
    if operand.length == 1
      (0..49).each do |n|
        if memory.send("m#{n}")[0] == operand[0]
          self.pr = n
          p "PRを#{n}に変更しました。"
          break
        end
      end
    # オペランドが[即値,ラベル]の場合
    else
      self.pr = operand[0].to_i + send("#{operand[1]}").to_i
      p "PRを#{operand[0]}+#{operand[1]}のデータに変更しました。"
    end
  end
JPL

Jump on PLusの略。フラグレジスタのZF=0かつSF=0の場合、プログラムレジスタの値を指定したメモリアドレスに変更します。
オペランドには下記2つが与えられます。

  • [ラベル]
  • [即値,レジスタ]
cpu.rb
  # JPL
  def jpl(operand, memory)
    if self.fr[0] == 0 && self.fr[1] == 0
      jump(operand, memory)
    end
  end
JMI

Jump on MInusの略。フラグレジスタのSF=1の場合、プログラムレジスタの値を指定したメモリアドレスに変更します。
オペランドには下記2つが与えられます。

  • [ラベル]
  • [即値,レジスタ]
cpu.rb
  # JMI
  def jmi(operand, memory)
    if self.fr[1] == 1
      jump(operand, memory)
    end
  end
JNZ

Jump on Non Zeroの略。フラグレジスタのZF=0の場合、プログラムレジスタの値を指定したメモリアドレスに変更します。
オペランドには下記2つが与えられます。

  • [ラベル]
  • [即値,レジスタ]
cpu.rb
  # JNZ
  def jnz(operand, memory)
    if self.fr[0] == 0
      jump(operand, memory)
    end
  end
JZE

Jump on ZEroの略。フラグレジスタのZF=1の場合、プログラムレジスタの値を指定したメモリアドレスに変更します。
オペランドには下記2つが与えられます。

  • [ラベル]
  • [即値,レジスタ]
cpu.rb
  # JZE
  def jze(operand, memory)
    if self.fr[0] == 1
      jump(operand, memory)
    end
  end
JOV

Jump on OVerflowの略。フラグレジスタのOF=1の場合、プログラムレジスタの値を指定したメモリアドレスに変更します。
オペランドには下記2つが与えられます。

  • [ラベル]
  • [即値,レジスタ]
cpu.rb
  # JOV
  def jov(operand, memory)
    if self.fr[2] == 1
      jump(operand, memory)
    end
  end
CALL

サブルーチン1を呼び出すための命令です。プログラムレジスタの値を変更して、プログラムの流れを変えるところまではJUMP命令と同じなのですが、CALL命令ではスタックに戻り番地をPUSHしておきます。これにより次に説明するRET命令を使って、サブルーチン実行前のアドレスに戻ることが出来ます。

オペランドには下記2つが与えられます。

  • [ラベル]
  • [即値,レジスタ]
cpu.rb
  # CALL
  def call(operand, memory)
    push([self.pr], memory)
    jump(operand, memory)
  end
RET

RETurn from subroutineの略。サブルーチンから戻るための命令です。CALL命令でスタック領域に保存した戻り番地をプログラムレジスタに戻します。
オペランドはありません。

cpu.rb
  # RET
  def ret(memory)
    self.pr = memory.send("m#{self.sp}")[0].to_i
    self.sp += 1
  end
SVC

Super Visor Callの略。入出力やハードウェアの制御といったOSのみが実行できる処理を一時的に使用するための命令です。OSのサブルーチンを呼び出す命令のためCALL命令と同じものと考えます。ただし、このSVC命令は概念的なもので、実際にCOMET2において記述することはありません。COMET2のOSも不明です。

cpu.rb
  # SVC
  def svc
    # ブラックボックス
  end

おわりに

今回はCPUの演算部の定義を行なってきました。厳密に正しくないところもありますが、ある程度のシミュレーション可能なオブジェクトが出来上がったと思います。
ここまでの状況はgithubに置いています。
次回の記事では、作成したCPUオブジェクト、メモリオブジェクトを使って、実際にプログラムを動かしてみたいと思います。

参考資料

  1. メインのプログラムとは別の流れのプログラム

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?