RV32I インストラクション・セット

# はじめに

RISC-V の最も基本となるインストラクション・セット・アーキテクチャ(ISA)モジュールである **RV32I**: RISC-V 基本整数ISA について紹介します。

# アセンブリコードの種類

GNU のアセンブラである gas の場合，アセンブリコードは大きく次の3つに分かれます。

|種類|例  |説明|
|:---|:--|:---|
|**インストラクション**| `li a5, i` | プログラムコードを変換したCPUに対する命令です。だいたいはアセンブリコードと1対1に対応した機械語命令に変換されますが，中には同じ意味を表す別の機械語列に変換することもあります。ISAが異なると，異なるアセンブリコードで表されます。 |
|**ラベル**| `func:` | アドレスに名前をつけた**シンボル**を定義します。制御命令やロードアドレス命令などと組み合わせます。英数字で表された名前の末尾にコロン(:)をつけます。 |
|**ディレクティブ**| `.file "sample.c"` | アセンブラに対する特殊な指示を表します。ピリオド(.)の後に英単語が続きます。|

# RISC-V のインストラクションの読み方

例えば次のように書かれていたとします。

```math
{\rm \underline{s}et} \, {\rm \underline{l}ess} \, {\rm \underline{t}han} \left\{
\begin{array}{l}
\_ \\
{\rm \underline{i}mmediate}
\end{array}
\right\}
\left\{
\begin{array}{l}
\_ \\
{\rm \underline{u}nsigned}
\end{array}
\right\}
```

この図式で表されるRV32Iの命令は，`slt`, `slti`, `sltu`, `sltiu` の4つです。

読み方としては，次の通りです。

* アンダーラインの引かれている文字をつなぎ合わせて命令を作ります。
* 「{ }」で囲まれている1組の項目は，その中の各行のどれかを選んだ文字となります。
* アンダースコア( \_ )は空文字，すなわち文字がないことを意味します。

# RV32I のインストラクション・セット (一部)

RV32I のインストラクション・セットは比較的単純ですが，それでも結構たくさんの命令が存在します。初学者の学習のしやすさを考慮し，厳選してみました。慣れてきたら，段階的に増やしていくと良いでしょう。

## 算術演算命令

### 加算命令

```math
{\rm \underline{add}} \left\{
\begin{array}{l}
\_ \\
{\rm \underline{i}mmediate}
\end{array}
\right\}
```

`add rd, rs1, rs2` は，レジスタ`rs1`とレジスタ`rs2`の値を加えて，結果をレジスタ`rd`に書き込みます。**算術オーバーフロー**，すなわち加算の結果，レジスタの桁があふれた場合は無視します。

immediate は**即値**，すなわちレジスタではなく数値を与えることを意味します。

`addi rd, rs1, immediate` は，レジスタ`rs1`の値と，即値`immediate`を符号付きだと解釈して加えて(このようなことを「レジスタ`rs1`の値に`immediate`を**符号拡張**して加える」と言います)，結果をレジスタ `rd` に書き込みます。算術オーバーフローは無視します。

### 減算命令

```math
{\rm \underline{sub}tract}
```

`sub rd, rs1, rs2` は，レジスタ`rs1`の値からレジスタ`rs2`の値を引いて，結果をレジスタ`rd`に書き込みます。算術オーバーフローは無視します。

`subi rd, rs1, immediate` は**存在しません**。代わりに，`addi`命令を使って`immediate`の符号を反転させます。つまり，`addi x10, x5, -5` とすると，レジスタ`x5`から5を引いた結果をレジスタ`x10`に書き込みます。

## 転送命令

### ロード命令

```math
{\rm \underline{l}oad}
\left\{
\begin{array}{l}
{\rm \underline{b}yte} \\
{\rm \underline{h}alfword}
\end{array}
\right\}
\left\{
\begin{array}{l}
\_ \\
{\rm \underline{u}nsigned }
\end{array}
\right\}\\
{\rm \underline{l}oad}\,{\rm \underline{w}ord}
```

ロード命令は，指定したメモリから値を読み取って結果にレジスタ`rd`に書き込む命令です。

メモリのアドレスの指定の方法(**アドレッシング・モード**(addressing mode)と呼びます)は，レジスタ`rs1`にオフセットoffsetの値を符号付きだと解釈して(符号拡張)加えた値をアドレスとします。このような指定のしかたを**ベース・オフセット**もしくは**レジスタ・オフセット**と呼びます。

`lb rd, offset(rs1)` は，レジスタ`rs1`で表される値と符号拡張されたoffsetの値を加えた値をメモリアドレスとして解釈して1バイトロード(読み込み)し，結果を符号拡張してレジスタ`rd`に書き込みます。

たとえば，レジスタ`rs1`の値が `0x0100`，offsetの値が `0x40` だったときには `0x140` 番地のメモリを1バイト読み，その値が0xffだったとすると，レジスタ`rd`には -1 すなわち `0xffffffff` を書き込みます。

`lbu rd, offset(rs1)` は，レジスタ`rs1`で表される値と符号拡張されたoffsetの値を加えた値をメモリアドレスとして解釈して1バイトロードし，結果を**ゼロ拡張**してレジスタ`rd`に書き込みます。

たとえば，レジスタ`rs1`の値が `0x0100`，offsetの値が -1 すなわち `0xffffffff` だったときには `0xff` 番地のメモリを1バイト読み，その値が0xffだったとすると，レジスタ`rd`には 255 すなわち `0x000000ff` を書き込みます。

`lb`と`lbu`の違いは，'0x80`から`0xff`の間の値(すなわち最上位ビットが1の場合)をレジスタ`rd`に書き込む場合に，値が符号付きになるか符号無しになるかです。符号付きの場合は符号拡張(上位ビットを最上位ビットの値にする)を行い，符号無しの場合はゼロ拡張(上位ビットを0にする)を行います。

`lh rd, offset(rs1)` は，レジスタ`rs1`で表される値と符号拡張されたoffsetの値を加えた値をメモリアドレスとして解釈して2バイトロードし，結果を符号拡張してレジスタ`rd`に書き込みます。

たとえば，レジスタ`rs1`の値が `0x0100`，offsetの値が `0x40` だったときには `0x140` 番地のメモリを1バイト読み，その値が0xffffだったとすると，レジスタ`rd`には -1 すなわち `0xffffffff` を書き込みます。

`lhu rd, offset(rs1)` は，レジスタ`rs1`で表される値と符号拡張されたoffsetの値を加えた値をメモリアドレスとして解釈して2バイトロードし，結果を**ゼロ拡張**してレジスタ`rd`に書き込みます。

たとえば，レジスタ`rs1`の値が `0x0100`，offsetの値が -1 すなわち `0xffffffff` だったときには `0xffff` 番地のメモリを1バイト読み，その値が0xffだったとすると，レジスタ`rd`には 65535 すなわち `0x0000ffff` を書き込みます。

`lh`と`lhu`の違いは，'0x8000`から`0xffff`の間の値(すなわち最上位ビットが1の場合)をレジスタ`rd`に書き込む場合に，値が符号付きになるか符号無しになるかです。符号付きの場合は符号拡張(上位ビットを最上位ビットの値にする)を行い，符号無しの場合はゼロ拡張(上位ビットを0にする)を行います。

`lw rd, offset(rs1)` は，レジスタ`rs1`で表される値と符号拡張されたoffsetの値を加えた値をメモリアドレスとして解釈して4バイトロードし，結果をレジスタ`rd`に書き込みます。

たとえば，レジスタ`rs1`の値が `0x0100`，offsetの値が `0x40` だったときには `0x140` 番地のメモリを1バイト読み，その値が0xffffffffだったとすると，レジスタ`rd`には -1 すなわち `0xffffffff` を書き込みます。

### ストア命令

```math
{\rm \underline{s}tore}
\left\{
\begin{array}{l}
{\rm \underline{b}yte} \\
{\rm \underline{h}alfword} \\
{\rm \underline{w}ord}
\end{array}
\right\}
```

ストア命令は，レジスタ`rd`の値を指定したメモリに書き込みます。メモリのアドレッシング・モードは，ロード命令と同様，レジスタ`rs`を用いたベース・オフセットです。

`sb rs2, offset(rs1)` は，レジスタ`rs2`の値の下位8ビットを，レジスタ`rs1`で表される値と符号拡張されたoffsetの値を加えた値をメモリアドレスとして解釈してメモリにストア(書き込み)します。

たとえば，レジスタ`rs2`の値が `0xffffffff` だった場合には，下位8ビットの値である `0xff`を，レジスタ`rs1`の値が `0x0100`，offsetの値が `0x40` だったときには `0x140` 番地のメモリに書き込みます。

`sh rs2, offset(rs1)` は，レジスタ`rs2`の値の下位16ビットを，レジスタ`rs1`で表される値と符号拡張されたoffsetの値を加えた値をメモリアドレスとして解釈してメモリにストアします。

たとえば，レジスタ`rs2`の値が `0xffffffff` だった場合には，下位16ビットの値である `0xffff`を，レジスタ`rs1`の値が `0x0100`，offsetの値が `0x40` だったときには `0x140` 番地のメモリに書き込みます。

`sw rs2, offset(rs1)` は，レジスタ`rs2`の値を，レジスタ`rs1`で表される値と符号拡張されたoffsetの値を加えた値をメモリアドレスとして解釈してメモリにストアします。

たとえば，レジスタ`rs2`の値 `0xffffffff`を ，レジスタ`rs1`の値が `0x0100`，offsetの値が `0x40` だったときには `0x140` 番地のメモリに書き込みます。

ストア命令では，`sbu`, `shu` 命令は存在しません。

### 移動命令 (擬似命令)

```math
{\rm \underline{m}o\underline{v}e}
```

`mv rd, rs1` は，レジスタ`rs1`の値をレジスタ`rd`にコピーします。

実際には，`addi rd, rs1, 0` に展開されます。

### 即値ロード命令 (擬似命令)

```math
{\rm \underline{l}oad \, \underline{i}mmediate}
```

`li rd, immediate` は，即値`immediate`をレジスタ`rd`に書き込みます。

実際の機械語命令への展開のしかたは複雑で，即値の具体的な値によって，どのような命令に展開するかが変わってきます。

### ロードアドレス命令 (擬似命令)

```math
{\rm \underline{l}oad \, \underline{a}ddress}
```

`la rd, symbol` は，シンボル`symbol`で表されるアドレス値をレジスタ`rd`に書き込みます。

実際の機械語命令への展開のしかたは複雑で，ディレクティブの設定によって変わってきます。

## 制御命令

### ジャンプ命令 (擬似命令)

```math
{\rm \underline{j}ump}
```

`j offset` は，現在のプログラムカウンタ `pc` に，符号拡張された `offset` を加えて，`pc` に設定します。

`offset` がシンボル `symbol` で表される場合は，`pc` にシンボル値を設定します。

実際には `jal x0, offset` に展開されます。

### ジャンプ・アンド・リンク命令

```math
{\rm \underline{j}ump}\,{\rm \underline{a}nd}\,{\rm \underline{l}ink}
\left\{
\begin{array}{l}
\_ \\
{\rm \underline{r}esister}
\end{array}
\right\}
```

`jal rd, offset` は，次の命令のアドレス `(pc + 4)` をレジスタ `rd` に書き込み，それから現在の `pc` に符号拡張された `offset` を加えて，`pc` に設定します。

`jalr rd, offset(rs1)` は，まず，次の命令のアドレス`pc+4`を一時保存します。レジスタ`rs1`に符号拡張された`offset`を加えて，算出されたアドレスの最下位ビットをマスクする，つまり最下位ビットは強制的に0として `pc` に設定します。最後に，一時保存した`pc+4`の値をレジスタ`rd`に書き込みます。

どちらも，`rd`を省略した場合には，`x1`を想定します。

### 分岐命令(ブランチ命令) (一部，擬似命令)

```math
{\rm \underline{b}ranch}
\left\{
\begin{array}{l}
{\rm \underline{eq}ual} \\
{\rm \underline{n}ot \, \underline{e}qual}
\end{array}
\right\}
\left\{
\begin{array}{l}
\_ \\
{\rm \underline{z}ero}
\end{array}
\right\}
\\
{\rm \underline{b}ranch}
\left\{
\begin{array}{l}
{\rm \underline{g}reater \, than \, or \, \underline{e}qual} \\
{\rm \underline{g}reater \, \underline{t}han} \\
{\rm \underline{l}ess \, than \, or \, \underline{e}qual} \\
{\rm \underline{l}ess \, \underline{t}han}
\end{array}
\right\}
\left\{
\begin{array}{l}
\_ \\
{\rm \underline{u}nsigned} \\
{\rm \underline{z}ero}
\end{array}
\right\}
```

`beq rs1, rs2, offset` は，レジスタ`rs1`がレジスタ`rs2`に等しければ，現在の`pc`に符号拡張された `offset` を加えて，`pc` に設定します。

`beqz rs1, offset` は，レジスタ`rs1`が0に等しければ，現在の`pc`に符号拡張された `offset` を加えて，`pc`に設定します。擬似命令で，実際には `beq rs1, x0, offset` に展開されます。

`bne rs1, rs2, offset` は，レジスタ`rs1`がレジスタ`rs2`に等しくなければ，現在の`pc`に符号拡張された `offset` を加えて，`pc` に設定します。

`bnez rs1, offset` は，レジスタ`rs1`が0に等しくなければ，現在の`pc`に符号拡張された `offset` を加えて，`pc`に設定します。擬似命令で，実際には `beq rs1, x0, offset` に展開されます。

`bge rs1, rs2, offset` は，値を符号付きだと解釈して，レジスタ`rs1`の値がレジスタ`rs2`の値以上であれば，現在の`pc`に符号拡張された `offset`を加えて，`pc`に設定します。

`bgeu rs1, rs2, offset` は，値を符号なしだと解釈して，レジスタ`rs1`の値がレジスタ`rs2`の値以上であれば，現在の`pc`に符号拡張された `offset`を加えて，`pc`に設定します。
  
`bgez rs1, offset` は，値を符号付きだと解釈して，レジスタ`rs1`の値が0以上であれば，現在の`pc`に符号拡張された `offset`を加えて，`pc`に設定します。擬似命令で，実際には `bge rs1, x0, offset` に展開されます。

`bgeuz` 命令はありません。値を符号なしだと解釈した時には，レジスタの値は必ず0以上になるので，値を比較する意味がありません。

`bgt rs1, rs2, offset` は，値を符号付きだと解釈して，レジスタ`rs1`の値がレジスタ`rs2`の値より大きければ，現在の`pc`に符号拡張された `offset`を加えて，`pc`に設定します。擬似命令で，実際には `blt rs2, rs1, offset` に展開されます。

`bgtu rs1, rs2, offset` は，値を符号なしだと解釈して，レジスタ`rs1`の値がレジスタ`rs2`の値より大きければ，現在の`pc`に符号拡張された `offset`を加えて，`pc`に設定します。擬似命令で，実際には `bltu rs2, rs1, offset` に展開されます。
  
`bgtz rs2, offset` は，値を符号付きだと解釈して，レジスタ`rs2`の値が0より大きければ，現在の`pc`に符号拡張された `offset`を加えて，`pc`に設定します。擬似命令で，実際には `blt x0, rs2, offset` に展開されます。

`bgtuz` 命令はありません。レジスタが0より大きいかどうかで分岐するので，値を符号なしだと解釈した時には，0と等しいかどうかの条件分岐と同じ意味になるからです。

`ble rs1, rs2, offset` は，値を符号付きだと解釈して，レジスタ`rs1`の値がレジスタ`rs2`の値以下であれば，現在の`pc`に符号拡張された `offset`を加えて，`pc`に設定します。擬似命令で，実際には `bge rs2, rs1, offset` に展開されます。

`bleu rs1, rs2, offset` は，値を符号なしだと解釈して，レジスタ`rs1`の値がレジスタ`rs2`の値以下であれば，現在の`pc`に符号拡張された `offset`を加えて，`pc`に設定します。擬似命令で，実際には `bgeu rs2, rs1, offset` に展開されます。
  
`blez rs2, offset` は，値を符号付きだと解釈して，レジスタ`rs2`の値が0以下であれば，現在の`pc`に符号拡張された `offset`を加えて，`pc`に設定します。擬似命令で，実際には `bge x0, rs2, offset` に展開されます。

`bleuz` 命令はありません。レジスタが0以下かどうかで分岐するので，値を符号なしだと解釈した時には，0と等しいかどうかの条件分岐と同じ意味になるからです。

`blt rs1, rs2, offset` は，値を符号付きだと解釈して，レジスタ`rs1`の値がレジスタ`rs2`の値未満であれば，現在の`pc`に符号拡張された `offset`を加えて，`pc`に設定します。

`bltu rs1, rs2, offset` は，値を符号なしだと解釈して，レジスタ`rs1`の値がレジスタ`rs2`の値未満であれば，現在の`pc`に符号拡張された `offset`を加えて，`pc`に設定します。
  
`bltz rs1, offset` は，値を符号付きだと解釈して，レジスタ`rs1`の値が0未満であれば，現在の`pc`に符号拡張された `offset`を加えて，`pc`に設定します。擬似命令で，実際には `bge rs1, x0, offset` に展開されます。

`bltuz` 命令はありません。値を符号なしだと解釈した時には，レジスタの値は必ず0以上になるので，値を比較する意味がありません。


# ディレクティブについて

さしあたり，無視して構いません。

詳しく知りたくなったら，gas のマニュアルなどを参照してください。