当前位置:首页 > > AdriftCoreFPGA芯研社

1前言

对于许多FPGA/IC工程师而言,设计实现游刃有余,验证仿真却常成短板——传统验证方法面临两难困局:学习UVM需投入大量时间成本,而纯Verilog自仿又会陷入重复造轮子的低效循环。以通信协议仿真为例,仅报文解析就需要重写整套解析逻辑,相当于用Verilog再实现一次协议栈,耗时费力。
此时,Python的生态优势便锋芒尽显。其丰富的字符串处理库可直接解析报文,配合Cocotb框架,仅需少量Python代码即可构建高效测试平台,将验证工作量压缩70%以上。Cocotb的独特价值正在于此:用Python解放验证生产力,让工程师专注于设计创新而非重复劳动。

2阻塞与非阻塞

在cocotb中阻塞操作需要使用await,而非阻塞队列操作则不需要,如果阻塞不使用await,协程则会一直暂停,直到条件满足,我们可以类比为await操作相当于Verilog中的阻塞赋值=,而不使用await则类似于Verilog中的非阻塞赋值<=。当我们在cocotb中执行阻塞队列操作时,协程会在执行到队列操作时暂停,直到特定条件被满足为止,这时需要使用await来使协程等待。例如,await queue.get()会阻塞当前协程,直到队列中有数据可供获取。而如果没有使用await,协程将会一直停留在这个位置,无法继续执行后续操作,直到条件满足。可以类比为Verilog中的阻塞赋值=,即当某个操作完成之前,后续的语句都无法继续执行。

//Verilog中的阻塞赋值 always @(*)begin  b = a;  c = b; end  //Verilog中的非阻塞赋值 always @(posedge clk)begin  b <= a;  c <= b; end 
#python 阻塞 asyncdefassign_testa:  b = a  asyncdefassign_testb:  c = b  await assign_testa #等待testa执行完,再执行testb await assign_testa   #python 非阻塞 task1 = asyncio.create_task(assign_testa()) # 不会阻塞,马上开始 assign_testa task2 = asyncio.create_task(assign_testb()) # 不会阻塞,马上开始 assign_testb 

3Cocotb-Queue

我们可以把Queue类比为硬件中的FIFO,也是先进先出的结构。
Cocotb 提供了几种队列类型,用于协同多个生产者和消费者协程的操作。它们是基于 asyncio 的Queue类,具有异步操作特性,并且可以帮助你在测试中同步不同的协程之间的通信。

class cocotb.queue.Queue(maxsize=0)

maxsize:指定队列的最大容量。如果 maxsize 小于或等于 0,则队列大小无限制。如果 maxsize 大于 0,当队列达到该大小时,put() 方法会阻塞,直到有空间。

我们在使用队列时,需要首先定义一个Queue类

from cocotb.queue import Queue rx_queue = Queue() 

主要方法

async put(item)
将 item 放入队列。
如果队列已满,则会等待直到有空间可用。

put_nowait(item)
将 item 放入队列,不会阻塞。
如果队列已满,立即抛出 QueueFull 异常。

async get()
从队列中移除并返回一个项。
如果队列为空,会等待直到有项可以取出。

get_nowait()
从队列中移除并返回一个项。
如果队列为空,立即抛出 QueueEmpty 异常。

qsize()
返回队列中当前项的数量。

empty()
如果队列为空,返回 True,否则返回 False。

full()
如果队列已满,返回 True,否则返回 False。

4Queue有什么用

其实他类似于硬件FIFO中的功能

生产者(例如monitor)负责从硬件或仿真环境中接收数据,并将这些数据放入队列中。消费者(例如校验器)则从队列中取出数据进行处理。这个过程的关键在于队列的使用,它能够确保生产者和消费者之间不会直接依赖于对方的执行进度,从而避免了一个操作(如生产者)阻塞整个协程的情况。

如果没有队列,消费者需要直接等待生产者生成数据,这就意味着消费者的协程会一直被阻塞,直到生产者完成数据的生成。而使用队列后,即使生产者暂时没有数据可供消费,消费者仍然可以继续执行其他操作,只有在队列中有数据时,消费者才会从队列中取出并进行处理。这种解耦使得系统的执行更为灵活和高效。

在cocotb仿真过程中,如果要校验的数据数据量很大,建议使用Queue来完成校验数据的存取。我们通过receiver_monitor来入队响应报文,通过data_validator来出队响应报文,并校验报文是否正确

async def receiver_monitor(dut: SimHandle, sink: AxiStreamSink) -> None:   """异步接收协程"""  while True:  try:  frame = await sink.recv()  # 阻塞式接收  # rx_queue.put_nowait(frame)  # 非阻塞入队  await rx_queue.put(frame)  # 阻塞入队  except Exception as e:  dut._log.error(f"queue error: {e}")  raise  errors = [] async def data_validator(dut: SimHandle, frame_count: int) -None:  result = 0  while(result < frame_count):  frame = await rx_queue.get()  # 阻塞式出队  data = (int.from_bytes(frame.tdata, byteorder='little'))  if data != result:  errors.append(f"CHECK ERROR, got {data},the result is {hex(result)}")  dut._log.error(f"verilate fail :{data},the result is {hex(result)}")  else:  dut._log.info(f"CHECK PASS receive data is :{data}")  result = result + 1  @cocotb.test() async def axis_simple_test(dut: SimHandle):  ......  assert len(errors) == 0,"TEST FAIL" 

5写在最后

本文为原创Cocotb技术专栏,欢迎工程师伙伴们留言讨论或交流,共同学习。若想第一时间获取更新,可点击下方「关注」或订阅Cocotb专题。


本站声明: 本文章由作者或相关机构授权发布,目的在于传递更多信息,并不代表本站赞同其观点,本站亦不保证或承诺内容真实性等。需要转载请联系该专栏作者,如若文章内容侵犯您的权益,请及时联系本站删除。
关闭