一、RISC-V GCC工具鏈種類
RISC-V GCC工具鏈與普通的GCC工具鏈基本相同,用戶可以遵照開源的riscv-gnu-toolchain項目(請在Github中搜索riscv-gnu-toolchain)中的說明自行生成全套的GCC工具鏈。
由於GCC工具鏈支持各種不同的處理器架構,因此不同處理器架構的GCC工具鏈會有不同的命名。遵循GCC工具鏈的命名規則,當前RISC-V GCC工具鏈有如下幾個版本:
1、以“riscv64-unknown-linux-gnu-”為前綴的版本,譬如riscv64-unknown-linux-gnu-gcc、riscv64-unknown-linux-gnu-gdb、riscv64-unknown-linux-gnu-ar等。
- “riscv64-unknown-linux-gnu-”前綴表示該版本的工具鏈是64位架構的Linux版本工具鏈。注意:此Linux不是指當前版本工具鏈一定要運行在Linux操作系統的電腦上,此Linux是指該GCC工具鏈會使用Linux的Glibc作為C運行庫。
- 同理,“riscv32-unknown-linux-gnu-”前綴的版本則是32位架構。
- 注意:此處的前綴riscv64(還有riscv32的版本)與運行在64位或者32位電腦上毫無關系,此處的64和32是指如果沒有通過-march和-mabi選項指定RISC-V架構的位寬,默認將會按照64位還是32位的RISC-V架構來編譯程序。有關-march和-mabi選項的含義,請參見第3節。
2、以“riscv64-unknown-elf-”為前綴的版本,則表示該版本為非Linux(Non-linux)版本的工具鏈。注意:
- 此Non-Linux不是指當前版本工具鏈一定不能運行在Linux操作系統的電腦上,此Non-Linux是指該GCC工具鏈會使用newlib作為C運行庫。
- 同上理,此處的前綴riscv64(還有riscv32的版本)與運行在64位或者32位電腦上毫無關系,此處的64和32是指如果沒有通過-march和-mabi選項指定RISC-V架構的位寬,默認將會按照64位還是32位的RISC-V架構來編譯程序。有關-march和-mabi選項的含義,請參見第3節。
3、以“riscv-none-embed-”為前綴的版本,則表示是最新為裸機(bare-metal)嵌入式系統而生成的交叉編譯工具鏈,所謂裸機(bare-metal)是嵌入式領域的一個常見形態,表示不運行操作系統的系統。該版本使用新版本的newlib作為C運行庫,並且支持newlib-nano,能夠為嵌入式系統生成更加優化的代碼體積(Code Size)。開源的蜂鳥E203 MCU系統是典型的嵌入式系統,因此將使用“riscv-none-embed-”為前綴的版本作為RISC-V GCC交叉工具鏈。注意:
- 此版本編譯器由於使用newlib和newlib-nano作為C運行庫,所以必須對 newlib底層的樁函數進行移植,否則無法正常使用調用底層樁函數的C函數(譬如printf會調用write樁函數)。
二、riscv-none-embed工具鏈下載
對於riscv-none-embed版本的工具鏈而言,為了方便用戶直接使用預編譯好的工具鏈,Eclipse開源社區會定期更新發布最新版本的預編譯好的RISC-V嵌入式GCC工具鏈,包括Windows版本和Linux版本。請在谷歌中搜索“releases gnu-mcu-eclipse/riscv-none-gcc”進入網頁下載Windows版本或者Linux版本,如下圖所示。對於Linux和Windows版本均只需在相應的操作系統中解壓即可使用。

三、RISC-V GCC工具鏈的(–march=)和(–mabi=)選項
3.1 (–march=)選項
由於RISC-V的指令集是模塊化的指令集,因此在為目標RISC-V平台進行交叉編譯之時,需要通過選項指定目標RISC-V平台所支持的模塊化指令集組合,該選項為(-march=),有效的選項值如下:
- rv32i[m][a][f[d]][c]
- rv32g[c]
- rv64i[m][a][f[d]][c]
- rv64g[c]
注意:在上述選項中rv32表示目標平台是32位架構,rv64表示目標平台是64位架構,其他i/m/a/f/d/c/g分別代表了RISC-V模塊化指令子集的字母簡稱。請參見RISC-V中文書籍《手把手教你設計CPU——RISC-V處理器篇》中附錄A.1節中關於RISC-V架構指令集的詳細中文介紹。
本節后文會介紹(-march=)選項使用的具體實例。
3.2 (–mabi=)選項
由於RISC-V的指令集是模塊化的指令集,因此在為目標RISC-V平台進行交叉編譯之時,需要通過選項指定嵌入式RISC-V目標平台所支持的ABI函數調用規則(有關ABI函數調用規則的相關信息請參見RISC-V中文書籍《手把手教你設計CPU——RISC-V處理器篇》中附錄A的圖A-1)。RISC-V定義了兩種整數的ABI調用規則和三種浮點ABI調用規則,通過選項(-abi=)指明,有效的選項值如下:
- ilp32
- ilp32f
- ilp32d
- lp64
- lp64f
- lp64d
在上述選項中兩種前綴(ilp32和lp64)表示的含義如下:
- 前綴ilp32表示目標平台是32位架構,在此架構下,C語言的“int”和“long”變量長度為32比特,“long long”變量為64位;
- 前綴lp64表示目標平台是64位架構,C語言的“int”變量長度為32比特,而“long”變量長度為64比特。
- RISC-V的32位和64位架構下更多的數據類型寬度如下圖所示。

上述選項中的三種后綴類型(無后綴、后綴f、后綴d)表示的含義如下:
上述選項中的三種后綴類型(無后綴、后綴f、后綴d)表示的含義如下:
- 無后綴:在此架構下,如果使用了浮點類型的操作,直接使用RISC-V浮點指令進行支持。但是當浮點數作為函數參數進行傳遞之時,無論單精度浮點數還是雙精度浮點數均需要通過存儲器中的堆棧進行傳遞。
- f:表示目標平台支持硬件單精度浮點指令。在此架構下,如果使用了浮點類型的操作,直接使用RISC-V浮點指令進行支持。但是當浮點數作為函數參數進行傳遞之時,單精度浮點數可以直接通過寄存器傳遞,而雙精度浮點數需要通過存儲器中的堆棧進行傳遞。
- d:表示目標平台支持硬件雙精度浮點指令。在此架構下,如果使用了浮點類型的操作,直接使用RISC-V浮點指令進行支持。當浮點數作為函數參數進行傳遞之時,無論單精度還是雙精度浮點數都可以直接通過寄存器傳遞。
本節后文會介紹(-mabi=)選項使用的具體實例。
3.3 (–march=)和(–mabi=)不同選項編譯實例
為了便於讀者更加形象地理解(-march=)和(-mabi=)選項的具體含義,下面以一個實例加以介紹。
假設有一段C語言函數代碼,如下所示:
//這是一個名為dmul的函數,其有兩個參數,為double類型的雙精度浮點數
double dmul(double a, double b) {
return b * a;
}
- 如果使用-march=rv64imafdc -mabi=lp64d的選項組合進行編譯,則會生成如下匯編代碼:
$ riscv64-unknown-elf-gcc test.c -march=rv64imafdc -mabi=lp64d -o- -S -O3
//所生成的匯編代碼如下,從中可以看出,浮點數乘法操作直接使用了RISC-V的fmul.d指令進行支持,且函數的兩個double類型的參數直接使用浮點通用寄存器fa0和fa1進行傳遞。這是因為:
- -march選項指明了目標平台支持的模塊化指令子集為imafdc,其中包含了F和D指令子集,即支持單精度和雙精度浮點指令,因此可以直接使用RISC-V的浮點指令來支持浮點數的操作。
- -mabi選項指明了后綴“d”,表示當浮點數作為函數參數進行傳遞之時,無論單精度還是雙精度浮點數都可以直接通過寄存器傳遞。
dmul:
fmul.d fa0,fa0,fa1
ret
- 如果使用-march=rv32imac -mabi=ilp32的選項組合進行編譯,則會生成如下匯編代碼:
$ riscv64-unknown-elf-gcc test.c -march=rv32imac -mabi=ilp32 -o- -S -O3
//所生成的匯編代碼如下,從中可以看出,浮點數乘法操作由C庫函數(__muldf3)進行支持,這是因為:
- -march選項指明了目標平台支持的模塊化指令子集為I/M/A/C,其中未包含了F和D指令子集,即不支持單精度和雙精度浮點指令,因此無法直接使用RISC-V的浮點指令來支持浮點數的操作。
dmul:
mv a4,a2
mv a5,a3
add sp,sp,-16
mv a2,a0
mv a3,a1
mv a0,a4
mv a1,a5
sw ra,12(sp)
call __muldf3
lw ra,12(sp)
add sp,sp,16
jr ra
- 如果使用-march=rv32imafdc -mabi=ilp32的選項組合進行編譯,則會生成如下匯編代碼:
$ riscv64-unknown-elf-gcc test.c -march=rv32imafdc -mabi=ilp32 -o- -S -O3
//所生成的匯編代碼如下,從中可以看出,浮點數乘法操作直接使用了RISC-V的fmul.d指令進行支持,但是函數的兩個浮點類型的參數均通過堆棧進行的傳遞,這是因為:
- -march選項指明了目標平台支持的模塊化指令子集為I/M/A/F/D/C,其中包含了F和D指令子集,即支持單精度和雙精度浮點指令,因此可以直接使用RISC-V的浮點指令來支持浮點數的操作。
- -mabi選項指明了“無后綴”,表示當浮點數作為函數參數進行傳遞之時,無論單精度還是雙精度浮點數都需要通過堆棧進行傳遞。
dmul:
add sp,sp,-16 //對堆棧指針寄存器(sp)進行調整,分配堆棧空間
sw a0,8(sp) //將函數參數寄存器a0中的值存入堆棧
sw a1,12(sp) //將函數參數寄存器a1中的值存入堆棧
fld fa5,8(sp) //從堆棧中取回雙精度浮點操作數
sw a2,8(sp) //將函數參數寄存器a2中的值存入堆棧
sw a3,12(sp) //將函數參數寄存器a3中的值存入堆棧
fld fa4,8(sp) //從堆棧中取回雙精度浮點操作數
fmul.d fa5,fa5,fa4 //調用RISC-V的浮點指令進行運算
fsd fa5,8(sp) //將計算結果存回堆棧
lw a0,8(sp) //通過堆棧將結果賦值給函數結果返回寄存器a0
lw a1,12(sp) //通過堆棧將結果賦值給函數結果返回寄存器a1
add sp,sp,16 //對堆棧指針寄存器(sp)進行調整,回收堆棧空間
jr ra
- 如果使用-march=rv32imac -mabi=ilp32d的選項組合進行編譯,則會報非法錯誤:
$ riscv64-unknown-elf-gcc test.c -march=rv32imac -mabi=ilp32d -o- -S -O3
cc1: error: requested ABI requires -march to subsume the 'D' extension
//從中可以看出報非法錯誤,這是因為:
- -march選項指明了目標平台支持的模塊化指令子集為I/M/A/C,其中未包含了F和D指令子集,即不支持單精度和雙精度浮點指令,因此無法直接使用RISC-V的浮點指令來支持浮點數的操作。
- -mabi選項指明了后綴“d”,表示目標平台支持硬件浮點指令。這一點與-march選項中指明的指令集子集產生了沖突。
3.4 (–march=)和(–mabi=)選項合法組合
雖然(–march=)和(–mabi=)選項理論上可以組成很多種不同的組合,但是目前並不是所有的(–march=)和(–mabi=)選項組合都是合法,目前的riscv-none-embed工具所支持的組合如下:
- march=rv32i/mabi=ilp32
- march=rv32ic/mabi=ilp32
- march=rv32im/mabi=ilp32
- march=rv32imc/mabi=ilp32
- march=rv32iac/mabi=ilp32
- march=rv32imac/mabi=ilp32
- march=rv32imaf/mabi=ilp32f
- march=rv32imafc/mabi=ilp32f
- march=rv32imafdc/mabi=ilp32f
- march=rv32gc/mabi=ilp32f
- march=rv64imac/mabi=lp64
- march=rv64imafdc/mabi=lp64d
- march=rv64gc/mabi=lp64d
注意:上述有效組合來自於本文撰寫時的信息。隨着時間推移,新發布的riscv-none-embed工具可能會支持更多的組合,請讀者以其最新的發布說明(Release Notes)為准。
四、RISC-V GCC工具鏈的(–mcmodel=)選項
目前RISC-V GCC工具鏈認為,在實際的情形中,一個程序的大小一般不會超過4GB的大小,因此在程序內部的尋址空間不能超過4GB的空間。而在64位的架構中,地址空間的大小遠遠的大於4GB的空間,因此針對RV64架構而言,RISC-V GCC工具鏈定義了(–mcmodel=)選項用於指定尋址范圍的模式,使得編譯器在編譯階段能夠按照相應的策略編譯生成代碼。其有效的選項值如下:
- -mcmodel=medlow
- -mcmodel=medany
注意:
- 在RV32架構中,整個地址空間的大小就是4GB,因此(–mcmodel=)選項的任何值對於編譯的結果都無影響。
- RISC-V GCC工具鏈在未來可能也會支持大於4GB的尋址空間。
medlow和medany兩個選項的含義分別解釋如下。
- 1.(-mcmodel=medlow)選項
(-mcmodel==medlow)選項用於指示該程序的尋址范圍固定只能在-2GB至+2GB的空間內。注意:地址區間沒有負數可言,-2GB是指整個64位地址空間最高2GB地址區間。
由於此模式的尋址空間是固定的-2GB至+2GB的空間內,因此編譯器能夠相對生成比較高效的代碼,但是由於尋址空間固定,對於整個64位的大多數地址空間都無法訪問到,用戶需小心使用。
- 2.(-mcmodel=medany)選項
(-mcmodel==medlow)選項用於指示該程序的尋址范圍可以在任意的一個4G空間內。由於此模式的尋址空間不是固定的,所以相對比較靈活。
五、RISC-V GCC工具鏈的其他選項
本章僅介紹了RISC-V GCC工具鏈幾個特別的選項,有關RISC-V GCC工具鏈的完整選項列表和解釋,感興趣的讀者可以在谷歌輸入“gcc/RISC-V-Options”關鍵字進行搜索進入相關網頁查詢,如下圖所示。

六、RISC-V GCC工具鏈的預定義宏
RISC-V GCC會根據編譯生成若干預定義的宏,在Linux操作環境中可以使用如下方法查看和RISC-V相關的宏:
//首先創建一個空文件
touch empty.h
//使用RISC-V GCC的-E選項對empty.h進行預處理,有關“預處理”的背景知識請參見本號文章《編譯過程簡介》
//通過grep命令對於處理后的文件搜索riscv的關鍵字
//如果使用-march=rv32imac -mabi=ilp32選項可以看出生成如下預定義宏
riscv-none-embed-gcc -march=rv32imac -mabi=ilp32 -E -dM empty.h | grep riscv
#define __riscv 1
#define __riscv_atomic 1
#define __riscv_cmodel_medlow 1
#define __riscv_float_abi_soft 1
#define __riscv_compressed 1
#define __riscv_mul 1
#define __riscv_muldiv 1
#define __riscv_xlen 32
#define __riscv_div 1
//如果使用-march=rv32imafdc -mabi=ilp32f選項可以看出生成如下預定義宏
riscv-none-embed-gcc -march=rv32imafdc -mabi=ilp32f -E -dM empty.h | grep riscv
#define __riscv 1
#define __riscv_atomic 1
#define __riscv_cmodel_medlow 1
#define __riscv_float_abi_single 1
#define __riscv_fdiv 1
#define __riscv_flen 64
#define __riscv_compressed 1
#define __riscv_mul 1
#define __riscv_muldiv 1
#define __riscv_xlen 32
#define __riscv_fsqrt 1
#define __riscv_div 1
