本期內容如下:
RV32I 指令應用注意事項指令立即數取值範圍RISC-V彙編偽指令圖1
一、RV32I 指令應用注意事項
RISC-V為了追求硬體設計上的簡化,很多功能並沒有單獨實現,而是透過編譯器來自動完成對未實現指令的自動轉換,編譯器會利用已有基本指令來完成對未有指令功能的擴充,由於RISC-V指令設計的巧妙,這種擴充,並不會損失太多CPU效能。
如:
X0暫存器,也被稱為zero,是一個只讀暫存器,返回值永遠為0,寫入的任何資料都將被丟棄。基本指令藉助於zero可以擴展出許多新的指令。
addi x0 , x0, 0 就等價與其他處理器指令的nop操作;
sub a0,x0,a1 就等價於取負數指令neg;
以上設計思路可以大大簡化硬體設計。
1.1 善用偽指令
在用機器指令進行編寫程式碼時,初學者總覺的,RV32I提供的指令集不完善,有些功能都沒有實現,如nop,neg,mv,not等,為此直接用有限的機器指令編寫程式碼,總覺的蹩腳,不夠直接。
如,我們使用的nop功能時,其實編寫的機器指令是addi x0 , x0, 0,像這樣編寫程式碼實在痛苦,整個程式碼也比較難讀。為此,gnu彙編器提供的偽指令就派上用途了,實際編寫“空”操作程式碼時,直接使用偽指令nop,在編譯時候,彙編器會自動幫助將nop轉換為addi x0,x0,0,這樣就方便多了。
1.2 立即數的取值範圍
RV32I中存在大量的立即數運算元指令,由於不同指令格式的立即數佔用bit不同,其所能取的數值也是有限的,如果立即數超出實際指令範圍,編譯器將會報錯。
如我們希望載入一個32bit常量資料0x12345678到t0暫存器中,不採用偽指令li t0,0x12345678的情況下,我們是找不到任何一條機器指令可以直接一步完成32bit立即數載入的,為了實現這一功能,我們可能想到的方法為如下程式碼:
addi t0,zero,0x123 //載入0x12345678的高12bit到t0slli t0,t0,12 //t0<<12bit =>t0=0x123000addi t0,t0,0x456 //t0=t0+0x456=0x123456slli t0,t0,8 //t0<<8bit =>t0=0x12345600addi t0,t0,0x78 //t0=t0+0x78=0x12345678
當然為了實現上述功能,也可採用他方法,之所以舉這個例子是為了讓大家知道,編寫彙編程式碼時,機器指令的立即數是有位寬限制的,addi t0,zero,0x123345678 顯然是一個錯誤指令。
二、指令立即數取值範圍
不同立即數操作指令,立即數取值範圍參見圖2。
圖2 立即數取值範圍
三、RISC-V彙編偽指令
偽指令是彙編器提供的類似於機器指令的操作指令,偽指令在實際的處理器指令集中並沒有被實現,編譯器在編譯彙編程式碼時,可以透過一條 或多條機器指令去實現偽指令對應的功能。這樣做的目的,就是可以最大限度簡化處理器指令集,降低處理器開發難度,使用偽指令可以更加快速方便的編寫彙編程式碼。
RISC-V在原有機器指令的基礎上,擴充套件了幾十個偽指令,透過這些偽指令,我們可以非常方便的編寫彙編程式碼,具體偽指令參見圖3和圖4。
為了理解和消化這些偽指令,讀者需要耐心的練習相關操作。
圖3 RISC-I 彙編偽指令1
圖4 RISC-V彙編偽指令2
這裡需要強調的幾條指令,中
3.1 載入立即數
偽指令
li rd, imm(32-bit)
等效機器指令
lui rd, imm(20-bit)
addi rd, rd, imm(12-bit)
3.2 載入地址
偽指令
la rd, label
等效機器指令
auipc rd, imm(20-bit)
addi rd, rd, imm(12-bit)
3.3 呼叫任意32-bit 絕對地址
lui x1, <high 20-bit>
jalr ra, x1, <low 12-bit> (pc)=32bit 地址,即,呼叫32bit絕對地址
3.4 跳轉相對PC的32bit偏移地址
auipc x1, <high 20-bit>
jalr x0, x1, <low 12-bit>(pc)=(pc)+32bit地址,即,跳轉到當前指令前後32bit偏移地址處
RISC-V彙編指令學習貴在練習,堅持一段時間後,這將更好地幫助我們理解RISC-V處理器架構。