我想使用 VPI 编写一个 Verilog 任务来对接口进行 bit-bang;但是,我无法弄清楚如何从 VPI 任务中提前模拟时间。我目前正在使用 Mentor Graphics Questa,但也可以访问 Icarus Verilog。我已经成功地在我有兴趣控制的端口上强制了值,但模拟时间并没有提前,即使在 vpi_put_value 上指定了延迟。
这一般是如何实现的?
我想使用 VPI 编写一个 Verilog 任务来对接口进行 bit-bang;但是,我无法弄清楚如何从 VPI 任务中提前模拟时间。我目前正在使用 Mentor Graphics Questa,但也可以访问 Icarus Verilog。我已经成功地在我有兴趣控制的端口上强制了值,但模拟时间并没有提前,即使在 vpi_put_value 上指定了延迟。
这一般是如何实现的?
您不会从 VPI(例如,从始终块)提前模拟时间 - 这会弄乱调度程序。您的 VPI 代码在零模拟时间内被调用;您创建新事务,并告诉调度程序何时对这些事务采取行动。您通常使用参数 3 和 4 来创建新事务vpi_put_value
(例如,指定阻塞或非阻塞,或者在将来的某个时间)。这基本上正是您在 process/always 块中所做的事情 - 一旦您返回控制权,调度程序就会计算出要做什么。
因此,简而言之,使用您的 C 代码来安排未来(或立即)事务,然后返回,以便调度程序可以确定要做什么。您不能在 C 代码中间放置(模拟)等待。我认为,在您的情况下,您想要做的是在 Verilog 中有一个主控制循环#10
,并在适当的时间调用您的 C 代码。
编辑
vpi_put_delay
回复您的评论:我认为如果您尝试在返回之前以不同的延迟多次调用它会做什么,我认为没有任何规范;您只看到最后一次延迟,这并不让我感到惊讶。或许有点像在一个总挡中对同一个对象有多个 NBA;最后一个获胜。如果您真的想在返回之前在同一个对象上安排多个事务,请使用vpi_put_delays
, 并列出多个延迟(Sutherland 中的示例,p194)。
但有什么意义呢?这实质上为您提供了 VHDLafter
功能,以及交易列表。它不会提前模拟时间。您同时将所有事务添加到事件队列。您无法在“稍后”模拟时间读取任何其他对象的值,因为没有更晚的模拟时间。请参阅 2005 LRM 中的 5.6.4(不是 SystemVerilog LRM;它是一种不同的语言)。这就像在一个总是块中具有多个 RHS 延迟的非阻塞分配 - 所有 RHS 操作数都被立即读取,没有提前时间,但 LHS 更新被安排在以后。
忘记提前的时间。您不能这样做,因为您会弄乱调度程序队列中的所有其他内容。如果其他人在您提前的中间安排了更新怎么办?您需要将控制权交还给 Verilog 代码(即交还给调度程序),以便调度程序可以提前时间。
我知道这个答案有点晚了,但我不确定之前的任何答案都解决了您的原始要求,尽管它们并不正确。
您不能vpi_put_value
在一个模拟步骤中多次调用同一个句柄来及时排队值。只有最后的调用才会有任何效果。
你想要做的是调用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;
如果您的总线与时钟同步,您可能只想使用 a 驱动时钟cbAfterDelay
(或从 Verilog 中的进程驱动它)并推进您的驱动程序状态cbValueChange
,在时钟本身上注册 a。这将模拟在模拟器中运行的时钟进程。
如果您熟悉 Python,您可能会对名为Cocotb的开源项目感兴趣,该项目将VPI 抽象为您在模拟器中运行的被测设备 (DUT) 提供了一个很好的 Pythonic 接口。如果您有想要接口的现有 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开发人员之一。
我找不到任何直接的例子。我能够在 LRM 中找到几个提到与 VPI 相关的“模拟时间”的地方。符合 LRM 是可能的;IEEE Std 1800-2012第 36.9.2 节第 2 段规定:
“VPI 模拟回调工具应为应用程序提供与工具动态交互的方法,检测值变化的发生、时间提前、模拟结束等。”
以下是其他时间作为 VPI 提及的地方:
如果一切都失败了,请尝试 SystemC ( IEEE Std 1666-2011 )。http://www.asic-world.com/systemc/systemc_time.html上的教程和第 3 部分的直接示例。