verilator如何实现RTL的仿真(腾讯混元)
Verilator 是一个用于将 Verilog 或 SystemVerilog RTL(寄存器传输级)代码转换为 C++ 或 SystemC 模型的工具,主要用于高性能的功能仿真和验证。它不是像 ModelSim 或 VCS 那样的传统事件驱动仿真器,而是通过静态编译的方式将 RTL 转换为可执行的 C++ 代码,从而实现高效仿真。下面详细介绍 Verilator 实现 RTL 仿真的流程与实现细节。
一、Verilator 的基本工作流程
Verilator 的 RTL 仿真过程大致可以分为以下几个步骤:
- 前端解析与中间表示生成
- 优化与转换
- C++ 模型生成
- C++ 编译与链接
- 运行仿真
下面我们逐层深入每个步骤的实现细节。
二、详细实现细节
1. 前端解析与中间表示生成
Verilator 首先使用其内部的 Verilog 解析器(基于 Flex/Bison 构建)对输入的 Verilog 或 SystemVerilog 文件进行词法分析和语法分析,生成一个抽象语法树(AST, Abstract Syntax Tree)。这个 AST 是 Verilator 内部表示 RTL 的数据结构,包含了模块、端口、信号、赋值语句、always 块等所有设计信息。
- 支持的语言特性:Verilator 支持大部分 Verilog-2001 和部分 SystemVerilog 特性(如
always_ff
、always_comb
等),但对某些高级特性(如interface
、assert
等)支持有限或需要额外配置。 - 限制:Verilator 不是一个完整的仿真器,它不支持动态行为如
initial
块中的复杂控制流(如fork/join
),也不支持某些高级调试功能。
2. 优化与转换
在生成 AST 后,Verilator 会对 AST 进行一系列的优化与转换,包括:
- 常量传播(Constant Propagation):将设计中可以静态确定的值提前计算出来,减少运行时的计算量。
- 死代码消除(Dead Code Elimination):移除不会被执行或对输出没有影响的代码。
- 层次扁平化(Hierarchy Flattening):将模块层次结构展平,减少模块调用开销。这是 Verilator 的一大特点,它默认会将所有模块实例化合并到一个顶层 C++ 类中,从而提升仿真速度。
- 时序逻辑转换:将
always @(posedge clk)
等时序逻辑块转换为状态机或寄存器更新逻辑,便于在 C++ 中高效实现。
这些优化显著提升了最终生成代码的执行效率,使 Verilator 的仿真速度远超传统事件驱动仿真器。
3. C++ 模型生成
经过优化后的 AST 会被转换为 C++ 代码。这是 Verilator 的核心功能之一。具体来说:
- 模块转换为 C++ 类:每个 Verilog 模块会被转换为一个 C++ 类,模块中的信号、寄存器、端口等会被映射为类的成员变量。
- always 块转换为函数:
always
块中的逻辑会被提取并转换为 C++ 函数。例如,always @(posedge clk)
会被转换为一个在时钟上升沿调用的函数。 - 组合逻辑与时序逻辑分离:Verilator 会区分组合逻辑和时序逻辑,并分别生成对应的 C++ 代码。组合逻辑通常在每次仿真步进中被调用,而时序逻辑则在时钟边沿触发。
- 线程与事件模拟:虽然 Verilator 不使用传统事件驱动机制,但它通过函数调用模拟了类似的行为。例如,
always
块中的逻辑会被封装为函数,在仿真主循环中被调用。
最终,Verilator 会生成一组 C++ 文件,包括:
- 顶层模块的 C++ 类:包含所有模块的逻辑。
- 辅助类与函数:如仿真时间管理、信号访问接口等。
- Makefile 或构建脚本:用于编译生成的 C++ 代码。
4. C++ 编译与链接
生成的 C++ 代码需要使用 C++ 编译器(如 g++ 或 clang++)进行编译,生成可执行文件。Verilator 通常会生成一个 Makefile,用户可以直接运行 make
来编译代码。
- 仿真主程序:用户需要编写一个简单的 C++ 主程序(通常是一个
main.cpp
文件),用于实例化顶层模块、驱动输入信号、监控输出信号等。Verilator 提供了一些 API(如Verilated
类)来帮助用户控制仿真流程。 - 编译优化:由于生成的 C++ 代码已经经过高度优化,编译器可以进一步进行优化(如
-O3
),从而提升仿真速度。
5. 运行仿真
编译完成后,运行生成的可执行文件即可开始仿真。仿真的基本流程如下:
- 初始化:在仿真开始时,Verilator 会初始化所有信号和寄存器的值。
- 时间步进:仿真以时间步(time step)为单位进行。在每个时间步中:
- 组合逻辑更新:所有组合逻辑块会被调用,更新信号值。
- 时序逻辑更新:在时钟边沿(如上升沿或下降沿)触发时序逻辑块,更新寄存器值。
- 用户控制:用户可以在主程序中控制仿真的开始、暂停、停止等行为,也可以在每个时间步中插入自定义逻辑(如打印信号值、检查断言等)。
- 结束条件:仿真会在达到指定的仿真时间或用户指定的结束条件时停止。
三、Verilator 仿真的特点与优势
- 高性能:由于采用静态编译的方式,Verilator 的仿真速度通常比传统事件驱动仿真器快几个数量级,特别适合大规模设计的回归测试和性能验证。
- 可扩展性:生成的 C++ 代码可以与其他 C++ 模块集成,方便进行软硬件协同仿真或系统级验证。
- 灵活性:用户可以通过编写自定义的 C++ 主程序,灵活控制仿真流程和测试场景。
然而,Verilator 也有一些局限性:
- 功能限制:不支持完整的仿真功能(如动态事件、复杂调试等),更适合功能验证而非时序验证。
- 学习曲线:需要用户具备一定的 C++ 编程能力,以编写主程序和集成仿真环境。
四、总结
Verilator 通过将 Verilog RTL 转换为高效的 C++ 模型,实现了高性能的仿真。其核心实现细节包括:
- 使用内部解析器生成 AST;
- 对 AST 进行优化与转换;
- 将 RTL 结构映射为 C++ 类与函数;
- 通过 C++ 编译生成可执行文件;
- 在主程序中控制仿真流程。
这种静态编译的方式使 Verilator 在大规模设计验证中具有显著优势,但也牺牲了一些传统仿真器的灵活性和功能完整性。