Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

1.5 从图灵机到 ECU

一座恶魔般的机房

1945 年,费城,宾夕法尼亚大学摩尔工程学院。

一座 30 吨重的巨兽蹲在一间约 167 平方米的机房里。它的名字叫 ENIAC(Electronic Numerical Integrator and Computer)——世界上第一台通用电子计算机。它有 18000 根真空管、70000 个电阻、10000 个电容、6000 个手动开关。它的功耗是 150 千瓦——每当 ENIAC 开机,费城的电灯都会闪一下。

真空管的故障率极高——平均每两天坏一根。ENIAC 的维护工程师们发明了一套诊断流程:他们学会根据 ENIAC 运算时不同单元的示波器波形“感觉“哪根真空管快不行了,然后提前更换。这和汽车工程师靠发动机的异响判断气门间隙过大,是同一类直觉。

ENIAC 每秒能做大约 5000 次加法或 357 次乘法。这个速度在 1945 年是石破天惊的——比当时最快的机电计算机(Harvard Mark I)快了 1000 倍。单条弹道的计算量,人工用台式计算器需要 20 小时——ENIAC 只需要 30 秒。

但你猜 ENIAC 怎么“编程“?

靠插拔电缆。 几十个女操作员——当时叫“计算员“(computers,这个头衔后来才指机器本身)——在 ENIAC 内部爬上爬下,手动把跳线插到不同的插孔上,设置函数表上 3000 多个开关的位置。换一个计算任务(比如从算炮弹弹道换成算原子弹的冲击波传播),需要花几天甚至几周来重新布线。

恩尼阿克的女操作员中,有六位被公认为世界上第一批程序员:Kay McNulty、Jean Jennings、Betty Snyder、Marlyn Wescoff、Fran Bilas 和 Ruth Lichterman。在当时,硬件(那台 30 吨的机房)被认为是“高级的工作“——男人做的。编程被认为是“文书工作“——女人做的。六十年后我们回看,软件工程成了地球上收入最高的职业之一,而“计算机“这个词从指人变成了指机器。


飞蛾:史上第一个 Bug

说到编程,就绕不开格蕾丝·霍珀(Grace Hopper)。

霍珀在 1944 年离开瓦萨学院(Vassar College)的数学教职,加入美国海军,被派去哈佛大学参与 Mark I 计算机的开发。她是第一个提出“用人类可读的英文词汇来代替机器码“的人——这个想法后来成长为 COBOL 编程语言和整个“高级编程语言“的概念。

1947 年的一个夏天,霍珀的团队在 Harvard Mark II 上排查一个奇怪的故障。继电器不工作了。他们打开机柜检查——发现一只飞蛾被夹在了继电器触点之间。他们用镊子把飞蛾夹出来,用胶带贴在日志本上,旁边写了一句:

“First actual case of bug being found.” ——首个真实的“bug“被发现案例。

从此以后,“bug”(虫子/故障)和“debug“(调试/去除虫子)成了计算机工程的通行行话。霍珀后来喜欢给年轻人讲这个故事——她会说:“我们没有在找 bug,bug 来找了我们。”


冯·诺依曼:那个在火车上想清楚一切的人

ENIAC 展示了“电子计算机可以工作“——但它也展示了“插拔电缆是一个死胡同“。如果每次换程序都要配线队忙几天,那计算还不是“通用“的。

解决这个问题的,是约翰·冯·诺依曼(John von Neumann)。他可能是 20 世纪最聪明的人——不是“之一“,而是“最聪明“这个级别里的并列第一名。他的生平是一份让人喘不过气的履历:

  • 22 岁发表关于序数定义的论文,奠定了现代集合论的公理化基础。
  • 25 岁提出量子力学的希尔伯特空间形式化——这是今天所有量子力学教科书的数学框架。
  • 24 岁证明博弈论的最小最大定理——博弈论的发源论文。
  • 29 岁加入普林斯顿高等研究院,成为那里最年轻的终身教授。
  • 40 岁加入曼哈顿计划,他的内爆透镜设计是原子弹“胖子“成功引爆的关键——他不只是“计算了冲击波“,他亲自去新墨西哥州的沙漠里观察了第一次核试验,还做出了大量数值模拟。

冯·诺依曼有一种近乎恐怖的心算能力。他的同事讲过一件事:有一次他在会议上提出了一组偏微分方程的数值解法,大家听完了觉得有点复杂。他说:“不急,我大概算一下。“然后他在脑海里模拟了计算机需要执行的前 50 步,给出了数值解的前几位有效数字。冯·诺依曼的大脑,是一台以 40Hz(他自己的生物时钟频率)运行的图灵机——不需要纸带、不需要真空管、不需要电。

他也研究了图灵在 1936 年的论文。在普林斯顿,图灵还做过他的助手。冯·诺依曼深知图灵机的数学意义——但他更关心它的工程实现。1945 年 6 月,他花了几个星期在火车通勤的路上,写出了一份 101 页的手稿——

First Draft of a Report on the EDVAC


EDVAC 报告:101 页改变世界

冯·诺依曼在 EDVAC 报告里做了一件事:他把图灵机的三大抽象组件——纸带、读写头、状态表——翻译成了可以用电子管、汞延迟线和导线实现的电路框图。

图灵机的纸带 → 存储器。不是串行纸带,而是一个可以“按地址随机访问“的存储空间。地址是存储器的坐标,数据是存储器的内容。随机访问这个特性至关重要——在图灵机里,你要读纸带上的第 73 个格子,必须先读过前 72 个格子。在冯·诺依曼架构里,你直接把地址线设为 73,数据线上就出现了那个格子里的内容。O(1) vs O(n)。这是计算效率的质变。

图灵机的读写头 → 运算器(ALU)+ 控制器。运算器是纯粹的“计算“——加法、减法、逻辑与或非。控制器是“决定“——读取指令、译码、发控制信号、更新程序计数器。两者合在一起就是 CPU(Central Processing Unit,中央处理器)。

图灵机的状态表 → 程序——存储在同一片内存里。这是最天才的决策。在 ENIAC 里,程序是物理线缆的连接方式。在冯·诺依曼架构里,程序就是内存中的一串数字——和存数据的数字没有区别。换程序,就是换一段内存的内容。 不需要插座和电缆——只需要把纸带(或纸带上的编码)替换掉。

这个决策意味着:计算机从“专门做特定计算的物理机器“变成了“可以加载任意程序的通用机器“。在冯·诺依曼的架构被实现之后,同一个硬件可以算弹道、算工资单、算天气预报、下国际象棋。唯一的变化是内存里的数据不同。

通用计算,从数学概念变成了工程现实。


五个方框,一颗心脏

冯·诺依曼架构的经典框图,你一旦看懂了,就看清了所有计算机的本质:

+----------+     +-----------+
|  存储器  |<--->|    CPU    |
|          |     |           |
| 程序+数据|     | +-------+ |
|          |     | | 运算器 | |
+----------+     | | (ALU) | |
                 | +-------+ |
                 | +-------+ |
                 | |控制器  | |
                 | +-------+ |
                 +-----------+
                      |
                 +-----------+
                 | 输入/输出 |
                 |   (I/O)  |
                 +-----------+

存储器在 ECU 上是 Flash(程序)+ SRAM(数据)+ EEPROM(标定参数)。你的 const 常量进 Flash,你的局部变量进 SRAM 的栈。

运算器(ALU) 在 ECU 上是 ARM Cortex-M4F 的 32 位 ALU。你在 C 里写的 x + ya & bc << 4,最终都在这里。ARM 的 ALU 还能做桶式移位 + 加法合并操作——所以编译器可以把 x = y * 3 翻译成 ADD r0, r1, r1, LSL #1(r0 = r1 + r1*2),一条指令、一个时钟周期。这是 RISC 架构的优雅。

控制器的核心只有一个寄存器——程序计数器(PC)。PC 永远指向“下一条要执行的指令的地址“。控制器的工作就是:取 PC 地址的指令 → 译码 → 执行 → PC 指向下一条指令 → 取 PC 地址的指令 → … 周而复始,直到断电、HALT、或异常。

输入/输出在 ECU 上是 CAN 控制器、SPI、I2C、ADC、GPIO。你没想过的——车速传感器发出的脉冲被 GPIO 脚捕获,经过内部定时器计数,换算出转速——这不是“数据“进了“CPU“——这是物理世界把信息写到了 CPU 的地址空间里

总线是五者之间的“公路网“。在 S32K144 上,总线不是一条——是 AHB 总线矩阵,用交叉开关把多条主设备(CPU、DMA)和多条从设备(Flash、SRAM、外设)连起来。DMA 可以直接从 ADC 数据寄存器搬运数据到 SRAM,不经过 CPU——这叫“解放 CPU“。


冯·诺依曼瓶颈

冯·诺依曼架构有一个著名的缺陷——冯·诺依曼瓶颈

存储器和 CPU 之间的双向总线是整台计算机最窄的通道。CPU 一个周期能吞 32 bit 数据,但总线每个周期只能传一个数据。这就是缓存存在的理由——把 CPU 频繁访问的指令和数据扣在离 ALU 最近的地方。

冯·诺依曼瓶颈的本质是物理距离。 电子在导线中约以光速 2/3 传播。1GHz 时钟周期 1ns,信号只能走约 20 厘米。如果你的片外 SDRAM 离 CPU 10 厘米——往返就是 20 厘米——这一个时钟周期里你根本碰不到它。而片上 SRAM 距离毫米级,延迟短得多。所以 S32K144 的 Flash 控制器能零等待输出 64 bit 预取数据(2 条 ARM 指令)。但访问片外 QSPI Flash 时,等待周期立刻跳升到几十个时钟。

这就是嵌入式工程师要关心 .data/.bss 段放置、LMA/VMA、链接脚本布局的原因。 你在跟冯·诺依曼瓶颈搏斗。


哈佛架构:对抗瓶颈的另一条路

冯·诺依曼架构的程序和数据共用一条总线。哈佛架构把指令和数据分到两条独立总线——CPU 可以同时取指令和访问数据。ARM Cortex-M 系列采用了改良哈佛:指令和数据在逻辑上分开(ICode 和 DCode 两条总线),但物理上映射到同一地址空间。你在 0x00000000 存得是指令还是数据——ICode 预取单元和 DCode load/store 单元走不同的 AHB 接口,硬件上并行访问。面对物理极限走侧面——这是整个计算机架构史的核心母题。


穿透 fetch-decode-execute:CPU 的一次心跳

现在我们把一架显微镜对准 CPU 内部,看一个完整的指令周期——CPU 的“一呼一吸“。

假设 PC 指向 Flash 地址 0x00001234,存储着 32 bit 的机器码 0xE0800001。以下是 Cortex-M4 三级流水线的行为:

第一个流水级:取指(Fetch)。CPU 把地址 0x00001234 放到地址总线上,发出读请求。Flash 控制器内部的预取缓冲区(Prefetch Buffer)和加速逻辑(Speculative Fetch)把相邻的 64 bit(两条 32 bit 指令)一起读出,送到译码级。这一步花费物理时间:地址总线的传播延迟 + Flash 存储阵列的读取延迟 + 数据总线的返回延迟。 在 112MHz 的 S32K144 上,约为 2-3 个 HCLK 周期。

第二个流水级:译码(Decode)。32 bit 机器码 0xE0800001 被送入译码器。译码器是一组硬连线逻辑——组合电路(combinational logic)——没有时钟,没有寄存器。它把 32 bit 拆成字段:

0xE0800001 = 1110 0000 1000 0000 0000 0000 0000 0001

[31:28] = 1110  → 条件码 AL(Always,无条件执行)
[27:26] = 00    → 指令类型:数据处理
[24:21] = 0100  → 操作码:ADD
[19:16] = 0000  → 目标寄存器:R0
[15:12] = 0000  → 第一操作数:R0
[11:0]  = ...   → 第二操作数:R1(通过 shift operand 编码)

译码结果是:“R0 = R0 + R1”——一个 32 位加法。一组控制信号从这个结论中展开:ALU 的操作选择设为 ADD,ALU 的 A 输入端连到 R0,ALU 的 B 输入端连到 R1,目标寄存器地址指向 R0。

第三个流水级:执行(Execute)。寄存器文件(Register File——一块内有 16 个 32 位寄存器的快速 SRAM)把 R0 和 R1 的值同时输出到 ALU 的两个输入端。ALU 的 32 位全加器接受两个输入,经过逻辑门传输延迟(几百皮秒),输出 R0+R1 的结果。写使能信号把 ALU 的输出写回寄存器文件中的 R0。与此同时,PC 递增:PC = PC + 4。 ARM 指令固定 4 字节,所以下一条指令的地址就是当前地址 + 4。

三条指令,九个流水级推进,约三个时钟周期。 你的 C 代码 GPIOA->ODR |= (1 << 5) 被编译成:

LDR r0, =0x40020014    ; 加载 GPIOA_ODR 地址
LDR r1, [r0]           ; 读取当前 ODR 值
ORR r1, r1, #(1<<5)    ; 第 5 位置 1
STR r1, [r0]           ; 写回 ODR

四条指令——取 GPIOD 地址、读当前值、置位、写回——约 8-10 个时钟周期后,PA5 引脚的电平从 0V 变成了 3.3V。

你的 C 代码不是在“控制硬件“——它是在驱动这条 fetch-decode-execute 流水线。 流水线的每一拍都在做同一组机械操作——取指、译码、执行、更新 PC——无限循环。从你按下编译按钮到 PA5 引脚拉高,中间的每一步都是确定性的、可追踪的、纯机械的。


内存映射 I/O:为什么写一个地址就能点亮 LED

冯·诺依曼架构还有一个不那么直观但极其强大的特性:I/O 设备也被映射到了同一个地址空间里。

ARM 的默认内存映射把 32 位地址空间(4GB)分配如下:

0x00000000 — 0x1FFFFFFF  Code(Flash, 512MB)
0x20000000 — 0x3FFFFFFF  SRAM(512MB)
0x40000000 — 0x5FFFFFFF  Peripherals(512MB,外设寄存器)
0x60000000 — 0x9FFFFFFF  External RAM
0xA0000000 — 0xDFFFFFFF  External Device
0xE0000000 — 0xFFFFFFFF  System(NVIC, SysTick, MPU, SCB 等)

当你写 *(volatile uint32_t *)0x40020014 = 0x20 时,CPU 不知道这是个 GPIO 寄存器——它只知道这是一次“写入地址 0x40020014 的 32 位数据“。地址解码器(address decoder)看到这个地址落在外设区间(0x40000000-0x5FFFFFFF),就在 AHB 总线矩阵中把它路由到 APB 桥(Advanced Peripheral Bus bridge),再路由到 GPIO 端口 A 的 ODR 寄存器。

GPIO 外设内部的输出驱动电路(output driver)接到寄存器 bit 5 从 0 变成 1 的信号。它打开对应的 PMOS(拉高)晶体管、关闭对应的 NMOS(拉低)晶体管,引脚电压在几纳秒内从 0V 爬升到 3.3V。如果引脚上挂着一颗 LED 和限流电阻——LED 就亮了。

从 C 代码的赋值语句到 LED 发光——中间经过的每一个步骤、每一层硬件、每一个 MOSFET 的栅极电容充放电——都是确定性的。 没有魔法。没有“操作系统在帮你做“。裸机上,就是你的一条 STR 指令击穿了两层总线桥(AHB → APB)、一个 GPIO 寄存器、一对输出驱动 MOSFET、一个外部引脚——最终变成了一个光子从 LED 表面逸出,打在你视网膜上。

这就是穿透。能穿透到这一层,你才真正理解“计算“和“物理“的连接点。


精简与复杂:RISC vs CISC 的世纪对决

在嵌入式和桌面 CPU 的两个世界里,有一条重要的设计哲学分歧值得了解——因为它解释了为什么你的车里是 ARM,你的笔记本里是 x86。

RISC(精简指令集计算机)的核心思想是:每条指令只做一件极简单的事,CPU 极快地把它们流水线化。 指令长度固定(ARM 是 32 bit),每条指令一个时钟周期(至少在设计目标上如此)。ARM 是 RISC。RISC-V 是 RISC。

CISC(复杂指令集计算机)的核心思想是:用一条复杂的指令完成很多事情。 指令长度不固定。x86 是 CISC。

为什么 ARM 赢了嵌入式?因为嵌入式最紧俏的是功耗。RISC 简单 → 译码逻辑简单 → 晶体管少 → 功耗低 → 不需要散热器。S32K144 全速跑 112MHz,功耗约几十毫瓦。CISC 用微码把复杂指令拆成内部微操作,在内部跑 RISC-like 执行引擎——外部兼容旧程序,内部用现代技术加速。这是“兼容性换复杂性“。

对嵌入式工程师来说,RISC 还有一个关键收益——ARM Thumb-2:把 32 位 ARM 指令压缩成 16 位,代码密度提高约 30%~40%。同样 512KB Flash,比纯 ARM 模式多装三分之一的代码。


你的 ECU,就是一台冯·诺依曼机器(但没那么简单)

现在打开任意一颗车规 MCU 的数据手册(比如 NXP S32K144),看它的框图。

         +-------------------+
         |   ARM Cortex-M4F  |  ← CPU (运算器+控制器)
         |   112 MHz         |
         +--------+----------+
                  |
         +--------+----------+
         |   AHB 总线矩阵     |  ← 多层总线交叉开关
         +--+----+----+-----+
            |    |    |
       +----+ +--+--+ +----+
       |SRAM| |Flash| |外设|
       |64KB| |512KB| |    |
       +----+ +-----+ +----+
         ↑       ↑       ↑
       内存    程序+数据   I/O

SRAM = 存储器(数据)。Flash = 存储器(程序 + 常量数据)。外设区 = I/O 接口。

但等一下——图灵的纸带是读写同一个介质,而这里程序在 Flash 里、数据在 SRAM 里。这是冯·诺依曼架构吗?

严格来说,Cortex-M 使用的是改进型哈佛架构(Modified Harvard Architecture):CPU 核心内部有两条独立的总线——I-Code 总线(读指令)和 D-Code 总线(读写数据),允许同时取指和访存。但在系统层面,Flash 和 SRAM 被统一编址到一个连续的地址空间里(0x00000000 - 0xFFFFFFFF),用同一套 AHB 总线矩阵访问。所以它在“统一编址“上是冯·诺依曼,在“内部并行取指/访存“上是哈佛。

为什么这对你重要? 当你在调试器里单步执行时,CPU 可以同时从 Flash 读取下一条指令、从 SRAM 读取操作数——这叫“指令级并行“的起点。如果是纯冯·诺依曼(一条总线),取指和访存必须串行——这就是“冯·诺依曼瓶颈“。

那图灵机的三要素呢?

图灵机(1936)冯·诺依曼(1945)S32K144(你手里的)
纸带(无限长)存储器(内存 + 磁盘)512KB Flash + 64KB SRAM
读写头(状态+符号)CPU(ALU + 寄存器)Cortex-M4F 核心
状态表(有限条规则)程序(指令序列)你烧录的固件

你的 CAN0->IFLAG1 = (1 << 15) 编译成 STR 指令,目标地址 0x40024030(FlexCAN 的 IFLAG1 寄存器)。CPU 从 Flash 读到这条指令,经译码发现是“写内存“,ALU 计算目标地址,数据总线把值写过去——CAN 模块内部的组合逻辑检测到中断标志位变化——触发中断控制器 NVIC——CPU 进入中断服务函数。每个你写的 C 句子,都是一根可以追踪到底的因果链——从代码到数字逻辑到半导体物理,没有断裂,没有模糊。


80 年,从图纸到公路:一场无声的接力

让我们把时间轴拉长,看清整幅图景。

1936年,图灵用一条无限长的纸带和一个有限的状态表,定义了计算的边界。同年,丘奇用λ演算给出了等价定义。

1945年,冯·诺依曼在火车上勾勒出EDVAC架构的蓝图——把图灵的抽象模型变成了可建造的电路逻辑框架。他在报告里连ALU的逻辑结构、指令格式、寻址方式、I/O调度都详细描述了。

1947年,贝尔实验室的巴丁、布拉顿和肖克利发明了点接触晶体管。它取代了真空管——体积缩小了几千倍,功耗降低了几个数量级。

1958年,德州仪器的杰克·基尔比做出了第一块集成电路——把晶体管、电阻和电容做在同一块锗片上。次年,仙童的罗伯特·诺伊斯用硅基平面工艺实现了商用集成电路。从此,电子元件不再“组装”——而是“刻”在硅上。

1971年,英特尔的费德里科·法金设计并制出了Intel 4004——世界上第一个商用微处理器。2250个晶体管,10μm制程,4位数据总线。法金在1970年代用最原始的工具——手工绘制掩模版的聚酯薄膜,然后用物理掩模做光刻——把CPU刻进了硅片。法金自己的话说:“那时候,你可以用肉眼看到每一个晶体管。”

中国人也在造计算机

当法金在加州设计 Intel 4004 的时候,中国也在走自己的计算之路。

1960 年,夏培肃带领团队研制出中国第一台自主设计的通用电子计算机——107 机。它用电子管搭建,每秒运算约 2500 次,内存只有 1K 字(约 4KB)。和她同时代的美国 IBM 7090 相比,107 机的性能差了约两个数量级。但它是一台完整的、能用的、中国人从零设计制造的通用计算机。夏培肃后来培养了一代又一代中国计算机科学家——她是“中国人计算之路”的第一个传递人。

1979 年,王选开始了一项近乎疯狂的项目:用计算机处理汉字。当时所有的计算机系统都是英文的——ASCII 编码只需要 128 个字符。而汉字有几千个常用字、几万个总字量。如果每个字用点阵存储,一个 64×64 的点阵就要 512 字节——几千个字需要几 MB 的存储,这在 1979 年是天文数字(当时主流磁盘容量只有几十 MB,但内存和软盘以 KB 为单位)。王选的解决方法:用轮廓描述字形——存储的不是点阵,而是笔画边界的数学曲线——然后让计算机在输出时“算出”点阵。这就是“轮廓+压缩”——有限资源下的最优解。王选的汉字激光照排系统让中国跳过了铅字印刷时代,直接进入了电子排版。他递出的这根接力棒,到今天还在运行。

而在英国,另一条计算之路也在萌芽。

1985 年,Acorn 计算机公司发布了第一颗 ARM 处理器(ARM1)。Sophie Wilson 设计指令集,Steve Furber 设计硬件。ARM1 接通电源后几乎不发热——Wilson 以为芯片坏了,其实只是功耗太低。2005 年,ARM Cortex-M3 发布,从此统治嵌入式 MCU 市场。你开的每辆车里,可能有几十颗 Arm Cortex-M 核心在同时运行。

图灵 → 冯·诺依曼 → 肖克利 → 基尔比 → 诺伊斯 → 法金 → Furber & Wilson → S32K144 的设计团队 → 你。

你在调试板上烧录的那颗 S32K144,是几百年来人类科学积累的终点。从纳皮尔的骨筹,到巴贝奇的分析机,到图灵的纸带,到冯·诺依曼的 101 页报告,到法金手工画的掩模版,到台积电 55nm 的铜互连——每一环都接上了。没有断档,没有奇迹。全部是科学发现和工程迭代的累积。

而你的固件烧录进去之后,在芯片里每天跑着——处理 CAN 报文、执行 UDS 诊断——然后这辆载着它的车每天上下班。下一个接手这颗 MCU 的工程师,会打开你的源码,读你的注释,调你的标定数据。你接过的棒,有一天会传给下一个人。


本篇小结

今天我们做了一件事:把80年的接力链串了起来——从图灵的纸带到你手里的S32K144。

关键结论:

  1. 冯·诺依曼把图灵机的抽象三要素翻译成了可建造的电路框图:纸带→存储器,读写头→CPU,状态表→程序。这份101页的报告,定义了此后所有计算机的骨架。
  2. fetch-decode-execute是你写的每一行C代码的物理心跳:从赋值语句到LED发光,中间的每一步都是确定性的、可追踪的——没有魔法。
  3. 你不是一个人在写代码:图灵→冯·诺依曼→肖克利→基尔比→诺伊斯→法金→Furber & Wilson→你——这是一场无声的接力,而你正在跑你那一棒。

下一节,我们走进硅的世界。从沙子到芯片——第二个思想实验开始了。

【下集预告】

下一个思想实验:你和 100 个各领域的专家被扔进原始森林里。你们有地球上所有的自然资源,有无穷的时间。但你们造不出一颗芯片

为什么?因为芯片背后是一整套工业文明体系。几百道工序、几十个学科、几千家工厂的协作——你严重低估了现代工业的复杂性。

接下来的一章,我们走进硅的世界。但这次我们从思想实验开始——在森林里,你为什么会失败?