アセンブリ言語 その1
この記事はkivantium Advent Calendarの18日目です。
CPUを設計する際に重要な要素の一つが命令セットアーキテクチャ (ISA) です。 これから設計する簡単なCPUのISAとして、分かりやすく参考文献も多いMIPSを採用することにしました。 MIPS ISA の詳細は https://www.cs.cornell.edu/courses/cs3410/2008fa/mips_vol2.pdf が詳しいです。
CPUの構成
昨日の記事で見たように歴史上様々なCPUが存在しており、CPUはこういう形だと言い切るのは難しいのですが、現在生き残っている多くのCPUはレジスタマシンになっています。
レジスタマシンを構成する主な要素は
- プログラムカウンタ (PC)
- 現在行っている命令の番号を保持します
- 命令メモリ
- CPUが実行する命令を保持します
- 汎用レジスタ
- 計算する値や計算結果を格納します
- ALU
- 汎用レジスタの値や命令で指定された値(即値)を入力として受け取り、命令によって指定された計算を行います
- データメモリ
- 汎用レジスタに入りきらないデータの格納や外部との通信などを行います
となっています。
(注: 実際にはデータメモリと命令メモリは同じものであることが多かったりしますが、概念を理解するために詳細は省きます)
これらの要素に対する操作を直接記述していくのが機械語によるプログラムで、バイナリで記述されます。バイナリでプログラムを書くのは人間には厳しいので機械語に対応する命令列をアルファベットで記述できるようにしたのがアセンブリ言語です。
アセンブリ命令の種類
以後しばらくの実装に使う命令セットを種類ごとに紹介します。
演算命令
レジスタ同士の加算やANDなどのいわゆる演算っぽい命令です。
命令 | 記法 | 処理 |
---|---|---|
addiu | addiu rt, rs, immediate |
R[rt] ← R[rs] + SignExtImm |
addu | addu rd, rs, rt |
R[rd] ← R[rs] + R[rt] |
and | and rd, rs, rt |
R[rd] ← R[rs] & R[rt] |
or | or rd, rs, rt |
R[rd] ← R[rs] | R[rt] |
sll | sll rd, rt, shamt |
R[rd] ← R[rt] << shamt |
srl | srl rd, rt, shamt |
R[rd] ← R[rt] >> shamt |
slt | slt rd, rs, rt |
R[rd] ← (R[rs] < R[rt]) ? 1 : 0 |
subu | subu rd, rs, rt |
R[rd] ← R[rs] - R[rt] |
R[rt]
は「rt
で指定された番号の汎用レジスタ」を意味します。SignExtImm
は指定された即値の符号拡張(最上位bitを必要なだけコピーして付け加えたもの)を表します。
ロード・ストア命令
データメモリへの格納を行う命令です。
命令 | 記法 | 処理 |
---|---|---|
lw | lw immediate(rs) |
R[rt] ← Memory[R[rs] + SignExtImm] |
sw | sw immediate(rs) |
Memory[R[rs] + SignExtImm] ← R[rt] |
Memory[R[rs] + SignExtImm]
は「rs
で指定された番号の汎用レジスタに入っている値に符号拡張した即値を足した値をアドレスに持つメモリ」を意味します。
ジャンプ命令
次の命令のアドレスを指定してプログラムカウンタを書き換える命令です。
命令 | 記法 | 処理 |
---|---|---|
j | j address |
PC ← {(PC+4)[31:28], address, 00} |
jal | jal address |
PC ← {(PC+4)[31:28], address, 00}; R[31] ← (PC+4) |
jr | jr rs |
PC ← R[rs] |
条件分岐命令
条件が成立したかどうかに応じて次の命令が変わる命令です。if文やfor文の実装などに不可欠な命令です。
命令 | 記法 | 処理 |
---|---|---|
beq | beq rt, rs, immediate |
if(R[rt]==R[rs]) PC ← (PC+4) + (SignExtImm<<2) |
bne | bne rt, rs, immediate |
if(R[rt]!=R[rs]) PC ← (PC+4) + (SignExtImm<<2) |
命令フォーマット
これらの命令はアセンブリ言語として書いた後、アセンブラというソフトでバイナリ列に変換する必要があります。ここで扱うMIPS 32の場合全ての命令は32bitの値として表されます。フォーマットにはR, I, Jの3種類あります。
R format
汎用レジスタ2つを演算して汎用レジスタに格納する命令はR formatになります。
命令 | op | rs | rt | rd | shamt | funct |
---|---|---|---|---|---|---|
addu | 000000 | XXXXX | XXXXX | XXXXX | 00000 | 100001 |
and | 000000 | XXXXX | XXXXX | XXXXX | 00000 | 100100 |
jr | 000000 | XXXXX | 00000 | 00000 | 00000 | 001000 |
nor | 000000 | XXXXX | XXXXX | XXXXX | 00000 | 100111 |
or | 000000 | XXXXX | XXXXX | XXXXX | 00000 | 100101 |
sll | 000000 | 00000 | XXXXX | XXXXX | XXXXX | 000000 |
srl | 000000 | 00000 | XXXXX | XXXXX | XXXXX | 000010 |
slt | 000000 | XXXXX | XXXXX | XXXXX | 00000 | 101010 |
subu | 000000 | XXXXX | XXXXX | XXXXX | 00000 | 100011 |
I format
汎用レジスタ2つと即値に対する処理を行う命令はI formatになります。
命令 | op | rs | rt | immediate |
---|---|---|---|---|
addiu | 001001 | XXXXX | XXXXX | XXXXXXXXXXXXXXXX |
beq | 000100 | XXXXX | XXXXX | XXXXXXXXXXXXXXXX |
bne | 000101 | XXXXX | XXXXX | XXXXXXXXXXXXXXXX |
lw | 100011 | XXXXX | XXXXX | XXXXXXXXXXXXXXXX |
sw | 101011 | XXXXX | XXXXX | XXXXXXXXXXXXXXXX |
J format
汎用レジスタを指定せずに長い即値を使う命令はJ formatになります。
命令 | op | address |
---|---|---|
j | 000010 | XXXXXXXXXXXXXXXXXXXXXXXXXXX |
jal | 000011 | XXXXXXXXXXXXXXXXXXXXXXXXXXX |
レジスタの使い方
MIPSには汎用レジスタが32個あり、以下のように使い分けることになっています。
名称 | 番号 | 用途 | 呼び出された側が内容を保存する必要があるか? |
---|---|---|---|
$zero | $0 | 読み出すと常に0 | N/A |
$at | $1 | アセンブラが一時的に使用 | No |
$v0-$v1 | $2-$3 | 関数の戻り値や式を評価した結果 | No |
$a0-$a3 | $4-$7 | 関数の引数 | No |
$t0-$t7 | $8-$15 | 一時変数 | No |
$s0-$s7 | $16-$23 | 一時変数だがセーブされる | Yes |
$t8-$t9 | $24-$25 | 一時変数 | No |
$k0-$k1 | $26-$27 | OSのカーネル用に予約 | No |
$gp | $28 | 広域(グローバル)ポインタ | Yes |
$sp | $29 | スタックポインタ | Yes |
$fp | $30 | フレームポインタ | Yes |
$ra | $31 | リターンアドレス | N/A |
次回はこれらの命令をつかってアセンブリ言語でどのようにプログラムを書くのか見ていきます。