これまでVerilog側からDPIを経由してC++の関数を呼び出したりする方法について調査した。
しかしよく考えたらDPIを経由してC++側の実装からVerilogのFunctionやTaskを呼び出す方法についてはあまり調べたことが無かったのでやってみることにする。
前回同様counter_4bit
のデザインを使うが、今回はC++側から適当なVerilogのFunctionを呼ぶのであまりモジュール自体は関係ない。
以下のようにしてcounter_4bit.v
の中にdpi_verilog_task
を定義した。
単純に$display()
で文字を出力するためのタスクだ。
logic [3:0] cnt
);
int return_value;
task dpi_verilog_task(input int in);
$display("dpi_verilog_task is called. in = %d", in);
endtask
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
次はこれを呼び出すためのC++コードを実装する。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) {
...
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;
...
if (cycle % 5 == 0) {
dut->en = 1;
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
ウームやはりdpi_verilog_task
の呼び出し部分で落ちていることは分かる。
void Vcounter_4bit::dpi_verilog_task(int in) {
VL_DEBUG_IF(VL_DBG_MSGF("+ Vcounter_4bit::dpi_verilog_task\n"); );
IData in__Vcvt;
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)) {
return scopep->m_callbacksp[funcnum];
} else {
return scopep->exportFindError(funcnum);
}
}
呼ばれ方からして、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; }