最終更新:2021-05-08
Verilator は Verilog HDL をサイクルベース方式で高速に動かす無償のシミュレータです。但、最上位モジュールは C++ または SystemC で記述する必用があります。本記事では、最上位のテストベンチを SystemC で記述する方法を、Verilog HDL の記述と1対1に比較して紹介します。
尚、シミュレーションの実行には、Windows 10 上の WSL で動いて居る Ubuntu-20.04 の標準パッケージのみを使用します。
目次: Verilator は Verilog HDL をサイクルベース方式で高速に動かす無償のシミュレータです。但、従来型のイベントドリブン方式のシミュレータと比べると若干の制約が有ります。【こちらの別記事】では最上位のテストベンチのみを C++ で記述する方法を紹介しましたが、本記事では、それを SystemC で記述する方法を紹介します。従来型のイベントドリブン方式の Verilog シミュレータを使用していた方が、違和感無く移行出来る事を目指します。 Verilator には、最上位モジュールは C++ または SystemC で記述せねばならないと言う制約が有る為、Verilog HDL で記載されたテストベンチを2つに分けます。上位階層のテストベンチには必用最小限の記述を移行し、下位階層のテストベンチは Verilog HDL のままとしますが、動作はサイクル カウントを元に記述します。 サンプルとして動かす回路は、別記事で紹介したパイプライン ROM をそのまま使用します。テストベンチでロードする ROM コードも同様です。 WSL の Ubuntu-20.04 を使用している場合には、次のコマンドで SystemC をインストールします。 【こちらの別記事】では、Verilog HDL で記述した下位階層のテストベンチにはポートを作らず、最上位階層の C++ 記述から階層を跨いで信号をドライブしていました。しかし、SystemC で記述する場合は、ドライブする信号を素直に上位階層へポート接続した方が都合が良いので Verilog HDL を修正します。修正した下位階層の Verilog HDL は次の通りです。最上位階層からドライブする cat から EOF までを ubuntu のターミナル上にコピー&ペーストすると、bench.v というファイルが出来ます。 2021-05-02: count を出力ポートに接続。ポートの書式変更。 上位階層のテストベンチの Verilog HDL は、次の通りとなります。always 文の中で、クロックの1サイクル分を記述しています。1サイクル ピリオドを1000タイムスケールとし、 cat から EOF までを ubuntu のターミナル上にコピー&ペーストすると、frame.v というファイルが出来ます。 2021-05-02: count を下位からポートに持ち上げ。 前記の上位階層のテストベンチを、Verilog HDL と出来るだけ1対1となるよう SystemC に置き換えます。クロック波形の定義は、通常の SystemC の記述方法である cat から EOF までを ubuntu のターミナル上にコピー&ペーストすると、scframe.v というファイルが出来ます。 2021-05-02: delete u_bench; を追加。count を下位からポートに持ち上げ。 この記述中の 同じ内容を、C++ で記述する場合は次の通りとなります。
SystemC の場合と比べると、トップ階層としての信号の定義が不要となります。但し、波形ダンプ用の記述を追加する場合には、 cat から EOF までを ubuntu のターミナル上にコピー&ペーストすると、cframe.v というファイルが出来ます。 2021-05-02: delete u_bench; を追加。 上述の上位階層のテストベンチの、 Verilog HDL と SystemC での記述を左右に並べて比較してみます。
シミュレーションするサンプル用の DUT (Device Under Test) の Verilog HDL 記述と ROM コードは、【こちらの別記事】と同様です。 SystemC 版の Verilator のコンパイルと実行用のスクリプトは、次の様になります。 cat から EOF までを ubuntu のターミナル上にコピー&ペーストすると、scveri.sh というファイルが出来ます。 2021-05-08: export OBJCACHE='ccache' 追加 スクリプトは、次のコマンドで実行します。 シミュレーションの再実行や、ROM コードの入れ替えのみの場合には、次のコマンドで実行します。
前提条件:
概要:
Ubuntu-20.04 への SystemC と ccache のインストール
また、ビルドの高速化の為に、ccache をインストールします。sudo apt update
sudo apt install libsystemc-dev
sudo apt install ccache
ドライブする信号を SystemC の階層に持ち上げる:
下位階層のテストベンチ (Verilog HDL のみ):
clk
と dumptrig
の2つの信号を入力ポートとします。また、count
は後々波形をダンプする時のタイムスタンプとして使用しますので出力ポートに接続しておきます。clk
は同期式回路用の通常のクロック、dumptrig
は回路内部をテキストダンプする時用のトリガです。また、count
という信号でクロック サイクル数をカウントしています。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
上位階層のテストベンチ (Verilog HDLの場合) :
#
による遅延記述でクロック波形を定義しています。
回路の内部状態をテキストダンプする為のダンプトリガーは、クロックの立ち上がりの直前に設定しています。これは、クロック同期式の回路を論理シミュレーションする場合の一般的な手法です。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
上位階層のテストベンチ (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" > 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-03: ”verilated.h" -> "verilated_sc.h" に修正。while の前に sc_start(); 追加。u_bench->final()
と return 0
の部分は無くても動作するのですが、行儀が悪いので記載しています。delete u_bench
はメモリ リークを発生させない為に記載したつもりでしたが、実際にはこの行は通過しません。上位階層のテストベンチ (C++ の場合) :
while
文の中で明示的に行わねばなりません。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
Verilog HDL と SystemC を並べて比較:
コンパイル用スクリプト:
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
chmod +x scveri.sh
./scveri.sh
./obj_dir/Vbench