如果將自己限制為僅16條指令,那么應該選擇哪一條,以及如何在不掉隊的情況下進行管理?
在我從頭開始構建4位HRRG(Heath Robinson,Rube Goldberg)計算機的項目的一篇專欄中,我們介紹了CPU寄存器和指令集。您可能還記得,由于我們只有4位數據總線(以及12位地址總線),因此我們選擇了只有2 ^ 4 = 16條指令以及2 ^ 4 = 16個CPU寄存器。
HRRG的CPU寄存器和指令。(來源:馬克斯·麥克菲爾德(Max Maxfield)
為了確保我們都敲打同一鼓音,讓我們提醒自己,六個通用寄存器R0至R5用于存儲數據值并“累加”任何算術或邏輯運算的結果。狀態寄存器S0和S1主要用于存儲任何算術或邏輯運算的狀態結果,例如,相減的結果是否為零。
程序計數器(PC)用于跟蹤CPU在程序中的當前位置。堆棧指針(SP)用于跟蹤堆棧的頂部。索引寄存器(IX)主要用于保存計數值或用于訪問內存的偏移量。中斷向量(IV)用于保存稱為中斷服務路由(ISR)的特殊子程序類型的內存地址。
引入堆棧指針
我們將考慮所有這些小寄存器流氓如何在以后的專欄中詳細介紹它們的魔力,但是如果萬一這對您來說是新手,則簡要描述一下SP的操作可能是一個好主意。
我們大多數人都去過自助餐廳,在該餐廳中,一堆餐盤堆疊在基于彈簧的機構上。假設您是負責將印版裝入機械裝置的人。我們還假設板編號為(1、2、3…),并且是一名強迫癥工程師,這是將前三個板裝入機械裝置的順序,如下所示:
基于Spring的自助餐廳板塊存儲機制(來源:Max Maxfield)
現在,假設有一個顧客進來,伸手去拿盤子。當然,它們將檢索您添加到堆棧頂部的最后一塊盤子(在我們的示例中為3)。在計算方面,這種形式的存儲和檢索將被歸類為后進先出(LIFO)過程。
好吧,我們的SP的工作方式與此類似。在程序開始時,我們將使用內存中某個區域的某個位置的地址加載SP,而該內存將不會用于其他任何用途。隨后,每次執行PUSH操作時,CPU會將指定的數據寫入SP當前指向的存儲位置(“堆棧頂部”),然后遞增SP使其指向下一個空閑位置。相比之下,每次執行POP操作時,CPU都會先將SP遞減以指向堆棧頂部的數據,然后從堆棧中讀取該數據并將其存儲在我們告訴它的任何位置。
引入6502
出于以下討論的目的,我們將使用MOS技術6502提供比較的基礎。6502于1975年推出,具有8位數據總線和16位地址總線,其寄存器包括一個8位累加器寄存器(A),兩個8位索引寄存器(X和Y),一個7位寄存器。位處理器狀態標志寄存器(P),8位堆棧指針(S)和16位程序計數器(PC)。
與HRRG不同,在HRRG中,我們可以用所需的任何值加載12位SP,而6502的8位SP在加電時會自動加載$ 00(請記住,我們使用“ $”字符表示十六進制值),堆棧的起始地址固定為$ 0100。這意味著6502的堆棧地址空間被限制為跨越256個地址,從$ 0100到$ 01FF。
盡管與今天的微處理器產品相比,6502看起來很簡單,但是在推出之初它就被認為是非常了不起的,尤其是它的價格合理(1975年為25美元)。許多人繼續基于此處理器創建令人驚嘆的項目,例如此基于6502的虛擬現實(VR)系統。并且6502的新形式不斷出現在現場,例如MOnSter 6502 CPU。
此外,與HRRG不同,在HRRG中,我們可以向12位中斷向量(IV)加載所需的任何值,而6502則硬接線以在內存地址$ FFFE和$ FFFF中查找以檢索其16位中斷向量,其中這個2字節的值將由用戶加載到內存中(當我們說“由用戶”時,我們的意思是“由用戶程序”)。
在2 ^ 8 = 256種可能的操作碼(指令)中,原始6502使用151將其組織為56條指令(取決于指令),一種或多種尋址模式。根據指令和尋址方式的不同,6502操作碼可能需要零個,一個或兩個字節作為操作數。因此6502機器指令的長度從1到3個字節不等。
MOV(加載和存儲)
6502允許用戶將值從存儲器加載到其累加器(A)和其索引寄存器(X和Y)中。同樣,它允許用戶將這些寄存器中的值存儲到內存中。所有這些都需要六個指令,如下所示:
LDA(加載累加器)
LDX(加載X寄存器)
LDY(加載Y寄存器)
STA(存儲累加器)
STX(存儲X寄存器)
STY(存儲Y寄存器)
相比之下,HRRG具有單個MOV指令,根據其操作數,該指令可用于從寄存器到寄存器,寄存器到內存,內存到寄存器以及內存到內存中移動(復制)數據。此外,這些說明適用于HRRG的所有寄存器(即使這樣做沒有任何意義,請參見下文)。
INC(遞增)和DEC(遞減)
6502允許用戶在指定的存儲位置或其索引寄存器(X和Y)中對值進行遞增(加1)和遞減(從中減去1)。為此,它需要執行以下六個指令:
INC(增加存儲單元的內容)
INX(增加X寄存器的內容)
INY(增加Y寄存器的內容)
DEC(減少存儲單元的內容)
DEX(減少X寄存器的內容)
DEY(遞減Y寄存器的內容)
“如何增加或減少累加器的內容?”我聽到你哭了。好吧,為了用6502做到這一點,您將必須執行常規的加法或減法運算,如本專欄的稍后部分所述。
相比之下,HRRG的INC和DEC指令可用于增加內存位置以及CPU的4位和12位寄存器中任何一個的內容。
“什么?任何寄存器-甚至程序計數器?”我聽到你緊張地尖叫。是的,即使似乎沒有必要,您也可以在任何寄存器上使用這些指令。例如,增加程序計數器(PC)通常被認為是一件壞事,但是HRRG允許在機器代碼和底層硬件中這樣做。
我們可能會在匯編器中標記某些“傻瓜”(我們將在以后的專欄中討論),但是如果用戶決定忽略并繞過匯編器發出的任何警告和/或錯誤消息,那么就這樣吧,因為(a)在沒有大量異常和特殊情況的情況下,更易于設計可工作的硬件,(b)用戶可能會想到我們沒有想到的狡猾的使用模型,并且(c)我們不是“明智的警察”(除了還有其他事情,我沒有合適的褲子)。
ADDC和SUBB(加減法)
在簡單計算機上考慮加法時,通常會考慮將兩個數字加起來,例如3 + 2 =5。問題是我們可以表示的數字大小為受我們的數據總線和數據字段的寬度限制。例如,在HRRG的情況下,可以使用單個4位半字節表示0到15范圍內的無符號數或-8到+7范圍內的有符號數。
這顯然是一個限制。幸運的是,我們可以使用多個半字節來表示我們的值。例如,在HRRG的情況下,可以使用一對4位半字節來表示0到255范圍內的無符號數或-128到+127范圍內的有符號數。
假設我們想將兩個2點值加在一起。在這種情況下,我們將從添加兩個最低有效的半字節(LSN)開始。根據它們的值,這將導致將0或1值存儲在進位(C)狀態標志中。當我們添加下一個對點時,我們還需要包括(添加)進位標志的內容。
一些早期的8位處理器提供了兩條加法指令,例如ADD(“無進位加法”)和ADDC(“有進位加法”)。其他用戶(例如6502)僅提供“帶進位加法”版本,并且要由用戶來實現“無帶進位加法”,方法是先將0的進位標志裝入然后執行加法。
同樣的事情也適用于減法。在這種情況下,某些早期的8位處理器提供了兩條減法指令,例如SUB(“無借位減法”)和SUBB(“無借位減法”)。諸如6502之類的其他軟件僅提供“帶借位減法”版本,并且要由用戶來實現“無帶借物減法”,方法是先將進位標志裝入1,然后執行減法。
“等等,我們沒有借用狀態標志,”我聽到你在抱怨。沒錯,但是在減法的情況下,進位(C)標志承擔借位(B)標志的作用。基于唯一的物理標志是進位標志,一些設計人員傾向于說“減去/不攜帶進位”,并使用諸如SUBC助記符之類的東西,但是,在我看來,這最終導致了更多的混亂,而不是值得的。
最重要的是6502提供了以下兩個說明:
ADC(帶進位加)
SBC(帶進位減)
此外,這些指令僅允許您將指定存儲位置的內容添加/累加到累加器的內容中,結果存儲在累加器中。
同樣,HRRH提供以下兩個說明:
ADDC(帶進位加)
SUBB(帶借位減)
但是,這些指令允許執行寄存器到寄存器,寄存器到內存,內存到寄存器以及內存到內存的加法和減法。(在我的下一篇專欄中,我們將考慮使用ADDC和SUBB指令來實現其ADD和SUB對應項的各種方式。)
ROLC和RORC(旋轉和移位)
可能有八個基本的旋轉和移位操作,我們可以為其分配助記符,如下所示:
ROL(向左旋轉)
ROR(向右旋轉)
ROLC(通過進位標志向左旋轉)
RORC(通過進位標志向右旋轉)
LSHL(邏輯左移)
ASHL(算術左移)
LSHR(邏輯右移)
ASHR(算術右移)正確的)
請記住,不同的CPUS的設計人員對這種事情使用各種不同的助記符。我上面顯示的那些對我來說最有意義。現在,如果我們決定(但沒有決定)在我們的4位HRRG中實現所有這8條指令,則其動作的圖形表示如下所示:
各種可能的移位和旋轉操作的動作(來源:Max Maxfield)
對于ROL(向左旋轉),所有位都向左移動一位;同樣,從概念上講“掉落到末端”的最高有效位(MSB)被復制到最低有效位(LSB)和進位標志。相比之下,在ROR(向左旋轉)的情況下,所有位都向右移一位;同樣,從概念上講“從末端掉下來”的LSB也被復制到MSB和進位標志中。
除了將進位標志的原始內容復制到LSB之外,ROLC(通過進位向左旋轉)與ROL非常相似。同樣,除了進位標志的原始內容被復制到MSB中外,RORC(從進位向右旋轉)與ROR非常相似。
LSHL(邏輯左移)操作與ROL(左旋轉)和ROLC(左移通過貓)操作非常相似,不同之處在于將0復制到LSB中。同樣,LSHR(邏輯右移)操作與ROR(右移)和RORC(右移進位)操作非常相似,不同之處在于將0復制到了MSB中。
ASHL(算術左移)操作在功能上與LSHL(邏輯左移)相同-兩者均導致將0復制到LSB中-因此,沒有設計者會費心將它們作為單獨的指令在CPU中實現。另一方面,在編寫程序時,我們可能更喜歡使用兩種不同的助記符作為注釋形式,以提醒自己(和其他讀者)我們在捕獲代碼時的想法。
最后,ASHR(算術右移)與LSHR(邏輯右移)類似,不同之處在于MSB(符號位)被自身復制回去(另請參見“C / C ++>移位運算符的工作方式”)。
對于HRRG(僅限16條指令),我們決定只實施八個基本旋轉和移位中的兩個:
ROLC(通過進位標志向左旋轉)
RORC(通過進位標志向右旋轉)
我們選擇這兩項的原因是,很容易將它們用作實現其他指令功能的基礎。(在下一篇專欄中,我們將考慮使用ROLC和RORC指令來實現其ROL,ROR,LSHL,LSHR,ASHR和ASHR對應項的各種方式。)
AND,OR,XOR和CMP(邏輯運算)
這些指令的工作方式與該星球上幾乎所有其他處理器上的指令工作方式相似,因此我們在這里不會花太多時間。只需說6502的AND(邏輯與),EOR(異或)和ORA(異或)僅允許您對累加器的內容執行操作,并在內存中保存另一個值,并將結果存儲在蓄能器。相比之下,HRRG的AND,OR和XOR等效項支持寄存器到寄存器,寄存器到內存以及內存到寄存器操作。
對于HRRG的CMP(比較指令),它也支持寄存器到寄存器,寄存器到內存,內存到寄存器和內存到內存操作,將比較的兩個值視為是無符號的二進制值。
CLR和SET(位操作)
一些處理器提供了一組指令,可用于清除或設置狀態寄存器中的各個位。例如,6502支持七種這樣的指令:
CLC(清除進位標志)
CLD(清除十進制模式標志)
CLI(清除中斷禁止標志)
CLV(清除溢出標志)
SEC(設置進位標志)
SED(設置十進制模式標志)
SET(設置中斷禁止標志)
HRRG沒有提供任何這些說明,但是如果提供了這些說明,它們的經濟學原理將如下所示(正如我們所看到的,這是我瘋狂的原因):
CLRN(清除負標志)
CLRZ(清除零標志)
CLRC(清除進位標志)
CLRO(清除溢出標志)
CLRI(清除中斷屏蔽標志)
SETN(設置負標志)
SETZ(設置零標志)
SETC(設置進位標志)
SETO(設置溢出標志)
SETI(設置中斷屏蔽標志)
SETH(設置停止標志)
觀察到沒有CLRH(清除暫停標志)。這是因為一旦暫停標志設置為1,重置它的唯一方法就是觸發一個中斷(假設中斷屏蔽標志設置為1)或重置機器。
關鍵是我們可以使用AND和OR邏輯運算來實現所有這些指令。假設我們想將進位標志(狀態寄存器S0中的位2)清除為0,我們可以通過將S1的內容與%1011進行“與”操作(請記住,我們使用'%'字符來表示二進制值)來實現。同樣,如果要將進位標志設置為1,可以通過將狀態寄存器S0的內容與%0100進行邏輯或運算來實現。
綜上所述,在編寫匯編代碼時最好有位操作指令對我們可用,因此我們將在下一節中討論如何使用匯編器將它們添加到庫中。
推入和彈出(或拉出)
這些指令用于將值壓入堆棧并再次彈出(或拉出)它們。在6502的情況下,有6條與堆棧相關的指令(請記住,正如我們前面所討論的),6502的8位堆棧指針本身在上電時會自動加載$ 00。
TSX(將堆棧指針的值傳輸到索引寄存器X)
TXS(將索引寄存器X的內容傳輸到堆棧指針)
PHA(將累加器的內容推送到堆棧)
PHP(將處理器狀態寄存器的內容推送到棧上)
PLA(將棧頂上的值拉到累加器中)
PLP(將棧頂上的值拉到處理器狀態寄存器中)
對于HRRG,我們只有兩個說明:
PUSH(將所選寄存器或存儲器位置的內容推入堆棧)
POP(將堆棧頂部的值彈出到所選寄存器或存儲器位置)
HRRG的指令可用于任何CPU的寄存器或存儲器位置。此外,HRRG的MOV指令提供(并超過了)6502的TSX和TXS指令的功能。
JMP,JSR和相關指令
JMP(無條件跳轉)指令允許CPU跳轉到程序的另一部分。JSR指令告訴CPU跳轉到子例程。JSR通常的工作方式是用戶將所有相關信息壓入堆棧,然后調用JSR。反過來,CPU將程序計數器(PC)中的返回地址壓入堆棧,然后跳轉到子例程。
仍在談論這通常的工作方式,在子例程的末尾,使用RTS(從子例程返回)指令將返回地址從堆棧頂部彈出到程序計數器(PC)中,然后將我們返回主程序。程序。
還值得注意的是,中斷服務程序(ISR)的作用有點類似于子程序,因為該中斷將導致CPU在服務該中斷之前將返回地址推入堆棧的頂部。在ISR的末尾,使用RTI(中斷返回)指令將返回地址彈出堆棧頂部,然后將我們返回主程序。
6502擁有以下所有四個說明:
JMP(無條件跳轉)
JSR(跳轉到子程序)
RTS(從子程序返回)
RTI(從中斷返回)
處理器還將支持一堆指令,這些指令將根據狀態標志的狀態觸發跳轉(或分支)。例如6502提供了八種這樣的指令,如下所示:
BCC(科若進位標志清除)
BCS(科若進位標志組)
BEQ(如果科零標志集)
BMI(分公司如果負數標記組)
BNE(分公司如果零標志清除)
BPL(分公司如果負數標記清除)
BVC(
BVS(如果設置了溢出標志則分支)(如果設置了溢出標志則分支)
與6502的JMP和JSR指令允許CPU在其16位地址空間內跳轉到任何地方不同,這些分支指令使用帶符號的8位相對地址將控制權轉移到位于前127個字節(后)和128個字節內的目標。分支指令后(之前)的字節數。程序往往會進行很多跳轉,例如循環循環,因此在時鐘有限的日子里,使用1字節的分支地址而不是2字節的跳轉地址可能會節省大量的時間和空間。速度,處理器周期和內存位置。
對于HRRG,我們只有兩個與跳轉有關的指令:
JMP(無條件跳轉)
JSR(跳轉到子例程)
我們沒有RTS或RTI指令-通過簡單地從棧頂檢索返回地址并將其使用POP指令加載到程序計數器(PC)中,可以達到相同的效果。
事實是,實現JMP指令的方式意味著我們可以使用它來實現與具有以下指令集相同的效果:
JMP(無條件跳轉)
JMPN(無條件跳轉,或“從不跳轉”)*
JPN(如果為負,則跳轉;如果N標志為1)
JPNN(如果為非負,則跳轉;如果N標志為0),
JPZ(如果為零,則跳轉;如果Z標志為1)
JPNZ(如果不為零則跳躍;如果Z標志為0)
JPC(如果進位則跳躍;如果C標志為1)
JPNC(如果不進位則跳躍;如果C標志為0)
JPO(如果溢出則跳躍;如果O標志為1)
JPNO(如果不溢出則跳轉;如果O標志為0)
JPI(如果中斷屏蔽則跳轉;如果I標志為1)**
JPNI(如果沒有中斷屏蔽則跳轉;如果I標志為0)**
JPH(如果暫停則跳轉;如果H標志為1)***
JPNH(如果不停止則跳轉;如果H標志為0)**
注意* JMPN(“永不跳轉”)可用于調試目的。
注意**基于I標志為0或1或H標志為0的狀態進行的跳轉不是特別有用,因為程序員已經知道這些標志包含的內容(與N,Z,C和O不同)標志,其值取決于算術和邏輯運算的結果)。但是,它們是通過執行HRRG的JMP指令的方式來實現的。
注意*** JPH(如果H標志為1,則跳轉)是完全沒有意義的,這是因為一旦程序將此標志設置為1,CPU就會停止操作,并且只能通過觸發中斷來重置該標志(假定中斷屏蔽標志設置為1)或通過重置機器,因此此處僅出于完整性考慮而包含此指令。
對于大多數處理器,JSR(跳轉到子例程)指令的行為與JMP(無條件跳轉)指令的行為類似;也就是說,沒有與JPN,JPNN等等效的JSR。但是,由于HRRG的指令體系結構,我們可以使用JSR來實現與以下指令相同的效果:
JSR(無條件JSR)
JSRN(無條件JSR)*
JSN(JSR如果為負;如果N標志為1)
JSNN(JSR如果不是負;如果N標志為0)
JSZ(JSR如果為零;如果Z標志為1)
JSNZ(如果不是零,則為JSR;如果Z標志為0;則為JSR);如果是
JSC,如果是進位;如果C標志為1;則為
JSNC;如果不是,則為JSR;如果C標志為0,則為
JSO;如果是溢出,則為JSR;如果O標志為1,
則為JSNO。(如果沒有溢出,則為JSR;如果O標志為0,則為JSR)
JSI(如果是中斷屏蔽,則為JSR;如果I標志為1))**
JSNI(如果不是中斷屏蔽,則為JSR;如果I標志為0)**
JSH(如果暫停,則為JSR;如果H標志是1)***
JSNH(如果不停止,則為JSR;如果H標志為0)**
注意*,**和***;對于上述各種跳轉指令,適用相同的警告。
編輯:hfy
-
寄存器
+關注
關注
31文章
5357瀏覽量
120615 -
cpu
+關注
關注
68文章
10876瀏覽量
212126
發布評論請先 登錄
相關推薦
評論