本記事では、Verilator と Microsoft Visual Studio C++(MSVC)での検証環境をご紹介。
参考: FPGA開発日記:Verilatorの使い方(1. Verilatorの考え方と基本的なシミュレーション実行方法)
Verilatorから生成されたC++コード(Verilatedコード)をコンパイルするのに、 多くはGCCやClangが使用される。 ただ、まれに Visual Studio を使う人もおるじゃろ? おらんか、いるとするじゃろ? 泣くなよ、やめよっかこの話例として、SNES on FPGA でやったスーファミ互換自作PPU(Picture Processing Unit)単体の検証例を示す。
$ tar zxvf verilator-4.216.tar.gz $ cd verilator-4.216 $ autoconf $ ./configure $ make -j `nproc` $ make test $ make install $ verilator --version Verilator 4.216 2021-12-05 rev UNKNOWN.REV検証対象をVerilogHDLからC++に変換。
$ verilator --cc ppu_top.v --compiler msvc --public --l2-name v
--compiler msvc
--public
全内部信号をトップに引き出しpublic指定で宣言する。
テストベンチコードから容易に読み書き可能になるが、
場合によってはクロックが誤ってシミュレートされる可能性があるらしい。
これはお好みで。
--l2-name <value>
信号名の先頭文字列として<value>を使用する。これもお好みで。
ppu_sim.cpp
(下方に載せる)
verilated.cpp
V*.cpp
のすべて
C:\cygwin64\usr\local\share\verilator\include\verilatedos.h(230,1): fatal error C1189: #error: "Verilator requires a C++11 or newer compiler"
これは文字通り、VerilatorがC++11以上を要求しているため。
:
#if __cplusplus >= 201103L || defined(__GXX_EXPERIMENTAL_CXX0X__) || defined(VL_CPPCHECK)
:
#else
# error "Verilator requires a C++11 or newer compiler"
#endif
:
しかし Visual Studio の既定では __cplusplus が 199711L となっている。
そこで、プロジェクトのプロパティ
[C/C++]→[コマンドライン]の[追加のオプション]に/Zc:__cplusplus
を追加。
参考:/Zc:__cplusplus (更新された __cplusplus マクロの有効化)
Vppu_top
のメンバ変数としてアクセスできる。
それ以外の内部信号は、--public
で生成されたVppu_top___024root.h
にメンバ変数としてpublic指定で宣言されており、
トップモジュールからはrootp
経由でアクセスできる。
参考:Connecting to Verilated Models
あとは基本的なVerilator向けのテストベンチコードなので一部省略して貼っておく。 動作としては、リファレンスモデルで取得しておいたPPUレジスタのスナップショットを読み込み、 VerilatedしたPPUの各レジスタやメモリに設定する。 シミュレーションを実行し、描画されたピクセルデータを画像として保存し、表示して確認する。
#include "Vppu_top.h"
#include "Vppu_top___024root.h" // publicオプションで生成される
int main()
{
Vppu_top* ppu_top = new Vppu_top();
Vppu_top___024root* ppu = ppu_top->rootp; // 内部信号へアクセスしやすいように
// リセット
ppu_top->eval();
ppu_top->p_reset = 1;
ppu_top->eval();
ppu_top->m_clock = 1;
ppu_top->eval();
ppu_top->p_reset = 0;
ppu_top->eval();
ppu_top->m_clock = 0;
ppu_top->eval();
// PPUスナップショットデータの読み込み
FILE* ifp = fopen("../snapshot/rockman.dat", "rb");
unsigned char* buf = new unsigned char[0x20000];
while (!feof(ifp)) {
fread(buf, 1, 11, ifp);
// レジスタの値を設定
if (strncmp((char*)buf, "PPU:", 4) == 0) {
fread(buf, 1, atoi((char*)buf + 4), ifp);
ppu->v__DOT__ppu1__DOT__bg_mode = buf[0];
ppu->v__DOT__ppu1__DOT__f_bg3_pri = (buf[0]==1) & buf[1];
ppu->v__DOT__ppu1__DOT__n_brightness = buf[2];
:
省略
:
}
// VRAMの値を設定
if (strncmp((char*)buf, "VRA:", 4) == 0) {
unsigned short* vram = new unsigned short[32768];
fread(vram, 2, atoi((char*)buf + 4) >> 1, ifp);
for (int i = 0; i < 32768; i++) {
ppu->v__DOT__vramA__DOT__cells[i] = vram[i] & 0xFF;
ppu->v__DOT__vramB__DOT__cells[i] = (vram[i] >> 8) & 0xFF;
}
delete[] vram;
}
}
fclose(ifp);
delete[] buf;
printf("read snapshot ok.\n");
ppu_top->eval();
unsigned char** image = new unsigned char*[480];
for (int i = 0; i < 480; i++) image[i] = new unsigned char[512 * 3];
// シミュレーション実行
for (int x = 0, y = 0;;) {
ppu_top->m_clock = 0;
ppu_top->eval();
ppu_top->m_clock = 1;
ppu_top->eval();
// 描画結果の取得
if(ppu->v__DOT__ppu2__DOT__scanline >= 0 &&
ppu->v__DOT__ppu2__DOT__scanline < 240 &&
ppu_top->VIDEO_enable)
{
image[y][x * 3 ] = ppu_top->cR << 3;
image[y][x * 3 + 1] = ppu_top->cG << 3;
image[y][x * 3 + 2] = ppu_top->cB << 3;
x++;
if (x == 512) {
x = 0;
memcpy(image[y + 1], image[y], 512 * 3);
y += 2;
if (y == 480) break;
}
}
}
ppu_top->final();
delete ppu_top;
// 描画結果をPPMフォーマットで出力
FILE* ofp = fopen("out.ppm", "w");
fprintf(ofp, "P3\n512 480 255\n");
for (int y = 0; y < 480; y++) {
for (int x = 0; x < 512 * 3; x += 3) {
fprintf(ofp, "%d %d %d\n", image[y][x], image[y][x+1], image[y][x+2]);
}
}
fclose(ofp);
for (int i = 0; i < 480; i++) delete[] image[i];
delete[] image;
// PPMの表示(あらかじめ画像ビューワに関連付けしておく)
system("cmd /c out.ppm");
return 0;
}
実行すると描画結果が出る。かっこいい。
#include "VR3000A.h"
#include "VR3000A___024root.h"
#include<windows.h>
#include<windowsx.h>
あと、Verilatorの使い方として本来なら、
アクセスしたい信号のみHDL記述中に/* verilator public */
を追記しpublic指定するのが速度を落とさない作法のようだ。
ただ、私の場合は検証対象のVerilogHDLに手を加えないスタイルの環境
(VerilogHDLは中間言語とみなしSFLから生成)でもあるので、
--public
ですべての信号を引き出している。
わざわざアクセスしたい信号を選別するのも面倒だし、
テストベンチからアクセスしない変数はコンパイルで上手いこと処理されるのを期待して。
WSL2でのVerilatorのコンパイルと、VerilogHDLからC++への変換はもちろん問題ない。 しかし Visual Studio 2022 でのコンパイルは、 なぜか標準Cライブラリ(x86)へのパスが通ってなくてあきらめた。 あとWSL2内のC++コードにアクセスするのにも別途設定が必要のようだ。 またそのうち試してみよう。
今回まとめて説明してしまっているけど、
--public
は Visual Studio によらずGCC向けでももちろん有効なオプションだし、
今回のPPU描画テスト程度なら WSL2 & VS Code 環境でもなんら問題ない。
Visual Studio を使う訴求力を求めるなら、Windowsアプリとして作ったリファレンスに、
HDLのCPUをVerilatedして組み込んで検証するケースなら少しは分かってもらえるかな…
ありえんか、やめよっかこの話。