指令集是處理器體系架構的重要組成部分。指令集有兩個發展方面,包含以X86為代表的CISC(復雜指令集)和以ARM、MIPS為代表的RISC(精簡指令集)。CISC的目標是盡可能將經常使用的功能用最少甚至一條指令來實現,因此該指令相應的運行電路往往是復雜的,其側重的是硬件功能的實現;RISC則相反,其是將復雜的運行電路進行分解,即用盡可能簡單的多指令去描寫敘述該功能,以軟件來減少硬件的復雜度,因此RISC對編譯器的要求比較高。CISC的指令長度是可變的,運行周期也不固定,而RISC則是定長的、往往都是單周期運行。寄存器多也是RISC的特點。
本文的重點不在於具體比較RISC和CISC,而是介紹CPU指令的編碼和譯碼。我們都知道CPU的流水線運行過程有取指、譯碼、運行、訪存和回寫(參看博文:CPU指令的流水線運行),但非常多人對於這些步驟的理解都只在概念層面上的,有必要對其進行具體闡述,理解CPU指令的設計和實現。
我們都知道C語言是對現實問題的解決方法和過程的高度抽象,其語法主要包含數值、邏輯運算和分支控制轉移。數值運算就是加減乘除(比較也是減法),邏輯運算就是與、或、非、異或等,分支控制轉移包含if/else、for、while等語法。匯編語言是在機器層面上對C語言的理解和建構,其指令相同也包含數值、邏輯運算、分支控制轉移,可是一行C語言可能須要多條匯編指令才干實現。同一時候因為指令在內存上,而CPU訪問寄存器要比訪問內存要快得多,所以CPU的運算一般都在寄存器中進行,因此匯編語言一般都須要添加內存和寄存器直接的數據載入/存儲指令。CPU僅僅認識二進制輸入,所以能夠把匯編語言當次硬件層面上的偽代碼,其便於開發者熟記,相同是低級語言。
指令的編碼就是實現匯編語言到二進制機器碼的過程,其是匯編器實現的(編譯器是將C語言轉為匯編語言)。 如今如果某種簡單的CPU僅僅支持4種功能:包含
1)加法 ADD Rd,Rs,Rn , 結果是Rd=Rs+Rn
2)減法 SUB Rd,Rs,Rn , 結果是Rd=Rs-Rn
3)數據傳送 MOV Rd,Rs,結果是Rd=Rs
4)數據載入 LDR Rd,[Rs] ,結構是將內存中以Rs寄存器的值為地址取值賦給Rd
怎樣進行編碼呢?
1)首先將指令分為操作碼+操作數 兩個部分,操作碼即代表指令功能,如ADD、SUB等,其在CPU中就代表某種詳細的電路,如ADD就代表加法電路,SUB代表減法電路;操作數即是代表功能的輸入和輸出,相應電路的輸入和輸出。
2)如今共同擁有4種功能,那至少須要2個比特來進行編碼,如00代表ADD,01代表SUB,10代表MOV,11代表LDR;
3)操作數的編碼,如果寄存器共同擁有8個,Rd,Rs,Rn都是當中的一個,即d,s,n的范圍是0到7,那至少須要3個比特來編碼,如000代表R0,001代表R1,以此類推,111代表R7.
那要實現以上四種功能指令,總共須要2+3+3+3=11個比特進行編碼。如SUB R6,R1,R2,即R1減去R2的值賦給R7,那其編碼就是01 110 001 010.
理解完指令的編碼,那指令的譯碼應該是比較好理解的。取指就是依據當前程序計數寄存器PC(如果硬件電路規定R7就是寄存器PC)的值從內存中取出機器碼指令,該機器碼的值是01 110 001 010。接下來的過程就是譯碼,即依據最先的兩個比特01送入譯碼器,選擇為減法電路;110即譯碼選擇為R6,001譯碼選擇R1,010譯碼選擇R2. 再接着的就是指令的運行了,運行就是減法單元電路對兩個輸入(R1,R2)進行運算,將結果賦給R6.
取指和譯碼都是CPU的控制單元(CU)完畢的,運行是ALU(邏輯運算單元)完畢,能夠將ALU看出是非常多種電路的集合。CPU指令的設計和指令編碼息息相關。
ARM和MIPS CPU都是32位字長,因此指令編碼是32比特,可以支持和表達很多其它的功能(操作碼)和寄存器(操作數)。如ARM體系是r0到r15,因此須要4個比特來表示,當然ARM的寄存器還有組的概念,即CPU在不同的工作模式時看到的寄存器可能是不同的,如r13,r14等。這些譯碼時不僅須要指令操作數作為輸入,還須要當前狀態寄存器的值作為輸入。
16位指令集是由於什么產生的呢?是由於相同的一段C語言代碼,用16位指令進行編碼比32位編碼可以節省30%的代碼量,代碼量越少,那占用的內存就越少,自然成本越低。在MCU領域,一般都是成本敏感的,所以16為指令在MCU領域有很廣泛的使用。
ARM和MIPS的指令設計是以32位為基礎進行設計,其運行也是32作為輸入的,那怎樣在32位指令集中實現16位呢?我們都知道二八原理,即20%的指令的使用率會達到80%,所以我們對這20%的指令(其是32位指令的一個子集)進行編碼,自然能夠用較少的比特數來進行編碼,而在16位指令使用時,我們能夠強制要求其僅僅使用一部分寄存器,那自然能夠以更少的比特數來進行寄存器的編碼。能夠將16位指令集看成是32位指令集的一個子集,CPU在譯碼階段先將16為指令轉化為相應的32位指令,再進行譯碼、運行。
ARM的16位指令集為thumb指令集,MIPS的16為指令集為MIPS16指令集。至於32位指令集和16位指令集之間的無縫切換,請參看還有一篇博文:32位和16位指令集模式自己主動切換機制。