为什么8008后的CPU没有保留片上堆栈的想法?

2020-06-08 03:08:35

8008的七个寄存器在右上角。右下角是地址堆栈,由8个14位地址字组成。与大多数处理器不同,8008的调用堆栈存储在芯片上,而不是存储在内存中。程序计数器只是这些地址之一,使子例程调用和返回非常简单。8008使用动态内存进行此存储。

这是一个有趣的想法,这可能会更快地进行调用和返回,代价是只能嵌套在八层深的地方。(如果要保存程序计数器以外的寄存器,则更少。)。

这个想法在8008之后就被抛弃了;后来的主流CPU(我指的是基本上排除了Forth芯片,以及反正只使用片上存储器的微型嵌入式芯片)都没有与片上堆栈一起使用。

这纯粹是为了决定支持更复杂的程序比快速的子例程调用更重要吗?或者,片上堆栈占用的芯片面积是否大于将程序计数器保存/加载到RAM的逻辑?或者两者兼而有之,或者其他原因?

如果您想保存程序计数器以外的寄存器,则";更少。";调用堆栈始终有全部8个级别可用,因为8008没有推送/弹出指令:堆栈上唯一可以保存的是用于中断的程序计数器(通过CALL指令或RST)。(这严重限制了您在中断时可以安全地做的事情,因为旗帜没有保存;我将在下面的回答中详细介绍这一点。)-cjs。

它不会是早期MCU上唯一被悄悄放弃的功能。在COSMAC1802之后,大多数CPU都没有16个程序计数器.-布莱恩·德拉蒙德(Brian Drummond)。

直到2000年代中期,MIPS 32/64才将返回地址保存在片上r个寄存器中的一个寄存器中(按照惯例)。可以使用任何寄存器,但是返回地址寄存器是分支指令IIRC的一部分。--彼得·史密斯(Peter Smith)。

@PeterSmith如果我正确读取您的注释,则ARM也是如此-BL(带链接的分支)指令将返回地址保存在LR寄存器中,该寄存器只是通用寄存器之一。不过,对于嵌套呼叫,你必须把它保存在某个地方.--TUTOM_

简而言之,为了更好地支持中断,因为在8008上中断是显而易见的(或者至少在可用性上非常有限)。

对于为什么要将堆栈移到芯片外的问题,直接的答案是:他们需要更大的堆栈,而在8080上专门为报警堆栈提供大量的芯片空间基本上是不可能的。但下面的问题是,为什么他们认为需要更大的烟囱?

通常,8008上的子例程存在可重入性问题:为了使用例程中的任何寄存器,您需要保存当前值,以便它们可以在返回时恢复。但更糟糕的是,如果中断例程使用任何更改任何标志的指令,特别是中断例程可能会使系统变得不可靠,因为这些指令可能会更改设置的标志,并且即将(但尚未)在中断的代码中进行测试。

实际上,这些值只能保存在固定位置,这意味着您只能有一个级别的中断,并且不能从中断调用非平凡的子例程。(也不是直接或间接的,除非注意被调用破坏的寄存器和标志,但这至少对程序员来说是一个更能解决的问题。)。

在8080中,通过扩展堆栈操作以不仅能够存储程序计数器值(用于从中断和子例程返回)而且能够存储寄存器对值(包括标志),解决了这个问题。这提供了一种方便而高效的方式来保存寄存器值并在以后恢复它们,并使编写重入例程变得更加方便。

英特尔8080汇编语言编程手册提供了一些明确的证据,表明这正是设计者的想法:

堆栈指针,使内存的各个部分能够用作堆栈的寄存器。这些反过来促进后述的子例程的执行和中断的处理。(第1页)。

在执行返回操作之前,任何中断子例程都应至少保存条件位并恢复它们。(要做到这一点,最明显也是最方便的方法是使用PUSH和POP操作将数据保存在堆栈中。)";(第60页)

没有特别的迹象表明八个级别的嵌套子例程/中断是一个主要问题。使用大量堆栈空间的现代开发人员通常认为小堆栈是一个可能的问题,但是为这种性质的8位系统编写代码的经验丰富的开发人员很清楚,您使用的堆栈比现代开发人员认为的要少得多。

也就是说,不管8级子例程嵌套是否有问题,只要你开始使用堆栈空间来临时存储寄存器,你就会需要比8个字多得多的堆栈空间。单独存储A寄存器和标志对会显著降低嵌套容量,中断例程可能还需要使用至少一个其他寄存器对,这并不是不合理的。

如果您将8080视为8008架构的一个相对类似的版本,那么将堆栈设置为32到64字(64到128字节)可能是合理的。但是一旦你决定把它移出芯片,增加它的大小就不那么便宜了,而且在某些方面,拥有一个完整的8位或16位堆栈指针要比使用一个奇数的大小要容易得多。在某些方面,拥有一个完整的8位或16位堆栈指针要比使用一个奇数的大小更容易。

我没有发现堆栈指针被填满16的特别证据,因为他们觉得堆栈需要这么大。很明显,至少一些有经验的微处理器开发人员(MOS 6502团队)认为8位堆栈指针(256字节堆栈)就足够了。有可能8080位设计师不同意,也有可能他们觉得他们不能像6502位设计师那样强迫某一特定区域成为随机存取存储器(RAM)。(甚至比MC6800更多的是,6502的设计强烈鼓励页面$00成为RAM,因此迫使页面$01成为RAM并不困难。)。或者,也许他们只是没有意识到,指向内存的寄存器可能会少于16位。

当时的一些小型机系统,特别是使用BCPL和C语言的PDP-11,有堆栈框架的概念,其中函数的参数空间和本地存储都是在堆栈上分配的。

这显然不是8080名设计师的意图。虽然它们使得加载堆栈指针变得很容易(通过SPHL),但没有简单的方法来检索它,更不用说PDP-11提供的与SP相关的索引指令了。(其他早期的8位处理器也是如此;第一个提供堆栈相关寻址模式的主要处理器可能是MC6809。)。此外,手册还明确表示,他们希望在寄存器中传递参数,当寄存器中无法容纳更多数据时,使用HL作为指向更多数据的指针:

有时让子例程加载自己的寄存器会更方便、更经济。要做到这一点,一种方法是将所需数据的列表(称为参数列表)放入内存的某个数据区,并将该列表的地址传递给H和L寄存器中的子例程。(";将数据传输到子例程,";第51页)。

事后诸葛亮的论点很少奏效。重新进入不是一个标准,特别是对于只有一个中断级别的机器。同样,在固定位置存钱在这种情况下效果也很好。递归也是一种罕见的特例,基本CPU没有什么需要担心的。-拉夫扎恩(Raffzahn)

@Raffzahn首先,请不要陷入重入是部分递归的常见混乱。虽然递归确实需要可重入性,但具有严格限制的调用图的非Recusrive例程可能也需要可重入性。其次,您似乎没有理解我的观点,即使中断和非中断代码都不需要单独重入,如果它们共享任何子例程,那么同时使用它们可能会引入重入要求。-两个cjs。

8008继承了4004的CPU堆栈;正如您所提到的,它的后继者8080用堆栈指针和内存中堆栈取代了它。

我怀疑这是8008之后立即出现的主要问题。如果您希望将返回地址以外的任何内容推送到堆栈,最终将需要更多的堆栈空间,并且所需的空间会迅速增加。当时,晶体管预算非常紧张,我怀疑“添加更多功能”胜过“在某些情况下提供快速的子例程调用”。(诚然,大多数8位系统的堆栈都很小,但与晶体管预算相比,它们仍然很大;一旦您了解到8088/8086,它明确侧重于在仍然很小的晶体管预算中支持高级语言,就非常需要内存堆栈。)。

还值得记住的是,当时的内存访问成本非常不同,像6502这样的8位CPU速度足够慢,内存访问问题不大。将堆栈移动到内存会立即大幅增加您的晶体管预算,以及对调用深度和堆栈大小的任何限制。还可以使用其他实现技巧来加快典型用例的执行速度:8080的RST指令(8088中通用软件中断的前兆)、6502的页面零位.。

后来,在SPARC设计中,确实出现了这种想法的变体:SPARC CPU具有寄存器窗口,其设计目的是提供类似于CPU堆栈的东西(有关详细信息,请参阅SPARC寄存器窗口的概述)。从理论上看,在查看单个程序行为时,这是一个很好的想法,但实际上并非如此,因为在SPARC大小的系统上,程序不能单独运行,并且多任务处理会扼杀寄存器窗口的好处。

在SPARC上,寄存器堆栈是可选功能。你可以干脆不用它。GCC可以选择在不使用寄存器堆栈的情况下生成SPARC代码。此外,оn现代CPU,还有一个快速硬件返回堆栈作为预测返回地址的独立单元:)-lvd。

我知道,但这并没有改变它们被实施的事实,事实证明它们并不是很有用。-斯蒂芬·凯特(Stephen Kitt)。

您如何确定它们是否无用?至少,它们减少了代码大小(在大多数情况下),因为每个过程的前言和结尾都变小了。上下文切换是一个争论,但像AVX512这样的东西有更多的数据要进行上下文切换,而且没有人抱怨:)-*LVD

现在没有人抱怨AVX512在上下文切换方面的问题,但在SPARC时代,内存带宽的成本会有所不同。是的,上下文切换中增加的成本是主要缺点。-斯蒂芬·凯特(Stephen Kitt)。

好的,那么它可能是一个瓶颈,现在它不再是瓶颈了。-欧洲LVD。

首先,显然片上堆栈占用了很大的芯片面积。粗略地说,内存堆栈需要多达单个额外的16位寄存器和稍大一点的PLA来执行POP和PUSH(基于已经存在的微操作,例如读取/写入内存中的16位值以及递增/递减16位寄存器)。另一方面,即使是8级深度的硬件返回堆栈也相当于8个16位寄存器的大小。

显然,能够运行复杂的程序(回想起来)是8080及其继任者Z80的巨大胜利。CP/M及其所有应用程序使用8级硬件返回堆栈是不可能的。一个真正明智且面向未来的决定。

直接连线到PC的小型片上堆栈可能需要比自动将PC堆叠到存储器所需的硬件更少的管芯面积,特别是在其程序计数器比存储器总线宽的机器上,或者在每个周期的时钟数目充分超过堆栈电平数目的动态逻辑NMOS机器上。如果具有4级堆栈的机器为正常指令花费8个周期,或为调用花费2个周期,则它可以为堆栈使用四深两相时钟移位寄存器。驾驶阶段1每隔一个周期,驾驶阶段2在所有剩余的周期为.-美国超级猫。

.大多数指令,在调用";的8个剩余周期中的5个周期上,以及在返回";的4个剩余周期中的3个周期上。这样的方法只适用于不需要支持随机访问的小堆栈,但是在8080发布后的几十年里,一些小堆栈就足够的体系结构一直在使用硬件堆栈。-美国超级猫。

处理器提供的是用于返回地址的堆栈,而不是用于过程激活记录(堆栈帧)的堆栈。

虽然这是一种可行的安排,但大多数编程语言确实使用堆栈来存储激活记录,特别是对于支持递归激活的语言。您可以从堆中分配,但这样会更慢。一旦内存中有了这样的堆栈,处理器内返回地址堆栈的吸引力就会大大降低。

久负盛名的KDF9在硬件中有一个16层深的返回地址堆栈,也就是子例程跳转嵌套存储(SJNS),但是当时的语言(特别是ALGOL)仍然需要管理用于激活记录的软件堆栈。

在ARM处理器上也使用了类似的想法。它们有一个高优先级中断FIQ,该中断具有7个存储体寄存器(R8-R14),这在许多情况下足以避免在堆栈上保存任何内容。

还有其他的,大部分是微控制器,但也有8位CPU,比如Valvo/Signetics 2650。

一旦调用的例程多于几条指令,节省的成本就微乎其微了。

现代高速缓存结构提供了一种一刀切的方法,消除了基于内存(返回)堆栈的所有剩余缺点。

它是一个严格的返回堆栈,更不用说它需要单词对齐了。将其限制为返回地址可以实现事半功倍的效果。毕竟,总是需要返回地址,因此将其放在芯片上是有意义的。其他一切都是可选的,因此任何ROI都会较低。

这个想法在8008之后就被抛弃了;后来的主流CPU(我指的是基本上排除了Forth芯片,以及反正只使用片上存储器的微型嵌入式芯片)都没有与片上堆栈一起使用。

那不是真的。例如,Valvo/Signetics 2650还具有8级(14位宽)返回堆栈。虽然没有出现在许多家用电脑中,但2650在嵌入游戏机和游戏机(如Space Invader)方面相当成功-甚至雅达利(Atari)也在他们的智力竞赛节目中使用了它。所以最明确的主流(*2)。也有其他的。

这纯粹是为了决定支持更复杂的程序比快速的子例程调用更重要吗?或者,片上堆栈占用的芯片面积是否大于将程序计数器保存/加载到RAM的逻辑?或者两者兼而有之,或者其他原因?

*1-Heck,甚至可以使用预加载或仍然加载的寄存器跳转到经常使用的例程单字节单周期指令。有点像SC/MP或a/370,它们根本没有堆栈。

*2-澳大利亚甚至有一个基于2650的S100系统…。仍然排在我愿望清单的首位。

*3-16位的8级已经是128位。静态完成这将需要750多个晶体管,外加数百个用于解码和缓冲的晶体管。在20世纪70年代中期,相当一部分CPU的晶体管总数为2-6000个。另一方面,无论如何都需要用于RAM访问的所有逻辑,因此,另外需要的是一种序列化/反序列化PC内容的方法-该方法也可以用于反序列化允许16位加载和存储的任何16位寄存器AKA。

把2650;主流称为主流绝对是将主流的定义延伸到了极限。它于1975年年中发布,是8080、6800、6502的同时代机型,在其生命的大部分时间里,Z80也是同时代的机型,应该说,所有这些机型都稍微更主流一些?--迈克尔·格拉夫(Michael Graf)。

@MichaelGraf当然,您可以继续添加一堆形容词,将所有内容缩小到您最喜欢的CPU。2650一直使用到90年代,你很有可能拥有一台带有一台电视机的电视机。请记住,(家用)计算机中CPU使用量始终只是实际发生的事情的一小部分。在这里,最重要的是,主流不是挑出一些顶级跑步者,而是大量使用WAS-就像Trabbi也是主流一样,从1964年到1990年,相当多的单元被建造和使用:)-Raffzahn。

它是一个严格的返回堆栈,更不用说它需要单词对齐。但是8080堆栈也是单词对齐的,不是严格返回堆栈。-两个cjs。

我的观点是,8080表明堆栈的严格字对齐(从这个意义上说,只有字被推入或弹出)并不是强制堆栈仅严格返回地址的东西,这直接与我引用的语句相矛盾。-两个cjs

@Raffzahn我已经完整地读过了这段话。我认为,删除个别句子和整个段落都会有所改进,因为它需要单词对齐,因为我认为这既有误导性,又与该段所表达的观点无关。-两个cjs。

点击“发布您的答案”,即表示您同意我们的服务条款、隐私政策和Cookie政策。

不是你想要的答案吗?浏览标记的其他问题或提出您自己的问题。