用一个很小但很典型的例子来展示如何从RTL到AIG
我选这个 RTL:
systemverilog
module demo (
input logic clk,
input logic rst,
input logic en,
input logic [1:0] a,
input logic [1:0] b,
output logic [1:0] q
);
always_ff @(posedge clk) begin
if (rst)
q <= 2'b00;
else if (en)
q <= a + b;
else
q <= q + 2'b01;
end
endmodule
这个例子同时有:
always_ffif / else if / else- arithmetic:
a+b和q+1
我会分 4 步讲:
- RTL 原语义
- 依赖图
- mux/dff 图
- AIG 图
1. 先看 RTL 在"说什么"
这段 RTL 的含义是:
每个时钟上升沿更新 q:
- 如果
rst=1,q_next = 0 - 否则如果
en=1,q_next = a+b - 否则,
q_next = q+1
所以它本质上是在描述一个next-state function:
q(t+1)= \\begin{cases} 0, \& rst=1 a+b, \& rst=0 \\land en=1 q+1, \& rst=0 \\land en=0 \\end{cases}
这里第一件最重要的事就是:
always_ff 会被拆成两部分
- 组合逻辑 :计算
q_next - 时序元件 :
q <= q_next的 DFF
也就是:
text
q_next = F(rst, en, a, b, q)
q = DFF(q_next, clk)
2. 第一步:变成"依赖图"
先不急着变门级。
我们先把这段代码的"值依赖关系"抽出来。
2.1 先定义几个中间节点
为了更清楚,给它起中间名:
text
n1 = a + b
n2 = q + 1
n3 = MUX(en, n1, n2)
n4 = MUX(rst, 2'b00, n3)
q_next = n4
q = DFF(q_next)
这里的 MUX(sel, x, y) 表示:
sel=1选xsel=0选y
所以:
n1是"加法结果"n2是"自增结果"n3是"en 控制下二选一"n4是"rst 控制下二选一"q_next是最终下个周期要写入的值
2.2 依赖图长这样
text
a ----\
+---- [ADD] ---- n1 ---\
b ----/ \
+---- [MUX en] ---- n3 ---\
q -------- [INC by 1] --- n2 --/ \
+---- [MUX rst] ---- q_next ---- [DFF] ---- q
rst ----------------------------------------------------/ ^
|
|
+------ current state
这就是你说的"逻辑依赖层"。
这里已经发生了什么?
原来 RTL 里:
if / else if / else<=a+bq+1
这些"代码形式",已经被抽成:
- operator 节点:
ADD、INC - 选择节点:
MUX - 状态节点:
DFF
2.3 这一层最重要的观察
你在这一层已经能看出来:
组合依赖
q_next 依赖于:
rstenab- 当前的
q
时序依赖
q 不是直接组合算出来的,它来自:
q_next经过一个 DFF
反馈
有一条反馈路径:
text
q -> INC -> MUX -> MUX -> q_next -> DFF -> q
这条反馈跨一个周期,所以它是合法的时序反馈,不是组合环。
3. 第二步:从依赖图变成 mux/dff 图
这一步更接近综合前端做的事。
综合前端通常会把行为语义降成:
ADDMUXDFF
也就是一个RTL-level netlist
3.1 先把 if / else if / else 改写成嵌套 mux
原 RTL:
systemverilog
if (rst)
q <= 0;
else if (en)
q <= a + b;
else
q <= q + 1;
等价于:
text
q_next = rst ? 0 : (en ? (a+b) : (q+1))
这就是标准的 mux 形式。
3.2 画成更规范的结构图
text
+------------------+
a[1:0] ---------->| |
| ADD |---- sum_ab[1:0] ----\
b[1:0] ---------->| (a + b) | |
+------------------+ |
v
+------------------+ +----------+
q[1:0] ---------->| |---- inc_q ---->| |
| ADD/INC | | MUX en |---- sel_en_out ----\
2'b01 --->| (q + 1) | | | |
+------------------+ +----------+ |
v
+-----------+
2'b00 -------------------------------------------------------------------------->| |
rst ---------------------------------------------------------------------------->| MUX rst |---- q_next[1:0]
sel_en_out --------------------------------------------------------------------->| |
+-----------+
+-----------+
clk ---------------------------------------------------------------------------->| DFF |---- q[1:0]
q_next ------------------------------------------------------------------------->| |
+-----------+
这里你已经很像在看综合器内部网表了。
4. 第三步:把 arithmetic 展开成布尔逻辑
现在还不是 AIG。
因为 ADD 这种节点太高级了。
AIG 只喜欢两种东西:
- 2 输入 AND
- 反相(NOT,通常附着在边上)
所以先要把加法器拆成 bit-level 布尔网络。
4.1 先展开 a + b
因为 a,b 都是 2 位:
text
a = a1 a0
b = b1 b0
最低位
sum_ab_0 = a_0 \\oplus b_0
carry_0 = a_0 \\land b_0
最高位
sum_ab_1 = a_1 \\oplus b_1 \\oplus carry_0
这里因为结果也是 2 位,所以溢出 carry 我们先不管。
4.2 再展开 q + 1
q+1 其实也是一个 2 位加法器,只不过第二个输入固定是 01。
设当前状态 q = q1 q0
最低位
inc_q_0 = q_0 \\oplus 1 = \\lnot q_0
carry_inc_0 = q_0 \\land 1 = q_0
最高位
inc_q_1 = q_1 \\oplus 0 \\oplus q_0 = q_1 \\oplus q_0
所以:
text
inc_q[0] = ~q0
inc_q[1] = q1 XOR q0
4.3 再展开 mux
一个 1-bit mux:
y = s ? x : z = (s \\land x) \\lor (\\lnot s \\land z)
所以 2-bit mux 就是每一位各做一次。
sel_en_out = en ? sum_ab : inc_q
对每一位 i:
sel_en_out_i = (en \\land sum_ab_i) \\lor (\\lnot en \\land inc_q_i)
q_next = rst ? 0 : sel_en_out
因为 0 是常量,所以:
q_next_i = (\\lnot rst) \\land sel_en_out_i
这个特别好,因为 rst ? 0 : x 直接简化成 ~rst & x。
5. 第四步:再把布尔逻辑统一成 AIG
现在还有:
- XOR
- OR
- AND
- NOT
但 AIG 只要:
- AND
- NOT
所以继续分解。
5.1 XOR 怎么变成 AND/NOT
一个常见写法:
x \\oplus y = (x \\land \\lnot y) \\lor (\\lnot x \\land y)
再把 OR 用德摩根变掉:
p \\lor q = \\lnot(\\lnot p \\land \\lnot q)
所以 XOR 最终也能只用 AND/NOT。
5.2 OR 怎么变成 AND/NOT
p \\lor q = \\lnot(\\lnot p \\land \\lnot q)
5.3 mux 也会自然变成 AND/NOT
因为 mux 本来就是:
(s \\land x) \\lor (\\lnot s \\land z)
继续套上德摩根即可。
6. 现在我们手工把其中一位真正写成 AIG 风格
为了别把图画炸,我重点画 q_next[0]。
6.1 先写出 sum_ab[0]
sum_ab_0 = a_0 \\oplus b_0
拆成:
t1 = a_0 \\land \\lnot b_0
t2 = \\lnot a_0 \\land b_0
sum_ab_0 = t1 \\lor t2 = \\lnot(\\lnot t1 \\land \\lnot t2)
6.2 写出 inc_q[0]
inc_q_0 = \\lnot q_0
这个很简单,就是一条反相边。
6.3 写出 sel_en_out[0]
sel_en_out_0 = (en \\land sum_ab_0) \\lor (\\lnot en \\land inc_q_0)
设:
t3 = en \\land sum_ab_0
t4 = \\lnot en \\land inc_q_0
sel_en_out_0 = \\lnot(\\lnot t3 \\land \\lnot t4)
6.4 写出 q_next[0]
q_next_0 = \\lnot rst \\land sel_en_out_0
这已经是纯 AND/NOT 了。
7. 画出 q_next[0] 的 AIG 图
注意:AIG 里通常把反相画在边上,这里我用 ~ 表示反相输入。
text
a0 ----+----[AND]---- t1 ----\
~b0 \
+----[AND with inverted inputs]---- sum_ab0
~a0 ---+----[AND]---- t2 ----/ (implements OR via DeMorgan)
b0
en -----------+----[AND]---- t3 ----\
sum_ab0 \
+----[AND with inverted inputs]---- sel_en_out0
~en ----------+----[AND]---- t4 ----/ (implements OR via DeMorgan)
~q0
~rst ---------+----[AND]----------------------------------------------- q_next0
sel_en_out0
如果严格写成"只有 AND 节点、边可反相"的 AIG 语气,就是:
t1 = AND(a0, ~b0)t2 = AND(~a0, b0)sum_ab0 = ~AND(~t1, ~t2)t3 = AND(en, sum_ab0)t4 = AND(~en, ~q0)sel_en_out0 = ~AND(~t3, ~t4)q_next0 = AND(~rst, sel_en_out0)
这就是 AIG 了。
8. q_next[1] 也类似,只是更复杂
因为 q_next[1] 里面会包含:
sum_ab[1] = a1 XOR b1 XOR carry0inc_q[1] = q1 XOR q0
所以它也会继续拆成更多的 AND/NOT 节点。
你可以把它理解成:
- bit0 比较简单
- bit1 多了 carry 传播
- 位宽越大,AIG 图就越大
9. 把全过程压成一张图
RTL
systemverilog
always_ff @(posedge clk) begin
if (rst)
q <= 0;
else if (en)
q <= a + b;
else
q <= q + 1;
end
↓
依赖图
text
n1 = a + b
n2 = q + 1
n3 = MUX(en, n1, n2)
n4 = MUX(rst, 0, n3)
q_next = n4
q = DFF(q_next)
↓
mux/dff 图
text
ADD(a,b) --->\
MUX(en) ---> MUX(rst) ---> q_next ---> DFF ---> q
INC(q,1) --->/
↓
bit-level 布尔图
text
ADD -> XOR/AND/carry 网络
MUX -> AND/OR/NOT 网络
DFF -> 状态边界
↓
AIG 图
text
所有组合逻辑都只剩:
- 2-input AND
- 反相边
DFF 保留为时序边界
10. 你应该抓住的真正重点
这个例子里,最关键的不是最后 AIG 怎么画,而是:
A. always_ff 本质会变成 next-state logic + DFF
这是一切时序逻辑综合的核心模式。
B. if/else 不会神秘消失,而是变成 mux 树
所以综合前端在"逻辑依赖层"里,实际上非常像在构造:
- 算子节点
- 选择节点
- 状态节点
C. arithmetic 会先变成 bit-level operator network
比如:
- 加法器 → XOR + carry
- 比较器 → bitwise compare tree
- 减法器 → 加法器变体
D. AIG 不是为了"更接近 RTL",而是为了"更适合布尔优化"
到 AIG 这一步时,很多高层语义已经被压平了。
所以如果你以后想做"自动插拍/物理感知 RTL 变换",重点不该放在 AIG 层,而应该放在:
mux/dff/operator graph 这一层。
因为只有这一层还保留着:
- stage 边界
- 数据通路
- 控制选择关系
- 反馈路径
11. 最后我再给你一个"脑内口诀"
看到一段时序 RTL,你可以脑内自动做这 4 步:
第一步:找状态
谁是寄存器输出?
这里是 q
第二步:写 next-state function
q_next = rst ? 0 : (en ? a+b : q+1)
第三步:拆成算子和 mux
a+bq+1MUX(en, ..., ...)MUX(rst, ..., ...)
第四步:把每个算子继续布尔化
- ADD → XOR/AND/carry
- MUX → AND/OR/NOT
- OR/XOR → AND/NOT
- 得到 AIG