はじめに
【rubyでcomet2を作る】②CPUオブジェクト、メモリオブジェクトを定義する1の続きです。
今回はCPUオブジェクトの演算部を定義していきます。本来2進数でビット計算が行われているところを、無理矢理10進数で表現しているため、厳密には計算が正しくないところがあります。だいたいこういうもの程度に見ていただけると嬉しいです。
CPUオブジェクトを定義する
前回の記事でCPUオブジェクトのプロパティ、制御部のメソッドの定義を行いました。今回はCPUの演算機能(各命令の処理)について定義していきます。
CPUの演算機能
COMET2では28個の命令セットが用意されていますので各命令を定義していきます。
各命令の定義を行う前に、命令の中には汎用レジスタやメモリの値を変更するものがいくつかありますので、最初に汎用レジスタやメモリを更新するためのメソッドを作成します。
# 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つが与えられることとします。
- [汎用レジスタ, 汎用レジスタ]
- [汎用レジスタ, 即値]
- [汎用レジスタ, 即値, 汎用レジスタ]
# 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つが与えられます。
- [汎用レジスタ, 即値]
- [汎用レジスタ, 即値, 汎用レジスタ]
# 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
メモリのスタック領域に格納されたデータを汎用レジスタに戻す命令です。スタックポインタに保存されているメモリアドレスのデータを読み出し、スタックポインタの値を一つ増やします。
オペランドには下記が与えられます。
- 汎用レジスタ
# 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つが与えられます。
- [汎用レジスタ, アドレス]
- [汎用レジスタ, ラベル]
# 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つが与えられます。
- [アドレス]
- [アドレス,汎用レジスタ]
# 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になります。
この前提で、まずはフラグレジスタを更新するメソッドを定義していきます。
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論理加算を行うメソッドを作成します。
# 算術論理加算
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
# 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つが与えられます。
- [汎用レジスタ, 汎用レジスタ]
- [汎用レジスタ, 即値]
- [汎用レジスタ, 即値, 汎用レジスタ]
# 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論理減算を行うメソッドを作成します。
# 算術論理減算
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
# 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つが与えられます。
- [汎用レジスタ, 汎用レジスタ]
- [汎用レジスタ, 即値]
- [汎用レジスタ, 即値, 汎用レジスタ]
# 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つが与えられます。
- [汎用レジスタ, 汎用レジスタ]
- [汎用レジスタ, 即値]
- [汎用レジスタ, 即値, 汎用レジスタ]
加算、減算と同じくまずは論理積を求めるメソッドを作成します。
# 論理積算
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
# 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つが与えられます。
- [汎用レジスタ, 汎用レジスタ]
- [汎用レジスタ, 即値]
- [汎用レジスタ, 即値, 汎用レジスタ]
これまでと同様にまずは論理和を求めるメソッドを作成します。
# 論理和算
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
# 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つが与えられます。
- [汎用レジスタ, 汎用レジスタ]
- [汎用レジスタ, 即値]
- [汎用レジスタ, 即値, 汎用レジスタ]
これまでと同様にまずは排他的論理和を求めるメソッドを作成します。
# 排他的論理和算
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
# 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]
# 算術論理比較
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
# 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つが与えられます。
- [汎用レジスタ, 汎用レジスタ]
- [汎用レジスタ, 即値]
- [汎用レジスタ, 即値, 汎用レジスタ]
# 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つが与えられます。
- [汎用レジスタ,即値]
- [汎用レジスタ,即値,レジスタ]
# 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つが与えられます。
- [汎用レジスタ,即値]
- [汎用レジスタ,即値,レジスタ]
# 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でパッチエリアを用意しておき、こちらを書き直すことでバグの修正を行います。
# NOP
def nop
p "何もしませんでした。"
end
制御系命令
JPL, JMI, JNZ, JZE, JOV, JUMP, CALL, RET, SVCを定義します。
通常プログラムは上から順番に実行されます。CPUではプログラムレジスタの値を順にインクリメントしていくことで、この動きを実現していますが、プログラムレジスタの値を変更することで、この流れを変えることができるのが制御形命令です。
各命令で、フラグレジスタの値によって、プログラムレジスタの値を変更するかどうかが決まりますが、まずは強制的にプログラムレジスタの値を変更するJUMP命令の定義を行います。
JUMP
unconditional JUMPの略。フラグレジスタの値に関係なくプログラムレジスタの値を変更します。
オペランドには下記2つが与えられます。
- [ラベル]
- [即値,レジスタ]
# 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つが与えられます。
- [ラベル]
- [即値,レジスタ]
# 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つが与えられます。
- [ラベル]
- [即値,レジスタ]
# JMI
def jmi(operand, memory)
if self.fr[1] == 1
jump(operand, memory)
end
end
JNZ
Jump on Non Zeroの略。フラグレジスタのZF=0の場合、プログラムレジスタの値を指定したメモリアドレスに変更します。
オペランドには下記2つが与えられます。
- [ラベル]
- [即値,レジスタ]
# JNZ
def jnz(operand, memory)
if self.fr[0] == 0
jump(operand, memory)
end
end
JZE
Jump on ZEroの略。フラグレジスタのZF=1の場合、プログラムレジスタの値を指定したメモリアドレスに変更します。
オペランドには下記2つが与えられます。
- [ラベル]
- [即値,レジスタ]
# JZE
def jze(operand, memory)
if self.fr[0] == 1
jump(operand, memory)
end
end
JOV
Jump on OVerflowの略。フラグレジスタのOF=1の場合、プログラムレジスタの値を指定したメモリアドレスに変更します。
オペランドには下記2つが与えられます。
- [ラベル]
- [即値,レジスタ]
# JOV
def jov(operand, memory)
if self.fr[2] == 1
jump(operand, memory)
end
end
CALL
サブルーチン1を呼び出すための命令です。プログラムレジスタの値を変更して、プログラムの流れを変えるところまではJUMP命令と同じなのですが、CALL命令ではスタックに戻り番地をPUSHしておきます。これにより次に説明するRET命令を使って、サブルーチン実行前のアドレスに戻ることが出来ます。
オペランドには下記2つが与えられます。
- [ラベル]
- [即値,レジスタ]
# CALL
def call(operand, memory)
push([self.pr], memory)
jump(operand, memory)
end
RET
RETurn from subroutineの略。サブルーチンから戻るための命令です。CALL命令でスタック領域に保存した戻り番地をプログラムレジスタに戻します。
オペランドはありません。
# 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も不明です。
# SVC
def svc
# ブラックボックス
end
おわりに
今回はCPUの演算部の定義を行なってきました。厳密に正しくないところもありますが、ある程度のシミュレーション可能なオブジェクトが出来上がったと思います。
ここまでの状況はgithubに置いています。
次回の記事では、作成したCPUオブジェクト、メモリオブジェクトを使って、実際にプログラムを動かしてみたいと思います。
参考資料
-
メインのプログラムとは別の流れのプログラム ↩