# 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 擴展