【IC】综合的具体过程示例

用一个很小但很典型的例子来展示如何从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_ff
  • if / else if / else
  • arithmetic:a+bq+1

我会分 4 步讲:

  1. RTL 原语义
  2. 依赖图
  3. mux/dff 图
  4. AIG 图

1. 先看 RTL 在"说什么"

这段 RTL 的含义是:

每个时钟上升沿更新 q

  • 如果 rst=1q_next = 0
  • 否则如果 en=1q_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=1x
  • sel=0y

所以:

  • 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+b
  • q+1

这些"代码形式",已经被抽成:

  • operator 节点:ADDINC
  • 选择节点:MUX
  • 状态节点:DFF

2.3 这一层最重要的观察

你在这一层已经能看出来:

组合依赖

q_next 依赖于:

  • rst
  • en
  • a
  • b
  • 当前的 q

时序依赖

q 不是直接组合算出来的,它来自:

  • q_next 经过一个 DFF

反馈

有一条反馈路径:

text 复制代码
q -> INC -> MUX -> MUX -> q_next -> DFF -> q

这条反馈跨一个周期,所以它是合法的时序反馈,不是组合环。


3. 第二步:从依赖图变成 mux/dff 图

这一步更接近综合前端做的事。

综合前端通常会把行为语义降成:

  • ADD
  • MUX
  • DFF

也就是一个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 carry0
  • inc_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+b
  • q+1
  • MUX(en, ..., ...)
  • MUX(rst, ..., ...)

第四步:把每个算子继续布尔化

  • ADD → XOR/AND/carry
  • MUX → AND/OR/NOT
  • OR/XOR → AND/NOT
  • 得到 AIG

相关推荐
迎风打盹儿17 天前
FPGA中if-else和case的理解:综合出来的电路真的会有优先级吗?
fpga·优先级·综合·case·if-else
cy4130263 个月前
后端综合的buf/inv/cellCount
综合
NobleGasex2 年前
可综合verilog用法总结
经验分享·笔记·芯片设计·综合
apple_ttt2 年前
FPGA时序分析与约束(6)——综合的基础知识
fpga开发·芯片设计·时序约束·综合