みねっちょのマイコン関係ブログ

組込開発系フリーソフトやハードの情報発信ブログ

無償な Verilog シミュレータ Verilator のテストベンチを SystemC で作る

サイト内 Google 検索:


最終更新:2021-05-08
Verilator は Verilog HDL をサイクルベース方式で高速に動かす無償のシミュレータです。但、最上位モジュールは C++ または SystemC で記述する必用があります。本記事では、最上位のテストベンチを SystemC で記述する方法を、Verilog HDL の記述と1対1に比較して紹介します。

尚、シミュレーションの実行には、Windows 10 上の WSL で動いて居る Ubuntu-20.04 の標準パッケージのみを使用します。

目次:

前提条件:

  • Windows 10 の WSL に Ubuntu-20.04 をインストール済なこと
  • 【こちらの別記事】で紹介済のサンプル回路を使用すること
  • Ubuntu-20.04 に Verilator をインストール済なこと (前記の記事で紹介済)

概要:

Verilator は Verilog HDL をサイクルベース方式で高速に動かす無償のシミュレータです。但、従来型のイベントドリブン方式のシミュレータと比べると若干の制約が有ります。【こちらの別記事】では最上位のテストベンチのみを C++ で記述する方法を紹介しましたが、本記事では、それを SystemC で記述する方法を紹介します。従来型のイベントドリブン方式の Verilog シミュレータを使用していた方が、違和感無く移行出来る事を目指します。

Verilator には、最上位モジュールは C++ または SystemC で記述せねばならないと言う制約が有る為、Verilog HDL で記載されたテストベンチを2つに分けます。上位階層のテストベンチには必用最小限の記述を移行し、下位階層のテストベンチは Verilog HDL のままとしますが、動作はサイクル カウントを元に記述します。

サンプルとして動かす回路は、別記事で紹介したパイプライン ROM をそのまま使用します。テストベンチでロードする ROM コードも同様です。

Ubuntu-20.04 への SystemC と ccache のインストール

WSL の Ubuntu-20.04 を使用している場合には、次のコマンドで SystemC をインストールします。
また、ビルドの高速化の為に、ccache をインストールします。

sudo apt update
sudo apt install libsystemc-dev
sudo apt install ccache

ドライブする信号を SystemC の階層に持ち上げる:

下位階層のテストベンチ (Verilog HDL のみ):

【こちらの別記事】では、Verilog HDL で記述した下位階層のテストベンチにはポートを作らず、最上位階層の C++ 記述から階層を跨いで信号をドライブしていました。しかし、SystemC で記述する場合は、ドライブする信号を素直に上位階層へポート接続した方が都合が良いので Verilog HDL を修正します。修正した下位階層の Verilog HDL は次の通りです。最上位階層からドライブする clkdumptrig の2つの信号を入力ポートとします。また、count は後々波形をダンプする時のタイムスタンプとして使用しますので出力ポートに接続しておきます。clk は同期式回路用の通常のクロック、dumptrig は回路内部をテキストダンプする時用のトリガです。また、countという信号でクロック サイクル数をカウントしています。

cat から EOF までを ubuntu のターミナル上にコピー&ペーストすると、bench.v というファイルが出来ます。

cat << "EOF" > bench.v
`default_nettype none

module bench(
    input   wire        clk,
    input   wire        dumptrig,
    output  time        count = 0
  );

  parameter     RSTCOUNT = 10, ENDCOUNT = 30;

  reg           nrst = 1;

  /* Reset and Simulation Finish */
  always @(posedge clk) begin
    casez (count)
      0        : nrst <= 0;
      RSTCOUNT : nrst <= 1;
      ENDCOUNT : $finish;
    endcase
    count <= count + 1;
  end

  /* Design Under Test */
  dut u_dut (.clk(clk), .nrst(nrst));

  /* ROM Code Load */
  initial begin
    $readmemh("main.v", u_dut.insrom);
  end

  /* Text Dump */
  initial begin
    $display("--------------------");
    $display("   C     A     D    ");
    $display("   O  n  D     A    ");
    $display("   U  R  D     T    ");
    $display("   N  S  R     A    ");
    $display("   T  T <=> <======>");
    $display("--------------------");
  end
  always @(posedge dumptrig) begin
    $write("%4d: %1h", count, nrst);
    $write(" %3h %8h", u_dut.addr_reg, u_dut.romout);
    $display;
  end

endmodule

`default_nettype wire
EOF

2021-05-02: count を出力ポートに接続。ポートの書式変更。

上位階層のテストベンチ (Verilog HDLの場合) :

上位階層のテストベンチの Verilog HDL は、次の通りとなります。always 文の中で、クロックの1サイクル分を記述しています。1サイクル ピリオドを1000タイムスケールとし、# による遅延記述でクロック波形を定義しています。
回路の内部状態をテキストダンプする為のダンプトリガーは、クロックの立ち上がりの直前に設定しています。これは、クロック同期式の回路を論理シミュレーションする場合の一般的な手法です。

cat から EOF までを ubuntu のターミナル上にコピー&ペーストすると、frame.v というファイルが出来ます。

cat << "EOF" > frame.v
`default_nettype none
`timescale 1ns/1ps

`define PERIOD 1000

module frame();

  /* Command Line Arguments */
  // parameter argv = 1'bz;

  /* Signal Definition */
  reg   clk;
  reg   dumptrig;


  /* Testbench and Port Connection */
  bench u_bench(
    .clk(clk),
    .dumptrig(dumptrig),
    .count()
  );


  /* Clock and DumpTrigger */
  always begin
    clk = 1;
    #(`PERIOD/2-1);
    dumptrig = 0;
    #1;
    clk = 0;
    #(`PERIOD/2-1);
    dumptrig = 1;
    #1;
  end

endmodule

`default_nettype wire
EOF

2021-05-02: count を下位からポートに持ち上げ。

上位階層のテストベンチ (SystemC の場合) :

前記の上位階層のテストベンチを、Verilog HDL と出来るだけ1対1となるよう SystemC に置き換えます。クロック波形の定義は、通常の SystemC の記述方法である sc_clock ではなく、Verilog HDL と1対1の記載となる様に敢えて Verilator 方式の while 文で記載しています。Veilog HDL では # による遅延記述をしていた部分を、SystemC の sc_start 記述に置き換えます。
Verilog HDL の time 型は64ビット符号無し整数ですので、SystemC では uint64_t に置き換えます。SystemC では Verilog HDL の様な空きポートの記述は出来ない様なので、取り敢えず定義します。
SystemC 特有の記述として、while 文のループの前に、sc_start(); を記載します。これを記載しないと、先頭サイクルでのリセットが Verilog HDL と同等になりません。

cat から EOF までを ubuntu のターミナル上にコピー&ペーストすると、scframe.v というファイルが出来ます。

cat << "EOF" > scframe.v
#pragma onece
#include <iostream>
#include <systemc.h>
#include "verilated_sc.h"
#include "obj_dir/Vbench.h"

#define PERIOD 1000

int sc_main(int argc, char* argv[]) {

  /* Command Line Arguments */
  // Verilated::commandArgs(argc, argv);

  /* Signal Definition */
  sc_signal<bool>       clk;
  sc_signal<bool>       dumptrig;
  sc_signal<uint64_t>   count;

  /* Testbench */
  Vbench* u_bench = new Vbench("bench");

  /* Port Connection */
  u_bench->clk(clk);
  u_bench->dumptrig(dumptrig);
  u_bench->count(count);

  /* Clock and DumpTrigger */
  sc_start();

  while (!Verilated::gotFinish()) {
    clk = 1;
    sc_start(PERIOD/2-1, SC_NS);
    dumptrig = 0;
    sc_start(1, SC_NS);
    clk = 0;
    sc_start(PERIOD/2-1, SC_NS);
    dumptrig = 1;
    sc_start(1, SC_NS);
  }

  /* Closing Operation */
  u_bench->final();
  // Never comes here.
  delete u_bench;
  return 0;
}
EOF

2021-05-02: delete u_bench; を追加。count を下位からポートに持ち上げ。
2021-05-03: ”verilated.h" -> "verilated_sc.h" に修正。while の前に sc_start(); 追加。

この記述中の u_bench->final()return 0 の部分は無くても動作するのですが、行儀が悪いので記載しています。delete u_bench はメモリ リークを発生させない為に記載したつもりでしたが、実際にはこの行は通過しません。

上位階層のテストベンチ (C++ の場合) :

同じ内容を、C++ で記述する場合は次の通りとなります。 SystemC の場合と比べると、トップ階層としての信号の定義が不要となります。但し、波形ダンプ用の記述を追加する場合には、while 文の中で明示的に行わねばなりません。

cat から EOF までを ubuntu のターミナル上にコピー&ペーストすると、cframe.v というファイルが出来ます。

cat << "EOF" > cframe.v
#pragma onece
#include <iostream>
#include "verilated.h"
#include "obj_dir/Vbench.h"

int main(int argc, char* argv[]) {

  /* Command Line Arguments */
  // Verilated::commandArgs(argc, argv);

  /* Testbench */
  Vbench* u_bench = new Vbench();

  /* Clock and DumpTrigger */
  while (!Verilated::gotFinish()) {
    u_bench->clk = 1;
    u_bench->eval();
    u_bench->dumptrig = 0;
    u_bench->eval();
    u_bench->clk = 0;
    u_bench->eval();
    u_bench->dumptrig = 1;
    u_bench->eval();
  }

  /* Closing Operation */
  u_bench->final();
  // Never comes here.
  delete u_bench;
  return 0;
}
EOF

2021-05-02: delete u_bench; を追加。

Verilog HDL と SystemC を並べて比較:

上述の上位階層のテストベンチの、 Verilog HDL と SystemC での記述を左右に並べて比較してみます。

f:id:minettyo:20210501231035j:plain

シミュレーションするサンプル用の DUT (Device Under Test) の Verilog HDL 記述と ROM コードは、【こちらの別記事】と同様です。

コンパイルスクリプト

SystemC 版の Verilator のコンパイルと実行用のスクリプトは、次の様になります。

cat から EOF までを ubuntu のターミナル上にコピー&ペーストすると、scveri.sh というファイルが出来ます。

cat << "EOF" > scveri.sh
#!/bin/sh -x

if [ -e objdir/Vbench ]; then
  /bin/rm obj_dir/Vbench
fi

verilator -Wall -Wno-SYNCASYNCNET --sc bench.v dut.v --exe scframe.cpp

if [ $? -eq 0 ]; then
  cd obj_dir

  export OBJCACHE='ccache'
  export CXX='g++'
  export CXXFLAGS='-Og -g -gdwarf-2 -fPIC'
  export LINK='g++'
  export LDFLAGS=''
  time make -e -j --output-sync -f Vbench.mk

  if [ $? -eq 0 ]; then
    (cd ..; time obj_dir/Vbench)
  fi

  cd ..
fi
EOF

2021-05-08: export OBJCACHE='ccache' 追加

スクリプトは、次のコマンドで実行します。

chmod +x scveri.sh
./scveri.sh

シミュレーションの再実行や、ROM コードの入れ替えのみの場合には、次のコマンドで実行します。

./obj_dir/Vbench

【論理回路設計の目次へ戻る】   【WSL 関係の目次へ戻る】