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