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() 的监控模式,才是可靠的专业做法。

相关推荐
s09071366 小时前
ZYNQ DMA to UDP 数据传输系统设计文档
网络协议·fpga开发·udp
燎原星火*8 小时前
QSPI IP核 基本参数
fpga开发
XINVRY-FPGA8 小时前
XCVU9P-2FLGC2104I Xilinx AMD Virtex UltraScale+ FPGA
嵌入式硬件·机器学习·计算机视觉·fpga开发·硬件工程·dsp开发·fpga
FPGA_小田老师8 小时前
FPGA Debug:PCIE一直自动重启(link up一直高低切换)
fpga开发·pcie debug·pcie初始化问题
hexiaoyan8278 小时前
视频信号检测板卡:208-Base Camera Link 图像信号模拟器
fpga开发·图像信号模拟器·视频信号检测·视频信号分析·智能图像分析
竹君子8 小时前
新能源知识库(151) RTDS和RT-LAB比较
fpga开发
brave and determined12 小时前
可编程逻辑器件学习(day34):半导体编年史:从法拉第的意外发现到塑造现代文明的硅基浪潮
人工智能·深度学习·fpga开发·verilog·fpga·设计规范·嵌入式设计
FPGA_Linuxer13 小时前
RFSOC PCIE 4.0读写测试
fpga开发
坏孩子的诺亚方舟14 小时前
FPGA系统架构设计实践8_复位参考设计
fpga开发·系统架构·复位
li星野14 小时前
打工人日报#20251124
fpga开发