FPGA基础 -- cocotb仿真之任务调度cocotb.start_soon与asyncio的使用注意事项

为什么在 cocotb 里不要asyncio、而要用 cocotb.start_soon()"讲透彻------从调度机制、时间语义、线程安全、异常传播与收尾、以及可替代方案全覆盖。

结论先行

  • cocotb 有自己的协程调度器 ,由仿真器(VPI/VHPI/FLI)事件驱动;不是 asyncio 的事件循环。
  • 仿真时间≠真实时间cocotb.Timer("10 ns") 推进的是模拟时间asyncio.sleep() 走的是墙钟时间,与仿真推进完全脱钩。
  • cocotb.start_soon() 把协程注册到 cocotb 调度器 ,能感知 RisingEdge/ReadOnly/ReadWrite仿真相位asyncio 完全不知道这些相位。
  • 生命周期与异常start_soon() 启的任务在测试结束会被自动取消/收尾 、异常会正确上抛asyncio 任务不会被 cocotb 管理,容易泄漏、卡住、吞异常
  • 单线程要求 :大多数仿真器 API 只能在主仿真线程 调用。asyncio 常见的跑法(单独事件循环/线程)会越线程调用仿真对象,直接未定义行为。

1) 调度模型:两个"世界"的事件循环不兼容

  • cocotb 调度器 :当你 await RisingEdge(sig)await Timer(10, "ns")await ReadOnly() 时,本质是把协程挂到仿真器的事件队列 上。仿真器到点触发回调,cocotb 才恢复协程。唯一时基模拟时间/相位
  • asyncio 调度器 :管理 Future/Task 的是 Python 的墙钟事件循环 。它既不认识 RisingEdge,也不认识 ReadOnly/ReadWrite,更无法在NBA 提交后保证你再读取信号。

结果 :你用 asyncio.create_task() 启的任务无法由仿真事件唤醒;反之,awaitRisingEdge 的 cocotb 协程也不会被 asyncio 驱动。两边是两套互不相干的"时空"


2) 时间语义:模拟时间 vs 真实时间

  • Timer(100, "ns"):推进仿真 100ns ,期间墙钟可能 0ms(Verilator 的零延时步进)或若干 ms。
  • asyncio.sleep(0.1)墙钟 100ms ,仿真时间可能没动 (尤其是事件驱动仿真)。
    → 常见灾难:你在 asyncio.sleep() 等"1ms 后再读数据",但仿真里下一拍都没到 ;或者你在 monitor 里用 asyncio.sleep(0) 当让步,结果仍在错相位读取,读不到 SOT/DT 等一次性字样。

3) 仿真相位:ReadOnly/ReadWrite 只能由 cocotb 感知

  • 你已经踩过一次坑:不加 ReadOnly() 在 NBA 落地前读取 hs_data_out,错过 0xB8B8B8B8
  • 这些相位触发(ReadOnly/ReadWrite/NextTimeStep)是 cocotb 对仿真器调度队列 的包装;asyncio 完全不认识,无法保证"在 NBA 之后读"、"在驱动区写"。

4) 线程与仿真器 API:只能主线程

  • 大多数 VPI/VHPI/FLI 后端必须在仿真主线程调用信号读写。
  • asyncio 常见做法是起一个独立 loop(甚至独立线程)→ 这会导致你在非仿真线程 里碰 dut.sig.value未定义行为(轻则读脏,重则崩仿真)。

5) 生命周期与异常传播

  • cocotb.start_soon(coro) → 返回 cocotb.task.Task

    • 随测试生命周期 自动管理:测试结束时自动取消未结束的子任务;
    • 子任务抛出的异常会关联到当前测试 ,让回归正确 fail
    • 你还能 await task显式 join
  • asyncio.create_task(coro)

    • cocotb 看不见 它,测试结束不回收
    • 异常可能被吞或仅在 Task 回收时打印 warning;
    • 容易在 CI 里造成间歇性挂死/泄漏

6) 可观测性与可重复性

  • cocotb 的调度顺序(不同 Trigger、不同 Task 之间)是为可重复/可验证设计的;
  • asyncio 的调度顺序受墙钟/事件循环实现影响,不可复现→ 回归不稳定。

7) 正确并发姿势:cocotb.start_soon() 模式

典型写法(也是你项目里应该遵循的套路):

python 复制代码
# 时钟
cocotb.start_soon(Clock(dut.tx_byte_clk, 8, units="ns").start())
cocotb.start_soon(Clock(dut.pixclk_o,   13.888, units="ns").start())

# Monitor(注意每拍 ReadOnly)
async def hs_monitor(dut, out_q):
    prev_active = False
    cur = []
    while True:
        await RisingEdge(dut.tx_byte_clk)
        await ReadOnly()
        active = int(dut.txclk_hsen.value) and (int(dut.txclk_hsgate.value) == 0)
        if active:
            cur.append(int(dut.hs_data_out.value))
        if prev_active and not active and cur:
            await out_q.put(cur)  # cocotb.queue.Queue
            cur = []
        prev_active = active

mon_task = cocotb.start_soon(hs_monitor(dut, my_queue))

# Driver(注意 ReadWrite/相位)
async def driver(dut):
    await RisingEdge(dut.tx_byte_clk)  # 对齐
    # 驱动 fv/lv/dvalid/pixdata...
    # 写之前可 await ReadWrite();或直接赋值,让 cocotb 放到写相位
drv_task = cocotb.start_soon(driver(dut))

# 等待或 join
await Timer(200, "us")
for t in (mon_task, drv_task):
    t.kill()  # 或 await t

8) 如果你必须asyncio(极少见)的替代方案

有些人想在 TB 里连网络/串口/文件异步 IO。建议:

  1. 子线程跑 asyncio loop ,与 cocotb 通过线程安全队列通信;
  2. 子线程禁止 直接读写 dut;把需要的操作封装成回调 ,回到主仿真线程 用 cocotb 触发(例如 cocotb.triggers.Timer(0) 安排一个"下一拍执行"的安全回调);
  3. 或者更简单:把外部 IO 做成同步阻塞 ,丢给 cocotb.extern 包一层,到主线程里再 await 返回值(仍需谨慎,避免卡住仿真推进)。

总之:任何触达仿真对象的操作都必须回到 cocotb 的调度上下文


9) 历史兼容:fork vs start_soon

  • 旧 API cocotb.fork() 早期就能用,但不返回 Task 对象(可控性差);
  • 新 API cocotb.start_soon() 返回 Task,支持 join()/kill()更安全、可管理
  • 现在统一用 start_soon()

小结

asyncio 和 cocotb 是两台互不连接的引擎 :一个按墙钟 调度,另一个按仿真事件 调度。把 TB 的并发交给 cocotb.start_soon(),才能获得正确的相位语义、可重复的调度、可控的生命周期与异常传播

当你采样像 0xB8B8B8B8 这种"一拍即逝"的字样时,RisingEdge + ReadOnly 搭配 start_soon() 的监控模式,才是可靠的专业做法。

相关推荐
FPGA小c鸡1 天前
【FPGA深度学习加速】RNN与LSTM硬件加速完全指南:从算法原理到硬件实现
rnn·深度学习·fpga开发
Aaron15881 天前
通信灵敏度计算与雷达灵敏度计算对比分析
网络·人工智能·深度学习·算法·fpga开发·信息与通信·信号处理
博览鸿蒙1 天前
IC 和 FPGA,到底区别在哪?
fpga开发
思尔芯S2C1 天前
FPGA原型验证实战:如何应对外设连接问题
fpga开发·risc-v·soc设计·prototyping·原型验证
Flamingˢ1 天前
FPGA实战:VGA成像原理、时序详解与Verilog控制器设计与验证
fpga开发
FPGA_小田老师1 天前
xilinx原语:OSERDES2(并串转换器)原语详解
fpga开发·lvds·xilinx原语·oserdese·并串转换
Blossom.1181 天前
从数字大脑到物理实体:具身智能时代的大模型微调与部署实战
人工智能·python·深度学习·fpga开发·自然语言处理·矩阵·django
漂洋过海的鱼儿2 天前
HLS (High-Level Synthesis)对比PS运行速度
fpga开发
Aaron15882 天前
无线信道下的通信链路设计分析
大数据·网络·人工智能·算法·fpga开发·硬件工程·射频工程
碎碎思2 天前
当 FPGA 遇上 Python:Glasgow 如何玩转数字接口(开源硬件 & 软件)
fpga开发