微处理器设计/计算机体系结构
在计算机科学的早期,计算机程序是硬连线的,只使用内存来存储数据。重新编程计算机涉及手动更改硬件开关,这需要花费大量的时间,并且有很高的代码错误可能性。为了解决这些问题,数学家和计算机科学家 约翰·冯·诺依曼 提出了现在被称为 冯·诺依曼体系结构 的体系结构,该体系结构将程序存储在内存中,从而避免了将它们硬连线的需要。
在冯·诺依曼体系结构中,一个称为 微处理器 的电路用于处理程序指令并执行它们。为了执行程序,微处理器首先从内存中获取程序的指令以及运行这些指令所需的数据。然后,微处理器解码和分离指令和数据,并激活运行程序所需的必要组件和通路。最后,微处理器执行程序,遍历指令,操作数据并存储结果。
这个获取、解码和执行的三步过程通常用两个硬件组件实现:控制单元和数据通路。如果数据被认为是水,那么数据通路就像一条有很多分支的运河,而控制单元就像一系列水闸。控制单元读取从内存中获取的指令,并使用它们来指导数据在数据通路中的流动方向。在此过程中,数据通路的不同分支将包含不同的机制来修改和转换流经它的数据。这些机制被称为 算术逻辑单元 (ALU)(稍后将更深入地讨论),它们对流经数据通路的 data 执行算术运算(如加法、减法、移位和求反)和逻辑运算(如 AND 和 OR)。
对控制单元和数据通路更细致的讨论将在后面一节中进行,该节的标题很方便,称为 控制和数据通路。
在 **哈佛体系结构** 机器中,计算机系统的内存被分成两个独立的部分:数据和指令。在纯哈佛系统中,这两个不同的内存占用独立的内存模块,并且指令只能从指令内存中执行。
许多 DSP 是修改后的哈佛体系结构,设计为同时访问三个不同的内存区域:程序指令、信号数据样本和滤波器系数(通常称为 P、X 和 Y 内存)。
理论上,这种三路哈佛体系结构可以比冯·诺依曼体系结构快三倍,后者必须一次读取指令、数据样本和滤波器系数。
现代台式计算机,尤其是基于英特尔 x86 ISA 的计算机,不是哈佛计算机,尽管较新的变体具有“哈佛式”的特性。所有信息、程序指令和数据都存储在同一个 RAM 区域中。但是,现代称为“分页”的功能允许物理内存被分割成称为“页面”的大块内存。每个内存页面可以是指令或数据,但不能两者兼而有之。
然而,现代嵌入式计算机通常基于哈佛体系结构。指令存储在与数据不同的可寻址内存块中,微处理器无法互换数据和指令。
从历史上看,第一种类型的 ISA(指令集体系结构)是 **复杂指令集计算机** (CISC),第二种类型是 **精简指令集计算机** (RISC)。一个常见的误解是 RISC 系统通常具有较小的 ISA(更少的指令),但用更快的硬件弥补了这一点。RISC 系统实际上具有“精简指令”,因为每条指令的功能很少,以至于执行起来只需要很少的时间。一个常见的误解是 CISC 系统有更多的指令,但通常会为增加的灵活性付出很高的性能代价。CISC 系统实际上具有“复杂指令”,因为至少有一条指令需要很长时间才能执行,例如,“双重间接”寻址模式本质上需要两个内存周期才能执行,而一些 CPU 具有“字符串复制”指令,这可能需要数百个内存周期才能执行。MIPS 和 SPARC 是 RISC 计算机的例子。英特尔 x86 是 CISC 计算机的一个例子。
有些人将堆栈机与 RISC 机归为一类;另一些人 [1] 将堆栈机与 CISC 机归为一类;有些人 [2],[3] 将堆栈机描述为既不是 RISC 也不是 CISC。
其他 ISA 类型包括 DSP、堆栈机、VLIW 机、MISC 机、TTA 体系结构、大规模并行处理器阵列等。
我们将在 稍后 更详细地讨论这些术语和概念。
微处理器的一些常见组件是
- 控制单元
- I/O 单元
- 算术逻辑单元 (ALU)
- 寄存器
- 缓存
下面简要介绍这些组件。
如上所述,控制处理单元读取指令并生成操作其他组件所需的数字信号。例如,将两个数字加在一起的指令将导致控制单元激活加法模块。
处理器需要能够与计算机系统的其他部分进行通信。这种通信通过 I/O 端口进行。I/O 端口将与系统内存 (RAM) 以及计算机的其他外设接口。
算术逻辑单元 (ALU) 是微处理器执行算术运算的部分。ALU 通常可以执行两个数字的加、减、除、乘以及逻辑运算(与、或、非、异或等)。
ALU will be discussed in far more detail in a later chapter, ALU.
有不同类型的寄存器。从上下文中可以很容易地看出我们指的是哪种类型的寄存器。
最一般的含义是“硬件寄存器”:任何可以用来存储信息位的器件,并且所有位可以同时写入或读出。由于 CPU 外部的寄存器也超出了本书的范围,因此本书只讨论处理器寄存器,它们是硬件寄存器,恰好位于 CPU 内部。但通常我们会参考更具体的寄存器类型。
寄存器在后面的一章寄存器文件中会有更详细的介绍。
程序员可见寄存器,也称为用户可访问寄存器,也称为架构寄存器,通常简称为“寄存器”,是指直接作为指令集中至少一条指令的一部分编码的寄存器。
寄存器是最快的可访问内存位置,因为它们非常快,所以通常数量很少。在大多数处理器中,寄存器的数量不到 32 个。寄存器的大小定义了计算机的大小。例如,一个“32 位计算机”的寄存器长度为 32 位。寄存器的长度被称为计算机的字长。
限制寄存器数量的因素有很多,包括:
- 对于一个新的 CPU,与旧 CPU 的软件兼容性非常重要。这要求新芯片的程序员可见寄存器数量与旧芯片完全相同。
- 将通用寄存器的数量翻倍,需要为每条指令添加一个额外的位,用于选择特定的寄存器。每个三操作数指令(指定两个源操作数和一个目标操作数)将扩展 3 位。现代芯片制造工艺可以在一个芯片上放置一百万个寄存器;这将使每个三操作数指令都需要 60 位来选择寄存器,不包括指定对这些操作数执行什么操作所需的位。
- 添加更多寄存器会为关键路径添加更多线,增加电容,从而降低 CPU 的最大时钟速度。
- 从历史上看,CPU 的设计寄存器数量很少,因为每个额外的寄存器都会大幅增加 CPU 的成本。但现在,现代芯片制造工艺可以在单个商品 CPU 芯片上放置数千万个存储位,所以这已经不再是一个问题。
微处理器通常包含大量的寄存器,但只有少数寄存器可供程序员访问。程序员可以用来根据需要存储任意数据的寄存器称为通用寄存器。程序员无法直接访问的寄存器称为保留寄存器[需要引用]。
有些计算机有高度专门化的寄存器——内存地址总是来自程序计数器或“该”索引寄存器或“该”堆栈指针;一个 ALU 输入总是连接到来自内存的数据,另一个 ALU 输入总是连接到“该”累加器;等等。
其他计算机有更多通用的寄存器——任何访问内存的指令都可以使用任何地址寄存器作为索引寄存器或堆栈指针;任何使用 ALU 的指令都可以使用任何数据寄存器。
其他计算机具有完全通用的寄存器——任何寄存器都可以用作任何指令中的数据或地址,不受限制。
除了程序员可见寄存器,所有 CPU 都有其他程序员不可见的寄存器,称为“微架构寄存器”或“物理寄存器”。
这些寄存器包括:
- 内存地址寄存器
- 内存数据寄存器
- 指令寄存器
- 微指令寄存器
- 微程序计数器
- 流水线寄存器
- 支持寄存器重命名的额外物理寄存器
- 预取输入队列
- 可写控制存储器(我们将在微处理器设计/控制单元和微处理器设计/微代码中讨论控制存储器)
- 有些人认为片上缓存是微架构寄存器的一部分;而另一些人则认为它位于 CPU “外部”。
实现任何一种指令集的方法有很多。绝大多数这些微架构寄存器在技术上并非“必需”。设计人员可以选择设计一个几乎没有物理寄存器(除了程序员可见寄存器)的 CPU。然而,许多设计人员选择设计一个包含大量物理寄存器的 CPU,以利用这些寄存器以比没有这些寄存器的 CPU 更快的速度执行相同的指令集。
大多数制造的 CPU 都没有缓存。
缓存是指位于芯片上的内存,但并不被视为寄存器。缓存的使用是因为读取外部内存速度非常慢(与处理器的速度相比),而读取本地缓存速度快得多。在现代处理器中,缓存可能占芯片总面积的 50% 或更多。下表显示了不同类型内存之间的关系
最小 | 最大 | |
寄存器 | 缓存 | RAM |
最快 | 最慢 |
缓存通常有 2 或 3 个“级别”,具体取决于芯片。级别 1 (L1) 缓存比级别 2 (L2) 缓存更小更快,而级别 2 (L2) 缓存更大更慢。有些芯片也有级别 3 (L3) 缓存,它比 L2 缓存更大(尽管 L3 缓存仍然比外部 RAM 快得多)。
我们将在后面的章节缓存中更详细地讨论缓存。
不同的计算机以不同的方式在 RAM 中排列其多字节数据字(即 16 位、32 位或 64 位字)。多字节字中的每个单独字节仍然是可单独寻址的。一些计算机以最高有效字节在最低地址的方式排列数据,而另一些计算机以最高有效字节在最高地址的方式排列数据。两种方法都有其背后的逻辑,这曾经是激烈争论的话题。
这种区别被称为字节序。以最低有效字节在最低地址的方式排列数据的计算机被称为“小端”,而以最高有效字节在最低地址的方式排列数据的计算机被称为“大端”。对于人类(通常是程序员)来说,如果以大端方式排列多字数据并将其转储到屏幕上,以逐字节的方式显示数据会更容易。但是对于其他人来说,将 LS 数据存储在 LS 地址更有意义。
在使用计算机时,这种区别通常是透明的;也就是说用户无法区分使用不同格式的计算机。但是,当不同类型的计算机尝试通过网络相互通信时,就会出现问题。
对于使用大端 68K 的机器来说,
address increases > ------ > data : 74 65 73 74 00 00 00 05
字符串“test”后跟 32 位整数 5。小端序 x86 类机器会将最后部分解释为整数 0x0500_0000。
在由大端序和和小端序机器组成的网络上通信时,网络硬件(应该)应用地址不变性原则,以避免文本混乱(避免 NUXI 问题)。高级软件(应该)将要传输到网络的数据包格式化为网络字节序。高级软件(应该)编写为“端序无关” - 始终将 16 位整数读取和写入为完整的 16 位整数,将 32 位整数读取和写入为完整的 32 位整数等 - 因此,无需更改即可为大端序或小端序机器重新编译。不是“端序无关”的软件 - 写入整数,然后将其读出为 8 位字节或其他长度的整数的软件 - 通常在为另一台计算机重新编译时会失败。
一些计算机 - 包括几乎所有 DSP - 是“非端序”的。它们始终读取和写入完整对齐的字,并且没有处理单个字节的硬件。在这些计算机上构建的系统通常*确实*具有特定的端序 - 但这种端序是写入软件中的,可以通过重新编译以相反的端序来切换。
堆栈是一块用作草稿区的内存。堆栈是一组连续的内存位置,设置为充当 LIFO(后进先出)缓冲区。数据通过“压入”操作添加到堆栈顶部,而顶部数据项在“弹出”操作期间从堆栈中删除。大多数计算机体系结构至少包含一个通常为堆栈指针保留的寄存器。
一些微处理器在 CPU 中包含一个独立于 RAM 的小型硬件堆栈。
有些人声称处理器必须具有硬件堆栈才能运行 C 程序。[1]
大多数计算机体系结构在其 汇编语言 中对递归“调用”指令提供硬件支持。一些体系结构(如 ARM、Freescale RS08 等)以这种方式实现“调用”
- “调用”指令将返回地址压入链接寄存器并跳转到子程序。子程序开头附近的单独指令将链接寄存器的内容压入主内存中的堆栈,以释放链接寄存器,以便子程序可以递归调用其他子程序。
一些体系结构(如 6502、x86 等)以这种方式实现“调用”
- “调用”指令将返回地址压入主内存中的堆栈并跳转到子程序。
一些体系结构(如 PIC16、RISC I 处理器、Novix NC4016、许多 LISP 机器等)以这种方式实现“调用”
- “调用”指令将返回地址压入一个专用的返回堆栈(独立于主内存)并跳转到子程序。