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.
还有很多,但它们涉及你尚未听说过的寄存器。
注意: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,等等。
至于符号位的状态对值的意义,它是这样的:如果符号位是清除的,则该值是一个正数,并且像无符号数一样正常存储。如果符号位是设置的,则该值是负数,并且以补码格式存储。要将一个正数转换为其负数对应值,你拥有两种方法,你可以根据方便选择其中任何一种。
- 计算零减去该数(就像现实世界中的负数)。如果你对如何执行此操作感到困惑,你可以将0和256(或者65536,如果合适)视为同一个数字。因此,-6将是256 - 6 或 250:%11111010。
- 翻转每个位的状态,然后加一。因此,-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 就知道了)。