はじめに
RISC-V の最も基本となるインストラクション・セット・アーキテクチャ(ISA)モジュールである RV32I: RISC-V 基本整数ISA について紹介します。
アセンブリコードの種類
GNU のアセンブラである gas の場合,アセンブリコードは大きく次の3つに分かれます。
種類 | 例 | 説明 |
---|---|---|
インストラクション | li a5, i |
プログラムコードを変換したCPUに対する命令です。だいたいはアセンブリコードと1対1に対応した機械語命令に変換されますが,中には同じ意味を表す別の機械語列に変換することもあります。ISAが異なると,異なるアセンブリコードで表されます。 |
ラベル | func: |
アドレスに名前をつけたシンボルを定義します。制御命令やロードアドレス命令などと組み合わせます。英数字で表された名前の末尾にコロン(:)をつけます。 |
ディレクティブ | .file "sample.c" |
アセンブラに対する特殊な指示を表します。ピリオド(.)の後に英単語が続きます。 |
RISC-V のインストラクションの読み方
例えば次のように書かれていたとします。
{\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 のインストラクション・セットは比較的単純ですが,それでも結構たくさんの命令が存在します。初学者の学習のしやすさを考慮し,厳選してみました。慣れてきたら,段階的に増やしていくと良いでしょう。
算術演算命令
加算命令
{\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
に書き込みます。算術オーバーフローは無視します。
減算命令
{\rm \underline{sub}tract}
sub rd, rs1, rs2
は,レジスタrs1
の値からレジスタrs2
の値を引いて,結果をレジスタrd
に書き込みます。算術オーバーフローは無視します。
subi rd, rs1, immediate
は存在しません。代わりに,addi
命令を使ってimmediate
の符号を反転させます。つまり,addi x10, x5, -5
とすると,レジスタx5
から5を引いた結果をレジスタx10
に書き込みます。
転送命令
ロード命令
{\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
を書き込みます。
ストア命令
{\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
命令は存在しません。
移動命令 (擬似命令)
{\rm \underline{m}o\underline{v}e}
mv rd, rs1
は,レジスタrs1
の値をレジスタrd
にコピーします。
実際には,addi rd, rs1, 0
に展開されます。
即値ロード命令 (擬似命令)
{\rm \underline{l}oad \, \underline{i}mmediate}
li rd, immediate
は,即値immediate
をレジスタrd
に書き込みます。
実際の機械語命令への展開のしかたは複雑で,即値の具体的な値によって,どのような命令に展開するかが変わってきます。
ロードアドレス命令 (擬似命令)
{\rm \underline{l}oad \, \underline{a}ddress}
la rd, symbol
は,シンボルsymbol
で表されるアドレス値をレジスタrd
に書き込みます。
実際の機械語命令への展開のしかたは複雑で,ディレクティブの設定によって変わってきます。
制御命令
ジャンプ命令 (擬似命令)
{\rm \underline{j}ump}
j offset
は,現在のプログラムカウンタ pc
に,符号拡張された offset
を加えて,pc
に設定します。
offset
がシンボル symbol
で表される場合は,pc
にシンボル値を設定します。
実際には jal x0, offset
に展開されます。
ジャンプ・アンド・リンク命令
{\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
を想定します。
分岐命令(ブランチ命令) (一部,擬似命令)
{\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 のマニュアルなどを参照してください。