4. RTL中间表示:从AST到IR的转换

好,咱们继续往下走。上一章我们把Verilog代码解析成了AST,那玩意儿说白了就是一棵语法树。但EDA工具真正干活的时候,不会直接在这棵树上折腾——太慢了,也太啰嗦了。我们需要一个更紧凑、更适合做分析和优化的中间表示,也就是IR。

我个人习惯把IR比作「半成品」。它既保留了RTL的语义信息,又去掉了语法糖衣。你想想看,AST里还有括号、分号、begin...end这些玩意儿,IR里统统不要。我们要的是:信号怎么连、数据怎么流、控制怎么走。

核心观点:AST是给人看的,IR是给机器分析的。从AST到IR的转换,本质上是「去语法化」+「结构化」的过程。

4.1 从AST到IR:怎么转?

我在项目中遇到过最头疼的事,就是AST里嵌套了十几层的if-else,转IR时差点把自己绕进去。后来我总结了一套方法,分三步走:

  1. 遍历AST:深度优先,遇到什么节点就处理什么节点
  2. 构建符号表:把所有信号、寄存器、端口都登记在册
  3. 生成IR指令:每个RTL语句对应一组IR操作

举个例子,下面这段Verilog:

always @(posedge clk) begin
  if (rst)
    q <= 0;
  else
    q <= d;
end

转成IR后,大概长这样:

// IR指令序列
BLOCK: "always_clk"
  COND_BRANCH: rst, TARGET: label_rst, FALLTHROUGH: label_else
label_rst:
  ASSIGN: q = 0
  JUMP: label_end
label_else:
  ASSIGN: q = d
label_end:
  // 结束

看到了吗?begin...end没了,always关键字也没了,只剩下控制流和数据流。这就是IR的精髓。

4.2 IR的数据结构设计

嗯,这里要注意。IR的数据结构设计直接决定了后续优化的效率。我见过有人用C++的std::vector硬怼,结果每次插入删除都卡成PPT。我的建议是:

我的经验:用SSA(静态单赋值)形式的IR。每个变量只赋值一次,这样数据依赖关系一目了然。虽然生成时麻烦点,但后续优化会爽到飞起。

一个典型的IR节点(我习惯叫它IRInst)包含以下字段:

字段 类型 说明
opcode enum 操作码,比如ASSIGN、BRANCH、JUMP
dest VarID 目标变量(SSA版本号)
src1, src2 VarID 源操作数
next InstID 下一条指令(顺序执行时)
branch_target InstID 跳转目标(仅BRANCH指令)

你可能会问:「为什么不用指针?」因为IR要支持序列化和反序列化,用ID索引比指针安全得多。我曾经在调试时被野指针搞到崩溃,从那以后所有IR都用ID索引。

4.3 控制流图(CFG)的生成

控制流图,说白了就是「程序执行路径的地图」。每个基本块(Basic Block)是一段顺序执行的指令序列,块与块之间通过跳转连接。

生成CFG的步骤很简单:

  1. 把IR指令按基本块分组
  2. 找到每个基本块的入口和出口
  3. 根据跳转指令建立块之间的连接

我习惯用下面的SVG图来展示CFG的结构:

BB0: 入口 读取输入信号 BB1: if (rst) 条件判断 BB2: else分支 q = d BB3: 汇合点 q = 0 (来自BB1) BB4: 出口 写入寄存器

这个图里,BB0是入口,BB1做条件判断,然后分叉到BB2或BB3,最后汇合到BB4。每个基本块内部没有分支,这就是CFG的基本单元。

注意:CFG的生成一定要处理「不可达代码」。我曾经在项目中遇到过一个always块里写了if(0),结果优化器死活找不到那条路径,浪费了我两天时间。后来我在CFG生成阶段直接剪掉不可达分支,世界清净了。

4.4 数据流图(DFG)的生成

CFG管的是「怎么走」,DFG管的是「数据怎么算」。DFG把每个操作都展开成一张图,节点是运算,边是数据依赖。

举个例子,表达式 y = a + b * c 的DFG长这样:

a b c MUL b * c ADD a + (b*c) y

DFG的好处是:一眼就能看出哪些运算可以并行。比如上面的MUL和ADD,如果硬件资源够,完全可以同时算。这就是综合工具做调度的基础。

我的小技巧:生成DFG时,别忘了处理「反压」和「握手信号」。很多新手只关注数据流,忽略了控制信号,结果综合出来的电路时序一塌糊涂。我习惯在DFG里用虚线表示控制边,实线表示数据边,一目了然。

4.5 CFG与DFG的协同

你可能会问:「CFG和DFG,到底用哪个?」答案是:两个都要。CFG管控制,DFG管数据,两者结合才能完整描述一个RTL设计。

我常用的做法是:

  • 用CFG做路径分析(比如找关键路径)
  • 用DFG做数据依赖分析(比如找公共子表达式)
  • 两者结合做调度(比如确定哪些操作可以并行)

举个例子,一个状态机有10个状态,CFG能告诉你每个状态怎么跳转,DFG能告诉你每个状态里数据怎么算。两者一结合,综合器就知道:状态A和状态B可以合并吗?状态C里的加法能不能提前算?

总结一下:AST是原材料,IR是半成品,CFG和DFG是加工图纸。没有好的IR,后面的优化全是空中楼阁。我见过太多人一上来就搞高级优化,结果IR设计得一塌糊涂,最后全白干。记住:基础不牢,地动山摇。

好了,这一章的内容就到这儿。IR的设计是个细活,多花点时间在上面,后面你会感谢自己的。


公众号:蓝海资料掘金营,微信deep3321