2 6 窺探 ISA 的精妙設計 - ianchen0119/AwesomeCS GitHub Wiki
學習 IEEE-754 的浮點數設計讓我們知道工程師對於每一個 bit 都是斤斤計較的。這樣的觀念同樣可以套用在 ISA 上面,即使現今的 CPU 有 32 位元以上的定址能力,也不代表我們所使用的組語指令可以一次處理 32 bits 的資料。 本篇文章會跟大家分享筆者在 ARM 與 RISC-V 上的發現,一起來學習兩個不同的 ISA 分別碰到哪些問題以及它們特有的處理方法。
ARM
在學習過 RV32I 的 Format 後,要去看別家 ISA 的 Spec 絕非難事。相信各位讀者都能看懂下圖的:
以其中的 MOV 指令為例:
MOV r0, #0x1000
該指令會讓 R0 暫存器儲存整數 4096 。 不過在 MOV 指令的格式表中, Oprand2 僅被分配到 12 個位元,這意味著我們只能存放 0 - 8191 的數值,若我們今天想要儲存遠大於 8191 的整數時該怎麼做呢? 在 ARM 中,他利用了 Rotate 達到該目的。
Rotate
ARM 不像其他 ISA ,沒有直接使用 immediate 指令,所以像 MOV 指令會透過 bit 25 來得知 operand2 為 register 還是 immediate , ARM 設計將 Oprand2 的 12 個 bits 分配成兩個部分:
- 4 bits 用來表示 16 種位移方式,因此我們可以選擇 rotate 2, 4, 8, ..., 32 個 bits 。
- 8 bits 用來表示 256 種數字組合,如果沒有進行 Rotate ,剛好能表示範圍為 0 - 255 的數字。
上圖為 rotate 4 個 bits 的示意圖,也就是將 LSB 的 4 個 bits 挪到 MSB 的位置,其指令如下:
MOV r0, r0, ROR #4
16 種位移模式的資料分布狀況也都可以從下圖得知:
如此一來,就能利用 12 個 bit 完整的操作 32 bit 的空間。
RISC-V
RISC-V 採用了 immediate 的方式,直接存取立即數。 關於 Instruction set 的格式,下圖都有紀錄:
根據上圖,因為還需要保留 register, funct, opcode 等資訊,不難發現在任何指令集的 immediate 都不可能分配到 32 bits 的空間。 不過, RISC-V 明顯是個狠腳色,如果一個指令做不完,那我就多做幾步即可,以 Hello, world! 程式的彙編檔案為例:
.text
.align 2
.globl main
main:
addi sp, sp, -16
sw ra, 12(sp)
lui a0, %hi(string1)
addi a0, a0, %lo(string1)
lui a0, %hi(string2)
addi a0, a0, %lo(string2)
call printf
lw ra, 12(sp)
addi sp, sp, 16
li a0, 0
ret
.secton rodata
.balign 4
string1:
.string "Hello, %s!\n"
string2:
.string "world"
重點放在第 7 - 8 行, lui
以及 addi
都是屬於 RV32I 指令,用途分別為:
- ADDI
常數部分為 sign-extended 12-bit ,會將 12-bit 做 sign-extension 成 32-bit 後,再與 rs1 暫存器做加法運算,將結果寫入 rd 暫存器,addi rd, rs1, 0 可被使用來當做 mov 指令。
- LUI
將 unsigned 20-bit 放到 rd 暫存器的最高 20-bit,並將剩餘的 12-bit 補 0 ,此指令可與 ADDI 搭配,一起組合出完整 32-bit 的數值。
31(MSB) | 30 | 29 | 28 | 27 | 26 | 25 | 24 | 23 | 22 | 21 | 20 | 19 | 18 | 17 | 16 | 15 | 14 | 13 | 12 | 11 | 10 | 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0(LSB) |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
19(imm) | 18(imm) | 17(imm) | 16(imm) | 15(imm) | 14(imm) | 13(imm) | 12(imm) | 11(imm) | 10(imm) | 9(imm) | 8(imm) | 7(imm) | 6(imm) | 5(imm) | 4(imm) | 3(imm) | 2(imm) | 1(imm) | 0(imm) | 4(rd) | 3(rd) | 2(rd) | 1(rd) | 0(rd) | 6(opcode) | 5(opcode) | 4(opcode) | 3(opcode) | 2(opcode) | 1(opcode) | 0(opcode) |
而 %hi()
和 %lo()
又能分別為我們取高位的 20 bits 以及低位的 12 bits ,透過這個技巧,我們就能在 RISC-V 架構中完整的利用 32 bits 的空間囉!