我们已经到了需要为 4 位 HRRG 计算机定义汇编语言的地步,但首先我们需要考虑某些概念。
如今,我们习惯于使用 C/C++、Java、Python 等高级语言对我们的计算机和微控制器进行编程,但生活并不总是那么容易。第一批计算机程序员用机器代码(处理器本身使用的数值)捕获他们的程序。由于这太痛苦了,他们很快将抽象阶梯向上移动到汇编语言。
另请参阅此索引,其中列出了构成我们的 4 位 HRRG 计算机项目的所有文章,以及一些有趣的相关专栏。
我们必须做的一件事是为我们的 4 位 HRRG 计算机定义这样一种汇编语言,但在我们满怀热情地投入战斗之前,我们需要先介绍几个概念。
Big-endian 与 little-endian
当现实世界的计算机使用多个字节来表示数据值或内存地址时,将这些字节存储在内存中的主要技术有两种:要么将最高有效字节 (MSB) 存储在具有最低地址的位置,在这种情况下,我们可以说它是“大端优先”存储的,或者最低有效字节 (LSB) 存储在最低地址中,在这种情况下,我们可以说它是“小端优先”存储的以终为先。”
让我们在 HRRG 计算机的上下文中考虑这两种机制,它具有 4 位(1-nybble)数据总线和 12 位(3-nybble)地址总线,通过可视化我们如何在内存中存储 3-nybble 值 $426 开始在内存位置 $100 如下图所示:
HRRG 采用大端方法。当然,您可能不会对此感到惊讶——由于各种超出这些讨论范围的技术原因——一些计算机设计师偏爱一种风格,而另一些则采取相反的策略。直到人们开始对创建异构计算环境产生兴趣,在该环境中将多台不同的机器连接在一起以便文件可以在它们之间传输,这才真正重要,在这一点上,许多激烈的争接踵而至。
1980 年,丹尼·科恩 (Danny Cohen) 撰写的一篇著名论文《论圣战与和平恳求》使用术语big-endian和little-endian来指代存储数据的两种技术。这些至今仍在使用的术语源自盎格鲁-爱尔兰讽刺作家乔纳森·斯威夫特所著的《格列佛游记》一书。little-endian 和 big-endian 的绰号来自这个故事的一部分,即两个国家就应该先吃煮鸡蛋的哪一端——小端还是大端——而发生战争!
如果你想知道的偶然机会,斯威夫特在 1726 年写下了他的伟大作品,那是在发明台球杆之前的 9 年(在此之前,球员过去常常用小钉头锤击球)。
寻址模式
术语寻址模式是指指定指令操作数的方式。这些小流氓有无数不同的名称和口味,因此以下内容应视为仅代表一个概述。
出于这些讨论的目的,我们将假设 HRRG 型架构具有 4 位(1-nybble)数据总线和 12 位(3-nybble)地址总线。但是,下面介绍的寄存器和指令助记符是虚构的,仅用于这些审议。
隐含(又名隐含):在隐含(有时称为隐含)寻址模式的情况下,目标是由指令本身隐含的。例如,假设我们有一个名为 Q 的寄存器和一个名为 INCQ 的指令,其目的是增加(加 1 到)寄存器 Q 的内容。在这种情况下,我们将只有一个没有操作数的 INCQ 操作码,如图所示以下:
假设程序计数器 (PC) 从地址 $100 开始,CPU 将读取并执行隐含的操作码。我们最终将 PC 指向地址 $101,这是 CPU 期望在程序中找到下一个操作码的地方。
立即:在立即寻址模式的情况下,数据立即出现在操作码之后。例如,假设我们有一个名为 Q 的寄存器和一个名为 LDQ 的指令,其目的是使用立即寻址模式将 nybble 数据加载到寄存器 Q 中,如下所示:
假设程序计数器(PC)从地址 $100 开始,CPU 读取操作码,实现此操作码使用立即模式,并将数据 nybble(本例中为 $F)加载到 Q 寄存器中。我们最终将 PC 指向地址 $102,这是 CPU 期望在程序中找到下一个操作码的地方。
相对:在相对寻址模式的情况下,目标地址被指定为相对于程序计数器 (PC) 中当前值的偏移量。这样的偏移量将被视为可以表示正值和负值的有符号二进制数。
假设我们的偏移量表示为 2-nybble 值。由于 2-nybble 字段可以表示 -128 到 +127 范围内的有符号数,这意味着偏移量可以指向当前 PC 值之前的 128 个位置(即较低的内存地址)和之后的127 个位置之间的某个内存位置。当前 PC 值(即更高的内存地址)。
纯粹出于与此处所示其他示例相关的示例的考虑,假设我们有一个名为Q的寄存器和一个称为LDQ的指令,其目的是使用相对寻址模式将一个nyble数据加载到寄存器Q中(虽然我们使用的是相同的助记符,但该LDQ的操作码与我们在上一个示例中讨论的LDQ指令不同)。此外,假设偏移值为$08(十进制+8),如下所示:
假设程序计数器(PC)从地址$100开始,CPU读取操作码,实现其使用相对模式,并将包含偏移值的以下两个nyble复制到内部(临时)寄存器中。
接下来,它将偏移值添加到PC中的当前值,并使用结果指向包含数据nyble的位置。最后,它将该数据值(本例中为F)加载到Q寄存器中。最后,PC指向地址$103,CPU期望在该地址找到程序中的下一个操作码。
纯粹出于完整性考虑,让我们考虑第二个相对寻址示例,其中偏移值为$F8(十进制中为-8),如下所示:
具有负偏移值的相对寻址模式。(来源:Max Maxfield)
需要注意的是,除了如上所示的数据操作指令外,还可以使用相对寻址来执行跳转或分支指令。
当然,并非所有处理器都支持所有类型指令的所有寻址模式。例如,正如我们在之前的专栏中所讨论的,6502 微处理器有一个 8 位数据总线和一个 16 位地址总线。在其 JMP(“无条件跳转”)指令的情况下,6502 仅支持使用 16 位(2 字节)地址的绝对和间接寻址(下面介绍绝对和间接模式)。但是,6502 还支持一套使用 8 位(1 字节)相对地址的分支指令。正如我在该专栏中指出的那样:
程序往往会进行大量跳转——例如循环循环——因此在时钟有限的日子里,使用 1 字节的分支地址而不是 2 字节的跳转地址可能会显着节省空间和时间速度、处理器周期和内存位置。
Zilog Z80 微处理器不支持相对寻址,您必须使用 Intel 8086 才能更好地查看使用相对寻址模式的“短跳转”指令。
最后一点,我们在上面的讨论中多次使用短语“PC 中的当前值”来说明要添加偏移量的值。当“迫在眉睫”时,我们使用了 103 美元的值,这是下一个操作码的地址。为什么我们使用这个值?使用 $100(原始操作码的地址)或 $102(偏移量中第二个 nybble 的地址)不是更有意义吗?
好吧,假设我们正在执行某种形式的分支指令,而不是执行我们想象的 LDQ 指令。现在考虑如果偏移值为 0 美元会发生什么。如果偏移量是从 $100 处的分支指令操作码的地址开始的,那么如果执行分支,$0 的偏移量将导致无限循环。或者,如果偏移量来自地址 $102 处操作数的第二个 nybble,那么 $0 的偏移量会导致 CPU 将操作数的第二个 nybble 误解为操作码。归根结底,如果偏移量为 0 美元,那么在我们的原始指令之后立即分支到操作码是有意义的;因此,我们使用下一个操作码的地址作为“PC 中的当前值”。
只是为了确认这一切,因为它在我能看到的任何地方都没有得到很好的记录,所以我请我的新朋友,基于 6502 的虚拟现实系统的创建者 Nick Bild提供经验证明。为此,Nick 创建了如下所示的小型 6502 汇编程序:
6502 组装程序(来源:Nick Bild)
请记住,6502 有一个 8 位数据总线和一个 16 位地址总线。观察地址 $0004 处的 BNE(“如果不相等则分支”)指令。如果满足分支条件,则该指令将分支到地址 $0008 处的 JUMPHERE 标签。现在观察汇编程序生成并存储在地址 $0005 中的偏移值是 $02。当然,$0008 – $02 = $0006,也就是 LDY(“加载索引寄存器 Y”)指令的地址;也就是说,紧跟在 BNE 指令之后的操作码。量子点
绝对(又名直接):在绝对(有时称为直接)寻址模式的情况下,目标地址立即出现在操作码之后。例如,假设我们有一个名为 Q 的寄存器和一个名为 LDQ 的指令,其目的是使用绝对寻址模式将 nybble 数据加载到寄存器 Q 中,如下所示(再一次,虽然我们使用相同的助记符,但这个 LDQ与我们在前面的例子中讨论的 LDQ 指令有不同的操作码):
假设程序计数器(PC)从地址 $100 开始,CPU 读取操作码,实现此操作码使用绝对模式,并将以下三个半字节(本例中为 $426)加载到内部寄存器中。然后 CPU 使用这个内部寄存器的内容来指向内存中的数据 nybble(在这个例子中是 $F),它加载到 Q 寄存器中。我们最终将 PC 指向地址 $104,这是 CPU 期望在程序中找到下一个操作码的地方。
与我们想象的 LDQ 指令相反,假设地址 $100 处的操作码指示 CPU 使用绝对寻址模式执行无条件 JMP。在这种情况下,CPU 将跳转(设置 PC)到地址 $426。
间接:这是事情开始变得有趣的地方。假设我们有一个名为 Q 的寄存器和一个名为 LDQ 的指令,其目的是使用如下所示的间接寻址模式将 nybble 数据加载到寄存器 Q 中:
至于绝对模式,操作码后面的三个半字节包含一个地址,该地址被加载到内部寄存器中。然而,在这种情况下,地址并不直接指向数据,而是指向另一个 3 nybble 地址的第一个 nybble,而正是这个第二个地址用于指向数据。
与我们想象的 LDQ 指令相反,假设地址 $100 处的操作码指示 CPU 使用间接寻址模式执行无条件 JMP。在这种情况下,CPU 最终会跳转(设置 PC)到地址 $971。
索引(又名绝对索引):此模式与绝对模式非常相似,不同之处在于它还涉及索引 (X) 寄存器。假设我们有一个名为 Q 的寄存器和一个名为 LDQ 的指令,其目的是使用索引寻址模式将 nybble 数据加载到寄存器 Q 中,如下所示:
假设程序计数器 (PC) 从地址 $100 开始,CPU 读取操作码,实现此操作码使用索引模式,并将以下三个半字节(本例中为 $426)加载到内部寄存器中。然后 CPU 将此内部寄存器的内容添加到索引 (X) 寄存器的内容中,并使用结果指向内存中的数据 nybble(本例中为 $F),然后将其加载到 Q 寄存器中。我们最终将 PC 指向地址 $104,这是 CPU 期望在程序中找到下一个操作码的地方。
与我们想象的 LDQ 指令相反,假设地址 $100 处的操作码指示 CPU 使用索引寻址模式执行无条件 JMP。在这种情况下,CPU 最终会跳转(设置 PC)到 549 美元的地址。
索引间接:此模式反映了索引和间接模式的一种可能组合。假设我们有一个名为 Q 的寄存器和一个名为 LDQ 的指令,其目的是使用索引间接寻址模式将 nybble 数据加载到寄存器 Q 中,如下所示:
假设程序计数器(PC)从地址 $100 开始,CPU 读取操作码,实现此操作码使用索引间接模式,并将以下三个半字节(本例中为 $426)加载到内部寄存器中。然后 CPU 将此内部寄存器的内容添加到索引 (X) 寄存器的内容中以生成新地址。然而,在这种情况下,新地址并不直接指向数据,而是指向另一个 3 nybble 地址的第一个 nybble,而第二个地址用于指向将要加载到Q 寄存器。
与我们想象的 LDQ 指令相反,假设地址 $100 处的操作码指示 CPU 使用索引间接寻址模式执行无条件 JMP。在这种情况下,CPU 最终会跳转(设置 PC)到 738 美元的地址。
间接索引:此模式反映了索引模式和间接模式的替代组合。假设我们有一个名为 Q 的寄存器和一个名为 LDQ 的指令,其目的是使用间接索引寻址模式将 nybble 数据加载到寄存器 Q 中,如下所示:
假设程序计数器(PC)从地址 $100 开始,CPU 读取操作码,实现此操作码使用间接索引模式,并将以下三个半字节(本例中为 $426)加载到内部寄存器中。该地址指向另一个 3 nybble 地址的第一个 nybble,该地址本身被复制到内部寄存器中。然后 CPU 将第二个内部寄存器的内容添加到索引 (X) 寄存器的内容中,以生成一个新地址,该地址指向将加载到 Q 寄存器中的数据。
与我们想象的 LDQ 指令相反,假设地址 $100 处的操作码指示 CPU 使用间接索引寻址模式执行无条件 JMP。在这种情况下,CPU 最终会跳转(设置 PC)到地址 $BD4。
Autoincrement 和 Autodecrement:除了上面讨论的基本索引模式之外,一些 CPU 还支持 autoincrement 和 autodecrement 版本,其中索引寄存器在其内容添加到临时寄存器中的地址后递增或递减。
事实上,由于增量/减量发生在加法之后,这些模式应该更恰当地称为“后自动增量”和“后自动减量”。这是因为一些处理器还支持“pre-autoincrement”和“pre-autodecrement”,其中索引寄存器在其内容添加到临时寄存器中的地址之前递增或递减。
另请注意,所有四种自动增量和自动减量都可以潜在地应用于索引间接和间接索引模式。
我的天啊!真的吗?
我知道当我们考虑上面讨论的所有可能的寻址模式时,有很多事情需要考虑。不要恐慌!HRRG 仅支持这些模式的一个子集,以及最简单的模式——即隐含、立即、绝对和索引模式。
另一方面,我们构建 HRRG 的方式意味着它有时会在同一指令中使用多种模式。我能说什么?这是一个有趣的旧世界。
在我的下一篇专栏中,我们将开始汇总我们在寄存器和指令集专栏、指令集权衡专栏以及本专栏中介绍的所有内容,以描述 HRRG 的汇编语言和汇编程序实用程序。在此之前,我一如既往地欢迎您提出意见、问题和建议。
审核编辑 黄昊宇
评论
查看更多