前回までのあらすじ
- 自作PCを作ろう!
- まずメモリを作ったよ!(動かしてない)
- ISAを作ったよ!(アセンブラやコンパイラはまだ)
基本設計
ここではまずアセンブラを作成します.
最終的に作成したいのはコンパイラですが,そのコンパイラは,プログラムをアセンブリ言語に翻訳し,そのアセンブリ言語を機械語にする二段階の動きにしようと思っています.
理由としては,いきなり機械語に翻訳しちゃうのはデバッグが辛かろうと思ったからです.
あとは,コンパイラの自作経験がないのでその勉強をするための時間稼ぎでもあります.
レジスタに名前を付けよう
まず,CPU内に存在するレジスタの一部には名前を付けたいと思います.
後ろから順番に埋めていくことにします.
アドレス | 名前 | 機能 |
---|---|---|
6'h1f | PC | プログラムカウンタ |
6'h1e | SP | スタックポインタ |
6'h1d | RAX | 演算結果 |
6'h1c | RSI | 引数が格納されているレジスタの一つ目の番地 |
6'h1b | FLG | フラグ.書き込み禁止.詳しくは後述 |
フラグについて
フラグはレジスタ内の6'h1b
を使用することにします.
詳しくは以下の通り.
ビット | 名前 | 機能 |
---|---|---|
0ビット目 | CF | キャリーフラグ.桁あふれが生じたときtrue |
1ビット目 | ZF | ゼロフラグ.計算結果が0のときtrue,1のときfalse |
2ビット目 | OF | オーバーフローフラグ.符号付の数の足し算や引き算において,最上位ビットが不正に書き変えられてしまったときにtrue |
3ビット目 | SF | サインフラグ.計算結果の符号を保存する.正ならfalse,負ならtrue |
アセンブリ言語
全体のルールを決め,その後,前回作成した機械語ひとつひとつに対応した命令を作ります.
全体のルール
-
;
以降はコメント - 関数ごとにブロックを作る.
main
ブロックは必ずある - main関数を一番最後に書く.またほかの関数についても,使用するタイミングでまだ宣言されていない,ということにはならないようにする
- このプログラムで使用するすべての関数名を一行目で挙げる
- インデントはスペース四個
- マジックナンバーは,とくに指定がなければ10進数として扱う.末尾に
b
,o
,h
がある場合はそれぞれ2進数,8進数,16進数として扱う - レジスタの番地は
r10
のように表記する.特に指定がなければ10進数で,末尾に文字があれば指定された奇数で扱うのはマジックナンバーと同様 - ループ命令はない
例
.global get_num, main
get_num:
mov rax 42 ; 返り値は42
ret
main:
call num
ret
演算系(P系)
r01
とr02
の演算結果をr03
に格納します.なお,処理と同時にプログラムカウンタや各種フラグも更新することとします.
main:
and r03 r01 r02 ; 論理積
or r03 r01 r02 ; 論理和
xor r03 r01 r02 ; 排他的論理和
not r03 r01 ; 反転 r02は使わない
nand r03 r01 r02 ; 否定論理積
add r03 r01 r02 ; 算術和
sub r03 r01 r02 ; 算術差
addi r03 r01 r02 ; 符号を考慮する算術和
subi r03 r01 r02 ; 符号を考慮しない算術差
シフト系(S系)
r01
をr02
またはイミディエイトデータ分だけシフトしr03
に格納します.イミディエイトデータを使用するかどうかは,引数が二つ七日三つ七日で判断します.
また,処理と同時にプログラムカウンタを更新し,各種フラグをリセットします.
main:
sll r03 r01 r02 ; 左シフト命令
srl r03 r01 r02 ; 右シフト命令
sla r03 r01 r02 ; 算術左シフト命令
sra r03 r01 r02 ; 算術右シフト命令
代入系(A系)
r01
またはイミディエイトデータをr03
に格納します.
処理と同時にプログラムカウンタを更新し,各種フラグをリセットします.
main:
mov r03 r01 ; イミディエイトデータを代入
分岐系(F系)
分岐系は2クロックで処理を完遂します.本とか解説サイトを見ていると,先に引き算や足し算をして結果の一部をフラグに格納し,それがtrueかfalseかで分岐したりしなかったりするという説明が多かったので従います.アセンブリ言語って確か機械語と一対一で対応していたはずなんですが,よく分かりません.アセンブリ言語にシステムコールがあるのもよく分かりません.
処理と同時に各種フラグをリセットします.
main:
eq 42 ; 直前の減算結果が0ならpcにイミディエイトデータを足す.ZFで判断
ne 42 ; 直前の減算結果が0ではないならpcにイミディエイトデータを足す.ZFで判断
lt 42 ; 直前の減算結果が負ならpcにイミディエイトデータを足す.SFで判断
gt 42 ; 直前の減算結果が正ならpcにイミディエイトデータを足す.SFで判断
elt 42 ; 直前の減算結果が0または負ならpcにイミディエイトデータを足す.ZFとSFで判断
egt 42 ; 直前の減算結果が0または正ならpcにイミディエイトデータを足す.ZFとSFで判断
ジャンプ系(J系)
レジスタ内の数字はすべて符号なし整数として扱います.
処理と同時に各種フラグをリセットします.
main:
jmp rs1 ; レジスタに保存された場所へジャンプ
jmp 41h ; 即値で指定された場所へジャンプ
メモリ系(M系)
r01
またはイミディエイトデータでメモリの番地を指定し,その番地のデータをr03
へ読み込み or 書き込みします.
処理と同時にプログラムカウンタを更新し,各種フラグをリセットします.
一クロックで完了するとは限りません.
main:
rm r03 r01 ; メモリ読み込み.r01を指定しなければイミディエイトデータで指定した番地のデータをr03へ読み込む
wm r01 r03 ; メモリ書き込み.r01を指定しなければイミディエイトデータで指定した番地のデータへr03の値を書き込む
アセンブラ
本当はここにコードを書くつもりだったんですが,書いていると思っていたより長くなってきたので次回にします.
次回の目標
アセンブラを作る