基于 sympy + CMOS 门级建模, 从最小译码器一路推演到 27B LLM 推理芯片规模估算
目录
- 实验概述
- 实验环境
- [第一阶段 · 基础门电路验证 (3-to-8 译码器)](#第一阶段 · 基础门电路验证 (3-to-8 译码器))
- [第二阶段 · 小规模乘法器两级 SOP 最小化](#第二阶段 · 小规模乘法器两级 SOP 最小化)
- [第三阶段 · 大规模乘法器 (8×8 / 16×16)](#第三阶段 · 大规模乘法器 (8×8 / 16×16))
- [第四阶段 · 阵列 vs 树形结构延迟对比](#第四阶段 · 阵列 vs 树形结构延迟对比)
- [第五阶段 · 4×4 矩阵乘法模块方案对比](#第五阶段 · 4×4 矩阵乘法模块方案对比)
- [第六阶段 · LLM 推理芯片规模 vs 速度](#第六阶段 · LLM 推理芯片规模 vs 速度)
- 总结与展望
- [附录: 实验脚本清单](#附录: 实验脚本清单)
- [附录: 实验脚本源码](#附录: 实验脚本源码)
1. 实验概述
目标 : 从最基础的门电路出发, 用 sympy 的 SOPform 工具验证 CMOS 数字电路设计的"晶体管数估算"方法, 并把这一思路逐级扩展 , 最终用于 27B 参数 LLM 推理芯片的规模与速度估算。
研究方法:
3-to-8 译码器
↓ (1 输入对应 1 输出, 8 位最小案例验证)
2×2 → 3×3 → 4×4 乘法器
↓ (用 SOP 最小化, 确认增长趋势)
8×8 (sympy 超时失败)
↓
16×16 乘法器 (SOP 不可行, 改结构化估算)
↓
阵列 vs 树形 (延迟分析)
↓
4×4 矩阵乘法模块 (5 种方案对比)
↓ (找到 systolic 是 LLM 推理最优)
27B LLM 推理: 芯片规模 vs 速度
关键认知 : 真正的瓶颈不是 SOP 字面量, 而是芯片面积 / 内存带宽 / 数据流。
2. 实验环境
| 项 | 值 |
|---|---|
| OS | Windows 11 |
| Python | 3.13 (.venv) |
| 包管理 | uv |
| 核心库 | sympy 1.14.0 (含 logic.boolalg.SOPform) |
| 工作目录 | D:\eda_llm\ |
环境搭建:
bash
# 原计划: pyeda (需 MSVC, 失败)
# 改用: sympy (纯 Python, 自带 SOPform/POSform)
uv pip install sympy --python .venv
晶体管估算模型 (标准 CMOS 静态逻辑):
| 门类型 | T 数 |
|---|---|
| INV | 2 |
| 2-NAND | 4 |
| 2-NOR | 4 |
| 2-AND (NAND+INV) | 6 |
| 2-OR (NOR+INV) | 6 |
| 2-XOR (XNOR+INV) | 12 |
| 1-bit 半加器 (XOR+AND) | 8 |
| 1-bit 全加器 (经典 28T) | 28 |
两级 SOP 估算公式:
T = (2L + 2K) [每个 AND 项 = NAND + INV, L 个字面量, K 个积项]
+ (2K + 2) [1 个 K-输入 OR = NOR + INV]
+ 2N × 2 [2N 个输入反相器]
3. 第一阶段 · 基础门电路验证 (3-to-8 译码器)
目的: 用最简单的电路验证 SOP 最小化 + 晶体管估算流程
真值表
3 个输入 (a, b, c) → 8 个 one-hot 输出 (Y0~Y7), 2³ = 8 行
SOP 最小化结果
- 每个 Yi 对应 1 个 minterm, 已是最简
- 例:
Y3 = a & b & ~c - 全 8 个输出合计 24 个字面量
晶体管估算
| 方案 | 描述 | 晶体管数 |
|---|---|---|
| A | active-high, 输入有反变量 | 64 T |
| B | active-high, 输入无反变量 | 70 T |
| C | active-low (NAND-NAND 直出) | 54 T |
关键发现: NAND-NAND 结构 (PLA 风格) 比纯 AND-OR 省 16 T (8 个输出 INV)
脚本
decoder3to8.py
4. 第二阶段 · 小规模乘法器两级 SOP 最小化
目的: 验证 SOP 估算方法, 摸清 2 级 SOP 随位宽的增长曲线
4.1 2×2 乘法器 (4 入 4 出, 16 行)
| 输出 | K | L | Wmax | T |
|---|---|---|---|---|
| c0 | 1 | 2 | 2 | 10 |
| c1 | 4 | 12 | 3 | 42 |
| c2 | 2 | 6 | 2 | 22 |
| c3 | 1 | 4 | 2 | 14 |
| 合计 | 88 T |
c0 = a0 & b0, c3 = a1 & b1 --- 两端极简, 中间是加法逻辑
4.2 3×3 乘法器 (6 入 6 出, 64 行)
| 输出 | K | L | Wmax | T |
|---|---|---|---|---|
| c0 | 1 | 2 | 2 | 10 |
| c1 | 4 | 12 | 3 | 42 |
| c2 | 9 | 36 | 4 | 110 |
| c3 | 10 | 44 | 6 | 130 |
| c4 | 8 | 35 | 5 | 104 |
| c5 | 3 | 14 | 5 | 42 |
| 合计 | 438 T | |||
| + 输入 INV | 450 T |
观察: c3 最复杂 (中间位), c0/c5 最简单 (LSB/MSB 受约束少)
4.3 4×4 乘法器 (8 入 8 出, 256 行)
| 输出 | minterms | K | L | Wmax | T |
|---|---|---|---|---|---|
| c0 | 64 | 1 | 2 | 2 | 10 |
| c1 | 96 | 4 | 12 | 3 | 42 |
| c2 | 112 | 9 | 36 | 4 | 110 |
| c3 | 120 | 30 | 172 | 8 | 466 |
| c4 | 100 | 36 | 214 | 8 | 574 |
| c5 | 88 | 32 | 189 | 7 | 508 |
| c6 | 66 | 22 | 121 | 6 | 332 |
| c7 | 32 | 9 | 46 | 6 | 130 |
| 合计 | 2 172 T | ||||
| + 输入 INV | 2 188 T |
关键观察:
- c0 =
a0 & b0(LSB 极简, 1 项) - c3/c4 是瓶颈 (K=30-36, Wmax=8)
- 2 级 SOP 耗时 0.6s
4.4 增长趋势
| N | 行数 | 2 级 SOP T | 阵列 T | SOP/阵列 |
|---|---|---|---|---|
| 2 | 16 | 88 | 120 | 0.7× ← SOP 占优 |
| 3 | 64 | 442 | 282 | 1.6× ← 持平 |
| 4 | 256 | 2 188 | 512 | 4.3× ← SOP 输 |
| 5 | 1 024 | ~13 000 | ~770 | ~17× ← 估算 |
| 6 | 4 096 | ~80 000 | ~1 100 | ~73× ← 估算 |
| 8 | 65 536 | 不可行 | ~2 100 | --- |
| 16 | 4.3×10⁹ | 完全不可行 | ~8 600 | --- |
关键拐点: N=3 之前 2 级 SOP 占优, N≥4 后结构化阵列必胜。
4.5 sympy 位序坑
实验中踩到的坑: sympy 的
SOPform中, minterm N 的 bit i 对应 inputs 列表的最后一个 变量。必须用bit_reverse()函数把位顺序翻转。
正确打包公式:
python
code = (bit_reverse(av, N) << N) | bit_reverse(bv, N)
脚本
mult3x3.py, mult4x4.py
5. 第三阶段 · 大规模乘法器 (8×8 / 16×16)
5.1 8×8 尝试 (16 入 16 出, 65 536 行)
实验结果 : sympy SOPform 调用 Espresso 算法, 对 16 变量 / 上万 minterm 极慢
- 单个输出位计算超过 15 分钟仍无结果
- 结论: 8×8 走 2 级 SOP 不可行
5.2 16×16 乘法器结构化估算
真值表规模: 2^32 ≈ 4.3×10⁹ 行, 存储需 ~16 GB → 不可枚举
| 结构 | AND | FA | HA | CPA | 总 T | 延迟 |
|---|---|---|---|---|---|---|
| 阵列 (ripple) | 256 | 240 | 32 | 0 | 8 576 | 32 FA 级 |
| Wallace 树 | 256 | 127 | 0 | 31 | 6 024 | ~16 CSA 级 |
| Dadda 树 | 256 | 95 | 0 | 31 | 5 128 | ~14 CSA 级 |
Wallace 树拆解:
256 个 2-AND 部分积 = 1 536 T
Wallace CSA 树 (127 个 FA) = 3 556 T
最终 32-bit CPA (31 个 FA) = 868 T
32 个输入反相器 = 64 T
─────────────────────────────────────
合计 = 6 024 T
关键发现:
- 2 级 SOP vs 结构化差 6 个数量级 (10¹¹ vs 10⁴ T)
- 实际工程 N≥4 永远用结构化方案
- 真实设计 (28nm) 16×16 multiplier ≈ 3 000-5 000 GE, 与估算吻合
脚本
mult8x8.py (超时), mult16x16.py
6. 第四阶段 · 阵列 vs 树形结构延迟对比
目的: 量化两种主流结构的延迟差异
6.1 延迟模型
- 1 个全加器延迟 = 1 T_FA (静态 CMOS 约 50-200 ps)
- 阵列: T = 2N × T_FA (沿对角线)
- Wallace: T = log_{1.5}(N²) × T_FA + T_CPA
- CPA 选型: ripple (2N), Kogge-Stone (log₂N + 2), CLSA (√N)
6.2 延迟对比表
| N | 部分积 | 阵列 2N | Wallace CSA | + ripple CPA | + Kogge-Stone | 速度比 |
|---|---|---|---|---|---|---|
| 2 | 4 | 4 | 2 | 10 | 6 | 0.67× |
| 3 | 9 | 6 | 4 | 16 | 8 | 0.75× |
| 4 | 16 | 8 | 6 | 22 | 11 | 0.73× |
| 8 | 64 | 16 | 10 | 42 | 16 | 1.00× |
| 16 | 256 | 32 | 13 | 77 | 20 | 1.60× |
| 32 | 1024 | 64 | 16 | 144 | 24 | 2.67× |
| 64 | 4096 | 128 | 20 | 276 | 29 | 4.41× |
| 128 | 16384 | 256 | 23 | 535 | 33 | 7.76× |
6.3 关键发现 (反直觉)
N ≤ 4 : 阵列比 Wallace 还快! (CPA 拖累了 Wallace)
N = 8 : 持平
N ≥ 16 : Wallace 优势明显, N=128 时快 7.76 倍
根本原因:
- 阵列: O(N) --- 线性
- Wallace + Kogge-Stone: O(log N) --- 对数
6.4 4×4 临界路径示意
阵列 (8 T_FA):
a0&b0 ─┐
├──FA─┐
a1&b0 ─┘ │
├──FA─┐
a2&b0 ─────┘ │
├──FA────→ 输出
a3&b0 ─────────┘
↑ 7 个 FA 串在一条线上
Wallace (6 CSA + Kogge-Stone CPA = 11 T_FA):
16 个 AND 部分积
↓ 6 级 CSA 树 (并行, 每级只 1 FA 深度)
2 个数
↓ Kogge-Stone CPA (log₂ 8 = 3 级)
8 个输出
6.5 工业选择
| 场景 | 选择 | 理由 |
|---|---|---|
| N ≤ 8 | 阵列 | 速度不输, 版图超规则 |
| N = 16-32 | Wallace + 普通 CPA | 面积/速度折衷 |
| N ≥ 64 | Wallace/Dadda + Kogge-Stone | 速度优势碾压 |
| 超高频 | Booth + Wallace + KS | 部分积减半 |
| 低功耗 | 阵列 + 流水线 | 漏电小, 流水降电压 |
脚本
delay_compare.py
7. 第五阶段 · 4×4 矩阵乘法模块方案对比
问题定义: 4×4 矩阵 C = A × B, 元素 4-bit, 累加到 10-bit
C[i][j] = Σk=0..3 A[i][k] × B[k][j] (4 次乘, 3 次加)
共 16 个内积, 每个内积需 4 个 4×4 mult。
7.1 五种方案
| 方案 | 面积 T | 延迟 | 吞吐 | AT | 适用 |
|---|---|---|---|---|---|
| A 1 mult + 累加 (时分) | 806 | 16 cy | 0.06/cy | 12 896 | 极小芯片 |
| B 4 mult + 4 累加 | 3 224 | 4 cy | 0.25/cy | 12 896 | 中等并行 |
| C 16 mult + 16 累加 (内积全并行) | 12 800 | 1 cy | 1.0/cy | 12 800 | 单次最快 |
| D 4×4 systolic (16 PE) | 12 992 | 7 cy | 1/cy | 90 944 | 流式最优 |
| E 16 mult + 加法树 | 18 944 | 1 cy | 1.0/cy | 18 944 | 无累加 |
7.2 C 方案 (内积全并行) 拆解
1 个内积单元 (PE):
4 个 4×4 mult (并行) = 4 × 512 = 2 048 T
1 个 4-输入加法树 = 3 × 8 × 28 = 672 T
小计 = 2 720 T
16 个内积单元 = 16 × 2 720 = 43 520 T
+ 16×8-bit 输出寄存器 = 16 × 8 × 8 = 1 024 T
─────────────────────────────────────────────────────
合计 = 44 544 T
7.3 D 方案 (systolic) 拆解
1 个 PE (处理单元):
1 个 4×4 乘法器 = 512 T
1 个 8-bit 加法器 = 224 T
8 个寄存器 (流水锁存) = 64 T
2 个 2 选 1 mux = 12 T
小计 = 812 T
16 个 PE = 12 992 T
7.4 4×4 systolic 数据流
A flows left-to-right, B flows top-to-bottom, C accumulates in PE
B[0,0] B[0,1] B[0,2] B[0,3]
| | | |
+----+ +----+ +----+ +----+
A--> | PE |-->| PE |-->| PE |-->| PE | ← 第 0 行
+--^-+ +--^-+ +--^-+ +--^-+
| | | |
+--v-+ +--v-+ +--v-+ +--v-+
A--> | PE |-->| PE |-->| PE |-->| PE | ← 第 1 行
+--^-+ +--^-+ +--^-+ +--^-+
... ... ... ...
Cycle 0..6: pipeline fill (A, B feeding in)
Cycle 7: C[0,0] 第一个结果
Cycle 8..15: 剩下 15 个结果
稳态吞吐: 1 个 C[i,j] / cycle
完整 4×4 矩阵乘: 15 周期
7.5 关键发现: C vs D
| 场景 | C 方案 | D 方案 | 胜者 |
|---|---|---|---|
| 只算 1 次矩阵 | 1 cy ✓ | 15 cy ✗ | C |
| 连续算 ≥ 7 次 | 7 cy 起就累 | 23 cy ✓ | D |
C 适合"突发" (单次大批量)
D 适合"流水线" (数据持续流过)
7.6 推荐: 按目标挑
| 目标 | 推荐 | 理由 |
|---|---|---|
| 极小面积 (嵌入式) | A | 1 mult 时分复用, 16 周期换 700 T |
| 中等性能, 省面积 | B | 4 mult 并行, 4 周期完成, 3K T |
| 单次最快 | C | 1 周期, 12.8K T |
| 流式吞吐 | D | systolic, 1 结果/cycle, 13K T |
脚本
matrix4x4.py
8. 第六阶段 · LLM 推理芯片规模 vs 速度
关键问题: 用 D 方案 (systolic) 设计芯片, 27B LLM 推理速度与芯片规模的关系是什么?
8.1 模型假设
| 项 | 值 |
|---|---|
| 模型 | 27B 参数 (Qwen2.5-27B / Gemma-2-27B) |
| 参数量 | 27 × 10⁹ |
| 量化 | INT8 (主流) → 27 GB 权重 |
| 每 token 算力需求 | 27 GOPs (INT8) |
| 每 token 内存加载 | 27 GB (decode 模式) |
8.2 Decode 模式: tok/s = 内存带宽 / 27 GB
| 带宽 | 读 27GB 用时 | tok/s | 体感 |
|---|---|---|---|
| 50 GB/s | 540 ms | 1.9 | 勉强能用 |
| 200 GB/s | 135 ms | 7.4 | 打字机 |
| 400 GB/s (M2 Max) | 67 ms | 14.8 | 可用 |
| 800 GB/s (AI100) | 34 ms | 29.6 | 流畅 |
| 1.6 TB/s | 17 ms | 59 | 很快 |
| 3.35 TB/s (H100) | 8 ms | 124 | 实时语音级 |
| 5 TB/s | 5 ms | 185 | 理论极限 |
8.3 主流芯片实测 (27B INT8 Decode)
| 芯片 | systolic | TOPS | 面积 | 功耗 | 带宽 | 27B tok/s |
|---|---|---|---|---|---|---|
| Apple M2 Max | 否 | 38 | 430 mm² | 60 W | 400 GB/s | 15 |
| TPU v2 | 128×128 | 45 | 300 | 200 | 700 | 26 |
| TPU v4 | 128×128 | 275 | 650 | 170 | 1 200 | 44 |
| A100 | 否 | 624 | 826 | 300 | 2 000 | 74 |
| H100 | 否 | 1 979 | 814 | 700 | 3 350 | 124 |
| 理论 64×64 | 64×64 | 4 | 40 | 20 | 200 | 7.4 |
| 理论 128×128 | 128×128 | 16 | 160 | 80 | 500 | 18.5 |
| 理论 256×256 | 256×256 | 66 | 640 | 320 | 1 000 | 37 |
核心观察 : H100 的 TOPS 是 1979, 但 27B 速度只到 124 tok/s --- 算力根本没用完, 瓶颈是带宽。
8.4 想达到 X tok/s, 需要什么芯片?
| 目标 tok/s | 需要带宽 | 需要算力 | systolic 阵列 | 面积 | 功耗 | 对标 |
|---|---|---|---|---|---|---|
| 10 | 270 GB/s | 1 | 24×24 | 5 mm² | 3 W | 边缘 NPU |
| 30 | 810 GB/s | 2 | 41×41 | 16 | 8 | 中端 |
| 50 | 1.35 TB/s | 3 | 52×52 | 27 | 14 | A100/昇腾 |
| 100 | 2.7 TB/s | 5 | 74×74 | 54 | 27 | A100/昇腾 |
| 200 | 5.4 TB/s | 11 | 104×104 | 108 | 54 | H100 |
8.5 关键公式
Decode 速度 (1 batch):
tok/s = 内存带宽(GB/s) / 27 (INT8, 内存瓶颈)
Prefill 速度 (大 batch):
tok/s = min( TOPS / 27G, 带宽 / 27G )
芯片规模:
TOPS ~ N^2 × 1 GHz × 1 op/cycle/PE
Area ~ N^2 × 0.01 mm^2/PE
Power ~ N^2 × 0.5 W (粗估)
8.6 为什么算力"浪费"?
Prefill 模式 (大 batch): 算力利用率高
tok/s = TOPS / (27 GFLOPs × batch) ← TOPS 主导
Decode 模式 (1 batch): 算力极度浪费
27 GOPs/token (INT8) ÷ 1 cycle = 27 GOPS 算力就够
但 100 TOPS 芯片做 decode 只用 0.027% 算力!
99.97% 的算力在等内存
现代 LLM 推理芯片的算力都是为 Prefill 准备的, Decode 几乎只用带宽。
8.7 业界参考
| 芯片 | 阵列规模 | 元素位宽 | 工艺 | 用途 |
|---|---|---|---|---|
| Google TPU v1 | 256×256 systolic | INT8 | 28nm | 推理 |
| Google TPU v2 | 128×128 systolic | BF16 | 16nm | 训练+推理 |
| Google TPU v4 | 多芯片 pod | INT8 | 7nm | 数据中心 |
| 华为昇腾 910 | 32×32×16 cube | INT8/FP16 | 7nm | 数据中心 |
| 寒武纪 MLU370 | 16× systolic | INT8 | 7nm | 推理 |
| Tesla Dojo | 大型 systolic 网格 | BF16/CFP8 | 7nm | 训练 |
全部是 systolic, 没有任何一个用方案 A/B/C 的纯并行。
脚本
llm27b_inference.py
9. 总结与展望
9.1 实验核心结论
| 阶段 | 结论 |
|---|---|
| 1. 译码器 | SOP + 晶体管估算方法可正常工作, NAND-NAND PLA 省 INV |
| 2. 小乘法器 | N=2 时 SOP 占优; N≥4 时结构化阵列必胜 (4.3× 起) |
| 3. 大乘法器 | 16×16 必须用结构化 (Wallace/Dadda), SOP 完全不可行 |
| 4. 延迟 | 阵列 O(N) vs 树形 O(log N), N=16 起树形开始赢 |
| 5. 矩阵模块 | 5 方案对比, systolic (D) 适合流式, 全并行 © 适合单次 |
| 6. LLM 推理 | 芯片速度瓶颈是 DRAM 带宽, 不是 TOPS |
9.2 从 1 个 PE 到 LLM 芯片的完整链路
PE (处理单元)
= 1 个 4×4 mult (512 T) + 1 个 8-bit 加法器 (224 T) + 8 个 FF (64 T) + 2 个 mux
≈ 800 T
4×4 systolic 阵列 (D 方案)
= 16 PE
≈ 13 000 T
32×32 systolic 阵列 (LLM 推理实际规模)
= 1024 PE
≈ 800 000 T
+ HBM 控制器
+ KV cache
+ Softmax / LayerNorm 单元
+ DMA 引擎
+ host 接口
完整 LLM 推理芯片
~ 100~300 mm² die + 4~8 个 HBM 堆栈
~ $30~100 单芯片成本
~ 27B Decode 速度: 50-150 tok/s
9.3 速度的"硬约束"
Decode 速度 (1 batch, 27B):
tok/s = 内存带宽(GB/s) / 27 GB
→ 速度翻倍 = 带宽翻倍 = HBM 堆数翻倍 = 成本 ~2×
Prefill 速度:
tok/s = min(TOPS, 带宽) / 27G
→ 算力和带宽共同决定
加速方法:
- INT4 量化 : 27GB → 13.5GB, 速度直接 ×2
- KV cache 优化 : 让 27B 实际加载量更少
- Speculative dec : 小模型猜 token, 速度 ×2-3
- 更大 HBM 堆栈 : 带宽翻倍
9.4 下一步工作建议
| 方向 | 内容 | 价值 |
|---|---|---|
| 1. PE Verilog | 实现 D 方案 4×4 systolic PE 的 RTL | 真正可综合 |
| 2. INT4 PE | 把 4-bit 量化加进 PE 设计 | 减半面积/带宽 |
| 3. Booth 编码 | 部分积数从 N 减到 N/2 | 再省 CSA 层级 |
| 4. Kogge-Stone CPA | 拆 1-bit FA + KS 加法器 | 完整延迟分析 |
| 5. 完整 27B 芯片 | 估 PE + KV + Softmax + DMA 总面积 | 接近流片规格 |
10. 附录: 实验脚本清单
| 文件 | 功能 | 状态 |
|---|---|---|
decoder3to8.py |
3-to-8 译码器 SOP + 晶体管估算 | OK |
mult3x3.py |
3×3 乘法器两级 SOP 完整分析 | OK |
mult4x4.py |
4×4 乘法器两级 SOP 完整分析 | OK |
mult8x8.py |
8×8 乘法器 (sympy 超时失败) | FAILED |
mult16x16.py |
16×16 乘法器结构化估算 | OK |
delay_compare.py |
阵列 vs Wallace 延迟对比 | OK |
matrix4x4.py |
4×4 矩阵乘法模块 5 方案对比 | OK |
llm27b_inference.py |
27B LLM 推理规模 vs 速度 | OK |
复用:
bash
cd D:\eda_llm
.venv\Scripts\python.exe <script>.py
11. 附录: 实验脚本源码
下面给出本次实验所有 8 个 Python 脚本的完整源码, 可直接复制运行。
11.1 decoder3to8.py --- 3-to-8 译码器
python
"""
3-to-8 decoder 晶体管估算
- 用 sympy.logic.SOPform 从真值表生成最简 SOP
- 按静态 CMOS 门数估算晶体管数
INV = 2T
NANDn = 2n T
NORn = 2n T
ANDn = NANDn + INV = 2n+2 T
ORn = NORn + INV = 2n+2 T
XOR2 = ~12 T (6T XNOR + 2T INV) 或 10T transmission-gate, 这里按 12T 算
"""
from sympy import symbols
from sympy.logic.boolalg import SOPform, simplify_logic
a, b, c = symbols('a b c') # 3 个输入
inputs = [a, b, c] # 符号顺序: a 是 MSB, c 是 LSB
def minterm(i):
"""生成真值表第 i 行的 dict: {a:bit2, b:bit1, c:bit0}, 输出为 1 当且仅当输入 == i"""
return {a: bool((i >> 2) & 1),
b: bool((i >> 1) & 1),
c: bool((i >> 0) & 1)}
print("=" * 70)
print("3-to-8 译码器真值表 (输入按 a b c 顺序, 1 = 高电平)")
print("=" * 70)
print(f"{'i':>3} {'a':>3} {'b':>3} {'c':>3} " + " ".join(f"Y{j}" for j in range(8)))
for i in range(8):
row = minterm(i)
outs = [1 if j == i else 0 for j in range(8)]
print(f"{i:>3} "
f"{int(row[a]):>3} {int(row[b]):>3} {int(row[c]):>3} "
+ " ".join(f" {y}" for y in outs))
print()
print("=" * 70)
print("每个输出 Yi 的最简 SOP (sympy.logic.SOPform)")
print("=" * 70)
expressions = []
total_lit = 0
for i in range(8):
mint = [i]
dontcares = []
expr = SOPform(inputs, mint, dontcares)
expr_s = simplify_logic(expr, form='dnf')
n_lit = sum(1 for s in expr_s.atoms() if s in inputs)
total_lit += n_lit
expressions.append((i, expr_s, n_lit))
print(f"Y{i} = {expr_s} (字面量数 = {n_lit})")
print()
print("=" * 70)
print("晶体管估算 (静态 CMOS, 假设输入端 a, a', b, b', c, c' 全部已就绪)")
print("=" * 70)
T_PER_AND3 = 8
transistors_outputs = 8 * T_PER_AND3
T_INPUT_INV = 3 * 2
T_OUTPUT_INV = 8 * 2
print(f"方案 A · active-high 输出 + 输入端有反变量 : {transistors_outputs} T")
print(f"方案 B · active-high 输出 + 输入端无反变量 : {T_INPUT_INV + transistors_outputs} T")
print(f"方案 C · active-low 输出 + 输入端无反变量 : {T_INPUT_INV + transistors_outputs - T_OUTPUT_INV} T")
print(f"全部 8 个输出合计字面量数 = {total_lit}")
11.2 mult3x3.py --- 3×3 乘法器
python
"""
3-bit × 3-bit 乘法器 (a * b = c)
- 输入: 6 bit (a0..a2, b0..b2), a 是 MSB
- 输出: 6 bit (c0..c5), c 是 LSB
- 真值表 2^6 = 64 行
- 用 sympy.logic.SOPform 求每个输出位的最简 SOP
- 估算静态 CMOS 晶体管数
"""
from sympy import symbols
from sympy.logic.boolalg import SOPform, simplify_logic
a = symbols('a0:3') # a[0]=LSB, a[2]=MSB
b = symbols('b0:3') # b0 LSB, b2 MSB
inputs = list(a) + list(b)
# ---------- 构造每个输出位的 minterm ----------
MINT = {k: [] for k in range(6)}
DC = {k: [] for k in range(6)}
for av in range(8):
for bv in range(8):
c = av * bv
# sympy 的位序: inputs 列表中最后一个变量对应 bit 0 (LSB)
a0, a1, a2 = av & 1, (av >> 1) & 1, (av >> 2) & 1
b0, b1, b2 = bv & 1, (bv >> 1) & 1, (bv >> 2) & 1
code = (a0 << 5) | (a1 << 4) | (a2 << 3) | (b0 << 2) | (b1 << 1) | b2
for k in range(6):
if (c >> k) & 1:
MINT[k].append(code)
def expr_stats(expr):
"""统计 SOP 中: 乘积项数 k, 字面量总数 L, 最大项宽 Wmax"""
expr = simplify_logic(expr, form='dnf', force=True)
from sympy import Or, And
if expr.func is Or:
terms = list(expr.args)
else:
terms = [expr]
k = len(terms)
L = 0
Wmax = 0
for t in terms:
if t.func is And:
lits = list(t.args)
else:
lits = [t]
L += len(lits)
if len(lits) > Wmax:
Wmax = len(lits)
return k, L, Wmax
def cmos_transistors(k, L):
return (2 * L) + (2 * k) + (2 * k) + 2
results = []
for k in range(6):
expr = SOPform(inputs, MINT[k], DC[k])
kk, LL, Wmax = expr_stats(expr)
T = cmos_transistors(kk, LL)
results.append((k, kk, LL, Wmax, T, expr))
print(f"c{k}: K={kk}, L={LL}, Wmax={Wmax}, T={T}")
print(f" SOP = {expr}")
total_T = sum(r[4] for r in results)
T_in_inv = 6 * 2
print(f"两级 SOP 总晶体管 (active-low) : {total_T} T")
print(f"两级 SOP 总晶体管 (active-high) : {total_T + T_in_inv} T")
11.3 mult4x4.py --- 4×4 乘法器
python
"""
4×4 乘法器 (a × b = c)
- 8 输入 (a0..a3, b0..b3), 8 输出 (c0..c7)
- 2^8 = 256 行
- 两级 SOP + 晶体管估算
"""
from sympy import symbols
from sympy.logic.boolalg import SOPform, simplify_logic
import time
def sop_cmos_t(k, L):
"""两级 SOP: k 个 AND 项 (总字面量 L) + 1 个 k-OR"""
return 2*L + 2*k + (2*k + 2)
N = 4
a = symbols(f'a0:{N}')
b = symbols(f'b0:{N}')
inputs = list(a) + list(b)
MINT = {k: [] for k in range(2*N)}
def bit_reverse(n, w):
"""N 位 n, 把 bit i 放到 bit (w-1-i)"""
r = 0
for i in range(w):
if (n >> i) & 1:
r |= 1 << (w - 1 - i)
return r
# 构建 minterm 集 (sympy 位序: inputs 列表最后变量对应 LSB)
for av in range(2**N):
for bv in range(2**N):
c = av * bv
# a0 放 bit (2N-1), a1 放 bit (2N-2), ..., bN-1 放 bit 0
code = (bit_reverse(av, N) << N) | bit_reverse(bv, N)
for k in range(2*N):
if (c >> k) & 1:
MINT[k].append(code)
# 最小化 + 统计
per_out = []
total_T = 0
t0 = time.time()
for k in range(2*N):
expr = SOPform(inputs, MINT[k], [])
expr = simplify_logic(expr, form='dnf', force=True)
from sympy import Or, And
if expr.func is Or:
terms = list(expr.args)
else:
terms = [expr]
L = sum(len(t.args) if t.func is And else 1 for t in terms)
K = len(terms)
Wmax = max((len(t.args) if t.func is And else 1) for t in terms)
T = sop_cmos_t(K, L)
per_out.append((K, L, Wmax, T))
total_T += T
print(f"c{k}: K={K}, L={L}, Wmax={Wmax}, T={T}")
print(f"\n最小化总耗时: {time.time()-t0:.1f}s")
T_inv_in = 2 * 2 * N
print(f"两级 SOP 晶体管合计: {total_T} T")
print(f"+ {2*N} 个输入反相器: {T_inv_in} T")
print(f"两级 CMOS 总 T (active-high): {total_T + T_inv_in} T")
print(f"两级 CMOS 总 T (active-low): {total_T} T")
# 对照: 阵列乘法器
N_full = N
T_AND_pp = N_full * N_full * 6
T_FAs = N_full * (N_full-1) * 28
T_HAs = 2 * N_full * 8
T_inv_str = 2 * 2 * N_full
T_array = T_AND_pp + T_FAs + T_HAs + T_inv_str
print(f"\n{N_full}x{N_full} 阵列乘法器 (结构化): {T_array} T")
11.4 mult8x8.py --- 8×8 乘法器 (超时失败版)
python
"""
8×8 乘法器, 两级 SOP - 逐位限时
- 16 输入 / 16 输出, 2^16=65536 行
- 每个输出位单独跑, 超时跳过
"""
from sympy import symbols
from sympy.logic.boolalg import SOPform, simplify_logic
import time, threading
T_INV = 2
def sop_cmos_t(k, L):
return 2*L + 2*k + (2*k + 2)
# 跨平台超时机制
def run_with_timeout(fn, args=(), kwargs=None, timeout=60):
result = [None]
exc = [None]
def target():
try:
result[0] = fn(*args, **(kwargs or {}))
except Exception as e:
exc[0] = e
th = threading.Thread(target=target, daemon=True)
th.start()
th.join(timeout)
if th.is_alive():
return None
if exc[0]:
raise exc[0]
return result[0]
N = 8
a = symbols(f'a0:{N}')
b = symbols(f'b0:{N}')
inputs = list(a) + list(b)
MINT = {k: [] for k in range(2*N)}
print(f"构建 16 输入 / 16 输出的真值表 ({2**(2*N)} 行) ...")
t0 = time.time()
for av in range(2**N):
for bv in range(2**N):
c = av * bv
bits = [(av>>i)&1 for i in range(N)] + [(bv>>i)&1 for i in range(N)]
code = 0
for j, bit in enumerate(bits):
code |= (bit << j)
for k in range(2*N):
if (c >> k) & 1:
MINT[k].append(code)
print(f" 构建耗时 {time.time()-t0:.1f}s\n")
TIMEOUT_PER = 90
print(f"最小化 --- 每个输出位最多 {TIMEOUT_PER}s\n")
per_out = [None]*16
for k in range(2*N):
n_mint = len(MINT[k])
def solve(kk=k):
expr = SOPform(inputs, MINT[kk], [])
expr = simplify_logic(expr, form='dnf', force=True)
from sympy import Or, And
if expr.func is Or:
terms = list(expr.args)
else:
terms = [expr]
L = sum(len(t.args) if t.func is And else 1 for t in terms)
K = len(terms)
Wmax = max((len(t.args) if t.func is And else 1) for t in terms)
return K, L, Wmax
t1 = time.time()
res = run_with_timeout(solve, timeout=TIMEOUT_PER)
dt = time.time() - t1
if res is None:
print(f"c{k}: minterms={n_mint} TIMEOUT ({dt:.1f}s)")
per_out[k] = None
else:
K, L, Wmax = res
T = sop_cmos_t(K, L)
per_out[k] = (n_mint, K, L, Wmax, T)
print(f"c{k}: minterms={n_mint} K={K} L={L} Wmax={Wmax} T={T} ({dt:.1f}s)")
done = [p for p in per_out if p is not None]
print(f"\n结果: {len(done)}/16 位在 {TIMEOUT_PER}s 内完成")
11.5 mult16x16.py --- 16×16 乘法器 (结构化估算)
python
"""
16×16 乘法器 (a × b = c) 晶体管估算
- 两级 SOP 不可行: 2^32 = 4.3e9 行真值表
- 用 2×2 / 3×3 / 4×4 看增长趋势
- 16×16 只做结构化估算 (array / Wallace / Dadda)
"""
from sympy import symbols
from sympy.logic.boolalg import SOPform, simplify_logic
import time
T_INV = 2
T_AND2 = 6 # NAND+INV
T_HA = 8 # 1-bit 半加器
T_FA = 28 # 1-bit 全加器 (经典 28T)
def sop_cmos_t(k, L):
return 2*L + 2*k + (2*k + 2)
def run_sop_mult(N):
a = symbols(f'a0:{N}')
b = symbols(f'b0:{N}')
inputs = list(a) + list(b)
MINT = {k: [] for k in range(2*N)}
for av in range(2**N):
for bv in range(2**N):
c = av * bv
bits = [(av>>i)&1 for i in range(N)] + [(bv>>i)&1 for i in range(N)]
code = 0
for j, bit in enumerate(bits):
code |= (bit << j)
for k in range(2*N):
if (c >> k) & 1:
MINT[k].append(code)
total = 0
per_out = []
for k in range(2*N):
expr = SOPform(inputs, MINT[k], [])
expr = simplify_logic(expr, form='dnf', force=True)
from sympy import Or, And
if expr.func is Or:
terms = list(expr.args)
else:
terms = [expr]
L = sum(len(t.args) if t.func is And else 1 for t in terms)
K = len(terms)
T = sop_cmos_t(K, L)
per_out.append((K, L, T))
total += T
return per_out, total
# 趋势: 2×2, 3×3, 4×4
print("两级 SOP 复杂度趋势 (2×2 / 3×3 / 4×4):")
for N in [2, 3, 4]:
per_out, total = run_sop_mult(N)
T_inv = 2 * N * 2
print(f" N={N}: 总 T={total}, +INV={T_inv}, 行数={2**(2*N)}")
# 16×16 结构化估算
N = 16
print(f"\n16×16 真值表: 2^{2*N} = {2**(2*N):.3e} 行")
print(f"存储需求: {2**(2*N)*32/8/1024/1024/1024:.1f} GB (不可行)")
# A. 阵列
T_AND_pp = N*N * T_AND2
T_FAs = N * (N-1) * T_FA
T_HAs = 2 * N * T_HA
T_inv_in = 2 * N * T_INV
T_array = T_AND_pp + T_FAs + T_HAs + T_inv_in
print(f"\n阵列 (ripple): {T_array} T ({N*N} AND + {N*(N-1)} FA + {2*N} HA)")
# B. Wallace 树
def wallace_fa_count(N):
pps = N * N
total_fa = 0
while pps > 2:
csas = pps // 3
rem = pps - 3*csas
total_fa += csas
pps = csas * 2 + rem
cpa_fa = 2*N - 1
return total_fa, cpa_fa
wallace_csa_fa, wallace_cpa_fa = wallace_fa_count(N)
T_wallace = N*N * T_AND2 + wallace_csa_fa * T_FA + wallace_cpa_fa * T_FA + T_inv_in
print(f"Wallace 树: {T_wallace} T ({wallace_csa_fa} CSA FA + {wallace_cpa_fa} CPA FA)")
# C. Dadda 树
dadda_csa_fa = wallace_csa_fa - 2*N
T_dadda = N*N * T_AND2 + dadda_csa_fa * T_FA + wallace_cpa_fa * T_FA + T_inv_in
print(f"Dadda 树: {T_dadda} T")
11.6 delay_compare.py --- 阵列 vs 树形延迟对比
python
"""
阵列 vs 树形 乘法器 延迟对比
- 一位全加器 FA 的延迟为 1 个单位时间 (T_FA)
- 阵列: 2N-1 级 FA
- Wallace: log_{1.5}(N^2) 级 CSA + 1 个 CPA
"""
import math
def array_delay(N):
return 1 + (2*N - 1)
def wallace_csa_levels(N):
pps = N * N
levels = 0
while pps > 2:
csas = pps // 3
rem = pps - 3*csas
pps = csas * 2 + rem
levels += 1
return levels
def cpa_delay(N_out, kind='ripple'):
if kind == 'ripple': return 2*N_out
if kind == 'kogsstone': return int(math.log2(N_out)) + 2
if kind == 'clsa': return int(math.sqrt(N_out))
raise ValueError(kind)
print("延迟对比 (单位: FA 延迟)")
print(f"{'N':<4}{'PPs':<6}{'阵列':<8}{'Wallace CSA':<14}{'+ripple CPA':<14}{'+Kogge-Stone':<14}{'速度比'}")
for N in [2, 3, 4, 8, 16, 32, 64, 128]:
pps = N*N
arr = array_delay(N)
wcsa = wallace_csa_levels(N)
w_ripple = wcsa + cpa_delay(2*N, 'ripple')
w_ks = wcsa + cpa_delay(2*N, 'kogsstone')
speedup = arr / w_ks
print(f"{N:<4}{pps:<6}{arr:<8}{wcsa:<14}{w_ripple:<14}{w_ks:<14}{speedup:.2f}x")
print("\n延迟公式:")
print(" 阵列乘法器 : T = 2N * T_FA (O(N))")
print(" Wallace 树 : T = log_{1.5}(N^2) * T_FA + T_CPA (O(log N))")
print(" Dadda 树 : T = 同 Wallace (略少 FA)")
print(" Booth+Wallace: T = (N/2) CSA 级 (再省一半)")
11.7 matrix4x4.py --- 4×4 矩阵乘法模块 5 方案
python
"""
4×4 矩阵乘法模块 (4-bit 元素, 输出 8-bit 累加)
C[i][j] = Σk A[i][k] × B[k][j]
"""
T_4x4_ARR = 512 # 4×4 阵列乘法器
T_FA = 28 # 1-bit 全加器
T_MUX2 = 6 # 2 选 1 mux
T_FF = 8 # 1-bit 寄存器
def adder_tree_4_8bit():
"""4 个 8-bit 数相加 (Wallace-like 树)"""
return 3 * 8 * T_FA
def pe_inner_product():
"""1 个内积单元: 4 个 4×4 mult + 1 个 4-输入 adder tree"""
mult = 4 * T_4x4_ARR
tree = adder_tree_4_8bit()
return mult + tree, mult, tree
# 方案 A: 1 个乘法器 + 累加器, 时间复用
def plan_A():
mult = T_4x4_ARR
acc = 8*T_FA + 8*T_FF + T_MUX2
area = mult + acc
cycles = 16
return ("A: 1 乘法器 + 累加器", area, cycles, 1/cycles, area*cycles)
# 方案 B: 4 乘法器 + 4 累加器
def plan_B():
mult = 4 * T_4x4_ARR
acc = 4 * (8*T_FA + 8*T_FF + T_MUX2)
area = mult + acc
return ("B: 4 乘法器 + 4 累加器", area, 4, 0.25, area*4)
# 方案 C: 16 乘法器 + 16 累加器
def plan_C():
mult = 16 * T_4x4_ARR
acc = 16 * (8*T_FA + 8*T_FF)
area = mult + acc
return ("C: 16 乘法器 + 16 累加器", area, 1, 1.0, area*1)
# 方案 D: 4×4 systolic 阵列
def plan_D():
pe = T_4x4_ARR + 8*T_FA + 8*T_FF + 2*T_MUX2
area = 16 * pe
return ("D: 4x4 systolic (16 PE)", area, 2*4-1, 1, area*(2*4-1))
# 方案 E: 16 乘法器 + 加法树
def plan_E():
mult = 16 * T_4x4_ARR
tree = 16 * adder_tree_4_8bit()
area = mult + tree
return ("E: 16 乘法器 + 加法树", area, 1, 1.0, area*1)
plans = [plan_A(), plan_B(), plan_C(), plan_D(), plan_E()]
print(f"{'方案':<35}{'面积 T':<10}{'延迟 cy':<10}{'吞吐':<12}{'AT':<12}")
print("-"*78)
for name, area, lat, thr, at in plans:
print(f"{name:<35}{area:<10}{str(lat):<10}{f'{thr}/cy':<12}{at:<12}")
# 方案 D 拆解
print("\n方案 D 拆解 (systolic):")
pe_area = T_4x4_ARR + 8*T_FA + 8*T_FF + 2*T_MUX2
print(f" 1 个 PE: 1 个 4x4 mult ({T_4x4_ARR}) + 1 个 8-bit 加法器 ({8*T_FA})")
print(f" + 8 个寄存器 ({8*T_FF}) + 2 个 mux ({2*T_MUX2}) = {pe_area} T")
print(f" 16 个 PE: {16*pe_area} T")
11.8 llm27b_inference.py --- 27B LLM 推理
python
"""
27B LLM 推理: 芯片规模 vs 推理速度
"""
import math
N_PARAMS = 27e9 # 27 billion params
PE_FREQ_GHZ = 1.0
PE_AREA_MM2 = 0.01
# 内存带宽 vs Decode 速度
print("27B Decode 速度 (INT8, 1 batch):")
print(f"{'带宽 GB/s':<14}{'读 27GB (ms)':<14}{'tok/s':<10}{'体感'}")
for bw in [50, 100, 200, 400, 800, 1600, 3200, 5000]:
model_bytes = N_PARAMS * 1.0
load_time_ms = model_bytes / (bw * 1e9) * 1000
tok_per_s = bw * 1e9 / model_bytes
feel = "勉强" if tok_per_s < 5 else ("打字机" if tok_per_s < 15 else ("流畅" if tok_per_s < 50 else "很快"))
print(f"{bw:<14}{load_time_ms:<14.1f}{tok_per_s:<10.1f}{feel}")
# 算力 vs Prefill 速度
print("\n27B Prefill 速度 (INT8, 1 batch):")
print(f"{'TOPS':<10}{'tok/s':<10}{'场景'}")
ops_per_token = N_PARAMS
for tops in [1, 10, 50, 100, 300, 600, 1000, 2000]:
tok_per_s = tops * 1e12 / ops_per_token
if tops < 50: scenario = "边缘"
elif tops < 500: scenario = "中等"
else: scenario = "数据中心"
print(f"{tops:<10}{tok_per_s:<10.0f}{scenario}")
# 主流芯片对照
chips = [
("Apple M2 Max", 0, 38, 430, 60, 400),
("TPU v2", 128, 45, 300, 200, 700),
("TPU v4", 128, 275, 650, 170, 1200),
("A100 (INT8)", 0, 624, 826, 300, 2000),
("H100 (INT8)", 0, 1979, 814, 700, 3350),
("理论 64x64", 64, 4, 40, 20, 200),
("理论 128x128", 128, 16, 160, 80, 500),
("理论 256x256", 256, 66, 640, 320, 1000),
]
print("\n芯片对照 (27B INT8 Decode):")
print(f"{'芯片':<16}{'阵列':<12}{'TOPS':<8}{'面积':<10}{'功耗':<8}{'带宽':<10}{'tok/s'}")
for name, arr, tops, area, pwr, bw in chips:
tok_per_s = bw * 1e9 / (N_PARAMS * 1)
pe_str = f"{arr}x{arr}" if arr > 0 else "(非 systolic)"
print(f"{name:<16}{pe_str:<12}{tops:<8}{area:<10}{pwr:<8}{bw:<10}{tok_per_s:.1f}")
# 想达到 X tok/s 需要什么芯片
print("\n想达到 X tok/s, 需要什么芯片?")
print(f"{'目标':<8}{'带宽':<10}{'算力':<10}{'阵列':<15}{'面积':<8}{'功耗':<8}{'对标'}")
for target_tps in [10, 30, 50, 100, 200]:
need_bw = target_tps * N_PARAMS * 1 / 1e9
need_tops = 2 * 27e9 * target_tps / 1e12
need_pe = int(need_tops * 1000)
side = math.sqrt(need_pe) if need_pe > 0 else 0
side_int = int(side) + 1 if side > 0 else 0
area_mm2 = need_pe * PE_AREA_MM2
power_w = area_mm2 * 0.5
if target_tps <= 10: similar = "edge NPU"
elif target_tps <= 30: similar = "midrange"
elif target_tps <= 100: similar = "A100/Ascend"
else: similar = "H100"
arr_str = f"{side_int}x{side_int}"
print(f"{target_tps:<8}{need_bw:<10.0f}{need_tops:<10.0f}{arr_str:<15}{area_mm2:<8.0f}{power_w:<8.0f}{similar}")
print("\n关键公式:")
print(" Decode 速度 (1 batch): tok/s = BW(GB/s) / 27 (INT8, 内存瓶颈)")
print(" Prefill 速度 (大 batch): tok/s = min(TOPS/27G, BW/27G)")
print(" 芯片规模: TOPS ~ N^2 x 1 GHz x 1 op/cycle/PE")
报告完成日期 : 实验 session 内
核心工具 : sympy 1.14.0
关键发现: LLM 推理芯片的瓶颈是 DRAM 带宽, 不是算力; systolic 阵列是工业标准