2、指令系統體系結構
2.1 設計自己的計算機
運算類指令
ADD R,M
:将寄存器R中的數和一個存儲器M中的數相加,然後存到這個寄存器R中
傳送類指令
LOAD R,M
:把存儲器M中的内容,加載到寄存器R中
STORE M,R
:把寄存器R中的數存入到存儲器M中
轉移類指令
JMP L
:無條件轉向L處
CPU是從記憶體中,按照位址依次(本質是計數器累計取值)取出指令開始執行的,如果想改變取指令的位置,就需要用到
JMP L
(即跳轉jump),當CPU執行該指令後,就會轉移到L所指向的存儲器單元中去取下一條指令來執行
注:M和L為存儲器位址,R為通用寄存器編号
指令格式
每條指令等長,均為兩個位元組
第一個位元組
第一個位元組的高四位是操作碼,如:(目前隻提供四條指令,最多可擴充至16條)
LOAD:0000
、
ADD:0001
、
STORE:0010
、
JMP:0011
第一個位元組的第四位是寄存器号,如:(目前隻提供4個寄存器,最多可擴充到16個)
R0:0000
、
R1:0001
、
R2:0010
、
R3:0011
第二個位元組
第二個位元組是存儲單元的位址,有八個二進制位,最大可以使用256個位元組的存儲器
指令示例
0001 0010 | 0000 1001
:
ADD R2,[9]
運算任務對應的程式
在存儲器中的機器語言
在下圖中,分别依次執行:
R3<-M5、R3<-R3+M6、M7<-R3、M18<-jump
即實作了M7=M5+M6的功能,并跳轉到M18的指令
模拟機的運作
一開始
PC
上記錄着即将執行的下一條指令的位址,開始執行時,
PC
上的位址會通過
MAR
傳送到位址總線上,并且由控制總線給出讀的控制資訊;存儲器傳回指令資訊到
MDR
上,再通過CPU内部總線傳送到
IR
上,經過指令譯碼,将之翻譯成不同的指令執行
這裡有一個問題是,為什麼
PC
寄存器中一開始的位址是這個呢?這也是一直困擾我的一個問題,隻有拿到備忘錄
PC
中第一條指令位址資訊,接下來才能夠通過計數的方式繼續讀取新的指令,而存儲器中的
JMP
指令提供了指令的位址跳轉方式(即使指令不一定要連續存放)
實際上
PC
寄存器的初值,是進行指令系統體系結構設計時,必須要約定的一個内容,CPU在啟動或複位完成後,第一條指令需要從哪裡取出,這也是軟硬體雙方一開始必須要協商好的事情,至于這個位址到底應該是什麼,并沒有明确的規則,但通常情況下,我們會約定為這個體系結構所能通路存儲單元的最小位址,也就是0,或者是接近最高位址的地方
2.2 x86體系結構
尋址能力和CPU位寬
關于記憶體尋址能力跟CPU位寬的關系,實際上是沒有關系
CPU的尋址能力與它的位址總線位寬有關,而我們通常說的CPU位寬指的是資料總線位寬,它和位址總線位寬半毛錢關系也沒有,自然也與尋址能力無關
注意到,每一個位址對應的資料都是8位,即一個Byte位元組,位址總線位寬實際上指的是位址總線的位數,比如說位址總線是32位,那麼它的位寬為32位,相當于有 2 32 2^{32} 232個位址,但是,尋址能力指的是這 2 32 2^{32} 232個位址的資料量大小,由于一個位址對應一個Byte,是以32位位址總線對應 2 32 2^{32} 232Byte即4GB的尋址能力
而CPU位寬,也就是我們通常所說的32位作業系統、64位作業系統中的那個32位和64位,它指的是一個時鐘周期内CPU能處理的二進制位數,可以被了解為通用寄存器的位數,如8086 CPU是16位的,可以一次處理2個位元組(16個bit),80386 CPU是32位,能一次處理4個位元組,目前的CPU基本上64位的了,一次能處理8個位元組
我們的Windows作業系統也分為32位和64位,主要是針對上面CPU的位寬做了些優化,比如32位的CPU就不能用64位的Windows(因為CPU一次隻能處理32bit,而作業系統給你的指令是要處理64bit),但64位的CPU就可以運作32位的Windows,也能運作64位Windows
在8086這個16位CPU上,它的位址總線位寬是20位,正好能尋址1MB,80286它的PAE是24位,在Pentium II(32位CPU)時這個PAE變成了36位,可以支援64GB的尋址;64位CPU出現之後,其位址總線位寬一般采用的是36位或者40位,它們尋址的實體位址空間為64GB或者1T
那位址總線和資料總線有什麼關系?可以這麼了解,位址總線用來定位,資料總線用來傳輸,也就是當CPU需要從記憶體讀取資料或向記憶體寫入資料時,它使用位址總線來指定其需要通路的存儲器塊的實體位址,然後通過資料總線發送資料
是以說,CPU的位寬和尋址能力是沒有關系的,16位CPU的位址總線位寬可以是20位,32位CPU的位址總線可以是36位,64位CPU的位址總線位寬可以是40位。是以你下次一定不要說32位的CPU隻能尋址2^32(4GB)了,大錯特錯
那作業系統的位寬和尋址能力有什麼關系嗎,這個其實還是有的。我們在使用計算機時,操縱的其實是邏輯位址,32位作業系統的邏輯位址尋址範圍隻有2^32=4GB
是以,不管你用什麼樣的CPU,它最多也隻支援4GB的記憶體容量,但這是作業系統的鍋,并不是說32位CPU隻能尋址4GB空間
CPU演進
Intel 8086
8086是一款16位的CPU,内部的通用寄存器為16位,可以處理16位和8位的資料
對外有16根資料線和20根位址線,可尋址的記憶體空間為1MB( 2 20 2^{20} 220Byte)
CPU發送到存儲器的位址為實體位址,實體位址的形成采用了“段加偏移”的方式
Intel 80286
采用實模式,即實位址模式,BIOS晶片的指令就是運作在實模式下的
Intel 80386——IA-32體系結構
是80x86系列中第一款32位微處理器
支援32位算術邏輯運算,提供32位通用寄存器
同時位址總線也擴充到了32位,可尋址4GB的記憶體空間
改進了“保護模式”,增加了“虛拟8086模式”以相容8086模式
主要工作模式為保護模式(Protected Mode即pmode):支援多任務、設定特權級、特權指令的執行、通路權限檢查、可以通路4GB實體存儲空間、引入了虛拟存儲器概念
x86-64位處理器
AMD Opteron
x86擴充到64位的第一款微處理器
可以通路高于4GB的存儲器
最重要的是它可以相容32位x86程式,且不降低性能
x86-64的運作模式
x86-64支援原有的32位x86的運作模式,并将之命名為傳統模式(Legacy mode)
新增的運作模式被稱為長模式(Long mode),又分為兩個子模式,64位模式(64-bit mode)和相容模式(Compatibility mode),在相容模式下,原有的x86程式不需要重新編譯就可以高效運作,這也是x86-64得以成功的關鍵
8086的寄存器模型
8086是16位系統,是以指令指針寄存器IP(Instruction Pointer)的尋址能力為 2 16 = 64 K B 2^{16}=64KB 216=64KB,而8086對外有20位位址線,尋址範圍是 2 20 = 1 M B 2^{20}=1MB 220=1MB,
我們在編寫程式時給出的位址是偏移位址,即偏移量,是16位的;而段寄存器中存放了段基值,也是16位的;這一對位址就被成為邏輯位址
其中段基值在運算時首先被左移四位,然後和偏移量相加,這樣就得到了一個20位的實體位址,這也就是從邏輯位址生成實體位址的過程:實體位址=段基值*16+偏移量
8086段加偏移的程式設計執行個體
MOV AX, [3000H]
在段加偏移的模式下,操作數是預設存放在DS即資料段寄存器所指向的段中,假設事先已經在DS段寄存器中存入了2000H,那麼實際實體位址是2000H*16+3000H=23000H
存儲器中,OP是操作碼,而30H和00H是操作數(一個位址存一個Byte,是以3000H這個16位數需要2個Byte即兩個位址來存放),CPU會取出DS寄存器中的内容,并将其左移四位後與偏移量相加,進而得到23000H這個實體位址,然後用這個位址去通路存儲器,因為我們的目标寄存器AX是16位的,是以從存儲器中取出兩個位元組,并依照高位址放在高位元組,低位址放在低位元組的原則存放在AX當中
2.5 MIPS體系結構
MIPS是精簡指令系統的代表,采用了與X86相反的設計理念,并引領了精簡指令系統的潮流
RISC(Reduced Instruction Set Computer):精簡指令系統計算機,如mips
CISC(Complex Instruction Set Computer):複雜指令系統計算機,如x86
MIPS(Microprocessor without Interlocked Piped Stages):流水線不會互鎖的微處理器
流水線是現代微處理器為提高性能而采用的一項技術,而流水線中的互鎖則是導緻其性能降低的一個非常重要的因素
是以MIPS主要的關注點是:減少指令的類型,降低指令的複雜度;其基本原則是
A simpler CPU is a faster CPU
MIPS指令的主要特點
- 固定指令長度(32-bit,即1 word),注:x86中一個word是16-bit,MIPS中一個word是32-bit
- 指令長度固定簡化了從存儲器取指令的工作,不用像x86CPU那樣需要判斷每條指令的長度
- 簡單的尋址模式,簡化了CPU通路存儲器讀取操作數的控制邏輯
- 指令數量少,指令功能單一(一條指令隻完成一個操作,而x86是一條指令完成許多操作),可以簡化指令的執行過程
- 隻有Load和Store指令可以通路存儲器,不支援x86指令中讓算術指令通路存儲器的操作,如
ADD AX,[3000H]
但是,這些特點使得MIPS指令進行直接程式設計變得非常困難,是以想要有高效率的MIPS程式,必須要有優秀的編譯器的支援
MIPS指令示例(運算指令)
以加法指令為例:
add a,b,c
->
a = b + c
,同理還有算術運算、邏輯運算、移位等指令
可以發現,MIPS指令的操作都非常簡潔統一,且這些指令的操作數都不可以是存儲器操作數,即不能直接對存儲器裡的内容進行操作
要通路存儲器,就必須要使用專門的訪存指令,假設A是一個100字(word)的數組,首位址在寄存器$19中,變量h對應寄存器$18,臨時資料存放在寄存器$8,我們要實作的是
A[10]=h+A[3]
注意:MIPS一個word是32bit即4Byte,是以每兩個位址之間相差4Byte
-
首先要擷取A[3],用load指令将19号寄存器對應的位址加上偏移量3*4=12Byte(因為MIPS一個字是32bit,是以第三個元素與首位址之前的偏移是12Byte)lw $8,12($19) # t0=A[3]
-
将8号寄存器和18号寄存器相加,并将結果放在8号寄存器add $8,$18,$8 # t0=h+A[3]
-
将運算結果存放到以19号寄存器為首位址,偏移量為10*4=40Byte的記憶體單元,即A[10]sw $8,40($19) # A[10]=h+A[3]
MIPS的通用寄存器
MIPS有編号從0-31共32個通用寄存器,每個通用寄存器都是32位寬(4Byte)
在編寫彙程式設計式時,我們可以用數字,也可以用名稱來表示這些寄存器,下面幾條指令與注釋内容等價:
lw $t0,12($s3) # lw $8,12($19)
、
add $t0,$s2,$t0 # add $8,$18,$8
、
sw $t0,40($s3) # sw $8,40($19)
值得注意的是,
$t0-$t7
、
$s0-$s7
這些寄存器都是我們常用的,s一般用于存放變量,t一般用于存放臨時結果
2.6 MIPS指令簡介
MIPS指令的基本格式
分為R型(Register,寄存器型)、I型(Immediate,立即數型)、J型(Jump,無條件轉移型)
R型指令的格式
R型指令格式包含6個域:2個6-bit域,可表示0~63的數;4個5-bit域,可表示0~31的數
opcode域和funct域:
用于指定指令的類型,對于所有R型指令,改域的值均為0
但這并不說明R型指令隻有一種,還需要用funct域來更為精确地指定指令的類型
是以對R型指令,實際上一共有12個bit的操作碼,那為什麼不将opcode域和funct域合并成一個12-bit的域呢?這樣不是更直覺明了嗎?
rs域:
Source Register,通常用于指定第一個源操作數所在的寄存器編号
rt域:
Target Register,通常用于指定第二個源操作數所在的寄存器編号
rd域:
Destination Register,通常用于指定目的操作數所在的寄存器編号,也就是儲存運算結果的地方
5個bit的域可以表示0-31的數,正好對應MIPS體系結構中的32個通用寄存器
shamt域:
shift amount,用于指定移位指令進行移位操作的位數,5bit域可以表示0-31個移位位數;對于32bit的數,更多的移位沒有實際意義;對于非移位指令,該域設為0
R型指令的編碼示例
add $8,$9,$10
查指令表可知:
opcode=0,funct=32,shamt=0(非移位指令)
根據指令操作數可知:
rd=8(目的操作數),rs=9(第一個源操作數),rt=10(第二個源操作數)
然後把各個域的數值轉化成二進制數,填寫到對應位置,即可将該彙編指令轉化成二進制機器碼:
000000(opcode)01001(rs)01010(rt)01000(rd)00000(shamt)100000(funct)
I型指令的格式
R型指令隻有一個5bit域也就是移位域來表示立即數,範圍為0-31;而常用的立即數遠大于這個範圍,是以需要新的指令格式,即I型指令
opcode域
用于指定指令類型(沒有funct域),是以不同的I型指令,opcode域是不同的
rs域
指定第一個源操作數所在的寄存器編号
rt域
指定用于目的操作數(儲存運算結果)的寄存器編号,對于某些指令,指定第二個源操作數所在的寄存器編号
immediate域
I型指令與R型指令不同,他隻有兩個寄存器數域,剩下的16位被整合成了一個完整的域immediate,可以存放16位立即數,表示 2 16 2^{16} 216個不同的數值
對于一般的訪存指令,我們需要用一個寄存器加上一個立即數來訓示一個記憶體單元,如
lw rt,imm(rs)
;這個立即數就是訪存位址的偏移量,16位立即數可以通路正負32K的空間,通常可以滿足通路位址偏移量的需求
對于運算指令,如
addi rt,rs,imm
;雖然無法滿足全部需求,但可以滿足大多數情況下的需求,這一點上就展現出x86的CISC指令系統的優勢,對x86指令來說,如果想用更大寬度的立即數,它可以很容易擴充,因為它的指令本來就沒有限制長度,但是MIPS指令總長度就是32位,再加上各個寄存器位域的使用,是以I型指令最多隻能使用16位立即數
I型指令的編碼示例
addi $21,$22,-50 # $21=$22+(-50)
addi可以讓一個源操作數為立即數(這裡的立即數指存儲器位址),而add指令的操作數必須都是寄存器
查指令編碼表可知:
opcode=8
分析指令可知:
rs=22(源操作數寄存器編号)、rt=21(目的操作數寄存器編号)、immediate=-50(立即數)
将這些數轉化為二進制,可得二進制機器碼:
001000(opcode)10110(rs)10101(rt)1111111111001110(immediate)
分支指令的分類
Branch,分支指令是用于改變控制流的指令,其實就相當于x86中的轉移指令
在MIPS中,分支指令也分為條件分支和非條件分支
-
Conditional Branch
條件分支:根據比較的結果改變控制流
兩條指令:
、branch if equal(beq);
branch if not equal(bne)
-
Unconditional Branch
非條件分支:無條件地改變控制流
一條指令:
jump(j)
條件分支指令(I型指令)
條件分支
beq rs,rt,imm # opcode=4
bne rs,rt,imm # opcode=5
格式:
beq reg1,reg2,L1
if (value in reg1)==(value in reg2)
goto L1
以
beq
指令為例,共有三個操作數,前兩個是寄存器操作數,第三個是存儲器位址,即立即數,CPU會判斷第一個寄存器第一個寄存器中的數和第二個寄存器中的數是否相等,如果相等就跳轉到L1所指向的寄存器單元取出下一條指令,否則,順序執行
beq
之後的那條指令
注:這裡的條件分支與x86轉移指令有很大不同,MIPS沒有标志位,而是在一條指令中既進行了比較又完成了轉移,MIPS的全稱,就是為了減少指令流水線的互鎖,也就是要盡量避免不同指令之間的互相影響,而标志位這件事很明顯就是前一條指令運作的結果,可能會對後面的某條指令産生影響,這是MIPS指令設計時要盡量避免的
舉例:
//c語言代碼
if(i == j)
f = g + h;
else
f = g - h;
s3和s4中儲存了i和j這兩個變量,如果它們内容相同,會轉移到True:後的語句,執行加分指令,也就對應于f=g+h;如果它們不等,則會順序地執行下一條指令,也就是減法指令,對應于f=g-h,執行完之後會跳過這條加法指令,然後進入後面的代碼Fin
#MIPS彙編語言代碼
beq $s3,$s4,True # branch i==j
sub $s0,$s1,$s2 # f=g-h(false)
j Fin # goto Fin
True: add $s0,$s1,$s2 # f=g+h(true)
Fin:...
條件分支指令的目标位址範圍
從條件分支指令的格式可以看出,目标位址隻能使用16bit的位移量,如何充分發揮16bit的作用?
以目前PC(在MIPS中,指向下一條指令位址的寄存器叫PC,類似于x86中的IP寄存器)為基準,16bit位移量可以表示 ± 2 15 ±2^{15} ±215bytes
MIPS的指令長度固定為32-bit(word),每條指令的位置一定會在四個位元組對齊的地方,位址的最低兩位肯定為0,是以16-bit位移量可以表示 ± 2 17 ±2^{17} ±217Bytes= ± 2 15 ±2^{15} ±215words (目标位址的範圍擴大四倍,可以達到±128KB)
此時目标位址的計算方法:(這裡的數字均指代Byte)
- 分支條件不成立,
PC = PC + 4 = next instruction
- 分支條件成立,
PC = (PC + 4) + (immediate * 4)
這裡的(+4)和(*4)是怎麼來的呢?這就涉及到了記憶體模型,我們知道,一個位址始終對應着8bit即一個Byte,而如果這是一條指令的話(MIPS的一條指令是32bit即4Byte),那麼相鄰的四個位址(四個Byte)必然是屬于這個指令的,由于我們每次拿到的PC都是這個指令的首位址,也就是4個Byte中的第一個Byte的位址,是以在他後面的第一條指令最起碼也應該是4個位址之後了,是以下一條指令的首位址:
next instruction = PC = PC + 4
而我們也得知了,每四個位址可以被看作是一個指令組,是以為了盡可能地利用immediate的16個bit去尋找更多的記憶體位址,我們使每一個bit都指代一個指令組即4Bytes,這就是這裡的immediate*4的由來
非條件分支指令(J型)
條件分支語句,有兩個寄存器用于比較條件,如果我們不需要判斷條件,就可以想辦法擴大目标位址的範圍,當然理想條件下是直接使用32位位址,但還是因為MIPS指令長度固定為32位,而每條指令至少需要有一個opcode域訓示它的指令類型(占用6個bit),那我們把剩下的26個bit全部用于目标位址,這就是J型指令
考慮到之前提到的一個指令組=4Bytes的情況,J型指令的目标位址計算方法為:目前pc加四之後,取最高的四位(考慮溢出),再加上J型指令編碼中的26位,然後在末尾添上兩個0(這裡把第一條指令的位址預設為
...0000
,那麼四個位元組過後的第二個位元組的位址為
...0100
,以此類推,每一個指令首位址的最後兩位必然是0)
New PC = {(PC + 4)[31..28], address, 00}
此時雖然目标位址的範圍還不能達到4G空間,但比之前的條件分支指令已經擴大了很多
J型指令的目标位址範圍: ± 2 28 B y t e s ( ± 256 M B ) ±2^{28}Bytes(±256MB) ±228Bytes(±256MB)
如何到達更遠的目标位址?可以2次調用j指令以擴大範圍,但是有些不友善
可以使用
jr
指令:
jr rs
,将要轉移的目标位址放到寄存器(32bit)中,這樣就可以使用32位的目标位址了,但是這樣的指令無法用J型指令來實作,但是可以用R指令來實作,隻用占用其中一個寄存器位域,然後新增一種funct的編碼就可以了
兩種分支指令示例
假設變量和寄存器對應關系如下:
f->$s0
、
g->$s1
、
h->$s2
、
i->$s3
、
j->$s4
C語言代碼如下:
if (i == j)
f = g + h;
else
f = g - h;
asm彙編代碼如下:
bne $s3,$s4,Else
add $s0,$s1,$s2
j Exit
Else: sub $s0,$s1,$s2
Exit:...