跳到內容

TI 83 Plus 彙編/暫存器

來自華夏公益教科書,開放的書籍,為開放的世界

本頁的許多部分基於取自 Sean McLaughlin 的 Learn Assembly in 28 Days,第 3 天。


這是必要的閱讀內容。計算機的計數方式與你我不同。

十進位制

[編輯 | 編輯原始碼]

所有數制都使用特定的基數。基數等同於“進位制”,如果這能幫到你的話,但我應該提醒你,說諸如“你們所有的基數都是屬於我們的”之類的話,是讓別人朝你扔尖銳東西的好方法(但到底是由於可怕的雙關語還是過時的流行文化引用,就很難說了... :-)。為了理解基數是什麼,請考慮我們日常使用的數字系統,它使用十進位制。

正如你在小學學過但在暑假忘記的那樣,在十進位制數中,每個數字指定 10 的某個冪,因此你需要十個不同的數字來表示任何數字。最右邊的數字指定 100,第二個數字指定 101,第三個指定 102,依此類推。因此,你可以將十進位制數(例如 276310)分解如下(儘管它最終是多餘的)

276310  = (2 x 103) + (7 x 102) + (6 x 101) + (3 x 100)
        = (2 x 1000) + (7 x 100) + (6 x 10) + (3 x 1)
        = 2000 + 700 + 60 + 3
        = 276310

計算機喜歡使用另外兩種進位制:二進位制和十六進位制。八進位制是 8 進位制,似乎已經消亡了。唯一使用八進位制的作業系統是 UNIX。

二進位制

[編輯 | 編輯原始碼]

二進位制是 2 進位制系統,因此它只使用兩個數字(0 和 1),每個數字表示 2 的某個冪

101101012  = (1 x 27) + (0 x 26) + (1 x 25) + (1 x 24) + (0 x 23) + (1 x 22) + (0 x 21) + (1 x 20)
           = (1 x 128) + (0 x 64) + (1 x 32) + (1 x 16) + (0 x 8) + (1 x 4) + (0 x 2) + (1 x 1)
           = 128 + 32 + 16 + 4 + 1
           = 18110

單個二進位制數字通常稱為位。八位稱為位元組。你可能還會聽到其他組合:名稱 大小 nibble 4 位 word 16 位 dword 32 位 quadword 64 位 由於 Z80 只能直接操作位元組和字(某些情況下還可以操作 nibble),因此你進行的大部分資料處理將主要涉及這些,因此你不必過多地關注其他方面(儘管熟悉一下還是個好主意)。

我們會發現自己使用,或者至少參考位元組或字的各個位。命名法

  • 如果我們將位水平排列,我們將最右邊的位稱為“位 0”,向左的每個位都比它大 1。
  • 最左邊和最右邊的位有特殊的名稱:最左邊的位稱為高位或最高有效位(因為它控制著數字的最高 2 的冪,因此對值的貢獻最大)。最右邊的位稱為低位或最低有效位。
  • 我們可以將這些要點應用於位元組中的 nibble、字或 dword 中的位元組等等。例如,64 位數量中最右邊的位元組將被稱為最低有效位元組。

十六進位制

[編輯 | 編輯原始碼]

十六進位制是 16 進位制,因此它使用 16 個數字:常規數字 0 到 9,以及字母 A 到 F,它們對應於十進位制值 10 到 15。

1A2F16  = (1 x 163) + (10 x 162) + (2 x 161) + (15 x 160)
        = (1 x 4096) + (10 x 256) + (2 x 16) + (15 x 1)
        = 4096 + 2560 + 32 + 15
        = 670310

十六進位制值與二進位制值之間存在有趣的關係:取數字 110100112。在十六進位制中,此值表示為 D316,但請考慮各個數字

D16 = 11012

316 = 00112

將這兩個二進位制數與原始數進行比較。你應該會看到,一個十六進位制數字等同於一個 nibble。這就是十六進位制如此棒的原因,將計算機使用的二進位制數轉換為更易於管理的十六進位制值非常容易。

指定基數

[編輯 | 編輯原始碼]

為了在上面指定基數,我們採用了許多數學家使用的表示法,將基數寫成下標。太可惜了,我們必須用純文字格式編寫彙編程式碼,它沒有這種功能。表示基數的方法多種多樣,但無論哪種情況,都涉及在數字後附加一個或多個額外的字元。TASM 讓你可以選擇使用符號字首或字母后綴。

Prefix Format  Suffix Format   Base
%10011011      10011011b       Binary
$31D0          31D0h           Hexadecimal
@174           174o            Octal
12305 *        12305d          Decimal
* no prefix

你使用哪種格式並不重要,只要你不混合使用它們(例如 $4F33h)。字首格式可能更容易閱讀,因為字母在數字中有點迷失了(尤其是如果它是大寫字母的話)。

暫存器

[編輯 | 編輯原始碼]

暫存器是 CPU 內部非常昂貴的 RAM 的部分,用於儲存數字並快速對其進行運算。此 CPU 有 14 個暫存器:A B C D E F H I L R PC SP IX 和 IY。你暫時不需要關注暫存器 I、R、PC 和 SP。

單字母暫存器大小為 8 位,因此它們可以儲存 0 到 255 之間的任何數字。由於這在許多情況下是不夠的,因此它們可以組合成四個暫存器對:AF BC DE HL。這些暫存器以及 IX 和 IY 都是 16 位的,可以儲存 0 到 65535 之間的數字。

這些暫存器是通用的,到了一定程度。我的意思是,你通常可以使用任何你想使用的暫存器,但在很多時候你不得不使用,或者最好使用特定的暫存器。例如,只有 HL、IX 和 IY 暫存器可用於在載入到除 A 以外的暫存器時進行間接記憶體定址,所有 16 位暫存器可用於在從暫存器 A 載入到暫存器 A 或從暫存器 A 載入時,從間接定址的記憶體位置載入到或從記憶體位置載入(分別)。

暫存器的特殊用途

8 位暫存器

  • A 也稱為“累加器”。它是算術運算和訪問記憶體的主要暫存器。事實上,它是唯一可以使用的 8 位暫存器。
  • B 通常用作 8 位計數器。
  • C 用於你想要與硬體埠互動時。
  • F 被稱為標誌。此暫存器的位表示(也就是說,它們“標記”)某些事件是否發生。例如,其中一個標誌(位)報告累加器是否為零。標誌的用途將在以後解釋,因為我們目前不需要它們。
  • 當處理器處於中斷模式 2(IM 2)時,I 是中斷向量的高位元組,向量的低位元組來自資料匯流排,在功能上是隨機的。請注意,此向量用於首先從 RAM 載入一個值,然後呼叫該地址。你只能使用 A 載入到或從該暫存器載入。
  • R 是動態 RAM 重新整理暫存器,它在每條指令後增加一個取決於指令的量。它的內容是偽隨機的。你只能使用 A 載入到或從該暫存器載入。

所有 16 位暫存器的兩個位元組也可以單獨使用。

16 位暫存器

  • AF 僅用於壓棧和出棧。
  • HL 有兩個用途。一是,它類似於累加器的 16 位等效項,即它用於 16 位算術。二是,它儲存記憶體地址的high 和low 位元組。
  • BC 用於操作位元組流的指令和程式碼段,作為byte counter。
  • DE 儲存記憶體位置的地址,該記憶體位置是destination。
  • PC 儲存當前執行指令的地址,你只能透過跳轉、呼叫和返回來更改其內容。直接載入到它的唯一方法是 'jp (hl)',你可以將此視為 'ld pc,hl'。
  • SP 是堆疊指標,它決定在 RAM 中的什麼位置進行壓棧和出棧,你只能使用 HL 載入到它,並且只能使用像 'add hl,sp' 這樣的算術運算從它載入,如果 HL 在加法之前為零,那麼它現在儲存 SP 的值。
  • IX 和 IY 是一個有趣的小暫存器,稱為index registers。幾乎在所有可以使用 HL 的地方,也可以使用 IX 和 IY。需要注意的是,使用 IX 和 IY 會導致比使用 HL 更慢且程式碼更膨脹(大約是大小和時間的兩倍),因為它們在 8080 上不存在(Z80 是基於該處理器的),因此僅在必要時使用它們(通常是當 HL 被佔用時)。IX 和 IY 可以執行其他暫存器無法執行的特殊操作,我們將在適當的時候討論這一點。

要儲存到暫存器,可以使用 LD 指令。

LD destination, source         Stores the value of source into destination.
LD 的有效引數

還有很多,但它們涉及你尚未聽說過的暫存器。

注意:imm8:8 位立即數。imm16:16 位立即數。

                                                                        Destination
source  A       B       C       D       E       H       L       BC      DE      HL      (BC)    (DE)    (HL)    (imm16)
A       *       *       *       *       *       *       *                               *       *             *        *
B       *       *       *       *       *       *       *                                       *       
C       *       *       *       *       *       *       *                                       *       
D       *       *       *       *       *       *       *                                       *       
E       *       *       *       *       *       *       *                                       *       
H       *       *       *       *       *       *       *                                       *       
L       *       *       *       *       *       *       *                                       *       
BC                                                                                                              *
DE                                                                                                              *
HL                                                                                                              *
(BC)    *                                                                                           
(DE)    *                                                                                           
(HL)    *       *       *       *       *       *       *
(imm16) *                                                       *       *       *                            
imm8    *       *       *       *       *       *       *                                               *
imm16                                                           *       *       *                            

你顯然不知道括號對運算元有什麼區別。你很快就會看到。你只能透過 A 來/去 I 和 R。

示例

LD A, 25       Stores 25 into register A
LD D, B        Stores the value of register B into register D.
LD ($8325), A  Stores the value of register A into address $8325 (explained later on).

應該明確說明的一些要點

LD指令的兩個運算元不能都是暫存器對,除了SP。你必須分別載入暫存器。

  ; Since we can't do LD DE, HL...
  LD    D, H
  LD    E, L
  ; But we can do this:
  LD SP,HL
  ; The following instruction effectively loads HL into PC
  JP (HL)

如果使用LD指令載入一個超過暫存器容量的數字,在彙編時會報錯。然而,儲存負數是合法的,但數字會“包裹”以適應。例如,如果你將-1賦值給A,它實際上會儲存255。如果你將-2330賦值給BC,它實際上會儲存63206。將1加上暫存器能夠儲存的最大值,就能得到實際儲存的值。這種現象的原因會在稍後解釋。

類似於LD指令但功能不同的指令是EX。雖然它對運算元有很強的限制,但它是一個非常有用的指令。(90%的情況下,你想要交換的暫存器是HL和DE)。

EX DE, HL      Swaps the value of DE with the value of HL.

如果你想交換其他暫存器對和HL(或索引暫存器)而不丟失其他暫存器,你可以執行以下操作(雖然速度很慢)。

PUSH BC
EX (SP),HL
POP BC

暫存器F和AF不能用作LD指令的運算元。實際上,除了少數指令外,這些暫存器不能作為任何指令的運算元。

到目前為止,我們一直預設暫存器只能取正值,但在現實世界中,負數同樣常見。幸運的是,有一些方法可以表示負數。在組合語言中,我們可以將一個數字歸類為有符號或無符號。無符號表示數字只能取正值,有符號表示數字可以是正數也可以是負數。我們需要關注的是有符號數的概念。

事實證明,存在許多有符號的數制方案,但我們唯一關注的是補碼。在補碼中,當我們有一個有符號值時,該值的最高位稱為符號位,其狀態決定了該值的符號。符號位的存在自然地限制了數字可以擁有的位數。因此,我們用來表示該值的位數減少了一個;對於一個八位字串,我們可以用十進位制表示從-128到+127的數值範圍。對於一個十六位字串,它是-32768到32767,等等。

至於符號位的狀態對值的意義,它是這樣的:如果符號位是清除的,則該值是一個正數,並且像無符號數一樣正常儲存。如果符號位是設定的,則該值是負數,並且以補碼格式儲存。要將一個正數轉換為其負數對應值,你擁有兩種方法,你可以根據方便選擇其中任何一種。

  1. 計算零減去該數(就像現實世界中的負數)。如果你對如何執行此操作感到困惑,你可以將0和256(或者65536,如果合適)視為同一個數字。因此,-6將是256 - 6 或 250:%11111010。
  2. 翻轉每個位的狀態,然後加一。因此,-6將是%11111001 + 1 或 %11111010。

補碼有一個特殊情況,在這種情況下取反會失敗,那就是當試圖取反最小的負值時。

     %10000000        -128
     %01111111        Invert all bits
     %10000000        Add one (=-128)

當然,-(-128) 並不是 -128,但 +128 在只有八位的補碼中無法表示,所以最小的負值永遠無法取反。

有一個指令可以自動執行補碼:NEG 計算累加器的補碼。我相信你會發現這個理論非常引人入勝,但你可能真正感興趣的是 CPU 如何處理無符號數和有符號數之間的差異。答案是:它不處理。你看,補碼的妙處在於,如果我們對兩個數進行加或減,結果對有符號數和無符號數都將是有效的。

                    unsigned        signed
       %00110010           5             5
     + %11001110       + 206          + -5
     %1 00000000         256             0   (Disqualify ninth bit)

Z80 的製造商並沒有忽視這種現象。你可以使用相同的硬體來新增有符號數,就像新增無符號數一樣,只是用補碼,更少的硬體意味著更便宜的晶片(沒錯,如今你可以花 50 美分買一大把 Z80,但在 1975 年,這是一個大事,看看 6502 就知道了)。


上一頁:跳轉
下一頁:記憶體
目錄:TI 83 Plus 組合語言

華夏公益教科書