Archipelago

Lec 3用 Bluespec 描述组合电路

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

Lec 3 用 Bluespec 描述组合电路

TL;DR

  • 组合电路硬件表达:Bluespec 函数在编译时完全内联展开为纯无环组合网表(DAG),代码中的所有 ?: 条件表达式均对应真实并行的物理多路选择器。
  • 分层加法器构建:半加器通过 XOR/AND 提取和与进位,级联构成全加器;多位行波进位加法器(RCA)串联传播进位链,关键路径延迟随位宽 O(N)O(N) 线性增长。
  • 对数桶形移位器优化:固定移位仅为零开销金属走线,动态移位若采用全交叉开关需 O(N2)O(N^2) 个 MUX;分级桶形移位器(Barrel Shifter)按权重阶梯分解,将面积与延迟压缩至 O(Nlog⁡N)O(N \log N) 与 O(log⁡N)O(\log N)。

算术计算与布尔组合映射

算术计算和布尔算术有很强的联系。数字可以用二进制(base 2)来表示,并且能对其进行算术运算。更进一步,二进制的算术运算和布尔代数中的逻辑运算之间存在一一对应关系,比如,二进制加法可以用 XOR 和 AND 实现(比如半加器、全加器)

因此,我们可以把所有的算术操作(像加、减、乘等)用布尔表达式来表示,而这些布尔表达式就可以被实现成组合逻辑电路(combinational circuits)

本讲导览

  • 加法器
  • 多路复用器
  • 移位器

加法器实现

加法器层次化设计:从半加器到全加器及多位级联加法器架构

加法器的组合逻辑:

  • 半加器(Half addr):将两个输入,两个输出,其中一个是 sum、另外一个是 carry
  • 全加器(Full addr): 三个输入(多了一个 carry 位),产生两个输出。
    • 可以由两个半加器组合成
  • 级联全加器可以执行二进制加法

32 位加法器级联全加器数据通路结构示意图

描述一个 32 位的加法器

半加器

ABSC
0000
0110
1010
1101

布尔表达式为, s = ~a·b + a·(~b) = a ⊕ b(异或),c = a · b

电路组合逻辑为

半加器门级逻辑结构图(包含异或门产生和值与与门产生进位)

function Bit#(2) ha(Bit#(1) a, Bit#(1) b);
    Bit#(1) s = a ^ b;
    Bit#(1) c = a & b;
  	return {c, s};
endfunction

Bit#(2) t = ha(1, 0);
%%eval t[0]
%%eval t[1]

其中 {c, s} 表示比特的连接。注意输出t[0] = 'h1,t[0] = 'h0

全加器

全加器(Full Adder)是处理包含低位进位运算的基础单元。在微架构上,一个全加器可以由两个半加器和一个或门级联构成:

  1. 第一级半加器:接收两个操作数位 aa 和 bb,计算本位局部和 S1=a⊕bS_1 = a \oplus b 及进位 C1=a⋅bC_1 = a \cdot b。
  2. 第二级半加器:接收 S1S_1 与来自低位的输入进位 cinc_{in},计算最终求和输出 S=S1⊕cinS = S_1 \oplus c_{in} 及中间进位 C2=S1⋅cinC_2 = S_1 \cdot c_{in}。
  3. 输出进位合并:由于 C1C_1 与 C2C_2 在物理上互斥(若 a=b=1a=b=1,则 S1=0S_1=0,第二级不可能再产生进位),最终进位输出可直接通过一个或门合并:cout=C1∨C2c_{out} = C_1 \lor C_2。

由两个半加器和一个或门级联组成的全加器逻辑原理图

// 全加器
function Bit#(2) fa(Bit#(1) a, Bit#(1) b, Bit#(1) c_in);
    Bit#(2) ab = ha(a, b);
    Bit#(2) abc = ha(ab[0], c_in);
    Bit#(1) c_out = ab[1] | abc[1];
    return {c_out, abc[0]};
endfunction

Let 语法与类型推断

Bluespec 是强类型语言,但在局部变量声明时,当编译器能从右侧表达式推断位宽与类型时,可以使用 let 关键字简化代码:

// 使用 let 语法简化的全加器
function Bit#(2) fa(Bit#(1) a, Bit#(1) b, Bit#(1) c_in);
    let ab = ha(a, b);
    let abc = ha(ab[0], c_in);
    let c_out = ab[1] | abc[1];
    return {c_out, abc[0]};
endfunction

2-bit 行波进位加法器

2 位行波进位加法器(2-bit Ripple-Carry Adder)实现两个 2 位二进制数的加法,输出 2 位和与进位。微架构通过将两个全加器串联构成进位链:低位全加器的进位输出直接驱动高位全加器的进位输入,进位信号像波浪一样在各级全加器之间向高位传递。

行波进位加法器具有简洁规整的版图结构,但其关键路径延迟与操作数位宽 NN 呈严格的线性正比关系(O(N)O(N)),每一级的求和都必须等待前级的进位信号完全稳定。

2 位行波进位加法器(RCA)级联全加器信号连线图

// 级联 FA 实现:紧凑写法
function Bit#(3) add2(Bit#(2) x, Bit#(2) y);
    let t = fa(x[0], y[0], 'h0);
    let t2 = fa(x[1], y[1], t[1]);
    return {t2[1], t2[0], t[0]};
endfunction

// 级联 FA 实现:显式位切片与连线映射
function Bit#(3) add2(Bit#(2) x, Bit#(2) y);
    Bit#(2) s = 0;
    Bit#(3) c = 0;
    c[0] = 0;
    let cs0 = fa(x[0], y[0], c[0]);
    s[0] = cs0[0];
    c[1] = cs0[1];
    let cs1 = fa(x[1], y[1], c[1]);
    s[1] = cs1[0];
    c[2] = cs1[1];
    return {c[2], s};
endfunction

多比特字赋值规则

整体赋值

Bit#(3) c = 0;

部分赋值

c[0] = c0;

初始化要求:多比特字的每一位都必须有初始值。使用未初始化的比特会:

  • 编译器发出警告 ⚠️
  • 程序行为不可预测 ❗️

多路复用器

背景: 选择某一位x[i]

假设x是 4-bit 宽,若使用常量选择器(如 x[2])这仅仅是引用了一个具体的线网,并不生成额外的硬件;当使用变量选择器(如 x[i])时,生成的硬件通常是一个多路选择器(例如 4 位宽的 x 就会生成一个 4 路 mux),因为它需要在运行时动态决定选哪一位。

位选择操作在硬件中的连线与多路复用器映射对比示意图

2 路多用复用器

对于 bluespec

(s == 0)? a: b;

对于 python

a if s == 0 else b

2 选 1 多路选择器(2:1 MUX)逻辑符号与内部门级实现图

踩坑:硬件没有“条件执行”。软件里 s ? foo(x) : bar(y) 会先算 s,再只执行其中一个分支(以避免做无用的昂贵计算)。但在组合硬件里做不到这一点——foo(x) 和 bar(y) 两个电路都会被实例化并并行求值,mux 只是最后在两个已经算好的结果中选一个。因此 ?:、if、case 在组合逻辑里统统综合成 mux,所有分支的硬件都真实存在。(真正的“条件执行”要到时序逻辑才有,见 lec6。)

如果你构造一个 2 路多路复用器,用于两个 n 位信号 a 和 b 之间进行选择,那么这个多路复用器的硬件实际上会被 按位复制 n 次。

举个例子,

Bit#(4) a = 4'b1010;
Bit#(4) b = 4'b1100;
Bit#(1) s = 1'b1;
Bit#(4) out = s == 0 ? a: b;

背后其实是 4 个 1-bit mux

out[0] = s? b[0]: a[0];
out[1] = s? b[1]: a[1];
out[2] = s? b[2]: a[2];
out[3] = s? b[3]: a[3];

每一位都独立地选择,但共享相同的选择信号 s

4 路多路复用器

case ({s1, s0})
  0: a;
  1: b;
  2: c;
  3: d;
endcase

由 2 选 1 MUX 树状级联构成的 4 选 1 多路选择器结构图

def mux(a, b, s):
  if s == 0:
    return a
  elif s == 1:
    return b
  elif s == 2:
    return c
  else:
    return d

n 路多路复用器可以用 n-1 个两路多路复用器实现。

移位器

固定长度的移位操作在硬件中非常廉价,只需完成接线操作(纯布线操作)

固定逻辑右移与左移在硬件中的纯金属走线无损实现示意图

循环移位和算术移位也差不多。循环移位就是把最高位接回最低位即可,仍然是接线;算术右移为例,保留符号位,最左边补原来的符号位,其他仍然通过布线完成。算术移位对于,乘除2n2^n的操作非常方便。

算术右移符号位扩展与循环移位接线逻辑图

逻辑右移 n 位

暴力法

动态移位器暴力解法:全交叉大 MUX 选择所有可能移位结果示意图

假设我们想要实现一个把 x 左移 n 位(0≤n≤310 \le n \le 31)的动态移位器,该怎么做?

一个简单的方法,就是准备 32 个移位器,通过 n 路的多路复用器连接。

这种方式需要多少个 2 路 1 位的多路复用器实现该结构?

Solution: n * (n-1)。 意思是把所有可能的移位结果(不需要 mux,只要移位连线)都准备好,然后用 n 路的大 mux 选择,对于每一位而言,例如 y[0], 可能来自 x[0], x[1]...x[7]中的任何一个,换句话说,需要 8:1mux,因此最终结果就是 n * (n-1) 个 mux。

分级移位(barrel)法

我们把 "移 k 位" 拆成几个固定移位的“步骤”,每个步骤只处理一种移位大小(比如移 1 位、移 2 位、移 4 位……),为什么可行? 任何整数 k 都可以分解为 2 的幂的和(,例如移位 x 5 位 ,5 的二进制是101,所以先左移 4 位,再左移 1 位。

分级桶形移位器(Barrel Shifter)按 2 的幂阶梯分级结构图

结构上来说,假如我们处理 8 位数据:

  1. shift by 1:通过 2:1 mux 决定是否左移 1 位
  2. shift by 2:再通过一层 mux 决定是否左移 2 位
  3. shift by 4:再通过一层 mux 决定是否左移 4 位

这样就可以组合出 0~7 的任何移位值 。一般地,对 NN 位输入,移位量 ss 用 log⁡2N\log_2 N 个比特编码,每一位 s[i]s[i] 控制“是否移 2i2^i 位”的一层 mux。

条件移位: 移位 vs. 不移位对比

我们需要一个多路复用器(mux)来从两个输入中选择合适的线路:如果选择信号s == 1,mux 会选择左边的线路,否则将选择右边的线路

成本对比(重点):

  • 暴力法(NN 个固定移位器 + 一个 NN:1 大 mux):每个输出位需要 N−1N{-}1 个 2:1 mux,共约 N(N−1)N(N{-}1) 个 1 位 mux(N=32N{=}32 时约 992 个),即 Θ(N2)\Theta(N^2)。
  • barrel shifter:只有 log⁡2N\log_2 N 层,每层 NN 个 1 位 mux,共 Nlog⁡2NN\log_2 N 个(N=32N{=}32 时 160 个),即 Θ(Nlog⁡N)\Theta(N\log N)。

N=4 的 Minispec barrel shifter 示例:

function Bit#(4) barrelShifter(Bit#(4) x, Bit#(2) s);
  Bit#(4) r1 = (s[1] == 0) ? x : {2'b00, x[3:2]};  // 是否右移 2 位
  Bit#(4) r0 = (s[0] == 0) ? r1 : {1'b0, r1[3:1]}; // 是否再右移 1 位
  return r0;
endfunction

核心机制思考与底层洞察

在通用软件编程中,函数调用是在时间维度上复用 CPU 算力,而条件控制流(if-else)通过跳转分支绕过不必要的计算;然而在组合逻辑硬件描述语言(如 Bluespec / Minispec)中,语义机制发生根本性颠覆:

  1. 函数是编译期的物理空间宏内联(Spatial Inlining):
    • 组合逻辑函数没有调用栈、没有程序计数器(PC)。调用一次 fa(...),编译器便在物理网表中直接例化生成一组真实的逻辑门与连线;在多处调用,硬件便在硅片不同坐标重复复制生成对应门电路。
  2. 条件分支对应全并发求值与物理 MUX 选择:
    • 三元表达式 (s == 0) ? a : b 在硬件中绝不意味着“先测 ss,再求 aa 或 bb”。输入信号 aa 和 bb 的整个上游逻辑锥(Logic Cone)在每个时钟周期均在并发翻转并消耗动态功耗;多路选择器仅在最末级根据控制电压 ss 选择通路。
  3. 空间复用与对数降维的架构美学:
    • 习惯于软件的工程师极易写出 32 路平铺的暴力选择逻辑,在硅片上直接引爆 O(N2)O(N^2) 的多路复用器面积开销。桶形移位器(Barrel Shifter)利用二进制权重的幂次分解性质,将二维空间交叉网络转化为 log⁡2N\log_2 N 级阶梯级联,在仅付出 O(log⁡N)O(\log N) 门延迟的前提下将面积压低至 O(Nlog⁡N)O(N \log N),这正是计算机体系结构“将数学性质投影为物理空间拓扑”的典范设计。 :::