インターフェイスをビットバンするために VPI を使用して Verilog タスクをコーディングしたいと考えています。ただし、VPI タスクからシミュレーション時間を進める方法がわかりません。現在 Mentor Graphics Questa を使用していますが、Icarus Verilog にもアクセスできます。制御したいポートの値を正常に強制しましたが、vpi_put_value で遅延が指定されていても、シミュレーション時間が進みません。
これは一般的にどのように達成されますか?
インターフェイスをビットバンするために VPI を使用して Verilog タスクをコーディングしたいと考えています。ただし、VPI タスクからシミュレーション時間を進める方法がわかりません。現在 Mentor Graphics Questa を使用していますが、Icarus Verilog にもアクセスできます。制御したいポートの値を正常に強制しましたが、vpi_put_value で遅延が指定されていても、シミュレーション時間が進みません。
これは一般的にどのように達成されますか?
VPI (または、always ブロックなど) からシミュレーション時間を進めないでください。これは、スケジューラを台無しにします。VPI コードはシミュレーション時間ゼロで呼び出されます。新しいトランザクションを作成し、それらのトランザクションをいつ処理するかをスケジューラに伝えます。通常は、パラメーター 3 と 4 を使用して新しいトランザクションを作成しvpi_put_value
ます (たとえば、ブロックまたは非ブロック、または将来のある時点を指定するため)。これは基本的にプロセス/常にブロックで行うこととまったく同じです。制御を返すと、スケジューラーは何をすべきかを解決します。
つまり、C コードを使用して将来の (または即時の) トランザクションをスケジュールし、それから戻ることで、スケジューラーが何をすべきかを判断できるようになります。C コードの途中に (シミュレーション) 待機を配置することはできません。あなたの場合、あなたがやりたいことは、Verilog にメインの制御ループを用意#10
し、適切なタイミングで C コードを呼び出すことだと思います。
編集
あなたのコメントについて:vpi_put_delay
戻る前に異なる遅延で複数回呼び出そうとするとどうなるかについての仕様はないと思います。最後の遅延だけが表示されていることは驚くことではありません。おそらく、always ブロック内の同じオブジェクトに複数の NBA を設定するのと少し似ています。最後の1つが勝ちます。戻る前に同じオブジェクトで複数のトランザクションをスケジュールしたい場合はvpi_put_delays
、複数の遅延のリストとともに を使用します (Sutherland の例、p194)。
しかし、ポイントは何ですか?これにより、トランザクションのリストを含む VHDLafter
機能が提供されます。シミュレーション時間は進みません。すべてのトランザクションを同時にイベント キューに追加しています。後のシミュレーション時間がないため、「後の」シミュレーション時間で他のオブジェクトの値を読み取ることはできません。2005 LRM の 5.6.4 を参照してください (SystemVerilog LRM ではなく、別の言語です)。これは、always ブロックに RHS 遅延を伴う複数のノンブロッキング割り当てがあるようなものです。すべての RHS オペランドは、時間を進めることなくすぐに読み取られますが、LHS の更新は後でスケジュールされます。
時間の進みを忘れてください。スケジューラ キューの他のすべてを台無しにしてしまうため、これを行うことはできません。あなたのタイムアドバンスの途中で他の誰かが更新をスケジュールした場合はどうなりますか? スケジューラが時間を進めることができるように、制御を Verilog コード (つまり、スケジューラ) に戻す必要があります。
この回答が少し遅れていることは承知していますが、以前の回答のいずれかがあなたの元のリクエストに対応しているかどうかはわかりませんが、それらは間違っていません.
vpi_put_value
1 つのシミュレーション ステップで同じハンドルを複数回呼び出して、時間内に値をキューに入れることはできません。最後の呼び出しだけが効果があります。
あなたがしたいことはvpi_put_value
、今必要な値を強制するために呼び出しcbAfterDelay
、次に値を変更したいときに を使用してコールバックを登録することです。コールバック ハンドラが実行されると、シミュレーション時間がその時点まで進んでいるため、ハンドルに次の値を強制できます。
コールバック関数は次のようになります。
static int32_t handle_vpi_callback(p_cb_data cb_data) {
// advance your driver state using user_data to retrieve current state
// call vpi_put_value with new signal value
// determine next time value requiring action
// register another callback
}
時限コールバックを登録するには:
s_cb_data cb_data_s;
s_vpi_time vpi_time_s;
p_vpi_cb_user_data user_data;
vpi_time_s.type = vpiSimTime;
vpi_time_s.high = (uint32_t)(time_ps>>32);
vpi_time_s.low = (uint32_t)(time_ps);
cb_data_s.reason = cbAfterDelay;
cb_data_s.cb_rtn = handle_vpi_callback;
cb_data_s.obj = NULL;
cb_data_s.time = &vpi_time_s;
cb_data_s.value = NULL;
cb_data_s.user_data = (char *)user_data;
vpiHandle new_hdl = vpi_register_cb(&cb_data_s);
これは、Verilog コードと同じ動作になります。
#10 value = something;
#5 value = something_else;
バスがクロックに同期している場合は、 を使用してクロックのみを駆動cbAfterDelay
(または Verilog のプロセスから駆動)cbValueChange
し、クロック自体に を登録してドライバーの状態を進めたい場合があります。これにより、シミュレーターで実行されているクロック プロセスが模倣されます。
Python に精通している場合は、VPI を抽象化して、シミュレーターで実行されているテスト対象デバイス (DUT) に優れた Python インターフェイスを提供するCocotbというオープンソース プロジェクトに興味があるかもしれません。インターフェイスにしたい既存の C++ コードがある場合は、Python を使用すると時間を節約できます。
たとえば、インターフェイスを介してバッファーを送信するには、ドライバーの送信ルーチンは次のようになります。
def send(dut, buffer):
"""Send a buffer over a packetised bus"""
bus_width = len(dut.data) / 8
firstword = True
while buffer:
yield RisingEdge(dut.clk)
nbytes = min(len(buffer), bus_width)
dut.data.value = buffer[:nbytes]
dut.valid.value = 1
dut.startofpacket.value = int(firstword)
if nbytes <= buswidth:
dut.endofpacket.value = 1
dut.empty.value = bus_width - len(buffer)
buffer = ""
else:
buffer = buffer[buswidth:]
firstword = False
yield RisingEdge(dut.clk)
dut.valid.value = 0
dut.endofpacket.value = 0
免責事項: 私はCocotb開発者の 1 人です。