Skip to content
Suzu

ISA 是軟體、CPU之間的規則:軟體可以用哪些機器指令,硬體用這些指令要產出什麼結果。

ISA (Instruction Set Architecture)

我自己平常比較喜歡白話一點的方式,

所以這邊列幾點舉例,

基本上,ISA 就是給 CPU 看的語言規則:


  • 能不能做加法?加法指令怎麼寫、怎麼 encode?

  • 有哪些 registers 可以用?每個有多少 bytes?

  • 如何從 memory 讀資料、寫回資料?

  • 怎麼跳去其他位置執行程式?


常見的 ISA 有:x86-64、AArch64、RISC-V


Code Explanation

我們用 C 語言來舉例

#include <stdint.h>

uint32_t add(uint32_t a, uint32_t b){
  return a + b;
}

把兩個 unsigned 32 bit int 相加回傳

在執行過程 compiler 會轉成目標 ISA 的 assembly

x86-64

Intel 語法、System V AMD64 Calling Convention:

add:
  mov eax, edi; edi 先丟進 eax,而且變數傳遞順序是 di->si->dx
  add eax, esi; eax 是我們的回傳值,所以加上去就好,不用再另外找 register
  ret

AArch64

Apple Silicon 使用的指令集

add:
  add, w0, w0, w1; w0 = w0 + w1
  ret

w0, w1是 32 bit,對應到 x0, x1 是 64 bit

RISC-V

以 RV32I 為例

add:
  add a0, a0, a1 # a0 = a0 + a1
  ret

a0, a1 是 x10, x11 的慣用別名

做點區分

看到這邊可能也會開始有人把 ISA, Calling Convention 開始搞混了,

這邊我們比較一下

項目說明
ISA有哪些暫存器、指令,指令如何運作
Calling Convention,屬於 ABI 的一部份參數放哪個暫存器、回傳值放哪、誰負責保存暫存器

記憶體的例子

既然談到 C 語言,

那我們也勢必要來看看 pointer 的例子:

#include <stdint.h>

void increase(uint32_t *p){
  *p = *p + 5;
}

關於 uint32_t 的部分可以參考 < 我還沒打完,之後更新XD >

假設,

uint32_t value = 10;
increase(&value); // 做完後 value = 10

用 RV32I assembly 表示:

increase:
  lw    t0, 0(a0)    ; 0(a0) 是指 register a0 的 addr 再加上 offset 0
  addi  t0, t0, 5    ; t0 = 10 + 5 = 15
  sw    t0, 0(a0)    ; 把 t0 的 32-bit 資料寫回原地址,所以 value = 15
  ret

像這個例子我們就可以看出 RISC-V 一個很重要的規則,

-> Load-Store Architecture

  • 一般整數算術指令:用 register 操作處理
  • 要處理記憶體中的數值:先用 load 讀,再用 store 寫回

這個理解後我們看 x86-64 應該會比較容易懂,

同樣的東西會被表示成:

increase:
  add DWORD PTR [rdi], 5   ; [rdi] 代表 rdi 存的地址指向的記憶體
  ret                      ; p.s. DWORD 代表大小是 32-bit

一行就可以直接解決,

但要特別注意,

這邊比較少行不代表效能比較快


一條指令不代表一個 clock 週期,

也不會保證比三條指令快,

CPU 還是要走完 read, compute, write,


相信看到這邊應該有會有人問了,

Q: 「阿那個 add 不就是一條 assembly,怎麼會有需要拆開來看的問題?」

這邊我們可以再探討多一點:


Dive Inside

其實像 add 這幾個字,跑的時候不會被 CPU 讀到,

他只是提供給人閱讀的 mnemonic(直翻是記憶術,但這邊可以看成是 容易理解的符號),

畢竟真正執行的還是 binary machine code,


再看一次 RV32I 的例子:

addi x5, x0, 5

意思是

x5 = x0 + 5

RISC-V 的 x0 固定讀出 0,所以是把 5 丟進 x5

這條指令的 32-bit machine code 是:

0x00500293

這串號碼其實就是 ISA 定義出來的

包括 32-bit 裡面:

  • 哪些表示操作種類
  • target register
  • source register
  • 常數 5

CPU 按照規則去 decode 並執行


btw 其實我們前面的 ret 在 RISC-V 也是 pseudoinstruction,

assembler 會把他轉成真正的指令:

jalr x0, 0(x1)

代表跳到 x1 儲存的 ret addr


這邊也放上 RISC-V 常見的 alias(ABI 定義):

編號名稱別名約定用途
x0zero固定讀出 0
x1ra儲存函式 ret addr
x2spStack pointer
x5t0暫存運算資料
x10a0傳遞第一個參數,也用於回傳值
x11a1傳遞第二個參數,也可用於回傳值

補充:ISA vs. microarchitecture

最後 自己有個鑽牛角尖的壞毛病 XD

雖然只是名詞上定義的問題

但其實一開始看我有花了一段時間分清楚他們的差別


所以還是整理了一個表格 給大家參考:

概念在乎的東西例子
ISA軟體能要求 CPU 做什麼?結果必須是什麼?add 如何運算、reg 有多寬
microarchitectureCPU 內部怎麼實現這些要求?Pipeline、亂序執行、分支預測、Cache 配置

例如兩顆 CPU 都支援某一組 RISC-V 指令,

就必須讓這些指令都符合 ISA,

但一顆可以是 In-order execution CPU,另一顆走 OoO,


效能跟耗電上也可能就會差很多了

這個就是 microarchitecture 的範圍,

至於 in-order, out-of-order 是什麼,


我們就等之後的文章再看吧。

Share this page