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

第七十 芯片

芯片(Chip),学名集成电路(Integrated Circuit, IC)。它是把成千上万乃至万亿个晶体管、电阻、连线,用微米乃至纳米级的工艺“雕刻“在一小片硅上的产物。指甲盖大小,却承载着整个信息社会的算力:手机里的 SoC、服务器里的 CPU/GPU、内存条上的 DRAM、固态硬盘里的 NAND、汽车里的 MCU、耳机里的蓝牙芯片……本章沿着“沙子 → 晶圆 → 晶体管 → 电路 → 芯片 → 系统“这条链条,讲清楚芯片是什么、怎么做出来、怎么分类、怎么设计,以及软件工程师为什么也应该懂一点芯片;最后用 Rust 写几段与芯片打交道的代码。


一、从沙子到芯片

芯片的原料是沙子。这不是比喻:沙子(石英砂)的主要成分是二氧化硅 $\mathrm{SiO_2}$,而硅正是半导体工业的基石。

  石英砂(SiO2)
      │  高温还原:SiO2 + 2C → Si + 2CO↑
      ▼
  冶金级硅(纯度 ~98%)
      │  西门子法提纯
      ▼
  电子级硅(纯度 99.9999999%,即 9N,杂质以十亿分之一计)
      │  直拉法(Czochralski):籽晶旋转提拉,长成单晶硅棒
      ▼
  单晶硅锭(直径 300 mm,长约 2 m)
      │  切片 → 研磨 → 抛光
      ▼
  晶圆(Wafer,厚约 0.775 mm,表面平整到原子级)
      │  光刻 + 刻蚀 + 掺杂 + 沉积 …… 反复上百次
      ▼
  布满芯片的晶圆(每片可容纳数百到上万个 die)
      │  测试 → 切割(Dicing)
      ▼
  晶粒(Die)
      │  封装(Packaging)→ 引线键合 / 倒装 / 2.5D / 3D
      ▼
  芯片(Chip)
概念说明
晶圆(Wafer)硅单晶切片,主流尺寸 300 mm(12 英寸),面积越大单片可产的芯片越多、单位成本越低
晶粒(Die)晶圆上独立的一颗芯片,切下来就是“裸片“
良率(Yield)合格晶粒占全部晶粒的比例,直接决定成本
封装(Package)把裸片保护起来,引出引脚,并兼顾散热、供电与信号完整性
制程(Process Node)常说的 7 nm / 5 nm / 3 nm,是一代工艺的名称

一个常被忽略的事实:芯片的面积就是钱。良率随面积呈指数下降(缺陷落在哪颗晶粒上是随机的),所以“把芯片做大“从来不是免费的,这正是后面要讲的 Chiplet(芯粒)路线兴起的根本原因。

每片晶圆能切出多少颗芯片,有个经典近似公式($d$ 为晶圆直径,$A$ 为单颗面积,$d_{\text{edge}}$ 为边缘不可用宽度):

$$ N_{\text{die}} \approx \frac{\pi \left(\frac{d}{2}\right)^2}{A} - \frac{\pi d}{\sqrt{2A}} $$

第一项是“按面积能放多少“,第二项是圆形边缘的损失。若缺陷密度为 $D$,面积 $A$ 的泊松良率模型为:

$$ Y \approx e^{-A \cdot D} $$

面积翻倍,良率就按指数衰减——这就是为什么先进节点的超大芯片(如 GPU)价格高昂,而小芯片(如 MCU)可以便宜到几毛钱。


二、晶体管:芯片的“开关“

2.1 从电子管到集成电路

年份事件意义
1904真空电子管(弗莱明)第一代电子器件,体积大、发热高、寿命短
1947贝尔实验室发明点接触晶体管(巴丁、布拉顿,肖克利理论奠基)固体器件取代电子管,可控、可靠、省电
1958基尔比(TI)做出第一块集成电路元件与连线集成在一块材料上
1959诺伊斯(仙童)发明硅平面工艺用氧化层与光刻实现“印刷式“布线,可量产
1971Intel 4004第一颗商用微处理器:2300 个晶体管,10 μm 工艺
2011Intel 22 nm FinFET晶体管从“平面“走向“立体“
2022三星 3 nm GAA(环栅)量产栅极四面环绕沟道,继续缩小

2.2 MOSFET 如何当开关

数字电路只需要两种状态:0 和 1。MOSFET(金属-氧化物-半导体场效应晶体管)正好是一个电压控制的开关:

        栅极 G(Gate)
          │   ← 加电压,形成电场
     ┌────┴────┐
     │ 绝缘氧化层 │        栅压足够高 → 下方感应出导电沟道
     └────┬────┘
  源极 S ──┴── 漏极 D     沟道导通 → 电流可流 → 逻辑 1
    (Source)  (Drain)     沟道截止 → 电流不通 → 逻辑 0

   衬底 / 体(Body,通常接地)

把它接成互补的一对(PMOS + NMOS)就是 CMOS 反相器:输入为高时 NMOS 导通、PMOS 截止,输出被拉到低;输入为低时相反。CMOS 的妙处是稳态几乎不耗电,只有翻转时才有动态功耗——这让“几十亿个开关塞进指甲盖“成为可能。

2.3 为什么要把晶体管做小

动态功耗的经典公式是:

$$ P_{\text{dynamic}} = \alpha C V^2 f $$

($\alpha$ 为翻转活动率,$C$ 为负载电容,$V$ 为电压,$f$ 为频率)。把尺寸缩小到原来的 $1/k$,电容 $C$ 和电压 $V$ 都会下降,于是同样的电路更快也更省电——这就是整条产业链拼命缩小的动力。

2.4 摩尔定律与它的黄昏

定律 / 效应内容现状
摩尔定律摩尔 1965 年提出“集成电路上晶体管数量每年翻一番“,1975 年修正为约每两年翻一番经济与物理双重压力下明显放缓,但“每代密度提升“仍在继续
登纳德缩放尺寸缩小时,功率密度保持不变(电压与尺寸同步等比缩小)约 2005—2007 年前后失效,因为它需要电压持续下降,而阈值电压不能无限降
频率墙主频不再是性能增长的主引擎2004 年前后 Intel 放弃 4 GHz 的 Pentium 4 后续计划,主频长期停留在数 GHz
多核时代既然不能更快,就把更多核放在一起从单核到多核、到异构(大核 + 小核 + 加速器)

登纳德缩放失效的直接后果是**“暗硅”(Dark Silicon):芯片上的晶体管多到无法同时全速开动,否则散热根本压不住。于是设计者转向两件事——专用化(为特定任务造专用电路)与三维化**(堆叠、Chiplet、先进封装)。

年代代表芯片晶体管数制程
1971Intel 40042,30010 μm
1978Intel 808629,0003 μm
1993Intel Pentium3,100,0000.8 μm
2000Pentium 442,000,000180 nm
2006Core 2 Duo291,000,00065 nm
2022NVIDIA H100(GPU)约 800 亿4 nm
2023Apple M2 Ultra约 1340 亿5 nm

从 2300 到上千亿,五十年间翻了近八位数。


三、芯片是怎么造出来的

制造一颗芯片,要在晶圆上反复“盖楼“上百次。工序可分为前道(Front-End,做器件)与后道(Back-End,做连线与封装)。

   循环 100+ 次(每层图案重复一遍):
   ┌──────────────────────────────────────────────┐
   │ ① 氧化/沉积  生长或淀积一层薄膜(SiO2、SiN…) │
   │ ② 涂胶       旋涂光刻胶                       │
   │ ③ 光刻       掩膜版图案 → 曝光 → 显影         │  ← 最关键、最贵
   │ ④ 刻蚀       把图案"刻"进材料(干法/湿法)    │
   │ ⑤ 掺杂       离子注入,形成 n 型 / p 型区域   │
   │ ⑥ 退火      修复晶格损伤、激活掺杂            │
   │ ⑦ 平坦化    化学机械抛光(CMP),为下一层铺平 │
   └──────────────────────────────────────────────┘
                     │
                     ▼
   后道:金属互连(铜,十几层)→ 晶圆测试 → 切割 → 封装 → 成品测试

3.1 光刻:把图案“印“到硅上

光刻(Lithography)的原理和洗照片类似:光刻胶见光变质,显影后留下图案,再用它当“模具“去刻蚀。因为光学衍射极限,能印多细的线取决于光的波长:

技术光源波长说明
早期紫外(g/i-line)436 / 365 nm微米时代
DUV(KrF / ArF)248 / 193 nm配合浸没式(用水提高数值孔径)、多重曝光,撑到了 7 nm 时代
EUV(极紫外)13.5 nm全反射镜系统,真空环境,目前先进节点的唯一选择

EUV 光刻机的复杂程度堪称人类工业之最:把锡液滴用高功率激光轰击成等离子体发出 13.5 nm 极紫外光,再经多层反射镜(不能用透镜,因为 EUV 会被空气和玻璃吸收)聚焦到晶圆上。目前 EUV 光刻机由 ASML 独家供应。

3.2 洁净、设备与产业链

  • 洁净度:先进制程要求 ISO 1~3 级洁净室(每立方米 ≥0.1 μm 的尘埃粒子不超过 10 个)。作为参照,一根头发直径约 70 μm——一颗灰尘落在晶圆上,就等于一颗芯片报废。
  • 设备:光刻(ASML)、刻蚀与沉积(泛林 Lam、应用材料 AMAT、东京电子 TEL)、量测与检测(KLA)、离子注入(AMAT/AIBT)等。
  • 掩膜版(Photomask):每层图案需要一块光罩,一套先进节点掩膜的成本可达数百万甚至上千万美元,开模就是赌注,所以初创公司常用 MPW(多项目晶圆,几家共用一次流片)来摊薄费用。

四、制程节点:名字与现实的差距

“7 nm”“5 nm”“3 nm”并不是某个晶体管尺寸,而是一代工艺的营销名称。真实指标要看金属间距、鳍片间距、接触栅间距、标准单元高度等参数。比如所谓“3 nm“,其金属间距仍在 20 nm 量级。

世代晶体管结构大致年代代表
≥ 32 nm平面(Planar)至 2011 年各类传统 CMOS
22 nm ~ 3 nm 级FinFET(鳍式,栅极包三面)2011—2022Intel 22 nm 起,台积电/三星延续
3 nm 及以下GAA / 纳米片(栅极四面环栅)2022 起三星 3 nm、台积电 2 nm 一代
未来方向CFET(上下堆叠互补器件)研发中3D 晶体管

当“缩小“越来越难,产业链转向几条新路:

路线思路例子
More Moore继续缩尺寸、换结构GAA、CFET、High-K 金属栅、应变硅
More than Moore不缩尺寸,改做功能集成先进封装、Chiplet、3D 堆叠
专用化为特定任务定制电路NPU/TPU、ISP、视频编解码器、加密引擎
新材料替代受限的材料铜互连、低 k 介质、新沟道材料

一个有趣的转变:随着尺寸缩小,连线(互连)的延迟已经超过晶体管本身的开关延迟。芯片性能的瓶颈从“器件“变成了“布线“——这也是 3D 堆叠与 Chiplet 越来越重要的原因。


五、芯片家族:通用与专用的光谱

芯片千差万别,但可以用两把尺子衡量:通用性(能不能跑任意程序)与能效(每瓦性能)。二者通常相互矛盾:

   通用 ←────────────────────────────────────────────→ 专用
   灵活低效                                          高效死板

   CPU ──── GPU ──── DSP ──── FPGA ──── ASIC
   任意程序  数据并行  信号处理  可编程硬件  固定功能

   每瓦性能:低 ────────────────────────────────→ 高
类型全称/含义特点典型用途
CPUCentral Processing Unit通用、强控制流、大缓存、乱序执行服务器、PC、手机主核
GPUGraphics Processing Unit海量简单核心、SIMT 并行、高带宽显存图形、AI 训练/推理
NPU / TPU神经网络处理器矩阵乘加阵列、低精度量化、极省电手机 AI、数据中心推理
DSPDigital Signal Processor乘加指令、定点优化、实时性音频、通信基带、电机控制
FPGA现场可编程门阵列硬件可编程、可反复改、吞吐极高原型验证、网络加速、小批量
ASIC专用集成电路为单一任务定制,性能功耗最优矿机、加密引擎、视频编解码
MCU微控制器CPU + Flash + RAM + 外设,低成本低功耗家电、汽车电子、工业控制
SoCSystem on Chip把 CPU/GPU/NPU/基带/内存控制器等整合在一颗芯片上手机、平板、IoT
PMIC电源管理芯片电压转换、时序控制、电量计一切电子设备
射频芯片RF收发、功放、滤波通信、Wi-Fi、蓝牙、雷达
CMOS 图像传感器CIS光电转换摄像头、手机、车载

以一颗手机 SoC 为例,它内部大致是这样一个“片上城市“:

                ┌───────────── 手机 SoC ─────────────┐
                │  CPU 集群(大核 + 小核)            │
                │  GPU(图形与通用并行计算)           │
                │  NPU(AI 推理)                    │
                │  ISP(图像信号处理,拍照用)         │
                │  DSP / 音频编解码                  │
                │  基带 / 射频接口                    │
                │  内存控制器(LPDDR)                │
                │  安全岛(安全启动、密钥存储)        │
                │  各种 IO:USB、PCIe、显示、摄像头    │
                └────────────────────────────────────┘

六、存储器芯片与存储层次

计算芯片负责“算“,存储芯片负责“记“。二者工艺路线完全不同:逻辑芯片追求速度,存储芯片追求密度(单位面积存更多比特),所以逻辑与存储通常分开制造,再通过先进封装拼在一起。

类型存储原理易失性速度容量/成本典型用途擦写寿命
SRAM6 个晶体管组成的双稳态触发器易失极快(约 1 ns)单位面积贵CPU 的 L1/L2/L3 缓存无限
DRAM1 晶体管 + 1 电容,靠电荷存储易失(需刷新)快(约 50~100 ns)中等主存无限
NAND Flash浮栅/电荷陷阱存电荷,按页读写、按块擦除非易失慢(μs 级)便宜、密度高SSD、U 盘、手机存储约 10³~10⁴ 次/块
NOR Flash同上,但支持随机按字节读非易失较快较贵单片机程序存储、固件约 10⁴~10⁵ 次
HBMDRAM 裸片垂直堆叠 + TSV 硅通孔易失极快、带宽极高很贵AI 加速卡、高端 GPU无限

DRAM 的“易失“来自它的存储方式:信息是电容上的一点点电荷,会漏掉,所以必须周期性刷新(JEDEC 通常规定 64 ms 内把全部行刷新一遍)。这也是“内存断电就丢数据“的物理原因。

6.1 存储层次金字塔

计算机用“容量越大越慢越便宜“的多层结构,把少量快存储放在离 CPU 最近的地方:

        容量 / 延迟 / 成本                      存储介质
   ┌──────────────────────────┐
   │  寄存器        几百字节   │  < 1 ns    (位于 CPU 内部)
   ├──────────────────────────┤
   │  L1 缓存       几十 KB    │  ~1 ns     SRAM
   ├──────────────────────────┤
   │  L2 缓存       几百 KB~几 MB │ ~5 ns    SRAM
   ├──────────────────────────┤
   │  L3 缓存       几 MB~几十 MB │ ~15 ns   SRAM
   ├──────────────────────────┤
   │  主存(DRAM)  几 GB~几 TB │ ~100 ns    DRAM
   ├──────────────────────────┤
   │  SSD(NAND)   几百 GB~几 TB │ ~100 μs   NAND Flash
   ├──────────────────────────┤
   │  HDD / 网络存储  TB~PB 级  │ ~10 ms    磁盘
   └──────────────────────────┘
        越靠上:越快、越小、越贵

这套结构之所以有效,靠的是局部性原理:程序在一段时间内倾向于访问最近访问过的数据(时间局部性)和邻近的数据(空间局部性)。硬件据此按**缓存行(Cache Line,通常 64 字节)**整块搬运,而不是按字节搬运。

6.2 亲测:存储层次的延迟阶梯

教科书上的数字到底对不对?自己量一遍最清楚。顺序扫描测不出层次差异(CPU 预取器会把延迟隐藏掉),要用指针追逐:让每次访问的地址依赖上一次的结果,预取器就无能为力了。

use std::time::Instant;

/// 用线性同余发生器生成一个 0..n 的伪随机排列(Fisher–Yates 洗牌)
fn permutation(n: usize, seed: u64) -> Vec<usize> {
    let mut idx: Vec<usize> = (0..n).collect();
    let mut state = seed;
    let mut next = move || {
        state = state
            .wrapping_mul(6364136223846793005)
            .wrapping_add(1442695040888963407);
        (state >> 33) as usize
    };
    for i in (1..n).rev() {
        let j = next() % (i + 1);
        idx.swap(i, j);
    }
    idx
}

/// 指针追逐:访问顺序随机,且下一次访问的地址依赖上一次的结果,
/// 因此 CPU 无法预取,测到的是真实的存储层次延迟。
fn chase_ns_per_access(bytes: usize, rounds: usize) -> f64 {
    let n = bytes / std::mem::size_of::<usize>();
    let perm = permutation(n, 0x9E37_79B9);

    // link[k] 指向"随机序列中 k 的下一个元素",把查表变成链表
    let mut link = vec![0usize; n];
    for k in 0..n {
        link[perm[k]] = perm[(k + 1) % n];
    }

    let mut cur = 0usize;
    // 预热一遍
    for _ in 0..n {
        cur = link[cur];
    }

    let mut best = f64::MAX;
    for _ in 0..rounds {
        let start = Instant::now();
        for _ in 0..n {
            cur = link[cur]; // 依赖加载:必须等上一次读回结果
        }
        let ns = start.elapsed().as_nanos() as f64 / n as f64;
        if ns < best {
            best = ns;
        }
    }
    std::hint::black_box(cur);
    best
}

fn main() {
    let sizes: [(usize, &str); 5] = [
        (16 * 1024, "16 KB    L1 数据缓存"),
        (256 * 1024, "256 KB   L2 缓存"),
        (4 * 1024 * 1024, "4 MB     L3 缓存"),
        (32 * 1024 * 1024, "32 MB    L3 / 主存交界"),
        (256 * 1024 * 1024, "256 MB   主存"),
    ];

    println!("{:<26} {:>12}", "工作集", "每次访问(ns)");
    for (bytes, label) in sizes {
        println!("{:<26} {:>12.1}", label, chase_ns_per_access(bytes, 3));
    }
}

在一台普通台式机上的实测结果:

工作集                            每次访问(ns)
16 KB    L1 数据缓存                    1.4
256 KB   L2 缓存                      5.5
4 MB     L3 缓存                     12.3
32 MB    L3 / 主存交界                109.9
256 MB   主存                       149.7

阶梯清晰可见:L1 约 1.4 ns,L2 约 5.5 ns(5 倍),L3 约 12 ns(10 倍),一旦超出缓存容量跌到主存,就是 100 ns 以上(约 100 倍)。这也解释了为什么“算法复杂度相同、常数不同“的两个实现,性能可能相差十倍——差别往往在访问模式是否对缓存友好。

由此派生的两个工程结论:(1) 遍历数组比遍历链表快,因为数组是连续访问、链表是随机跳跃;(2) 多线程时若两个线程频繁写同一缓存行里的不同变量,缓存行会在核间来回“乒乓“,性能反而比单线程更差——这叫伪共享(False Sharing),通常用填充(padding)把变量对齐到不同缓存行来解决。

6.3 往三维要空间

当平面缩放变难,存储芯片转向“向上长“与“叠起来“:

技术做法效果
3D NAND把存储单元垂直堆叠成上百层密度大幅提升,成本持续下降
TSV(硅通孔)在裸片上打孔穿金属,把多层垂直连通缩短互连长度,提高带宽与能效
HBM多层 DRAM 用 TSV 叠在硅中介层上,紧邻 GPU位宽从 32/64 bit 跃升到上千 bit,带宽达 TB/s 级
Chiplet把不同功能的小芯片(含存储)用先进封装拼装良率更高、可混用不同工艺

七、芯片是怎么设计出来的

制造是“盖楼“,设计则是“画图纸“。一颗复杂芯片的设计流程大致如下:

  ① 需求与规格       功耗、性能、面积、成本(PPA)目标
        ▼
  ② 架构 / 微架构    多少核?缓存多大?几条流水线?
        ▼
  ③ RTL 设计         用 Verilog / VHDL / Chisel 写"硬件的行为"
        ▼
  ④ 功能仿真验证     跑海量测试用例,比对期望结果(验证占大量工时)
        ▼
  ⑤ 逻辑综合         RTL → 门级网表(用代工厂提供的标准单元库)
        ▼
  ⑥ DFT 插入         扫描链、内建自测(BIST),保证可测性
        ▼
  ⑦ 布局布线(PnR)  把百万级门与连线摆到版图上
        ▼
  ⑧ 签核(Sign-off) 时序收敛、功耗、DRC/LVS、电磁与热分析
        ▼
  ⑨ GDSII 交付       交给代工厂(称为"流片",Tape-out)

用 Rust 的流水线类比:RTL 是源码,综合是编译,布局布线是最优化加目标码生成,签核是测试与审计——软件工程师其实很熟悉这套思维方式(可对照第六十九“编译器与解释器“)。

环节说明工具/语言
前端(Front-End)规格、RTL 编写、功能验证Verilog/VHDL/SystemVerilog、Chisel、仿真器
验证占项目工作量的大头,甚至超过设计本身UVM 方法学、形式化验证、FPGA 原型
后端(Back-End)综合、布局布线、时序与物理验证EDA 工具链
版图与制造GDSII、掩膜、光刻代工厂

7.1 EDA:芯片设计的“编译器“

设计几十亿晶体管的芯片,人手工画是不可能的,全靠 EDA(Electronic Design Automation) 工具。这个市场高度集中:

厂商代表工具领域
SynopsysDesign Compiler、VCS、PrimeTime综合、仿真、时序签核
CadenceInnovus、Virtuoso、Palladium布局布线、模拟设计、硬件仿真
Siemens EDA(原 Mentor)Calibre、Questa物理验证、仿真验证

EDA 是芯片产业链上最容易被忽视、却最难替代的一环:它同时依赖半导体工艺知识、算法与几十年的工程积累,被称为“卡脖子的卡脖子“。

7.2 商业模式:谁设计、谁制造

一颗芯片从想法到成品,往往要经过多家公司:

模式含义代表
IDM设计、制造、封测全都自己做Intel、三星、德州仪器
Fabless只设计,不建厂NVIDIA、AMD、高通、苹果、华为海思
Foundry只代工制造台积电、三星、格罗方德、中芯国际
OSAT只做封装测试日月光、安靠、长电科技、通富微电
EDA / IP卖工具与可复用模块Synopsys、Cadence、ARM(IP)
设备 / 材料卖光刻机、刻蚀机、硅片、光刻胶ASML、泛林、应用材料、信越、沪硅产业

IP 复用是现代芯片设计的基石:ARM 卖 CPU 核(Cortex 系列),RISC-V 提供开放指令集,内存控制器、PCIe 控制器、SerDes 等都可以买现成的 IP,Fabless 公司只需把注意力集中在差异化部分。可以说,芯片设计已经变成“搭积木 + 少量自研“。

7.3 一次流片要花多少钱

项目量级
先进节点(如 5 nm)完整设计投入数亿美元(含人力、EDA 授权、IP 授权、验证)
一套先进节点掩膜数百万 ~ 上千万美元
一次 MPW 试验流片几十万 ~ 数百万美元(多家共享晶圆)
成熟节点(如 180 nm MCU)从几万到几十万美元起

所以芯片行业的规律是:流片一次,就是一次无法回退的下注。这也解释了为什么仿真验证要如此“过度“——与其在硅片上发现 bug,不如在电脑里发现。


八、指令集架构:芯片与软件的契约

硬件与软件之间有一份“契约“,它就是指令集架构(Instruction Set Architecture, ISA):软件按 ISA 编写,硬件按 ISA 实现。

概念含义例子
ISA(架构)程序员与编译器看到的抽象:有哪些指令、寄存器、内存模型、异常机制x86-64、ARMv9、RISC-V
微架构(Microarchitecture)ISA 的具体实现:流水线级数、缓存大小、乱序窗口宽度Zen 5、Golden Cove、Neoverse

同一个 ISA 可以有完全不同的微架构——就像同一份 C 源码可以被不同编译器编译出不同机器码。ISA 管兼容,微架构管性能:只要 ISA 不变,换一代 CPU 老程序仍能跑(这叫二进制兼容)。

8.1 CISC 与 RISC

维度CISC(复杂指令集)RISC(精简指令集)
指令数量与长度多、不等长、功能复杂少、定长、职责单一
内存访问许多指令可直接操作内存只有 load/store 能访存
实现风格微码译码,硬件更复杂硬布线,利于流水线与编译器优化
功耗效率相对低相对高
代表x86(Intel/AMD)ARM、RISC-V、LoongArch

如今两者已互相借鉴:x86 内部把复杂指令译成类似 RISC 的微操作执行,而 ARM 也在不断扩展指令集。

8.2 三大阵营与授权模式

阵营开放性授权方式生态
x86封闭Intel 与 AMD 交叉授权,外部几乎无法进入PC / 服务器软件生态最厚
ARM半开放内核 IP 授权(用 Cortex 核)或架构授权(自研核)手机、嵌入式、云原生服务器
RISC-V开放指令集标准开放,可自由扩展与自研核IoT、嵌入式、新型加速器、教学

授权模式对产业格局影响巨大:买 ARM 的 Cortex 核就像买成套家具,省事但差异化空间小;买架构授权就像买建材自己设计,投入大但可以做苹果 M 系列那样的自研核;而 RISC-V 则像开源硬件——门槛低、可定制,但需要自建生态。

8.3 指令在芯片里怎么跑:流水线

CPU 执行一条指令,要经历多个阶段。把多个阶段重叠起来,就像工厂流水线一样,每个时钟周期都能完成一条指令:

 时钟   T1    T2    T3    T4    T5    T6
 ────┼─────┼─────┼─────┼─────┼─────┼─────
 指令1│ 取指 │ 译码 │ 执行 │ 访存 │ 写回 │
 指令2│     │ 取指 │ 译码 │ 执行 │ 访存 │ 写回
 指令3│     │     │ 取指 │ 译码 │ 执行 │ 访存
 指令4│     │     │     │ 取指 │ 译码 │ 执行

但当遇到分支与依赖时,流水线会“停转“。现代高性能处理器用一整套技术填补这些空拍:

技术解决的问题
分支预测不知道下一条指令是哪条?先猜(准确率可达 99% 以上)
乱序执行(OoO)指令互相依赖时,先执行不受影响的那些
超标量 / 多发射一个周期同时发射多条指令
SIMD 向量指令一条指令算多个数据(SSE/AVX/NEON/SVE)
同时多线程(SMT)一个物理核呈现为两个逻辑核,填满执行单元
多级缓存用片上高速缓存掩盖主存延迟

8.4 一个简得不能再简的性能模型

处理器性能可以用时间公式拆开(第六十九“编译器“与第三十六“神经网络“里的优化都绕不开它):

$$ T = \frac{IC \times CPI}{f} = IC \times CPI \times T_{\text{cycle}} $$

其中 $IC$ 是指令条数(编译器与算法决定)、$CPI$ 是每条指令平均周期数(微架构决定)、$f$ 是主频(工艺与功耗决定)。三者往反方向拉:指令越复杂 $IC$ 可能越少但 $CPI$ 升高;主频越高但功耗与散热成本急剧上升($P \propto V^2 f$)。硬件与编译器的所有优化,本质上都是在调这三个量。


九、芯片中的功耗、并行与安全

9.1 功耗与散热:真正的物理限制

设备典型功耗散热方式
单片机(MCU)毫瓦级自然散热
手机 SoC几瓦 ~ 十几瓦石墨/热管,无风扇
笔记本 CPU15 ~ 45 W风扇 + 热管
服务器 CPU200 ~ 400 W大型散热器 + 风道
训练用 GPU700 W 以上加速卡散热/液冷
数据中心机柜数十 kW机房级液冷与电力调度

相应的工程手段有:DVFS(动态调频调压,按负载升降频)、时钟门控与电源门控(不用的模块直接断电)、大小核异构(轻载用小核省电)。

当频率与功耗都碰到墙,剩下的路子就是并行:多核、SIMD、多卡。但要记住 Amdahl 定律:若程序中只有比例 $p$ 可以并行,那么再多核也把总加速比卡在 $1/(1-p)$——通讯、同步、数据划分这些“串行尾巴“永远是并行计算的天花板。

9.2 芯片里的安全

芯片是信任链的根。如果芯片本身不可信,上面跑的一切安全机制都像沙子上的城堡。主要威胁与对策如下:

类型例子说明
微架构侧信道Spectre(2018)、Meltdown(2018)利用分支预测、乱序执行等微架构行为泄露内存内容;修复往往要牺牲性能
硬件故障攻击Rowhammer(2014)频繁读写一行 DRAM,导致相邻行位翻转(“比特翻转”),可被利用提权
侧信道分析功耗分析、电磁分析、时序分析智能卡、银行卡常用于防它;靠旁法推断密钥
故障注入电压尖脉冲、激光照射、时钟毛刺使芯片在关键步骤算错,绕过安全校验
物理逆向开盖(decap)、微探针、电子显微镜拍照读出版图与存储内容,或改写电路(FIB)
硬件木马 / 伪劣芯片在设计中植入触发式后门;回收件翻新根源于供应链不透明

防护思路可以分成“硬件筑坝“与“软件校验“两类:

对策内容
安全启动与信任根(RoT)从不可变的 ROM 开始,逐级校验固件签名后才交出控制权
安全岛 / TEEARM TrustZone、Intel SGX/TDX、AMD SEV:在芯片内划出隔离世界
硬件密钥与加密引擎密钥永不出芯片(如安全元件 SE、TPM);总线和内存加密
物理防护屏蔽层、传感器(光照/电压/温度异常检测)、防篡改封装
可审计的开放硅厂OpenTitan 等开放硅信任根项目,让“根“可以被公开审视

对软件工程师的实际含义:不要把安全机制建在“硬件一定可信“的假设上。Spectre/Meltdown 之后,跨权限边界的数据泄露不再只靠软件 bug 就能避免;这也是第五十九“软件漏洞“、第六十“网络安全“、第六十一“数据安全“三章反复强调“分层防御“的原因。


十、Rust 与芯片

芯片需要的是确定性:不能意外分配内存、不能有运行期 GC 抖动、不能有隐藏的异常抛出。这正是 Rust 的强项——它既能像 C 一样贴近硬件,又在编译期拦住了大量内存与并发错误。

维度CRust
内存安全靠程序员自律,未定义行为风险高编译期借用检查;越界、悬垂、二次释放很难写出来
并发安全靠约定与审查Send/Sync + 所有权,数据竞争在编译期就被拦截
抽象成本宏、函数指针、代码生成泛型、trait、内联:零成本抽象
依赖与构建往往手工集成第三方库cargo + crates.io,交叉编译一条命令
运行环境极小#![no_std] 下同样可以没有运行时
生态成熟度几十年积累,驱动与参考代码最丰富增长很快,部分外设与老旧平台仍缺现成库

Rust 在嵌入式领域的做法是“把 unsafe 关进笼子“:直接操作寄存器的代码集中在 PAC/HAL 库里,应用层代码全是安全 Rust。即使必须写 unsafe,也能用 #[repr(C)]、core::ptr::read_volatile 等工具把边界划得清清楚。

10.1 Rust 嵌入式与芯片生态

Crate / 项目用途
cortex-m / cortex-m-rtCortex-M 内核访问与启动运行时
riscv / riscv-rtRISC-V 指令/CSR 与启动运行时
embedded-hal / embedded-nal硬件抽象层 trait 标准(写一遍驱动,跑遍各种板子)
svd2rust从 SVD 描述文件生成外设寄存器访问层(PAC)
stm32-rs各大 STM32 型号的 PAC 与 HAL 集合
embassy异步嵌入式框架(async/await 写裸机并发)
RTIC基于中断优先级的并发框架,静态保证无数据竞争
esp-hal / rp-halESP32 / RP2040 的 HAL
probe-rs / cargo-embed烧录与调试(替代 OpenOCD)
defmt极低开销的嵌入式日志框架
heapless无堆内存容器(固定容量 Vec/String/Map)
bitflags / modular-bitfield位标志与位域
crc各类 CRC 校验算法
embedded-graphics小屏显示的 2D 图形库

10.2 与寄存器打交道:位域与校验

芯片编程的日常就是“按位读改写“:配置寄存器里的某个位域,或从寄存器里解出一个有符号数值。下面的例子还顺手实现了嵌入式通信最常用的 CRC-32:

// ---------- 位域读写 ----------

/// 把 value 写入 reg 的 [high:low] 位,其余位保持不变
fn set_bits(reg: &mut u32, high: u32, low: u32, value: u32) {
    let width = high - low + 1;
    let mask = ((1u32 << width) - 1) << low;
    *reg = (*reg & !mask) | ((value << low) & mask);
}

/// 从 reg 中取出 [high:low] 位
fn get_bits(reg: u32, high: u32, low: u32) -> u32 {
    let width = high - low + 1;
    (reg >> low) & ((1u32 << width) - 1)
}

/// 芯片温度寄存器:低 12 位是补码,1 LSB = 0.0625 °C
fn parse_temperature(raw: u16) -> f64 {
    let value = (raw & 0x0FFF) as i16; // 只保留低 12 位
    let signed = if value & 0x0800 != 0 {
        value - 4096 // 最高位为 1:负数,做符号扩展
    } else {
        value
    };
    signed as f64 * 0.0625
}

// ---------- CRC-32 校验 ----------

/// CRC-32(IEEE 802.3,与 zip / 以太网一致)
fn crc32(data: &[u8]) -> u32 {
    let mut crc = 0xFFFF_FFFFu32;
    for &byte in data {
        crc ^= byte as u32;
        for _ in 0..8 {
            let lsb = crc & 1;
            crc >>= 1;
            if lsb == 1 {
                crc ^= 0xEDB8_8320; // 反射后的生成多项式
            }
        }
    }
    !crc
}

fn main() {
    // 位域:配置一个控制寄存器
    let mut ctrl = 0u32;
    set_bits(&mut ctrl, 3, 0, 0b1010); // 低 4 位:分频系数
    set_bits(&mut ctrl, 7, 4, 0b0110); // 次 4 位:模式
    println!("ctrl = {ctrl:#010X} (0b{ctrl:010b})");
    println!("分频 = {}, 模式 = {}", get_bits(ctrl, 3, 0), get_bits(ctrl, 7, 4));

    // 修改中间位域而不影响其他位
    set_bits(&mut ctrl, 7, 4, 0b1111);
    println!("改模式后 ctrl = 0b{ctrl:010b}, 分频仍是 {}", get_bits(ctrl, 3, 0));

    // 温度:0x03C0 -> 60 °C,0x0FC0 -> -4 °C
    for raw in [0x03C0u16, 0x0FC0, 0x0640] {
        println!("raw = {raw:#06X} -> {:.4} °C", parse_temperature(raw));
    }

    println!("CRC-32(\"123456789\") = {:#010X}", crc32(b"123456789"));
}

输出:

ctrl = 0x0000006A (0b0001101010)
分频 = 10, 模式 = 6
改模式后 ctrl = 0b0011111010, 分频仍是 10
raw = 0x03C0 -> 60.0000 °C
raw = 0x0FC0 -> -4.0000 °C
raw = 0x0640 -> 100.0000 °C
CRC-32("123456789") = 0xCBF43926

最后一行是 CRC-32 的标准“校验值“(check value):输入字符串 123456789 得到 0xCBF43926,与所有标准实现一致。写通信协议时,先拿这个值验证自己的 CRC 实现,是能省掉大量调试的老经验。

10.3 抽象隔离芯片差异:HAL 与测试替身

嵌入式开发最痛苦的事之一是“换个芯片就重写“。Rust 的 trait 把“外设能做什么“抽象出来,驱动只依赖抽象,于是同一份驱动能跑遍所有芯片,而且在电脑上就能测:

/// 最小化的 SPI 抽象(对应 embedded-hal 的 SpiDevice trait)
trait SpiDevice {
    fn transfer(&mut self, read: &mut [u8], write: &[u8]) -> Result<(), &'static str>;
}

/// 传感器驱动:只依赖抽象,不依赖任何具体芯片
struct Sensor<S: SpiDevice> {
    spi: S,
}

impl<S: SpiDevice> Sensor<S> {
    fn new(spi: S) -> Self {
        Sensor { spi }
    }

    /// 读取 WHO_AM_I 寄存器(0x0F),用于确认通信链路正常
    fn who_am_i(&mut self) -> Result<u8, &'static str> {
        let mut buf = [0u8; 1];
        // 最高位置 1 表示读操作
        self.spi.transfer(&mut buf, &[0x80 | 0x0F, 0x00])?;
        Ok(buf[0])
    }
}

/// 真实硬件:把读写交给芯片的 SPI 外设寄存器(此处为示意)
#[allow(dead_code)]
struct HardwareSpi {
    base: usize,
}

impl SpiDevice for HardwareSpi {
    fn transfer(&mut self, read: &mut [u8], write: &[u8]) -> Result<(), &'static str> {
        // 真实实现里会写 SPI 数据寄存器、轮询状态位、读回数据,
        // 例如(示意):
        //   let dr = (self.base + 0x0C) as *mut u32;
        //   unsafe { core::ptr::write_volatile(dr, write[0] as u32) };
        let _ = (self.base, write);
        read[0] = 0x00;
        Ok(())
    }
}

/// 测试替身:完全不需要硬件,就能验证驱动的协议是否正确
struct MockSpi {
    id: u8,
    written: Vec<u8>,
}

impl SpiDevice for MockSpi {
    fn transfer(&mut self, read: &mut [u8], write: &[u8]) -> Result<(), &'static str> {
        self.written.extend_from_slice(write);
        read[0] = self.id;
        Ok(())
    }
}

fn main() {
    let mut sensor = Sensor::new(MockSpi {
        id: 0x6B,
        written: Vec::new(),
    });
    println!("WHO_AM_I = {:#04X}", sensor.who_am_i().unwrap());
    println!("驱动发出的字节 = {:02X?}", sensor.spi.written);
}

输出:

WHO_AM_I = 0x6B
驱动发出的字节 = [8F, 00]

这里有两个实用结论:**(1)驱动里的协议逻辑(读寄存器要置高位、发两个字节)在电脑上就验证完了,真正上板子的只剩 HAL 那几十行;(2)**同样的思路可以推广到 I²C、SPI、串口、网络层——embedded-hal 正是把这种模式做成了社区标准。

10.4 裸机点灯:no_std 与寄存器映射

最经典的入门例程:直接写寄存器,让一颗 LED 闪起来。注意所有外设都是“内存映射“的,每个寄存器就是一块固定地址,所以只需要往确定的地址写值:

#![no_std]
#![no_main]

use core::panic::PanicInfo;
use core::ptr::{read_volatile, write_volatile};

// 以 STM32 风格的 GPIOA 为例(具体地址随型号不同,务必查对数据手册)
const GPIOA_MODER: *mut u32 = 0x4800_0000 as *mut u32;
const GPIOA_BSRR: *mut u32 = 0x4800_0018 as *mut u32;

fn delay(cycles: u32) {
    for _ in 0..cycles {
        core::hint::spin_loop();
    }
}

#[no_mangle]
pub extern "C" fn main() -> ! {
    unsafe {
        // PA5 配置为通用输出模式(MODER5 = 0b01)
        let moder = read_volatile(GPIOA_MODER);
        write_volatile(GPIOA_MODER, (moder & !(0b11 << 10)) | (0b01 << 10));

        loop {
            write_volatile(GPIOA_BSRR, 1 << 5); // 置高:点亮
            delay(1_000_000);
            write_volatile(GPIOA_BSRR, 1 << (5 + 16)); // 置低:熄灭
            delay(1_000_000);
        }
    }
}

#[panic_handler]
fn panic(_info: &PanicInfo) -> ! {
    loop {
        core::hint::spin_loop();
    }
}

交叉编译到 Cortex-M4F,得到目标文件:

rustup target add thumbv7em-none-eabihf
rustc --target thumbv7em-none-eabihf -C panic=abort --emit=obj -o blink.o blink.rs

几个必须理解的细节:

细节为什么必须这样
#![no_std]裸机没有操作系统,没有 std(没有文件、线程、堆)
#![no_main]启动流程由芯片的复位向量与启动代码接管,不是 std 的 main
#[panic_handler]no_std 下必须自己定义 panic 行为(通常死死机或复位)
read_volatile / write_volatile寄存器值可能被硬件改变(或被硬件读到),编译器不允许把它优化掉
unsafe解引用裸指针本身就是不安全操作;这部分应当尽量局限在 PAC/HAL 层
-C panic=abort不展开栈(裸机通常没有栈展开的运行时支持)
链接脚本(memory.x)告诉链接器 Flash 与 RAM 的地址范围,把代码和数据放到正确位置
烧录与调试用 probe-rs/cargo-embed(基于 SWD/JTAG)代替 OpenOCD,且自带 defmt 日志

真实项目不要直接写这种魔数地址:用 svd2rust 从厂家提供的 SVD 文件生成类型安全的 PAC,或用 embassy/RTIC 这类框架,既保留零成本,又拿回类型检查与并发安全。

10.5 芯片设计侧的 Rust

Rust 正在从“写固件“渗透到“造芯片“的工具链里:

方向项目
硬件描述语言工具链Veryl(用 Rust 实现的现代 HDL 工具链)、rust_hdl(VHDL 解析器与语言服务器)
硅根信任与量产工具OpenTitan 的宿主端工具库(如 opentitanlib)用 Rust 编写
RISC-V 工具与模拟riscv、riscv-rt 等 crate;可用于指令编解码、模拟器与固件开发
Σ 部安全与功能安全Tock OS(面向嵌入式的安全操作系统,见第六十二“操作系统“)
测试/验证加速用 Rust 写测试平台、协议模拟器与固件回归测试

一句话总结芯片与软件的关系:芯片是软件的地基,软件是芯片的价值实现。同一个算法,写成 C 还是 Rust、访存是连续还是随机、能否搞成 SIMD/多核,性能可能差出十倍;而这十倍往往不是算法的事,是“知不知道下面那块硅是怎么工作的“的事。懂一点芯片,就不会再让缓存行白白报废。


十一、总结与练习

本章小结

知识点要点
原料与制造沙子 → 电子级硅 → 单晶锭 → 晶圆(300 mm)→ die → 封装
晶体管MOSFET 是电压控制的开关,CMOS 稳态几乎不耗电
摩尔定律密度增长放缓;登纳德缩放失效 + 频率墙 → 多核与专用化
制造流程氧化/沉积 → 光刻 → 刻蚀 → 掺杂 → CMP,反复上百次;EUV 13.5 nm 是当前关键
制程节点3 nm/5 nm 是代数名称而非尺寸;结构从平面 → FinFET → GAA
芯片分类CPU/GPU/NPU/DSP/FPGA/ASIC/MCU/SoC;通用性与能效相互矛盾
存储层次SRAM/DRAM/NAND/HBM;局部性原理 + 64 字节缓存行;L1≈1 ns,主存≈100 ns
设计流程RTL → 仿真验证 → 综合 → 布局布线 → 签核 → 流片;EDA 三巨头
商业模式IDM / Fabless / Foundry / OSAT / EDA+IP;IP 复用是现代设计基石
ISA架构(兼容性)与微架构(性能)分离;x86 / ARM / RISC-V 三大阵营
性能模型$T = IC \times CPI / f$;Amdahl 定律限制并行收益
芯片安全Spectre/Meltdown、Rowhammer、侧信道、硬件木马;安全启动/安全岛/密钥不出芯片
Rust 与芯片no_std、PAC/HAL 抽象、volatile 寄存器、probe-rs、defmt、embassy/RTIC

练习建议

  1. 位域驱动:为某个真实传感器(如加速度计)写一个寄存器映射模块,把“读 XYZ 轴数据“封装成 read_accel() -> (f32, f32, f32),并用 12/16 位补码处理符号。
  2. 验证 CRC 实现:用第 10.2 节的代码算出 123456789 的 CRC-32,确认结果是 0xCBF43926;再试着改成 CRC-16/CCITT,用于串口协议。
  3. 画出你机器上的延迟阶梯:把第 6.2 节的指针追逐程序改成多线程、多步长版本,找出你机器 L1/L2/L3 的实际容量(观察延迟突变的拐点)。
  4. 伪共享实验:两个线程各写一个变量,分别放在“同一缓存行相邻位置“与“间隔 64 字节“两种布局下,比较总耗时。
  5. 交叉编译一个 no_std 程序:为 thumbv7em-none-eabihf 或 riscv32imac-unknown-none-elf 编译第 10.4 节的代码,再用 probe-rs 或模拟器(如 QEMU)跑起来。
  6. 读懂一张数据手册:任选一颗 MCU,找到时钟树、GPIO 寄存器基地址与位定义,手算出“把 PA5 设成输出并拉高“需要对哪几个地址写什么值。
  7. 算一笔成本账:用第一节的两个公式($N_{\text{die}}$ 与 $Y = e^{-AD}$),估算一个 300 mm 晶圆上、面积 100 mm²、缺陷密度 0.1 个/cm² 的芯片的良品数量与单位成本。
  8. 安全思考:查一下 Spectre 的公开资料,回答两个问题:它利用的是芯片的哪个微架构特性?为什么“修改软件“很难彻底修好它?