Lec 3用 Bluespec 描述组合电路
Lec 3 用 Bluespec 描述组合电路
TL;DR
- 组合电路硬件表达:Bluespec 函数在编译时完全内联展开为纯无环组合网表(DAG),代码中的所有
?:条件表达式均对应真实并行的物理多路选择器。 - 分层加法器构建:半加器通过 XOR/AND 提取和与进位,级联构成全加器;多位行波进位加法器(RCA)串联传播进位链,关键路径延迟随位宽 线性增长。
- 对数桶形移位器优化:固定移位仅为零开销金属走线,动态移位若采用全交叉开关需 个 MUX;分级桶形移位器(Barrel Shifter)按权重阶梯分解,将面积与延迟压缩至 与 。
算术计算与布尔组合映射
算术计算和布尔算术有很强的联系。数字可以用二进制(base 2)来表示,并且能对其进行算术运算。更进一步,二进制的算术运算和布尔代数中的逻辑运算之间存在一一对应关系,比如,二进制加法可以用 XOR 和 AND 实现(比如半加器、全加器)
因此,我们可以把所有的算术操作(像加、减、乘等)用布尔表达式来表示,而这些布尔表达式就可以被实现成组合逻辑电路(combinational circuits)
本讲导览
- 加法器
- 多路复用器
- 移位器
加法器实现

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

描述一个 32 位的加法器
半加器
| A | B | S | C |
|---|---|---|---|
| 0 | 0 | 0 | 0 |
| 0 | 1 | 1 | 0 |
| 1 | 0 | 1 | 0 |
| 1 | 1 | 0 | 1 |
布尔表达式为, 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)是处理包含低位进位运算的基础单元。在微架构上,一个全加器可以由两个半加器和一个或门级联构成:
- 第一级半加器:接收两个操作数位 和 ,计算本位局部和 及进位 。
- 第二级半加器:接收 与来自低位的输入进位 ,计算最终求和输出 及中间进位 。
- 输出进位合并:由于 与 在物理上互斥(若 ,则 ,第二级不可能再产生进位),最终进位输出可直接通过一个或门合并:。

// 全加器
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 位和与进位。微架构通过将两个全加器串联构成进位链:低位全加器的进位输出直接驱动高位全加器的进位输入,进位信号像波浪一样在各级全加器之间向高位传递。
行波进位加法器具有简洁规整的版图结构,但其关键路径延迟与操作数位宽 呈严格的线性正比关系(),每一级的求和都必须等待前级的进位信号完全稳定。

// 级联 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

踩坑:硬件没有“条件执行”。软件里
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

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 个两路多路复用器实现。
移位器
固定长度的移位操作在硬件中非常廉价,只需完成接线操作(纯布线操作)

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

逻辑右移 n 位
暴力法

假设我们想要实现一个把 x 左移 n 位()的动态移位器,该怎么做?
一个简单的方法,就是准备 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 位。

结构上来说,假如我们处理 8 位数据:
- shift by 1:通过 2:1 mux 决定是否左移 1 位
- shift by 2:再通过一层 mux 决定是否左移 2 位
- shift by 4:再通过一层 mux 决定是否左移 4 位
这样就可以组合出 0~7 的任何移位值 。一般地,对 位输入,移位量 用 个比特编码,每一位 控制“是否移 位”的一层 mux。
条件移位: 移位 vs. 不移位对比
我们需要一个多路复用器(mux)来从两个输入中选择合适的线路:如果选择信号s == 1,mux 会选择左边的线路,否则将选择右边的线路
成本对比(重点):
- 暴力法( 个固定移位器 + 一个 :1 大 mux):每个输出位需要 个 2:1 mux,共约 个 1 位 mux( 时约 992 个),即 。
- barrel shifter:只有 层,每层 个 1 位 mux,共 个( 时 160 个),即 。
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)中,语义机制发生根本性颠覆:
- 函数是编译期的物理空间宏内联(Spatial Inlining):
- 组合逻辑函数没有调用栈、没有程序计数器(PC)。调用一次
fa(...),编译器便在物理网表中直接例化生成一组真实的逻辑门与连线;在多处调用,硬件便在硅片不同坐标重复复制生成对应门电路。
- 组合逻辑函数没有调用栈、没有程序计数器(PC)。调用一次
- 条件分支对应全并发求值与物理 MUX 选择:
- 三元表达式
(s == 0) ? a : b在硬件中绝不意味着“先测 ,再求 或 ”。输入信号 和 的整个上游逻辑锥(Logic Cone)在每个时钟周期均在并发翻转并消耗动态功耗;多路选择器仅在最末级根据控制电压 选择通路。
- 三元表达式
- 空间复用与对数降维的架构美学:
- 习惯于软件的工程师极易写出 32 路平铺的暴力选择逻辑,在硅片上直接引爆 的多路复用器面积开销。桶形移位器(Barrel Shifter)利用二进制权重的幂次分解性质,将二维空间交叉网络转化为 级阶梯级联,在仅付出 门延迟的前提下将面积压低至 ,这正是计算机体系结构“将数学性质投影为物理空间拓扑”的典范设计。 :::