計算機組成
3 指令系統體系結構
3.6 MIPS指令簡介

MIPS秉承着指令數量少,指令功能簡單的設計理念。那這樣的設計理念是如何實現的呢?在這一節,我們就將來分析MIPS指令的特點。

相比於X86指令所提供的動輒上千頁的指令說明,MIPS指令只用這兩頁紙就可以說清楚了。

MIPS指令的基本格式就分為這三種:R型,I型和J型。
R型指的是寄存器型;
I型指的是立即數型;
J型指的是轉移型。

我們用這張表對MIPS的指令進行不同緯度的分類,橫軸是按照指令的格式分為R型、I型和J型,縱軸則是根據指令的功能類型分為運算指令、訪存指令和分支指令。
首先,我們來看指令格式為R型的運算指令。

R型指令總共包含六個域。其中最高位的opcode域是六個比特,最低位的funct域也是六個比特,中間的四個域均為五個比特。我們分別來看各個域的用途:
opcode域,用於指定指令的類型。對於所有的R型指令,這個域的值均為零。但這並不是說明R型指令只有一種,它還需要用funct域來更為精確的指定指令的類型。所以說,對於R型指令,實際上一共有12個比特操作碼。那大家可以思考一下,為什么不將opcode域和funct域合並成一個12比特的域呢?那樣豈不是更直觀明了嗎?

我們再來看這些5比特的域。rs 域,這個域通常用來指定第一個源操作數所在的寄存器編號;rt 域,通常用來指定第二個源操作數所在的寄存器的編號;rd 域,通常用來指定目的操作數的寄存器編號,也就是保存運算結果的地方。
5個比特的域可以表示0-31的數,正好對應MIPS的體系結構中的32個通用寄存器。

還剩下最后一個域 shamt,它指示的是移位操作的位數。因為對於32比特的數,5比特的域正好可以表示0-31的移位位數。那這個域只是對於移位指令有用,對於非移位指令,這個域被設為0。

我們來看一個例子,這是將9號寄存器和10號寄存器中的數相加,把運算結果保存在8號寄存器中。那我們通過這條匯編指令的描述,如何得到MIPS指令的二進制編碼呢?這其實很容易。首先,我們查詢MIPS指令編碼表,就可以得到加法指令的opcode域應該是0(十進制),funct域應該是32。因為它不是移位指令,所以移位的域shamt被設為0。然后我們根據這條指令的操作數,可以得到目的操作數也就說rd這個域等於8,第一個源操作數應該是9,第二個源操作數應該是10。這樣我們把各個域的數值轉換成二進制數,填寫到對應的位置,就可以得到這條指令的二進制編碼了。
MIPS指令系統簡潔明了的規則可以讓我們非常容易的對指令進行這樣的手工編碼轉換,同樣也說明了CPU對這樣的指令進行硬件的譯碼也會非常的方便。

如果指令中需要用到立即數,那么就要用到I型指令。

因為R型指令當中只有一個5比特的域,也就是shamt移位這個域可以用來表示立即數,那能表示的數的范圍為0-31。在程序中常用的立即數遠大於這個范圍,所以R型指令並不適用,我們需要新的指令格式。這就是I型指令,I型指令的大部分域與R型指令是相同的。

I型指令的第一個域,也是opcode域,用於指定指令的類型。但它沒有funct域,所以不同的I型指令,其opcode域是不一樣的。
第二個域rs,指定了第一個源操作數所在的寄存器編號;
第三個域rt,用於指定目的操作數。
I型指令與R型指令不同,它只有兩個寄存器數域。

剩下的16位被整合成了一個完整的域,可以存放16位的立即數,可以表示2的十六次方個不同的數值。對一般的訪存指令,我們需要用一個寄存器加上一個立即數來指示一個內存單元。那么這個立即數就是訪存地址的偏移量,16位的立即數,可以訪問正負32K(\(2^{16-1}=32K\),1位符號位)的空間,對於一般的訪存指令來說,就可以滿足了。而對於運算指令,雖然無法滿足全部的需求,但是大多數情況下,16位也可以使用了。在這一點上,就可以體現出X86這樣的CISC指令系統的優勢,對於X86指令來說,如果它想使用更大寬度的立即數,它可以很容易的擴展,因為它的指令本來就沒有限制長度。但是,MIPS指令就不行,它的指令總長度就是32位的,再加上各個寄存器位域的使用,所以I型指令最多只能使用十六位的立即數。

我們來看一個例子。對於加法,如果我們想讓其中的源操作數是一個立即數的話,就可以用 addi 這個指令, 注意它和 add 指令是不一樣的。add 指令的操作數必須都是寄存器。我們再來練習一下手工轉換指令的編碼。我們通過查指令編碼表,可以發現 addi 指令的opcode域是8,從這一點我們也可以看出 addi 和 add 雖然只有一個字母的差別,但是他們指令格式是完全不一樣的。剩下的域我們通過分析這條指令的操作數就可以得到,rs域等於22,rt域等於21,立即數域等於 -50。我們將這些數轉換成二進制,就可以得到這條指令的編碼了。

然后我們來看所有的分支指令。

分支指令是用於改變控制流的指令,其實就相當於X86當中的轉移指令。在MIPS中,分支指令也分為條件分支和非條件分支兩種。對於條件分支有兩條指令,beq和bne;對於非條件分支,只有一條指令,j。

我們先來看條件分支指令,條件分支指令實際上是i型指令。這就是兩條條件分支指令,他們的opcode域分別是4和5。我們以beq指令為例,它共有三個操作數,前兩個是寄存器操作數,第三個操作數是存儲器地址,也就說一個立即數。CPU會判斷第一個寄存器當中的數和第二個寄存器當中的數是否相等。如果相等就跳轉到L1所指向的寄存器單元取出下一條指令,否則,順序執行beq之后的那條指令。我們需要注意,這里和X86的條件轉移指令有很大的不同,MIPS沒有標志寄存器,它就在一條指令當中既進行了比較,又完成了轉移。
我們還記得MIPS的全稱,就是為了減少指令流水線的互鎖。也就說要盡量避免不同指令之間相互的影響。而標志位這件事,很明顯就是前一條指令運行的結果可能會對后面的某一條指令產生影響,這是MIPS指令設計時要盡量避免的。所以beq指令也很好的體現了MIPS的這一設計理念。

我們來看一個例子。這段C語言代碼是我們經常會寫的。如果把它轉換為MIPS指令,是這樣的,第一條beq指令,如果S3寄存器和S4寄存器內容相同,則轉移到True所對應的這行指令。那么S3和S4中保存了I和J這兩個變量,如果他們內容相同,會轉移到True這里,執行加法指令,也就對應於F=G+H;如果他們不等,則會順序的執行下一條指令,也就一條減法指令sub對應於F=G-H。執行完之后,會跳過 add $s0, $s1, $s2 這條加法指令,然后進入后面的代碼。

從條件分支指令的格式可以看出,目標地址只能使用16位的位移量,這是一個很大的局限,但是我們還得考慮如何充分發揮這16位的作用。如果以當前的PC寄存器為基准,在MIPS中,指向下一條指令地址的寄存器稱為PC,類似於X86中的IP寄存器。PC這個寄存器,是指向32位地址的,如果以它為基准,16位位移量可以表示出當前指令前后2的15次方字節這么一個范圍。但是我們要注意一點,MIPS的指令長度固定位32個比特,因此每條指令的位置,一定會在四個字節對齊的地方,這樣地址最低兩位肯定為0。所以我們實際上可以用十六位的位移量去指示每四個字節為一個單位的地址,這樣就可以把目標地址的范圍擴大四倍,可以達到前后128KB。
在這樣的條件下,目標地址應該這么計算:
當分支條件不成立時,下一條指令的地址就等於當前的pc+4;
如果分支條件成立,那下一條指令的地址就等於已經加了4的pc, 再加上這個立即數乘以4。

然后我們來看非條件分支指令。相比於條件分支指令,有兩個寄存器域用於比較條件,那如果我們不需要判斷條件,我們就可以想辦法擴大目標地址的范圍。當然理想情況下是直接使用32位的地址,但還是因為MIPS的指令長度固定為32位。而每條指令至少需要有opcode域,指示它指令類型,這就占用了6個bit。那我們把剩下的26個bit全都用於目標地址,這就是J型指令。在考慮到MIPS指令是四字節對齊的這個情況,對於J型指令,下一條指令的地址的計算方法可以是將當前的pc加4之后,取最高的四位,再加上J型指令編碼中的26位,然后在末尾填上兩個0。
雖然目標地址的范圍還不能達到整個4G的空間,但比之前的條件分支指令已經擴大了很多。

我們用一個例子來進行進一步的說明。假設我們在高級語言中用的若干變量與寄存器的對應關系是這樣的。那我們就可以用這樣一種方式來實現這段C語言的代碼,第一條指令 bne 是判斷i和j是否相等,如果不相等,則轉移到 Else 這個標號所對應的位置,也就是執行一條減法指令對應於 f = g - h;如果判斷條件不成立,也就是 i = j 的時候,順序地執行下一條加法指令,也就對應於 f = g + h。然后用無條件分支指令跳到 Else 條件之后繼續執行后面的程序。

我們現在已經知道這個J型指令的目標地址可以是當前指令前后256MB(\(2^{26+2}\)字節)的范圍,那如果我們還想跳轉到更遠的地址,應該怎么辦呢?有一個很簡單的方法就是兩次調用J指令。第一條J指令盡可能跳到最遠的地方,然后在那個目標地址再放一條J指令,像接力一樣再跳一次。這個方法很簡單,但是用起來不算太方便。那么還可以用什么方法呢?大家還記得我們曾說過間接轉移指令嗎?MIPS中也可以用同樣的方法,這就是jr指令。jr指令有一個寄存器操作數,可以把要轉移的目標地址放到寄存器當中,這樣就可以使用32位的目標地址了,但是這樣的指令顯然無法用J型指令來實現。那么需要新增一種指令類型嗎?其實也不需要,我們就用原來的R型指令就可以很好的實現。只用占用其中的一個寄存器位域,然后新增一種funct的編碼就可以了。

這就是MIPS指令系統的核心內容,我們只用熟悉這兩頁的內容就可以輕松的掌握MIPS的指令了。

我們已經介紹完了MIPS指令系統體系結構。它不愧為精簡指令系統(RISC)的經典設計,指令簡潔而且精巧。
