Archipelago

Lec 1计算机体系架构的介绍

讲义完成更新于 2026-09-29
承接

这是当前课程已收录的第一篇。

Lec 1 计算机体系架构的介绍

TL;DR

  • 存储程序计算机开端:冯·诺依曼架构(EDVAC)确立“程序即数据”原则,终结了 ENIAC 时代繁复的物理插板重新接线模式。
  • 抽象层与软硬件契约:指令集架构(ISA)作为最核心的抽象边界,实现了跨代微架构独立演进与软件基础设施的长期复用。
  • 寄存器演进脉络:从昂贵代价下的单累加器与自修改代码,逐步分化出专用索引寄存器(IX),最终融合为现代正交通用寄存器组(GPR)。

计算机体系结构与架构师职责

计算机架构师设计和优化计算机系统的硬件结构,以提高性能、能效和可靠性。他们的工作包括设计处理器、内存系统、输入输出设备和其他组件,并且需要考虑成本、功耗和物理限制。他们经常使用仿真和建模工具来预测设计的性能,并与软件工程师合作确保硬件和软件的协同工作。

计算机发展简史与存储程序范式

  • 早期 ENIAC、EDVAC 和 EDSAC

    • ENIAC (Electronic Numerical Integrator and Computer):

      • ENIAC 由埃克特(Eckert)和莫克利(Mauchly)于 1943-1945 年在宾夕法尼亚大学设计和建造。
      • 技术特点:它是第一台完全数字化、电子化、操作的通用分析计算器。ENIAC 的出现标志着计算机技术从机械和电子混合系统向全电子数字计算机的重要进步。
      • 规模:ENIAC 重达 30 吨,占地面积 72 平方米,功耗高达 200 千瓦。
      • 性能:
        • 每分钟能读取 120 张卡片。
        • 加法操作耗时约 200 微秒,除法操作耗时约 6 毫秒。
      • ENIAC 最初应用于弹道计算。例如,计算炮弹射击时的角度需要考虑多个因素,如位置、顺风、横风、空气密度、温度、炮弹重量和推进剂量等。ENIAC 的高速计算能力使得这些复杂的弹道计算成为可能,帮助军事应用提高了精度和效率。
    • EDVAC (Electronic Discrete Variable Automatic Computer):

      • ENIAC 的编程系统是外部的
        • 指令序列独立于计算结果执行
        • 需要人工干预以“乱序”执行指令
      • EDVAC 由 Eckert、Mauchly 和 von Neumann 在 1944 年设计,旨在解决这一问题
        • 解决方案是存储程序计算机
        • “程序可以像数据一样被操作”
      • 1945 年首次发布了《EDVAC 报告草案》,但只有 von Neumann 的签名!
        • 这篇报告毫无疑问是计算机体系结构中最有影响力的论文
    • EDSAC (Electronic Delay Storage Automatic Calculator):

      • 1950 年建于剑桥大学,是第一台实用化的存储程序计算机。
      • 使用了水银延迟线存储器
    • IBM 650 和 IBM 360

      • IBM 650:
        • 1950 年代中期发布的第一台大规模生产的计算机,被广泛用于商业和科学计算。
        • 使用了磁鼓存储器。
      • IBM 360:
        • 1960 年代发布,标志着现代计算机系统的一个重要里程碑。
        • 统一了多个型号的计算机体系结构,支持多种编程语言和操作系统。
      • 西摩·克雷 – CDC 6600 和 Cray 1
        • CDC 6600:
          • 由西摩·克雷设计,于 1964 年发布,是世界上第一台超级计算机。
          • 采用了创新的分布式处理器和高效的流水线技术
        • Cray 1:
          • 1976 年发布,是第一台使用向量处理器的超级计算机。
          • 以其高性能和创新的 C 形设计而著名
    • 微处理器和个人计算机

    • 多核处理器

    • 手机


存储程序计算机的出现

  • 内部存储器
    • ENIAC 计算机(1946 年)使用插板作为内部存储器,是一种只能读取的存储程序计算机。
    • EDVAC 计算机(1947 年概念提出)提出了可读写存储器的概念,允许程序和数据存储在同一存储器中,大大提高了灵活性和效率。

早期计算机的主要问题是可靠性,指的是计算机能够持续运行而不发生故障的能力。**平均故障间隔时间(MTBF)**是衡量指标。但是最可靠的是 20 分钟。

到 20 世纪 50 年代中期,计算机技术的几个特点:

  • 硬件昂贵
  • 存储容量小:只有 1000 个字
  • 内存访问速度慢,比 CPU 慢 10-50 倍
  • 程序员需要与机器直接打交道
  • 设计复杂的控制电路来执行指令是当时的主要设计关注点,而不是指令解码速度或算术逻辑单元(ALU)操作的速度

在早期计算机设计中,使用了单一累加器作为主要的运算寄存器。这种设计借鉴了计算器的设计理念。 为什么? 寄存器昂贵:在那个时代,寄存器(用于暂存数据的高速存储单元)非常昂贵。因此,为了节省成本,计算机设计中只使用了一个主要的运算寄存器。

单累加器架构下的基本硬件组织与数据通路


计算机体系结构的抽象层设计

计算机系统跨层次抽象与软硬件垂直分工

[!IMPORTANT]

计算机体系结构本质上是抽象层的设计。

那抽象层的好处是什么?

  • 在一代产品内的环境稳定性(Environmental stability within generation)

  • 跨代产品的环境稳定性(Environmental stability across generations)

  • 大量单元的一致性(Consistency across a large number of units)

抽象层的结果是什么?

  • 激励应用创新(Enticement for application innovation)
    • 简化了设计
    • 提供的稳定开发环境,可以快速迭代和发布
  • 鼓励创建可重用的基础设施(Encouragement to create reusable foundations)
    • 工具链
    • OS
    • Library

技术演化动力与多维设计约束

推动(push)因素通常是由技术的进步和创新驱动,它们为计算机科技的发展奠定了基础和前进方向。

  • Transistors(晶体管):晶体管的发明是从真空管到固态电子设备的重大进步,极大地提高了计算机的速度、可靠性和效率。
  • Integrated circuits(集成电路):将多个晶体管集成到一个芯片上,使得计算机的尺寸缩小,功耗降低,同时性能提升。
  • VLSI(超大规模集成电路,初期):进一步提高了集成电路的密度和复杂度,为后来的处理器和存储器提供了更大的性能和功能。
  • Flash memories(闪存):非易失性存储技术的发展,提供了更快的数据访问速度和更大的存储容量,广泛用于固态硬盘和移动设备中。

拉动(pull)因素主要是由市场需求和用户需求驱动,它们直接影响到计算机科技的应用和使用场景。

  • Core memories(核心内存):早期计算机中使用的一种稳定且可靠的内存存储技术,满足了用户对数据持久性和稳定性的需求。
  • Magnetic tapes(磁带):提供了大容量、低成本的数据存储和备份解决方案,适用于需要长期存储和大规模数据处理的场景。
  • Disks(磁盘):硬盘驱动器提供了快速、随机访问的数据存储解决方案,满足了用户对数据快速访问和处理的需求。

相互作用(push and pull)指的是技术推动和市场需求之间的双向影响,它们共同推动了计算机科技的进步和发展。

  • ROMs, RAMs(只读存储器和随机存储器):ROM 提供了固化程序和数据的存储解决方案,而 RAM 则支持动态数据存储和快速访问,二者共同满足了计算机系统中对数据存储和处理的需求。
  • VLSI(超大规模集成电路):VLSI 技术既是技术推动(通过集成更多的功能和性能)、又是市场拉动(满足更高计算需求)的双重作用体现。
  • Packaging(封装技术):有效的封装技术不仅保护芯片,还提高了其性能和可靠性,使得计算机硬件可以更好地应对环境和使用条件。
  • Low Power(低功耗技术):随着移动计算和嵌入式设备的普及,低功耗技术的推广满足了市场对长续航时间和节能环保的需求,同时也推动了芯片设计和制造技术的发展。

软件的影响呢?

随着人们编写程序和使用计算机,我们对编程和程序行为的理解不断提高。 这对计算机体系结构虽然影响深远但速度较慢。 现代的架构师必须重视软件和编译问题。

体系架构是在限制下的工程设计,考虑因素有哪些:

  • 系统在目标应用中的整体性能

    • 平均情况和最坏情况
  • 制造芯片及支持系统的成本

  • 运行系统所需的功耗

    • 峰值功耗和每次操作的能量消耗
  • 系统的可靠性

    • 软错误和硬错误
  • 芯片设计的成本(工程师、计算机、CAD 工具)

    • 在许多情况下成为限制因素,较少的独特芯片能够得到合理的证明
  • 开发应用程序和系统软件的成本

    • 通常是任何可编程设备的主要约束条件

在不同时间和相同时间的不同应用中,这些因素的相对平衡可能导致广泛变化的架构选择。


早期指令集演进:单累加器与自修改代码

一般少于 24 个指令:

早期精简指令集与单累加器基本指令表

单累加器机器与汇编循环

向量加法循环在单累加器机器上的汇编时序

解释:

  1. LOOP: 加载 N,即当前循环的计数器
  2. JGE DONE: 检查 N 是否>=0,如果是跳转 DONE,结束
  3. ADD ONE:将 1 加到 N
  4. STORE N: 存储更新后的 N
  5. F1: LOAD A, 加载 Ai。
  6. F1: ADD B,进行加法运算将 A

自修改代码(Self-modifying Code)的妥协

早期计算机通过自修改代码动态改写内存地址字段


突破内存瓶颈:索引寄存器到通用寄存器

  • 索引能力(Indexing capability): 在计算机早期,编程涉及大量的数据管理和地址计算操作。例如,当你需要遍历一个数组或访问数据结构中的元素时,必须明确指定每个元素的内存地址。这需要许多指令来计算和管理这些地址,这种操作称为“书目管理”(bookkeeping),索引功能有效地减少了处理器需要执行的书目管理指令。
  • 处理器的本地存储:减少加载和存储操作(数据在 CPU 和内存之间的传输)数量。通过在 CPU 中提供少量快速的本地存储单元,称为寄存器(通常是 8-16 个)。
  • 复杂指令(Complex Instructions):提供更复杂和功能强大的单个指令,使得一个指令可以完成多步操作,从而减少了指令获取的数量。这提高了处理效率,因为 CPU 不需要频繁从内存中提取指令。
  • 紧凑指令(Compact Instructions):紧凑指令通过隐含操作数的地址位,使得每条指令所需的比特数减少,从而降低了获取指令的成本。

索引寄存器(Index Register)设计

增加特殊寄存器来简化地址计算:

修改现有的指令为:

LOAD x, IX  // AC <- M[x+(IX)]
ADD x, IX   // AC <- (AC) + M[x+(IX)]

其中, M[...] 表示从内存中读取数据;AC 是累加器;x 是一个基地址;IX 是索引寄存器。

第一个指令意思是从内存地址 x+IX 处读取数据, 并加载到累加器中。 第二个指令意思是从内存地址 x+IX 处读取数据, 并将该数据与累加器 AC 的当前值相加,存回 AC。

添加新的指令在操纵索引寄存器:

JZi x, IX  // if (IX) = 0 then PC <- x else IX <- (IX) + 1
LOADi x, IX // IX <- M[x] (截断以适应 IX)

PC 是程序计数器(Program Counter),用于指示当前执行的指令地址。

第一指令意思是,如果 IX 为 0,则将 PC 设置为 x(跳转到地址 x 执行)。如果 IX 的值不为 0,则将 IX 的值加 1。遍历数组时,如果 IX 为 0,可以跳转到特定位置执行其他,否则继续遍历。 第二条指令从内存地址 x 读取数据,将该数据加载到索引寄存器 IX 中。如果超出 IX 容量则截断。这条指令允许动态地更新索引寄存器的值,根据程序的需要动态地设置新的偏移量或基地址。

索引寄存器有累加器类似的特性。


至此,上述的 Ci←Ai+Bi,0≤i≤nC_i \leftarrow A_i + B_i, 0 \le i \le n,可以写成:

			LOADi N, IX 
LOOP 	JZi DONE, IX 
      LOAD LASTA, IX 
      ADD LASTB, IX 
      STORE LASTC, IX 
      JUMP LOOP 
DONE  HALT 

引入索引寄存器后向量累加的精简汇编指令流

索引寄存器的硬件代价与权衡

索引寄存器带来的寻址灵活性与控制逻辑开销对比

引入索引寄存器代价:

  • 更加复杂的控制逻辑
  • 索引寄存器的加法计算(需要类似 ALU 的地址计算电路)
  • 指令需要多 1-2 个比特来选择寄存器

再举个例子:

// 给索引寄存器增加k
AC <- (IX)
AC <- (AC) + k
IX <- (AC)
// 累加器AC必须保存和重新加载

// 直接增加IX可能更好
INCi k, IX  // IX <-(IX) + k
// 需要操作索引寄存器
STOREi x, IX // M[x] <- (IX) 需要适应一个字大小来索引

一开始,IX 看起来像个累加器。后面引入多个索引寄存器和多个累加器,这样可以同时处理更多的数据和地址需求;再后来索引寄存器和累加器的功能开始融合,就演变成了通用寄存器(General-Purpose Registers, GPR)。


控制流演进与子例程调用支持

子例程跳转指令JSR在内存中保存返回地址的实现机制

一个特殊的子例程跳转指令:

A: JSR F  // Jump to Subroutine at address F, 并将返回地址 A+1 保存至指定位置

早期的实现方式:

  1. 内存保存返回地址:将返回地址写入子例程 F 的首个字(M[F] <- A+1),子例程代码从 F+1 开始执行,返回时通过间接跳转 JUMP (F) 返回。缺陷是无法支持递归调用与多线程重入。
  2. 专用链接寄存器(Link Register)与硬件调用栈(Stack):随着芯片晶体管集成度提高,体系结构引入专用的返回地址寄存器(如 RISC-V 的 ra/x1)或硬件栈指针寄存器(SP),使现代函数调用具备完整的嵌套与重入能力。

我的理解

计算机架构演进史本质上是在“硬件制造物理极限”与“软件可编程性诉求”之间不断移动抽象边界的历史。早期自修改代码(Self-modifying Code)是对缺乏寻址寄存器硬件的廉价妥协,但它彻底破坏了指令与数据的只读保护边界;现代通用寄存器、流水线与多级缓存的诞生,则是通过在微架构层显式区分状态空间,将因果顺序与保护机制固化为不可动摇的契约。