はじめに
【rubyでcomet2を作る】①概要の続きです。
今回はCPUオブジェクト、メモリオブジェクトを定義していきたいと思います。
CPUオブジェクトを定義する
CPUクラスを作成し、プロパティとメソッドを定義していきます。
class CPU
# プロパティ
# メソッド
end
必要となるプロパティ
前回の記事で説明した通りCOMET2のCPUは下記4種類のレジスタを持ちます。
- プログラムレジスタ
- スタックポインタ
- フラグレジスタ
- 8つの汎用レジスタ
本来のCOMET2ではフラグレジスタのみ3ビット、それ以外は16ビットの容量を持ちます。
命令によっては16ビット命令と32ビット命令がありますが、今回はビット数まで考えることはせず、全ての命令が16ビット命令(1語命令、1メモリ番地を使用する)という前提で話を進めます。そのためCPUについてもビット数は無視して考え、フラグレジスタは[ZF,SF,OF]という配列、それ以外は任意のデータが入るということにします。
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.は省略されることもあります。
# 制御部
def control(memory)
end
命令の読み出し(フェッチ)
プログラムレジスタと命令レジスタ(IR)3を用います。
①プログラムレジスタに保持されているメモリアドレスのプログラムを取り出し、命令レジスタに保存します。
②プログラムレジスタの値を一つ進めます。
# 命令の読み出し(フェッチ)
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)
・
・ #(全部で28パターン記述。省略)
・
when 'RET' then
ret(memory)
when 'SVC' then
self.svc
end
オペランドのアドレス計算、データの読み出し
COMET2におけるオペランドに含まれる情報は下記3つです。
- レジスタ名(例:'gr0'など)
- 即値(例:'100'など)
- メモリアドレス(ラベルで表される、例:'label1'など、/^gr[0-7]$/を除く)
この工程では、情報がメモリアドレスで与えられた場合、即値に置き換える処理を行います。
# オペランドのアドレス計算、データの読み出し
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にて行います。
def control(memory)
while true
case ir[1]
・
・ #(省略)
・
when 'END'
break
end
end
end
擬似命令とマクロ命令
擬似命令
- START・・・プログラムの始まりを定義する
- END・・・プログラムの終わりを定義する
- DC・・・メモリ上に定数を定義する(Define Constant)
- DS・・・メモリ上に領域を確保する(Define Strage)
マクロ命令
- IN・・・I/O6からデータを入力する
- OUT・・・I/Oへデータを出力する
- RPUSH・・・全てのレジスタの内容をスタックに退避する(Register PUSH)
- RPOP・・・スタックの内容をレジスタに復帰する(Register POP)
ここまでのCPUオブジェクト
演算部の処理は次回の記事で記述しますので、今回の記事でのCPUオブジェクトの定義はここまでとします。
ここまでのCPUオブジェクト
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番地までを用意することとします。また、命令は[ラベル,オペコード,オペランド]の配列で与えられることとします。
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オブジェクトの演算部の定義を行っていきたいと思います。
参考資料
-
命令の内容を解読して実行の準備をする装置。機械語の命令を解読して、他の装置へ制御信号を出す。 ↩
-
Arithmetic and Logic Unitの略。四則演算や論理演算などの処理を、加速器や論理演算器などの演算回路を用いて行う。 ↩
-
取り出した命令を一時的に記憶するためのレジスタ(Instruction register)。この概念がCOMET2に存在するのか分かりませんが、処理の流れを分かりやすくするためにもローカル変数を定義します。 ↩
-
CPUに直接的に解釈・実行させるのではなく、アセンブラに指示を与える命令のこと。見かけ上はCPUへの命令と同じ構文で記述するので擬似命令と呼ぶ。 ↩
-
複数の命令を並べて実現した処理全体に名前をつけたもの。擬似命令と同様に、見かけ上はCPUへの命令と同じ構文で記述できる。 ↩
-
入出力装置。Input/Output ↩