---
# System prepended metadata

title: 第 3-1 講 RISC-V 指令介紹
tags: [計算機結構]

---

# RISC 簡介與類型

結構如下：

$$
\text{RV + 暫存器長度 + 基礎指令型號 + 擴展型號}
$$

例如：

$$
\text{RV32I}\\
$$

這個 RISC 指令集每個暫存器長度是 32 bit，並且是單純只有基本整數指令集。

$$
\text{RV32EM}\\
$$

這個 RISC 指令集每個暫存器長度是 32 bit，並且有嵌入式系統的基本整數指令集，還有整數乘除法標準擴充。

詳情可以看[維基百科的詳細介紹](https://zh.wikipedia.org/zh-tw/RISC-V#%E6%8C%87%E4%BB%A4%E5%AD%90%E9%9B%86)。


# 基礎指令集 -- RV32IM 介紹

下面介紹 RV32I 中的指令。

- [這是超方便的查詢資料庫](https://msyksphinz-self.github.io/riscv-isadoc/html/index.html)
- [這是救命稻草 1](https://zhuanlan.zhihu.com/p/502146080)
- [這是救命稻草 2](https://zhuanlan.zhihu.com/p/540887151)
- [這是救命稻草 3](https://ithelp.ithome.com.tw/articles/10194907)

:::info
下面語法介紹建議直接看這個救命網站
- [RISC 組語語法](https://zhuanlan.zhihu.com/p/540887151)
:::

## 邏輯運算、位移及算術指令

當中後綴帶有 i 的，代表是跟「立即數」做運算。

### 邏輯運算

形式如下：

$$
\displaylines{
\text{MNM, rd, rs1, rs2      R-type}\\
\text{MNM, rd, rs1, IMM  I-type}\\
}
$$

其中 MNM 有：`and or xor andi ori xori`，帶有 i 後綴的就是具有 IMM 的指令。

### 位移

形式如下：

$$
\displaylines{
\text{MNM, rd, rs1, rs2      R-type}\\
\text{MNM, rd, rs1, IMM  I-type}\\
}
$$

其中 MNM 有：`sll srl sra slli srli srai`，帶有 i 後綴的就是具有 IMM 的指令。

翻譯：shift left/right logical/arithmetic

算術位移只有右移，目的是保持負數右移後依舊是負數。
>-4 除以 2 後等於 -2

### 算術

形式如下：

$$
\displaylines{
\text{MNM, rd, rs1, rs2      R-type}\\
\text{MNM, rd, rs1, IMM  I-type}\\
}
$$

其中 MNM 有：`add sub addi mul div divu rem remu`，帶有 i 後綴的就是具有 IMM 的指令。

- 沒有 subi
- 乘、除和模除是 M 擴展的酷東西。
- divu 跟 remu 帶有 u 後綴，代表是無符號的運算。

## 數據移動指令

也就是我們常用的存取指令 store 跟 load。

- w：word
- h：half word
- b：byte

4、2、1 個 bytes。

如果是 RV64I，還會有：

- d：double word

8 個 bytes。

### 加載指令

- lw rd, imm(rs1)
- lh rd, imm(rs1)
- lhu rd, imm(rs1)
- lb rd, imm(rs1)
- lbu rd, imm(rs2)

這裡的 u 後綴一樣是無符號的意思；如果讀取的時候是一個負數，那麼暫存器就會高位填滿 1

### 存取指令

- sw rd, imm(rs2)
- sh rd, imm(rs2)
- sb rd, imm(rs2)

存取的動作就無關符號了。

### 數據移動指令

這三個是好用的偽指令：

- mv rd, rs
    - 是透過 addi rd, rs, zero 實作的
- li rd, imm
    - 讀取更大的 IMM 用
- la rd, label
    - 直接讀取某個 label 的地址

:::warning
注意，因為 RV32I 暫存器最大就是 32 bit，因此並沒有 ld 跟 sd 指令，只有 RV64I 才有。
:::

## 條件設置指令

他們的共通特點是，會比較 rs1 跟 rs2 的值，然後把判斷結果寫進 rd，1 代表 True 0 代表 False。

翻譯下來就是 set if lesser than...。

- `slt rd, rs1, rs2`
- `slti rd, rs1, imm`
- `sltu rd, rs1, rs2`
- `sltui rd, rs1, imm`

這四個是主要的判斷式，下面四個是偽指令。

- `seqz rd, rs1`
- `snez rd, rs1`
- `sltz rd, rs1`
- `sgtz rd, rs1`

z 代表跟 zero 比較；greater 可以用對調位置達成。


## 控制流指令

用來控制流程的指令。

### 條件跳轉指令

也就是「分枝」。

形式如下：

$$
\text{MNM, rs1, rs2, label  B-type}\\
$$

其中 MNM 有：`beq bne blt bltu bge bgeu`。

並且有兩個偽指令可以用：

- `beqz rs1, lab`
- `bnez rs1, lab`

`z` 是指 `zero / x0`。

### 無條件跳轉指令

- `j lab`
    - 偽指令，同： jal zero lab
- `jr rs1`
    - 偽指令，同： jalr zero rs1 0
- `jal lab`
    - 偽指令，同： jal ra lab
- `jal rd, lab`
- `jalr rd, rs1, imm`
- `ret`
    - 偽指令，同： jalr zero ra 0

---

# 指令格式 Instruction format

:::info
此章節最好搭配 pipeline 部分食用
:::

RISCV 中有 4 種主要的指令格式，並且有兩種變形，所以總共有 6 種。

<iframe src="https://drive.google.com/file/d/1RX986bsxS-zTTnfiMt1ukVeJ--rQj7l_/preview"  height="380"></iframe>

>圖片來自官方 DOC。下面請隨時服用這張圖
>[這是官方文檔](https://riscv.org/technical/specifications/)

下面四種是主要的格式。

## R-type

R-type 就是暫存器之間的操作。

- 長的如「MNM rd, rs1, rs2」的樣子。

## I-type

I-type 算是 R-type 的一種特化，用於處理「立即數」

- 長的如「MNM rd, rs1, IMM」的樣子
- 如果是讀取指令則會是「MNM rd, IMM(rs1)」

## S-type

S-type 也算是 R-type 的一種特化，用於處理不需要目的地的情形

- 只用於存儲指令，長得像「MNM rs2, IMM(rs1)」的樣子

:::warning
可以發現 I 跟 S 兩種型別很類似，都是從 R 型別換掉一個暫存器的位置放 IMM 的部分區域。
原因是在於為了「保持 rs1 的位置不動」，可以看到兩行指令內都具有 IMM(rs1) 這個取值得共同部分。
:::

## U-type

U-type 是專門處裡比較大的 IMM 時會用到的指令

- 作用是把要讀的大數字 IMM[31:12] 放到 rd 的前 20 個 bit 裡面
- 偽指令 li 其實就是 lui 跟 addi 的組合

:::info
U 型別算是一種比較特別的型別；上面可見大部分的 IMM 都是 12 個 bit，而 RISCV 是以 sign-extended 的方式處理 IMM，所以 IMM 的範圍就是 -2048~2047，如果想要超過這個範圍的話，就是 U 型別指令派上用場的地方。
:::

<iframe src="https://drive.google.com/file/d/1RX986bsxS-zTTnfiMt1ukVeJ--rQj7l_/preview"  height="380"></iframe>

>再次服用這張圖

## B-type

B-type 是從 S-type 延伸出來的，可以發現只差在 IMM 的位置不一樣

- 會發現 IMM[4:1] 跟 IMM[10:5] 的位置是固定的，也就是讓大部分的骨架是一致的。
- 唯一的差別在 IMM[0] 被 IMM[11] 取代掉了，然後 IMM[10:5] 的左邊就多放了 IMM[12]。

這類型的指令主要是用於「分枝 Branching」，這也是 B 字頭的來源，例如：

$$
\text{beq rs1, rs2, }\underbrace{\text{label}}_{\text{IMM}}
$$

因為分支的時候 IMM 是某個 label，或者說某個區段的起始位置，RISCV 考量了以下事情修改了表達 label 的方法：

1. PC-relative addressing：
    - label 的值並非直接就是該區段的地址，而是該區段相對 PC 的地址
2. 4 的倍數：
    - 一行 machine code 在 RISCV 中只需要 4 個 bytes，所以 machine code 的記憶體起始位置都是 4 個 bytes 為間隔。
    - 所以跟 PC 相差的值也是 4 的倍數，因此 IMM 的 IMM[1] 跟 IMM[0] 一定是 0，理論上可以不用花空間去紀錄
    - 但是為了未來著想，以後可能會出現長度只有 16 bit 的 machine code，也就是上面提到的都會變成 2 的倍數，所以實際上只有忽略 IMM[0] 而已

因此就發生了上面的結構改變。

## J-type

J-type 是從 U-type 延伸過來的，但是結構也融合了 I-type 的特色。

從下面的回顧圖可以發現，

- 在 IMM 的左半邊，J 跟 I 在 IMM[10:1] 的部分是重疊的，
- 在 IMM 的又半邊，J 跟 U 在 IMM[19:12] 的部分是重疊的，

然後錯開的部分就填上剩餘的段落。

J-type 指令主要是用於「無條件跳躍 Jumping」，這也是 J 字頭的來源，例如：

$$
\text{jal rd,  }\underbrace{\text{label}}_{\text{IMM}}
$$

不過要注意，jalr 是 I 型別的，因為他的指令內具有兩個暫存器：

$$
\text{jalr rd, rs1, IMM}
$$

而因為又是跟 machine code 地址有關，所以除了也有 PC-relative addressing 之外，也會發生上面提到的 IMM[0] 不用紀錄的現象。

<iframe src="https://drive.google.com/file/d/1RX986bsxS-zTTnfiMt1ukVeJ--rQj7l_/preview"  height="380"></iframe>
>再再次服用這張圖



# 平行化設計 -- A 擴展