20211125のRubyに関する記事は4件です。

【Ruby のまずいコード】ファイル書き出し

お題 Math::PI の値をファイル pi.txt に書き出してください。 コード File.open("pi.txt", "w") do |f| f.print Math::PI end 改善 ファイルに書き出す専用のメソッド IO.write があります。 これを用いると IO.write("pi.txt", Math::PI) と簡素化できます。 IO.write は File.write とも書けます。これについては次節で述べます。 ファイル読み込みのほうも同様で, text = nil File.open("text.txt") do |f| text = f.read end とか1 text = File.open("text.txt"){ |f| f.read } などとしなくても,IO.read を使って text = IO.read("text.txt") と書けます。 IO.read は File.read とも書けます(次節参照)。 IO.write/IO.read か File.write/File.read か IO.read や IO.write を使ったコードを RuboCop で検査すると,セキュリティーの観点から「IO.write でなく File.write を使う」ように言われます2。 というのは,File.read や File.write はファイルの読み書きしかできませんが,IO.read や IO.write はファイル以外のさまざまなものに対しても読み書きでき,第一引数に与えるものによってはコマンドの実行すらできてしまうからです。 例えば, IO.read("| mkdir hoge") とやると,ディレクトリーが作れてしまいます!3 よって,たとえばウェブアプリでユーザー入力に基づく値を第一引数に与えることはコマンドインジェクション攻撃に繋がり得ます。 そういう観点からすると,この記事の対象読者(初心者を想定)に IO.write や IO.read を勧めるのは良くない気もしますね。 File.write や File.read を使う,と覚えておいたほうがよいかもしれません。 このコードでわざわざ text = nil としているのは,ブロック外でローカル変数を定義しないとこのあとで text が使えないから。代入される nil に意味はありません。原理的には何でも OK ですが,nil 以外だと何か意味のある値なのかと誤解される恐れがあります。 ↩ 検出されるかどうかは RuboCop の設定によります。 ↩ もちろんもっと怖いこともできます。ここでは,macOS,Linux,Windows で共通に使えるコマンドを例に取りました。 ↩
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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

はじめに 【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 # FRの更新 def update_fr(result, pattern) # サインフラグ 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 == 'logic' && result > 65535 self.fr[2] = 1 result -= 65536 elsif pattern == 'logic' && 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) 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オブジェクト、メモリオブジェクトを使って、実際にプログラムを動かしてみたいと思います。 参考資料 放送大学/コンピュータの動作と管理 全15回 日経XTECH/アセンブラで学ぶコンピュータ基礎のキソ 全5回 Arcanum/CASL2 キャッスルシミュレータ メインのプログラムとは別の流れのプログラム ↩
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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

はじめに 【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オブジェクト、メモリオブジェクトを使って、実際にプログラムを動かしてみたいと思います。 参考資料 放送大学/コンピュータの動作と管理 全15回 日経XTECH/アセンブラで学ぶコンピュータ基礎のキソ 全5回 Arcanum/CASL2 キャッスルシミュレータ メインのプログラムとは別の流れのプログラム ↩
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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

はじめに 【rubyでcomet2を作る】①概要の続きです。 今回はCPUオブジェクト、メモリオブジェクトを定義していきたいと思います。 CPUオブジェクトを定義する CPUクラスを作成し、プロパティとメソッドを定義していきます。 cpu.rb class CPU # プロパティ # メソッド end 必要となるプロパティ 前回の記事で説明した通りCOMET2のCPUは下記4種類のレジスタを持ちます。 プログラムレジスタ スタックポインタ フラグレジスタ 8つの汎用レジスタ 本来のCOMET2ではフラグレジスタのみ3ビット、それ以外は16ビットの容量を持ちます。 命令によっては16ビット命令と32ビット命令がありますが、今回はビット数まで考えることはせず、全ての命令が16ビット命令(1語命令、1メモリ番地を使用する)という前提で話を進めます。そのためCPUについてもビット数は無視して考え、フラグレジスタは[ZF,SF,OF]という配列、それ以外は任意のデータが入るということにします。 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)と演算(ALU2)に分かれます。 CPUの制御機能 CPUが命令を実行する手順は以下の通りです。 命令の読み出し(フェッチ) オペコードの解読 オペランドのアドレス計算、データの読み出し 命令実行(演算処理) 命令によっては、3.は省略されることもあります。 cpu.rb # 制御部 def control(memory) end 命令の読み出し(フェッチ) プログラムレジスタと命令レジスタ(IR)3を用います。 ①プログラムレジスタに保持されているメモリアドレスのプログラムを取り出し、命令レジスタに保存します。 ②プログラムレジスタの値を一つ進めます。 cpu.rb # 命令の読み出し(フェッチ) ir = memory.send("m#{pr}") self.pr += 1 オペコードの解読 命令レジスタに保存したオペコードを命令デコーダにより解読し、必要な装置に制御信号を送ります。 メモリオブジェクトを定義するにて説明しますが、今回は命令を[ラベル,オペコード,オペランド]の配列で与えますので、下記のように命令デコーダを定義します。それぞれの命令(インスタンスメソッド)の中身は次の記事で記述します。 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]$/を除く) この工程では、情報がメモリアドレスで与えられた場合、即値に置き換える処理を行います。 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にて行います。 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/O6からデータを入力する OUT・・・I/Oへデータを出力する RPUSH・・・全てのレジスタの内容をスタックに退避する(Register PUSH) RPOP・・・スタックの内容をレジスタに復帰する(Register POP) ここまでのCPUオブジェクト 演算部の処理は次回の記事で記述しますので、今回の記事でのCPUオブジェクトの定義はここまでとします。 ここまでのCPUオブジェクト 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 メモリオブジェクトを定義する メモリはメソッドを持たないプロパティのみの記憶装置として定義します。 本来のCOMET2では命令によって1語命令と2語命令が存在します。今回は簡易的に全て1語命令(1メモリ番地を使用する)として考えます。また、本来65536個の番地を持ちますが、シミュレーションするプログラムはそこまでメモリを必要としないため、0~49番地までを用意することとします。また、命令は[ラベル,オペコード,オペランド]の配列で与えられることとします。 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回 日経XTECH/アセンブラで学ぶコンピュータ基礎のキソ 全5回 Arcanum/CASL2 キャッスルシミュレータ 命令の内容を解読して実行の準備をする装置。機械語の命令を解読して、他の装置へ制御信号を出す。 ↩ Arithmetic and Logic Unitの略。四則演算や論理演算などの処理を、加速器や論理演算器などの演算回路を用いて行う。 ↩ 取り出した命令を一時的に記憶するためのレジスタ(Instruction register)。この概念がCOMET2に存在するのか分かりませんが、処理の流れを分かりやすくするためにもローカル変数を定義します。 ↩ CPUに直接的に解釈・実行させるのではなく、アセンブラに指示を与える命令のこと。見かけ上はCPUへの命令と同じ構文で記述するので擬似命令と呼ぶ。 ↩ 複数の命令を並べて実現した処理全体に名前をつけたもの。擬似命令と同様に、見かけ上はCPUへの命令と同じ構文で記述できる。 ↩ 入出力装置。Input/Output ↩
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む