对Verilator的定位

verilator并不是一个传统的仿真器,而是一个编译器,用于将hdl代码编译为C++等语言进行执行。
PS:什么是lint?
在计算机科学中,lint是一种工具程序的名称,它用来标记源代码中,某些可疑的、不具结构性(可能造成bug)的段落。 它是一种静态程序分析工具,最早适用于C语言,在UNIX平台上开发出来。 后来它成为通用术语,可用于描述在任何一种计算机程序语言中,用来标记源代码中有疑义段落的工具。

运作原理

  1. 读取hdl代码,对其进行分析,并利用其构建一个C++仿真模型,该模型可选择加入覆盖率分析、绘制波形图等功能;
  1. 为了构建仿真模型,需要用户手动编写仿真main文件,在其中实例化一个C++对象,作为仿真验证模型;
  1. 将用户编写的main文件(wrapper)、verilator创建的文件、verilator提供的运行时库一并使用C++编译器进行编译,得到一个可执行仿真文件;
  1. 这个可执行仿真文件将真正实现仿真器的功能

对在shell中发起的一条verilator指令进行解析

verilator --cc --exe --build -j 0 -Wall sim_main.cpp our.v
  • 其中--cc的含义是表明最终的输出模型是c++模型;
  • --exe一般和wrapper file一并使用,表示需要构建一个可执行文件,而非仅仅创建一个库;
  • -j 0 to Verilate using use as many CPU threads as the machine has.:可能跟cpu线程有关系,用于调高verilator性能;
  • -Wall so Verilator has stronger lint warnings enabled.

常用Verilator参数

  • --top/--top-module <filename>显示指定顶层模块的文件名称,一般用于同时拥有多个顶层模块的仿真当中;
  • --Mdir <path> 显示指明仿真验证模型的目标文件,默认情况下是obj_dir;
  • --prefix应该是用来显示指定目标文件的前缀的,但具体用法并不是很清楚;
  • --exe,加入该参数后,verilator最终会构建一个可执行仿真文件,如果不使用该参数,则只会产生一个.a文件;
    • 将两种情况产生的结果进行对比,对比后可以理解,只要使用该参数,verilator则会将sim_main.cpp与其产生的文件进行链接,得到一个可执行文件;
      notion image
notion image
  • --build该参数会调用make来构建仿真模型:删掉该选项,得到如下结果;
    • notion image
      可见并没有生成.a文件,即连基本的编译都没有进行。但是仍旧存在的疑问是,使用该选项调用的第一个makefile文件是什么;

连接Verilated module

Verilator会输出一个prefix.h文件,里面定义了一个prefix类,在main函数中,你需要去实例化这个类,并将其作为Verialted module的接口;
输出文件中会包括一个prefix.mk文件,其作用是利用make构建prefix__All.a库文件;
产生的模型类会管理模型所有的内部状态,并将如下接口暴露出来:
  • 顶级模块的io端口;
  • Public top level module instances are exposed as pointers to allow access to /* verilator public */items.
  • The root of the design hierarchy (as in SystemVerilog $root) is exposed via the rootp member pointer to allow access to model internals, including /* verilator public_flat */ items.

Wrappers编写及仿真循环

当使用C++对模型进行评估时,需要调用eval() eval_step() eval_end_step()这一类函数。
  • 如果只有一个设计实例,在一个给定上下文中,需要调用designp->eval();
  • 当有多个设计实例时,需要逐个调用designsp->eval_step()并在其后调用designsp->eval_end_step().
  • eval()等函数被调用时,Verilator会寻找时钟信号的改变,并且评估与之相关的,顺序的always块;随后Verilator会评估组合逻辑;
  • 文档中有一句话并不是很明白:
Alternatively, if all always_ff statements use only the posedge of clocks, or all inputs go directly to always_ff statements, as is typical, then you can change non-clock inputs on the negative edge of the input clock, which will be faster as there will be fewer eval() calls.
  • 更多评估信息,可以看发行版中的docs/internals.rst文件.

Verilated 以及 VerilatedContext

多个C++ Verilated module可能会共享同一个仿真context。这个通用的仿真context信息被存储在一个VerilatedContext结构中。如果VerilatedContext没有先于模型被创建,那么一个默认的全局变量将被自动创建。
有一中VerilatedContext方法的调用细则如下:
  • 可以使用Verilated::<methods>来调用方法,包括Verilated::commandArgs,这种调用是使用默认的全局VerilatedContext变量;如果你使用了多个仿真context,你不应该这样使用,应该为相应的VerilatedContext对象调用函数,例如:
    • 更多内容可以查看include/verilated.h.

    仿真细则(Verilated-Model Runtime)

    暂略

    如何使用C++产生仿真波形

    方法A

    1. 使用--trace选项调用verilator;
    1. 在顶级C++代码中,利用context,调用Verilated::traceEverOn(true)(默认)。
    1. 然后在verilog代码中利用$dumpfile$dumpvars去使能traces;

    方法B

    1. 创建VerilatedVcdC对象;(需要包含verilated_vcd_c.h头文件)
    1. 在main仿真循环中,在eval()之后调用trace_object->dump(contextp->time()),在每一个time step,并在最后调用trace_object->close()

    相关API

    • topp->trace(): 应该是起到一个初始化trace的作用;
    • tfp->open(): 打开波形图文件
    • contextp->timeInc(1): 1 timeprecision period passes
    • contextp->time(): 获取仿真时间
    • tfp->dump(contextp->time()):每个time step保存一下;
    • contextp->gotFinish()
    • tfp->close(): 关闭tfp;