眼鏡止水

FPGA、ネットワーク機器、料理、そしてメガネ女子。

【CMake】Verilatorによるシミュレーションモデルの生成をCMake+Ninja+Clang+Ccache+moldで行う

概要

Verilatorは便利で高速なVerilog/SystemVerilogシミュレータですが、パラメタなどを変えた複数の実行ファイルを生成する場合はターゲットとソースの管理が煩雑になりえます。 本記事では、CMakeを用いて効率的にターゲットとソースを管理することを試みます。 また、GNU Make+GCCといった典型的なツールチェーンではなく、NinjaClangCcache、そしてmoldを採用する方法も調査します。

本編

本記事はVerilatorをインストール済みの読者を想定します。 まだインストールしていない方は、以下の記事などを参考にインストールします。

blog.ojioji.ikeojihouse.com

CMakeたちをインストールします。 筆者はUbuntuを用いるため、aptを使います。

sudo apt install -y cmake ninja-build mold clang-19 # ccacheは上記の記事でインストール済み

インストールが完了したら、Verilatorのチュートリアルに沿って環境を構築します。

verilator.org

プロジェクトのルートディレクトリにCMakeLists.txtを作成し、以下を記述します。

cmake_minimum_required(VERSION 3.12)               # CMake v3.12以降を指定

project(cmake_example)                             # プロジェクト名を設定
find_package(verilator HINTS $ENV{VERILATOR_ROOT}) # VerilatorのCMakeスクリプトを検索し統合
add_executable(Vour sim_main.cpp)                  # sim_main.cppというテストベンチからVourという実行ファイルを生成するルール
verilate(Vour SOURCES our.v)                       # our.vをverilateしVourに統合

続いて、our.vを作成します。 内容は問いませんが、ひとまずは32bitの加算器とします。

module our (
  input [31:0] a,
  input [31:0] b,
  output [31:0] c
);

  assign c = a + b;

endmodule

続いて、sim_main.cppを作成します。 こちらも内容は問いません。 our.vのテストベンチを早速実装してもよいですが、ひとまずはHello, world!とします。

#include <iostream>

int main() {
  std::cout << "Hello, world!\n";
  return 0;
}

3つのファイルができたら、buildディレクトリを作成し、その中でCMakeを実行します。 そして、makeを実行します。

mkdir build
cd build
cmake .. # ルートディレクトリのCMakeLists.txtを参照
make

buildディレクトリにVourが生成されます。 それを実行すると、"Hello, world!"と表示されます (上記のソースを使った場合)。

続きまして、ビルドシステムをGNU MakeからNinjaへと変更します。

cd ..
rm -rf build
mkdir build
cd build
cmake -GNinja .. # -GオプションでNinjaを指定
ninja

同様に、Vour実行ファイルが生成されます。

続いて、コンパイラにClangを指定します。 CMakeLists.txtに以下を追記します。

+ set(CMAKE_C_COMPILER clang-19)                     # Cのコンパイラにclangを指定
+ set(CMAKE_CXX_COMPILER clang++-19)                 # C++のコンパイラにclang++を指定

ここで、Ccacheが自動的に有効化されない場合があります。 それを避けるためには、以下を追記します。

+ set(CMAKE_CXX_COMPILER_LAUNCHER ccache)            # ccacheがclangを呼ぶように設定

CMakeを再実行してNinjaのビルドスクリプトを作り直し、今度は詳細なログを出力しながらビルドします。

cmake -GNinja ..
ninja -v

この結果から、NinjaがCcacheを経由してClangを実行しているとわかります。 以下に例を示します。

[8/11] /usr/bin/ccache clang++-19 -DVM_COVERAGE=0 -DVM_SC=0 -DVM_TRACE=0 -DVM_TRACE_FST=0 -DVM_TRACE_SAIF=0 -DVM_TRACE_VCD=0 -I/root/veri_cmake/build/CMakeFiles/Vour.dir/Vour.dir -I/usr/local/share/verilator/include -I/usr/local/share/verilator/include/vltstd  -MD -MT CMakeFiles/Vour.dir/CMakeFiles/Vour.dir/Vour.dir/Vour___024root__Slow.cpp.o -MF CMakeFiles/Vour.dir/CMakeFiles/Vour.dir/Vour.dir/Vour___024root__Slow.cpp.o.d -o CMakeFiles/Vour.dir/CMakeFiles/Vour.dir/Vour.dir/Vour___024root__Slow.cpp.o -c /root/veri_cmake/build/CMakeFiles/Vour.dir/Vour.dir/Vour___024root__Slow.cpp
[9/11] /usr/bin/ccache clang++-19 -DVM_COVERAGE=0 -DVM_SC=0 -DVM_TRACE=0 -DVM_TRACE_FST=0 -DVM_TRACE_SAIF=0 -DVM_TRACE_VCD=0 -I/root/veri_cmake/build/CMakeFiles/Vour.dir/Vour.dir -I/usr/local/share/verilator/include -I/usr/local/share/verilator/include/vltstd  -MD -MT CMakeFiles/Vour.dir/usr/local/share/verilator/include/verilated_threads.cpp.o -MF CMakeFiles/Vour.dir/usr/local/share/verilator/include/verilated_threads.cpp.o.d -o CMakeFiles/Vour.dir/usr/local/share/verilator/include/verilated_threads.cpp.o -c /usr/local/share/verilator/include/verilated_threads.cpp
[10/11] /usr/bin/ccache clang++-19 -DVM_COVERAGE=0 -DVM_SC=0 -DVM_TRACE=0 -DVM_TRACE_FST=0 -DVM_TRACE_SAIF=0 -DVM_TRACE_VCD=0 -I/root/veri_cmake/build/CMakeFiles/Vour.dir/Vour.dir -I/usr/local/share/verilator/include -I/usr/local/share/verilator/include/vltstd  -MD -MT CMakeFiles/Vour.dir/usr/local/share/verilator/include/verilated.cpp.o -MF CMakeFiles/Vour.dir/usr/local/share/verilator/include/verilated.cpp.o.d -o CMakeFiles/Vour.dir/usr/local/share/verilator/include/verilated.cpp.o -c /usr/local/share/verilator/include/verilated.cpp

さて、ここまでで本記事の目的の大部分を達成しました。 以下では、オプションとしてコンパイラにフラグを渡したり、リンカを変更したりする方法を述べます。 これにより、シミュレーションの実行や再構築が高速化し、効率的に作業が行えることが期待されます。

CMakeLists.txtに以下を追記します。 コンパイラフラグとして、実行中のCPU向けにコードを生成する (-march=native) とともに、レベル3の最適化を行う (-O3) ことを設定します。 リンカフラグとして、moldの使用を要求します。

+ set(CMAKE_CXX_FLAGS "-march=native -O3")           # C++のコンパイラフラグを設定
+ set(CMAKE_EXE_LINKER_FLAGS "-fuse-ld=mold")        # リンカフラグを設定

これを用いてビルドを行うと、Clangにフラグが渡されます。 以下に、ninja -vの実行結果の一部を示します。 最適化オプションやリンカフラグが渡されていることを確認できます。

[9/11] /usr/bin/ccache clang++-19 -DVM_COVERAGE=0 -DVM_SC=0 -DVM_TRACE=0 -DVM_TRACE_FST=0 -DVM_TRACE_SAIF=0 -DVM_TRACE_VCD=0 -I/root/veri_cmake/build/CMakeFiles/Vour.dir/Vour.dir -I/usr/local/share/verilator/include -I/usr/local/share/verilator/include/vltstd -march=native -O3 -MD -MT CMakeFiles/Vour.dir/CMakeFiles/Vour.dir/Vour.dir/Vour__Syms.cpp.o -MF CMakeFiles/Vour.dir/CMakeFiles/Vour.dir/Vour.dir/Vour__Syms.cpp.o.d -o CMakeFiles/Vour.dir/CMakeFiles/Vour.dir/Vour.dir/Vour__Syms.cpp.o -c /root/veri_cmake/build/CMakeFiles/Vour.dir/Vour.dir/Vour__Syms.cpp
[10/11] /usr/bin/ccache clang++-19 -DVM_COVERAGE=0 -DVM_SC=0 -DVM_TRACE=0 -DVM_TRACE_FST=0 -DVM_TRACE_SAIF=0 -DVM_TRACE_VCD=0 -I/root/veri_cmake/build/CMakeFiles/Vour.dir/Vour.dir -I/usr/local/share/verilator/include -I/usr/local/share/verilator/include/vltstd -march=native -O3 -MD -MT CMakeFiles/Vour.dir/usr/local/share/verilator/include/verilated.cpp.o -MF CMakeFiles/Vour.dir/usr/local/share/verilator/include/verilated.cpp.o.d -o CMakeFiles/Vour.dir/usr/local/share/verilator/include/verilated.cpp.o -c /usr/local/share/verilator/include/verilated.cpp
[11/11] : && clang++-19 -march=native -O3 -fuse-ld=mold CMakeFiles/Vour.dir/sim_main.cpp.o CMakeFiles/Vour.dir/CMakeFiles/Vour.dir/Vour.dir/Vour.cpp.o CMakeFiles/Vour.dir/CMakeFiles/Vour.dir/Vour.dir/Vour___024root__DepSet_hf7027e39__0.cpp.o CMakeFiles/Vour.dir/CMakeFiles/Vour.dir/Vour.dir/Vour___024root__DepSet_h637983f1__0.cpp.o CMakeFiles/Vour.dir/CMakeFiles/Vour.dir/Vour.dir/Vour___024root__Slow.cpp.o CMakeFiles/Vour.dir/CMakeFiles/Vour.dir/Vour.dir/Vour___024root__DepSet_hf7027e39__0__Slow.cpp.o CMakeFiles/Vour.dir/CMakeFiles/Vour.dir/Vour.dir/Vour___024root__DepSet_h637983f1__0__Slow.cpp.o CMakeFiles/Vour.dir/CMakeFiles/Vour.dir/Vour.dir/Vour__Syms.cpp.o CMakeFiles/Vour.dir/usr/local/share/verilator/include/verilated.cpp.o CMakeFiles/Vour.dir/usr/local/share/verilator/include/verilated_threads.cpp.o -o Vour  -pthread  -lpthread  -latomic && :

さて、実際にmoldが使われていることを二つの方法で確認します。 まず、Clangに-###オプションを渡しverboseにビルドを実行します。 その結果、ld.moldが呼び出されています。

[11/11] : && clang++-19 -march=native -O3 -### -fuse-ld=mold CMakeFiles/Vour.dir/sim_main.cpp.o CMakeFiles/Vour.dir/CMakeFiles/Vour.dir/Vour.dir/Vour.cpp.o CMakeFiles/Vour.dir/CMakeFiles/Vour.dir/Vour.dir/Vour___024root__DepSet_hf7027e39__0.cpp.o CMakeFiles/Vour.dir/CMakeFiles/Vour.dir/Vour.dir/Vour___024root__DepSet_h637983f1__0.cpp.o CMakeFiles/Vour.dir/CMakeFiles/Vour.dir/Vour.dir/Vour___024root__Slow.cpp.o CMakeFiles/Vour.dir/CMakeFiles/Vour.dir/Vour.dir/Vour___024root__DepSet_hf7027e39__0__Slow.cpp.o CMakeFiles/Vour.dir/CMakeFiles/Vour.dir/Vour.dir/Vour___024root__DepSet_h637983f1__0__Slow.cpp.o CMakeFiles/Vour.dir/CMakeFiles/Vour.dir/Vour.dir/Vour__Syms.cpp.o CMakeFiles/Vour.dir/usr/local/share/verilator/include/verilated.cpp.o CMakeFiles/Vour.dir/usr/local/share/verilator/include/verilated_threads.cpp.o -o Vour  -pthread  -lpthread  -latomic && :
Ubuntu clang version 19.1.1 (1ubuntu1~24.04.2)
Target: x86_64-pc-linux-gnu
Thread model: posix
InstalledDir: /usr/lib/llvm-19/bin
 "/usr/bin/ld.mold" "-z" "relro" "--hash-style=gnu" "--build-id" "--eh-frame-hdr" "-m" "elf_x86_64" "-pie" "-dynamic-linker" "/lib64/ld-linux-x86-64.so.2" "-o" "Vour" "/lib/x86_64-linux-gnu/Scrt1.o" "/lib/x86_64-linux-gnu/crti.o" "/usr/lib/gcc/x86_64-linux-gnu/13/crtbeginS.o" "-L/usr/lib/gcc/x86_64-linux-gnu/13" "-L/usr/lib/gcc/x86_64-linux-gnu/13/../../../../lib64" "-L/lib/x86_64-linux-gnu" "-L/lib/../lib64" "-L/usr/lib/x86_64-linux-gnu" "-L/usr/lib/../lib64" "-L/lib" "-L/usr/lib" "CMakeFiles/Vour.dir/sim_main.cpp.o" "CMakeFiles/Vour.dir/CMakeFiles/Vour.dir/Vour.dir/Vour.cpp.o" "CMakeFiles/Vour.dir/CMakeFiles/Vour.dir/Vour.dir/Vour___024root__DepSet_hf7027e39__0.cpp.o" "CMakeFiles/Vour.dir/CMakeFiles/Vour.dir/Vour.dir/Vour___024root__DepSet_h637983f1__0.cpp.o" "CMakeFiles/Vour.dir/CMakeFiles/Vour.dir/Vour.dir/Vour___024root__Slow.cpp.o" "CMakeFiles/Vour.dir/CMakeFiles/Vour.dir/Vour.dir/Vour___024root__DepSet_hf7027e39__0__Slow.cpp.o" "CMakeFiles/Vour.dir/CMakeFiles/Vour.dir/Vour.dir/Vour___024root__DepSet_h637983f1__0__Slow.cpp.o" "CMakeFiles/Vour.dir/CMakeFiles/Vour.dir/Vour.dir/Vour__Syms.cpp.o" "CMakeFiles/Vour.dir/usr/local/share/verilator/include/verilated.cpp.o" "CMakeFiles/Vour.dir/usr/local/share/verilator/include/verilated_threads.cpp.o" "-lpthread" "-latomic" "-lstdc++" "-lm" "-lgcc_s" "-lgcc" "-lpthread" "-lc" "-lgcc_s" "-lgcc" "/usr/lib/gcc/x86_64-linux-gnu/13/crtendS.o" "/lib/x86_64-linux-gnu/crtn.o"

続いて、生成されたELFファイルの.comment領域を確認します。 やはりmoldが使われていると考えられます。 最後のGCCはなんでしょう?

readelf -p .comment Vour
String dump of section '.comment':
  [     0]  mold 2.30.0 (compatible with GNU ld)
  [    25]  Ubuntu clang version 19.1.1 (1ubuntu1~24.04.2)
  [    54]  GCC: (Ubuntu 13.3.0-6ubuntu2~24.04) 13.3.0

以上で、Verilatorによるシミュレーションモデルの生成をCMake+Ninja+Clang+Ccache+moldで行えるようになりました。 今後は、これを用いて自作CPUの開発などを行いたいと思います。