FPGA開発日記

カテゴリ別記事インデックス https://msyksphinz.github.io/github_pages , English Version https://fpgadevdiary.hatenadiary.com/

VerilatorでDPIを使ってC++側からVerilogのFunctionやTaskを呼ぶための方法調査

これまで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になっているのが原因で落ちているようだ。 戻ってみると、__VscopepVerilated::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; }