昨日總算是克服了我們的簡單目標,最後一塊拼圖就是在前幾天規劃的 rvgc 函式庫。今天就讓我們開始探索 RISC-V 的指令集架構吧!主要的參考來源是 RISC-V 基金會的規格文件。歷史、沿革這些東西對於理解技術在當代科技的地位是非常重要沒有錯,但是這些面向不是本系列的重點,因此予以略過。
給新加入的讀者:筆者是多年來享受電腦系統的方便性的後生晚輩,由於某一天發現自己對於 ELF 的格式如此漠不關心感到汗顏,因此決定以最黑手的方式理解這個已經奮鬥了數十年的規格。
同樣息息相關但我們應該沒有時間觸及的面向是除錯器。如果各位讀者對 debug 相關議題有興趣,這裡是公司夥伴的鐵人賽系列文,歡迎大家去參考參考。
RISC-V 的設計哲學是追求硬體簡化,因此指令集非常精簡。同時,又為了顧及軟體的需求,RISC-V 基金會將指令集拆分為模組化的設計,而由每一個英文字母代表模組的意義。比方說,最基本的模組叫做 I
指令集,因為整數處理的指令都在這裡;又,最近剛結束的 RISC-V 第七次 workshop 裡面有研究團隊報告了 H
指令集代表虛擬化平台需使用的 Hypervisor 等指令。
我們這個函式庫名稱取作 rvgc,是為了代表 RISC-V General-Comppression 的縮寫,G
代表通用的部份,涵蓋 IMAFD
;C
代表壓縮指令。根據目前社群的共識,雖然標準上說的基本支援指令集是 I
,但是事實上如果沒有支援 IMA
,幾乎不可能運行作業系統。對於這個系列文要支援到哪裡,筆者打算至多完成 IMA
,也就是整數操作、乘法除法、以及原子操作指令集,原因無他,要迴避浮點數很簡單,但是在通用的整數領域裡面,不曉得什麼時候可能會碰到非得要乘除法和原子操作的情況;至於壓縮指令,會讓程式設計的內容複雜化,就暫且不做。
今日會開始介紹 rvgc 的實作,主要參考自最新的 2.2 版規格中的 RV32I、RV64I、M 與 A 等四個章節;至於像是編碼的細節之類的部份,筆者參考第 19 章與第 20 章。等到完成的差不多之後,我們會再回頭用其他稍微複雜的程式來檢驗 as 的實作,必要時也重構一些既有的 as 內容。以下筆者會先介紹 RISC-V 64-bit GC 使用的暫存器,然後會介紹指令型態。預計花三天的時間來講解指令集與函式庫,同時為了提昇 go 語言能力,筆者也考慮引進測試框架來強化這個部份的設計。
整數暫存器共有 32 個,寫組語程式時可以用 x0~x31 來代表這些暫存器;因此,在指令的編碼中,一個暫存器會佔去 5 個位元。其中,讀取 x0 總是回傳 0 且寫入無效,算是 RISC (減量指令集 CPU 設計)很通用的一個設定。除了這些編號之外,它們還有一些綽號,是為了彰顯它們的使用方式而命名的別名。以表格列舉如下
真實名稱 | 綽號 | 呼叫後保留 | 說明 |
---|---|---|---|
x0 | zero | 總是為零 | |
x1 | ra | 否 | 回傳時的目標位址 |
x2 | sp | 是 | stack 指標 |
x3 | gp | 不應修改 | global 指標 |
x4 | tp | 不應修改 | thread 指標,與 gp 同屬其他系統機制可能用到的暫存器 |
x5~x7,x28~x31 | t0~t2,t3~t6 | 否 | t 字頭代表暫存用 |
x8~x9,x18~x27 | s0~s1,s2~s11 | 是 | s 字頭暫存器由被呼叫函數儲存 |
x10~x17 | a0~a7 | 否 | a 字頭暫存器代表參數,a0 也作回傳值使用 |
RV64I 的指令集一律是 32-bit 的長度,但由於編碼上的先進設計,讓 C 指令集可以方便地加入 16-bit 指令,能夠大幅壓縮編譯出來的執行檔大小。設計指令型態時,原始團隊的優先考量是讓硬體在解碼指令的時候能夠越方便越好,因此刻意讓功能相近的區間能夠盡量在同一個區段;比方說所有種類的指令裡面,暫存器最多會用到三個,最少也會用到一個,但它們所佔的空間都相同。這個原則能夠精簡化硬體在處理暫存器編碼時的努力,進而提昇效能。
根據規格書,總共有 4 種指令集型態。為求排版美觀,筆者這裡用純文字格式編排,再以無 markdown 格式輸出。ASCII 表格上方數字是該欄位的上界與下界位元,數字越大表示作為 32-bit 整數越高位(significant)。
| 31 25 | 24 20 | 19 15 | 14 12 | 11 07 | 06 00 |
+------------+---------+---------+------------+--------+------------+
| funct7 | rs2 | rs1 | funct3 | rd | opcode |
+------------+---------+---------+------------+--------+------------+
R 型態指令包含三個暫存器,完全在處理暫存器。暫存器的邏輯運算指令是這一類的大宗。
| 31 20 | 19 15 | 14 12 | 11 07 | 06 00 |
+----------------------+---------+------------+--------+------------+
| immediate[11:0] | rs1 | funct3 | rd | opcode |
+----------------------+---------+------------+--------+------------+
牽涉到一個 12-bit 整數的使用的指令。從記憶體取值即是一例:以 rs1
為基準,12-bit 有號整數 imm
為調整的記憶體位址的內容,會被存入 rd
暫存器中。
| 31 25 | 24 20 | 19 15 | 14 12 | 11 07 | 06 00 |
+------------+---------+---------+------------+--------+------------+
| imm[11:5] | rs2 | rs1 | funct3 | i[4:0] | opcode |
+------------+---------+---------+------------+--------+------------+
例子:存入值到記憶體:以 rs1
為基準,12-bit 有號整數 imm
為調整的記憶體位址的內容,會被寫入 rs2
的內容。
| 31 12 | 11 07 | 06 00 |
+---------------------------------------------+--------+------------+
| immediate[31:12] | rd | opcode |
+---------------------------------------------+--------+------------+
這個指令需要動到 rd
暫存器中高達 20-bit 的內容。有兩個主要的指令使用這個格式,它們是 lui
指令,代表將 rd
暫存器的 [31:12]
取代為指令中的整數;另一個是 auipc
,將 rd
取代為 pc
暫存器加上這個整數部份,常用於函數呼叫。
今日我們開始介紹 RISC-V 指令集了,相較於標題中的酷炫關鍵詞 go、binutils,它是最慢登場的。本日的介紹應該讓各位讀者基本地認識了 RISC-V 指令集架構才是,甚至也可能讓各位想起了計算機結構裡面的 MIPS,他們的確有相當的淵源。RISC-V 正在緩慢崛起的此時,各位除了觀望之外,希望筆者以及夥伴的的相關系列文能夠讓各位有種見證歷史的感覺,如果真的能學到東西那就更棒了。無論如何,我們明日再會!