AST 可以先粗暴理解成:

“把 Verilog 源码从字符串,变成一棵有层次的语法树。”

它还很像原始代码结构,还没变成门级网表,也还没变成 mux/dff 图。
在 Yosys 里,Verilog frontend 会先把源码变成内部 AST;这个 AST 由很多 AST::AstNode 组成,每个节点至少有:节点类型、子节点、属性、节点内容、源代码位置。Yosys 文档还明确说,这个 AST “closely resembles the structure of the original Verilog code”。([yosyshq.readthedocs.io][1])

你可以把它理解成下面三层区别:

  • 源码:给人看的文本
  • AST:给编译器/综合前端看的“语法树”
  • RTLIL / 网表:更像电路结构的数据表示

也就是说,AST 的重点不是“这个电路怎么实现”,而是“这段 Verilog 语法上是什么意思”。后面 simplifier 和 RTLIL generator 才会继续把它降成更接近电路的表示。Yosys 也明确写了:完整 AST 太复杂,不能直接转 RTLIL,要先 simplify。([yosyshq.readthedocs.io][1])


先看一个最简单的例子

源码:

module m(input a, input b, output y);
    assign y = a & b;
endmodule

它对应的 AST,概念上大概像这样:

AST_DESIGN
└── AST_MODULE  ("m")
    ├── AST_WIRE ("a")   // input
    ├── AST_WIRE ("b")   // input
    ├── AST_WIRE ("y")   // output
    └── AST_ASSIGN
        ├── AST_IDENTIFIER ("y")
        └── AST_BIT_AND
            ├── AST_IDENTIFIER ("a")
            └── AST_IDENTIFIER ("b")

这里每个节点都对应一种 Verilog 构造。
Yosys 文档里列得很清楚,比如:

  • AST_MODULE 对应 module
  • AST_WIRE 对应 input/output/wire/reg
  • AST_ASSIGN 对应连续赋值 assign
  • AST_IDENTIFIER 对应标识符
  • AST_BIT_AND 对应按位与 &
  • AST_ADD 对应 +
  • AST_ALWAYS 对应 always
  • AST_ASSIGN_LE 对应非阻塞赋值 <=
  • AST_CASE / AST_COND / AST_DEFAULT 对应 case/条件分支。([yosyshq.readthedocs.io][1])

这个例子里你能看出 AST 的味道:

  • 它保留了“assign y = ...”这种语法层次
  • 右边表达式 a & b 还是一个表达式树
  • 还没有“门”“网表”“布尔优化”这些概念

再看一个更像你关心的时序例子

源码:

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_ffalways 的语法差异,只看“AST 长什么样”。概念上它会像这样:

AST_DESIGN
└── AST_MODULE ("demo")
    ├── AST_WIRE ("clk")
    ├── AST_WIRE ("rst")
    ├── AST_WIRE ("en")
    ├── AST_WIRE ("a")
    ├── AST_WIRE ("b")
    ├── AST_WIRE ("q")
    └── AST_ALWAYS
        ├── AST_POSEDGE
        │   └── AST_IDENTIFIER ("clk")
        └── AST_BLOCK
            └── AST_CASE        // if/else 在内部常常会统一成 case/cond 风格
                ├── AST_COND
                │   ├── AST_IDENTIFIER ("rst")
                │   └── AST_ASSIGN_LE
                │       ├── AST_IDENTIFIER ("q")
                │       └── AST_CONSTANT ("2'b00")
                └── AST_DEFAULT
                    └── AST_CASE
                        ├── AST_COND
                        │   ├── AST_IDENTIFIER ("en")
                        │   └── AST_ASSIGN_LE
                        │       ├── AST_IDENTIFIER ("q")
                        │       └── AST_ADD
                        │           ├── AST_IDENTIFIER ("a")
                        │           └── AST_IDENTIFIER ("b")
                        └── AST_DEFAULT
                            └── AST_ASSIGN_LE
                                ├── AST_IDENTIFIER ("q")
                                └── AST_ADD
                                    ├── AST_IDENTIFIER ("q")
                                    └── AST_CONSTANT ("2'b01")

这个树里最值得你注意的是:

  1. always 还是一个 AST_ALWAYS 节点
  2. posedge clk 还是事件节点,比如 AST_POSEDGE
  3. if / else if / else 还是“条件+分支”的语法树
  4. q <= a+b 还只是“左边是 q,右边是一个 AST_ADD 表达式”

也就是说,在 AST 这一步,工具还在忠实保存“代码长什么样”,而不是“电路应该怎么搭”。这正是 Yosys 所说的:AST 很接近原 Verilog 结构。([yosyshq.readthedocs.io][1])


AST 不是“最终答案”,它只是前端的中间站

接下来 simplifier 会对 AST 做一系列整理,Yosys 文档列出的典型动作包括:

  • inline task/function
  • 展开 generate
  • unroll for
  • 常量折叠
  • 把 primitive 改写成更普通的赋值形式
  • 把数组访问改写成 AST_MEMRD / AST_MEMWR,或者在太复杂时改成 signals + cases
  • 解析 range
  • AST_IDENTIFIER 绑定到它真正声明的位置。([yosyshq.readthedocs.io][1])

所以对上面那个时序例子,simplify 之后你可以把它理解成:

  • “这棵树更规整了”
  • 位宽、常量、标识符引用关系都更明确了
  • 更适合继续转 RTLIL

然后 genRTLIL() 再把表达式节点递归变成实现这些表达式所需的电路对象;遇到 always/initial 时,则创建 RTLIL::Process,再在后续 pass 中逐步降成 mux、DFF、memory write 等结构。([yosyshq.readthedocs.io][1])


你可以把 AST 理解成“语法骨架”,不是“电路骨架”

这是最关键的一句。

比如这段:

if (en)
    q <= a + b;
else
    q <= q + 1;

在 AST 里更像:

条件分支
├── 条件:en
├── 真分支:q <= (a+b)
└── 假分支:q <= (q+1)

但在后面的 RTL/netlist 里,它才会变成你熟悉的:

sum1 = a + b
sum2 = q + 1
q_next = MUX(en, sum1, sum2)
q = DFF(q_next)

所以:

  • AST 关心语法和作用域
  • RTLIL / process 关心赋值语义
  • mux/dff 网表关心硬件结构
  • AIG 关心统一布尔表示

一个更直观的类比

同一句话:

q <= a + b;

在不同层次看起来完全不一样:

在源码里

是一行文本

在 AST 里

像这样:

AST_ASSIGN_LE
├── AST_IDENTIFIER("q")
└── AST_ADD
    ├── AST_IDENTIFIER("a")
    └── AST_IDENTIFIER("b")

在 RTL/netlist 里

像这样:

tmp = ADD(a, b)
DFF.D = tmp
DFF.Q = q

在 AIG 里

就已经只剩很多个 AND/NOT 节点了


你自己怎么验证 AST

Yosys 文档明确提到,read_verilog-dump_ast 选项,可以直接打印 abstract syntax tree;-yydebug 可以看 parse tree。([yosyshq.readthedocs.io][1])

例如可以试:

yosys -p 'read_verilog -dump_ast demo.v'

你会看到一个比我上面“概念树”更详细的内部打印版本。


最后帮你压成一句话

AST 就是“把 RTL 源码翻译成一棵树形的语法/语义表示”,它仍然保留 modulealwaysif<=+ 这些源码层次;它还不是电路网表,但已经让综合前端可以系统地做 simplify、符号解析、位宽解析,再继续降成 RTLIL、mux/dff 和 AIG。 ([yosyshq.readthedocs.io][1])


1: https://yosyshq.readthedocs.io/projects/yosys/en/0.40/yosys_internals/flow/verilog_frontend.html “The Verilog and AST frontends - YosysHQ Yosys documentation”

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐