堆栈计算机的原理和实现

您所在的位置:网站首页 堆栈是如何实现的 堆栈计算机的原理和实现

堆栈计算机的原理和实现

2024-06-26 12:06| 来源: 网络整理| 查看: 265

堆栈计算机的原理和实现

原书《 Stack Computers: the new wave 》

(原书封面)

原作者 Philip J. Koopman, Jr. 编译者 赵宇 张文翠

这是第一本讨论新一代堆栈计算机的书,而实现这种体系结构的第一块芯片是 Novix 公司的NC4016芯片。本书作者从堆栈如何被用于计算开始,对过去和现在大约 70 多个堆栈计算机进行分类,并对其中的7个堆栈计算机进行了详细的描述,给出了方框图和指令集,这些计算机来自Harris Semiconductor、Novix、Johns Hopkins University/APL、MISC、WISC Technologies 和 Wright State University 。本书讨论的问题还包括堆栈计算机体系结构分析、堆栈计算机的软件策略、堆栈计算机的应用领域和未来开发的前景。

本书反映了工程师和程序员对于可理解出版物的需求,他(她)们工作的领域包括:计算机体系结构、实时控制系统、控制应用中的专家系统经、计算机图形、图像处理、军事电子和任何要求体积紧凑、功能强大计算机的应用场合。

目录

第一章 简介和概览

第二章 硬件支持的堆栈计算机分类

第三章 多堆栈、 0 操作数计算机

第四章 16 位堆栈计算机系统体系结构

第五章 32 位系统的体系结构

第六章 理解堆栈计算机

第七章 堆栈计算机的软件开发

第八章 堆栈计算机的应用

第九章 堆栈计算机的未来

附录 A 具有硬件堆栈支持的计算机纵览

附录 B Forth 原语汇总

附录 C 完整的指令频率统计

附录 D 参考文献

第一章 简介和概览 1.1 概览

硬件支持的后进先出堆栈(LIFO)从50年代后期就已经开始用于计算机中。早期增加这些堆栈的目的是加快 ALGOL 一类高级语言的执行速度。从那个时候开始,堆栈在硬件设计者面前就一时儿被使用、另一时被放弃,最后在大多数计算机中变成了次要的数据处理结构。令堆栈鼓吹者大惑不解的是,把堆栈作为主要数据处理结构的计算机系统从来就没有被基于寄存器的机器设计者们所广泛接受。

随着超大规模集成电路(VLSI)技术被引入微处理器中,传统的计算机设计方法再一次受到了质疑。复杂指令计算机系统(CISC)被可理解的指令集所替代。精简指令集计算机(RISC)使用简化的处理器内核在许多应用中都达到了更高的原始处理速度。

堆栈计算机再一次被作为一种设计风格来考虑。新一代的堆栈计算机基于VLSI 设计技术,提供了传统堆栈计算机所不能提供的附加收益。这些新的堆栈计算机通过许多特性的组合,得到令人印象深刻的速度、灵活性和简单性。

堆栈计算机的复杂性比 CISC 要低得多,系统复杂性更是比 RISC 和 CISC 都低很多;堆栈计算机的高性能不需要复杂的编译器或者CACHE 硬件控制器就能够实现;它们也提供了有竞争力的原始性能,在许多编程环境中用同样的价格能够提供更高的性能;它们的第一个成功领域都是嵌入式实时控制环境,在这里它们比其它系统设计方法做得更好。此外,堆栈计算机在执行其它应用程序方面也表现出美妙的前景,这些语言包括:逻辑程序设计语言 Prolog、函数程序设计语言 Miranda 和 Scheme、人工智能研究语言 OPS-5 和 Lisp 。

新一代的堆栈计算机与旧的堆栈计算机系统相比,其最主要的差异是:旧系统把堆栈放置到程序存储器中,而新的堆栈计算机维护分离的存储器芯片,甚至把堆栈存储器放到芯片上,原因是这些巨大的、高速度的、专用的堆栈存储器现在成本极低。这些堆栈计算机提供极高速的子程度调用能力、极佳的中断处理和堆栈切换性能。把这些特点集成在一起,我们就得到了新的计算机系统:快速、灵巧、紧凑。

本章将首先讨论堆栈的运算规则,然后介绍硬件支持的堆栈计算机设计的术语,讨论一个抽象的堆栈计算机和几个商业实现,进而研究堆栈计算机的性能特点、硬件和软件考虑,最后给出几个堆栈计算机设计的未来趋势。

1.2 什么是堆栈?

LIFO 堆栈,也称为“下推栈”,从概念上讲,它是一般计算机操作中计算表达式和处理递归子程序调用时在临时存储器位置保存信息的最简单的方法。

1.2.1 自助餐厅的例子

让我们用一个生活中的例子来说明堆栈是如何工作的。回忆一下在自助餐厅中经常见到的那种有弹簧的盘子容器,假设我们给每个盘子上都贴了一个标号,任何时刻盘子都是从最上面放上去的,这时其它的盘子压缩弹簧为后续的盘子留出了空间。如图 1.1 所示, 42、 23、 2 和 9 号盘子依次放入,并且 42 号是最先放入的,而 9 号盘子最后放入。

图 1.1 一个堆栈操作的例子

最后进入的盘子编号为 9 。这样,最先出去的盘子也是 9 。假设消费者总是从顶上拿盘子,第一个被取走的盘子就是 9 号,每二个被取走的是 2 号。现在让我们考虑这个时候加入了其它的盘子,如果我们要取走早先放入的盘子,那么这些新加入的盘子必须先被拿走。在多次对这个容器进行 PUSH (装入)和 POP (取走)之后, 42 号盘子仍然在底部。如果 42 号盘子从顶端被取走,那么堆栈就再一次变成空的了。

1.2.2 软件实现的例子

LIFO 堆栈可以通过几种方法在传统的计算机上编程实现。最直接的方法就是在存储器中分配一个数组,用一个变量记录最高可用元素的数组下标。那些追求效率的程序员还可以先分配一块存储器,再使用一个指针指向栈顶元素的实际地址。不论哪种情况,“PUSH”一个堆栈元素就是在栈顶定位一个新字,然后放入数据的过程;“POP”操作就是从堆栈顶移出一个元素然后把它的值返回给所要求的子程序的过程。

堆栈通常放置在机器存储器的最高地址,它们实际是从高地址向低地址增长,这样就可以在使用存储器方面得到最大的灵活性:栈顶和程序存储器空间之间的区域都是可用的。在我们讨论的情况中,堆栈是向下还是向上增长并没有特别大的关系。“TOP”元素是最后入栈的元素,它将被最先弹出。“BOTTOM”元素是这样的元素:当它被移出后,堆栈就清空了。

堆栈有一个非常重要的特点:按最纯粹的意义上讲,它们只允许访问这个数据结构的最顶端的元素。后面我们将会看到这个特点意义深远:它使得程序紧缩、硬件简单、执行速度提高。

堆栈对于存储子程序调用中的临时数据是一个优异的机制,根本的原因是它们允许过程递归而不必考虑会不会破坏前一次访问的数据,它们也支持再入代码。作为一个附加的优点,堆栈可以在这些相同的子程序间传递参数。最后,它们通过让不同的过程使用相同的存储器空间来分配临时变量而节省空间,否则你就得在每一个过程中为临时变量分配空间。

除了数组之外,我们还可以使用其它几种软件方法创建堆栈。元素的链表可以用来分配堆栈字,而不需要知道实际存储器的地址。同样,也可以使用堆来分配堆栈空间,这也不是什么问题,因为堆管理实际上是堆栈管理的超集。

1.2.3 硬件实现的例子

用硬件来实现堆栈有明显的优点,它可以比软件实现运行得更快,在那些有大量堆栈指令的机器上,这种效率的提高对于得到系统的高性能是至关重要的。

虽然软件实现堆栈的各种方法都可以用硬件来实现,但通常的做法是:使用一片相邻的存储器并把堆栈指针指向这个区域。指针是一个专用的硬件寄存器,它可以在 PUSH 和 POP 元素时增量或者减量。有时还需要在指针上增加偏移量来访问存储器,这样就可以非破坏性地跳过前面的元素来访问指定的元素,避免一个一个地弹出这些元素。在多数情况下,堆栈与程序代码驻留在同一个存储器设备中,但有时考虑效率,堆栈也驻留在它自己的存储器件中。

另一个可行的用硬件构造堆栈的办法是使用使用大量的移位寄存器。每个移位寄存器是一个寄存器的长链,它的一端可以视为栈顶元素的一位。 32 个这样的 N 位移位寄存器排列起来就构成了 N 个元素的 32 位宽度的堆栈。不过这样的方案早期并没有实际使用,而用 VLSI 设计堆栈计算机时,不仿把它作为与传统的基于寄存器不同的方案来考虑。

1.3 为什么堆栈计算机如此重要?

从理论上讲,堆栈本身是非常重要的,因为堆栈是处理“结构良好代码”的最基本和最自然的结构 (Wirth 1968) 。有 LIFO 堆栈的机器也是编译计算机语言所需要的,可能也是翻译自然语言所需要的( Evey 1963) 。任何拥有硬件支持堆栈的计算机能够在执行那些要求堆栈结构的应用程序时更快。

有些观点认为,对堆栈计算机进行编程比传统计算机编程更容易,堆栈计算机程序也比其它程序运行得更可靠(McKeeman 1975) 。为堆栈计算机编写编译程序会更容易,因为对于编译器来说,它们很少有例外的情况,而正是那些例外情况使编译器变得非常复杂( Lipovski 1975 )。由于在许多情况下,运行编译器会占用大量的计算机资源,所以能够构造一个高效编译器的计算机也是非常重要的。

正如我们在本书中将要看到的,堆栈计算机在运行某些特定类型的程序时比基于寄存器的计算机更高效,特别是那些模块化良好的程序。堆栈计算机也比其它类型的计算机简单,能够以很少的硬件提供强大的计算能力。堆栈计算机一个特别值得提及的应用领域是实时嵌入式控制应用,这种应用要求这样的组合:小尺寸、高处理速度和对中断处理的良好支持,只有堆栈计算机才能够同时提供所有这些能力。

1.4 为什么在计算机中使用堆栈?

硬件和软件堆栈已经用于支持4个主要的计算领域:表达式计算,子程序返回地址存储,动态分配局部变量存储器和子程序参数传递。

1.4.1 表达式计算堆栈

表达式计算堆栈是被硬件广泛支持的第一种堆栈,在编译器解释一个算术表达式的时候,它必须使用一个表达式堆栈来记录操作的中间步骤和优先级。如果是解释执行的语言,则必须使用两个堆栈:一个堆栈包含等待更高优先级的未决操作,另一个堆栈保存与这些未决操作符对应的中间输入。在编译语言中,编译器在它的指令生成过程中保持对未决操作的跟踪,硬件使用单个的表达式计算堆栈保持中间结果。

要明白为什么堆栈适合表达式计算,考虑下面的表达式是如何计算的:

X = (A + B) * (C + D)

首先, A 和 B 应该相加;接着,中间结果必须保存到什么地方,比如可以被推入表达式计算堆栈;接下来, C 和 D 相加的结果也进入表达式计算栈。最后,两个栈顶元素 (A+B) 和 (C+D) 相乘并把结果写入 X 。表达式计算栈提供了表达式中间结果的自动管理能力,允许表达式中包含有与可用堆栈元素一样多级别的处理。如果读者使用过 HP 的使用逆波兰表示法的计算器,那么应该对表达式计算栈有直接的印象。

表达式计算栈在表达式计算中是非常基本的,所以甚至在基于寄存器的机器中,编译器也常常按表达式计算栈那样的方式来分配寄存器。

1.4.2 返回地址栈

上个世纪 50 年代,递归被作为语言的一个必要的特性而提出,这时就需要一种方法来动态分配存储器以保存子程序的返回地址。当时的问题是:在像 FORTRAN 一样的非递归语言中,保存子程序返回地址的方法是在子程序体内部分配一片空间。这当然就阻止了一个子程序直接或者间接地调用它自己,因为以前保存的地址会丢失。

递归问题的解决方法是使用堆栈来存储子程序的返回地址。因为每个子程序被调用的时候,机器都会把调用程序的返回地址保存到堆栈上,这就保证了子程序的返回地址是按递归所要求的顺序处理的。因为新的元素在每个子程序调用时自动分配,子程序可以毫无问题地调用它自己。

现代计算机都使用某种类型的硬件支持返回地址堆栈。在传统的机器上,这种支持常常表现为堆栈指针寄存器、执行子程序调用和子程序返回的指令。返回地址栈通常保存在程序存储器中不用的区域中。

1.4.3 局部变量堆栈

在进行递归时又出现了另一个问题:对本地局部变量的管理。这个问题在允许再入(相同的代码被不同的线程多次使用的可能性)就更为明显。我们再看看旧的语言,比如像 FORTRAN 中,它对一个子程序信息的管理就是简单地在子程序代码中分配不变的存储区域。这种静态分配存储器的方法对那些既不重入也不递归的程序是合适的。

然而,只要一个子程序可能被多个线程并发访问或者可能被递归调用,在过程中静态地定义局部变量就几乎是不可能的,一个正在执行的线程的变量值很容易被另一个竞争的线程改变。最常用的解决方法就是在局部变量堆栈上分配空间,每次子程序调用都在局部变量堆栈上分配新的存储器块、为子程序创建工作空间,甚至在只使用寄存器保存临时变量的情况下,调用子程序也需要某种局部变量堆栈,这种堆栈可以在寄存器的值被破坏之前保存它们。

本地变量堆栈不仅允许再入和递归,而且也能够节省存储器。在使用静态方式分配存储器的子程序中,不管子程序是否活动,变量都将占用空间,而使用局部变量堆栈后,堆栈上的空间在子程序调用时能够伴随着堆栈的深度增加或者减少而被再次使用。

1.4.4 参数堆栈

计算机中使用堆栈的最后一个领域是子程序参数栈。不论什么时候,当一个子程序被调用时,还必须给出一系列操作参数,这些参数值可以放在寄存器中,但这种方法有一个缺点,就是受可用寄存器数量的限制。也可以在调用子程序时把参数的值拷贝到或者通过指针指到一个列表中,但是这样做了之后,重入和递归就不可能了。最灵活的方法就是在子程序调用之前简单地复制元素到参数栈上,参数堆栈可以在程序中实现递归和再入。

1.4.5 综合堆栈

实际的机器组合了不同的堆栈类型,在基于寄存器的机器中,最常见的就是把局部变量栈、参数栈和返回地址栈组合到活动记录或者称为“帧”的单一堆栈中。在这些机器里,表达式计算栈被编译器省略,取而代之的是通过寄存器分配来执行表达式计算。

本书后面所描述的堆栈计算机,其使用堆栈的方法是分离硬件表达式计算栈和返回栈,表达式计算栈也被用来传递参数和进行局部变量分配。有时,特别是当传统语言比如C或者 Pascal 执行时,使用帧指针寄存器指出程序存储器区域的局部变量。

1.5 新一代的堆栈计算机

新一代的堆栈计算机是本书讨论的焦点,它们继承了堆栈计算机设计的丰富历史经验,同时利用了 VLSI 新的工艺技术。这种组合提供了过去所有类型计算机所缺乏的简单性和灵活性,这些特点产生的结果以及它们与传统设计最大的区别是:多个带有硬件缓冲区的堆栈、零操作数基于堆栈的指令集和快速过程调用的处理能力。

这些设计特点在最终的机器中又产生了一系列的特点,包括:不需要使用流水线就能够达到极高的性能,系统复杂度极低,程序代码尺寸小,程序执行速度快,中断响应开销小,在所有时间度量中有一致的程序执行速度,很低的上下文切换开销。其中一些结论是很明显的,而有些结论却与传统计算机体系结构中所公认的智慧完全相反。

这种堆栈计算机设计可的大多数都有它们的根:Forth 程序设计语言,这是因为对这些有两个堆栈的堆栈计算机来说, Forth 可以同时作为高级语言和汇编语言:一个堆栈用于表达式计算、参数传递,另一个堆栈用于保存子程序调用的返回地址。从某种意义上讲, Forth 语言实际上定义了一个基于堆栈的计算机体系结构,它被主处理器执行 Forth 程序时所模拟。 Forth 语言和硬件设计的相似性不是偶然的,这些堆栈计算机的成员毫无例外地都被设计成、并且允许有 Forth 程序设计背景的人使用。

需要注意的一个有趣点是:尽管一些机器最开始是设计用来运行 Forth 语言的,但它们也能够很好地运行传统语言。这样,既使它们不能被选择用来替代个人计算机或者工作站中的核心处理器,但却可以实际地用于许多传统语言的开发、应用和编程中。最有趣的是:这些应用都关注堆栈计算机特有的优点:小的系统尺寸、良好的外部事件响应、有效的硬件资源高效率使用等。

1.6 本书的内容包括

所有类型的堆栈处理器都可以按堆栈的数目、专用堆栈存储器的大小、指令中的操作数来进行分类。本书讨论的堆栈计算机都是多个堆栈、0 操作数寻址的计算机。堆栈缓冲存储器的大小是设计时根据系统成本和操作速度来拆衷考虑的,所以本书中的“堆栈计算机”都是指的这类机器。

堆栈计算机都具有程序代码尺寸小、系统性能高、在不同的条件下一致的高性能等特点。堆栈计算机运行传统语言编写的程序时性能也相当好,而达到这种性能所使用的硬件却比基于寄存器计算机达到同样性所需要的硬件要少。

堆栈计算机在运行 Forth 语言时表现极好。 Forth 是以它的交互性、灵活性和快速程序开发能力而著称的,Forth 语言能够产生非常紧缩的代码,所以特别适合于实时控制问题。

本书详细讨论了 4 个 16 位堆栈计算机,在进行设计时所做的的折衷考虑包括集成度和速度,包括 WISC CPU/16 、 MISC M17 、 Novix NC4016 和 Harris RTX 2000。

本书的 3 个 32 位堆栈计算机在设计时考虑了更多的问题,它们是是:Johns Hopkins/APL FRISC 3 ( 也称 Silicon Composers SC32) 、 Harris RTX 32P 和 Wright State University 的 SF1.

理解堆栈计算机要求收集和分析大量的资料,并与基于寄存器的计算机进行比较。目前可以使用的比较标准包括:大约 1000 万 Forth 动态和静态指令的执行频率,在RTX30P上同一指令中组合操作码和子程序调用的影响,堆栈尺寸要求,堆栈溢出管理策略,处理大量中断和上下文切换时性能的下降程度。

为堆栈计算机选择软件需要考虑许多因素。大量地使用传统语言编写的程序应该能相当有效地在堆栈计算机上运行,特别是如果对经常使用的代码段进行小的修改就更好了。堆栈计算机一个很好的应用领域是嵌入式实时控制,这个应用领域是计算机应用的一个主要部分。当然,我们也讨论了其它感兴趣的领域。

对于堆栈计算机来说,未来硬件和软件的努力方向可能包括提高传统程序语言在堆栈计算机上的执行效率,也包括硬件不必再像其它处理器那样受限于存储器带宽的不良影响等。

第二章 硬件支持的堆栈计算机分类

从历史上看,在为支持高级语言处理而提供大量硬件支持的计算机设计可包含有很多对堆栈硬件的支持,这些支持从硬件指针寄存器到 CPU 单元内的多个硬件堆栈存储器。有两类新的处理器对用硬件支持堆栈表现出了特别的兴趣:一类是 RISC 处理器,它经常需要把大的寄存器文件作为堆栈看待;另二类面向实时控制的处理器,它使用堆栈指令以减少程序大小并降低处理器的复杂性。

理解基于堆栈计算机的重要一步是对它们进行分类。好的分类方法可以更明确地考察全局设计拆衷,而不是陷入某个特殊机器的细节当中,分类也可以帮助我们理解为什么在一个已有的设计中使用某种体系结构。从分类开始我们的讨论,还有一个目的就是:在面对多堆栈、0 操作数的机器之前,先了解一下更多的处理器类型。

在 2.1 中我们将基于3个属性来描述堆栈计算机的分类:堆栈的数量、堆栈缓冲区大小、指令中操作数的数目。我们也将讨论每一类系统的优点和缺点。

在 2.2 中我们将按这种分类来讨论现有的堆栈处理器,在 2.3 中我们将讨论每一类结构中各处理器的相似和差异,这种相似和差异能够帮助我们思考堆栈计算机设计决策。

2.1 堆栈设计空间的三个方向

图 2.1 堆栈设计空间的三个方向

堆栈计算机的设计空间可以按图 2.1 的坐标图进行分类,图中的3个坐标是:硬件支持堆栈的数量、堆栈元素专用缓冲区的大小、在一个指令中允许多少个操作数。

虽然从某些方面看,3 维可以表示一个连续体,但从我们的分类目的考虑,它们应该按3个可能的取值分为 12 个类别:

堆栈的数量 = 单个S或者多个M

堆栈缓冲区的尺寸 = 小S或者大L

操作数的多少 = 0 或者 1 或者 2 个

2.1.1 单个堆栈和多个堆栈

堆栈支持功能最明显的例子就是用单一堆栈支持子程序返回地址,这个堆栈也常常用于传递参数给子程序。有时也附加一个或者多个堆栈,在不影响参数列表的情况下处理子程序调用,或者在一个与调用信息分离的表达式堆栈上处理值。

单一堆栈的计算机只有唯一的一个由指令集支持的堆栈,这个堆栈主要用于保存子程序调用和中断信息,也用于表达式计算。不管哪种情况,它都被某种语言的编译器用于传递子程序参数。通常单一的堆栈使得硬件简单,但是把数据参数与返回地址信息混合在一起的代价却是高昂的。

单一堆栈的优点是能够简化操作系统,操作系统的每个进程只管理一块可变尺寸的存储器。为结构程序设计语言而设计的机器通常只使用一个堆栈组合子程序参数和子程序返回地址,并常常配合使用某种类型的帧指针机制。

单一堆栈的缺点是参数和返回地址不得不互相嵌套。如果模块化软件设计技术要求参数列表元素通过多个软件接口层进行传播,则把它们多次复制到新的活动记录中就要有额外的开销。

多堆栈计算机有两个或者多个由指令集支持的堆栈,一个堆栈通常专用于存储返回地址,其它的堆栈则用于表达式计算和子程序参数传递。多个堆栈允许控制流信息与数据操作数分离。

在参数栈与返回地址栈分离的情况下,软件可以通过几个子程序层传递参数而不需要把数据复制到新的参数列表中。

多个堆栈的一个重要优点是速度。多个堆栈可以在一个时钟周期内访问多个值。例如,如果一个机器能够同时访问数据栈和返回地址栈,则它就能够在进行数据操作的同时,并行地执行子程序调用和返回。

2.1.2 堆栈缓冲区的大小

专门用于缓冲堆栈元素的存储器的大小对于性能至关重要,实际的实现方法包括使用程序存储器存储堆栈元素、在处理器可设置几个栈顶元素寄存器、拥有完全分离的堆栈存储器单元。这里的分类包括几乎完全驻留在程序存储器中(可能有几个缓冲元素在 CPU 中)和提供高效堆栈缓冲区的设计。

使用小尺寸堆栈缓冲区的体系结构典型地把堆栈视为通用程序存储器地址空间的一个分区。堆栈使用同样的存储器子系统为指令和变量使用,在需要时还可以使用常规的存储器访问指令去访问堆栈操作数。堆栈的元素也可以通过指向存储器的堆栈指针或者帧指针加上一个偏移量来访问。

为了提高运行速度,堆栈计算机必须至少有一个或者两个堆栈元素缓冲在处理器之中。为了明白其中的原因,我们可以考虑一个没有缓冲元素机器上的加法操作:,一个单一的加法指令使用三个以上的存储器周期以访问两个操作数和保存结果。如果有两个元素都在堆栈缓冲区里,加法只有一个存储器周期。这个周期用于读取新的次栈顶元素以填充加法所消耗的堆栈参数。

如果用一个小的堆栈缓冲区配合驻留在程序存储器中的原始堆栈,则我们就可以在不同任务的堆栈之间快速切换,因为堆栈元素早就存在于存储器里了。

小尺寸专用堆栈缓冲区易于实现和管理,所以它的应用也非常普遍。大多数据元素驻留在主存储器中也使得管理指针、串和其它数据结构非常容易。这种方式的缺点是,大量的存储器带宽都被堆栈元素的读写消耗掉了。

如果一个体系结构拥有足够大的堆栈缓冲区,则访问堆栈元素时通常不消耗主存储器的带宽。这种“拥有足够大堆栈缓冲区”的体系结构使用以下几种结构之一:它可以是一系列的寄存器,通过一个寄存器窗口来访问,比如 RISC I ( (Sequin & Patterson 1982) ;与程序存储器隔离的一个单独的存储器单元;或者是在处理器中的一个专用的堆栈存储器 CACHE ( Ditzel & McLellan 1982 )。

一般来说,如果几级子程序调用(比如说 5 级或者更多)还没有用尽全部的堆栈存储器,我们就可以说堆栈缓冲区“足够大”。如果堆栈是用来作运算堆栈使用的,则大约有 16 个元素就可以认为是“足够大”的,因为单个表达式一般都不会太复杂。在第六章中,我们将对一些程序的执行情况进行统计,给出“多大才是足够大”这个问题一个更明确的答案。

使用大尺寸堆栈缓冲区的一个优点是在访问数据元素和子程序返回地址时不会占用程序存储器周期,这可以明显地提高程序执行速度,特别是对于子程序调用敏感的程序。

使用分离堆栈存储器单元的一个缺点是它的大小可能不足以满足所有应用程序的需要。这种情况下,可能需要在新堆栈元素加入时分裂数据到程序存储器中。另外,在一个多任务环境中,进行上下文切换时要保存全部的堆栈存储器,这种开销可能是不允许的,当然可以考虑把堆栈存储器按任务分配。从更低的层次看,把片外堆栈存储器和程序存储器分离将要增加一些管脚,这对于微处理器来说是比较昂贵的。

很明显,这里对“大”和“小”的描述比较模糊,但在实际设计时,这些对于设计者而言却通常是明确的。

2.1.3 0- 、 1- 和 2- 操作数寻址

初看起来,一个机器指令中操作数的个数对于硬件支持的堆栈计算机来说没有什么用。然而实际上,寻址模式的数目对堆栈如何构成和如何用于程序设计有很大的影响。

0- 操作数据指令不允许在操作码中含有任何操作数,所有的操作都隐含指定堆栈上的操作数,这种寻址方式一般称为“纯”堆栈寻址。

当然, 0- 操作数体系结构必须使用它自己的堆栈用于表达式计算机。

就是在纯堆栈的机器中,也必须有几个指令指定地址,用于在程序存储器中装入和存储变量、装入文字量(常数)值、子程序调用和条件分支。这些指令一般使用非常简单的格式,通常使用位于操作码后面的一个存储字来保存操作数。

简单的 0- 操作数指令有几个优点。由于一个指令每次只引用栈顶的一个或者两个位置,我们可以简化堆栈存储器的结构,因为使用一个或者两个栈顶寄存器后就可以使用单端口的堆栈存储器。另一个优点是速度,由于每个指令的操作数都是栈顶元素,我们可以在指令译码的同时并行地装入操作数寄存器。这就可以完全省略取指和存储操作数流水线。

另一个优点是每个指令能够做到极其紧缩, 8 位指令格式就可以满足 256 个不同操作码的需要。更进一步,指令译码也被简化,译码硬件不需要解释操作数的寻址模式。

0- 操作数寻址模式的一个缺点是用于数据结构访问的复杂寻址模式需要几个指令组合才能实现。如果要访问埋在堆栈内部很深的数据元素也很困难,除非事先就提供了一个“复制第 N 个数据元素”的操作指令。

1- 操作数指令计算机通常指定一个操作数,并使用栈顶元素作为第二个隐含操作数。 1- 操作数寻址也称为堆栈/累加器寻址,比 0- 操作数更灵活,因为它组合了操作数读取和堆栈操作。

Keedy (1978) 结论说,堆栈/累加器体系结构与纯堆栈体系结构相比,前者可以使用更少的指令进行表达式计算。他的论据是所有 1- 操作数指令的程序都比 0- 操作数设计要短。当然这其中也存在折衷,由于一个操作数是由指令指定的,为了有效地访问操作数,必须采用两种方法之一:或者有一个操作数读取流水线,或者使用更长的时钟周期。如果一个参数在子程序参数栈或者计算栈上时,堆栈存储器的寻址必须使用操作数的偏移量方式来读取元素。这与栈顶元素预取并等待操作相比,需要更多的执行时间或者更多的流水线硬件。

1- 操作数堆栈体系结构几乎总是有一个表达式计算栈,许多1- 操作数体系结构也支持0- 操作数寻址模式以便在不使用操作数字段时节省指令位。

2- 操作数指令格式,包括了为了本书分类目的,我们也把 3- 操作数指令作为其特例,允许每个指令同时指定源和目的。如果堆栈仅仅用来存储返回地址,则 2- 操作数计算机就简化成为通用寄存器计算机。如果子程序参数是通过堆栈传递的,则 2- 操作数机器或者指定相对于堆栈或者帧的偏移量或者指定用于操作的当前寄存器窗口的一个寄存器对。 2- 操作数指令计算机不需要表达式计算栈,但是它把跟踪表达式中间结果的负担交给了编译器。

2- 操作数机器提供了最大的灵活性,但是,为了提高效率却需要更复杂的硬件。因为在一个指令译码之前不可能知道操作数,所以必须使用数据流水线和双口寄存器文件向执行单元提供操作数。

2.2 分类的表达方法 2.2.1 符号

为了方便讨论,我们使用基于3个分类坐标轴字符缩写来记录一种体系结构。第一个字母缩写指定堆栈的数量(S = 单一, M = 多个),每二个字母缩写表示专用堆栈存储器的大小(S = 小, L = 大),第三个是数字,用来表示指令中操作数的多少(0、1、2 )。于是 SS0 就是指这样的体系结构,它有单一的堆栈、小尺寸的专用堆栈存储器和 0- 操作数寻址模式。 ML2 则指定多个堆栈、大尺寸专用存储器和 2 操作数寻址模式的体系结构。

2.2.2 列表

表 2.1 按我们的分类方式给出了当前已经存在的和历史曾经有过的基于堆栈作体系结构计算机的目录。附录 A 简单地讨论了每一种结构和它们实现的特点。

表 2.1 堆栈计算机分类

分 类

计 算 机

SS0

Aerospace Computer, Burroughs family, Caltech Chip,EULER, GLOSS, HITAC-10, ITS, LAX2, Mesa, Microdata 32/S, Transputer, WD9000

SS1

AAMP, Buffalo Stack Machine, EM-1, HP300/HP3000, ICL2900, IPL-VI, MCODE, MU5, POMP Pascal

SS2

Intel 80x86

SL0

G Machine, NORMA

SL1

AADC, Micro-3L

SL2

AM29000, CRISP, Dragon, Pyramid 90x, RISC I, SOAR

MS0

Action Processor, APL Language Machine, FORTRAN Machine, HUT, Internal Machine, MISC M17, Rockwell Microcontrollers, Symbol, Tree Machine

MS1

PDP-11

MS2

Motorola 680x0

ML0

ALCOR, An ALGOL Machine, FRISC 3, KDF-9, Kobe University Machine, MF1600, NC4016, OPA, PASCAL Machine, QForth, Reduction Language Machine, Rekursive, RTX 2000, RTX 32P, RUFOR, The Forth Engine, TM, Vaughan & Smith's Machine, WISC CPU/16, WISC CPU/32

ML1

Lilith, LISP machines, SF1, Soviet Machine

ML2

PSP, SF1, Socrates

2.3 分类之后我们感兴趣的方面

可能最令我们惊奇的是在分类空间中的全部 12 种处理器都有设计实现,这说明不同的堆栈体系结构都已经被大量地研究了。另一个特点是不同的机器类型都趋向于把操作数轴作为主要的设计参数,每个设计组中的差异是堆栈缓冲区的大小和数量的不同。

0- 操作数寻址模式是“纯”堆栈计算机。一点儿都不奇怪,这类系统有最学院化的和最概念化的设计项目,因为它们包括规范化的堆栈计算机形式。由于其固有的简单性,SS0 机器通常用于硬件资源受限、设计周期受限或者两者都受限制的情况。在 SS0 设计中,如果不提供有效的深堆栈元素复制的方法,并且还要把返回地址和数据元素交错地存放在堆栈上,就会产生效率问题。

SL0 类系统看来只能用于 combinator graph reduction 应用 (一种执行函数式程序设计语言应用的技术,见 Jones 1987) ,这种应用执行树的遍历,并在执行遍历时使用堆栈来存储节点的地址。不需要表达式计算机栈,因为结果是存储在树存储器本身中的。

MS0 和 ML0 非常相似,其主要区别是片上或者板上缓冲堆栈元素的存储器的数量。所有的 Forth 语言处理器和其它许多高级语言的处理器都属于这个范围。这些机器在实时嵌入式控制领域中非常有用,当然是因为它们的简单性、高速处理和小尺寸的程序代码(Danile & Malinowski 1987, Fraeman et al. 1986) 。许多 MS0 和 ML0 设计允许极快甚至是0周期的子程序调用和返回。

1- 操作数寻址的设计是试图打破 0- 操作数设计的瓶颈,方法就是把纯堆栈模型转变为堆栈/累加器模型。 SS1设计比 SS0 设计更容易使用地址或者帧指针访问本地变量。通常,1- 操作数设计的一个明显优点是 PUSH 操作可以与算术操作组合,在某些环境下节省指令。另外,由于 P-code 和 M-code 的性质, Pascal和Modula-2 机器都使用 1- 操作数寻址。

2- 操作数寻址模式是更主流的设计,传统的处理器归类为 SS2 。由于使用了寄存器窗口设计, RISC 可以归类为SL2 ,但此外就再也没有其它的设计可以归于这一类了。 MS2 分类有 MOTOROLA 的 680x0 家族,它反映了这种机器的灵活性,可以使用8个地址寄存器中的任何一个作为堆栈指针。 ML2 机器中的 PSP 机器反映了这样一种努力,其概念设计包含一个寄存器窗口而极大地提高了子程序的调用速度。SF1 机器也使用多个堆栈,但在实时控制环境中,每个激活的进程都使用专门的硬件堆栈。

从以上讨论我们可以看出,计算机设计可以分为我们划定的12个类别,每个类别中的不同设计都表现出了很强的相似性,而不同类别之间的设计却在有着很大的差异,这些差异影响系统的实现和操作。这样,分类就是考察面向堆栈计算机属性的一个有用的工具。

在下一章中,我们将把焦点放在堆栈计算机设计空间的特定部分:MS0和ML0 ,以后当我们使用“堆栈计算机”或者“堆栈处理器”这些术语时,就是专门指 MS0 和 ML0 计算机。

第三章 多堆栈、 0 操作数计算机

本章主要讨论按每二章分类的、属于MS0和ML0类的多堆栈、0- 操作数计算机。

在3.1中,我们将比较堆栈计算机与传统的 CISC 和 RISC 体系结构之间的区别。

在3.2中,我们将描述一个被称为 Canonical Stack Machine (规范堆栈计算机)的原型堆栈计算机结构,我们将给出方框图和实现的指令集。这种两堆栈的机器可以作为后面各章的真实堆栈计算机的起点。

在3.3中,我们将简单地讨论 Forth 程序设计语言。 Forth 是一种非传统的计算机程序设计语言,它使用一个双堆栈计算模型,鼓励使用很多的短小过程调用。许多 ML0 和 MS0 设计都发源于 Forth 语言,当然也非常适合于用 Forth 进行程序设计。

3.1 我们为什么对这类计算机感兴趣?

多堆栈、 0- 操作数计算机与其它计算机相比,有两个固有的优点: 0- 操作数寻址方式使指令尺寸最小,多个堆栈允许子程序调用和数据处理并发进行。这些特点和其它的特点使得程序代码短小、系统复杂度低、系统性能高。 MS0 和 ML0 之间的主要差异是 MS0 为了降低 CPU 的成本而使用了最少的资源来构造堆栈缓冲区,当然这样做也同时舍弃了一些性能。

我们将在第六章讨论堆栈计算机如何得到这些优点,现在,我们考察的是得到这些优点背后的细节。

首先,让我们汇总一下这些优点。

堆栈计算机通过两方面来缩小程序大小:一是通过鼓励更多地使用子程序来减少代码的大小,二是基于堆栈计算机指令短小的事实。小的程序尺寸减少了存储器成本、元件的数量和功耗,通过使用成本更有效的、更小的、更高速的存储器芯片来提高系统的速度。附加的优点包括在虚拟存储环境下更好的性能,几乎不需要为提高命中率而设计CACHE 。 0- 操作数计算机比其它机器有更小的代码尺寸。

降低系统复杂度也就减少了开发时间,也减少了芯片尺寸。这也为片上程序存储器和半定制特性留下了更多的芯片面积。

一个系统的性能不仅仅是指原始的执行速度,同时也包括整个系统的性能和系统在实际真实世界中的适应性。系统性能中的速度也不仅仅是指每秒钟可以执行多少条线性指令,更应该考虑分支和过程处理导致的性能降低。在堆栈计算机中, 0- 操作数寻址模式和更多地使用子程序调用以减少代码尺寸和系统复杂度的实际结果是:为应用程序改进了系统性能。

堆栈处理器支持高效率过程调用的另一个附加好处是从体系结构上鼓励程序员使用许多小的过程而改进了代码结构,通过鼓励更好的编码习惯而提高了可维护性,通过把小的子程序当成构建块来使用而提高了代码的可重用性。

3.2 一个原型化的规范堆栈计算机

在涉及真实的 MS0 和 ML0 设计细节之前,我们需要建立一定的基准,所以我们将考察一个规范的 ML0 计算机设计。这个设计尽可能地简单以便作为比较其它设计的共同出发点。

3.2.1 方框图

图 3.1 是规范堆栈计算机的方框图。图中的每个框代表了与 ML0 最基本组件对应的逻辑资源。这些组件是:数据总线,数据堆栈 DS ,返回堆栈 RS ,带有栈顶寄存器 TOS 的算术逻辑计算单元 ALU,程序计数器 PC,有指令寄存器 IR 的控制逻辑,以及输入输出部分 I/O 。

图 3.1 规范堆栈计算机

3.2.1.1 数据总线

为简单起见,规范堆栈计算机只有一个单一总线连接系统的所有资源。真实的处理器可以使用多条数据总线以便于指令读取和计算并行操作。在规范堆栈计算机中,数据总线在任何单操作周期中都允许单个的发送功能模块和单个的接收功能模块。

3.2.1.2 数据堆栈

数据堆栈是一个使用内部机制实现 LIFO 堆栈的存储器。通常的实现方法是使用传统的存储器配合一个增/减计数器用来产生存储器地址。数据堆栈允许两个操作:PUSH 和 POP 。 PUSH 操作在堆栈顶部分配一个新的单元,把数据总线上的值写入这个单元。 POP 操作把堆栈顶部的元素放到数据总线上,然后删除这个单元,把堆栈上的下一个元素露出来以便进行下一次操作。

3.2.1.3 返回栈

返回栈是使用与数据栈相同的方法实现的一个 LIFO 堆栈。唯一的区别是返回栈用于存储子程序的地址而不是指令的操作数。

3.2.1.4 ALU 和栈顶元素寄存器

ALU 功能块对两个数据元素执行算术和逻辑计算,两个数据元素中的一个是栈顶元素寄存器 TOS ,它保存着给程序员使用的数据堆栈上最顶端的元素。这样,实际的数据模块顶端元素是程序员可以见到的每二个元素,因为第一个元素是在 ALU 的一个寄存器 TOS 中。这种策略可以保证在对堆栈上的两个元素进行操作时,比如加法,能够使用单端口数据堆栈存储器。

ALU 支持任何计算所需要的原语操作。为了说明方便,只包括加法、减法、逻辑功能( AND 、 OR 、 XOR )、零测试。由于这里只是概念设计,所以算术操作都是整数。当然没有任何理由说不能给 ALU 扩展浮点算术操作。

3.2.1.5 程序计数器

程序计数器保存着将要执行的下一个指令的地址。 PC 可以从数据总线装入以实现分支,也可以在程序存储器顺序取下一个指令的时候增量。

3.2.1.6 程序存储器

程序存储器模块包括一个存储器地址寄存器(MAR)和一定数量的随机访问存储器单元。为了访问存储器, MAR 首先写入要读出或者存入的地址,然后在下一个系统周期,程序存储器单元或者从数据总线读入或者向数据总线写出数据。

3.2.1.7 I/O

和许多概念设计一样,我们对输入输出的讨论也将是一带而过的。这里能说明的只是 I/O 也是系统资源, I/O 模块处理这个任务,不过,说出这些也就足够了。

3.2.2 数据操作

表 3.1 给出了规范堆栈计算机的最小操作指令集,选择这样一个操作指令集的目的是解释计算机的使用 -- 很明显,对于高效率的程序执行并不是足够的。实际上我们没有包括乘法、除法和移位操作,原因还是为了简化。表述的方法参看 Forth 语言(见 3.3 ),这也是我们后面各章讨论时用到、被广泛使用的表达方法。值得注意的是, Forth 经常使用一些特殊的字符,比如用!(在 Forth 中读作“存储”)和 @( 在 Forth 中读作“读取” ) 。

表 3.1 规范堆栈计算机的指令集

指令

数据堆栈 输入 -> 输出

指 令 功 能 描 述

!

N1 ADDR ->

存储N1到程序存储器ADDR位置

+

N1 N2 -> N3

N1 和 N2 相加,结果为 N3

-

N1 N2 -> N3

N1 减去 N2 ,结果为 N3

>R

N1 ->

N1 进入返回栈

@

ADDR -> N1

读取程序存储器 ADDR ,返回 N1

AND

N1 N2 -> N3

N1 和 N2 位与,结果为 N3

DROP

N1 ->

从堆栈上去除 N1

DUP

N1 -> N1 N1

复制 N1

OR

N1 N2 -> N3

N1 和 N2 按位或,结果为 N3

OVER

N1 N2 -> N1 N2 N1

把第二个元素 N1 复制到栈项

R>

-> N1

弹出返回栈顶元素放到数据栈上

SWAP

N1 N2 -> N2 N1

交换栈顶两元素的顺序

XOR

N1 N2 -> N3

N1 和 N2 位异或,结果为 N3

[IF]

N1 ->

如果 N1 为假(值为 0 ),则执行分支(地址在存储器的下一个单元)否则继续

[CALL]

->

执行子程序调用地址在下个单元

[EXIT]

->

执行子程序返回

[LIT]

-> N1

把程序存储器的下一个单元视为 整数常量,把它放到栈项

3.2.2.1 逆波兰表示法

堆栈计算机使用后缀表示法执行数据处理操作符,这些操作符通常称为“逆波兰表示法( RPN )”。后缀操作的最明显特征是操作数在操作符之前。例如,传统的表达式(中缀)表示这样的内容:

(12 + 45) * 98

在这个表达式中,使用括号来强制加法在乘法之前计算。甚至在没有括号的表达式中,也有隐含的操作符优先级规定,例如,没有括号的乘法应该在加法之前计算。上面加了括号的表达式写成等效的后缀形式应该是:

98 12 45 + *

在后缀表示中,操作符对最近可见的操作数进行操作,隐含使用一个堆栈用于计算。在这个后缀的例子中,数 98 、 12 和 45 如图 3.2 所示推入堆栈,然后 + 操作对栈顶的两个元素(数 25 和 12 )运算得结果 57 ,最后 * 操作对两个新的栈顶元素 57 和 98 操作,结果是 5586.

图 3-2 逆波兰表示法的例子

后缀表示法与中缀表示法相比,有一个很经济的地方,这就是它不需要任何操作符优先级,也不需要任何括号,它非常适合计算机的需要。事实上,编译器都是把 C 或者 FORTRAN 语言中的中缀表达式翻译成后缀机器代码的,只是有时用显式的寄存器分配来代替表达式堆栈。

前面描述的规范堆栈计算机被设计成直接执行后缀操作符,不需要编译器处理寄存器分配。

3.2.2.2 算术和逻辑操作符

为了完成基本的算术运算,规范堆栈计算机要求算术和逻辑运算符,下面对每一条指令进行讨论,并使用寄存器传输级伪码进行描述,它们将是自解释的,比如,第一个运行符是加法:

运算符

+

堆栈

N1 N2 -> N3

描述

把 N1 和 N2 相加,结果为 N3

伪码

TOSREG N3

描述

把 N1 和 N2 相减,结果为 N3 差

伪码

TOSREG N3

描述

把 N1 和 N2 相逻辑与,结果为 N3

伪码

TOSREG N3

描述

把 N1 和 N2 逻辑或,结果为 N3

伪码

TOSREG N3

描述

把 N1 和 N2 相逻辑异或,结果为 N3

伪码

TOSREG

描述

从堆栈上去除 N1

伪码

TOSREG R R> ?DUP R@ @ ROT ABS S->D AND SWAP BRANCH U* D! U/MOD D+ XOR D@

表 4.1(a) CPU/16 指令集 -- Forth 原语 (参看附录B的描述)

@ + @ - DROP ; DROP DUP I + I + @ OVER + OVER - R> DROP R> SWAP >R SWAP - SWAP DROP DUP @ SWAP 1+ (取并增量地址) DUP ROT ROT ! 1- (存并增量地址) @ @ (间接取) @ ! (间接存) DUP @ @ 1 ROT +! (为软件堆栈自动后增量间接取) -1 OVER +! @ ! (为软件堆栈自动前增量间接存)

表 4.1(b). CPU/16 指令集汇总 -- 组合的Forth原语

操作码 数据堆栈 返回栈

DOCOL -> -> ADDR 执行子程序调用 SEMIS -> ADDR -> 执行子程序返回 HALT -> -> 把控制返回给主机处理器 SYSCALL N -> -> 从主机上请求代号为N的I/O 服务 DOVAR -> ADDR -> 用于实现 Forth 变量 DOCON -> N -> 用于实现 Forth 常量

表 4.1(c) CPU/16 指令集汇总 -- 特殊字

下列Forth 操作都有微码支持内层循环或者运行时间行为 SP@ (fetch contents of data stack pointer) SP! (initialize data stack pointer) RP@ (fetch contents of return stack pointer) RP! (initialize return stack pointer) MATCH (string compare primitive) ABORT" (error checking & reporting word) +LOOP (variable increment loop) /LOOP (variable unsigned increment loop) CMOVE (string move) EXP2 UD4 -> Floating point normalize of unsigned 32-bit mantissa ADC N1 N2 CIN -> N3 COUT -> Add with carry. CIN and COUT are logical flags on the stack. ASR N1 -> N2 -> Arithmetic shift right. BYTESWAP N1 -> N2 -> Swap high and low bytes within N1. D+! D ADDR -> -> Sum D into 32-bit number at ADDR. D>R D -> -> D Move D to return stack. DLSLN D1 N2 -> D3 -> Logical shift left of D1 by N2 bits. DLSR D1 -> D2 -> Logical shift right of D1 by 1 bit. DLSRN D1 N2 -> D3 -> Logical shift right of D1 by N2 bits. DR> -> D D -> Move D from return stack to data stack. DROT D1 D2 D3 -> D2 D3 D1 -> Perform double-precision ROT. LSLN N1 N2 -> N3 -> Logical shift left of N1 by N2 bits. LSR N1 -> N2 -> Logical shift right of N1 by 1 bit. LSRN N1 N2 -> N3 -> Logical shift right of N1 by N2 bits. Q+ Q1 Q2 -> Q3 -> 64-bit addition. QLSL Q1 -> Q2 -> Logical shift left of Q1 by 1 bit. RLC N1 CIN -> N2 COUT -> Rotate left through carry N1 by 1 bit. CIN is carry-in, COUT is carry-out. RRC N1 CIN -> N2 COUT -> Rotate right through carry N1 by 1 bit. CIN is carry-in, COUT is carry-out. TDUP D1 N2 -> D1 N2 D1 N2 -> Duplicate a temporary floating point number (32-bit mantissa, 16-bit integer).

注意: CPU/16 使用RAM 微码存储器,所以用户可以按需要增加或者修改所有指令。以上所列出的只是标准开发软件包说明的指令。

表 4.1(e) CPU/16 指令集汇总 -- 扩展算术和浮点支持字

值得注意的一点是这个指令集中指令的密度。在表 4.1A 中指令是一个很大的 Forth 原语操作的集合。表 4.1B 显示了一些可以通过单指令实现的 Forth 常用字组合。表 4.1C 显示了支持 Forth 底层操作的字,比如子程序调用和退出。表 4.1D 是一些用微码实现的高级 Forth 字,目的是加快的执行速度。表 4.1E 的字用于支持扩展精度整数操作和 32 位浮点操作。

指令的执行时间根据指令的复杂性而在一个很大的范围内变化。处理堆栈上数据的简单指令比如 + 和 SWAP 花费 2 或者 3 个微周期,复杂的指令需要更多的时钟周期(比如 Q+ ,是 64 位的加法,花费 18 个周期),但是也比高级代码快。如果必要,也可以用微码写出几千个时钟周期的存储器移动或者其它重复性的操作。

正如前面讨论过的那样,每个指令访问一系列的微指令,这些微指令在一个微存储器页中,与指令的 8 位操作码相对应。图 4.3 显示了微指令的微码格式,微码使用水平译码方式,也就是说对于微码来说只有一种格式,它被分为几个独立的字段以控制机器的不同部分。

图 4-3 CPU/16 微指令格式

由于堆栈计算机的简单性和 CPU/16 的实现方法,每个微指令中只需要 32 位。我们可以把这 32 位与其它使用水平微码机器的 48 位或者更宽的计算机相比较,比如与那些使用 AMD2900 系列元件的计算机相比较。这种简单性使得微码程序的编写并不比传统计算机的汇编语言程序更复杂。

比如,我们规范堆栈计算机实现加法的伪码描述是这样的:

TOSREG 2* R@ >R SWAP @ XOR

表4.3(a) RTX 2000 指令集汇总 -- Forth 原语 (参看附录B的说明)

inv shift DUP @ SWAP lit inv DUP nn G! inv lit SWAP inv DUP U! inv lit SWAP op DUP U@ op nn inv (short literal) nn G! inv nn OVER op nn G@ inv nn SWAP op nn G@ DROP inv op shift nn G@ OVER op ! inv nn G@ SWAP op ! nn OVER inv shift @ inv OVER SWAP op shift @ nn OVER SWAP ! inv @ SWAP inv OVER SWAP ! nn @ SWAP op OVER SWAP @ op ?DUP ?BRANCH SWAP inv shift DDUP inv shift SWAP DROP inv shift DDUP nn SWAP op SWAP DROP @ nn DDUP ! SWAP DROP DUP inv shift DROP inv shift SWAP DROP DUP @ nn ROT op DROP lit inv SWAP DROP DUP @ SWAP DROP nn inv SWAP OVER op shift DROP DUP inv shift SWAP OVER ! DUP inv shift U! inv DUP lit op U@ op DUP @ nn ROT op U@ SWAP inv 说明: inv - 1's complement or no-op lit - long literal value nn - short literal value op - ALU operation shift - shift select or no-op

表 4.3(b) RTX 2000 指令集汇总 -- 组合 Forth 原语

指令 数据栈 返回栈

nn G@ -> N -> Fetch the value from internal register or ASIC bus device nn (stored as a 5 bit literal in the instruction). nn G! N -> -> Store N into the internal register or ASIC bus device nn (stored as a 5 bit literal in the instruction) * N1 N2 -> D3 -> Single clock cycle hardware multiply *' D1 -> D2 -> Unsigned Multiply step (takes two 16 bit numbers and produces a 32 bit product). *- D1 -> D2 -> Signed Multiply step (takes two 16 bit numbers and produces a 32 bit product). *F D1 -> D2 -> Fractional Multiply step (takes two 16 bit fractions and produces a 32 bit product). */' D1 -> D2 -> Divide step (takes a 16 bit dividend and divisor and produces 16 bit remainder and quotients). */'' D1 -> D2 -> Last Divide step (to perform non-restoring division fixup step). 2/ N1 -> N2 -> Arithmetic shift right (same as division by two for non-negative integers. D2/ D1 -> D2 -> 32 bit arithmetic shift right (same as division by two for non-negative integers. S' D1 -> D2 -> Square Root step. NEXT -> N1 -> N2 Count-down loop using top of return stack as a counter.

表4.3(c) RTX 2000 指令集汇总 -- 特殊目的字

4.4.4 体系结构特点

NC4016 体系结构是按单周期执行指令面设计的。除了存储器访问和长常数读取外的所有原语操作都在单周期完成,这比规范堆栈计算机需要更多的片上互联通道, 但也提供了更好的性能。

NC4016 允许把非冲突的连续操作组合到一个相同的指令中。例如,在 Forth 程序中,一个值可以从存储器中取出并使用序列 @ + 加到栈顶元素中,而这些操作在 NC4016 中可以组合到一个单指令中。

NC4016 的子程序返回位允许把子程序返回以相似的方法与其它指令组合起来。通过把子程序出口处的指令与返回相结合,在许多情况下可以得到“免费”的子程序返回操作。 NC4016 编译器所做的一个优化是执行尾递归消除。所谓的尾递归消除就是:使用一个对子程序的无条件分支来代替子程序调用 / 返回对。

NC4016 的另一个革新机制是把程序存储器的前 32 个位置作为全局的“用户变量”来访问。这种机制通过把一个任务的关键信息比如主存储器的多个堆栈指针放到快速访问的变量中而有助于简单地解决高级语言实现中的一些问题,它也使得高级语言编译器达到应有的性能,方法是把这 32 个快速访问变量作为寄存器集的模拟,而这些编译器最初是为基于寄存器的计算机而开发的。

4.4.5 实现和面向的应用领域

NC4016 使用不到 4000 个门、 3.0 微米的 HCMOS 门阵列技术实现,封装成 121 PGA ,运行速度为 8MHz. 。

在最初 NC4016 实现的时候,门阵列技术还不允许把堆栈存储器放到片内。因此最小的 NC4016 系统包括 3 个 16 位的存储器:一个用于程序和数据,一个用于数据堆栈,一个用于返回堆栈。

由于 NC4016 可以单周期地执行大多数指令,包括条件分支和子程序调用,于是在时钟开始和用于取指下一条指令的存储器地址有效之间就有大量的时间,这个时间近似等于时钟的一半,这也意味着程序存储器访问时间大约应该是时钟周期的 2 倍。

NC4016 最初是作为概念验证和原型机而设计的,因此它含有必须由软件和外部硬件克服的麻烦。比如, NC4016 希望能够处理中断,但是门阵列的一个错误使得中断响应不正确。 Novix 因此发布了一个应用说明,指出如何使用一个 20 引脚的 PAL 器件来解决这个问题。后续的产品将消除这些实现困难并增加其它的能力。

NC4016 的目标是嵌入式控制市场 , 它以适当小的系统实现了非常高的性能。适合 NC4016 的应用是:激光打印机控制、图形化 CRT 显示控制、电信控制( T1 交换、传真控制器等)、局域网控制器和光学字符识别系统。

本章的信息来自 Golden et al. (1985), Miller (1987), Stephens & Watson (1985), Novix 的 NC4016 微处理器程序员简介( NOVIX 1985 )。

4.5 HARRIS RTX2000 的体系结构 4.5.1 介绍

HARRIS 半导体公司的 RTX2000 是一个 16 位的处理器,是 NOVIX NC4016 的派生产品。 RTX2000 具有很高的集成度,它不仅包括核心处理器,也含有堆栈存储器、硬件乘法器和片上的计数器 / 定时器。

4.5.2 方框图

图 4.8 RTX2000 方框图

图 4.8 给出了 RTX2000 的方框图。 RTX2000 和 NC4016 的主要区别是 CPU 核心之外的片上资源,这些资源包括: 256 个元素的返回栈, 256 个元素的数据栈, 16 x 16 位单周期硬件乘法器, 3 个计数器/定时器和一个有优先级的向量中断控制器。除了片上堆栈外, RTX2000 通过 I/O 总线(在 NC4016 上称为 G 总线,在 RTX2000 上称为 ASIC 总线)访问所有的外部设备。

RTX2000 相对于原始的 Novix 设计来说,所做的增强包括:用于处理 8 位数据的字节交换能力,执行条件分支时在相邻的存储器块之间的跳转能力,堆栈上溢/下溢时的中断。

RTX2000 的另一个特点是片上存储器页控制逻辑。它通过为代码段、数据段(用于读写),用户存储器基地址和页寄存器(用于重新分配用户变量区)、索引寄存器(用于扩展返回栈地址值)指定分别的页寄存器来突破程序存储器 32K 字的限制。因为返回栈是 21 位的,可以通过一个远调用指令序列来访问存储器的任何位置,该指令能够把完整的地址保存在返回栈中。

4.5.3 指令集汇总

RTX2000 指令集与 NC4016 指令在功能上非常相似,但格式上有很大的不同,所以需要分别描述,图 4.9 给出了 RTX2000 的指令格式。

图 4.9A RTX 指令格式 -- 子程序调用

图 4.9A 给出了子程序调用的指令格式。在这个格式中,指令的最高位是 0 ,子程序其它位是 15 位的子程序地址,这样就把程序限制于 32K 字之内。

图 4.9B RTX 指令格式 -- 条件分支

图 4.9B 给出了条件分支指令格式。 BIT11-12 选择分支条件: T 为 0 (可以同时地进行条件或者无条件地 POP 数据堆栈)、无条件分支、使用索引寄存器减量并为 0 时转移以用于循环。 BIT 8-0 指定目标地址的低 9 位,这时 BIT9-10 控制一个对分支地址的增量 / 减量器以实现在同一个 512 字存储器页的分支,调整页或者跳转到 0 页

图 4.9C RTX 指令格式 -- ALU 操作

图 4.9C 给出了 ALU 指令的格式。 BIT0-3 操作 ALU 输出移位器的移位操作。

ALU 指令的 BIT5 指示子程序返回操作,这就允许子程序返回与算术操作的过程混合以便在许多情况下得到“免费”的子程序返回。

BIT8-11 选择 ALU 功能, BIT7 控制输出是否进行反转。

图 4.9D RTX 指令格式 -- ALU 操作(多步模式)

图 4.9D 是 ALU 指令多步模式的格式。这个格式与 ALU 指令格式非常相似。 BIT0-3 选择移位控制功能, BIT5 控制子程序返回功能, BIT9-11 选择 ALU 操作。

在多步模式中, BIT8 选择是乘法 / 除法寄存器还是平方根寄存器用于特殊操作,同时 BIT6-7 选择特殊的多步控制功能。多步模式的一个基本用途是重复乘法和除法步操作。

 

图 4.9E -- RTX 指令格式 -- 存储器引用

图 4.9E 给出了存储器访问指令的格式,这些指令需要两个周期:一个用于指令读取,一个用于实际的操作数读写。存储器的地址总是来自于 T 寄存器。 BIT12 选择字节或者字操作,因为 RTX2000 使用字存储器地址,所以这一位选择存储器字的“低半字 / 高半字”或者“全字”。

BIT6-7 指示存储器读、写和其它控制信息。 BIT0-4 指定一个小的常数可以与 T 相加、相减以实现自动增量和自动减量寻址功能。 BIT8-11 指示的 ALU 功能与 ALU 指令相同。

图 4.9F RTX 指令格式 -- 其它指令

图 4.9F 给出了其它指令格式,这些指令可以用于 32 个字的可重定位用户空间读写,以节省每次从存储器中读写时都把存储器地址放到堆栈上的时间。它也用于在片上寄存器之间传输(在一个单周期内)或者装入 16 位的常数(使用两个时钟周期)到栈顶。 BIT8-11 指示的 ALU 功能与 ALU 指令格式相同。

RTX2000 专门设计用于执行 Forth 语言。因为许多指令使用了非编码格式,对应于 Forth 操作序列的机器操作可以编码到一个单指令中。表 4.3 给出了 Forth 原语和 RTX2000 执行的指令序列。

: (子程序调用) AND ; (子程序退出) BRANCH ! DROP + DUP - I 0 LIT 0< NOP 0BRANCH OR 1+ OVER 1- R> 2* R@ >R SWAP @ XOR

表 4.3(a) RTX 2000 指令集汇总 -- Forth 原语(参看附录B)

inv shift DUP @ SWAP lit inv DUP nn G! inv lit SWAP inv DUP U! inv lit SWAP op DUP U@ op nn inv (short literal) nn G! inv nn OVER op nn G@ inv nn SWAP op nn G@ DROP inv op shift nn G@ OVER op ! inv nn G@ SWAP op ! nn OVER inv shift @ inv OVER SWAP op shift @ nn OVER SWAP ! inv @ SWAP inv OVER SWAP ! nn @ SWAP op OVER SWAP @ op ?DUP ?BRANCH SWAP inv shift DDUP inv shift SWAP DROP inv shift DDUP nn SWAP op SWAP DROP @ nn DDUP ! SWAP DROP DUP inv shift DROP inv shift SWAP DROP DUP @ nn ROT op DROP lit inv SWAP DROP DUP @ SWAP DROP nn inv SWAP OVER op shift DROP DUP inv shift SWAP OVER ! DUP inv shift U! inv DUP lit op U@ op DUP @ nn ROT op U@ SWAP inv 说明: inv - 1's complement or no-op lit - long literal value nn - short literal value op - ALU operation shift - shift select or no-op

表4.3(b) RTX 2000 指令集汇总 -- 组合的 Forth原语

指令 数据栈 返回栈

nn G@ -> N -> Fetch the value from internal register or ASIC bus device nn (stored as a 5 bit literal in the instruction). nn G! N -> -> Store N into the internal register or ASIC bus device nn (stored as a 5 bit literal in the instruction) * N1 N2 -> D3 -> Single clock cycle hardware multiply *' D1 -> D2 -> Unsigned Multiply step (takes two 16 bit numbers and produces a 32 bit product). *- D1 -> D2 -> Signed Multiply step (takes two 16 bit numbers and produces a 32 bit product). *F D1 -> D2 -> Fractional Multiply step (takes two 16 bit fractions and produces a 32 bit product). */' D1 -> D2 -> Divide step (takes a 16 bit dividend and divisor and produces 16 bit remainder and quotients). */'' D1 -> D2 -> Last Divide step (to perform non-restoring division fixup step). 2/ N1 -> N2 -> Arithmetic shift right (same as division by two for non-negative integers. D2/ D1 -> D2 -> 32 bit arithmetic shift right (same as division by two for non-negative integers. S' D1 -> D2 -> Square Root step. NEXT -> N1 -> N2 Count-down loop using top of return stack as a counter.

表4.3(c) RTX 2000 指令集汇总 -- 特殊功能字

4.5.4 体系结构特点

像 NC4016 一样,RTX2000 的内部结构被优化以实现单周期指令执行,除了存储器读取和长常数外的所有原语操作都在单周期完成。

RTX2000 也允许把某些 Forth 指令序列组合到一个单一的指令中,所提供的一个关键能力是某些格式中的返回位,它允许把 ALU 操作和子程序返回合并在一起。

4.5.5 实现和面向的应用领域

RTX2000 使用 2.0 微米CMOS 标准单技术实现,封装为 84 PGA。RTX2000 运行速度 10MHz 。标准单元技术一个大的优点是 RAM可以和逻辑电路混合在同一个芯片中,允许返回栈和数据栈都放到片内。

由于 RTX2000 执行大多数指令、包括条件分支和子程序调用都是单周期的,因此在时钟周期开始和用于取指下一条指令的存储器地址有效之间有大量的时间。这个时间大约是时钟周期的一半,这就意味着程序存储器的速度大约应该是时钟速度的 2 倍。

RTX2000 最初是基于 NC4016 设计的,所以它改进了 NC4016 的设计,也没有 NC4016 上那些怪异的硬件。

RTX2000 的目标是高端 16 位微控制器市场,由于使用半定制技术实现,处理器的特殊版本可以用于指定的设计应用。一些可能的应用包括激光打印机、图形化 CRT 显示控制、电信控制、光学字符识别、信号处理和军事控制应用。

这一部分的信息来自 RTX2000 数据手册( HARRIS 1988A )和 RTX2000 指令集手册(HARRIS 1988B)

4.5.6 标准单元设计

HARRIS RTX2000 使用了标准单元方法,而没有使用门阵列,这种方法使它受益非浅。在门阵列中,设计者把硅片上预先放置的逻辑门的规则模式进行客户化。而在标准单元中,设计者使用逻辑功能库进行工作,这些功能库在硅片上可以任意安排,没有预定的安排模式。门阵列与预定的存储器区域一同使用,标准单元设计技术的灵活性是门阵列支持不可比拟的。

这样, NC4016 和 RTX2000 的主要区别就表现出来了:RTX2000 可以利用标准单元灵活性的优点把堆栈 RAM 放到片上。由于这种灵活性,具有不同能力和容量的 RTX2000 系列处理器可以使用同一个核心,而这个核心可以在设计过程中作为一个大的标准单元来使用。

除了 RTX2000 系列的标准版本产品之外,用户也可以通过应用指定的专用硬件受益。专用硬件的例子包括:串口、 FFT 地址发生器、数据压缩电路或者其它必须在片外安装的硬件。使用标准单元技术,用户还可以根据自己的需要裁减芯片版本。在小于 2.0 微米的工艺成熟之后,我们可以通过这种裁减,安排一定数量的片上程序 RAM 和 ROM 。

第五章 32 位系统的体系结构

32 位堆栈计算机是从 1989 年才开始作为产品出现的,但是很快就将在未来的堆栈计算机系统中扮演重要的角色。在 5.1 中我们将讨论与 32 位堆栈处理器一同出现的优点和问题。

在 5.2 中我们将讨论 Johns Hopkins University/Applied Physics Laboratory FRISC 3 设计,这个设计也称为 Silicon Composers SC32 。 FRISC3 是一个硬连线堆栈处理器,体现了 NC4016 及其后续处理器的设计精神,但是更加灵活。它使用了非常小的片上堆栈缓冲器,并通过自动缓冲器控制电路来管理堆栈。

在 5.3 中,我们将讨论 Harris RTX 32P 的设计。 RTX32P 是一个从 WISC CPU/16 派生出来的微码处理器,是一个 WISC CPU/32 处理器的两片实现。 RTX 32P 使用基于 RAM 的微码处理器以增加灵活性,还使用了很大的片上堆栈缓冲区。 RTX 32P 是一个正在开发的 32 位商用处理器的原型处理器。

在 5.4 中,我们将讨论 Wright State University SF1 设计。 SF1 实际上是一个 ML1 堆栈计算机,它把栈帧用于多个硬件堆栈中以支持 C 语言和其它传统的语言。然而 SF1 有很强的 ML0 系统的特点,所以它可以作为一个例子来说明 ML1 设计是如何成为一个 ML0 设计的。

尽管这三种处理器的实现策略大不相同,但它们的目标都是堆栈程序的高速执行。

5.1 为什么使用 32 位系统

第四章描述的 16 位处理器在广泛的应用领域有足够的能力,特别是在嵌入式控制环境中。但是,也有些应用要求 32 位处理器所提供的更强大的能力,这些应用包括扩展的 32 位整数运算,大的存储器寻址空间或者浮点运算能力。

设计 32 位堆栈处理器所面对的一个技术困难就是堆栈的管理。一个鲁莽的方法就是像 NC4016 那样采用一个片外堆栈存储器,但是对于 32 位设计来说,这需要 64 个额外的引脚用于数据和地址,这就使得在成本敏感的应用中变得很不实用。 FRISC3 使用了一个简单的办法解决了这个问题:设置两个自动管理的片上栈顶元素缓冲区,通过普通的 RAM 数据引脚配合程序存储器来分裂堆栈元素。而 RTX32P 简单地在片上分配一个大容量的堆栈空间,配合存储器通过堆栈元素的块移动来实现堆栈的分裂。

第六章将进一步讨论这些方法的细节。

5.2 FRISC3 ( SC32 ) 体系结构 5.2.1 介绍

Johns Hopkins University/Applied Physics Laboratory (JHU/APL) FRISC 3 是一个硬连线实现的 32 位处理器,为执行 Forth 语言而进行了优化。名称“ FRISC”表示“Forth 精减指令集计算机”, 3 是相对前两代原型计算机而言的。 FRISC3 关注的焦点是在实时控制环境中 Forth 原语的单周期执行。

JHU/APL 开发 FRISC3 是为了满足对快速 Forth 语言处理器的需要,这主要用于空间控制处理应用,比如卫星和航天飞机试验。 FRISC3 项目也许可以追溯到 JHU/APL HUT project (see Appendix A) ,这是一个为 Forth 语言而优化的位片式处理器。

在完成了 HUT 处理器之后, Johns Hopkins 设计小组设计了一个使用 4.0 微米silicon-on-sapphire 32 位 Forth 处理器( FRISC1 )和 3.0 微米 bulk CMOS 版本 (FRISC 2) ,它们都是全客户定制设计。最后的版本 FRISC3 是商用的处理器,也是它们早期工作的副产品。

Silicon Composers 购买了 FRISC 3 商用产品的产权,并更名为 SC32 。本章的描述全部适合 FRISC3 和 SC32 ,但我们在本书的后续章节中仍然使用 FRISC3 这个名称。

FRSIC3 的最初用途是嵌入式实时控制,特别是飞船(这是 JHU/APL 小组关注的焦点),但是也适合其它的工业和商业应用。

5.2.2 方框图

图 5.1 是 FRISC3 体系结构的方框图

图 5.1 FRISC 3 方框图

数据栈和返回栈使用专用的硬件实现。它们包括堆栈指针和特殊的控制逻辑,提供 16 个元素 32 位堆栈存储器的地址,组成环形缓冲区。每个堆栈顶部的 4 个元素可以直接读入 B- 总线。此外,数据堆栈栈顶元素可以读入 T- 总线(栈顶元素总线),返回栈顶元素可以读入 A- 总线(返回地址总线),这就使得两个有潜在差异的堆栈元素可以同时读出,在同一个时间内只有一个堆栈元素可以写回。

FRISC3 一个创新点是使用与堆栈指针相配合的堆栈管理逻辑。这个逻辑自动地在 16 字片上堆栈缓冲区的程序存储器堆栈溢出区中交换堆栈元素,这可以保证片上堆栈缓冲区永远也不会发生上溢和下溢的情况。这个功能通过窃取程序存储器周期来实现,节省了额外的堆栈数据引脚,也使得程序执行的性能损失最小。

FRISC3 的设计者把这个特点称为堆栈 CACHE ,因为它为快速的片上访问缓存了少量的栈顶元素,但又不像通常的数据或者指令 CACHE ,它不必使用关联存储器查找表结构来访问那些驻留在离散存储器空间中的数据。

FRISC3 的算术逻辑单元包含有一个标准的 ALU ,它的两个输入分别是 B- 总线和 T- 总线的锁存器。这两个不同的 ALU 源允许数据栈顶元素(通过 T- 总线)和任何的 4 个数据栈元素(通过 B- 总线)通过一个指令进行操作,因为数据栈是双口的。 B- 总线也能够通过 ALU 的 B 边提供任何的非堆栈资源。

在时钟的前半个同期,提供 ALU 输入的 B- 总线和 T- 总线锁存器用于俘获数据。这就允许 B- 总线在时钟的后半个周期把从 ALU 来的数据写入芯片的其它寄存器。 ALU B 输入的移位模块把 B 输入左移 1 位用于除法,但是也可以传输不移位的数据。同样, ALU 输出的移位单元也可以按需要在提供 B- 总线数据的同时把数据右移 1 位用于实现乘法。

ALU 输出的锁存器允许指针加偏移量寻址以访问存储器。在存储器读写的第一个时钟周期里, ALU 通过 T- 总线把文字常量值加到由 B- 总线选择的数据堆栈字。在第二个周期里, B- 总线用于传输选定的目标。

标志寄存器 FL 用于保存 ALU 产生的 16 个可选择的条件码之一,可以用于分支和多精度算术运算。 ZERO 寄存器用于向 B- 总线提供常数 0.

4 个用户寄存器用于保存指向存储器的指针或者其它的值。这些寄存器的 2 个用于堆栈控制逻辑,它指向数据堆栈和返回堆栈在程序存储器驻留分区里的栈顶元素。

程序计数器用于提供 A- 总线作为读取指令的存储器地址。 PC 也可以经过 ALU 到达返回栈以实现子程序调用。返回栈也可以像 PC 一样驱动 A- 总线以实现子程序返回。指令寄存器可以驱动 A- 总线以用于指令读取、子程序调用和分支。

5.2.3 指令集汇总

图 5.2 显示了 FRISC3 的 4 种指令格式:一个用于控制流,一个用于存储器装载和存储,一个用于 ALU 操作,一个用于移位操作。 FRISC3 使用类似于 NC4016 、 RTX2000 和 M17 的非编码指令格式。所有的指令都通过高 3 位来区分指令类型。

图 5.2A FRSIC3 指令格式 -- 控制流

图 5.2A 给出了控制流指令格式。三种控制流指令分别是子程序调用、无条件转移和条件转移。条件指令在 FL 寄存器置 0 时执行,这个值是由最近的 FL 寄存器设置指令给出的。地址字段包含有 29 位的绝对地址。非条件分支可以供编译器来消除尾递归。

图 5.2B FRISC3 指令格式 -- 存储器访问

图 5.2B 显示了存储器访问指令的格式。 BIT0-15 包含无符号偏移量,它将加到总线源操作数据提供的地址上。它是这样完成的:在 ALU 输入端锁存总线源和来自指令的偏移量字段,执行一个加法,把 ALU 输出结果送给 A- 总线用于存储器寻址。

BIT16-19 指定增加 / 减少返回栈和数据栈指针的信息。 BIT20-23 指定 B- 总线源。这里, TOS 表示数据栈顶元素, SOS 数据栈的第二个元素, 3OS 表示数据栈第三个元素, TOR 表示返回栈顶。 BIT24-27 以类似的方式指定 B- 总线源。

BIT28 指出下一个指令是从返回栈栈顶还是从程序计数器所指定的地址读出。使用 BIT28 实现两个组合的功能:返回栈栈顶元素作为读取指令的地址,返回栈执行一个弹出操作,这使得子程序返回可以与其它操作并行完成。

BIT29-31 指示指令的类型。对于存储器访问指令格式, 4 个可能的指令是:从存储器装入、存入存储器、装入低地址,装入高地址。存储器读写指令使用总线源提供地址,总线目的字段指出数据寄存器目标或者源。装入和存储指令是仅有的 2 个时钟周期的指令,因为它们必须访问存储器 2 次来完成数据移动和下一条指令的读取。

两个装入地址指令只是简单地装入计算的存储器地址到目的寄存器,根本不做存储器访问。这也可以视为一个加立即数指令。装入高地址指令在执行加法之前把偏移量左移 16 位。装入地址指令也意味着装入一个常数值,因为可以选择 ZERO 寄存器作为地址寄存器。通过这种方法,执行一个装入高地址后随一个装入低地址指令可以完成一个 32 位常数的拼装。

图 5.2C FRISC3 指令格式 -- ALU 操作

图 5.2C 给出了 ALU 指令格式。在这种指令格式中, BIT0-6 指定将要执行的 ALU 操作。 ALU 的 A 边连接到 T- 总线,同时 B 边连接到 B- 总线。 BIT7 控制装入由指令 BIT10-13 选择的条件码。这些条件码提供了不同的组合: ZERO 位、 Negative 位、 Carry 位、 Overflow 位,以及常数 0 和 1 。 BIT8-9 选择到 ALU 操作的进位。 BIT14 选择实际的 ALU 结果 FL 寄存器的内容进入 B- 总线。 BIT15 是 0 ,它指出这个指令是一个 ALU 操作。

BIT16-28 指定图 5.2B 给出的存储器访问格式。 BIT29-31 指出 ALU/ 移位操作指令类型。

图 5.2D FRISC3 指令格式 -- 移位操作

图 5.2D 给出了移位指令格式。这个指令的 BIT0 没有使用。 BIT1 指出 FL 寄存器的输入是来自 BIT 10-13 选定的条件码还是被选择移位寄存器的移出位。 BIT 2-3 选择执行特殊的操作步骤用于执行乘法和恢复除法。 BIT4 选择右移的输入位是来自 FL 寄存器还是 ALU 条件码。 BIT5-6 指定执行左移还是右移操作。 BIT7 指定 FL 寄存器是用移位输出还是用 BIT10-13 和 BIT1 产生的条件码装入。 BIT8-9 选择 ALU 操作的进位,而 BIT14 决定是用 ALU 的输出还是用 FL 寄存器来驱动 B- 总线。 BIT15 是 1 ,指示这个指令是一个移位操作。

BIT 16-28 指示图 5.2B 给出的存储器访问格式, BIT29-31 指定 ALU/ 移位操作类型。

所有的指令执行只需要一个时钟周期,只有存储器读写指令例外,它们需要两个时钟周期。每个时钟周期在执行时被分为源相和目的相。在源相中,选定的 B- 总线源和 T- 总线源被读入 ALU 输入锁存器。在目的相中, B- 总线目的被写出。每个指令的装入都是与前一个指令的执行并行完成的。

子程序调用也是单周期完成的,子程序返回不需要额外的扩展时间因为它们是和其它指令组合在一起的。

0 >R 0< @ 0= AND 0> BRANCH 0BRANCH CALL 1 DROP 1+ DUP 1- EXIT 2* LITERAL 2+ NEGATE 2/ NOT 4+ OR + OVER -1 R> - R@ < S->D U< = U> > XOR

表 5.1(a) FRISC 3 指令集汇总 -- Forth 原语(参看附录B的描述)

FRISC 3 支持许多的组合的Forth 原语,所以我们这里只给出其中的几个例子

LIT + @ (address plus offset fetch) LIT + ! (address plus offset store) @ (fetch a variable) ! (store a variable) 2 PICK (copy the third element on the stack) 3 PICK (copy the fourth element on the stack) R> DROP R@ < SWAP DROP OVER OVER + LIT + DROP LIT OVER + DUP LIT + OVER - DROP DUP DUP + DROP OVER DUP AND OVER @ DUP XOR 2 PICK @ DUP 1+ 3 PICK @ OVER + OVER ! 2 PICK + 2 PICK ! 3 PICK + 3 PICK ! R@ + + >R R> + DUP >R DUP < DUP R> DROP DUP > R> DROP DUP

FRISC 3 的灵活性也支持许多没有包含在 Forth 语言中的操作,比如处理返回栈的字(例如,返回栈的 SWAP ), FRISC3 还包含了对数据栈和返回栈顶 4 个元素的操作。

表 5.1(b) FRISC 3 指令集汇总 -- 组合的Forth原语

5.2.4 体系结构特点

就像我们已经讨论过的其它计算机设计一样, FRISC3 有一个分离的存储器地址总线以实现在指令操作的同时并行取指。另外, FRISC3 没有为数据堆栈准备专用的栈顶元素寄存器,取而代之的是一个双口堆栈存储器,它允许对 4 个栈顶元素进行任意访问,这就提供了比纯堆栈机器更通用的能力,也可以提高某些代码序列的执行速度。

堆栈控制逻辑是用于防止程序运行时堆栈灾难性上溢或者下溢,它使得在整个运行期间堆栈至少保持 4 个元素的空间。这种请求式堆栈缓冲区管理方法的细节将在 6.4.2.2 中讨论。每个堆栈由 16 个字组成环形缓冲区,只要片上堆栈缓冲区的元素少于 4 个或者多于 12 个,堆栈控制器就与存储器交换数据,每次执行一个元素,因为堆栈指针在一个指令中只能增量或者减量 1 次。每个堆栈元素与存储器交换需要两个周期。每六章中讨论了这些额外的时钟消耗, FRISC3 的设计者认为这些消耗典型地低于 2%的机器执行时间。

5.2.5 实现和面向的应用领域

FRISC3 使用 2.0 微米 CMOS 技术和硅编译器实现,使用了 35,000 个晶体管,封装为 85 PGA , FRISC3 的运行频率为 10MHz. 。

FRISC3 为实时控制应用领域而设计,特别是在空间站方面。它专注有效地执行 Forth 语言程序,尽管它也应该能有效地执行 C 或者其它传统的高级语言。实现 C 语言时可以使用一个用户寄存器作为栈帧指针,使用存储器访问偏移量进行相对于帧指针的偏移寻址。

本章中的信息基于 Hayes & Lee (1988) 关于 FRISC3 的描述, FRISC 以前版本体系结构方面的信息可以通过 Fraeman et al. (1986), Hayes (1986), and Hayes et al. (1987) 找到。

5.3 RTX 32P 的体系结构

5.3.1 介绍

Harris Semiconductor RTX 32P 是 32 位的 RTX(Real Time Express ) 处理器成员,它是一个 HARRIS 商业 32 位机器设计的原型机。

RTX32P 是 WISC 技术公司 CPU/32 的 CMOS 器件实现 (Koopman 1987c) ,后者最初用分立元件设计的。 CPU/32 是基于第 4 章描述的 WISC CPU/16 开发的。由于这个历史, RTX32 P 是一个微码化的机器,它具有片上微码 RAM 和片上堆栈。

RTX32P 是一个 2 芯片的堆栈处理器,最初设计目标是作为一个体系平台,应该具有最大的灵活性。大量的高速 RAM 迫使设计者使用两个芯片,但这也与产生一个研究和开发平台的目标相一致。实时控制是 RTX32P 的最初应用方向。

RTX32P 原始的程序设计语言是 Forth 。然而, RTX32P 的继承者却为更加传统的语言提供了很好的支持,比如 C 语言、 ADA 、 PASCAL ,还有专用的语言比如 LISP 和函数程序设计语言。

RTX32P 的一个重要设计哲学是:随着处理器速度的增加, ALU 在每个片外存储器访问周期可以执行两次,因此 RTX32P 在每个主处理器访问时执行两个微指令,包括指令读取。每个指令是两个或者更多的时钟周期长度,在每个时钟周期执行不同的微指令。使用这个策略的理由需要很长的篇幅才能说清,我们将在 9.4 中讨论。

5.3.2 方框图

图 5.3 是 RTX32P 的体系结构方框图

图 5.3 RTX 32P 方框图

数据堆栈和返回堆栈都用专用的硬件堆栈来实现,包括一个 9 位的增 / 减计数器(堆栈指针),可以寻址 32 位宽的 512 个元素。堆栈指针可以被系统读写以实现对深层堆栈元素的高效率访问。

ALU 部分包括一个标准的多功能 ALU 和一个 DHI 寄存器用于保存中间结果。为了方便, DHI 寄存器作为栈顶元素的缓冲寄存器,这就意味着堆栈指针实际指向程序可见的堆栈次栈顶元素,这样的结果是,进行栈顶两个元素的操作,比如加法,可以用一个周期完成。这时 ALU 的 B 边从数据堆栈上读出次栈顶元素, ALU 的 A 边从数据 HI 寄存器中读出栈顶元素。

ALU 的 B 边输入锁存器通常进行透明锁存,这样就可以在单个周期内返回数据,提高了 DHI 寄存器和数据堆栈之间交换数据的操作速度。

在机器语言程序中没有可见的条件代码。带进位的加法和其它多精度操作通过微码支持。它们把进位标志作为一个逻辑值放到堆栈上( 0 表示进位被清除, -1 表示进位被设置)

DL0 寄存器作为一个指令内部中间结果的临时保持寄存器。 DHI 和 DLO 寄存器都是移位寄存器,它们连接成 64 移位方式以支持乘法和除法。

一个片外主机接口用于与个人计算机 PC 相连,因为所有的片上存储器都是基于 RAM 的,需要一个外部主机来初始化 CPU 。

RTX32P 没有程序计数器,每个指令都含有下一条指令的地址或者是引用返回栈的栈顶,这种设计决策是注意到了 Forth 程序包含有很高比例的子程序调用操作。 6.3.3 更详细地讨论了 RTX32P 指令格式的影响。

取代程序计数器的是方框图中描述的包含在存储器地址逻辑中的下一个地址寄存器( NAR ),它含有用于读取下一条指令的指针。存储器地址逻辑使用返回栈栈顶元素来寻址存储器实现子程序返回,也使用 RAM 地址寄存器( ADDR REG )来进行高效率的存储器读写。存储器地址逻辑也包含一个增 4 电路为子程序调用操作产生返回地址。因为返回栈和存储器地址逻辑与系统总线隔离,子程序调用、子程序返回和无条件跳转都可以与其它操作并行地执行。结果是在许多情况下这些控制传输操作不需要任何时钟周期。

程序存储器组织成 4G 字节的存储器,可以在字节边界上寻址,指令和 32 位数据项需要按 32 位边界对齐,因为存储器中的数据是按 32 位字来访问的。但是由于封装引脚的限制,实际的 RTX32P 只能寻址 8M 字节。

微程序存储器是一个片上 2K 字的 30 位读写存储器。存储器按每页 8 个字的 256 个页面来录址。机器中的每个微码定位在它自己的 8 个字的页中。微程序计数器提供 9 位的页地址,但在本实现中只使用了 8 位。这种策略允许在当前微指令中提供 3 位作为相同微码页中的下一条微指令的地址,其中的最低位一个 8 选 1 条件微分支选择,这就能够在执行单个操作码时实现分支和循环。

指令译码非常简单,它把 9 位的操作码装入微程序计数器,把它作为微程序存储器的页地址。由于微程序计数器是用一个计数器电路实现的,在需要的时候可以跨越 8 个微指令的页。

微指令寄存器( MIR )保持微程序存储器的输出,这就使得在执行当前指令的同时,并行地访问下一条微指令。 MIR 寄存器完全消除了系统关键路径上的微程序存储器访问延迟。它的访问也降低了对一个指令两个时钟周期的限制。如果一个指令能够在单时钟周期完成,则必须在后面加入一个 NO-OP 指令,这可以保证下一个指令能够正确地通过 MIR 取指。

主机接口允许 RTX 32P 以两个可能模式进行操作:主模式和从模式。在从模式中, RTX 32P 被 PC 控制以实现微程序装入、任意改变寄存器和系统上存储器的位置用于初始化和调试。在主模式下, RTX 32P 自由地运行程序,同时主机监视一个寄存器的状态用服务请求。当 RTX 32P 处于主模式的时候,主机可以进行一个特定的服务循环,或者执行其它的任务比如比如从磁盘输入流中读出下一个块,或者显示一幅图像,或者只是周期性地查询状态寄存器。在需要的情况下, RTX 32P 都会等待主机的服务。

5.3.3 指令集汇总

图 5.4 RTX32P 指令格式

RTX32P 只有一种指令格式,如图 5.4 所示。每个指令含有 9 位的操作码用于寻址微码页,还有一个 2 位的程序流控制字段指示一个非条件转移、子程序调用和子程序返回。在子程序调用和非条件转移的情况下, BIT2-22 给出 23 位字对齐目标地址的高 21 位。本设计限定程序大小在 8M 字节以内,但是通过使用存储器地址逻辑页寄存器可以实现远程跳转和子程序调用。数据存取时把存储器视为连续的 4G 字节的地址空间。

只要有可能, RTX32P 的编译器就把一个操作码和后面的一个子程序调用、子程序返回或者跳转指令压缩到一个指令中。如果不能压缩,就把一个 NOP 与 CALL JMUP 或者 RETURN 、 JUMP 到下一行指令与一个操作码压缩。消除尾递归就是把一个子程序调用改为对于子程序入口的跳转,这样可以节省一个执行调用之后的返回开销。

因为 RTX32P 使用 RAM 作为微码存储器,微码可以按用户的要求进行全部改变。 CPU/32 的标准软件环境是 MVP-Forth ,这是一种 Forth-79 的方言( Haydon 1983 )。包括在微码指令集中的一些 Forth 指令如表 5.2 所示。需要注意的是这个指令集的数量和复杂性。

! DDROP + DDUP +! DNEGATE - DROP 0 DSWAP 0< DUP 0= I 0BRANCH I' 1+ J 1- LEAVE 2* LIT 2/ NEGATE < NOP PICK NOT ROLL OR = OVER >R R> ?DUP R@ @ ROT ABS S->D AND SWAP BRANCH U* D! U/MOD D+ XOR D@

表 5.2(a) RTX 32P 指令集汇总 -- Forth 原语 ( 参看附录 B 的描述 )

@ (fetch a variable) @ + (fetch and add a variable) ! (store a variable) @ + DUP @ LIT + OVER + OVER - R> DROP R> SWAP >R SWAP ! SWAP - SWAP DROP

表 5.2(b) RTX 32P 指令集汇总 – 组合的 Forth 原语

RTX 32P 指令集可以按用户的要求扩展以支持特殊应用程序所需要的堆栈操作原语。

HALT -> -> 返回控制权给主机处理器 SYSCALL N -> -> 从主机请求 I/O 服务号 N DOVAR -> ADDR -> 用于实现Forth 变量 DOCON -> N -> 用于实现Forth 常量

表 5.2(c) RTX 32P 指令集汇总 – 特殊的字

下面这些 Forth 操作都由微码来完成它们大多数的工作。

SP@ (fetch contents of data stack pointer) SP! (initialize data stack pointer) RP@ (fetch contents of return stack pointer) RP! (initialize return stack pointer) MATCH (string compare primitive) ABORT" (error checking & reporting word) +LOOP (variable increment loop) /LOOP (variable unsigned increment loop) CMOVE (string move) EXP3 U4 -> Floating point normalize of unsigned 32-bit mantissa ADC N1 N2 CIN -> N3 COUT -> Add with carry. CIN and COUT are logical flags on the stack. ASR N1 -> N2 -> Arithmetic shift right. BYTE-ROLL N1 -> N2 -> Rotate right by 8 bits. D+! D ADDR -> -> Sum D into 32-bit number at ADDR. D>R D -> -> D Move D to return stack. DLSLN D1 N2 -> D3 -> Logical shift left of D1 by N2 bits. DLSR D1 -> D2 -> Logical shift right of D1 by 1 bit. DLSRN D1 N2 -> D3 -> Logical shift right of D1 by N2 bits. DR> -> D D -> Move D from return stack to data stack. DROT D1 D2 D3 -> D2 D3 D1 -> Perform double-precision ROT. LSLN N1 N2 -> N3 -> Logical shift left of N1 by N2 bits. LSR N1 -> N2 -> Logical shift right of N1 by 1 bit. LSRN N1 N2 -> N3 -> Logical shift right of N1 by N2 bits. Q+ Q1 Q2 -> Q3 -> 128-bit addition. QLSL Q1 -> Q2 -> Logical shift left of Q1 by 1 bit. RLC N1 CIN -> N2 COUT -> Rotate left through carry N1 by 1 bit. CIN is carry-in, COUT is carry-out. RRC N1 CIN -> N2 COUT -> Rotate right through carry N1 by 1 bit. CIN is carry-in, COUT is carry-out.

表 5.2(e) RTX 32P 指令集汇总 – 扩展的算术和浮点支持字

注意 : RTX 32P 使用 RAM 微码存储器,所以用户可以增加或者修改所需要的任何指令。以上所列的只是被标准开发软件包所提供的指令。

表 5.2 给出了一些可以作为单指令使用的常用 Forth 字组合。表 5.2C 给出了一些用于支持低层 Forth 操作的一些字,比如子程序调用和返回。表 5.2D 列出了一些支持被特殊微码支持高级 Forth 字,表 5.2E 给出了微码支持的增加字以支持扩展精度整数操作和 32 位浮点计算。

指令的执行时间依复杂度而变化很大。简单处理堆栈数据的指令比如 + 和 SWAP 需要 2 个周期(一个存储器周期)。复杂的指令比如 Q+ ( 128 位加)需要 10 个或者更多个微指令,但还是比高级代码执行得要快。如果需要,微码循环也可以做到延迟几千个周期以完成存储器块移动等工作。

图 5.5 RTX32P 微指令格式

就像前面说明的那样,每个指令访问微程序存储器页的一个微码序列,这个页对应指令的 9 位操作码。图 5.5 给出了微指令格式。微码使用水平编码,这意味着微码只有一个格式,它被分成几个字段来控制机器的不同部分。

如同 WISC CPU/16 ,堆栈计算机的简单性使得 RTX32P 的微码格式也很简单,这种情况下每个微指令只使用30 位,RTX32P 的微码格式和已经讨论过的 CPU/16 相似。

微码的 BIT0-3 指定了系统数据总线的源,总线源中的两个用来给出特殊控制信号以配置 RTX32P 实现一个时钟一位乘法和 32/64 位数的不可恢复除法。

BIT8-9 给出了数据总线目的,目的存在着两个特殊情况: DL0 可以单独指定作为总线目的而使用 BIT22-23 , DHI 总是用 ALU 的输出装入。 BIT8-9 和 10-11 分别指定数据堆栈指针和返回栈指针控制。 BIT12-13 控制 ALU 输出的移位器。这个移位器可以左移和右移,也有 8 位循环功能。

微指令的 BIT14-15 没有使用,因此也就不包含在微码 RAM 中, BIT16-20 控制 ALU 的功能。 BIT21 指定一个 0 或者 1 的进位,为了进行多精度运算,微码根据结果低半字的进位做一个条件分支,并强制下个进位对应为 0 或者 1 , IT22-23 控制 DL0 装入和移位。

BIT24-29 用于计算一个 3 位的偏移量来读出下一个微指令。 BIT24-26 选择 8 个码之一形成低地址位,同时 BIT27-28 作为常数产生高位地址。 BIT29 用于增量 9 位微程序计数器以允许使用多于 8 个微码的微码存储器位置。 BIT30 为下一个指令初始化译码序列,这种要求是因为指令是时钟周期长度是可变的。 BIT31 控制返回地址增量器,可以作为存储器块数据访问的计数器。

微指令在每个微码时钟周期内执行,每个机器微指令使用两个或者多个微周期执行。

5.3.4 体系结构特点

在 RTX32P 体系结构中可以明显地看到 WISC CPU/16 的传统,最明显的改进是增加了存储器地址结构的有效性以及在子程序调用和返回操作中把返回栈与数据栈隔离。这些改变加上 RTX32P 单一的指令格式使得子程序调用、返回和跳转能够与操作码组合起来,运行时不需要任何代价。

RTX32P 运行时钟是主存储器访问时间的 2 倍,这样在每个存储器周期将给出两个时钟,而每个指令最小为两个时钟周期时间。

RTX32P 指令格式有多种使用方法,其中很多并不是明显可见的,比如执行条件分支。 RTX32P 没有直接的硬件用于支持条件分支,因为这样将大大地降低其它指令的硬件执行速度或者要求很高的程序存储器访问速度,条件分支是这样完成的:通过把一个 0BRANCH 操作码和一个对分支目标的子程序调用组合起来,子程序调用是由硬件处理的,它与计算栈顶元素是否为 0 (如果是 0 则执行分支)的判断并行进行。如果发生分支,则 POP 返回栈,把子程序调用变成一个 JUMP 执行继续。如果分支没有发生,则微码 POP 返回栈,并使用这个值读取分支不匹配指令,其效果是执行一个立即的子程序调用。条件分支在发生时需要 3 个时钟而没有发生时需要 4 个时钟周期。记住:当我们这样说的时候,处理器的每个存储器周期是 2 个时钟周期。

RTX32P 的另一个有趣的能力是把任何的存储器位置作为一个变量来快速访问。尽管0- 操作数的指令格式看起来需要另一个存储器位置以指明变量地址,但可以使用这样的操作:把一个特殊的操作码和一个子程序调用一同编译,这时“子程序”的地址实际上是需要读取的变量的地址。微码然后“窃取”变量值作为指令读取逻辑读出,并在这个值被当成指令执行之前强制进行子程序返回。

这里讨论的两种方法主要是为了说明硬件有一些重要的能力,它们对使用传统计算机的程序员来说还不是明显可见的,这些能力在编写访问数据结构的程序时特别有用(例如专家系统的决策树),实际上它允许直接执行数据结构。这种直接执行是这样实现的:把数据放在一个具有 9 位标记的标记格式中(对应一个特殊的用户定义微码),使用一个 23 位的地址,它是一个对下一个元素的子程序调用或者跳转,或者在 NIL 指针中是一个子程序返回。

RTX32P 一个重要的实现特点是机器的所有资源都可以通过一个主计算机进行控制,这是因为主机接口支持微指令寄存器的装入和单步时钟。有了这些能力,一个微指令的执行就可以先装入值到任何一个或者所有的系统寄存器中,装入微指令,给出周期时钟,然后再把值读回来检查结果。这种技术使得微码的编写非常直接,省去了昂贵的外部开发硬件,也使得诊断程序易于编写。

RTX32P 支持中断处理,包括数据堆栈和返回堆栈的上/下溢出中断。对这种溢出,通常的处理方法是把片上堆栈内容的一半通过页交换放到程序存储器中,这样程序就能够使用任意深度的堆栈。通过使用 512 元素硬件堆栈缓冲大小,典型的 Forth 程序从来都不会出现堆栈溢出的情况。

5.3.5 实现和面向的应用领域

RTX32P 使用 2.5 微米CMOS 标准单元技术,通过 2 个芯片实现。数据通道芯片包含有 ALU 、数据栈、程序存储器的 ALU 位,它是一个 84 LCC 封装。控制芯片包含系统的其它部分,使用 145 PGA 封装。 RTX32P 运行在 8MHz 时钟频率上。

RTX32P 设计用于实时嵌入式控制应用,特别是要求低功耗、小尺寸的嵌入式系统应用领域。就像前面说的那样, RTX32P 是一个商用处理器的原型,计划命名为 RTX4000,这种处理器有几个特点使得它适合于实时控制应用,也适合做个人计算机的协处理器用于加速计算。它的应用包括:混合的 ROM 和 RAM 微码把系统缩小到一个芯片、独立操作、片上硬件支持浮点、更快的时钟速度和片上的动态程序存储器芯片支持。当然有些版本的芯片可能没有包含全部这些特点。另外,增强的体系结构可以支持C、ADA、LISP 语言,这是通过使用指令中地址字段来指明快速访问 21 位常数实现的,这就允许一些关键的操作比如栈帧指针加偏移量寻址能够高速地运行。

本章信息基于 WISC CPU/32 的描述 Koopman (1987c) 、 Koopman (1987d) 和 RTX32P 介绍 Koopman (1989).

5.4 SF1 体系结构 5.4.1 介绍

Wright State University's SF1 (名称的含义为堆栈帧计算机 1 号)是一个多堆栈的处理器,设计用来执行高级语言,包括 Forth 和 C 。它被设计成有大量的堆栈,这里描述的是 5 个堆栈的实现。 SF1 是以 Forth 语言为发端的,但是它跨越了ML0 和 ML2 机器的界限,每个指令可以直接从堆栈存储器中寻址任何两个堆栈元素。它也混合了 FRISC3 和 RTX32P 一些特点,也有一些独特的革新。

Wright State University 开发了一系列基于堆栈的计算机,最初是从 RUFOR (Grewe & Dixon 1984) 开始的,这是一个纯的基于 Forth 的计算机,使用位片元件实现。 1985 年到 1986 年,一个计算机体系结构班构造了分立元件原型,是这一个更加通用化的堆栈处理器,称为 SF1 。 1986-1987 年,一个 VLSI 班扩展了这个体系结构,并使用多片客户定制硅片实现了 VLSI 版本的 SF1 。下面描述的就是 VLSI SF1 实现。

SF1 的应用领域是使用Forth、C 和其它高级语言的的实时控制系统。

5.4.2 方框图

图 5.6 是 SF1 体系结构方框图

图 5.6 SF1 方框图

SF1 有两个总线 SBUS 和 MBUS 。 MBUS 是一个多路复用总线,用于传输地址到程序存储器、与程序存储器交换指令和数据, SBUS 用于在系统资源之间传输数据。双总线设计使得在 SBUS 发生数据操作的同时,指令可以从 MBUS 中取得。

ALU 有一个栈顶寄存器 TOS ,它可以接收所有 ALU 操作的结果。 ALU 输入寄存器ALUI作为一个保持缓冲区用来保存 ALU 的第二个操作数。 ALUI 可以从 SBUS 上得到用于大多数操作的操作数,也可以通过 MBUS 进行存储器读取。ALUI和 TOS 都可以定位到 MBUS 上和 SBUS 上。为了方便, TOS 寄存器保存栈顶元素以便用于特别的操作,尽管它可以让程序员适当地管理。

有 8 个不同的源和目的连接到堆栈总线

S - 用于参数和通用堆栈 L - 循环计数器堆栈 G - 全局堆栈 F - 帧堆栈 R - 返回地址栈 C - 在线常数值 I - I/O 地址空间 P - 程序计数器

在机器手册中,所有这8 个源都称为堆栈,但实际上,C、I 和 P 并不是堆栈结构。堆栈 L、G、F 和 R 在使用时的大多数情况下是可交换的,也可以用于任何目的。S 堆栈有些特殊,所有的子程序返回堆栈都自动地 PUSH 到 S 堆栈。

这些堆栈栈顶的 8192 个元素中的任何一个都可以被指定为总线的源或者目的。不论什么时候读取堆栈,栈顶元素都可以返回或者 POP 。当一个堆栈 POP 时,栈顶元素总是移出堆栈存储器,而不管实际读的是哪个元素。

与此相似,当一个堆栈用于总线目的时,栈顶的 8192 个元素的任何一个都可以写入,或者栈顶元素可以从 SBUS 上压入。

C 总线源用于从指令的地址字段上返回一个 13 位的有符号常数。 P 用于装入和存储程序计数器。 I 用于寻址 8K 字的 I/O 地址空间。

程序计数器 PC 是一个计数器,它可以向 MBUS 总线提供地址来读取指令,也用于从 MBUS 装入以实现子程序调用和 JUMP 。 PC 可以在 SBUS 上读写以保存和恢复子程序返回地址。

5.4.3 指令集汇总

SF1 有两种指令格式,如图 5.7 所示。第一种格式用于子程序调用和跳转,第二种格式用于其它指令。

图 5.7A SF1 指令格式 -- JUMP/CALL

图 5.7A 给出了跳转 / 子程序调用指令格式。这个指令中的 BIT0 为 0 。 BIT1 为 1 时跳转,为 0 时是子程序调用。 BIT2-31 是字对齐的 JUMP/CALL 目标地址。这个指令格式与图 5.4 给出的 RTX32P 非常相似,但是没有高位操作码。 JMUP 和子程序调用指令都是单周期执行的。

图 5.7B SF1 指令格式 -- 操作

图 5.7B 给出了操作指令的格式,它更像是图 5.2C 的 FRISC3 ALU 指令格式。在这种指令中, BIT0 恒为 1.

BIT1 选择是否为 SKIP 操作,如果选择了 SKIP 操作,则当 ZERO 标志寄存器设为 1 时,指令流的下一个指令被取出。这可以用于实现非 0 的条件分支指令序列。

BIT2-7 选择 ALU 操作,一个特殊的 ALU 操作返回前一个 ALU 指令产生的状态标志。这些标志可以作为一个用于多条件分支的多路跳转表偏移。这些条件分支比使用 SKIP 指令慢,但是更加灵活。

在讨论 BIT8-28 之前,让我们考察一下在一个时钟周期里 SBUS 的工作情况。 SBUS 在一个时钟周期中被使用两次,前半个时钟周期里, SBUS 用于读 8 个总线源的一个,读入的数据总是放到 ALUI 寄存器里。在时钟周期的后一半, ALU 根据新的 ALUI 值和老的 TOS 值执行操作。同时,老的 TOS 值被写回 8 个总线目的中的一个。指令中的 BIT29 可以覆盖 TOS 作为写入的值而强制 ALUI (它是在时钟周期的前半个周期刚刚装入的)在时钟周期的后半个周期里出现在 SBUS 上。

BIT8-11 选择 SBUS 目的。这个目的在指令的时钟后半个周期用前一个指令设置的 TOS 值写回。 BIT8 选择目的堆栈是 PUSH 或者仅仅是写回。与此相似, BIT12-15 选择 SBUS 源,它是在时钟前半个周期读入的。 BIT12 选择源堆栈是否被弹出。

BIT16-18 提供读写堆栈的地址,这个地址允许直接读写 8K 堆栈元素的任何一个。注意在指令中只有一个地址,所以在给定的周期内,源和目的堆栈必须使用同一地址。

BIT29 用于写入 SBUS 目的时覆盖 TOS 的值。当这一位设置时,使用 ALUI 寄存器而不再是 TOS 寄存器。这可以实现在两个 SBUS 源之间的直接数据移动,方法是在时钟的前半个周期内把值装入 ALUI ,在时钟周期的后半部分用同样的值存储。

BIT30-31 用于控制存储器访问。 BIT31 选择扩展指令周期,它在第二个时钟周期通过 MBUS 访问 RAM (第一个时钟周期用于读取下一条指令)。 BIT30 指定 RAM 读或者写操作, TOS 寄存器在第一个时钟周期读出并提供地址,在第二个时钟周期读或者写以提供或者接收数据。注意 RAM 的读写是在两个时钟周期的第二个里执行的,所以 BIT2-29 可以用于在第一个时钟周期内执行一个正常的操作。第一个时钟周期经常用于执行 TOS 重新装入操作( TOS 包含一个地址),装入的值就是在第二时钟周期中要写入 RAM 的值。

5.4.4 体系结构特点

我们又一次看到了这个重要性:通过 MBUS 的方式提供一个指定的通道用于取指,同时以 SBUS 的方式提供第二个通道用于数据处理。和其它的堆栈计算机一样, SF1 设计成支持指令的快速执行,特别是子程序调用的快速执行。

SF1 操作指令格式使用操作数的方式是非常新奇的,把单个栈顶寄存器作为 ALU 操作的一个输入和实际上只有一个单指令字段使得体系结构看起来像一个 1- 操作数堆栈计算机。然而,每个指令都可以指定源和目的的事实使这个机器看起来更像一个 2- 操作数计算机。也许把这种指令格式称为 1- 操作数指令更合适,因为在同时选择源和目的时只有一个地址可用。

每个堆栈有 8K 个直接可寻址堆栈元素的原因是为像 C 语言这类有栈帧要求的高级语言提供支持,另外,一个堆栈也可以作为一个非常大的寄存器文件使用,不进行特殊堆栈的 POP 和 PUSH 操作。

有几个硬件堆栈的原因是为了在实时控制应用中进行快速的上下文切换,尽管这里描述的实现仅仅包含 5 个硬件堆栈,但其数量在不同的设计版本中可以增加。分配堆栈的一个简单方法是为 4 个高优先级任务中的每个分配一个堆栈,再把第 5 个堆栈给低优级任务并在需要时从程序存储器中交换。

子程序返回是在程序控制下 POP 返回栈并把栈顶元素写入 PC 寄存器。因为使用了一个预取流水线,所以子程序返回之后的指令也将在有效返回前被执行。

32 位文字量通过使用 13 位偏移的 PC 相对寻址访问程序存储器的常数空间得到,这个常量典型地放到一个非条件分支之后,或者是在使用这个过程的子程序返回之后。

5.4.5 实现和面向的应用的领域

SF1 使用 3.0 微米 CMOS MOSIS 技术实现,使用全定制的方法,用了两个客户化芯片,一个芯片含有 ALU 和 PC ,另一个芯片实现 32 位的堆栈。控制和指令译码使用硬编码逻辑完成,但是最终组合进一个客户定制的 VLSI 中。

堆栈的实现与我们已经见到的堆栈计算机非常不同。因为堆栈必须设计成元素能够自由访问,一个明显的设计方法就是组合一个加法器和一个堆栈指针到标准存储器。这个方法的一个缺点是比较慢,另外也难于扩展到多芯片堆栈中。

而 SF1 采用的方法却完全不同。每个堆栈存储器实际上是一个巨大的移位寄存器,当堆栈 PUSH 或者 POP 时,在邻近的存储器字之间移动堆栈元素。寻址堆栈上的第 N 个字就是简单地访问存储器的第 N 个字,因为堆栈的顶部元素总是保持移位到第 0 个存储器地址。这种方法的一个缺点是移位寄存器单元比正规的存储器单元大,所以 SF1 最大堆栈的芯片只包含 32 位宽度的 128 个字。

SF1 最开始是时候是一个研究平台,通过快速上下文切换来强调实时控制(给每个任务指定一个堆栈芯片)并支持高级语言。

这个部分的信息基于 Dixon (1987) and Longway (1988) 对 SF1 描述。

第六章 理解堆栈计算机

在前面的章节中,我们已经抽象地描述了一个堆栈计算机,还具体地描述了几个实际的堆栈计算机例子。我们现在要讨论的是:为什么这些计算机要这样设计?为什么堆栈计算机与传统的计算机相比有其固有的优点?

本章使用了三个不同的计算机设计方法作为参照点。第一个参照是复杂集指令计算机 CISC ,最典型的是 VAX 系列和个人计算机上使用的微处理器;第二个参照是精简集指令计算机 RISC ,典型的是 Berkeley RISC 项目 (Sequin & Patterson 1982) 和 Stanford MIPS 项目 (Hennesy 1984) ;第三个就是我们在前几章已经讨论过的堆栈计算机。

6.1 讨论了在过去的许多年里,那些鼓吹基于寄存器的计算机、基于堆栈的计算机和存储器到存储器计算机人之间的争论,以及最近关于在 CISC 上实现高级语言和 RISC 体系结构的争论。

6.2 讨论了堆栈计算机的优点。堆栈计算机程序尺寸小、硬件复杂度低、系统性能高,在许多应用环境中比其它处理器执行的平滑性更好。

6.3 给出了 Forth 程序中指令执行频率的研究结果。我们一点儿都不奇怪的是:子程序调用指令在 Forth 程序中占了很大的比重。

6.4 给出了对堆栈进行访问模拟而得到的堆栈管理结果,结果显示对许多应用程序来说,最小需要 32 个堆栈元素空间。这里还讨论了处理堆栈溢出问题所采用的 4 个不同方法:使用很大的堆栈、请求式堆栈管理、分页的堆栈管理和使用联想式 CACHE 存储器进行堆栈管理。

6.5 考察了在堆栈计算机上中断和多任务的成本,一个模拟结果显示在许多环境中,上下文切换是次要的成本。而通过一些方法,堆栈缓冲区上下文切换的成本将进一步降低,这些方法包括:适当地编程中断、使用轻量级的任务、分配堆栈存储器到多个小的缓冲区。

6.1 历史的回顾

使用硬件支持堆栈计算机和其它体系结构设计者之间的争论已经有很长的历史了,这种争论可以分成两个主要的领域:在基于寄存器和非寄存器设计者之间的争论,高级语言机器设计者和 RISC 设计者之间的争论。我们不能够给这些争论一个进一步肯定的答案,参考文献中给出的思想值得有兴趣的读者考虑。

6.1.1 寄存器和非寄存器计算机

在设计计算机时是不是应该把寄存器显式地提供给程序员?这种设计决策可以回到 1950 年。现存的基于堆栈的 KDF.9 计算机( Haley 1962 )证明了在采用基于寄存器的设计方法的许多年之前就已经开始思考改变了。

对于是不是使用基于寄存器计算机的争论导致了几个解决方法:纯的基于堆栈的计算机、单操作数基于堆栈的计算机(也称为堆栈 / 累加器计算机)和存储器到存储器计算机。

纯的堆栈计算机是 SS0、 MS0 和 SL0 和 ML0 分类之一,它们都非常简单。喜爱基于堆栈计算机的一个明显论点就是表达式一类的计算需要使用类似堆栈的结构, 而基于寄存器的计算机在计算表达式时需要花一些时间来模拟堆栈行为。当然,纯的堆栈计算机可能需要多于堆栈/累加器计算机的指令(SS1、MS1、SL1、 ML1 分类),因为它们不能够同时读取一个值并对这个值执行算术操作。聪明的读者可能已经注意到了第 5 章讨论的堆栈计算机使用多操作数指令比如 Variable @ + 以便在很大程序上弥补这一缺陷。

在存储器到存储器计算机体系结构中,所有的操作数都在存储器中,这看起来对运行 C 或者 PASCAL 一类的高级语言很有价值。原因是这样:在这些高级语言中,大多数的赋值表达式在赋值操作符的右边都只有一个或者两个变量,这就意味着许多表达式都可以用单个指令处理,不再需要把数据在寄存器内外搬来运去。 CRISP 体系结构 (Ditzel et al. 1987a, 1987b) 是一个存储器到存储器处理器的例子。

在争论中最经常被引用的一些观点可以在这些论文中见到 Computer Architecture News: Keedy (1978a), Keedy (1978b), Keedy (1979), Myers (1977), Schulthess & Mumprecht (1977), and Sites (1978),这些论文并没有涉及所有的问题而且某些方面已经过时了,但是它们为那些有兴趣研究这些争论历史的人提供了一个好的起点。

6.1.2 是高级语言还是 RISC 计算机

一个相关的争论是使用高级语言计算机还是使用 RISC 哲学来进行计算机设计。

高级语言计算机可以作为 CISC 哲学的进化。这些计算机有潜在的非常复杂的指令可以直接映射到一个或者多个高级语言的函数,在某些情况下,一个编译器前端用于产生可以被机器直接执行的中间代码,比如用于 PASCAL 的 P- 代码或者用于 MODULA-2 的 M- 代码。这种哲学的极限扩展可能是 SYMBOL 项目( itzel & Kwinn 1980 ),它用硬件实现了全部的系统功能,包括源程序编辑和编译。

高级语言支持的 RISC 体系的哲学是为编译器提供最简单、可能的构造块进行综合高级语言的操作。这通常包括装入、存储和算术操作的代码序列以实现每个高级语言语句,RISC 设计者宣称他们所选择的代码序列可以运行得比 CISC 计算机上等效的复杂集指令要快。

堆栈计算机设计哲学在某种程序上位于高级语言设计和 RISC 设计之间,堆栈计算机提供非常简单的原语,它们可以在单存储器周期中执行完成,符合 RISC 哲学的精神。然而,堆栈计算机上高效率的应用程序代码是通过廉价的子程序调用完成的,这些子程序的选择可以看成是一个虚拟指令集,它们被裁剪成适合高级语言编译器的需要,不需要复杂的硬件支持。

关于高级语言和 RISC 计算机之间的争论可以参考 Cragon (1980), Ditzel & Patterson (1980), Kavipurapu & Cragon (1980), Kavi et al. (1982), Patterson & Piepho (1982), and Wirth (1987).

6.2 与传统计算机的体系结构差异

堆栈计算机和传统计算机之间的明显差异是前者使用0操作数堆栈寻址取代后者的寄存器或者存储器寻址,当这种差异与快速子程序调用相结合的时候,就使得堆栈计算机在下列方面超过了传统计算机:程序大小、处理器复杂度、系统复杂度、处理器性能和程序执行的一致性。

6.2.1 程序尺寸

人们常说:“存储器很便宜”。这样说是因为他们看到了存储器尺寸快速增长的历史,知道随着时间的增加,芯片可用存储器的大小可以得到梦幻般的增加。

问题是在芯片容量增加的同时,人们使用计算机解决问题的尺寸也在一致性地增加,这就意味着程序和数据集的尺寸比可用存储器容量增加得更快。这种情况随着人们在各个程序设计阶段广泛使用高级语言而变得更为严重,这种结果当然是巨大的程序,但是也提高了程序生产率。

不奇怪,程序复杂性的爆发看起来有些矛盾,有人说“让程序扩展到填满整个存储器,然后再使用其中的一部分”。程序可用存储器的数量对于一个应用程序来说是固定的,这完全是出于对存储器芯片成本、印刷电路板面积等等经济性方面的考虑,它也受到机械性能方面的影响,比如功耗、散热和系统中扩展槽的限制(这也是出于经济性方面的考虑)。就算不考虑成本和预算,电气负荷和连线延迟速度等等方面最终将限制处理器可用存储器芯片的数量。小的程序尺寸能够降低存储器成本、元件数量、电源要求,通过把成本高效率地分配给更小的、更高速度的存储器芯片来提高系统的速度。附加的收益包括:在虚拟存储器环境中性能的改进 (Sweet & Sandman 1982, Moon 1985) ;只需要很少的 CACHE 芯片就可以提高命中率。某些应用场合、特别是嵌入式处理器应用场合,对印刷电路板空间和存储器芯片非常苛刻,因为这些资源是所有系统成本中固定不变的那一部分 (Ditzel et al. 1987b).

面对不断增加的程序尺寸,传统的解决办法是使用分层的存储器器件作一系列的容量/成本/访问时间的折衷。层次组成包括(从最廉价/最大/最慢到最昂贵/最小/ 最快):磁带、光盘、硬盘,动态存储器、片外 CACHE 和片上指令缓冲存储器。所以,对“存储器很便宜”这句话更为准确的说法应该是:“慢速的存储器很便宜,但快速的存储器却真的死贵”。

存储器问题最后归结于以处理器所能支付的合理的价格为处理器提供一个数量足够多、速度足够快的存储器,这是通过把大多数程序安排到最快的存储器层次中来完成的。

管理最快速存储器层次的通常办法是使用 CACHE 存储器。 CACHE 存储器的工作原理是:一小段程序在一定的时间内会被使用多次,这样,当这一小段程序第一次被引用时,它就被从慢速的存储器复制到 CACHE 中并在其中保存供以后使用,这为第二次和以后的连续访问减少了延迟。因为 CACHE 的容量是有限的, CACHE 中的指令在它的位置被用于最新的取指时可能被替换, CACHE 的问题是它必须足够大以便为偶尔的重用保留足够长的代码片段。

CACHE 存储器足够大到保存一定数量的指令称为“工作集”,可以有效地改进系统的性能。程序的大小如何影响程序性能的提高呢?假设我们在工作集中给出一定数量的高级语言操作,考虑提高代码紧缩性后的影响。直觉告诉我们,如果完成一个高级语言语句的代码序列在 A 机器上比在 B 机器上更紧缩,则 A 机器就需要比 B 机器更小的 CACHE 字节就可以保存下同样代码产生的指令。这就意味着 A 机器只需要较小的 CACHE 就可以得到相同的存储器响应时间。

Davidson and Vaughan (1987) 通过实际的例子提出建议,对于相同的程序, RISC 使用的 CACHE 大小应该是 CISC 的 2.5 倍(尽管其它的来源特别是 RISC 开发商,把这个数目说成是 1.5 倍多一点儿)。他们也建议为了得到相同的性能, RISC 计算机需要 CISC 体系结构 2 倍的 CACHE 。进一步说, RISC 计算机需要 2 倍于 CISC 机器的 CACHE 仍然会产生 2 倍的 CACHE 非命中率(因为对于 2 倍的 CACHE 访问,固定的非命中率导致 2 倍的非命中),结果为了得到同样的性能就需要更快速的主存储器器件,于是我们有这样的规则:运行于 10MIPS 的 RISC 处理器对于满意的性能需要 128K 字节的的 CACHE 存储器,而高端的 CISC 处理器典型地只需要不多于 64K 字节的 CACHE 。

堆栈计算机与 RISC 和 CISC 相比,其程序尺寸更小,堆栈计算机的程序大约比 CISC 代码小 2.5 至 8 倍( (Harris 1980, Ohran 1984, Schoellkopf 1980) ,尽管后面的讨论会对这个结论做出一些限制。这意味着为了得到相当的存储器响应时间, RISC 处理器的 CACHE 存储器大小与堆栈处理器的全部程序存储器一样大!这是真的吗?我们考虑这样的情况:就在 RISC 处理器上的 UNIX/C 程序员面对 8M 到 16M 字节的存储器和 128K 字节的 CACHE 而闷闷不乐的时候, Forth 程序程序却在热烈地争论着堆栈计算机上的程序是不是真的需要多于 64K 字节空间。

堆栈计算机小的程序尺寸不仅节省存储器芯片进而减少了系统成本,同时它也实际提高了系统的性能。这是由于提高了指令在需要时就驻留在高速存储器中的机会,而有可能的是把全部的程序都放到快速存储器中去。

堆栈计算机为什么只需要这么小的存储器呢?有两个因素说明在堆栈计算机上做到极小的程序尺寸是可能的。一个比较明显的因素也是在论文中经常引用的,堆栈计算机的指令格式小,传统的体系结构不仅需要在每个指令中指定一个操作,而且需要指定操作数和寻址模式。例如,一个典型的把两个数相加的操作在基于寄存器的计算机是是这样的:

ADD R1,R2

这个指令不仅需要指定 ADD 操作码,而且还要说明相加的两个寄存器,它们是 R1 和 R2.

相反,基于堆栈的指令集只需要指定 ADD 操作,因为操作数隐含地就是指当前的栈顶,只有在执行一个装入或者存储指令或者把一个立即数放到栈上的操作时才需要指定操作数。 WISC CPU/16 和 Harris RTX 32P 分别使用 8 位和 9 位的操作码,保留的很多操作码为有效地运行程序提供了空间。本书中讨论的其它处理器比如 Novix NC4016 使用宽松的编码指令,可以把 2 个或者多个操作包装在一个指令中,这可以在一定程度上弥补面向字节代码密度的损失。

堆栈计算机能够更紧缩代码的一个不太明显但是更重要的原因是它们能有效地支持高频率子程序的重用编码,常常被称为串线编码( Bell 1973, Dewar 1975 )。当这些代码运行在传统的计算机上时,其执行速度将大受影响。事实上, RISC 和 CISC 大多数基本的编译器优化就是把过程调用编译成内嵌的宏。另外,许多程序员也有这样的经验:在传统计算机上过多的过程调用将损害程序的性能,这些都导致传统计算机上的程序明显过大。

相反,堆栈计算机能够有效地支持过程调用。因为所有的工作参数都在堆栈上,过程调用的开销最小,不需要为参数传递而花费存储器周期。在许多堆栈处理器上,过程调用只需要一个时钟周期,而子程序返回在大多数情况下可以与其它操作组合而不需要任何时钟周期。

在说明堆栈计算机代码比其它计算机紧缩的时候我们有几个条件,特别是像我们这样没有给出一个全面的研究结果的情况下。程序的尺寸很大程序上依赖于所使用的程序设计语言、编译器、程序设计风格和处理器所使用的指令集。同时, Harris 、 Ohran 和 Schoellkopf 对堆栈计算机的研究主要是基于可变长度的指令,而本书中描述的处理器都是 16 或者 32 位的固定长度指令。

考虑固定指令长度下面这样一个事实:运行 Forth 的处理器比其它堆栈计算机的程序更小。程序更小是因为它们更经常地使用子程序调用,允许在一个单一的应用程序中进行高度的代码再用。同时,我们在后面部分还可以看到:即使是 32 位的处理器比如 RTX32P ,也不像通常大家所想象的那样占用许多存储器空间。

6.2.2 处理器和系统复杂度

当我们考察一个计算机的复杂度时,重要的是两个级别:处理器的复杂度和系统的复杂度。处理器的复杂度是处理器核心中实际用于计算的逻辑单元数量(用芯片面积、晶体管数等来度量);系统的复杂度是考虑嵌入一个全功能系统的处理器,同时也包括电路、存储器和软件。

CISC 计算机这些年来已经变得极端复杂,这种复杂性来源于要求它们能同时很好地满足许多功能。一个很大的复杂性就是要求使用许多指令格式对各种不同的指令进行紧密的编码,其它的复杂性来自于它们对多个编程和数据模型的支持。没有一个机器能同时有效地支持所有这样的应用:在一个时间片内既处理 COBOL 的 PACKED十进制数,又进行 FORTRAN 的浮点矩阵操作,还要处理 LISP 的专家系统,什么都想做到,那当然是极端地复杂了。

CISC 的复杂性一部分是为了保持程序较小而进行编码的结果。它的目标是缩小高级语言和机器指令的语义间隙,并产生更高效的代码。不幸的是,这样就使得几乎所有的芯片可用面积都用来实现控制和数据通道了(比如 Motorola 680x0 和 Intel 80x86 产品)。 所以, RISC 的设计者总是说 CISC 设计者可能是“赔了性能,又折了尺寸”。

某些 CISC 处理器所具有的极端复杂性看起来有些不可思义,但它们却来自于一个普遍的和良好的目标:在硬件和软件之间建立起一个一致的和简单的接口。其成功可以从 IBM SYSTEM/370 产品线上见到,这种计算机系列包含了各种不同的性能和价格,从个人计算机插卡到超级计算机都使用同样的汇编语言指令集。

软件和硬件在汇编语言级清晰一致的接口意味着编译器不需要很复杂就能够产生各方面可接受的代码,它们也可以在同一系列的不同机器上共用。 CSIC 的另一个优点是:因为代码非常紧缩,它们不需要为提高系统性能而使用大容量的 CACHE ,也就是说,CISC 通过提高处理器复杂度而减少了系统复杂度。

RISC 计算机背后的概念是通过减少它的复杂度而使处理器运行得更快。在这一点上,实际的处理器内部比 CISC 使用更少的晶体管用于控制电路,它的实现是使用简单的指令格式和低语义内容的指令,每条指令不做太多的工作,但是只需要很少的时间就可以完成。指令格式通常以适合运行某种特殊程序设计语言和任务的需要而进行选择,典型地是 C 程序设计语言的整数运算。

减少处理器的复杂度并不是没有成本的,许多 RISC 处理器使用巨大的寄存器体来保证经常访问的数据能够快速地重用,这些寄存器必须是双端口存储器(以便允许两个不同地址能够同时访问)来实现每个周期的两个源的读取。更进一步,由于这些指令都是低语义内容的, RISC 处理器必须有更高速的存储器带宽以保证进入 CPU 的指令流。这意味着为了得到应有的性能,片上和系统中固定的资源必须是 CACHE 存储器。同时, RISC 处理器的特点之一就是内部的流水线,这意味着需要有更多的硬件和编译技术来管理流水线。在中断处理时,应该特别注意并使用额外的硬件资源来确保流水线状态被正确地保存和恢复。

最后,不同的 RISC 实现策略对编译器提出了更高的要求:调度流水线以避免竞争冒险、填充分支延迟时隙、管理寄存器体的分配和溢出。在减少处理器复杂度以便更容易地得到没有 BUG 的硬件的同时,把更多的复杂性推给了编译器,这就使得编译器更加复杂,开发和调试成本更高。

减少 RISC 处理器的复杂度导致了偏向于(甚至可能更为严重地)增加了系统的复杂度。

堆栈计算机努力改善处理器复杂度和系统复杂度之间的平衡。堆栈计算机设计者认识到处理器的简单性不仅受限于指令的数量,更受限于指令可能操作的数据:所有的操作都是对堆栈顶部元素的。从这个意义上说,堆栈计算机不应该是“减少了指令集的计算机”,而应该是“减少了操作数集的计算机”。

限制操作数选择而不是指令做多少操作的观点确实有几个优点,指令可以非常紧缩,因为它们只需要指定实际的操作,而不需要指定源是从哪里来的。片上堆栈存储器可以是单端口的,因为每个时钟周期只有一个元素需要进行堆栈的 PUSH 或者 POP 操作(假设栈顶的两个元素保存在寄存器中)。更重要的是,因为已经知道所有下一步的操作数都是堆栈元素,所以不需要使用流水线进行操作数预取。操作数总是在栈顶寄存器中因而是立即可用的,作为一个例子,我们可以考虑 NC4016 设计中的 T 和 N 寄存器,并把它们与数以十计甚至数以百计的 RISC 计算机中可随机访问的寄存器相比较一下。

选择隐含的操作数也简化了指令格式,就算是 RISC 计算机也有多种指令格式。然而,堆栈计算机只有很少的指令格式,甚至更极端的比如 RTX32P 只有一种指令格式,限制指令格式的种类简化了指令译码逻辑,加快了系统的操作速度。

堆栈计算机格外地简单:16位堆栈计算机的核心典型地只使用 20000到35000个晶体管。比较 Intel 80386 芯片有275000 晶体管,Motorola 68020 有200000 晶体管。就算 80386 和 68020 是 32 位的计算机,这其中的差异也是明显的。

堆栈计算机的编译器也很简单,因为指令的格式和操作选择是一致的。事实上,寄存器计算机的编译器通过一个像堆栈一样的表达式视图用于表达式计算,然后把这些信息映射到寄存器集中。堆栈计算机的编译器在进行堆栈方式的源程序版本映射到汇编语言时只需要做很少的工作。 Forth 编译器的简单和灵活是众所周知的。

堆栈计算机系统总体上也非常简单,因为堆栈程序是如此之小,以至于为了高性能而进行 CACHE 控制的方法是多余的,典型的程序可以全部放到 CACHE 速度的存储器中,而不需要复杂的 CACHE 控制电路。

在程序和/或者数据太大而不能放到存储器的情况下,可以使用软件管理的存储器分层:经常使用的子程序和程序段可以放到高速存储器中,不经常使用的程序段放到慢速存储器中。对高速存储器进行成本低廉的单周期调用使得这种技术非常有效。

数据堆栈在许多场合可以作为一个数据 CACHE 看待,比如过程的参数传递,数据元素可以在软件的控制下按需要从高速存储器中移入和移出。虽然传统的数据 CACHE 、或者多多少少地扩展一下成指令 CACHE,的确能够改进一些速度,但堆栈计算机在运行小的或者是中等规模的应用时是不要求甚至是不需要CACHE的。

因此,堆栈计算机通过限制指令可用的操作数而降低了处理器的复杂度。这样做并没有强制性减少可用的指令数,也没有引起支持处理器运行的硬件和软件发生大的改变。这种减少复杂度的结果是堆栈计算机为片上程序存储器和片上专用硬件留下了更多的空间,一个隐见的影响是:堆栈计算机的程序是如此之小以至于许多应用程序可以完全放到片上。这种片上存储器比片外的 CACHE 更快,它也将省去复杂的控制电路而没有任何速度方面的牺牲。

6.2.3 处理器的性能

处理器的性能是一个很难说清楚的方面,数不清的功夫耗费在争论哪一个处理器比另一个更好,但这些功夫常常基于粗略的、明显可疑的测试程序,其热情也主要来自于自己的兴趣和对具体产品的忠心(或者是存在购买关系)。

之所以难于进行比较的原因主要是应用领域的问题,使用整数算术运算的测试程序并不能适合浮点性能、商业应用或者符号处理。最好也不过是使用测试程序说明在给定的硬件(相关联的 CACHE 、存储器、硬盘、时钟速率等)、使用给定的操作系统、使用给定的编译器、使用相同的编程语言的情况下, A 处理器比 B 处理器更好,但这也只是测试程序的结果。很明显,测试不同的计算机性能是很困难的。

测试完全不同的体系结构就更加困难。困难的核心是单个指令做多少工作。因为 VAX 计算机上多项式运行指令所作的工作量与 RISC 上寄存器到寄存器间移动的指令是大不相同的,“每秒指令数”的概念往最好里说也只能是精细(甚至当归一化到标准指令测试、使用同样的测试程序,我们还是不能真的相信)。另外的问题是不同的处理器使用不同的技术实现(双极性、ECL、SOS、NMOS 和 CMOS 将有不同的尺寸)和不同的设计实现策略(昂贵的全定制布线、标准单元自动布线、门阵列布线)。然而,非常概念地比较不同的体系结构就要求扣除不同的实现技术的影响。更进一步,执行的软件对性能有非常大的影响,问题是在真实的世界中,一个指定计算机的效率并不仅仅决定于处理器的速度,也要受到系统硬件、操作系统、程序语言和编译器性能和质量的影响。

所有这些困难给了读者一个结论:精确性能测试的问题不可能在这里解决。相反,我们集中讨论一些原因:为什么堆栈计算机可以比其它种类的计算机每个指令都快?为什么堆栈计算机有良好的系统性能特点?它最适合于运行哪一类的程序?

6.2.3.1 指令执行的速率

图 6.1A 指令阶段的重叠 -- 原始指令阶段

最诡辩的 RISC 处理器宣称它们有最高的执行速率 -- 一个时钟一条指令。它们是通过流水线完成的,把指令分成几个序列:地址发生、指令读取,数据存储周期,如图 6.1A 所示。这种在指令流上的加速削弱了指令的执行,同时也产生了一些问题,最明显的就是为了避免由数据依赖引起的竞争,当一条指令依赖于前面指令的结果时就会产生这样的问题。因为第二条指令在读取它的操作数时必须等待第一条指令把结果存入。有几个硬件和软件策略可以减轻数据依赖的影响,但是没有一个能够完全解决这个问题。

堆栈计算机可以把程序执行得和 RISC 一样快,由于没有数据依赖问题而可能更快。有一种说法是寄存器计算机由于能够使用流水线而比不能使用流水线的堆栈计算机执行得有效,这是由于事实上每条指令都依赖于前面的指令在堆栈上的影响。但现在的问题,堆栈计算机不需要流水线就可以和 RISC 一样快。

考虑 RISC 计算机的指令流水线在用于堆栈计算机时如何设计。两种机器都需要读取指令,两种机器都可以与前面的指令并行执行。为了方便,我们的把这些与指令译码一同考虑,由于堆栈计算机比如 RTX32P 不需要执行条件操作以从指令中分出参数字段或者选择使用哪种格式,所以它比 RISC 计算机更简单。

在流水线的下一步操作中,主要的差别开始变得明显。 RISC 计算机必须花费一个流水线阶段来访问指令后面的操作数(至少某些情况是这样)才能完成译码。一个 RISC 指令指定两个或者多个寄存器作为 ALU 操作的输入。而堆栈计算机不需要读取数据;它们在需要时等待栈顶数据。这就意味着在最小的情况下,堆栈计算机可以省去流水线的操作数读取部分。实际上,堆栈访问也可以做得比寄存器访问更快,这是由于单端口的堆栈存储器可以做得很小,也当然就比双口寄存器存储器更快。

RISC 和堆栈计算机的指令执行部分可以认为是相同的,因为同样的 ALU 可以用于两种系统中。但就是在这种情况下,堆栈计算机也可以有 RISC 所没有的优点,比如在 M17 堆栈计算机上,ALU 功能可以在指令译码之前对栈顶元素进行操作。

操作数存储阶段在某些 RISC 设计中需要占用存储器的另一个周期,因为结果必须写回寄存器文件。这种写回与新指令的读是有冲突的,需要延迟或者用一个三口的寄存器文件。这也可能要求 ALU 把输出保持在一个寄存器中,然后这个寄存器在下一时钟周期里作为寄存器文件写操作的源。相反,堆栈计算机只是简单地把 ALU 输出的结果放到栈顶寄存器中就完成了。另外的一个问题是 RISC 处理器需要附加的前向数据逻辑以保证在 ALU 的输出作为下一个指令的 ALU 输入时结果写入了寄存器。而堆栈计算机 ALU 输出总是可用的,它作为 ALU 的一个隐含的输入。

图 6.1B 指令阶段重叠 -- 典型的 RISC 计算机

图 6.1C 指令阶段重叠 -- 典型的堆栈计算机

图 6.1B 显示出, RISC 处理器需要至少3个或者4个流水线阶段以维持相同的吞吐率:指令读取、操作数据读取、指令执行 / 操作数读取。同时,我们也注意到 RISC 设计方法与生俱来的几个问题比如数据依赖和资源争夺在堆栈计算机中都非常简单地“不存在”。图 6.1C 给出了堆栈计算机只需要两级流水线:指令读取和指令执行。

所有这些都意味着堆栈计算机没有理由比 RISC 处理器在执行指令时更慢,而使用相同的工艺,堆栈计算机只会比 RISC 计算机更快、也更简单。

6.2.3.2 系统性能

系统性能可能比原始的处理器性能更难于度量,它不仅包括每秒钟能执行多少直线编码的指令,也包括处理中断的速度、上下文切换的速度和由于条件分支、子程序调用引起的性能降低。一些方法比如三维计算机性能 (Rabbat et al. 1988) 比原始的指令数据更能反映计算机的系统性能。

RISC 和 CISC 计算机都是用来执行正常情况下的直线编码,在这些机器上执行很多的过程调用将严重影响它们的性能。调用过程的成本不仅包括保存程序指针和读取不同的指令流,也包括保存和恢复寄存器、安排参数和可能发生的流水线破坏。而一个称为返回栈的实际数据结构在堆栈计算机上对于控制流结构比如过程调用来说非常重要,因为堆栈计算机在硬件上保存了所有的工作变量,在把参数传送给子程序时所要求的时间就非常少,通常只需要一个DUP 或者OVER 指令。

对于所有种类的处理器来说,处理条件分支都是很困难的,其中的原因是指令的预取机制和流水线机制的前提是指令不被中断,这样才能保持繁忙,而条件分支将使得分支在未决时被迫等待。唯一的一个选择是在可能的路径之前预测以希望在等待分支时进行非破坏性的工作。 RISC 机器使用“分支延迟时隙” (McFarling & Hennesy 1986) 并放置一个非破坏性指令或者是 NOOP ,它在分支之后总是执行。

堆栈计算机以不同的方法处理分支,它能够在单周期内实现分支而不需要要延迟时隙,也不需要增加编译器的复杂性。 NC4016 和 RTX2000 通过指定存储器周期比处理器周期更短来处理这个问题,这就意味着在处理器周期中有时间为条件分支产生一个地址,并且还能够在时钟周期的尾部读取指令。这种方法工作得很好,但问题是半导体工艺的进步总是使得处理器速度的提高远远高于程序存储器速度的提高。

FRISC3 为条件分支产生一个分支指令,并和下个指令一同完成分支。这是一个非常聪明的办法,因为在任何机器上大多数的分支指令之前总是需要一个比较或者其它操作。除了比较操作之外(通常是一个减法),FRISC3 也指定哪一个条件是下一条分支指令所关心的。这就把大部分的分支决策工作交给了比较指令,而在随后执行条件分支时只需要测试一个单独的位。

RTX32P 使用微码把比较和条件分支组合到两个指令周期中,它们与比较指令后随一个条件分支的时间相同。例如, = 0BRANCH 可以合为一个单个的 4 微码周期(两个指令周期)的操作中。

对于中断处理,堆栈计算机比 RISC 和 CISC 计算机都简单。在 CISC 计算机中,复杂的指令使用多个周期才能完成,所以它们需要被中断。这就要求大量的处理开销,以及在指令的中间保存控制逻辑和恢复机器状态。 RISC 计算机也好不到哪儿去,因为它们有一个流水线,需要在每个中断中保存和恢复。它们也还有寄存器需要保存和恢复以便于中断服务程序得到工作所需要的资源。在 RISC 和 CISC 机器上,通常需要几百微秒来响应一个中断。

另一方面,堆栈计算机处理中断的时间典型地只需要几个时钟周期。中断作为由硬件执行的子程序调用来处理,不需要清除和恢复流水线,处理器处理中断需要做的唯一事情就是把中断响应地址作为一个子程序调用插入到指令流中,把堆栈掩码寄存器 PUSH 到堆栈上(以防止对中断服务子程序的递归调用)。一但进入中断服务程序,则不需要保存任何寄存器,因为新的子程序只需要简单地把它的数据 PUSH 到栈顶。为了说明堆栈处理器有多快的中断处理速度,我们看一个例子: RTX2000在中断响应确认和中断服务程序的第一条指令执行之间只需要花费 4 个时钟周期(400 ns)。

人们可能会认为堆栈计算机的上下文切换比其它处理器慢,但我们后面会看到,事实并不是这样。

堆栈计算机的最后一个优点是:它们的简单性为客户在微处理器中实现应用指定硬件留下了许多空间。例如, HARRIS RTX2000 有一个片上的硬件乘法器,其它的应用指定硬件包括 FFT 地址发生器、A/D、 D/A 或者是通讯接口。这样的硬件可以有效地减少最终系统中元件的数量,并能够极大地减少程序执行时间。

6.2.3.3 堆栈计算机最适合哪些程序运行?

能够在堆栈计算机上有效执行的程序包括子程序调用敏感的程序、有大量控制流结构的程序、执行符号计算(通常使用堆栈结构和递归来解决问题)的程序、设计用来处理高频率中断的程序、在有限存储器上运行的程序。

6.2.4 程序执行的一致性

先进的 RISC 和 CISC 处理器依赖于许多特殊的技术,它们能够在一个相当长的时间段里达到静态的高性能,但是并没有办法保证在短时间内的高性能。它们使用的技术包括:指令预取队列、复杂的流水线、记分牌、 CACHE 存储器、分支目标缓冲区、分支预测缓冲区。当然,现在的问题是这些技术都不能保证处理器在任何特定时间里的瞬时高性能。外部事件或者内部数据一个不合适的序列可能导致 CACHE 存储器完全失配、队列刷新和其它的延迟。当然,平均的高性能也可以为某些程序所接受,但是对于许多实时应用来说,可预测的连续一致高性能更为重要。

堆栈计算机没有使用任何一种前面列出的动态加速技术,但是却得到了优秀的系统性能。堆栈计算机程序执行的简单性,得到了在任何时间度量中都非常一致的性能。我们在第 8 章中可以看到,这对实时控制应用程序的执行有巨大的影响。

6.3 Forth 指令频率研究

我们已经对堆栈计算机与其它计算机的区别有了一个概念性的理解,现在我们可以对堆栈计算机的执行给出一些数量上的统计结果。测试基于堆栈计算机和基于寄存器的计算机的指令指令频率和代码大小的工作包括: Blake (1977), Cooke & Donde (1982), Cooke & Lee (1980), Cragon (1979), Haikala (1982), McDaniel (1982), Sweet & Sandman (1982), and Tanenbaum (1978)) 。不巧的是,许多结果都是从传统语言编写的程序中得到的,而不是基于天生就是基于堆栈的语言 Forth 。 Hayes et al. (1987) 给出了一个 Forth 程序的执行统计,我们将对此加以扩展。

本章的结果都是基于 Forth 编写的程序,这样可以利用堆栈计算机的大多数优点。注意测试程序都是静态的,所以这些结果应该视为“TRUTH”的近似(无论你怎么理解)。

下面部分引用了 6 个不同的测试程序。除了另有说明,所有的程序都是用 16 位 Forth 编写的。它们是:

Frac: 一个分形图表产生程序,它使用一个随机数发生器。它总是使用相同的初始值作为种子来产生图形 (Koopman 1987e, Koopman 1987f)

Life: Conway的生命游戏在80列25行字符显示器上的简单实现。每个程序都计算机一个全屏幕移动的10代。

Math: 一个32位的浮点程序包,完全用高级Forth代码编写,没有使用诸如归一划一类的机器原语,每个程序运行用以产生1-10度整数的正弦、余弦、正切函数表。 (Koopman 1985)

Compile: 一个脚本用于编译Forth程序,测试Forth编译器本身的执行。

Fib: 使用递归过程方法计算24th Fibonacci 数,也称为"dumb" Fibonacci.

Hanoi: Hanoi 塔问题,编写成一个递归过程,每个程序运行计算12个盘子。

Queens: N 皇后问题 (从8皇后问题导出),编写成递归过程。程序找到N x N 棋盘上的第一个可接受的位置,每个程序运行计算N = 12 皇后。

有三个程序最好地代表了不同应用领域的混合,它们是: Math -- 它使用了密集的堆栈操作以便在 16 位堆栈计算机上管理 32 位数(还有一个 48 位的中间浮点格式);Life -- 通过许多条件分支对存储器单元的矩阵进行管理; Frac -- 它进行图形画线和基本的图形映射。

编译器测试程序也是有用的,它反映了编译器的行为,它必须对输入流进行 TOKEN 化并进行标识符搜索。

6.3.1 动态指令频率

NAMES FRAC LIFE MATH COMPILE AVE CALL 11.16% 12.73% 12.59% 12.36% 12.21% EXIT 11.07% 12.72% 12.55% 10.60% 11.74% VARIABLE 7.63% 10.30% 2.26% 1.65% 5.46% @ 7.49% 2.05% 0.96% 11.09% 5.40% 0BRANCH 3.39% 6.38% 3.23% 6.11% 4.78% LIT 3.94% 5.22% 4.92% 4.09% 4.54% + 3.41% 10.45% 0.60% 2.26% 4.18% SWAP 4.43% 2.99% 7.00% 1.17% 3.90% R> 2.05% 0.00% 11.28% 2.23% 3.89% >R 2.05% 0.00% 11.28% 2.16% 3.87% CONSTANT 3.92% 3.50% 2.78% 4.50% 3.68% DUP 4.08% 0.45% 1.88% 5.78% 3.05% ROT 4.05% 0.00% 4.61% 0.48% 2.29% USER 0.07% 0.00% 0.06% 8.59% 2.18% C@ 0.00% 7.52% 0.01% 0.36% 1.97% I 0.58% 6.66% 0.01% 0.23% 1.87% = 0.33% 4.48% 0.01% 1.87% 1.67% AND 0.17% 3.12% 3.14% 0.04% 1.61% BRANCH 1.61% 1.57% 0.72% 2.26% 1.54% EXECUTE 0.14% 0.00% 0.02% 2.45% 0.65% Instructions: 2051600 1296143 6133519 447050

表 6.1. 重要 Forth 原语的动态指令执行频率

表 6.1 给出了许多常用 Forth 原语的执行频率: Frac、Life、Math 和 Compile。动态指令执行频率就是在一个程序运行过程中一个指令的执行次数。附录 C 给出了对应表 6.1 完整的结果。AVE 列为四个测试程序的加权平均结果,这可以看成是大多数 Forth 程序的近似结果,选择表中 Forth 字的原则是:它们或者是 AVE 列的前10 位,或者是一个特殊程序的前 10 位。例如, EXECUTE 在 AVE 中只占 0.65% ,但是在编译中占 2.45% ,它在编译测试中列第 10 位。

这些数字中最明显的一件事就是子程序调用和返回操作远远多于其它操作,这也很好地说明了为什么源自于 Forth 的堆栈处理器都特别强调子程序调用/返回与其它指令组合的影响。子程序返回的数量比子程序调用要少,因为某些 Forth 操作直接弹出返回栈元素进而返回两级子程序调用 -- 这是执行一个子程序调用的条件返回。

消耗在堆栈处理原语上的时间也很有趣。在所有的样例指令中,大约有25%花在处理堆栈上。这初看起来很高,然而,我们应该想到,因为堆栈处理器都有把堆栈处理和其它工作组合起来的能力 (比如组合 OVER -) ,这个数应该比我们所看到的要高得多。而这25%由于浮点数学包在处理 32 位数需要使用 R 操作而增大了 5% 。这种代价在 32 位处理器或者使用快速访问用户存储器空间(比如 NC4016 和 RTX2000 )的 16 位机器上不会存在。

另一个有趣的方面是为了准备处理而把数据读取到堆栈上的过程也非常重要(这个过程包括在 VARIABLE、@、LIT 、 CONSTANT 和 USER 中)。幸运的是,堆栈计算机可以把这些指令与其它操作组合在一起。

最后一个结论是:许多在附录C 中列出的指令其动态执行频率都少于1% ,但是,并不能因此就认为这些指令是不重要的,其中许多指令如果没有硬件支持,则执行时就会耗费很长的时间,所以不能仅仅依靠执行频率来决定一个指令是否重要。

6.3.2 静态的指令频率

NAMES FRAC LIFE MATH COMPILE AVE CALL 16.82% 31.44% 37.61% 17.62% 25.87% LIT 11.35% 7.22% 11.02% 8.03% 9.41% EXIT 5.75% 7.22% 9.90% 7.00% 7.47% @ 10.81% 1.27% 1.40% 8.88% 5.59% DUP 4.38% 1.70% 2.84% 4.18% 3.28% 0BRANCH 3.01% 2.55% 3.67% 3.16% 3.10% PICK 6.29% 0.00% 1.04% 4.53% 2.97% + 3.28% 2.97% 0.76% 4.61% 2.90% SWAP 1.78% 5.10% 1.19% 3.16% 2.81% OVER 2.05% 5.10% 0.76% 2.05% 2.49% ! 3.28% 2.12% 0.90% 2.99% 2.32% I 1.37% 5.10% 0.11% 1.62% 2.05% DROP 2.60% 0.85% 1.69% 2.31% 1.86% BRANCH 1.92% 0.85% 2.09% 2.05% 1.73% >R 0.55% 0.00% 4.11% 0.77% 1.36% R> 0.55% 0.00% 4.68% 0.77% 1.50% C@ 0.00% 3.40% 0.61% 0.34% 1.09% = 0.14% 2.76% 0.29% 0.26% 0.86% Instructions: 731 471 2777 1171 表 6.2. 重要 Forth 原语的静态执行频率

表 6.2 给出了 Frac 、 Life 和 Math 在许多编译原语中的静态编译频率和在 Compile测试程序中被编译程序最常使用的原语(包括 Frac 、 Queens 、 Hanoi 和 Fib )。一个指令的静态频率是它在源程序中出现的次数,AVE 列给出了4个测试程序的加权平均,它可以看成是大多数 Forth 程序编译的大致结果。这里列出的 Forth 字是 AVE 列的前10 名,或者是位于某个特殊程序的前 10 位。

在这种静态结果中,子程序调用非常频繁,大约占全部编译指令的1/4 。注意 Frac 由于包含在 Compile 中而被计算了两次,所以实际的子程序调用数应该略低于表中所列的值。

6.3.3 RTX32P 的指令压缩

由于子程序调用是如此普遍,我们也就不奇怪为什么大多数堆栈处理器都有把子程序调用指令与其它指令组合的机制。一个关注点是,在源代码中子程序调用比子程序返回更加普遍,把它与其它指令结合就更有吸引力。

在第5章中讨论的 RTX32P 有一个独特之处是它有单一的指令格式,把操作码与子程序调用/子程序返回/无条件转移组合起来。这初看起来好象浪费了存储器空间,但实际上是性能大大提高而存储器成本也相对较低。不幸的的,这种单一指令格式只能用于 32 位的处理器,因为16 位处理器指令中没有足够的位来组合操作码和大的地址字段。

表 6.3 和 6.4 是利用 32 位优点编写的 Frac、Life 和 Math 程序的执行和编译结果统计。

6.3.3.1 执行速度的收益

表 6.3 是专门为 RTX32P 优化的动态程序执行的4 个统计结果。表 (A) 给出的是没有把操作码和子程序压缩在一起、没有窥孔优化(操作码组合)的程序执行结果。

表 6.3. RTX32P 指令类型的动态指令执行频率

FRAC LIFE MATH AVE OP 57.54% 46.07% 49.66% 51% CALL 19.01% 26.44% 19.96% 22% EXIT 10.80% 12.53% 16.25% 13% OP+CALL 0.00% 0.00% 0.00% 0% OP+EXIT 0.00% 0.00% 0.00% 0% CALL+EXIT 0.00% 0.00% 0.00% 0% OP+CALL+EXIT 0.00% 0.00% 0.00% 0% COND 5.89% 9.95% 6.56% 7% LIT 6.76% 5.01% 7.57% 6% LIT-OP 0.00% 0.00% 0.00% 0% VARIABLE-OP 0.00% 0.00% 0.00% 0% VARIABLE-OP-OP 0.00% 0.00% 0.00% 0% Instructions: 8381513 1262079 940448 OP-OP 0.00% 0.00% 0.00% 0%

表 6.3(a) 关闭指令压缩和操作码组合

 

FRAC LIFE MATH AVE OP 50.92% 42.22% 45.94% 46% CALL 17.81% 28.31% 21.42% 23% EXIT 12.48% 13.42% 17.45% 14% OP+CALL 0.00% 0.00% 0.00% 0% OP+EXIT 0.00% 0.00% 0.00% 0% CALL+EXIT 0.00% 0.00% 0.00% 0% OP+CALL+EXIT 0.00% 0.00% 0.00% 0% COND 6.82% 10.66% 7.05% 8% LIT 2.60% 1.94% 2.53% 2% LIT-OP 5.21% 3.43% 5.59% 5% VARIABLE-OP 2.67% 0.00% 0.01% 1% VARIABLE-OP-OP 1.49% 0.00% 0.01% 1% Instructions: 7250149 1178235 875882 OP-OP 4.72% 3.68% 1.76% 3%

表 6.3(b) 关闭指令压缩,进行操作码组合 ON.

表的 B 部分是把常用的操作码序列(比如 SWAP、DROP、OVER、+、Variable、 @ 和 Variable @ + )组合在一个指令中而产生的影响。标记为 OP-OP 的列是把两个操作按单一的操作码对待,包括 OP 、 OP-CALL 、 OP-EXIT 和 OP-CALL-EXIT 。 LITERAL + 、 LITERAL AND 等等是特殊情况,全部标记为 LITERAL-OP ,而 Variable @ 和 Variable ! 标记为 VARIABLE-OP. @ + 和 Variable @ - 标记为 VARIABLE-OP-OP 。所有的常数和变量的特殊情况都需要一个完整的指令来保持一个操作码和地址,没有同其它指令组合。对于例子程序,操作码的窥孔优化可以减少 10% 的指令执行。

FRAC LIFE MATH AVE OP 48.84% 31.26% 40.81% 40% CALL 8.46% 22.20% 15.53% 15% EXIT 4.57% 0.00% 4.80% 3% OP+CALL 13.93% 11.47% 6.68% 11% OP+EXIT 7.71% 15.96% 12.90% 12% CALL+EXIT 0.80% 0.00% 2.04% 1% OP+CALL+EXIT 0.15% 0.00% 0.03% 0% COND 7.23% 12.69% 7.99% 9% LIT 8.31% 6.39% 9.22% 8% LIT-OP 0.00% 0.00% 0.00% 0% VARIABLE-OP 0.00% 0.00% 0.00% 0% VARIABLE-OP-OP 0.00% 0.00% 0.00% 0% Instructions: 6827482 990313 772865 OP-OP 0.00% 0.00% 0.00% 0%

表 6.3(c) 打开指令压缩,关闭操作码组合

表 C 给出了用指令压缩代替操作码组合的影响,这意味着只要可能,操作码就与后面的子程序调用和返回合并在一起。子程序调用后随一个返回则组合成一个无条件转移,结果是总数的 24% 能够实现操作码与子程序调用/返回组合。这使得源程序中的40% 子程序调用是“免费的”,而几乎所有的子程序返回都是免费的。除了特殊的指令,比如常数和返回栈处理指令,它们无法与子程序返回相组合。

  FRAC LIFE MATH AVE OP 39.05% 24.91% 39.19% 34% CALL 6.75% 24.27% 15.94% 16% EXIT 6.54% 0.01% 10.78% 6% OP+CALL 12.71% 12.53% 6.87% 11% OP+EXIT 6.78% 17.44% 7.40% 11% CALL+EXIT 0.95% 0.01% 2.10% 1% OP+CALL+EXIT 0.09% 0.00% 0.03% 0% COND 7.84% 13.86% 8.21% 10% LIT 3.00% 2.52% 2.95% 3% LIT-OP 6.00% 4.45% 6.51% 6% VARIABLE-OP 3.08% 0.00% 0.01% 1% VARIABLE-OP-OP 1.72% 0.00% 0.01% 1% Instructions: 6294109 906469 752257 OP-OP 5.44% 4.79% 2.05% 4%

表 6.3(d) 指令压缩打开,操作码组合打开

表 D 给出了同时进行操作码组合和指令压缩的影响。结果是比原始的程序小了 25%。这种性能的提升几乎不需要软件和硬件的扩展,因为在子程序调用和操作码之间有天生的并行性。

有趣的一点是, Math 测试程序从16位系统上的610万指令减少到在 RTX32P 上的 94 万指令,说明32位处理器对于浮点计算是很有必要的。Life 测试程序(它几乎都是使用 8 位和数据处理)在各个系统上几乎相同。 Frac 测试程序增加了 4 倍,但是这是由于 32 位版本的 Frac 使用了更高的图形分辨率,有 4 倍的点数需要计算,所以需要大约 4 倍的指令。

6.3.3.2 存储器尺寸的成本

把子程序调用与操作码结合对性能的提升是值得的,特别是由于它本质上不需要在处理器中增加硬件。事实上,它通过只用一种指令格式而简化了硬件。现在还有一个问题需要解决:它的存储器成本如何?

幸运的是,Forth 程序的静态子程序调用频率比动态频率更高。这为操作码/子程序调用组合提供了一个成熟的机会。表 6.4 给出了静态程序尺寸的的差异:没有经过压缩的原始程序和同时进行指令组合和代码压缩的程序。

表 6.4. RTX 32P 指令类型的静态指令频率

  FRAC LIFE MATH AVE OP 48.40% 51.46% 44.72% 48% CALL 28.48% 33.01% 35.64% 32% EXIT 5.12% 6.41% 7.55% 6% OP+CALL 0.00% 0.00% 0.00% 0% OP+EXIT 0.00% 0.00% 0.00% 0% CALL+EXIT 0.00% 0.00% 0.00% 0% OP+CALL+EXIT 0.00% 0.00% 0.00% 0% COND 3.52% 4.46% 4.04% 4% LIT 14.48% 4.66% 8.05% 9% LIT-OP 0.00% 0.00% 0.00% 0% VARIABLE-OP 0.00% 0.00% 0.00% 0% VARIABLE-OP-OP 0.00% 0.00% 0.00% 0% Instructions: 1250 515 2422 OP-OP 0.00% 0.00% 0.00% 0%

表 6.4(a) 指令压缩关闭,操作码组合关闭

FRAC LIFE MATH AVE OP 33.71% 35.78% 37.05% 36% CALL 17.33% 21.94% 27.03% 22% EXIT 1.47% 2.87% 2.39% 2% OP+CALL 11.65% 21.15% 10.54% 14% OP+EXIT 3.78% 4.70% 1.73% 3% CALL+EXIT 1.05% 1.04% 4.02% 2% OP+CALL+EXIT 0.42% 0.00% 1.17% 1% COND 4.62% 6.00% 4.98% 5% LIT 16.17% 4.18% 8.61% 10% LIT-OP 2.83% 2.08% 1.32% 2% VARIABLE-OP 5.46% 0.26% 1.01% 2% VARIABLE-OP-OP 1.47% 0.00% 0.15% 1% Instructions: 952 383 1965 OP-OP 2.73% 5.22% 1.98% 3%

表 6.4(b) 指令压缩打开,操作码组合关闭

RTX32P 使用 9 位的操作码、21 位的地址、2 位的控制字段。如果我们想假设一种优化的封装指令格式,我们可以如下设计:使用 11 位来指定操作码,有一个单独的子程序返回位,再有一个 23 位的子程序调用/ 返回格式。同样,如果我们假设这种指令格式可以通过把子程序返回与操作码结合或者通过用 JUMP 来代替子程序调用而使所有的子程序返回没有任何花费,那么这种假设的机器应该有 11 位或者 23 位可变字长度。但我们不用担心,因为这只是我们理论计算而得到的最小数值。

在优化的格式中,三个程序一起由 1952 位操作码组成(所有都是 11 位), 1389 子程序调用(每个 23 位), 565 个结合操作码 / 地址位( 34 位),它们之和是 72640 位。

现在让我们来考虑一下在 RTX32P 上优化编译的程序,考虑每个指令都使用 32 位的固定长度编码,则有一些没有使用的余量,全部指令是 32 位的 3300 条,总计105600 位,存储器开销是 32960 位,或者比理论计算的结果要“浪费” 31% 的存储器。

当然,设计一个使用 11 位操作码和 23 位子程序调用的计算机是很整洁的。但更实际地考虑,我们应该看到在压缩版本中的程序“空白”操作码是 766 位(每个 9 位),“空白”子程序调用是 917 (每个 23 位),总数是 27985 位 -- 仅用 27% 的浪费就换来了 25% 执行指令的减少。所以我们说,就算是与可变长度指令相比,我们也是用相对低的成本就得到了很大的性能提升。

这一部分的测试中还有一个小问题,就是用于测试的程序都相对较小,程序执行了很复杂的操作,所以可以看到堆栈计算机程序是紧缩的,问题是很大的 Forth 程序难以得到。不过,所选择的程序包括了通常 Forth 代码的相关部分,从作者的观点看,如果能够得到更大的程序,结果也是可信的。

当然,我们也有一个办法来得到更大的程序,那就是使用传统的高级语言编译器的输出,但是,这类代码有完全不同的特点,因为程序员解决问题的方法在 Forth 和 C 、 FORTRAN 中是不同的,我们可以在第 7 章中再讨论这个问题。

6.4 堆栈管理策略

由于堆栈计算机依赖于每个指令中对高速堆栈存储器的访问,所使用的堆栈存储器的特点就是至关重要的了。特别是随着处理器越来越快,问题就产生了:为了得到高性能,需要在片上安排多少堆栈存储器?对这个问题的回答是至关重要的,因为它决定了把存储器放到芯片上的这样一类高端堆栈处理器的成本和性能。

一个同等重要的问题是:如何管理程序存储器,特别是在多任务的环境中?

6.4.1 评估堆栈尺寸:一个试验

第一个问题是需要多少片上存储器作为堆栈,最好通过对不同的程序用不同大小的存储器来仿真。这种仿真测量的是由于堆栈上下溢出而产生的与存储器进行数据交换的数量。上溢时需要从硬件堆栈上拷贝数据元素到存储器,下溢时需要把数据元素从堆栈缓冲存储器拷贝回来。

FRAC LIFE MATH FIB HANOI QUEENS # INSTRUCTIONS 2051600 1296143 6133519 880997 235665 140224 MAX STACK DEPTH 44 6 23 25 52 29 # STACK OPERANDS 3670356 1791638 11786764 1483760 446642 257320 # STACK SPILLS FOR BUFFER SIZE: 0 3670356 1791638 11786764 1483760 446642 257320 2 838960 148448 3919622 370940 155656 41426 4 202214 4098 1313566 92732 69608 9216 8 39040 0 238020 13526 32752 512 12 10236 0 28300 1970 8184 196 16 3580 0 800 284 4088 64 20 1532 0 280 38 2040 22 24 636 0 0 2 1016 10 28 220 0 0 0 504 2 32 92 0 0 0 248 0 36 36 0 0 0 120 0 40 10 0 0 0 56 0 44 0 0 0 0 24 0 48 0 0 0 0 8 0 52 0 0 0 0 0 0

表 6.5. 花费在数据堆栈分裂上的存储器扩展周期

图 6.2 数据栈分裂

表 6.5 和图 6.2 给出了一个仿真结果,它对程序 Life、Hanoi、Frac、Fib 、 Math 和 Queens 花在数据堆栈分裂和恢复的存储器周期数进行监控。“TOY” 程序中的 Hanoi 和 Queens 不代表典型的程序,它们都是深度递归的,可以看成是堆栈程序的最坏情况。

分裂算法就是在堆栈缓冲区满时进行 PUSH 操作的情况下,每次从堆栈中分裂出一个数据元素和在堆栈空时执行 READ/POP 的情况下每次读出一个数据元素到堆栈中。仿真假设硬件自动地处理堆栈,并以每个存储器周期读或者写一个元素的代价完成。使用 RTX32P 指令集进行这个仿真,每个指令相当于那些硬连线处理器比如 RTX2000 的 2 倍复杂度,测量的周期数是存储器周期。仿真的目的是显示所期望的最佳行为,当然,在大多数实现中都需要约 3-4 倍的因子。

令人惊奇的是, Frac 程序几乎与 Hanoi 程序一样坏, 这是由于 Frac 在进行大量的定点除法的子除步骤时,每次都把6个元素 PUSH 到堆栈上。很明显,所有的递归程序都会在堆栈上产生大量的数据元素。

关于堆栈尺寸的好消息是对所有的应用程序,堆栈的上下溢出的存储器交换以指数方式减少。在堆栈缓冲区尺寸为 24 时,甚至 Hanoi 程序所产生的堆栈溢出也只有指令的 1% 。实际上,如果堆栈的尺寸达到 32 个元素时,几乎所有的程序都不会出现堆栈溢出情况。

FRAC LIFE MATH FIB HANOI QUEENS # INSTRUCTIONS 2051600 1296143 6133519 880997 235665 140224 MAX STACK DEPTH 14 7 30 22 14 39 # STACK OPERANDS 725224 680676 3199170 185472 41056 53722 # STACK SPILLS FOR BUFFER SIZE: 0 725224 680676 3199170 185472 41056 53722 2 326778 135608 1235678 57312 12310 26070 4 179938 118 642798 21890 2048 13306 8 27932 0 273686 3192 128 1158 12 132 0 57262 464 8 572 16 0 0 13442 66 0 314 20 0 0 1062 8 0 154 24 0 0 382 0 0 62 28 0 0 42 0 0 32 32 0 0 0 0 0 16 36 0 0 0 0 0 8 40 0 0 0 0 0 0

表 6.6. 用于返回栈分裂的存储器同期

图 6.3 返回栈分裂

表 6.6 和图 6.3 给出同样程序的返回栈分裂和恢复仿真结果。结果相似, Math 程序需要大量的返回栈。这是由于数学包为了在不同的系统之间保持可移植而严格地采用模块化方式编写,所以使用了大量的深层次的子程序嵌套。同时,Math 程序使用返回栈来存储大量的临时变量以便能在16位处理器上处理 48 位数据。

6.4.2 溢出处理

现在我们已经考察了在程序执行时是如何发生堆栈上溢和下溢的,问题是如何处理它们呢?处理堆栈分裂有 4 个可能的方法:保证溢出永不发生;把它们作为灾难性的系统崩溃;使用按需请求的堆栈控制器;使用页式堆栈控制逻辑或者使用一个数据 CACHE 存储器。每种方法都有它自己的优势和弱势。

6.4.2.1 一个非常大的堆栈存储器

解决堆栈问题的最简单的方法就是假设堆栈溢出从来都不会发生。这种方法初看起来好象很愚蠢,但它也有一些优点,使用这种策略的最好结果就是系统性能完全可以预测(没有堆栈分裂来减慢系统),也不需要任何的堆栈管理硬件。

这种方法使用大量的堆栈存储器以避免溢出。MISC 的 M17 就使用了这种方法,它只在程序存储器容量耗尽时才发生堆栈溢出。 NC4016 使用的方法是高速的片外存储器,它可以容纳几千个数据元素,这些处理器都是简单地忽略溢出问题。使用这种方法的价格是在片外存储器大小/速度和处理器速度之间的一个折衷。

在使用小容量片外存储器的情况下,这种方法把溢出作为一个系统崩溃的灾难性事件。在程序进行调试时,可以简单地告诉程序员说堆栈只能有 X 个元素,程序员有责任不超过这个限制。如果编写的程序很小、很简单, X 的值又大于 16 或者 32 ,这种方法也很实用。 WISC CPU/16 使用这种方法,把堆栈元素设为 256 ,这样就可以保持堆栈简单。

6.4.2.2 请求式单元素堆栈管理器

让堆栈的溢出按一定的规则发生,处理这个问题最概念性的办法是使用一个请求式堆栈管理器,它按需要在堆栈和存储器之间移动单个元素。

为了实现这个策略,堆栈缓冲区按环形缓冲区构建,并有头和尾指针。另外还需要一个指向存储器的指针用于记录存储器中堆栈的栈顶位置。只要堆栈溢出,栈底缓冲区驻留的元素就被复制到存储器中,释放一个缓冲区位置。如果下溢,就从存储器中复制一个元素到缓冲区。这种技术只有在绝对需要时才由处理器移动元素,保持了堆栈流量的最小。

对这种技术可以做一些小的改进,堆栈管理器可以总是保持堆栈中几个元素的空或者满。这种管理可以利用空闲的存储器周期,并减少由于溢出而暂停的次数。不幸的是,这种小的改进在真正的堆栈计算机上可能没有什么价值,因为处理器总是用 100% 的时间使用程序存储器访问指令和数据,并没有给堆栈管理器剩下带宽。

请求式堆栈管理的好处是堆栈缓冲区元素总是可用的。因此,它适合于那些有芯片空间可以用于堆栈缓冲区的系统中。作为一个附加的好处,堆栈的上下溢出在整个程序的执行过程的每个指令中最大只有 2 次,这就是数据栈分裂同时子程序返回溢出。这种高性能的代价是相对复杂的硬件控制电路和每个堆栈都需要三个计数器来实现这个策略。

FRISC3 的堆栈管理策略与请求式策略很像。这个系统对此有了深入的研究,一个通用化的算法称为 cutback-K algorithmwas 由 Hasegawa & Shigei (1985)提出, Stanley & Wedig (1987) 也讨论了 RISC 计算机的栈顶元素缓冲管理策略。

6.4.2.3 页式堆栈管理

与请求式堆栈管理策略不同的一种方法是在堆栈上溢或者下溢时产生一个中断,然后用软件来管理堆栈分裂。这种方法比请求式管理所使用的硬件要少,但是要求更大一些的堆栈缓冲区来减少中断的频率。

这种方法的一个通常做法是使用“限制寄存器”来指向堆栈缓冲区空间顶部和底部的的附近位置。当一个指令引起堆栈指针小于下溢指针时,相当于全部缓冲区一半的元素将从程序存储器中复制到堆栈中。当一个指令引起堆栈指针越过上溢指针时,相当于堆栈尺寸一半的元素被复制到程序存储器中。

页式请求策略允许大堆栈存储器的任意尺寸段在一个时间片中用于不同的过程。由此,堆栈存储器作为特殊存储器的一段出现,而不作为环形缓冲区的方式出现。因此在实际应用中,一个堆栈溢出实际上是复制缓冲区的一半到存储器中,然后在堆栈缓冲区的开始位置重新定位另一半。

页式管理方法的成本按消耗于移动元素的存储器周期计算是请求式管理的2倍。缓冲区的尺寸也应该是请求式的2倍才能保证在上溢和下溢之间有相同的 PUSH 和 POP 数,不过在实际应用中这种增大的尺寸几乎没有什么价值。

使用这种方法有趣的一点是程序上下溢出的编程风格不太可能受到欢迎,因为如果堆栈的大小是 32 个元素,则就完全不再需要它们。对于那些超过了它们缓冲区尺寸的程序,这种方法提供了一种降级。依靠这种方法,一个病态的程序也可以运行(尽管很慢),而操作系统可以产生一个警告信息来指示错误的程序。

RTX2000 和 RTX32P 都使用了页式方法进行堆栈管理。

6.4.2.4 联合 CACHE

许多传统处理器管理程序堆栈的方法是使用一个传统的数据 CACHE ,通常映射到程序存储器空间。这种方法与我们前面讨论的堆栈计算机相比,应用了非常复杂的硬件,但是并没有提供任何优点,因为堆栈计算机并不经常随意地访问它的元素。对于可变长度的数据结构比如串和记录,当这些内容像 C 或者 ADA 程序环境定义的那样 PUSH 到“堆栈”上时也能提供一些好处。

其它有关堆栈管理的有趣论文是: Blake (1977), Hennesy (1984), Prabhala & Sethi (1977), and Sites (1979).

6.5 中断和多任务

中断处理的性能有三个组成部分:第一个是处理器收到中断和处理器开始执行中断服务程序之间的时间,这个延迟称为中断潜伏。

第二中断处理的性能组成部分是中断处理时间,这个时间量是处理器实际花费的保存中断任务机器状态并开始执行中断服务程序的时间。通常需要保存的机器状态都很少,假设中断服务程序可以通过只保存那些准备使用的寄存器而使开销最小。有时,我们见到术语“interrupt latency”,它通常是指前面两项之和。

中断服务性能的第三个部分是我们所说的状态保存开销。这些是为了保存那些没有被机器中断处理逻辑自动存储的、但是在中断服务程序中必须使用的机器寄存器的时间。状态保存可以差异很大,依赖于中断服务程序的复杂度。在极端的情况下,状态保存开销需要切换多任务的全部上下文。

当然,恢复这些机器状态并退出中断服务程序的开销也决定着系统的性能。我们不在这里明确地讨论,因为它们差不多等于状态保存时间(因为保存的每样东西都必须恢复),但是它们对中断响应的时间要求并不重要。

6.5.1 中断响应延迟

CISC 计算机可以有需要花费很长时间才能完成的指令,极大地延长了中断的响应。堆栈计算机像 RISC 计算机一样可以有很快的中断响应延迟,这是由于许多堆栈计算机指令只是单周期长,所以按最坏的情况来考虑,在中断识别和中断处理之间只有几个时钟的延迟。

然而,一但中断被处理, RISC 和堆栈计算机的差异就开始表现出来。 RISC 计算机必须通过一个流水线来保存识别中断时的过程,在返回时恢复过程流水线,以避免丢失特别指令的信息。另一方面,堆栈计算机没有指令执行流水线,所以只需要保存正在执行的下一条指令的地址。这意味着可以把中断视为一个由硬件产生的过程调用。当然,因为过程调用很快,所以中断处理时间很短。

6.5.1.1 指令重新启动

堆栈响应延迟可能有一个问题,这就是流化指令和微码循环。

流化指令用于重复执行一个操作比如写栈顶元素到存储器中。这些指令使用 NC4016 和 RTX2000 的重复指令、 M17 的指令缓冲、 CPU/16 RTX32 的微码循环来编写。这些原语非常有用,它们可以用来构建高效率的字符串处理原语和堆栈上下溢出服务子程序。现在的问题是在多数情况下,这些指令也是不可中断的。

一个解决办法是通过外部中断逻辑使得这些指令可中断,它只是很少地增加了处理器的复杂性。一个潜在的硬件问题是非堆栈处理器也用这种解决方法来保存中间结果,对于一个堆栈处理器没有任何问题,因为中间结果保存在堆栈上,它是一个在中断期间保存状态的理想机制。

堆栈处理器的另一个方法是使用软件限制用于流指令的重复次数。这就意味着如果有效100 个字符的块需要在存储器中移动,可以通过每组8个字符的几个组来完成。这就使得在不牺牲性能的情况下满足断响应时间的要求。这是在绝对计算机效率(使用长的流指令)和中断响应时间之间的一个折衷。

在微码机器中,折衷与此相似,然而,在 RTX32P 的商业版本中,有一个非常简单的微码策略做得更好。这种策略是使用一个微码可见的条件位指示是否有中断未决。在每个微码循环的迭代中都要测试这个中断未决位,但是这并不消耗任何执行时间。如果没有未决的中断,则进行一次循环。如果有中断未决,流指令的地址就被 PUSH 到返回栈上作为中断返回的地址并允许中断程序执行。只要流指令所有的状态都保存在堆栈上(它只是一个简单的操作比如字符块移动),则这种方法在处理中断时只有很小的开销,而在正常的指令执行时却没有任何开销。

6.5.2 轻量级中断

让我们考察不同的中断所需要的保存状态的程度:快速中断、多任务的轻量级线程中断和完全的上下文切换。

快速中断是运行时最常见到的中断。这些中断做的事情是为时间计数器增加几个毫秒,或者从输入口拷贝几个字节到存储器。传统的计算机处理这种中断时,它们通常都需要保存两、三个寄存器到程序存储器以便在寄存器文件中腾出工作空间。在堆栈计算机上,没有任何状态信息需要保存,中断服务程序可以简单地把它的信息 PUSH 到栈顶而不干扰被中断程序的信息。所以对于快速中断来说,堆栈计算机的状态信息保存开销为0。

轻量级线程是多任务系统中的任务,它的执行策略与我们刚才描述的中断相同。它们可以得到多任务的收益而不需要全过程的开始和结束成本。一个堆栈处理器可以简单地要求每个任务执行很短的指令序列来简单地实现轻量级线程,这可以被称为非剥夺或者是协同的任务管理。如果每个任务在它的操作期间都不把参数留在堆栈上,则在任务之间就没有上下文切换的开销。这种方法的多任务成本本质上是 0 ,因为一个任务只是在程序的一个逻辑转换点上把控制交给任务管理器,那里堆栈应该是空的。

从这两个例子可以看出,在堆栈计算机上,中断处理和轻量级线程多任务是非常廉价的。留下的唯一问题是需要上下文切换才能完成的全过程的、可剥夺的多任务。

6.5.3 上下文切换

上下文切换开销是大家常说的“堆栈计算机不能很好地运行多任务”的一个问题。这种观点背后的的原因通常是基于这样的想法:需要把大量的堆栈缓冲区保存到程序存储器中。但是说堆栈计算机在运行多任务时比其它计算机机性能更差的说法是可笑的。

上下文切换对所有的任务系统来说都是潜在的开销,在使用 CACHE 的 RISC 和 CISC 计算机中,上下文切换的开销可能比厂商说的更昂贵,上下文切换之后还有一个隐藏的结果是由于 CACHE 失配而带来的性能恶化。 RISC 计算机还有很大的寄存器文件,它们也严格地需要面对堆栈计算机的同样问题。而 RISC 计算机的一个缺点是由于它们对寄存器的访问是随机的,所以每次必须保存所有的寄存器(或者加入复杂的硬件来检测哪个寄存器正在使用),但是堆栈计算机却可以只保存堆栈缓冲区的活动记录。

6.5.3.1 一个上下文切换的实验

表 7 给出了从追踪驱动仿真器得到的数据,它是 Forth 程序在一个上下文切换的环境中花费在保存和恢复数据堆栈元素的存储器器周期数。程序仿真 Queen、 Hanoi和 Quick-sort 。对于 Queen 和 Hanoi 使用小的 N 值以保持仿真时间合理。堆栈上下溢出和上下文切换的影响同时被测量,因为在这样的环境中它们是严重地交织在一起的。

表 6.7 对于不同频率上下文切换和不同堆栈尺寸分裂的存储器扩展周期

Combination of Queens, Qsort, Hanoi # Instructions = 36678 Average stack depth = 12.1 Maximum stack depth = 36 Buffer Size timer=100 timer=500 timer=1000 timer=10000 2 17992 16334 16124 15916 4 12834 9924 9524 9214 8 8440 3950 3430 2910 12 10380 3944 3068 2314 16 11602 2642 1620 632 20 12886 3122 1846 626 24 13120 2876 1518 330 28 14488 3058 1584 242 32 15032 3072 1556 124 36 15458 3108 1568 82

表 6.7(a) 页式缓冲区管理

BufferSize timer=100 timer=500 timer=1000 timer=10000 2 26424 24992 24798 24626 4 11628 8912 8548 8282 8 7504 3378 2762 2314 12 6986 1930 1286 630 16 7022 1876 1144 322 20 7022 1852 1084 180 24 7022 1880 1066 124 28 7022 1820 1062 90 32 7022 1828 1060 80 36 7022 1822 1048 80

表 6.7(b) 请求式缓冲区管理

图 6.4 页式堆栈管理的开销

图 6.4 – 页式管理的开销

表 6.7A 和图 6.4 给出了页式管理的结果。文字"xxx CLOCKS/SWITCH" 指示在上下文切换之间的时钟数,在上下文切换之间的时钟为 100 时,用在管理堆栈上的存储器周期随着缓冲区尺寸的增加而减少。这是由于在程序访问堆栈的同时减少了分裂速率的结果。然而,在堆栈尺寸增加到 8 个元素时,存储器交换的流量增加,因为随着缓冲区的增大,在上下文切换时的存储器复制是一个常数。

注意程序在上下文切换之间为 500 个时钟周期的行为,甚至在这种相对高的速率下(它对应于在 10MHZ 的处理器上每秒 20 000 次的上下文切换 -- 在实际应用中这个速率是太高了),对于堆栈缓冲区尺寸大于 12 个元素情况下,花在上下文切换上的成本也仅仅只有每个指令 0.08 个时钟周期。因为在实际的经验中,每个指令没有上下文切换开销时平均 1.688 个时钟周期,这只有 4.7% 的开销。在上下文切换之间达到 10000 个时钟时( 1 毫秒一次上下文切换),开销小于 1% 。

怎么可能有这样低的开销呢?一个原因是执行这三个深度递归的的程序时,平均堆栈深度只有 12.1 个元素。这就意味着堆栈从来就不会有很多的信息,在上下文切换只需要保存很少的信息。事实上,与 12 个寄存器的 CISC 计算机相比,在这个实验中的堆栈计算机仿真实际在上下文切换中需要保存的状态比 CISC 更少。

 

图 6.5 请求式堆栈管理的开销

表 6.7B 和图 6.5 给出了运行请求式堆栈管理算法时的仿真结果。在这些结果中,多于 12 元素的 100- 周期间隔曲线的抬升在堆栈缓冲区中是不存在的。这是由于当恢复机器状态时,堆栈并不需要重新填充,而只是在程序执行时以请求的方式填充。对于合理的上下文切换频率(少于每秒 1000 次),请求式策略比页式管理略好,但并不是压倒性的。

6.5.3.2 为多任务而有多个堆栈空间

堆栈计算机还可以使用一种方法处理上下文切换,甚至对任何成本都可以接受。所有的程序不是使用单一的巨大的堆栈,而是为高优先级/时间紧迫的程序部分提供它们自己的堆栈。这意味着每个过程都使用一个堆栈指针和堆栈界限寄存器来创建一个它自己使用的堆栈。考虑上下文切换,过程管理只是简单地保存当前的堆栈指针,因为它已经知道了堆栈的界限。当新的堆栈指针值和堆栈限制寄存器装入时,新的过程就做好了准备。不需要为复制堆栈元素而花费任何时间。

在典型的情况下,大多数程序所需要的堆栈存储器都非常小,通过设计很小的、时间短而紧迫的过程可以进一步保证。所以,就算是一个适度合理的 128 个元素的堆栈缓冲区也可以按每 32 个元素分配成 4 个堆栈。如果一个多任务系统需要多于4个过程,其中的一个可以设计成低优级堆栈缓冲区,它在多个低优级任务之间通过存储器复制而共享。

从这些讨论中我们可以看到,堆栈处理器的概念之大以至于不能用一句话来说明多任务的高效率。事实上,在许多情况下,堆栈处理器可以比其它的传统计算机在多任务和中断处理方面做得更好。

Hayes and Fraeman (1988) 已经独立地得到了 FRISC3 上堆栈分裂和上下文切换的结果,它与本章的结果相似。

 

第七章 堆栈计算机的软件开发

有高效率的、支持软件运行的硬件是最重要的,任何计算机系统如果没有软件将毫无用途,堆栈计算机提供了在考虑软件问题时新的折衷和选择。

7.1 讨论了快速子程序调用的重要性,它们如何直接和间接地影响程序的执行速度、也影响软件的质量和程序员的生产率。

7.2 解释了如何为堆栈计算机选择一种合适的程序设计语言,以及为什么堆栈计算机可以有效地支持传统语言。

7.3 讨论了堆栈计算机所有级别之间的接口,软件接口的单一性在基于寄存器计算机上是不可能实现的,所以这也是一个明显的优点。

7.1 快速子程序调用的优点

“程序员已经学会了为程序效率而避免使用过程调用和参数传递。然而,过程调用是设计一个结构良好的程序的重要工具,堆栈体系结构具有非常高效访问过程的潜能” (Schulthess & Mumprecht 1977, p. 25)

对昂贵的过程调用的复杂情感、由于强制程序员考虑效率而产生了结构很差的程序,这在 CISC 和 RISC 结构鼓吹者的软件风格中多有体现 (Atkinson & McCreight 1987, Ditzel & McLellan 1982, Parnas 1972, Sequin & Patterson 1982, Wilkes 1982) 。事实上, Lampson (1982) 走得更远,他甚至说,过程都应该用无条件转移来代替,这能够使程序运行得更快。

7.1.1 小过程的重要性

在编写程序时使用大量的小过程可以减少每一片代码的复杂性,包括编写、测试、调试和程序员的理解。低的软件复杂度意味着低的开发和维护成本,同时软件也更加可靠。可是为什么程序员都不愿意大量地使用小过程呢?

大多数应用程序是用通用语言比如 FORTRAN 、 COBOL 、 PL/1 、 Pascal 和 C 编写的。早期的高级语言比如 FORTRAN 是它们所运行的机器哲学思想的直接扩展:使用寄存器的、顺序的冯诺曼机器。因此,这些语言和它们已经开发的通常用法都强调长的赋值语句序列,配合很少的条件分支和过程调用。

然而最近这些年,软件的复杂度发生了变化。当前被接受的最好软件设计实践是使用模块化的结构程序设计。从大的方面看,使用模块可以方便地为程序设计团队的成员分配任务。从小的方面看,通过限制程序员在任何时候都必须接受信息的数量来控制复杂度。

更先进的语言比如 Modula-2 和 Ada 特别强调模块化设计,模块的普遍使用使硬件产生了一项革新,结构化语言使用一个寄存器用于堆栈指针而指向主存储器。除了堆栈指针和几个复杂的指令外(这些指令编译器并不经常使用),CISC 硬件多年来没有给子程序调用提供其它的支持。正因为如此,现代语言的编译器输出的优化代码看起来与为早期非结构化语言所产生的输出极为相似,这就是问题所在,传统计算机对执行程序进行静态优化产生了串行的指令流。对于许多程序的跟踪显示过程调用指令在所有的指令中只占很小的比例,这当然也可以归于这样的事实:程序员在避免使用它们。相反,现代程序实践强调非顺序指令控制流和小过程的重要性,因此,今天的通用计算机上的硬件和软件环境是昂贵的。

这并不意味着使用结构化语言把程序组织得更好或者是维护失败了,它不过是说明出于效率的考虑和所使用的硬件强迫编写顺序程序已经阻止了模块化程序能够达到的效率。尽管当前的哲学是把程序划分成非常小的过程,但大多数程序却依然比它们应该的样子有更少的、更大的、更复杂的过程。

7.1.2 过程的适当尺寸

一个典型的过程应该有多少功能? Miller 给出明确的数量是7加上或者减去 2 就可以适合多个方面的考虑 (Miller 1967) 。人类处理复杂问题的方式是把相似的对象组分成更少的更抽象的对象。在一个计算机程序中,这就意味着每个过程应该包含大约7个基本的操作,比如赋值或者过程调用,这样可以更容易地掌握它们。如果在一个过程中包含有7个以上截然不同的操作,它就应该被分成多个过程以减少程序各个部分的复杂性。在他书中别的部分, Miller 指出人的大脑在一个单一的上下文中只能掌握三个层次的嵌套,这就强烈地建议深度的循环嵌套和条件结构应该安排成过程调用,而不是使用在一个过程中费解的、锯齿状的结构。

7.1.3 为什么程序员不使用小的过程?

现在唯一的问题是,为什么许多程序员并不这样做呢?

程序员避免使用小的、深度嵌套过程的最明显的原因是执行程序方面的花销。如果过于经常地使用过程调用,则子程序参数的建立和实际过程调用指令将花去许多程序执行时间。如果过程嵌套很深,连最好的优化编译器都没有帮助,更何况这些优化都是有条件的,结果就是有效的程序都是相对较浅的过程嵌套。

另一个不能过多地使用过程调用的原因是它们增加了程序设计的难度。为了写程序而定义过程常常使短过程的定义工作过于繁重。当这个弱点与项目管理和文档一起考虑时,就会进一步妨碍在一个大的项目中创建小的过程(规则是这样的:每个过程必须有一个管理控制文档,于是我们也就不奇怪了:过程的平均合理大小通常是1或者 2 页纸的长度而不是 1 或者 2 行的长度)

这里还有一个更深层的原因,说明为什么现在程序设计语言中过程难于创建,为什么它们出现的频率比结构化程序设计教科书的读者所期望要少:传统的程序语言和使用它们的人还沉浸在批处理过程中。批处理并没有给使用小过程的人在可测试性方面提供方便。真正的交互式处理(这不是指在一个终端上做面向批处理的编辑 - 编译 - 连接 - 执行 - 崩溃 - 调试的过程)只在很少的环境中可用,在大学的计算机课程中也没有讲授。

这些因素的结果就是,今天的程序设计语言仅仅为高效率的模块化程序设计提供了有限的可用能力。今天的硬件和程序设计环境对模块化给出了不必要的限制,也就不必要地增加了使用计算机解决问题的成本。

7.1.4 过程的体系结构支持

由于低劣的过程调用性能而引发的问题已经被计算机体系结构设计者以不同的方式予以关注。RISC 设计者使用了两种不同的方法,Stanford MIPS 小组使用编译器技术在需要的时候把过程扩展为内嵌的代码; MIPS 编译器使用灵巧的寄存器分配方法以避免为过程调用保存和恢复寄存器,选择这种策略的统计来自传统的软件设计方法,使用大、嵌套不深的过程。当 MIPS 的方法在现有软件表现良好的时候,它在更好的软件环境中也会有同样的影响,就像我们在 CISC 上看到的那样。

第二种方法是 Berkeley RISC I 小级组提出的,它使用寄存器窗口形成一个寄存器栈帧。一个指向寄存器栈的指针可以移动并快速地 PUSH 或者 POP 一组寄存器,这种方法与堆栈计算机的方法有同样的优点。需要考虑的是实现的细节问题,它实际上决定了一个产品的成败,这个问题变成了下面这些问题:使用单一堆栈还是多个堆栈、使用固定还是可变尺寸的寄存器帧、溢出管理和整个机器的复杂度。

7.2 语言的选择

选择哪种程序设计语言来解决一个特定问题并不是一个轻松的事情。有时这种选择完全由外部决定,比如为了美国国防部的合同就必须使用 Ada 语言;其它情况下,选择语言受限于现有的编译器。当然,在设计一个新系统时有许多的可能选择。

软件选择不应该作为一个孤立的问题来考虑,使用的语言应该反映要开发的整个系统,包括系统操作环境、语言对解决所面对问题的适用性、开发时间和成本、最后产品的维护、底层处理器运行各种语言的能力、本项目程序员以前的编程经验等。注意,程序员的经验并没有放在列表的前面,基于程序员过去熟悉某种的不好选择可能会与其提高生产率的愿望相去甚远。

7.2.1 Forth 优点和弱点

Forth 是堆栈计算机程序设计最应该选用的语言, 这是因为 Forth 是基于一系列能够在虚拟堆栈计算机上运行的语言而构成的,本书所有的堆栈计算机都支持 Forth 程序的高效率实现,所有这些机器都可以使用 Forth 编译器来产生高效率的机器码。使用 Forth 的最大优点是:可以从机器上得到最高的处理速率。

Forth 的特点之一是它以很高的频率来使用子程序调用,这促成了空前的模块化。与这种高度模块化相捆绑的是 Forth 使用的交互式开发环境,在这种环境中,程序是自顶向下、使用适应的桩模块来设计的,然后通过自底向上构建、在每个短小的过程编写时进行交互式测试的。在大的项目中,自底向上和自顶向下方法周期性地重复使用。

由于 Forth 是一个基于堆栈的、交互式的语言,所以我们不需要为测试编写程序或者“框架”代码。相反,给过程的值可以通过键盘 PUSH 到栈上,过程通过执行而被测试,结果在栈顶返回。有经验的 Forth 程序员说模块程序的这种交互式开发能够10倍地提高程序员的生产率,改进软件质量、减少维护成本。这些收益的一部分来自于 Forth 程序通常比其它语言完成同样功能的程序要小得多,只需要编写和调试更少的代码。不包含符号表在内的一个 Forth 程序如果有 32K 字节,则将被认为是惊人的,它们需要许许多多的源程序代码才能产生。

Forth 程序设计语言的一个优点是它覆盖了程序开发的各各层面。某些语言,比如汇编语言,只允许与硬件打交道。另一些语言,比如 FORTRAN ,处理抽象级而很少与底层联系。 Forth 程序可以覆盖程序抽象的所有方面。在底层, Forth 可以直接访问系统硬件接口以实现实时 I/O 处理和中断服务;在更高的级别上,同样的 Forth 程序又可以管理复杂的知识库。

Forth 最有趣的一个方面(在外行看来是不可理解的)就是它是一种可扩展的语言。随着过程被不断地加入到语言中,语言对程序员表现出来的可用性也在不断地增长,在这种方式下Forth 很像是 LISP 。语言的核心功能和程序员所做的扩展和增加之间没有区别,这使得语言的灵活性远远在没有使用过扩展能力的人理解性之外。

Forth 的可扩展性确实混合着许多祝福。 Forth 希望成为一个程序员放大器,好的程序员使用 Forth 语言编程会变得异常好;优秀的程序员能够变得成就显著;普普通通的程序员写出的代码能够工作;差的程序员返回到用其它语言编程; Forth也有一个相当困难的学习曲线,因为它与其它的程序设计语言极不相同,许多坏的习惯必须克服,新的解决问题的概念方法必须通过实践得到。可一旦掌握了这些新的技能,那些基于 Forth 的解决问题的技能和普遍经验和特别的编程方法实际上也提高了程序在其它程序语言中的效率。

某些 Forth 系统的另一个问题是它们没有为程序提供足够的编程工具集,老的 Forth 系统也很少与主机操作系统协作,这些问题来自于 Forth 历史上只使用小的、硬件资源很少的系统。在实时控制应用中,这些限制不会产生问题,但其它应用需要更好的支持工具。幸运的是,现在 Forth 系统提供更好的开发环境和库支持。

所有这些影响的结果是 Forth 最适合于中等尺寸、通过不多于 2-3 个、使用兼容程序风格的程序员就可以完成的程序项目。在很大的程序项目中,相互冲突的风格和能力会阻止产生很高质量的软件。然而,尽管有这些限制, Forth 程序员通常在非常短的时间里提供非常好看结果,经常解决那些使用其它语言无法解决、至少是不能在给定的开发时间里解决的问题。

7.2.2 C 和其它的传统语言

当然,总是有一些应用,它们最好用传统语言来做,可能需要使用传统语言最通常的原因是现存的大量代码需要移植到更好的处理器上。

为了解释有关的权衡,我们来考虑把一个 C 语言写的程序通过堆栈计算机的 C 编译器移植到堆栈计算机上的问题。我们将跳过把 C 源代码翻译成中间代码形式的问题,因为这与程序运行的机器是独立的。对 C 编译器移植最感兴趣的问题是所谓的“后端”,这是编译器的一部分,它通过由程序产生的简化的中间代码来产生目标机器的代码。

实际是,堆栈计算机表达式代码的产生相对直接,对于把中缀形式的算术表达式变成基于堆栈的(后缀/RPN) 表达式已经有了深入的研究 (Bruno & Lassagne 1975, Couch & Hamm 1977, Randell & Russell 1964).

从 C 代码为堆栈计算机产生代码的问题是语言中有几个关于操作环境的假设难于改变。最有意义的是必须有一个单一的驻留在程序存储器中的堆栈,包含数据和子程序返回信息,只要不希望破坏许多 C 程序,这个假设就不能改变。作为一个例子,考虑一个引用局部变量指针的情况。局部变量必须驻留在程序存储器中,否则它就不能被程序正确地引用。

更糟糕的是, C 程序典型地把大量的数据 PUSH 到 C 语言堆栈上,包括字符串和数据结构,然后, C 语言可以在当前栈帧的范围内进行任意地访问(堆栈包含的变量属于当前的过程)。这些限制使得不可能把 C 堆栈放到机器的数据堆栈中。

那么堆栈计算机如何有效地运行 C 语言程序呢?答案是堆栈计算机应该有效地支持“帧指针加偏移量寻址”方式以访问程序存储器。 RTX2000 可以使用它的用户指针高效率地完成这些;FRISC3 可以使用它的用户定义寄存器和带偏移量的 LOAD/SRORE 指令完成;RTX32P 的商业后续者将有一个帧寄存器和一个专用的加法器来完成存储器寻址。在所有的情况下,访问局部变量需要的时间与访问存储器相同:两个周期的一个用于指令,另一个用于数据本身。对于许多计算机,这可能是最好的,它不需要采取那些昂贵的技术,比如分离的数据和指令 CACHE 。

在 C 语言和高级语言中常见、但是不能很好地映射到堆栈计算机上的概念是“寄存器变量”,因为堆栈计算机没有寄存器集合,这就隐含地说明堆栈计算机将失去优化的机会。但这只是问题的一部分,还有的就是堆栈计算机不能适应把大量的临时变量值放到堆栈,但是少量的经常访问的值可以保存在堆栈上以便于快速引用。比如,这些值可以包括一个放在返回栈上的循环计数器,两个用于比较的字符串的地址可以保存在数据栈上。用这种方法,许多高效率的硬件就可以被 C 程序所使用。

还有一个概念可以使许多 C 语言程序在堆栈计算机上运行得与 Forth 程序一样块,这个概念就是支持 Forth 作为处理器的汇编语言,这种方法被几个堆栈计算机的开发商大力鼓吹。使用这种方法,就是把已有的 C 程序传输到堆栈计算机上,使用分析工具得到它们的运行情况。运行的信息用来指出程序中的几个关键的循环。这些循环可以用 Forth 重写以得到更快的速度,在 RTX32P 上还可以使用几个应用指定的代码。使用这种技术,只需要通过很小的努力就可以使 C 程序实际上得到了与全部用 Forth 编写的程序同样的性能。

当这些好的性能与堆栈计算机低系统复杂度和高处理器处理速度相结合的时候, C语言就变成了堆栈计算机编程语言中一个可行的选择。

7.2.3 基于规则的系统和函数程序设计

基于规则系统的编程语言比如 Prolog 、 LISP 和 OPS-5 明显地适合堆栈计算机,一个特别激动人心的可能性是把实时控制应用与基于规则的知识库相结合。对这个领域的早期研究是受到鼓励的,许多工作是以 Forth 为工具完成的。涉及的领域包括: LISP实现 (Hand 1987, Carr & Kessler 1987) 、一个 OPS-5 实现 (Dress 1986)、Prolog 实现 (Odette 1987) 、神经网络模拟、实时专家系统开发环境 (Matheus 1986, Park 1986) 。许多 Forth 实现后来被移植到堆栈计算机硬件上,取得了极佳的结果。例如,Park (1986) 描述的基于规则的 Expert-5 系统在 WISC CPU/16 上运行结果比标准IBM PC 快15倍。一个类似的基于规则的系统 ( 实际上更接近于 Park 的 Expert-4, 它比 Expert-5 慢 ) 在 RTX32P 上运行大约比 4.77MHz 8088PC 快 740 倍。这种接近 3 个数量级的加速使一些人大为惊奇,但是它也只是反映了使用堆栈计算机的适当性。它适合进行树的遍历 , 解决那些需要使用决策树来解决的问题。

我们看到的对规则系统的加速现象实际上是基于一个能够广泛适用的原理,堆栈计算机可以把数据结构作为可执行的程序代码。考虑树型数据结构的例子,它有内部节点指针和在叶子上执行的行为。树的结点是子树地址的指针,它在许多堆栈计算机上等同于子程序调用。树的叶子可以是执行的指令,也可以是完成某些任务处理的过程调用,传统的处理器在寻找叶子时必须使用一个解释器遍历树。因为堆栈计算机能够很快地执行子程序调用,结果极其有效,这种直接执行树型数据结构的技术是我们前面讨论的 RTX32P 极高速度的原因。

堆栈计算机非常适合用 LISP 语言编程和实现专家系统,这是由于 LISP 和 Forth 在许多方面非常相似,它们都把程序作为对其它列表的函数调用,它们都是可扩展的语言,都用逆波兰表示法表达算术操作。主要的区别是 LISP 对它的单元使用动态的存储分配, Forth 使用静态的存储分配,因为没有理由认为堆栈计算机在垃圾收集方面比其它计算机更坏, LISP 应该能在堆栈计算机上高效率地运行。

堆栈计算机适合于运行 LISP 的结论同样适合于 PROLOG,在一个 RTX32P PROLOG 实现中,作者对于如何有效地把 PROLOG 映射到堆栈计算机上有新的发现。 PROLOG 使用的数据类型可以是实际的数据,也可以是另一个数据的指针。 PROLOG 数据元素的一种可能的编码方式是使用 32 位字的最高 9 位作为数据类型标记,低 23 位可以作为指向另一个节点的指针、指向 32 位常数的指针或者是一个短常数值。使用这种数据格式,数据元素实际上可以作为指令执行。 RTX32P 指令可以构造成按每存储器周期一个 dereference 的速率遍历任意长度的的指令序列,简单地把数据结构作为程序执行; NIL 指针的检测可以把一个 NIL 指针值定义为对一个错误俘获子程序的调用。这种数据表达的有效性是其它类型的计算机所无法实现的。

函数程序设计语言提供了一种新的解决问题的方法,它使用的计算模型与传统计算机使用的模型不同 (Backus 1978) 。一种特别的执行函数程序的方法是使用 graph reduction 技术,它使用的直接执行程序图的方法与上面讨论的基于规则的系统是同样的技术,这样,堆栈计算机应该能够很好地执行函数程序设计语言。 Belinfante (1987) 给出了一个基于 Forth 的 graph reduction 实现。 Koopman & Lee (1989) 描述了一个使用串线的、解释性的 graph reduction 引擎。

从理论的观点看,有效的 graph reduction 机器比如 G- 机器和 NORMA 属于我们在第二章讨论的 SL0 分类。 ML0 机器是 SL0 能力的超集,应该可以有效地进行 graph reduction 。这个领域的先期研究是针对 RTX 32P 进行的,它是非常简单的堆栈计算机,能够与非常复杂的 graph reduction 计算机比如 NORMA 竞争。

使用函数程序设计语言的一个边际影响是在程序执行时有高度的并行性,这就产生了一个理想:使用大量的堆栈处理器进行计算,而这些处理器是用函数程序设计语言来编程的。

7.3 软件接口的单一性

堆栈计算机一个关键的概念是它们在高级语言和机器语言间有单一的、一致的接口,不论是过程调用还是操作码都把堆栈作为传递参数的方法,这种一致的接口将对软件开发产生几个影响。

程序的源代码不需要以任何方式区分哪个指令是机器直接支持的,哪个指令是用 Forth 实现的过程。对这种能力的建议是使用类似于 Forth 语言的低级堆栈操作作为所有语言的目标代码。在这个目标语言基础上给出一个汇编器,用户就不用担心一个指定的函数是如何实现的。这就意味着同样的体系结构的不同实现在基于堆栈的源码级是高度兼容的,在低成本的实现中也不需要提供全部的指令。如果同样的接口用于传统的语言和 Forth ,那么把 C 代码、 Forth 代码和其它语言的代码混合在一起就没有任何问题。

在像 RTX32P 一样的微码机器中,这种接口可以进一步扩展。应用指令的微码可以用于替换应用程序中关键的指令序列,使普通编译器产生对用户透明的代码序列。事实上,在一个微码堆栈计算机上,通用的应用代码开发方法是首先用高级代码编写全部的应用,然后回到微码关键循环,重新把高级语言子程序写成微码,这对软件的其它部分是不可见的,除了程序速度的提高,这种速度的提高对许多应用程序大约相当于是2倍的加速因子。

第八章 堆栈计算机的应用

堆栈计算机就像许多计算机一样,适应于广泛的应用领域,任何需要高速度和低系统复杂性的系统都可以选用堆栈处理器。

8.1 讨论了有这些需求、很适合堆栈处理器的应用领域 -- 这个领域就是实时嵌入式控制。实时控制应用需要的是尺寸小、重量轻、成本低、可靠性高。

8.2 讨论了 16 位和 32 位硬件的不同能力和折衷。正确选择处理器的尺寸对成功的设计是至关重要的。

8.3 讨论了系统实现时的考虑。选择硬连线和微码系统涉及一系列的折衷,包括复杂度、速度和灵活性,不同的集成度也影响系统的性能。

8.4 给出了4个适用于堆栈计算机的广泛应用领域,以列表方式给出了可能应用的详细说明。

8.1 实时嵌入式控制

实时嵌入式控制处理器是这样一类计算机,它们(通常)构建在一个复杂的设备中,比如汽车、飞机、计算机外设、音频电子和军用运输工具/武器,但它们自己却不再被当成是一个计算机。

8.1.1 实时控制的要求

大多数情况下,嵌入在系统中的计算机对于用户来说是不可见的,比如在一个汽车防滑减速系统中。通常,处理器以低成本和多功能来替代系统中的一个昂贵和体积巨大的部件,另外也有一些明显地表现是计算机的情况,比如在飞机的自动驾驭仪中。但是,在所有的情况下,计算机只是大系统的一个部件。

许多嵌入式系统对处理器作出了苛刻的限制,包括尺寸、重量、成本、功率、可靠性和操作环境。这是因为处理器只是大系统的一个部件,而那个大系统有它自己的操作环境和制造限制。

同时,处理器却必须提供最大的可能性以响应实时事件。实时事件典型地是异步地到达系统的外部请求,它要求在几微秒到几毫秒的时间内响应。例如,一些高性能的喷气飞机天生就不稳定,它依赖于计算机来保持它们飞行平稳。一个空中计算机必须很轻、很小,不可能过度地要求功率和冷却;同时,它又不能落后于依靠它飞行的飞机。在超过音速的时候,飞机大约每秒运行 1000 英尺,在这种速度下,几个毫秒就决定了飞机的生与死。

8.1.2 堆栈计算机是如何满足这些需要的

第4章和第5章所描述的堆栈计算机制造商都把实时控制应用作为它们技术的可能应用之一,是什么使得堆栈计算机适用于这个应用领域呢?

尺寸和重量

我们已经看到了,从处理器方面看,堆栈计算机非常简单。然而,决定整个系统尺寸和重量的并不仅仅是处理器自己有多少个门电路,更是整个系统的复杂度。一个处理器如果有大量的引脚就会占用宝贵的印刷电路板面积;如果需要 CACHE 控制器和大量的存储器器件就会占用更多的印刷电路板面积;如果由于巨大的软件环境而要求一个硬盘来做虚拟存储器管理那就更是无从说起了。做到尺寸和重量要求的核心是保持元件数量最小,堆栈计算机由于有很低的硬件复杂度和小的程序存储器要求,所以在这方面做得很好。由于堆栈计算机比其它计算机更简单,也就有更高的可靠性。

功耗和冷却

处理器的复杂度可以影响系统的功耗,处理器的功耗与晶体管的数目有关,特别是与处理器的引脚数目有关,依赖于特别工艺来得到速度的处理器就是一个“消耗功率的猪”。需要大量的高功耗、高速度存储器器件的处理器可能超过功耗的限制。

堆栈计算机趋向于低功耗,制造工艺可以对功耗有巨大的影响,使用新的 CMOS 工艺的器件器件其功耗与双极型和 NMOS 设计相比要小得多。当然,功耗直接影响冷却要求,因为计算机使用的所有功率最后都将以热的形式表现出来。对 CMOS 组件进行冷却能够降低元件的失效数,提高系统的可靠性。

操作环境

嵌入式处理器应用对操作环境的要求是极端苛刻的,特别是在汽车和军用设备中。处理系统必须面对震动、撞击和高低温,或许还有辐射。在远程安装应用中,系统必须能够在没有现场技术人员的支持下生存。通常避免操作环境所引起问题的规则是把元件的数量和引脚的数目减少到最小。堆栈计算机由于低的系统复杂度和高度的集成,在忍受恶劣操作环境方面做得很好。

成本

对于低端和中级性能来说,处理器自身的成本可能非常重要。因为芯片的成本与片上的晶体管数目和引脚数目相关,低复杂度的堆栈计算机在成本方面有天生的优势。

在高性能的系统中,处理器的成本淹没在多层印刷电路板、支持芯片、高速存储器芯片中。在这种情况下,低系统复杂度的堆栈计算机提供了附加的优点。

计算机性能

在实时嵌入式控制环境中,计算性能并不简单地是每秒指令的执行速率。尽管原始的计算性能非常重要,但其它的因素也可能导致系统崩溃,包括中断响应特性和上下文切换的开销。一个附加的期望特性是提供良好的子程序调用性能,子程序调用是减少程序存储器尺寸的有效方式,就算快速程序芯片的成本不作为这个系统追求的目标,可是小的空间和印刷电路板实际上也强制要求把程序塞进小的程序存储器中。前面讨论的堆栈计算机的特点显示了它们非常适合于这个领域。

8.2 16 位还是 32 位硬件

在为一个特别的应用选择堆栈处理器时,一个基本决策就是处理器的数据元素的尺寸:16 位还是 32 位。这其中需要考虑成本,尺寸和性能。

8.2.1 16 位硬件常常是最好的

16 位堆栈处理器的成本通常比 32 位处理器低。它们的内部数据通道比较窄,所以它们使用更少的晶体管,制造成本也低。它们与外部存储器的连接通道只有 16 位,所以它们与 32 位处理器相比只有一半的存储器总线。系统成本也很低,因为与 32 位处理器相比,最简单的 16 位配置只需要一半的存储器芯片。

16 位芯片也把合理数量的硅面积用于特殊功能,比如硬件乘法器、片上程序存储器和外设接口。现在的趋势是对于 16 位的堆栈处理器比如 RTX2000 用 SOC 方式实现,包括了 I/O 外设和程序存储器以便用于嵌入式系统。

在面对一个应用时,应该首先考虑 16 位的处理器,如果改用 32 位处理器,那就应该有清晰和明显的优点。

8.2.2 有时也需要 32 位系统

16 位的处理器能够很好地服务于大多数传统的实时控制应用,它们以最小的成本在一个小的系统中提供了高速处理。当然, 16 位处理器能够很好地适合传统应用的原因是在很长时间里,32 位处理器的能力没有被广泛应用。随着更多 32 位处理器能力的广泛应用,可以发现适合它们应用的新领域。

只是在下列情况下,才应该使用 32 位处理器代替 16 位处理器: 32 位整数计算、访问大量的存储器、浮点计算。

32 位整数计算很明显地适合 32 位处理器,主要用于图形应用或者处理大的数据结构;16 位处理器可以通过双精度来仿真 32 位计算,但是 32 位处理器更有效。

16 位处理器可以通过段寄存器来访问大于 64K 元素的存储器,但是如果经常访问大于 64K 的元素,这种技术就变得笨拙和缓慢;如果一个程序必须连续地改变段寄存器来访问数据结构(特别是单个数据元素大于 64K 的情况下),就会把大量的时间用于计算段值上。更坏的情况是,由于在计算数据记录位置时使用大于 16 位的宽度,地址的计算也必须是双精度的。一个 32 位的处理器提供了 32 位线性地址空间,它可以在 32 位数据通道上完成地址计算。

浮点计算也需要 32 位处理器来得到高的效率。16 位处理器在处理浮点数时需要花费大量的时间处理堆栈元素,而 32 位处理器自然地适应数据元素的尺寸。在许多情况下,比例化的整数计算比浮点数在某些计算机上对于提高速度更合适,这时 16 位处理器就足够了。然而,浮点数经常用来降低编程的成本,支持用高级语言编写的代码。这样伴随着极高速度的浮点处理硬件的到来,传统的整数操作相对于浮点操作速度方面的优点正在降低。

32 位处理器的缺点是成本和系统的复杂性。由于 32 位处理器芯片比 16 位的系统有更多的晶体管和引脚而成本更高。它们也要求 32 位的程序存储器和通常更大的印刷电路板面积,它们也没有更多的空间给硬件乘法器等外部设备,不过当工艺技术变得更细时,这些东西也就会出现了。

8.3 系统实现方法

一但在 16 位和 32 位系统之间作出了选择,接着就是选择制造商。本书所讨论的7个堆栈计算机都有不同的设计折衷,包括系统复杂度、灵活性以及性能方面,这些折衷反映了它们对不同应用的适应性,一个折衷就是使用硬连线还是微码控制。

8.3.1 硬连线还是微码控制

在所有的计算机领域中,使用硬连线还是使用微码来实现控制电路的争论由来已久,硬连线方法的优点是对于那些系统直接支持的指令执行得更快,缺点是这些计算机只支持简单的指令,必须执行多个指令才能综合一个复杂的操作。

微码机器比硬连线机器灵活,这是因为可以通过执行一个任意长的微码序列来实现非常复杂的指令。每个指令都可以视为对微码过程的一个子程序调用,在具有微码 RAM 的计算机上,指令集可以通过应用指定的指令来增强,这样可以对一个特殊的程序给出有效的速度提升。

硬连线堆栈计算机都支持某些复杂的堆栈操作,它们组合了堆栈处理、算术操作和子程序返回。这是通过处理指令中的不同字段来完成的。从这个意义上讲。硬连线指令格式更像是微码。事实上, NOVIX 把它的 NC4016 指令称为“外部微码”。

在使用微码的堆栈计算机上,简单的操作比如加法通常比在硬连线计算机上要长,复杂的操作码比如双精度算术操作在硬连线计算机上并不封装到一个单一的指令中。对于这些复杂的指令,微码计算机可以通过提供特殊的、复杂的操作码而运行得更快。通常这种灵活性的提高可以更多地弥补两种不同处理器的差异。最后的结论是,如果不评估所有的方法,则对于一个特定的应用来说,我们并不能说硬连线和微码计算机哪种方式更快。重要的一点是,在选择处理器之前,应该小心地评估应用的需求。

8.3.2 集成度和系统成本/性能

在讨论了硬连线和微码折衷之后,我们可能会记得在第4章中讨论 16 位堆栈处理器时给出了完整的集成度方面的考虑。集成度是放在处理器芯片上的系统硬件的数量,放在处理器上的系统功能越多,集成度就越高。当然,另一方面,我们还必须在设计中考虑成本/性能的折衷,使得为运行系统而使用的元件种类和数量最小。

WISC CPU/16 在给出的所有堆栈处理器中有最低的系统集成度,它使用了一打儿的元件在电路板上实现了处理器。当然,这种方法省去了生产一个单个芯片时的大量的布局投资。

MISC M17 是一个简单的单芯片处理器,由于它使用程序存储器作为堆栈,只需要程序存储器和处理器就可以工作,集成度可以算是高的,系统复杂度较低。这种简单性设计带来的限制是它的速度比分开的堆栈存储器要慢。

NOVIX NC4016 也是一个单芯片处理器,其集成度可以与 M17 相比。不奇怪,两种处理器都使用了门阵列工艺,所用的面积大致相当。主要的区别是 NC4016 使用了分离的存储器作为两个堆栈,分离的堆栈在给定的时钟速率下有更快的处理速度,因为有更多的存储器带宽可以使用,但是在成本方面需要有更多的系统级元件。

HARRIS RTX2000 通过片上堆栈存储器而比 NC4016 提高了系统集成度,这实际是在提供了速度效率的同时减少了系统的复杂度,因为片上存储器比片外存储器更快,代价是片上使用更多的晶体管。然而,这些增加的晶体管不一定就会大大增加芯片尺寸。因为 RTX2000 使用了称为标准单元的不同的技术,它很适合提供片上存储器。事实上, RTX2000 定制系统设计成在包含片上堆栈存储器的同时也包含片上程序存储器,提供一个单芯片的堆栈计算机系统。

未来的堆栈计算机设计在这些方面应该有所折衷:数据通道的宽度(对于大多数的处理是 16 位和 32 位宽度,对于信号处理应该是 24 位宽度,对于标记的数据结构应该是 36 位宽度),系统集成度,所需要的片外支持,原始的系统性能。在我们遇到每个目标应用时,这些问题都要考虑,然后才能决定选择一个合适的处理器。

8.4 应用领域举例

堆栈计算机能够应用的领域,就像通常的计算机一样,只受想象力的限制。看起来非常适合堆栈计算机应用的领域包括:

图像处理

目标识别、包括光电字符识别、指纹识别和手写体识别以及图像增强都需要特别强大的处理器,但是都有广泛的应用。许多商业应用所感兴趣的处理器是很小、成本低和便携的。

机器人控制

机器人有 5 到 6 个连接(自由度),典型的策略是为每个连接安排一个微控制器,再用一个更强大的微控制器作中央控制,每个连接可以实时地执行复杂的位置计算。在一个汽车中,小尺寸和低功耗是至关重要的。

数字滤波

滤波器要求很高的处理速度以保持高的数据流,堆栈计算机有片上空间用于实现硬件乘法器和算法指定的硬件以快速执行数字滤波计算。

过程控制

更加强大的处理器可以在简单的过程控制技术之外对实时过程监督和控制施以专家系统技术,堆栈计算机特别适合于基于规则的系统。

计算机图形

现在市场上有几个专用的图形加速器芯片,它们趋向于原始的画线和位块的移动,在这个领域中一个激动人心的机会是解释性的高级图形命令语言用于激光打印机和设备独立的屏幕显示。其中一个卓越的语言与 Forth 非常相似,那就是 Postscript 。

其它的计算机外设

堆栈计算机的低成本使得它适合于控制计算机外设,比如磁盘驱动器和通信链路。

电信

高速控制可以提供数据压缩能力,因此可以使传真和 MODEM 应用的成本更低。它们也可以监视收发设备的性能。

汽车控制

汽车市场对成本和环境有严格的限制,在这种业务中,每个元件成本的微小差异加起来就是巨大的利益或者损失,高级别的系统集成度是强制的要求,计算机可以提高系统的性能和安全性并同时减少应用中系统的成本。应用领域包括:计算机化的点火,刹车、燃料分配、反盗窃设备、碰撞告警系统和撞击显示系统。

消费类电子

消费类产品是一类对价格和系统集成度比汽车更敏感的东西,任何用过便宜的计算器或者是数字手表的人可能都会奇怪这种产品怎么能够仅仅通过塑料和一个单芯片就能做出来。高速、便携、成本低廉的堆栈处理器有这样的机会:音乐合成(比如 MIDI 设备)、CD、数字磁带设备、通过电话线的慢扫描电视、交互式电缆电视服务和视频游戏。

军事和太空控制应用

现代的太空应用已经可以有商业目的了,但它们与军用有着一样的可靠性和环境要求,堆栈处理器很好地适用于导弹和飞船的高速控制应用。此外,还有一些应用比如声音和电信号处理、图形增强、通信、点火控制和战场管理。

并行处理

原始的研究显示出堆栈计算机可以高效地执行函数程序设计语言,用这些语言写的程序有大量的与生俱来的并行性,它有待于多处理器堆栈计算机系统开发。

第九章 堆栈计算机的未来

前面几章讨论的堆栈计算机是第一代可商用的堆栈处理器,随着这些计算机的广泛应用,要求堆栈计算机重新设计以面向市场并改进效率。这一章的问题是:我们希望见到的是哪一种计算机?它们将对堆栈计算机体系结构和应用产生哪些影响?

回答堆栈计算机将会如何应用于许多不同的领域的所有问题还为时尚早,但是,我们可以思索一些重要的观点。这里的观点和推理可以形成未来对堆栈计算机概念探索的基础。不过,这一章的思想只是思索,而不是已经证明的事实。

9.1 讨论了在需要支持传统的程序设计语言时需要考虑的几个方面,正像已经说明的那样,现在的堆栈计算机设计已经能够很好地处理大部分问题。

9.2 讨论了虚拟存储器和存储器保护的方法,在现在的堆栈计算机上并没有出现虚拟存储器,因为对于大多数应用领域来说是不需要的,但是在未来的应用领域中就可能会需要。

9.3 考虑了对第三个堆栈的需要,并提出一个建议:驻留在存储器中的栈帧可以同时满足第三个堆栈和传统语言支持的需要。

9.4 讨论了迫切的问题就是存储器带宽限制,在计算机中使用分层存储器结构背后的历史。存储器计算机为存储器带宽问题提出了一个解决方案,它可以很好地适用于重要的应用领域。

9.5 介绍了两种有趣的但是没有在现有的设计中使用的堆栈计算机设计思想,一个思想是通过使用子程序条件返回来省去条件分支,另一个思想是使用堆栈来临时性地保存汇编语言代码。

9.6 提供了堆栈计算机对计算机影响的思考。

9.1 支持传统的语言

堆栈计算机最初的应用市场是实时控制,堆栈计算机高度集成可以使它作为一个低成本、高性能的协处理器而被用于个人计算机或者低端工作站的插卡,这些协处理器可以针对一个特定的应用领域进行定制,甚至是针对一个单一的重要软件包进行定制。所有这些环境都需要运行许多用传统高级语言编写的应用代码。

传统高级语言可以很容易地在堆栈计算机上实现,唯一的问题是,纯的堆栈计算机在运行使用通常程序风格编写的传统程序时可能不如寄存器机器快。这个问题主要是传统语言的要求和堆栈计算机的能力不匹配,传统程序趋向于使用很少的过程调用和大量的局部变量。堆栈计算机能够很好地运行由许多小过程和很少局部变量组成的程序,这种差异部分是由于通常程序设计语言所培养的程序设计风格和传统程序设计语言的结构所造成的。更进一步说,其中的差异是寄存器计算机能够很好地适应于通用的数据处理,而堆栈计算机在实时控制环境中表现得更好。从任何意义上讲,只要能够提供一定的硬件,传统语言在堆栈计算机上的性能在所有的应用领域中都可以与最高性能的寄存器计算机相近。当然,其思想在适当地匹配寄存器最好的方面的同时,不牺牲堆栈计算机所适应其它领域的特点。

9.1.1 栈帧

接着,我们来确定附加哪些硬件来支持高级语言的结构。奇异的是,高级语言运行时间栈竟然是纯堆栈计算机不能很地支持传统高级语言的唯一主要领域。这是由于高级语言有称为“活动记录”的结构,它是在每个子程序调用过程中建立的、位于程序存储器中的、由软件管理的“帧”。在通常的实现中,每个栈帧在子程序调用之前被一次性地分配成一个大块的存储器。这个帧包含有输入参数(它实际是被调用子程序分配的)、用户声明的局部变量、编译器按自己的需要而产生的中间变量。在子程序运行的过程中,可以在栈帧内进行任意的访问而不需要实际执行 PUSH 和 POP 。

栈帧是一个供子程序使用的临时存储器分配设备,它不是一个传统的保存连续计算中间结果的下推栈。这就意味着它与我们已经研究的、建立在堆栈计算机内部的硬件堆栈是不兼容的。修改堆栈计算机以满足这种要求的一个直观方法就是在一个帧内分配一大块存储器用于随机访问。这正是 RISC 计算机的寄存器窗口(在第二章描述成 SL2 )解决方案,但是这样做对堆栈计算机并没有什么意义,因为所有的数据访问都要付出在指令格式中提供操作数的代价。

一个变通的方法是建立第二个硬件堆栈,用很慢的速度访问局部变量,而原始的数据处理在 LIFO 硬件堆栈上进行。这让我们同时有了两个美好的世界,并这并不是没有没有成本,为了得到好的操作特性,第二个寄存器帧栈的大小通常要是数据堆栈的 5-10 倍。

9.1.2 寄存器和存储器混用

如果这样的折衷是唯一的问题,那我们可能就会试着在芯片上建立一个帧。但是,还有一个因素让我们考虑把栈帧放到程序存储器上。这个附加的因素就是传统语言的语义允许通过存储器地址来访问这些局部变量。 C 语言在这个问题上声名狼籍,它影响了堆栈计算机和寄存器计算机的相似性。

把寄存器当成存储器使用可以通过灵巧的硬件和编译器来处理,但是硬件和/或软件复杂性的成本与堆栈计算机用最小的复杂度取得最大性能的设计哲学不符。因此,对于一个堆栈计算机最好的选择是在程序存储器中维护传统语言的栈帧,并使用一个帧指针寄存器作为栈帧访问的硬件寄存器。如果有很富裕的片上空间,堆栈计算机可以提供一个片上 RAM 作为程序存储器空间的一部分来加速对栈帧的访问。

这个想法可能很吸引人:编写复杂的编译程序,努力地使传统程序执行时把大多数局部变量都放到硬件堆栈上。然而,按本书作者对一些堆栈计算机的经验来看,试图把所有局部变量都放到硬件堆栈上的 C 编译器和用一个帧指针来访问程序存储器的 C 编译器,两者的差异很小。事实上,如果计算机有一个帧指针,把帧放到程序存储器中还更快。

通常我们会这样想:把局部变量保存到硬件数据堆栈上应该比放到程序存储器中更快。但实际情况并不如此,原因是堆栈计算机访问埋藏很深的元素时效率不高,特别是在这些元素可能被分裂到程序存储器中的情况下。“全部都在硬件堆栈上”的方法使大量的时间花在查找堆栈元素上,而访问驻留程序存储器帧元素比访问硬件数据堆栈上的元素慢,但是在堆栈处理方面节省的时间使两者没有差异。

9.1.3 栈帧处理的一个策略

在处理栈帧时,可以使用一个很好的折衷方法,把程序存储器驻留与硬件堆栈数据驻留组合起来。这种方法的细节如下:所有的过程调用都把参数放到硬件堆栈上,因为这些参数或者是用于计算、或者是用于通过硬件数据堆栈在不同的栈帧位置间移动,这个过程没有浪费任何时间。然后进行过程调用,被调过程从硬件堆栈上复制参数到分配的帧位置处作为局部变量,只留下一、两个参数。编译器保证留在数据堆栈上的参数不使用地址引用,这可以通过程序员声明这是一个寄存器变量、或者编译器分析得到,最不济也可以仅仅为了安全而把所有的参数都复制到存储器中去。在数据硬件堆栈上留下一、两个数据可以使堆栈分裂最少,也提高了性能。当过程结束时,返回值被到数据堆栈上。

这种方法通过保存大量的存储器引用而提供了很好的效率,它也为不同的语言之间互相调用而提供了很好的接口,比如 Forth 语言和 C 语言,处理这些问题的编译器也相对易于编写。于是,现在的许多堆栈计算机都保存着帧指针,可以很容易实现这种策略。

9.1.4 传统语言的执行效率

从这些讨论中我们可以看到,堆栈计算机通过使用栈帧指针方法而有足够的效率支持传统语言的运行。当然,使用栈帧指针指向程序存储器不可能期望达到 RISC 机器的效率,因为 RISC 计算机有大量的片上寄存器窗口用于直接支持帧,或者通过灵巧的优化编译器来进行全局寄存器分配。

传统语言程序服从于高性能寄存器计算机使用的模型,堆栈计算机看起来性能不佳。导致这种影响的传统程序方面包括:大段的直线代码,接近 100% 的 CACHE 命中率,在长过程之间交叉引用的局部变量和那些暗中可以编译成内嵌代码的过程。

考察能够在堆栈计算机上高效率运行的程序所使用的结构,堆栈计算机就可以达到甚至超过基于寄存器计算机的程序性能。能够很好地被堆栈计算机运行的代码包括:高度模块化过程有多级嵌套或者递归;相对少量的和频繁使用的子程序、内层嵌套可以放置到快速存储器中,甚至可能提供微码支持;向下通过许多层的接口的局部变量很少;深度嵌套的子程序调用。同时,那些操作在频繁中断和上下文切换环境的程序也可以从堆栈计算机上受益。

在堆栈处理器上使用传统语言的一个实际方法是使用一种适度效率的高级语言来实现程序的大部分,然后把程序的内层循环用机器的汇编语言重新编码。这样就可以通过适当的努力而得到非常高的性能。在那些需要使用堆栈计算机优点的实时控制处理项目中,这种方法可以为程序员的时间投资获取最大的处理程序收益。

我们应该记住的是:在选择一个计算机时,除了单一程序的原始速度之外还需要考虑许多其它的因素,这些因素也许能够使天平偏向堆栈计算机:中断处理速度、任务切换速度、低的整机复杂度、支持应用程序指定的微码和/或硬件操作。在最后的分析中,堆栈计算机可能没有能够使得传统语言的程序运行得和寄存器计算机一样快,但是,其它的考虑可能消除这一缺点,特别是对于实时控制应用,使得堆栈计算机成为一个优秀的可选择对象。

9.3 虚拟存储器和存储器保护

虚拟存储器和存储器保护的概念在目前的堆栈计算机中并没有被广泛考虑,这是由于至今为止的大多数堆栈计算机应用程序相对较小,加之硬件和软件方面的严格限制而没有为这些技术留下空间。

9.3.1 存储器保护有时更重要

存储器管理可以指许多事情,但我们这里只讨论属于保护特点的那一部分。保护是存储器管理的一个重要方面,对于某些实时控制用户,特别是军事领域的用户,这就更为重要。保护是硬件所具有的能力,它可以防止一个程序在没有严格控制的条件下访问不属于它的、另外的程序存储器。一个没有被许可的、对不属于自己的存储器访问将引起一个中断,这种中断导致操作系统关闭或者复位这个不适当的任务,这就提供了一个安全的方法以防止病态的程序破坏其它的存储器。存储器保护功能通常由一个单独的、由操作系统管理的芯片来完成,堆栈计算机并没有阻止使用这种类型的芯片,相反,由于堆栈计算机的一个优点是它们都足够小,因而有可能把这样的存储器保护电路做到芯片上。

9.2.2 没有使用虚拟存储器控制器

同样,堆栈存储器也没有什么原因不提供虚拟存储器能力。使用虚拟存储器的一个问题是存储器失配的影响,它可能要求一个指令重新进入,由于堆栈计算机本质上是一个 LOAD/STORE 计算机,指令的重启并不比 RISC 计算机更困难。事实上,由于没有流水线,在堆栈计算机上处理中断的速度更快,它应该能够更好地处理虚拟存储器。

堆栈计算机不设置虚拟存储器的原因非常简单,许多堆栈计算机的目标是实时控制应用,而虚拟存储器由于需要使用大容量硬盘并导致性能波动,所以,这种技术并不能简单地适应于嵌入式实时控制环境。

9.3 使用第三个堆栈

一个堆栈计算机设计建议是使用第三个堆栈,给出第三个硬件堆栈的目的通常是存储循环变量和局部变量。

当前堆栈计算机的循环计数器通常都是作为返回栈的栈顶元素,这是由于子程序和循环能够很好地实现嵌套,但是让子程序去访问它的父程序的循环变量并不是一个很好的程序设计风格。所以,给循环变量一个自己的堆栈而避免在返回栈是混合非地址数据,当然有一些概念上的好处,但是所得到的程序执行效率与硬件所增加的成本并不一定成比例。

第三个堆栈的另一个用途是存储本地变量,甚至就是在用 Forth 编写程序时,程序员也感到编译器管理的本地变量可以使某些程序更易于编写和维护。为了高效率地实现这些能力,硬件需要访问一个按帧方式分配的堆栈,并通过帧中地址随机进行读写,这就和支持传统语言的方法相似。所以,事实上,最好的方法是不使用第三个堆栈,而是使用一个帧指针指向软件管理的程序存储器堆栈,它可以同时为高级语言和 Forth 语言局部变量提供支持。

9.4 存储器带宽的限制

可能这些年对计算机体系结构最大的挑战就是存储器带宽问题了,存储器带宽是每个时间单位里能够与存储器交换的信息数量。另一个说法就是,存储器带宽决定了可以访问多少存储器值。

问题的症结是,程序存储器通常比处理器需要更多的晶体管和其它器件,这就意味着在给定的条件下,CUP 比较容易地做得比存储器更快。下面是一个常识:使用一定的制造技术和工艺,更快的元件就更贵,消耗更多的功率等等。

9.4.1 存储器带宽问题的历史

存储器带宽限制的幽灵存在于计算机机设计的整个历史中。在开始的时候,大家都感谢计算机能够运行,那时,程序存储器的速度和处理器的速度都不是问题。随着计算机的应用,主要的计算机存储能力事实上是用磁带来保存数据文件,但也比什么都没有要好多了。

由于早期大型计算机中使用的磁芯存储器速度很慢,于是就产生了非常复杂的指令集,它们把许多工作填充在每个指令中,它也产生了很实用的微码,因为少量的微码存储器可以在所限制的范围内做得和 CPU 一样快,而这时大量的程序存储器还不可能做得到。半导体存储器出现后,捕捉重复执行的小片程序段、特别是循环代码段的 CACHE 存储器开始普遍使用。 CACHE 存储器变得越来越大,于是越来越多的程序驻留在 CACHE 存储器中,速度足以匹配处理器的速度。

接着就出现了微处理器,早期的微处理器很慢,当时的程序存储器芯片也足以匹配它的速度(于是,问题又一次变成这个样子:不是它能运行得多快,而是它是否能够运行)。存储器带宽问题暂时平息了一段时间,后来的微处理器制造商也跟随大系统并使用复杂指令集和微码。

主流微处理器开发了一段时间之后,就开始在访问存储器时使用“等待状态”。一个等待状态就是在处理器速度比存储器芯片速度更快的时候、处理器为了等待存储器响应而浪费的一个时钟周期。一个简单的解决办法是花费更多的钱去购买更快的存储器芯片,另一个简单的解决办法就是等待存储器芯片厂能够以合理的价格生产更快的存储器。最后, CISC 微处理器提出了 CACHE 和从大系统中学来的其它技术而尽量地把更多一些、再多一些的程序代码放进快速存储器中。

RISC 通过宣布传统计算机上使用的微码不好而打乱了格局,它们使用很低级的指令,能够把编译器产生的很好的微码放到程序存储器中。这种方法学说有很大的优点,但是更进一步提高了对存储器带宽的要求, RISC 计算机的性能更依赖于 CACHE 存储器。

9.4.2 当前存储器带宽的关系

对于最新一代的计算机,还有一个新问题:CACHE 存储器芯片可能不足以保持未来的处理器处于繁忙状态。当然,现在这种情况并没有发生,这是由于处理器中晶体管的开关速度比存储器芯片的速度提高得更快(也可以不是这样)。问题是处理器和存储器的引脚数成了主要的问题。

引脚成为瓶颈的原因是随着芯片密度更高,晶体管会越来越小、速度会越来越快。不幸的是,焊接和连接它们的引脚并没有变得更小,除非使用怪异的封装方式。引脚和驱动它们所必需的的电子数量与晶体管所驱动电子的相比就产生了巨大的差异,所以引脚成了瓶颈。这就更意味着任何片外存储器都要比片上存储器慢一个数量级,这是因为它们之间的延时。

现在我们所处的境况是所有的片外存储器都由于太慢而不能保持处理器繁忙。这就产生了存储器响应速度的附加层需要:片上 CACHE 存储器。不幸的是,这种方法与前面的方法相比有一个根本的问题:虽然印刷电路板可以做得很大而没有任何问题,哪怕是印刷电路板的废品率随着板上芯片的数量呈线性增长,可如果出现故障,印刷电路板还是可以修理的。不幸的是,芯片的废品率随着面积的增加按指数方式恶化,而且芯片是不容易修复的。

使用分离的 CACHE 芯片,只要在印刷电路板增加更多的芯片就可以满足合理的需要。然而,如果一个单芯片没有足够的面积用于片上 CACHE 存储器,靠增加芯片尺寸来提供更多的存储器就会使处理器因为废品率问题而无法制造。高速处理器特别是RISC 处理器对大量存储器的需要,使我们看到最好的希望是有一定数量的高速片上CACHE 存储器和低速度大容量的片外 CACHE 存储器 -- 现在我们的程序性能就靠这两种不同的 CACHE 来混日子,这是我们希望的最好结果吗?

9.4.3 堆栈计算机解决方案

堆栈计算机提供了完全不同的方法来解决问题。传统的计算机通过 CACHE 试图在程序运行时捕捉到一小段程序,这些努力是重用运行时已经读取的指令进行循环或者频繁的子程序调用。与 CACHE 接口性能有关的问题是传统的程序语言和编译器产生零乱的、随意放置的代码,而相反的是堆栈计算机鼓励编写使用进行重用的紧缩代码。

堆栈计算机程序行为方式的结果是不使用动态分配的CACHE 存储器,而代之以小的、静态分配或者操作系统管理的程序存储器用于所选定的子程序高速执行。频繁使用的子程序可以放置到这一部分程序存储器中,只要编译器或者用户知道它们需要快速运行,这部分存储器就可以自由使用。这就更加鼓励使用模块化的、重用的过程,因为这样做的结果是可以提高性能,而不是像在其它计算机上那样过多地伤害这一原则。当然,由于片上程序存储器不需要复杂的、大量的控制电路来管理 CACHE 存储器,多余的面积就可以用于另外的程序存储器。在16位堆栈处理器上,把完整的实时控制程序和它的局部变量数据存储器全部放到芯片上是合理的,随着亚微米制造工艺的出现, 32 位的处理器也开始使用同样的方案。

考虑这种方法与分层存储器结构的差异性,让我们看看微码计算机比如 RTX32P ,它有大容量的动态 RAM 可以用于保存许多程序和数据。实际上这是一个极端的情况, RTX32P 的程序很少需要比它的静态存储器更大的容量,但是让我们假设有这种情况。动态 RAM 组成了一个存储器元素,用于很高级别的、不常访问的程序,或者是很少、不经常访问的数据。

接着,静态存储器芯片加入系统中,它们用于执行频率比较高的中级程序,也可以用于程序数据,它们被频繁处理,数据可以驻留在这一片存储器中,也可以在需要的时候从动态存储器中拷贝过来并驻留一段时间。实际上,可以有两级静态存储器芯片:大容量、慢速度的和小容量、高速度的,各有不同的功耗,不同的成本、不同的印刷电路板空间等特点。

片上程序空间可以是另一个层次,程序中重要过程的内层循环可以驻留在这里供处理器快速访问。几百个字节的程序 RAM 可以很容易地放到处理器芯片中用于数据和程序。在运行特定程序的情况下(这是实时嵌入式系统常常遇到的情况),可以让几千字节的程序ROM驻留在芯片上。实际上,任何语言都可以使用许多通用的 ROM子程序以帮助程序员和编译器。

最后,微码存储器驻留在片上用于 CPU 的实际控制。按存储器分层理解,微码可以作为另一个层次的程序存储器,它为处理器保存许多频繁执行的行为,对应于所支持的机器指令,再一次, ROM 和 RAM 的混合是适当的。当然,数据堆栈作为一个快速访问的设备用于保存计算的中间结果。

于是我们就得到了一个贯穿于整个系统的存储器尺寸和速度的分层结构。分层的概念并不新,新的是它不需要在运行时的硬件进行管理,编译器和程序员可以很容易地管理它们。关键是,因为堆栈计算机程序很小,大量的代码可以驻留在静态分配的每个层次上。因为堆栈计算机支持快速的过程调用,只有内层循环和小部分频繁使用的代码才需要驻留在高速存储器中,而不需要把全部的用户定义过程都放到高速存储器中。这就意味着实际上并不是真的需要动态存储器分配。

9.5 堆栈计算机设计的两个新思想

这里有两个有趣的关于堆栈计算机设计细节的想法,它们并没有在现有的设计中使用,但是,也许能够在未来的设计中被证明是有用的。

9.5.1 条件子程序返回

实现细节是这样的: Doran (1972) 发现堆栈计算机不需要条件分支,它们只需要条件返回子程序指令。考虑一个高级语言中使用的 IF 语句,如果我们忽略可选择的 ESLE 子句, IF 语句就是“有一个入口和两个出口的一段代码”。入口代码开始这个语句,并在这里计算分支条件。当第一个出口条件为假时,其它所有的语句都不再执行。第二个出口是这个语句的结束,所有的动作都已经完成。

实现 IF 语句的通常办法是测试 IF 条件,当它为假时进行条件分支。相反,我们可以考虑让一个子程序包含整个 IF 语句, IF 语句的入口点是进入这个特殊子程序的调用,第一个出口点可以是一个条件子程序返回指令,在 IF 语句的条件子句为假时返回;第二个出口是一个无条件返回。

这种方法消除了程序中含有地址的条件分支,所需要的只是一个条件子程序返回语句。这种技术非常适合于堆栈计算机,因为进入条件子程序的调用和子程序的返回成本都很低,它可以比现在使用的条件分支方法更有效。

9.5.2 使用堆栈保存代码

另一个堆栈计算机程序执行的有趣建议是 Tsukamoto (1977) 提出的,他考察了自修改代码的相互矛盾的优点和缺点。尽管自修改代码可以非常有效,但是几乎所有的专业软件程序员都避之而恐不及,因为其中的风险太大。自修改代码破坏了程序的内容,程序员不能相信编译器或者汇编器产生的指令在整个程序执行期间是正确的。

Tsukamoto 的思想是没有缺陷地使用自修改代码,他建议简单地使用运行程序栈来存储修改的程序代码段用于执行。代码可以被应用程序产生并在运行时执行,这个过程并没有破坏程序存储器,当代码执行完后,就可以简单地 POP 堆栈而将它丢弃。

这两种技术现在都没有使用,但是它们可能在未来的应用中变得非常重要。

9.6 堆栈计算机对计算的影响

我们已经看到了,用原始的每秒执行多少指令来衡量,堆栈计算机至少与基于寄存器的计算机一样快,它们在实时控制应用中更显示出高级特点。我们还看到,只要使用小的嵌套过程调用就可以在程序中使用本地局部变量,但是,提出 CISC 、RISC 和堆栈计算机哪个最好这样的问题并不合适,因为所有这些设计技术在不同的应用中都有自己的位置。堆栈计算机好象并不能作为一个基本的 CPU 而很好地应用于工作站和小型机市场,正是由于这个原因,它们并没有得到应有重视。但是在适合的领域中,它们都真实存在并表现良好。

稍微想一下我们就可以发现,支持某种计算任务中的问题并不是堆栈计算机,而是当前的程序设计实践。考虑堆栈计算机特点适合的程序:使用许多小的、深度嵌套过程的高度模块化程序;在过程中传递少量参数、隐藏操作细节的程序;频繁再用这些小的模块以减少尺寸和复杂度的程序;由于尺寸小而很容易调试的程序;使用单一的接口来连接各种级别的模块抽象、从高级子程序到指令的程序。所有这些特点看起来都是需要的。不幸的是,它们很少用于实践中,使用堆栈计算机也许能够改变这一局面。

可能我们使用的、由寄存器计算机形成的传统程序设计语言和硬件应该对此负有责任。过程调用不能过于频繁地使用,因为它们耗费时间。因为过程相对较长,语言的语法也没有强制地进行简单的定义和引用,所以就要求不同数目的专用代码、参数列表、类型信息和其它类似的东西,甚至项目管理风格都要求在每个新的子程序创建时进行分开的文书工作和形式化的过程。由于编写和使用许多小的过程相对困难,转而只能使用更少的过程,这就形成了一个恶性循环。

堆栈计算机为改变这种循环提供了一个机会。过程调用开销很低,像 Forth 一类的语言使定义一个新过程的开销最小,实际上是提供了一个鼓励开发和测试模块化、易测试代码的环境。所有需要做的就是设计一个新的高级语言以适合高级语言计算机 (Chen et al. 1980) ,而堆栈计算机就是一个实践者。我们希望能够看到在传统语言上扩展和变化出一些控制结构,以更好地使用堆栈计算机硬件。

寄存器计算机能够给那些结构很差的程序提供一定的性能回报,但通常是更难以维护,更难以调试和更大的程序尺寸。通过对程序员编写结构良好代码的回报,堆栈计算机鼓励更好的程序实践。这可能进一步影响传统语言的变革,传统语言也倾向于提供更好的方法来创建、维护和执行程序。

附录 A 具有硬件堆栈支持的计算机纵览

本附录是第 2 章中所包括的堆栈体系结构的纵览。它包括了大多数出现在会议和杂志上的堆栈计算机。每种机器都有分类、实现技术、解决的应用问题和设计史。此外还包括了参考和简要描述。

AADC

分类目录: SL1 实现: 16-Bit minicomputer 应用: Direct APL execution, military environment 制造商和时间: Raytheon for the US Navy, 1971 参考: Nissen, S. & Wallach, S. (1973)

The AADC (All Applications Digital Computer) was designed for direct execution of the APL language. The target application area was Naval platforms (especially Naval aircraft), so small size and weight were important. The APL language was chosen for efficient machine code and execution. In particular, APL was chosen for its conciseness, which was predicted to give smaller programs and therefore fewer page faults in a virtual memory environment. The AADC converted expressions from infix to Polish notation on-the-fly at execution time. Programs were interpreted at run-time by the program management unit and executed by an arithmetic processor. The execution unit used 1-operand stack notation.

AAMP

分类目录: SS1 实现: 16-Bit microcoded silicon-on-sapphire 应用: Radiation hard for military use, multi-tasking 制造商和时间: Rockwell International, 1981 参考: Best et al. (1982)

The AAMP (Advanced Architecture MicroProcessor) was designed for military and space use. A stack architecture was chosen for ease of compilation and good code density since it can use mostly 1-byte instructions. AAMP uses a single stack with a frame pointer for activation records as well as expression evaluation with a separate stack pointer. The expression evaluation area is just on top of the current frame. Many instructions are 1 byte long, with the possibility of using local variable addresses relative to the frame pointer for 1-operand addressing. Four top-of-stack registers are used for evaluation, with spillage into program memory.

ACTION PROCESSOR

分类目录: MS0 实现: 16-Bit microcoded bit-sliced 应用: Direct execution of Forth 制造商和时间: Computer Tools, 1979 参考: Rust (1981)

The ACTION Processor FORTHRIGHT is a microcoded Forth-language processor. Typical of Forth hardware implementations, it has a data stack used for expression evaluation and parameter passing as well as a return address stack used for subroutine return address storage. The top elements of both stacks are kept in registers in the bit slices. Stacks reside in program memory to reduce hardware costs.

AEROSPACE COMPUTER

分类目录: SS0 实现: 64-Bit processor 应用: High reliability, multiprocessor spacecraft computer 制造商和时间: Intermetrics, 1973 参考: Miller & Vandever (1973)

The Aerospace Computer used stack instructions to save program memory space, which has a major impact on reducing size, weight, power, and cost for spacecraft applications. Stack instructions were also chosen to direct support high order languages to enhance software reliability. The design draws heavily from the B6700 architecture. All computation was done in floating point in the ALU, with integers converted to floating point format as fetched from memory. When set, the highest bit of an operand on the stack indicated that the element was really a pointer to memory, which caused an transparent fetch.

ALCOR

分类目录: ML0 实现: Emulator on various early European computers 应用: Conceptual machine emulated for transportable ALGOL programming 制造商和时间: ALCOR joint project, 1958-60 参考: Samelson & Bauer (1962)

The ALCOR (ALgol COnverteR) joint project was a very early conceptual design for the interpretation of ALGOL 60. The European group devised a high level language machine architecture which was emulated on various machines. The conceptual machine had two stacks which were used for expression parsing and evaluation. One stack held pending operations, while the other stack held intermediate results. Variables and return addresses were statically allocated in program memory.

AN ALGOL MACHINE

分类目录: ML0 实现: Research prototype project 应用: Direct execution of ALGOL 制造商和时间: Burroughs, 1961 参考: Anderson (1961)

The exploration for a direct execution architecture was motivated by the observation that two-thirds of computer time was then spent doing compilation and debugging. The focus of the research was on making computers easier to use. The approach taken was to directly execute a high level language. The machine discussed used three hardware stacks to execute ALGOL constructs. Two stacks formed a value and operator stack pair for expression parsing and evaluation. The third stack held subroutine return address information.

AM29000

分类目录: SL2 实现: 32-Bit microprocessor 应用: General purpose RISC processor 制造商和时间: Advanced Micro Devices (AMD) 1987 参考: Johnson (1987)

The AM29000 is a RISC processor. While its instructions are not stack-oriented, it provides considerable hardware support for stacks for parameter passing for high level languages. It has 192 registers, 64 of which are conventional registers, the other 128 of which are used as a stack cache. A stack frame pointer into the register file provides relative addressing of registers. If the stack cache overflows, it is spilled to program memory under software control. The chip has the capability of dividing the 256 register address space into 16 banks for multi-tasking. Each instruction may access registers either globally, or based on the register stack pointer.

APL LANGUAGE

分类目录: MS0 实现: Microcoded emulation on IBM 360/25 with WCS 应用: Direct execution of APL 制造商和时间: International Business Machines, 1973 参考: Hassitt et al. (1973)

APL is an inherently interpreted language, so creating an APL direct execution machine is an attractive alternative to interpreters on conventional machines. This project used an IBM 360 Model 25 with writable control store to emulate an operational APL machine. The machine used two stacks resident in program memory: one for expression evaluation, the other for temporary allocation of variable space.

BUFFALO STACK MACHINE

分类目录: SS1 实现: 32-Bit microcoded emulation on a B1700 应用: Block structured language execution 制造商和时间: State University of New York at Buffalo, 1972 参考: Lutz (1973)

The BSM (Buffalo Stack Machine) was a microcoded emulation of a stack architecture that ran on a Burroughs B1700 system. The architecture was designed to support ALGOL-60 type languages. Variables were stored as tagged data in memory with 32 data bits and 4 tag bits. Interrupts were treated as hardware invoked procedure calls, thus saving state automatically on the stack. A sticky point with doing this was that interrupts on stack overflow/underflow had to be made before the stack is completely full/empty to prevent a system crash.

BURROUGHS MACHINES

分类目录: SS0 实现: A family of minicomputers 应用: General purpose multi-user computing 制造商和时间: Burroughs Corporation: 1961-77 (and beyond) 参考: Carlson (1963), Doran (1979), Earnest (1980), Organick (1973)

The Burroughs line of stack computers originated with the ALGOL-oriented B5000 machine in 1961. One of the motivations for this machine was the observation that conventional machines required compilers that were so complex that they were too expensive to run (in 1961).

The B5000 was a 0-operand pure stack machine that kept the stack elements in program memory. The top two stack elements of the B5000 were kept in special registers in the CPU. A special feature of these registers is that there were hardware status bits that allowed 0, 1, or 2 of the registers to contain valid data. This reduced the amount of memory bus traffic by eliminating redundant reads and writes to memory (for example, a POP followed by a PUSH would not cause the same value to be read in from memory, then written back out). The B7700 elaborated on this scheme by using a 32-register stack buffer.

The stacks on these machines were used both for expression evaluation and stack frames for ALGOL procedure calls. Thus, return addresses were interleaved with parameters on the stack. One of the advantages to keeping the stacks resident in program memory was rapid response to interrupts and a low cost for task swapping. Stacks enabled the hardware to treat procedure calls, interrupts, and task calls in a uniform manner.

CALTECH CHIP

分类目录: SS0 实现: 8-Bit microcoded VLSI chip 应用: University VLSI design project 制造商和时间: California Institute of Technology, 1979 参考: Efland & Mosteller (1979)

This stack machine was implemented as a student project for a VLSI design course. The objective was to design and lay out the simplest possible computer in a two and one-half week period. To keep the design simple, the students chose a 0-operand stack machine. The stack on this machine was maintained in program memory with 2 registers containing the top two stack elements on-chip. The instruction set was patterned after the primitives needed by a student-written Pascal compiler.

CRISP

分类目录: SL2 实现: 32-Bit CMOS microprocessor 应用: C language RISC machine. 制造商和时间: AT&T Bell Laboratories, 1987 参考: Ditzel et al. (1987a), Ditzel et al. (1987b), Ditzel & McLellan (1982), Ditzel & McLellan (1987)

The CRISP microprocessor is a RISC machine optimized for executing the C programming language. It is designed as a register-less machine, with all operands memory-resident. However, since the C language uses stacks to allocate storage for local parameters, most of the operand data references are to memory locations relative to a stack pointer register. To support these stack references, CRISP has a 32-element stack cache on-chip. Thus, when a memory-to-memory operation is performed on data near the top of the stack, the operands are fetched and stored using the on-chip cache. CRISP also supports branch folding, a technique where branches are executed in parallel with some instructions.

DRAGON

分类目录: SL2 实现: 2-Chip microprocessor. 应用: Experimental multiprocessor design. 制造商和时间: Xerox Palo Alto Research Center, 1985 参考: Atkinson & McCreight (1987)

The Dragon is an experimental design created with an emphasis on compact binary instruction encodings and fast procedure calls. Variable length instructions and the use of stack-register addressing keep instruction size small while allowing the use of 3-operand instructions. The Dragon has a 128-element execution unit register stack with variable size frames implemented by a pointer pair that define the upper and lower frame bounds.

EM-1

分类目录: SS1 实现: Conceptual design 应用: Structured programming 制造商和时间: Vrije University, The Netherlands, 1978 参考: Tanenbaum (1978)

This often-cited paper gives a discussion of how structured programming techniques should impact machine design, then presents an example design, the Experimental Machine-1 (EM-1). The motivation behind the EM-1 is to provide an efficient environment for well-structured programs. To do this, it uses a single memory-resident stack in typical block-structured language style, and provides 1-operand addressing to access local variables on the stack frame for evaluation using the top of stack. The design skirts the issue of memory bus contention between stack items and instructions by presuming the existence of a stack cache independent of an instruction cache.

EULER

分类目录: SS0 实现: Microcoded interpreter on IBM 360/30 应用: Research into implementing direct high level interpreters in microcode 制造商和时间: IBM Systems Development Division, 1967 参考: Weber (1967)

EULER is an extension of the ALGOL programming language. The EULER project discussed by this paper was an early attempt to implement a direct interpretation machine by adding special-purpose microprogramming to a standard IBM Model 360 computer. In operation, an EULER program was compiled to an intermediate byte-code format. Each byte-code invoked a routine resident in microprogram memory. This system may well have been the first "p-coded" machine. The use of microcoded interpretation was justified for this project by the fact that EULER supports structures such as dynamic typing, dynamic storage allocation, and list processing that were poorly handled by available compilers. The EULER implementation used a single stack resident in program memory for dynamic data allocation and expression evaluation. Data operation instructions were 0-operand RPN byte codes.

FORTH ENGINE

分类目录: ML0 实现: Discrete LS-TTL 应用: Execution of Forth programming language 参考: Winkel (1981)

The Forth Engine was a discrete TTL microcoded stack processor for the Forth language. In addition to a hardware stack for evaluation and subroutine parameter passing and the hardware stack for return address storage, this processor featured a 60-bit writable control store for microcode.

FORTRAN MACHINE

分类目录: MS0 实现: Conceptual Design 应用: Direct execution of the FORTRAN language 制造商和时间: University of Science and Technology of China, PRC, 1980 参考: Chen et al. (1980)

This paper presents a conceptual design paper for a direct execution FORTRAN machine. The proposed machine would have several hardware stacks for return address, loop limit and branch address, and expression evaluation storage. While the implementation method was not specified, isolated memory space stacks would certainly be appropriate to reduce memory traffic. As with most other direct execution machines, stacks were mandatory to support program parsing.

FRISC 3

分类目录: ML0 实现: 32-Bit 2 micron silicon compiler CMOS microprocessor 应用: General purpose space-borne computing and control. Optimized for the Forth language 制造商和时间: Johns Hopkins University, 1986 参考: Fraeman et al. (1986), Hayes (1986), Hayes & Lee (1988), Hayes et al. (1987), Williams et al. (1986)

The Johns Hopkins University/APL Forth processing chip is designed for spacecraft processing applications. The chip executes Forth primitives, and allows multiple operations to be compacted into microcode-like fields in the instruction. Although Forth is a 0-operand language, the chip allows selecting any of the top 4 stack elements to be used with the top stack element for an operation, thus making it a "1/2" operand machine. The on-chip data and return stacks are rather small: 16 elements each, forced mostly by technology constraints.

G-MACHINE

分类目录: SL0 实现: 32-Bit processor simulation 应用: Graph Reduction 制造商和时间: Oregon Graduate Center, 1985 参考: Kieburtz (1985)

The G-Machine was specially built to perform graph reduction in support of executing functional programming languages. It executed G-code, which was a zero-address machine language designed to manipulate its single stack. Program memory was highly structured to support the requirements of graph reduction. Each memory word included four fields used for reference counting and two 32-bit cells used for graph pointers.

GLOSS

分类目录: SS0 实现: Conceptual design 应用: Multiple communicating processors. 制造商和时间: University of Washington, 1973 参考: Herriot (1973)

The GLOSS conceptual design was an attempt to define a generic high level language machine for a variety of languages, including ALGOL 68, LISP 1.5, and SNOBOL 4. It was based on using a demand driven data-flow system where sub-processes were invoked on multiple parallel processors in a manner similar to procedure calls. Each processor had a set of evaluation stacks resident in memory.

HITAC-10

分类目录: SS0 实现: Add-on hardware to a minicomputer 应用: Experimental minicomputer addition 制造商和时间: Keio University, Japan, 1974 参考: Ohdate et al. (1975)

The stack hardware discussed in this paper was back-fitted onto an existing HITAC-10 minicomputer. In order to simplify the design and construction, the stack hardware was added as an I/O device on the system bus. The stack controller had four top-of-stack registers. All extra elements were stored in an area of memory using DMA. The controller had two stack limit pointers for memory bounds checking. The stack controller was used for subroutine parameter passing; no arithmetic could be performed on stack elements.

HP300 & HP3000

分类目录: SS1 实现: 16-Bit Minicomputer family 应用: General purpose multi-user computer. 制造商和时间: Hewlett Packard, 1976-1980's 参考: Bartlett (1973), Bergh & Mei (1979), Blake (1977)

The HP3000 family is a commercial line of minicomputers based on a 1-operand stack architecture. The origins of the family may be found in the HP300 computer, which could be considered a 3-address machine that had two top-of-stack registers buffering a program memory resident stack. Later, the HP3000 series used a stack/accumulator addressing mode, and included four top-of-stack registers. The stacks were featured in the architectures to ease implementation of reentrancy, recursion, code sharing, program protection, and dynamic storage allocation in a multi-user environment. The stack is used not only for expression evaluation, but also for parameter passing and subroutine return address storage.

HUT

分类目录: MS0 实现: 16-Bit AM2903 bit-sliced processor 应用: Spacecraft experiment control. Optimized for the Forth language 制造商和时间: Johns Hopkins University, Applied Physics Laboratory 1982 参考: Ballard (1984)

The HUT processor was designed to control the Hopkins Ultraviolet Telescope (HUT) Space Shuttle experiment. At the time it was designed, no space-qualified microprocessors were powerful enough for the task, so a bit-sliced processor was custom designed for the job. The designers chose to implement a Forth language processor for simplicity of implementation, extensibility, and flexibility.

ICL2900

分类目录: SS1 实现: Family of mini-computers 应用: General purpose computing. 制造商和时间: ICL, 1975 参考: Keedy (1977)

The designers of the ICL family were concerned with protection and code sharing in a multiprogrammed environment, as well as efficient compilation and execution with compact object code. While often compared with the contemporary Burroughs machines, the ICL machines had several distinct characteristics. One of these was the use of a stack/accumulator 1-operand addressing scheme and several specialized registers. With this capability, a register or memory location could be used with the top stack element for operations.

INTEL 80x86

分类目录: SS2 (when used in stack mode) 实现: Family of 16 and 32-bit microprocessors 应用: General purpose computing 制造商和时间: Intel Corporation 1980's 参考: Intel (1981)

The 80x86 processor family, which includes the 8088, 8086, 80186, 80286, and 80386 is a family of microprocessors with a general purpose register architecture. Simple PUSH and POP instructions are supported to manipulate the stack. Many high level language compilers produce code that uses the BP (base pointer) register as a frame pointer to a combined return address and parameter passing stack. When used in this mode, the 80x86 family can be considered to be doing stack processing. In the context of stack computers, the 80x86 is simply included in this listing as a representative example of a conventional machine that can be used as an SS2 architecture.

INTERNAL MACHINE

分类目录: MS0 实现: Conceptual design 应用: Directly interpretable languages 制造商和时间: North Electric Co., 1973 参考: Welin (1973)

The Internal Machine was a conceptual design for a machine that could efficiently execute directly interpretable languages. A stack instruction model was picked for generality. The design specifies two stacks: one for expression evaluation and parameter passing, and a second stack for subroutine return addresses.

IPL-VI

分类目录: SS1 实现: Conceptual design for microcoded interpreter 应用: General purpose computing 制造商和时间: Rand Corporation, 1958 参考: Shaw et al. (1959)

The Information Processing Languages (IPL's) were a series of conceptual language designs for implementing high level programs. IPL-VI was a language designed meant to be implemented as an interpreted language with microcode support. IPL-VI emphasized advanced (for 1959) computing structures for non-numerical computing, especially list manipulation. A stack was used to pass parameters between subroutines. Since all memory in the IPL-VI design was formatted as list elements, the subroutine parameter LIFO consisted of a list of elements that pointed to the next element further down in the list. IPL-VI instructions used 1-operand addressing.

ITS (Pascal)

分类目录: SS0 实现: 16-Bit microprocessor 应用: Direct execution of Pascal P-code 制造商和时间: Nippon Electric Co., 1980 参考: Tanabe & Yamamoto (1980)

The ITS processor was designed to execute UCSD Pascal P-code. The designers claimed a several-times speedup over fully compiled code on a contemporary microprocessor (presumably an 8086). The ITS had a 256-word stack on-chip, which was apparently only used for expression evaluation. The top two stack elements were kept in registers for speed.

KDF-9

分类目录: ML0 实现: 48-Bit mainframe 应用: General purpose computing using ALGOL 制造商和时间: English Electric, 1960 参考: Allmark & Lucking (1962), Duncan (1977), Haley (1962)

The KDF-9 was perhaps the first true stack computer. It was inspired by the advent of ALGOL-60, and introduced many of the features found on modern stack computers. The KDF-9 had an expression evaluation stack which could be used for parameter passing, as well as a separate return address stack. Unfortunately, these stacks were limited by technology considerations to only 16 elements apiece (constructed from magnetic cores!). A problem with the design was that while 16 elements is quite sufficient for expression evaluation, the ALGOL compiler was constrained by the 16-element stack depth, causing slow compilation.

KOBE UNIVERSITY MACHINE

分类目录: ML0 实现: 16-Bit wide AM2903 bit-slice with writable control store. 应用: Academic Research 制造商和时间: Kobe University, Kobe Japan, 1983 参考: Kaneda et al. (1983), Wada et al. (1982a), Wada et al. (1982b)

This machine was designed to execute both Forth and Pascal efficiently using a stack architecture. Forth was executed by directly implementing Forth primitives in microcode. Pascal was executed by supporting a UCSD P-code emulator. This machine had separate data memory in addition to program memory.

LAX2

分类目录: SS0 实现: Microcoded interpreter on Varian V73 应用: Experimental 制造商和时间: Group for Datalogical Research & Royal Institute of Technology, Sweden, 1980 参考: Bage & Thorelli (1980)

The LAX2 architecture was implemented as a partially microcoded interpreter with the goals of cost effective software production along with good memory and execution time economy for string manipulation and interactive applications. The architecture used tagged data types. Each process in the machine had a private memory area shared between the evaluation stack and a garbage-collected heap for temporary string storage.

LILITH

分类目录: ML1/MS1 实现: 16-Bit AM2901 bit-sliced processor 应用: Direct execution of Modula-2 M-code and interactive user interfaces. 制造商和时间: ETH (Swiss Federal Institute of Technology), 1979. 参考: Ohran (1984), Wirth (1979)

Lilith was a Modula-2 execution machine developed by Niklaus Wirth. The goals of the machine were to provide efficient support for the Modula-2 language as well as an effective user interface. A stack architecture was chosen for compact code. The Lilith had two stacks: a program memory resident stack for parameter passing, and a hardware expression evaluation stack. The instruction set was stack-based, with the ability to read an element from any location in the parameter stack and operate upon it with the top element of the evaluation stack.

LISP MACHINES

分类目录: ML1 实现: Various machines. 1982 to present. 应用: List processing, artificial intelligence research 制造商和时间: Various companies (such as Symbolics) 参考: Lim (1987), Moon (1985), Sansonnet et al. (1982)

LISP machine architecture is a whole topic in its own right; this is simply a summary of some of the common characteristics of LISP-specific processors. LISP machines tend to have multiple stacks and 1-address (relative to top of stack) instruction formats. Some machines have substantial hardware stacks (over 1K word) that can overflow into program memory. Procedure calls tend to be very important, because of the recursion commonly used in traversing lists. These machines typically store data elements in a garbage-collected program/data memory.

MCODE

分类目录: SS1 实现: Unspecified 应用: Execution of Modula-2 M-code (using StarMod language) 制造商和时间: University of Wisconsin - Madison, 1980 参考: Cook, R. & Donde, N. (1982), Cook, R. & Lee, I. (1980)

The MCODE machine was designed to execute Modula-2 M-code, which was compiled from a language called StarMod, a Modula-2 derivative. MCODE was based on Tanenbaum's EM-1 design, but with several improvements to solve problems that arise in real computers. One improvement was the use of a set-mode instruction that changed the interpretation of the data types (integer, floating point, etc.) for all subsequent operations.

MESA

分类目录: SS0 实现: Architectural family for workstations 应用: Graphics-intensive workstations (Alto, Dorado machines) 制造商和时间: Xerox Office Products Division, 1979 参考: Johnsson & Wick (1982), McDaniel (1982), Sweet & Sandman (1982)

Mesa was actually a modular high level language expanded to include an architecture for a family of processors. The goals of the architecture were efficient implementation, compact encoding, and technology independence. In order to accomplish these goals, the Mesa architecture specified a single stack for use in expression evaluation and parameter passing to procedures for the purpose of producing compact 0-operand stack instructions. While the stack implementation was not specified, Mesa follows the general pattern of machines with a small stack buffer and the bulk of the stack in program memory. An interesting instruction was the "recover" operation, which captured a previously popped stack value that had not yet been overwritten by doing a stack push without writing a value.

MF1600

分类目录: ML0 实现: TTL discrete components, 16-bit machine 应用: General purpose processing 制造商和时间: Xycom & Advance Processor Designs, 1987 参考: Burnley & Harkaway, 1987

The Advanced Processor Design MF1600, which is the processor used on the Xycom XVME-616 product, is a high performance Forth machine design that makes use of fast TTL logic devices. It features a 16-bit data path and a microcode memory ROM that can be customized by the manufacturer for specific applications.

MICRO-3L

分类目录: SL1 实现: Simulated machine 应用: Functional Programs 制造商和时间: University of Utah, 1982 参考: Castan & Organick (1982)

The micro-3L processor project used the 3L-model (Lisp Like Language model) for specifying a processor that is well suited to list processing. The project proposed creating a multiprocessor system to execute functional languages. Each micro-3L processor was to use a 256-element register file. 128 of the registers were intended to be used as a return address stack, with overflow handled by swapping into main memory. Data manipulations were performed using an Accumulator and an operand from the bottom 128 elements of the register file.

MICRODATA 32/S

分类目录: SS0 实现: Microcode upgrade to a 16-bit register-oriented minicomputer. 应用: Running a version of PL/I 制造商和时间: Microdata Corporation, 1973 参考: Burns & Savitt (1973)

The Microdata 32/S was a version of the Microdata 3200 general-purpose minicomputer that had additional microcode to implement stack instructions. The 3200 system was a 16-bit minicomputer implemented in discrete TTL technology. The reason for adding the stack-based capabilities was that compilers of the time could not produce efficient code. Stack architectures made code generation easier. The reason good code generation is important was to remove the impetus for programming in assembly language. The main memory stack was used for expression evaluation and parameter passing, with up to four stack elements buffered in registers.

MISC M17

分类目录: MS0 实现: 16-Bit 2.0 Micron HCMOS gate array 应用: Low cost real time control 制造商和时间: Minimum Instruction Set Computer, Inc., 1988 参考: MISC (1988)

The MISC M17 microprocessor is a low cost, embedded micro-controller. The M17 instruction set is based on Forth primitives. In contrast with most other Forth machines, the M17 reduces hardware costs at some compromise in performance by keeping its two stacks in program memory with a few top-of-stack buffer registers on-chip.

MOTOROLA 680x0

分类目录: MS2 (when used in stack mode) 实现: Family of 32-bit microprocessors 应用: General purpose computing 制造商和时间: Motorola Corporation 1980's 参考: Kane et al. (1981)

The 680x0 processor family, which includes the 68000, 68010, 68020, and 68030, is a family of microprocessors with a general purpose register architecture. Registers are divided into two groups: address registers and data registers. The address registers support postincremented and predecremented addressing modes. This allows a programmer to use up to eight stacks, one stack per address register. By convention, the A7 register is used as the stack frame pointer for most languages. Of course, the 680x0 family is usually not used as a multiple-stack machine, but nonetheless this capability exists.

MU5

分类目录: SS1 实现: Minicomputer 应用: Research 制造商和时间: University of Manchester, 1971 参考: Morris & Ibbett (1979)

The MU5 used a 1-operand instruction format with a single stack in program memory. Stack instructions were used because they led to easy code generation, compact programs, and easily pipelined hardware. An interesting twist is that there were five registers accessible to the programmer, all of which were simultaneously at the "top-of-stack." Pushing a value into any register pushed the previous register value onto the single stack. This arrangement is subtly different from having the top five stack elements accessible as registers.

NC4016

分类目录: ML0 实现: 16-Bit Gate Array processor 应用: Real Time Control, direct support for Forth programming language 制造商和时间: Novix, 1985 参考: Golden et al. (1985), Jennings (1985), Miller (1987), Novix (1985)

The NC4000, later renamed the NC4016, was the first chip designed to execute Forth. Since it is on a gate array, the two hardware stack memories reside off-chip, connected to the processor by dedicated stack busses. The top two data stack elements are buffered on-chip, as is the top return stack element. The processor executes most Forth primitives including subroutine call in a single clock cycle, and allows multiple primitive operations to be compressed into a single instruction in some circumstances.

NORMA

分类目录: SL0 实现: Experimental machine using MSI/LSI standard logic and gate arrays 应用: Functional programming/graph reduction 制造商和时间: Burroughs Corporation Austin Research Center, 1986 参考: Scheevel (1986)

The Normal Order Reduction MAchine (NORMA) is a research processor developed by Burroughs for high speed graph reduction operations in support of functional programming languages. Five specialized functional units that handle arithmetic, graph memory, garbage collection, graph processing, and external I/O are connected using a central bus. The graph processor maintains a single stack used during the depth-first traversal of the tree-structured program graphs.

OPA (Pascal)

分类目录: ML0 实现: Emulator running on Lilith computer 应用: Support for Pascal & Modula-2 programs 制造商和时间: Federal Institute of Technology, Zurich, 1984 参考: Schulthess (1984)

The Object Pascal Architecture (OPA) is a design for a machine that efficiently executes compiled Pascal code. The OPA contains three stacks: one for descriptors and expression evaluation, one for storing subroutine parameters, and one for return addresses. The OPA instruction set is billed as a "reduced high level language" instruction set, since it supports Pascal constructions with a small number of opcodes.

PASCAL MACHINE

分类目录: ML0 实现: Experimental processor 应用: Direct execution of Tiny-Pascal source code 制造商和时间: University of Maryland, 1981 参考: Lor & Chu (1981)

The Pascal interactive computer is an experimental system for direct execution of Pascal source code. Since the system includes a hardware compiler as well as execution unit, hardware stacks in the system abound. Some of the stacks are used to store return addresses, operator precedence, expression evaluation values, and subprogram nesting levels. Since expressions are evaluated as they are interpreted, the actions taken by the execution unit are the same as would be taken by a 0-operand stack architecture.

PDP-11

分类目录: MS1 (when used in stack mode) 实现: Family of mini & microcomputers (also, later the VAX family) 应用: General purpose mini-computer 制造商和时间: Digital Equipment, 1970 参考: Bell et al. (1970)

The DEC PDP-11 was an early general-purpose computer to integrate stack usage into a general-purpose register machine. While the machine is clearly register-oriented, it includes as a subset of its capabilities those of a one-address stack machine. By using register-indirect addressing with auto-postincrement and auto-predecrement, a general-purpose register can be used as a stack pointer for an evaluation stack. The PDP-11 also has a stack pointer for use with interrupts, traps, and subroutine calls. Later, the VAX line of computers introduced hardware support for single-stack dynamic frame allocation for block-oriented languages. Of course the PDP-11 is really a general-purpose register machine, but Bell's article describes how it can be used in an MS1 stack mode.

POMP PASCAL

分类目录: SS1 实现: Bit-sliced processor (AMD 290x) 应用: Research into emulating intermediate forms for block structured languages 制造商和时间: Stanford University, 1980 参考: Harris (1980)

The Pascal Oriented MicroProcessor (POMP) project used a bit-sliced processor to execute stack code. Stack code was chosen to reduce program size from 3 to 8 times smaller than traditional compiler outputs. In fact, the POMP code was claimed to be only 50% larger than Flynn's ideal DEL encoding, but is much easier to decode since it was encoded in byte-wide blocks. The stack machine could accesses up to 8 local variables for operations, making it a 1-operand machine.

PSP

分类目录: ML2 实现: Architectural proposal 应用: General purpose computing 制造商和时间: University of Illinois, 1985 参考: Eickemeyer & Patel (1985)

The Parallel Stack Processor (PSP) architecture is an attempt to preserve the function of a normal general purpose register machine yet reap the benefits of having hardware stacks for saving registers on a subroutine call. To accomplish this, the machine hides a stack behind every register in the machine. Whenever a subroutine call is encountered, each register is pushed onto its own stack simultaneously, performing a single-cycle multiple register save. Strictly speaking, this is more of a register machine that has hardware to save registers than a stack processor architecture, but the idea is intriguing for other stack applications.

PYRAMID 90X

分类目录: SL2 实现: 32-Bit minicomputer 应用: General purpose RISC processor 制造商和时间: Pyramid Technology, 1983 参考: Ragan-Kelley & Clark (1983)

The Pyramid 90x was one of the first commercial processors to have many RISC attributes. The 90x uses a register stack that is organized as 16 non-overlapped windows of 32 registers plus 16 global registers for a total of 528 registers. The registers are spilled to memory if subroutine nesting is more than 15 levels deep.

QFORTH

分类目录: ML0 实现: Architectural study 应用: Direct support for Forth programming language 制造商和时间: Queens College of CUNY, 1984 参考: Vickery (1984)

The QFORTH architecture was built for multitasking single-user execution of the Forth programming language. The internal architecture included two source busses (which could be read the top two elements of the data stack) and a single destination bus to write the top-of-stack back. The stack management unit internally buffered the top stack elements in high speed registers, and allowed for a single stack memory to be partitioned into several simultaneously used stacks.

REDUCTION LANGUAGE MACHINE

分类目录: ML0 实现: Laboratory model 应用: Execution of reduction language programs 制造商和时间: GMD Bonn, 1979 参考: Kluge & Schlutter (1980)

Reduction languages use structures of the form: apply function to argument. These structures are well represented by subtrees with a function node having children that are its operands. Since the execution of a program involves evaluating these tree structures, three major stacks are central to the operation of the machine. One stack acts as a program source, another as a program sink, and the third as a temporary evaluation stack area. An interesting feature of the machine is that there is no program memory, and the operation of the machine does not involve any addresses as such. All programs are shuffled between the source and sink stack memories.

REKURSIV

分类目录: ML0 实现: 1.5 Micron CMOS using 3 gate arrays 应用: Object oriented programming 制造商和时间: Linn Products, 1984-88 参考: Pountain (1988)

Rekursiv is designed for fast execution of object-oriented programs. It supports a very high level instruction set that may be extended using a large amount of off-chip microcode, and has extensive support for memory management designed into the system. An evaluation stack is used for expression evaluation, while a control stack is used for microcode procedure return address storage.

RISC I

分类目录: SL2 实现: 32-Bit microprocessor 应用: RISC processor for C and other high level languages 制造商和时间: University of California, Berkeley, 1981 参考: Patterson & Piepho (1982), Patterson & Sequin (1982), Sequin & Patterson (1982), Tamir & Sequin (1983)

The RISC I was the first highly publicized RISC computer. It owes a substantial amount of its performance to the use of register windows. The "gold" RISC I chip uses an overlapped register window scheme with 78 registers. At any given time, there are 32 addressable registers: 10 global registers, 6 registers shared with the calling subroutine, 10 private registers, and 6 registers used to pass parameters to subroutines at the next deeper nesting level. The registers are accessed using normal 2-operand register-to-register instructions. The RISC I allows accessing the contents of a register as a memory location by automatically mapping the memory access into the register space. This solves the up-level addressing problem that can occur in languages like Pascal.

ROCKWELL MICROCONTROLLERS

分类目录: MS0 实现: Forth-in-ROM on 6502 and 68000 microcontrollers. 应用: Embedded controllers that run Forth programs. 制造商和时间: Rockwell International, 1983 参考: Dumse (1984)

While not strictly speaking hardware-supported stack machines, microcontrollers that have Forth burned into their ROM's are an interesting member of the stack-based computer family. The R65F11, based on the 6502 processor, and the F68K processor, based on the 68200 microcontroller of the 68000 processor family, are general purpose microcontrollers that come with preprogrammed Forth primitives. These chips in effect emulate a two-stack Forth engine, using variables and program memory to provide the emulation. Other dedicated Forth microcontrollers have been made since (including the Zilog Super8 chip), but Rockwell was the first to do it.

RTX 2000

分类目录: ML0 实现: 16-Bit, 2 micron standard cell CMOS microprocessor 应用: Semicustom design for application-specific designs. Optimized for Forth programming language 制造商和时间: Harris Semiconductor, 1987-89 参考: Danile & Malinowski (1987), Harris Semiconductor (1988a), Harris Semiconductor (1988b), Jones et al. (1987)

The RTX (Real Time Express) is a macrocell in the Harris standard cell library. This allows the processor to be built as a stand-alone microprocessor, or as an integrated microprocessor with I/O devices, hardware multiplier and stack memory on-chip. The instruction set directly corresponds to FORTH programming language primitives. The design uses an unencoded instruction format that allows multiple operations to be compacted into each instruction. As with many Forth processors, the RTX 2000 supports single-cycle subroutine calls.

RTX 32P

分类目录: ML0 实现: 32 Bits, 2.5 micron CMOS 应用: Stack-based processing for real time control and expert systems. 制造商和时间: Harris Semiconductor and WISC Technologies, 1987-89 参考: Koopman (1987c), Koopman(1987d), Koopman (1989)

The Harris RTX 32P is a prototype 32-bit stack processor chip set. A unique feature of the RTX 32P is the combination of an opcode with a next-address field in every instruction. This allows zero-cost subroutine calls, returns, and unconditional branches by overlapping the next address computation with the opcode execution. The system can execute one opcode and a subroutine call each memory cycle.

RUFOR

分类目录: ML0 实现: 16-Bit AM2901 bit-slice microcoded processor 应用: Research processor for Forth language 制造商和时间: Wright State University, 1984 参考: Grewe & Dixon (1984)

The RUFOR system is a conventional bit-sliced approach to building a machine optimized for the Forth programming language. There are two hardware stacks, one for data and one for return addresses. The top entry of each stack is held in one of the 2901 internal registers, so that only a single input bus to the ALU and a single output bus back to the stacks is required.

SF1

分类目录: ML2 实现: 3-Chip, 32-bit microprocessor using 3 micron CMOS 应用: High level language support for real time control 制造商和时间: Wright State University, 1987-88 参考: Dixon (1987), Longway (1988)

The SF1 (which stands for Stack Frame computer number 1) is an experimental multi-stack processor designed to efficiently execute high level languages, including both Forth and C. The current implementation has five stacks, any two of which may be selected as the source and destination for an instruction. The SF1 allows arbitrary access to its stack elements by using a 13 bit address relative to the top stack element in the instruction format.

SOAR

分类目录: SL2 实现: Microprocessor 应用: Support for Smalltalk-80 language 制造商和时间: University of California, Berkeley, 1984 参考: Bush et al. (1987)

The Smalltalk On A RISC project (SOAR) modified the Berkeley RISC II architecture to adapt it to Smalltalk-80. Since Smalltalk-80 is a stack-oriented bytecode language, this is an exercise in mapping stack code onto a register-oriented RISC, which in turn has its registers arranged in an overlapped window register stack. The window size of the register stack was only 16 registers, half that of RISC II, since Smalltalk methods tend to be smaller than procedures in traditional programming languages.

SOCRATES

分类目录: ML2 实现: Conceptual design 应用: Use of bubble memories for main program storage 制造商和时间: University of Massachusetts/Amherst, 1975 参考: Foster (1975)

SOCRATES (Stack-Oriented Computer for Research and Teaching) was a design that proposed using magnetic bubble memories as its main storage. At the time of the design, bubble memories were projected to cost 100 times less per bit than other memories. The only problem was that they could only be accessed sequentially. SOCRATES took advantage of this situation by proposing 64 addressable registers of 32 bits, with each register being the top element of a 32K word bubble memory configured as a LIFO stack.

SOVIET MACHINE

分类目录: ML1 实现: Conceptual design 应用: Execution of block-structured languages 制造商和时间: Academy of Sciences of the USSR, 1968 参考: Myamlin & Smirnov (1969)

This paper presented a design for a stack computer for executing block-structured languages. The design had two stacks: one for holding arithmetic operations and one for holding operands. While not a directly interpreting machine, it was apparently intended to have source programs maintain an infix format with infix to postfix conversion done on-the-fly. Stacks could be addressed as part of program memory if desired, but were physically separate components.

SYMBOL

分类目录: MS0 实现: Discrete TTL prototype machine 应用: Research 制造商和时间: Iowa State University, 1971 参考: Ditzel & Kwinn (1980), Hutchison & Ethington (1973)

The SYMBOL project constructed an operational computer using no software. The editor, debugger, and compiler were all implemented using random logic circuits. User programs were entered in source code, then compiled and executed using hardwired control circuits. The compilation unit transformed code into a stack-based intermediate form before execution. Several other stacks were used elsewhere as required by the compiler.

TRANSPUTER

分类目录: SS0 实现: Family of 16- and 32-bit microprocessors 应用: Parallel processing 制造商和时间: INMOS Limited, 1983 参考: Whitby-Strevens (1985)

The Transputer is a single-chip microprocessor system designed for parallel processing. Since replicating a complete processor with memory and peripherals is very expensive, the Transputer attempts to squeeze an entire functional system onto a single chip to hold costs down for systems with large numbers of processors. This constraint places program memory space at a premium, so a stack-based instruction set was selected to reduce program size. The Transputer uses 3 registers to form an expression evaluation stack.

TM

分类目录: ML0 实现: Simulated design 应用: Research 制造商和时间: Carnegie Mellon University, 1980 参考: Harbison (1982)

The Tree Machine (TM) architecture was an attempt to make compilers simpler by performing common compiler optimizations using a value cache. This cache would do common subexpression elimination and invariant code motion in hardware by caching results to recently computed expressions. A stack-based architecture was chosen because this allowed better operation with the value caching hardware and eliminated the compiler complexity associated with register allocation. The TM used two stacks: a data stack for expression evaluation, and a control stack for dependency information and return address storage.

TREE MACHINE

分类目录: MS0 实现: Conceptual design 应用: Executing block-structured languages 制造商和时间: Massey University, New Zealand, 1971 参考: Doran (1972)

Doran's tree machine recognized that good programs have an inherent tree structure, and was tailored to execute these well-structured programs. The machine had three stacks resident in program memory: a control stack for return addresses, a value stack to store intermediate results for non-tree-leaf nodes, and a data stack for scratch storage allocation. An interesting feature of the machine was that conditional branches are not required. All conditional execution were accomplished with a conditional procedure return to the parent program node.

VAUGHAN & SMITH'S MACHINE

分类目录: ML0 实现: Conceptual design 应用: Support for Forth programming language 参考: Vaughan & Smith (1984)

This paper discusses the design of a Forth-based computer. The architecture was chosen because Forth is good at representing the tree nature of structured programs. Forth's small subroutine size allows good code compaction through subroutine reuse. The proposed design featured two independent hardware stacks. The return stack had one top-of-stack register, while the data stack had two registers.

WD9000 P-ENGINE

分类目录: SS0 实现: 5-Chip LSI set 应用: Direct execution of Pascal P-code 制造商和时间: Western Digital, 1979 参考: O'Neill (1979)

The Western Digital Pascal micro-engine (the WD9000 chip set) was built to execute Pascal P-code. Since P-code presumes the existence of a single data stack, the WD9000 supported a single program memory resident stack for expression evaluation and parameter passing.

WISC CPU/16

分类目录: ML0 实现: Discrete LS-TTL, 16-bit data paths 应用: Stack-based processing 制造商和时间: WISC Technologies, 1986 参考: Haydon & Koopman (1986), Koopman (1986), Koopman (1987b), Koopman & Haydon (1986)

The WISC CPU-16 is a user-microcodable processor with a Forth-language machine heritage. It has both a data stack and a return address stack. Additionally, it has a 2K word by 30 bit writable control store for user-defined microcode. The architecture is general, and allows supporting other languages besides Forth. An interactive single-step capability is intended for use in teaching microcode techniques to students.

WISC CPU/32

分类目录: ML0 实现: 32 Bits, discrete TTL 应用: Stack-based processing for real time control and expert systems. 制造商和时间: WISC Technologies, 1986-87 参考: Koopman (1987c), Koopman(1987d)

The WISC CPU/32 is the discrete TTL system upon which the Harris RTX 32P is based. The RTX 32P and CPU/32 are microcode and instruction set compatible.

其它参考资料

Bulman (1977): A general tutorial on stack architectures with emphasis on the B5500 and HP3000 as example architectures. Also mentions the Data General Eclipse and PDP-11 as examples of conventional machines incorporating some stack concepts.

Carlson (1975): A good survey of various high level language computer architectures, many of which are stack-oriented.

Doran (1975): Reviews the use of stacks for expression evaluation, data tree traversal, and subroutine return address saving, concentrating on the Burroughs architectures.

McKeeman, W. (1975): A comprehensive tutorial on the operation of stack-based computers and the role of stacks in general purpose computing.

Myers (1982): While not specifically about stack machines, this text describes many of the architectural innovations that are pertinent to discussions on stack machines. Of particular interest is the discussion of semantic gap.

Siewiorek, Bell & Newell (1982): This computer architecture text contains many chapters on stack machines.

Yamamoto, M. (1981): Gives a large list of high level language machines developed in Japan, many of which are stack-oriented.

附录 B Forth 原语汇总

Forth 语言是基于一个可扩展的、交互式的编译器,它为虚拟堆栈计算机产生代码。这个虚拟机有两个堆栈。数据栈用于表达式计算和子程序参数传递。返回栈用于保存子程序返回地址和循环控制变量。 Forth 源代码直接反映了堆栈计算机的底层,它使用逆波兰表示法( RPN )来执行所有的操作。

Forth 程序是按子程序分层建立的。每个子程序在 Forth 术语被称为“字”。一个程序就是由一个字调用其它的字,并按这种方式形成程序的树结构。在低层,树的叶子就是 Forth 的原语字,它处理堆栈并完成计算。

下面是本书中讨论堆栈计算机时遇到的 Forth 原语字。大多数原语都可以用于任何语言为堆栈计算机编写程序(例如,对栈顶的两个元素相加或者交换两个栈顶元素的顺序)。在讨论中使用 Forth 术语是为了保证与现有堆栈计算机操作标准字汇的一致性。

每个 Forth 字都在同一行的后面有一个“堆栈说明”,它给出了所描述的这个字在 Forth 数据栈的输入和输出参数。在“ - ”左边的值指示输入参数,“ _ ”右边是输出参数。在参数列表中。堆栈最顶端的元素在最右边。 N1 , N2 , N3 等表示单精度整数。 D1 , D2 等表示双精度整数,它占据两个数据栈的位置。 ADDR 是地址,可以认为是一个指针。 FLAG 是一个整数,如果为 0 是 FALSE ,如果非 0 则为 TRUE 。

0 - 0 把整数0放到堆栈上 0< N1 - FLAG 如果N1是负数则返回FLAG标志为真 0= N1 - FLAG 如果N1是0则返回FLAG标志为真 0 N1 - FLAG 如果N1大于等于0则返回 FLAG 为真 0BRANCH N1 - 如果 N1 为假(值为0) 则执行分支其地址在下一个程序单元,否则继续 1+ N1 - N2 把N1加1,返回N2 1- N1 - N2 从N1减1,返回N2 2+ N1 - N2 N1加2,返回N2 2* N1 - N2 把N1乘2,返回N2 2/ N1 - N2 N1除以2,返回N2 4+ N1 - N2 N1加4,返回N2 < N1 N2 - FLAG 如果N1小于N2,则返回FLAG为真 N1 N2 - FLAG 如果N1不等于N2,返回 FLAG 为真 = N1 N2 - FLAG 如果N1等于N2则返回 FLAG 为真 R N1 - 把N1压入返回栈 > N1 N2 - FLAG 如果N1大于等于N2则返回 FLAG 为真 ! N1 ADDR - 把N1存入程序存储器ADDR位置 + N1 N2 - N3 加 N1 和 N2, 给出和 N3. +! N1 ADDR - 把N1与指针ADDR位置处的值相加,结果存入那个位置 - N1 N2 - N3 从N1中减去N2,给出差N3 : - 开始一个子程序的定义。每次这个子程序被其它的定义引用时原语[CALL]被编译 ; - 执行子程序返回并结束一个子程序的定义。原语 [EXIT] 被编译。 ?DUP N1 - N1 N1 ( if N1 non-zero ) N1 - N1 ( if N1 is zero ) 如果N1不等于0,则复制栈顶,否则数据栈不变 @ ADDR - N1< 读出位于程序存储器ADDR处的值,返回N1 ABS N1 - N2 取N1的绝对值返回结果N2 AND N1 N2 - N3 执行N1和N2的位与,给出结果N3 BRANCH - 执行一个无条件分支 D! D1 ADDR - 把一个双精度的值存储在程序存储器ADDR开始的位置。 D+ D1 D2 - D3 双精度D1和D2求和并返回D3 D@ ADDR - D1 从程序存储器ADDR开始的地址处取出双精度值D1 DDROP D1 - 去除双精度整数D1 DDUP D1 - D1 D1 在堆栈上复制双精度数 D1 DNEGATE D1 - D2 返回D2,它是D1的二进制补码 DROP N1 - 从堆栈上去除N1 DSWAP D1 D2 - D2 D1 交换堆栈上的两个双精度数 DUP N1 - N1 N1 复制 N1到堆栈顶上 I - N1 返回当前活动循环的索引 I' - N1 返回当前活动循环的限制值 J - N1 返回嵌套循环结构外层循环的索引值 LEAVE - 通过设置返回栈顶的循环计数器等于循环的限制值而退了循环。 LIT - N1 把编译的内嵌值视为一个整数,把它压入返回栈作为N1 NEGATE N1 - N2 返回N2,它是N1 的二进制补码 NOP - 为做任何事情 NOT FLAG1 - FLAG2 求反标志 OR N1 N2 - N3 对于N1和N2执行位或,给出结果N3 OVER N1 N2 - N1 N2 N1 复制堆栈的第二个元素N1作为新的栈顶 PICK ... N1 - ... N2 把数据栈上的第N1个元素作为栈顶。在 Forth-83, 0 PICK 等于 DUP, 1 PICK 等于 OVER . R> - N1 弹出当前返回栈顶元素把它压入数据栈作为N1 R@ - N1 复制返回栈顶N1到数据栈 ROLL ... N1 - ... N2 取出数据栈的第 N1个元素到栈顶,填充堆栈上的空间。 在Forth-83中, 1 ROLL 等于SWAP , 2 ROLL 等于 ROT ROT N1 N2 N3 - N2 N3 N1 拉出堆栈上的第三个元素作为栈顶 S-D N1 - D2 对N1进行符号扩展使之占两个字的位置,成为一个双精度数据D2 SWAP N1 N2 - N2 N1 交换两个栈顶元素的顺序 U< U1 U2 - FLAG 如果无符号数N1小于N2则 FLAG 返回真 U U1 U2 - FLAG 如果无符号数据U1大于 U2 则FLAG返回真 U* N1 N2 - D3 对N1和N2执行无符号乘法,得到一个无符号双精度结果D3 U/MOD D1 N2 - N3 N4 执行双精度数据D1和单精度数N2的无符号整数除法,得到商N4和余数N3 XOR N1 N2 - N3 执行N1和N2的按位异或,得到结果 N3 附录 C 完整的指令频率统计

以下是在第六章中讨论的没有删节的动态指令频率。

NAMES FRAC LIFE MATH COMPILE AVE ! 1.89% 0.00% 0.71% 0.98% 0.90% * 0.00% 0.00% 0.02% 0.05% 0.02% + 3.41% 10.45% 0.60% 2.26% 4.18% +! 0.00% 0.00% 0.11% 0.83% 0.24% +- 0.34% 0.00% 0.00% 0.02% 0.09% - 0.97% 1.24% 0.08% 1.94% 1.06% / 0.07% 0.00% 0.00% 0.05% 0.03% 0< 1.84% 0.00% 0.66% 0.05% 0.64% 0= 0.00% 0.00% 0.77% 0.00% 0.19% 0> 0.00% 0.00% 0.09% 0.02% 0.03% 0BRANCH 3.39% 6.38% 3.23% 6.11% 4.78% 1+ 1.72% 0.08% 0.01% 1.36% 0.79% 1- 0.41% 0.00% 0.54% 0.01% 0.24% 2* 2.11% 2.05% 0.02% 0.64% 1.21% 2+ 0.49% 0.00% 0.19% 0.66% 0.34% 2- 0.07% 0.00% 0.00% 1.02% 0.27% 2/ 0.92% 0.00% 0.00% 0.01% 0.23% < 0.11% 0.08% 0.01% 1.08% 0.32% 0.00% 0.00% 0.00% 0.00% 0.00% 0.20% 0.00% 0.01% 0.18% 0.10% 0.62% 0.08% 0.06% 1.19% 0.49% >R 2.05% 0.00% 11.28% 2.16% 3.87% ?DUP 0.00% 0.00% 0.00% 1.11% 0.28% ?STACK 0.00% 0.00% 0.00% 0.49% 0.12% @ 7.49% 2.05% 0.96% 11.09% 5.40% ABS 0.51% 0.00% 0.01% 0.01% 0.13% ADC 0.00% 0.00% 2.53% 0.00% 0.63% AND 0.17% 3.12% 3.14% 0.04% 1.61% ASR 0.00% 0.00% 0.88% 0.00% 0.22% BRANCH 1.61% 1.57% 0.72% 2.26% 1.54% C! 0.07% 0.36% 0.03% 0.87% 0.33% C@ 0.00% 7.52% 0.01% 0.36% 1.97% CALL 11.16% 12.73% 12.59% 12.36% 12.21% CONSTANT 3.92% 3.50% 2.78% 4.50% 3.68% CONVERT 0.00% 0.00% 0.00% 0.04% 0.01% D! 0.21% 0.00% 0.59% 0.00% 0.20% D+ 1.15% 0.00% 0.54% 0.00% 0.42% D+- 0.07% 0.00% 0.03% 0.02% 0.03% D< 0.00% 0.00% 0.00% 0.00% 0.00% D@ 0.21% 0.00% 0.62% 0.00% 0.21% DDROP 2.08% 0.52% 0.11% 0.35% 0.77% DDUP 1.86% 0.00% 1.16% 0.84% 0.97% DIGIT 0.00% 0.00% 0.00% 0.00% 0.00% DNEGATE 0.00% 0.00% 0.11% 0.00% 0.03% DOVER 0.00% 0.00% 0.91% 0.00% 0.23% DROP 3.08% 0.16% 0.68% 1.04% 1.24% DROT 0.00% 0.00% 0.17% 0.00% 0.04% DSWAP 0.00% 0.00% 0.92% 0.00% 0.23% DUP 4.08% 0.45% 1.88% 5.78% 3.05% ENCLOSE 0.00% 0.00% 0.00% 0.58% 0.15% EXECUTE 0.14% 0.00% 0.02% 2.45% 0.65% EXIT 11.07% 12.72% 12.55% 10.60% 11.74% I 0.58% 6.66% 0.01% 0.23% 1.87% I' 0.00% 0.00% 0.00% 0.00% 0.00% J 0.16% 0.08% 0.00% 0.00% 0.06% LEAVE 0.00% 0.00% 0.00% 0.00% 0.00% LIT 3.94% 5.22% 4.92% 4.09% 4.54% LSL 0.00% 0.00% 0.04% 0.00% 0.01% LSR 0.00% 0.00% 0.96% 0.00% 0.24% MAX 0.00% 0.00% 0.00% 0.01% 0.00% MIN 0.00% 0.00% 0.05% 0.00% 0.01% NEGATE 0.52% 0.00% 0.00% 0.00% 0.13% NOT 0.00% 0.00% 0.69% 0.25% 0.24% OR 0.00% 0.08% 1.41% 0.64% 0.53% OVER 1.23% 1.75% 1.24% 0.89% 1.28% PICK 1.92% 0.00% 0.53% 0.09% 0.64% R> 2.05% 0.00% 11.28% 2.23% 3.89% R@ 0.14% 0.00% 0.02% 0.71% 0.22% RLC 0.00% 0.00% 0.01% 0.00% 0.00% ROLL 0.21% 0.00% 0.81% 0.00% 0.26% ROT 4.05% 0.00% 4.61% 0.48% 2.29% RP! 0.00% 0.00% 0.00% 0.00% 0.00% RP@ 0.00% 0.00% 0.00% 0.00% 0.00% RRC 0.00% 0.00% 0.00% 0.00% 0.00% S->D 0.07% 0.00% 0.00% 0.01% 0.02% SP@ 0.00% 0.00% 0.00% 0.05% 0.01% SWAP 4.43% 2.99% 7.00% 1.17% 3.90% TOGGLE 0.00% 0.06% 0.00% 0.08% 0.04% TRAVERSE 0.00% 0.00% 0.00% 0.05% 0.01% U* 0.62% 0.00% 0.34% 0.01% 0.24% U/MOD 0.60% 0.00% 0.01% 0.05% 0.17% U< 0.00% 0.00% 0.00% 0.00% 0.00% USER 0.07% 0.00% 0.06% 8.59% 2.18% VARIABLE 7.63% 10.30% 2.26% 1.65% 5.46% XOR 0.29% 0.00% 0.24% 0.01% 0.14% Instructions: 2051600 1296143 6133519 447050

下面是在第六章讨论的没有删节的静态指令频率

NAMES FRAC LIFE MATH BENCH AVE ! 3.28% 2.12% 0.90% 2.99% 2.32% * 0.00% 0.21% 0.00% 0.43% 0.16% + 3.28% 2.97% 0.76% 4.61% 2.90% +! 0.00% 0.00% 0.18% 0.17% 0.09% +- 0.14% 0.00% 0.00% 0.09% 0.06% - 2.05% 1.91% 0.58% 1.54% 1.52% / 0.14% 0.00% 0.00% 0.09% 0.06% 0< 0.96% 0.00% 0.65% 0.68% 0.57% 0= 0.00% 0.00% 0.11% 0.26% 0.10% 0> 0.00% 0.00% 0.47% 0.00% 0.12% 0BRANCH 3.01% 2.55% 3.67% 3.16% 3.10% 1+ 0.41% 0.64% 0.72% 0.51% 0.57% 1- 1.09% 0.42% 0.54% 1.28% 0.83% 2* 1.92% 2.12% 0.14% 1.79% 1.49% 2+ 0.27% 0.00% 0.11% 0.34% 0.18% 2- 0.27% 0.00% 0.00% 0.34% 0.15% 2/ 0.96% 0.00% 0.00% 0.77% 0.43% < 0.14% 0.42% 0.47% 0.34% 0.34% 0.27% 0.21% 0.04% 0.26% 0.20% 0.27% 0.00% 0.00% 0.17% 0.11% 1.23% 0.21% 0.32% 1.11% 0.72% >R 0.55% 0.00% 4.11% 0.77% 1.36% ?DUP 0.00% 0.00% 0.04% 0.00% 0.01% ?STACK 0.00% 0.00% 0.07% 0.09% 0.04% @ 10.81% 1.27% 1.40% 8.88% 5.59% ABS 0.27% 0.00% 0.18% 0.17% 0.16% ADC 0.00% 0.00% 0.07% 0.00% 0.02% AND 0.27% 1.06% 0.54% 0.43% 0.58% ASR 0.00% 0.00% 0.11% 0.00% 0.03% BRANCH 1.92% 0.85% 2.09% 2.05% 1.73% C! 0.00% 1.49% 0.04% 0.68% 0.55% C@ 0.00% 3.40% 0.61% 0.34% 1.09% CALL 16.82% 31.44% 37.61% 17.62% 25.87% CONSTANT 1.23% 1.91% 0.07% 1.62% 1.21% CONVERT 0.00% 0.00% 0.00% 0.00% 0.00% D! 0.41% 0.00% 0.18% 0.17% 0.19% D+ 0.55% 0.21% 0.25% 0.51% 0.38% D+- 0.00% 0.00% 0.14% 0.00% 0.04% D< 0.00% 0.00% 0.14% 0.00% 0.04% D@ 0.27% 0.00% 0.32% 0.17% 0.19% DDROP 2.60% 0.42% 0.79% 1.88% 1.42% DDUP 1.23% 0.21% 0.61% 1.71% 0.94% DIGIT 0.00% 0.00% 0.11% 0.00% 0.03% DNEGATE 0.00% 0.00% 0.18% 0.00% 0.05% DOVER 0.00% 0.00% 0.32% 0.00% 0.08% DROP 2.60% 0.85% 1.69% 2.31% 1.86% DROT 0.00% 0.00% 0.29% 0.00% 0.07% DSWAP 0.00% 0.00% 1.22% 0.00% 0.31% DUP 4.38% 1.70% 2.84% 4.18% 3.28% ENCLOSE 0.00% 0.00% 0.00% 0.00% 0.00% EXECUTE 0.00% 0.00% 0.07% 0.00% 0.02% EXIT 5.75% 7.22% 9.90% 7.00% 7.47% I 1.37% 5.10% 0.11% 1.62% 2.05% I' 0.00% 0.00% 0.07% 0.00% 0.02% J 0.27% 1.91% 0.07% 0.26% 0.63% LEAVE 0.00% 0.00% 0.00% 0.09% 0.02% LIT 11.35% 7.22% 11.02% 8.03% 9.41% LSL 0.00% 0.00% 0.04% 0.00% 0.01% LSR 0.00% 0.00% 0.07% 0.00% 0.02% MAX 0.00% 0.00% 0.11% 0.09% 0.05% MIN 0.00% 0.00% 0.04% 0.17% 0.05% NEGATE 0.14% 0.00% 0.04% 0.26% 0.11% NOT 0.00% 0.00% 0.47% 0.26% 0.18% OR 0.00% 0.21% 0.61% 0.00% 0.21% OVER 2.05% 5.10% 0.76% 2.05% 2.49% PICK 6.29% 0.00% 1.04% 4.53% 2.97% R> 0.55% 0.00% 4.68% 0.77% 1.50% R@ 0.00% 0.00% 0.29% 0.17% 0.12% RLC 0.00% 0.00% 0.07% 0.00% 0.02% ROLL 0.14% 0.00% 0.32% 0.09% 0.14% ROT 1.50% 0.00% 0.58% 1.37% 0.86% RP! 0.00% 0.00% 0.00% 0.00% 0.00% RP@ 0.00% 0.00% 0.00% 0.00% 0.00% RRC 0.00% 0.00% 0.07% 0.00% 0.02% S->D 0.00% 0.00% 0.25% 0.00% 0.06% SP@ 0.00% 0.00% 0.00% 0.00% 0.00% SWAP 1.78% 5.10% 1.19% 3.16% 2.81% TOGGLE 0.00% 0.42% 0.00% 0.00% 0.11% TRAVERSE 0.00% 0.00% 0.00% 0.00% 0.00% U* 0.41% 0.00% 0.14% 0.26% 0.20% U/MOD 0.14% 0.00% 0.00% 0.09% 0.06% U< 0.00% 0.00% 0.04% 0.00% 0.01% USER 0.00% 0.00% 0.00% 0.00% 0.00% VARIABLE 1.09% 1.91% 0.29% 1.37% 1.17% XOR 0.14% 0.00% 0.50% 0.09% 0.18% Instructions: 731 471 2777 1171 参考文献

Allmark, R. & Lucking, J. (1962) Design of an arithmetic unit incorporating a nesting store. In: Information Processing 1962: Proc. of the IFIP Cong. 62, 27 August - 1 September 1962, Munich, North-Holland, Amsterdam, 1963, pp. 694-698

Anderson, J. (1961) A computer for direct execution of algorithmic languages. In: Proc. of the EJCC, 12-14 December 1961, Washington DC, Vol. 20, Macmillan, New York, 1961, pp. 184-193

Atkinson, R. & McCreight, E. (1987) The Dragon processor. In: Proc. of the Second Int. Conf. on Architectural Support for Programming Languages and Operating Systems (ASPLOS II), Palo Alto CA, 5-8 October 1987, pp. 65-69

Backus, J. (1978) Can programming be liberated from the von Neumann style? A functional style and its algebra of programs. Comm. of the ACM, August 1978, 21(8) 613-641

Bage, G., & Thorelli, L. (1980) Partial evaluation of a high-level architecture. In: Proc. of the Int. Workshop on High-Level Language Computer Architecture, Fort Lauderdale FL, 26-28 May 1980, pp. 44-51

Ballard, B. (1984) Forth direct execution processors in the Hopkins Ultraviolet Telescope. J. Forth Application and Research 2(1) 33-47

Bartlett, J. (1973) The HP 3000 computer system. In: ACM-IEEE Symp. on High-Level-Language Computer Architecture, College Park MD, 7-8 November 1973, pp. 61-69

Belinfante, J. (1987) S/K/I: Combinators in Forth. J. Forth Application and Research, 4(4) 555-580

Bell, G., Cady, R., McFarland, H., DeLaig, B., O'Laughlin, J., Noonan, R., & Wulf, W. (1970) A new architecture for mini-computers: The DEC PDP-11, AFIPS Proc. SJCC, 1970, pp. 657-675. Reprinted In: Siewiorek, D., Bell, C. G., & Newell, A. (1982) Computer Structures: Principles and Examples, McGraw-Hill, 1982, pp. 649-661

Bell, J. (1973) Threaded code. Comm. of the ACM, June 1973, 16(6) 370-372

Bergh, A. & Mei, K. (1979) HP300 architecture. In: Proc. of the nineteenth IEEE computer society Int. Conf. (Fall COMPCON 79), Washington DC, 4-7 September 1979, pp. 62-66

Best, D., Kress, C., Mykris, N., Russell, J. & Smith, W. (1982) MOS/SOS Microprocessor. IEEE Micro, August 1982, 2(3) pp. 10-26

Blake, R. (1977) Exploring a stack architecture. Computer, May 1977, 10(5) 18-28

Bruno, J. & Lassagne, T. (1975) The generation of optimal code for stack machines. J. of the ACM, July 1975, 22(3) pp. 382-396

Bulman, D. (1977) Stack computers: An introduction. Computer, May 1977, 10(5) 18-28

Burnley, P. & Harkaway, R. (1987) A high performance VME processor card when 32-bit super-micros can't cut it. In: Proc. of the 1987 Rochester Forth Conf., (J. Forth Application and Research 5(1)) 101-107

Burns, R. & Savitt, D. (1973) Micro-programming, stack architecture ease minicomputer programmer's burden. Electronics, 15 February 1973, 46(4) 95-101

Bush, W., Samples, A., Ungar, D. & Hilfinger, P. (1987) Compiling Smalltalk-80 to a RISC. In: Proc. of the Second Int. Conf. on Architectural Support for Programming Languages and Operating Systems (ASPLOS II), Palo Alto CA, 5-8 October 1987, pp. 112-116

Carlson, C. (1963) The mechanization of a push-down stack. In: AFIPS Conf. Proc., 1963 FJCC, Vol. 24, Spartan Books, Baltimore, pp. 243-250

Carlson, C. (1975) A survey of high-level language computer architecture. In: Chu, Y. (Ed.) High-Level Language Computer Architecture, Academic Press, New York, 1975 pp. 31-62

Carr, H. & Kessler, R. (1987) An emulator for Utah Common Lisp's abstract virtual machine. In: Proc. of the 1987 Rochester Forth Conf., (J. Forth Application and Research 5(1)) 113-116

Castan, M. & Organick, E. (1982) Micro-3L: An HLL-RISC processor for execution of FP-language programs. In: Conf. Proc.: The 9th Annual Symp. on Computer Architecture, 26-29 April 1982, Austin TX, pp. 239-247

Chen, Y., Chen, K. & Huang, K. (1980) Direct-execution high-level language FORTRAN computer. In: Proc. of the Int. Workshop on High-Level Language Computer Architecture, Fort Lauderdale FL, 26-28 May 1980, pp. 9-16

Cook, R. & Donde, N. (1982) An experiment to improve operand addressing. In: Proc. of the Symp. on Architectural Support for Programming Languages and Operating Systems (ASPLOS I), Palo Alto CA, 1-3 March 1982, pp. 87-91

Cook, R. & Lee, I. (1980) An extensible stack-oriented architecture for a high-level language machine. In: Proc. of the Int. Workshop on High-Level Language Computer Architecture, Fort Lauderdale FL, 26-28 May 1980, pp. 231-237

Couch, J. & Hamm, T. (1977) Semantic structures for efficient code generation on a stack machine. Computer, May 1977, 10(5) 42-48

Cragon, H. (1979) An evaluation of code space requirements and performance of various architectures. Computer Architecture News, February 1979, 7(5) 5-21

Cragon, H. (1980) A case against high-level language computer architecture. In: Proc. of the Int. Workshop on High-Level Language Computer Architecture, Fort Lauderdale FL, 26-28 May 1980, pp. 88-91

Danile, P. & Malinowski, C. (1987) Forth processor core for integrated 16-bit systems. VLSI Systems Design, June 1987, 8(7) 98-104

Davidson, J. & Vaughan, R. (1987) The effect of instruction set complexity on program size and memory performance. In: Proc. of the Second Int. Conf. on Architectural Support for Programming Languages and Operating Systems (ASPLOS II), Palo Alto CA, 5-8 October 1987, pp. 60-64

Dewar, R. (1975) Indirect threaded code. Comm. of the ACM, June 1975, 18(6) 330-331

Ditzel, D. & Kwinn, W. (1980) Reflections on a high level language computer system, or, Parting thoughts on the SYMBOL project. In: Proc. of the Int. Workshop on High-Level Language Computer Architecture, Fort Lauderdale FL, 26-28 May 1980, pp. 80-87

Ditzel, D. & McLellan, H. (1982) Register allocation for free: The C machine stack cache. In: Proc. of the Symp. on Architectural Support for Programming Languages and Operating Systems (ASPLOS I), Palo Alto CA, 1-3 March 1982, pp. 48-56

Ditzel, D. & McLellan, H. (1987) Branch folding in the CRISP microprocessor: reducing branch delay to zero. In: The 14th Annual Int. Symp. on Computer Architecture; Conf. Proc., 2-5 June 1987, Pittsburgh, pp. 2-9

Ditzel, D., McLellan, H., & Berenbaum, A. (1987a) The hardware architecture of the CRISP microprocessor. In: The 14th Annual Int. Symp. on Computer Architecture; Conf. Proc., 2-5 June 1987, Pittsburgh, pp. 309-319

Ditzel, D., McLellan, H. & Berenbaum, A. (1987b) Design tradeoffs to support the C programming language in the CRISP microprocessor. In: Proc. of the Second Int. Conf. on Architectural Support for Programming Languages and Operating Systems (ASPLOS II), Palo Alto CA, 5-8 October 1987, pp. 158-163

Ditzel, D. & Patterson, D. (1980) Retrospective on high-level language computer architecture. In: Proc. of the 7th Int. Conf. on Computer Architecture, 1980, pp. 97-104, Reprinted In: Fernandez, E. & Lang, T. (Eds.) Tutorial: Software-Oriented Computer Architecture, IEEE Computer Society Press, Washington DC, 1986, pp. 44-51.

Dixon, R. (1987) A stack-frame architecture language processor. In: Proc. of the 1987 Rochester Forth Conf., (J. Forth Application and Research 5(1)) 11-25

Dress, W. (1986) REAL-OPS: A real-time engineering applications language for writing expert systems. In: Proc. of the 1986 Rochester Forth Conf., (J. Forth Application and Research 4(2)) 113-124

Dress, W. (1987) High-performance neural networks. In: Proc. of the 1987 Rochester Forth Conf., (J. Forth Application and Research 5(1)) 137-140

Doran, R. (1972) A computer organization with an explicitly tree-structured machine language. The Australian Computer Journal, February 1972, 4(1) 21-30

Doran, R. (1975) Architecture of stack machines. In: Chu, Y. (Ed.) High-Level Language Computer Architecture, Academic Press, New York, 1975, pp. 63-108

Doran, R. (1979) Computer Architecture: A Structured Approach (APIC Studies in Data Processing No. 15), Academic Press, London.

Dumse, R. (1984) The R65F11 and F68K single-chip Forth computers. J. Forth Application and Research 2(1) 11-21

Duncan, F. (1977) Stack machine development: Australia, Great Britain, and Europe. Computer, May 1977, 10(5) 50-52

Earnest, E. (1980) Twenty years of Burroughs high-level language machines. In: The Proc. of the Int. Workshop on High-Level Language Computer Architecture, 26-28 May 1980, Fort Lauderdale FL, pp. 64-71

Efland, G. & Mosteller, R. (1979) Stack Data Engine; Description and Implementation, Technical Report #3364, Computer Science Department, California Institute of Technology, Pasadena CA, December 1979.

Eickemeyer, R. & Patel, J. (1985) A parallel stack processor (PSP). In: Proc.: IEEE Int. Conf. on Computer Design: VLSI in Computers (ICCD 85), 7-10 October 1985, Port Chester NY, pp. 473-476

Evey, R. (1963) Application of pushdown-store machines. In: AFIPS Conf. Proc., 1963 FJCC, Vol. 24, Spartan Books, Baltimore MD, 1963, pp. 215-227

Foster, C. (1975) Socrates. In: Conf. Proc.; The 2nd Annual Symp. on Computer Architecture, 20-22 January 1975, pp. 165-169

Fraeman, M., Hayes, J., Williams, R. & Zaremba, T. (1986) A 32 bit processor architecture for direct execution of Forth. In: 1986 FORML Conf. Proc., 28-30 November 1986, Pacific Grove CA, pp. 197-210

Golden, J., Moore, C. & Brodie, L. (1985) Fast processor chip takes its instructions directly from Forth. Electronic Design, 21 March 1985, 127-138

Grewe R. & Dixon, R. (1984) A Forth machine for the S-100 system. J. Forth Application and Research 2(1) 23-32

Haikala, I. (1982) More design data for stack architectures. In: Proc. of the ACM '82 Conf., Dallas, October 25-27 1982, pp. 30-36

Haley, A. (1962) The KDF.9 computer system. In: AFIPS Conf. Proc., Vol. 22: 1962 Fall Joint Computer Conf., Spartan Books, Washington DC, 1962, pp. 108-120

Hand, T. (1987) A Forth implementation of LISP. In: Proc. of the 1987 Rochester Forth Conf., (J. Forth Application and Research 5(1)) 141-144

Harbison, S. (1982) An architectural alternative to optimizing compilers. In: Proc. of the Symp. on Architectural Support for Programming Languages and Operating Systems (ASPLOS I), Palo Alto CA, 1-3 March 1982, pp. 57-65

Harris, N. (1980) A directly executable language suitable for a bit slice microprocessor implementation. In: Proc. of the Int. Workshop on High-Level Language Computer Architecture, Fort Lauderdale FL, 26-28 May 1980, pp. 40-43

Harris Semiconductor (1988a) RTX 2000 Instruction Set, Harris Corporation, Melbourne FL

Harris Semiconductor (1988b) RTX 2000 Real Time Express Microcontroller Data Sheet, Harris Corporation, Melbourne FL

Hasegawa, M. & Shigei, Y. (1985) High-speed top-of-stack scheme for VLSI processor: a management algorithm and its analysis. In: The 12th Annual Int. Symp. on Computer Architecture, 17-19 June 1985, Boston, pp. 48-54

Hassitt, A., Lageshulte, J. & Lyon, L. (1973) Implementation of a high level language machine. Comm. of the ACM, April 1973, 16(4) 199-212.

Haydon, G. (1983) All About Forth: An Annotated Glossary, 2nd Ed., Mountain View Press, Mountain View CA

Haydon, G. & Koopman, P. (1986) MVP microcoded CPU/16: History. In: Proc. of the 1986 Rochester Forth Conf., (J. Forth Application and Research 4(2)) pp. 273-276

Hayes, J. (1986) An interpreter and object code optimizer for a 32 bit Forth chip. In: 1986 FORML Conf. Proc., 28-30 November 1986, Pacific Grove CA 211-221

Hayes, J. & Fraeman, M. (1988) Private communications, October 1988.

Hayes, J., Fraeman, M., Williams, R. & Zaremba, T. (1987) An architecture for the direct execution of the Forth programming language. In: Proc. of the Second Int. Conf. on Architectural Support for Programming Languages and Operating Systems (ASPLOS II), Palo Alto CA, 5-8 October 1987, pp. 42-49

Hayes, J. & Lee, S. (1988) The Architecture of FRISC 3: a summary. In: Proc. of the 1988 Rochester Forth Conf., 14-18 June 1987 pp. 81-82.

Hennesy, J. (1984) VLSI Processor architecture. IEEE Trans. Computers, December 1984, C-33(12) 1221-1246. Reprinted In: Fernandez, E. & Lang, T. (Eds.) Tutorial: Software-Oriented Computer Architecture, IEEE Computer Society Press, Washington DC, 1986, pp. 90-115.

Herriot, R. (1973) GLOSS: A high level machine. ACM-IEEE Symp. on High-Level-Language Computer Architecture, 7-8 November 1973, College Park, MD, pp. 81-90.

Hutchison, P. & Ethington, K. (1973) Program execution in the Symbol 2R computer. ACM-IEEE Symp. on High-Level-Language Computer Architecture, 7-8 November 1973, College Park, MD, pp. 20-26.

Intel (1981) The iAPX 88 Book. Intel Corporation, 1981.

Jennings, E. (1985) The Novix NC4000 Project. Computer Language, October 1985, 2(10) 37-46

Johnson, M. (1987) System considerations in the design of the AM29000. IEEE Micro, August 1987, 7(4) 28-41

Johnsson, R. & Wick, J. (1982) An overview of the Mesa processor architecture. In: Proc. of the Symp. on Architectural Support for Programming Languages and Operating Systems (ASPLOS I), Palo Alto CA, 1-3 March 1982, pp. 20-29

Jonak, J. (1986) Experience with a Forth-like language. SIGPLAN Notices, February 1986, 21(2) 27-36

Jones, S. P. (1987) The Implementation of Functional Programming Languages, Prentice-Hall, New York

Jones, T., Malinowski, C. & Zepp, S. (1987) Standard-cell CPU toolkit crafts potent processors. Electronic Design, 14 May 1987, 35(12) 93-101

Kane, G, Hawkins, D. & Leventhal, L. (1981) 68000 Assembly Language Programming, Osborne/McGraw-Hill, Berkeley CA

Kaneda, Y., Wada, K. & Maekawa, S. (1983) High-speed execution of Forth and Pascal programs on a high-level language machine. In: Microcomputers: developments in industry, business and education, Ninth EUROMICRO Symp. on microprocessing and microprogramming, 13-16 September 1983, Madrid, North-Holland, Amsterdam, 1983, pp. 259-266.

Kavi, K., Belkhouche, B., Bullard, E., Delcamber, L. & Nemecek, S. (1982) HLL architectures: Pitfalls and predilections. In: Conf. Proc.: The 9th Annual Symp. on Computer Architecture, 26-29 April 1982, Austin TX, pp. 18-32

Kavipurapu, K. & Cragon, H. (1980) Quest for an 'ideal' machine language. In: Proc. of the Int. Workshop on High-Level Language Computer Architecture, Fort Lauderdale FL, 26-28 May 1980, pp. 33-39

Keedy, J. (1977) An outline of the ICL 2900 series system architecture. Australian Computer Journal, July 1977, 9(2) 53-62. Reprinted in: Siewiorek, D., Bell, C. G., & Newell, A., Computer Structures: Principles and Examples, McGraw-Hill, 1982, pp. 251-259

Keedy, J. (1978a) On the use of stacks in the evaluation of expressions. Computer Architecture News, February 1978, 6(6) 22-28

Keedy, J. (1978b) On the evaluation of expressions using accumulators, stacks, and store-to-store instructions. Computer Architecture News, December 1978, 7(4) 24-27

Keedy, J. (1979) More on the use of stacks in the evaluation of expressions. Computer Architecture News, 15 June 1979, 7(8) 18-21

Kieburtz, R. (1985) The G-machine: a fast, graph-reduction evaluator. In: Jouannaud, J. (Ed.) Functional Programming Languages and Computer Architecture, 16-19 September, Nancy, France, pp. 400-413 (Goos, G. & Hartmanis, J. Lecture Notes in Computer Science, No. 201)

Kluge, W. & Schlutter, H. (1980) An architecture for direct execution of reduction languages. In: Proc. of the Int. Workshop on High-Level Language Computer Architecture, Fort Lauderdale FL, 26-28 May 1980, pp. 174-180

Kogge, P. (1982) An architectural trail to threaded-code systems. Computer, March 1982, 15(3) 22-32

Koopman, P. (1985) Forth Floating Point: MVP-FORTH Series Vol. 3 (revised), Mountain View Press, Mountain View, CA

Koopman, P. (1986) CPU/16 Technical Reference Manual. WISC Technologies, Inc., La Honda CA.

Koopman, P. (1987a) Microcoded versus hard-wired control. Byte, January 1987, 12(1) 235-242

Koopman, P. (1987b) The WISC concept. Byte, April 1987, 12(4) 187-194

Koopman, P. (1987c) Writable instruction set, stack oriented computers: the WISC concept. In: Proc. of the 1987 Rochester Forth Conf., (J. Forth Application and Research 5(1)) 49-71

Koopman, P. (1987d) CPU/32 Technical Reference Manual. WISC Technologies, Inc., La Honda CA.

Koopman, P. (1987e) Bresenham line drawing. Forth Dimensions, March/April 1987, 8(6) 12-16. Reprinted In: Dr. Dobb's Toolbook of Forth, Vol. 2, M&T Books, Redwood City CA, 1987, pp. 347-356

Koopman, P. (1987f) Fractal Landscapes. Forth Dimensions, March/April 1987, 9(1) 12-16. Reprinted In: Dr. Dobb's Toolbook of Forth, Vol. 2, M&T Books, Redwood City CA, 1987, pp. 357-365

Koopman, P. (1989) Introduction of the RTX 32P. J. Forth Application and Research 5(2), forthcoming

Koopman, P., & Haydon, G. (1986) MVP microcoded CPU/16: Architecture. In: Proc. of the 1986 Rochester Forth Conf., (J. Forth Application and Research 4(2)) pp. 277-280

Koopman, P., & Lee, P. (1989) A fresh look at combinator graph reduction. In: 1989 Conf. on Programming Language Design and Implementation, June.

Lampson, B. (1982) Fast procedure calls. In: Proc. of the Symp. on Architectural Support for Programming Languages and Operating Systems (ASPLOS I), Palo Alto CA, 1-3 March 1982, pp. 66-76

Lim, R. (1987) LISP machine architecture issues. In: Digest of Papers, Thirty-second IEEE Computer Society Int. Conf. (Spring COMPCON 87), San Francisco, 23-27 February 1987, pp. 116-119

Lipovski, G. (1975) On a stack organization for microcomputers. In: Hartenstein, R. & Zaks, R. (Eds.) Workshop on the Micro-architecture of Computer Systems, 23-25 June 1975, Nice, North-Holland, Amsterdam, 1975, pp. 137-147

Longway, C. (1988) Instruction Sequencing and Decoding in the SF1, Master of Science thesis, Wright State University

Lor, K. & Chu, Y. (1981) Design of a PASCAL Interactive Direct-Execution Computer, Technical Report TR-1088. Department of Computer Science, University of Maryland, College Park MD, August 1981.

Lutz, M. (1973) The design and implementation of a small scale stack processor system. In: AFIPS Conf. Proc., Vol. 42: 1973 National Computer Conf. and Exposition, 4-8 June 1973, AFIPS Press, Montvale NJ, pp. 545-553

Matheus, C. (1986) The Internals of FORPS: a Forth-based production system. J. Forth Application and Research 4(1) 7-27

McDaniel, G. (1982) An analysis of a Mesa instruction set using dynamic instruction frequencies. In: Proc. of the Symp. on Architectural Support for Programming Languages and Operating Systems (ASPLOS I), Palo Alto CA, 1-3 March 1982, pp. 167-176

McFarling, S. & Hennesy, J. (1986) Reducing the cost of branches. In: The 13th Annual Int. Symp. on Computer Architecture; Conf. Proc., 2-5 June 1986, Tokyo, pp. 396-403

McKeeman, W. (1975) Stack computers. In: Stone, H. (Ed.) Introduction to Computer Architecture, Science Research Associates, Chicago, 1975, pp. 281-317

Miller, G. (1967) Psychology of Communication: Seven Essays, Basic Books, New York

Miller, J. & Vandever, W. (1973) Instruction architecture of an aerospace multiprocessor. In: ACM-IEEE Symp. on High-Level-Language Computer Architecture, 7-8 November 1973, College Park, MD, pp. 52-60.

Miller, D. (1987) Stack machines and compiler design. Byte, April 1987, 12(4) 177-185

MISC (1988) MISC M17 Technical Reference Manual, MISC Inc., 1988.

Moon, D. (1985) Architecture of the Symbolics 3600. In: The 12th Annual Int. Symp. on Computer Architecture, 17-19 June 1985, Boston, pp. 76-83

Moore, C. (1980) The evolution of Forth, an unusual language. Byte, August 1980, 5(8) 76-92

Morris, D. & Ibbett, R. (1979) The MU5 Computer System, Springer-Verlag, New York.

Myamlin, A. & Smirnov, V. (1969) Computer with stack memory. In: Morell, A. (Ed.) Information Processing 68: Proc. of IFIP Cong. 1968, 5-10 August 1968, Edinburgh,, Vol. 2, North-Holland, Amsterdam, 1969, pp. 818-823

Myers, G. (1977) The case against stack-oriented instruction sets. Computer Architecture News, August 1977, 6(3) 7-10

Myers, G. (1982) Advances in Computer Architecture, John Wiley & Sons, New York, 1982

Nissen, S. & Wallach, S. (1973) The all applications digital computer. In: ACM-IEEE Symp. on High-Level-Language Computer Architecture, 7-8 November 1973, College Park, MD, pp. 43-51.

Novix (1985) Programmers Introduction to the NC4016 Microprocessor, Novix Inc., Cupertino CA.

Odette, L. (1987) Compiling Prolog to Forth. J. Forth Application and Research, 4(4) 487-533

Ohdate, S., Yamashita, K. & Hishinuma, C. (1975) Push-down stack architecture to a minicomputer interface. In: Information Processing in Japan, Vol. 15, Information Processing Society of Japan, Tokyo, 1975

Ohran, R. (1984) Lilith and Modula-2. Byte, August 1984, 9(8) 181-192

O'Neill, E. (1979) Pascal microengine. In: Proc. of the nineteenth IEEE computer society Int. Conf. (Fall COMPCON 79), Washington DC, 4-7 September 1979, pp. 112-113

Organick, E. (1973) Computer System Organization: The B5700/B6700 Series, Academic Press, New York, 1973

Park, J. (1986) Toward the development of a real-time expert system. In: Proc. of the 1986 Rochester Forth Conf., (J. Forth Application and Research 4(2)) 133-154

Parnas, D. (1972) On the criteria to be used in decomposing systems into modules. Comm. of the ACM, December 1972, 15(12) 1053-1058

Patterson, D. (1985) Reduced instruction set computers. Comm. of the ACM, January 1985, 28(1) 8-21. Reprinted In: Fernandez, E. & Lang, T. (Eds.) Tutorial: Software-Oriented Computer Architecture, IEEE Computer Society Press, Washington DC, 1986, pp. 76-89

Patterson, D. & Piepho, S. (1982) RISC assessment: A high-level language experiment. In: Conf. Proc.: The 9th Annual Symp. on Computer Architecture, 26-29 April 1982, Austin TX, pp. 3-8

Patterson, D. & Sequin, C. (1982) A VLSI RISC. Proc. of the Eighth Int. Symp. on Computer Architecture, May 1981, pp. 443-457. Reprinted In: Milutinovic, V. (Ed.) Tutorial on Advanced Microprocessors and High-Level Language Computer Architecture, IEEE Computer Society, Washington DC, 1986, pp. 145-157

Pountain, D. (1988) Rekursiv: an object-oriented CPU. Byte, November 1988, 13(12) 341-349

Prabhala, B. & Sethi, R. (1977) A comparison of instruction sets for stack machines. In: Conf. Record of the Ninth Annual ACM Symp. on Theory of Computing, Boulder CO, 2-4 May 1977, pp. 132-142

Rabbat, G., Furht, B. & Kibler, R. (1988) Three-dimensional computer performance. Computer, July 1988, 21(7) 59-60

Ragan-Kelley, R. & Clark, R. (1983) Applying RISC theory to a large computer. Computer Design, November 1983. Reprinted In: Milutinovic, V. (Ed.) Tutorial on Advanced Microprocessors and High-Level Language Computer Architecture, IEEE Computer Society, Washington DC, 1986, pp. 297-301

Randell, B. & Russell, L. (1964) ALGOL 60 Implementation: The translation and use of ALGOL 60 programs on a computer (APIC Studies in Data Processing No. 5), Academic Press, London, 1964, pp. 22-33

Rust, T. (1981) ACTION processor FORTHRIGHT. In: Proc. of the 1981 Rochester Forth Standards Conf., Institute for Applied Forth Research, Rochester, NY, 1981, pp. 309-315.

Samelson, K. & Bauer, F. (1962) The ALCOR project. In: Symbolic languages in data processing: Proc. of the Symp. organized and edited by the Int. Computation Center, Rome, 26-31 March 1962, Gordon and Breach, New York, pp. 207-217

Sansonnet, J., Castan, M, Percebois, C., Botella, D. & Perez, J. (1982) Direct execution of LISP on a list-directed architecture. In: Proc. of the Symp. on Architectural Support for Programming Languages and Operating Systems (ASPLOS I), Palo Alto CA, 1-3 March 1982, pp. 132-139

Scheevel, M. (1986) NORMA: A graph reduction processor. In: Proc. of the 1986 ACM Conf. on LISP and Functional Programming, pp. 212-218

Schoellkopf, J. (1980) PASC-HLL: A high-level-language computer architecture for Pascal. In: Proc. of the Int. Workshop on High-Level Language Computer Architecture, Fort Lauderdale FL, 26-28 May 1980, pp. 222-225

Schulthess, P. (1984) A reduced high-level-language instruction set. IEEE Micro, June 1984, 4(3) 55-67

Schulthess, P. & Mumprecht, E. (1977) Reply to the case against stack-oriented instruction sets. Computer Architecture News, December 1977, 6(5) 24-26

Sequin, C. & Patterson, D. (1982) Design and implementation of RISC I. In: Randell, B. & Treleaven, P. (Eds.) VLSI Architecture: Advanced Course on VLSI Architecture, 19-30 July 1982, Bristol England, Prentice-Hall, 1983, pp. 276-298

Shaw, J., Newell, A., Simon, H., & Ellis, T. (1959) A command structure for complex information processing. In: Proc. of the Western Joint Computer Conf., 6-8 May 1958, Los Angeles CA, American Institute of Electrical Engineers, 1959, pp. 119-128

Siewiorek, D., Bell, C. G., & Newell, A. (1982) Computer Structures: Principles and Examples, McGraw-Hill, 1982

Sites, R. (1978) A combined register-stack architecture. Computer Architecture News, April 1978, 6(8) 19

Sites, R. (1979) How to use 1000 registers. In: Seitz, C. (Ed.) Proc. of the Caltech Conf. on Very Large Scale Integration, 22-24 January 1979, pp. 527-532

Stanley, T. & Wedig, R. (1987) A performance analysis of automatically managed top of stack buffers. In: The 14th Annual Int. Symp. on Computer Architecture; Conf. Proc., 2-5 June 1987, Pittsburgh, pp. 272-281

Stephens, C. & Watson, W. (1985) Preliminary Report on the Novix 4000, Computer Solutions Ltd., Chertsey, Surrey England.

Sweet, R. & Sandman, J. (1982) Empirical Analysis of the Mesa instruction set. In: Proc. of the Symp. on Architectural Support for Programming Languages and Operating Systems (ASPLOS I), Palo Alto CA, 1-3 March 1982, pp. 158-166

Tamir, Y. & Sequin, C. (1983) Strategies for managing the register file in RISC. IEEE Trans. Computers, November 1983, C-32(11) 977-989. Reprinted In: Milutinovic, V. (Ed.) Tutorial on Advanced Microprocessors and High-Level Language Computer Architecture, IEEE Computer Society, Washington DC, 1986, pp. 167-179

Tanabe, K. & Yamamoto, M. (1980) Single chip Pascal processor: ITS architecture and performance evaluation. In: Proc. of the twenty-first IEEE computer society Int. Conf. (Fall COMPCON 80), Washington DC, 23-25 September 1980, pp. 395-399

Tanenbaum, A. (1978) Implications of structured programming for machine architecture. Comm. of the ACM, March 1978, 21(3) 237-246

Tsukamoto, M. (1977) Program stacking technique. In: Information Processing in Japan, Vol. 17, Information Processing Society of Japan, Tokyo, 1977, pp. 114-120

Vaughan, J. & Smith, R. (1984) The design of a Forth computer. J. Forth Application and Research 2(1) 49-64

Vickery, C. (1984) QFORTH: A multitasking Forth language processor. J. Forth Application and Research 2(1) 65-75

Wada, K., Kaneda, Y., & Maekawa, S. (1982a) Software and system evaluation of a Forth machine system. Systems, Computers, Controls, 1982, 13(2) 19-28

Wada, K., Kaneda, Y., & Maekawa, S. (1982b) System design and hardware structure of a Forth machine system. Systems, Computers, Controls, 1982, 13(2) 11-18

Weber, H. (1967) A microprogrammed implementation of EULER on IBM System/360 Model 30. Comm. of the ACM, September 1967, 10(9) 549-558

Welin, A. (1973) The internal machine. In: ACM-IEEE Symp. on High-Level-Language Computer Architecture, 7-8 November 1973, College Park, MD, pp. 101-108.

Williams, R., Fraeman, M., Hayes, J. & Zaremba, T. (1986) The development of a VLSI Forth microprocessor. In: 1986 FORML Conf. Proc., 28-30 November 1986, Pacific Grove CA 189-196

Whitby-Strevens, C. (1985) The Transputer. In: The 12th Annual Int. Symp. on Computer Architecture, 17-19 June 1985, Boston pp. 292-300

Wilkes, M. (1982) Keynote Address: the processor instruction set. 15th Workshop on Microprogramming, pp. 3-5

Winkel, D. (1981) The Forth engine. Forth Dimensions, September/October 1981, 3(3) 78-79

Wirth, N. (1968) Stack vs. Multiregister computers. SIGPLAN Notices, March 1968, 3(3) 13-19

Wirth, N. (1979) A personal computer based on a high-level language. In: Tobias, J. (Ed.), Language Design and Programming Methodology. Proc. of a Symp. Held in Sydney, Australia, 10-11 September 1979, pp. 191-193. Reprinted in: Goos, G. & Hartmanis, J. (Ed.) (1980) Lecture notes in computer science. Springer-Verlag, Berlin

Wirth, N. (1987) Hardware architectures for programming languages and programming languages for hardware architectures. In: Proc. of the Second Int. Conf. on Architectural Support for Programming Languages and Operating Systems (ASPLOS II), Palo Alto CA, 5-8 October 1987, pp. 2-7

Yamamoto, M. (1981) A survey of high-level language machines in Japan. Computer, July 1981, 14(7) 68-78



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3