これまでVerilog側からDPIを経由してC++の関数を呼び出したりする方法について調査した。 しかしよく考えたらDPIを経由してC++側の実装からVerilogのFunctionやTaskを呼び出す方法についてはあまり調べたことが無かったのでやってみることにする。
前回同様counter_4bit
のデザインを使うが、今回はC++側から適当なVerilogのFunctionを呼ぶのであまりモジュール自体は関係ない。
以下のようにしてcounter_4bit.v
の中にdpi_verilog_task
を定義した。
単純に$display()
で文字を出力するためのタスクだ。
counter_4bit.v
logic [3:0] cnt ); int return_value; task dpi_verilog_task(input int in); $display("dpi_verilog_task is called. in = %d", in); endtask // dpi_verilog_task // Function from Verilog Task export "DPI-C" task dpi_verilog_task; always_ff @(posedge clk, negedge reset_n) begin if (!reset_n) begin cnt <= 4'h0; end else begin if (en) begin cnt <= cnt + 4'h1; end end end endmodule // counter_4bit
次はこれを呼び出すためのC++コードを実装する。tb_counter_4bit.cpp
に以下のようにコードを追加した。
tb_counter_4bit.cpp
#include <iostream> #include <verilated.h> #include <verilated_fst_c.h> #include "svdpi.h" #include "Vcounter_4bit.h" #include "Vcounter_4bit__Dpi.h" ... int main(int argc, char** argv) { ... // Instantiate DUT Vcounter_4bit *dut = new Vcounter_4bit(); ... svSetScope(svGetScopeFromName("counter_4bit")); ... int cycle = 0; while (time_counter < 500 && !Verilated::gotFinish()) { if ((time_counter % 5) == 0) { dut->clk = !dut->clk; // Toggle clock ... if (cycle % 5 == 0) { dut->en = 1; // Assert En dut->dpi_verilog_task(cycle); printf("dpi_verilog_task is called. verilog_task_value"); ...
これでVerilog側のタスクを呼び出せるはずだ。コンパイルして実行してみよう。
verilator --cc --exe --trace-fst --trace-params --trace-structs --trace-underscore -CFLAGS "-g" counter_4bit.v -exe tb_counter_4bit.cpp dpi_counter.cpp ./obj_dir/Vcounter_4bit
すると以下のエラーメッセージが表示されて実行が停止してしまった。
%Error: unknown:0: Testbench C called 'dpi_verilog_task' but scope wasn't set, perhaps due to dpi import call without 'context', or missing svSetScope. See IEEE 1800-2017 35.5.3. Aborting...
ネット上でいろいろ情報を調査したがイマイチ解決方法が分からない。 もう少し調査すべきか。。。あるいはソースコードを読むべきか。
GDBを挿入して調べた結果、
./obj_dir/Vcounter_4bit run bt
(gdb) bt #0 __GI_raise (sig=sig@entry=6) at ../sysdeps/unix/sysv/linux/raise.c:50 #1 0x00007fffff1f2535 in __GI_abort () at abort.c:79 #2 0x0000000008005e66 in vl_fatal(char const*, int, char const*, char const*) () #3 0x0000000008005f76 in VL_FATAL_MT(char const*, int, char const*, char const*) () #4 0x0000000008010df0 in VerilatedScope::exportFindNullError(int) () #5 0x000000000803e47c in VerilatedScope::exportFind (scopep=0x0, funcnum=0) at /usr/local/share/verilator/include/verilated.h:334 #6 0x000000000803e136 in Vcounter_4bit::dpi_verilog_task (in=5) at Vcounter_4bit.cpp:110 #7 0x0000000008004dd7 in main (argc=1, argv=0x7ffffffed778) at ../tb_counter_4bit.cpp:53
ウームやはりdpi_verilog_task
の呼び出し部分で落ちていることは分かる。
Vcounter_4bit.cpp
void Vcounter_4bit::dpi_verilog_task(int in) { VL_DEBUG_IF(VL_DBG_MSGF("+ Vcounter_4bit::dpi_verilog_task\n"); ); // Variables IData/*31:0*/ in__Vcvt; // Body static int __Vfuncnum = -1; if (VL_UNLIKELY(__Vfuncnum==-1)) { __Vfuncnum = Verilated::exportFuncNum("dpi_verilog_task"); } const VerilatedScope* __Vscopep = Verilated::dpiScope(); Vcounter_4bit__Vcb_dpi_verilog_task_t __Vcb = (Vcounter_4bit__Vcb_dpi_verilog_task_t)(VerilatedScope::exportFind(__Vscopep, __Vfuncnum)); in__Vcvt = in; (*__Vcb)((Vcounter_4bit__Syms*)(__Vscopep->symsp()), in__Vcvt); }
さらにverilated.h
の中身をのぞいてみる。
/usr/local/share/verilator/include/verilated.h
static inline void* exportFind(const VerilatedScope* scopep, int funcnum) VL_MT_SAFE { if (VL_UNLIKELY(!scopep)) return exportFindNullError(funcnum); if (VL_LIKELY(funcnum < scopep->m_funcnumMax)) { // m_callbacksp must be declared, as Max'es are > 0 return scopep->m_callbacksp[funcnum]; } else { // LCOV_EXCL_LINE return scopep->exportFindError(funcnum); // LCOV_EXCL_LINE } }
呼ばれ方からして、scopep
がNULLになっているのが原因で落ちているようだ。
戻ってみると、__Vscopep
はVerilated::dpiScope()
から呼び出されている。
これが設定されていないのが問題なのか?
if (VL_UNLIKELY(__Vfuncnum==-1)) { __Vfuncnum = Verilated::exportFuncNum("dpi_verilog_task"); } const VerilatedScope* __Vscopep = Verilated::dpiScope(); Vcounter_4bit__Vcb_dpi_verilog_task_t __Vcb = (Vcounter_4bit__Vcb_dpi_verilog_task_t)(VerilatedScope::exportFind(__Vscopep, __Vfuncnum));
うーん、これはdpiContext()
で設定するのだろうか。
static void dpiContext(const VerilatedScope* scopep, const char* filenamep, int lineno) VL_MT_SAFE { t_s.t_dpiScopep = scopep; t_s.t_dpiFilename = filenamep; t_s.t_dpiLineno = lineno; }