跳到主要内容 cocotb平台用VCS仿Xilinx FPGA | 极客日志
Python
cocotb平台用VCS仿Xilinx FPGA 文章目录 概要 建立cocotb仿真VIP库 调用VIP库仿Xilinx IP 1\. VIVIDO生成IP,完成设计。 2\. 写python仿真代码 3、编写Makefile 4、运行仿真,看波形 概要 基于cocotb框架的AXI Stream接口验证方法。主要内容包括:1)开发AXIS VIP库,实现字节级数据发送(axis\_tx\_byte)、随机接收(axis\_rx)和总…
开源信徒 发布于 2026/4/6 更新于 2026/4/18 76K 浏览文章目录
概要
本文介绍了基于cocotb框架的AXI Stream接口验证方法。主要内容包括:1)开发AXIS VIP库,实现字节级数据发送(axis_tx_byte)、随机接收(axis_rx)和总线监控(axis_monitor_byte)功能;2)以Xilinx AXIS FIFO为例,展示VIP库的调用方法,包括测试平台搭建、数据生成和自动验证机制。该方案支持LSB配置,能模拟真实硬件背压情况,适用于AXIS接口模块的功能验证。代码提供完整的仿真环境,包含时钟复位控制、参考模型和计分板等组件,详细解析完整代码和Makefile文件。
建立cocotb仿真VIP库
例如新增一个axis.py文件,实现一个AXI Stream(AXIS)接口的驱动和监控功能,主要用于硬件验证(如使用cocotb框架)。核心功能包括数据发送、接收和监控,支持字节级操作。
import logging import math import copy import random from cocotb.triggers import RisingEdge from random_number import random_number classaxis:def__init__ (self,signal,lsb=1 ): self.log = logging.getLogger ("cocotb.tb" ) self.log.setLevel (logging.DEBUG) self.signal= signal self.lsb=lsb self.axis_monitor_data_byte=[] self.axis_monitor_data_fixed=[]asyncdefaxis_tx_byte (self,tx_data_byte): self.signal.tvalid.value =0 self.signal.tkeep.value =0 self.signal.tlast.value =0 tx_data=copy.deepcopy (tx_data_byte) tkeep_width=len (self.signal.tdata)//8 data_width =len (self.signal.tdata) temp = math.ceil (len (tx_data)/tkeep_width) tkeep=0 if temp*tkeep_width>len (tx_data):for i inrange (temp*tkeep_width-len (tx_data)): tx_data.append ( ) tkeep=tkeep+ (self.lsb== ): tkeep=(( **tkeep_width- )<<tkeep)&( **tkeep_width- )else: tkeep=(( **tkeep_width- )>>tkeep)&( **tkeep_width- ) data=[]for i (temp): data_temp= for m (tkeep_width):if self.lsb== : data_temp = data_temp +(tx_data[i*tkeep_width+m]<<(data_width- -m* ))else: data_temp = data_temp +(tx_data[i*tkeep_width+m]<<(m* )) data. (data_temp)# self.log. ( ( (hex,data)))for i ( (data)): self.signal.tvalid.value = self.signal.tdata.value = data[i]if i == (data)- : self.signal.tlast.value = self.signal.tkeep.value = tkeep else: self.signal.tlast.value = self.signal.tkeep.value = **tkeep_width- await (self.signal.aclk)while self.signal.tready.value == :await (self.signal.aclk) self.signal.tvalid.value = (self,tready_interval_max= ):whileTrue: tready_interval=random. ( , tready_interval_max) self.signal.tready.value = await (self.signal.aclk) self.signal.tready.value = for i (tready_interval):await (self.signal.aclk) (self): data=[] tkeep_width= (self.signal.tdata)// data_width = (self.signal.tdata)whileTrue:await (self.signal.aclk) (self.signal.tready.value == ) (self.signal.tvalid.value == ): temp=self.signal.tdata.value for m (tkeep_width):if self.lsb== : (self.signal.tkeep.value&( <<(tkeep_width- -m))): data. (((temp>>(data_width- -m* ))& xff))else: (self.signal.tkeep.value&( <<m)): data. ((temp>>(m* ))& xff)if self.signal.tlast.value == : self.axis_monitor_data_byte. (data) data=[]
微信扫一扫,关注极客日志 微信公众号「极客日志」,在微信中扫描左侧二维码关注。展示文案:极客日志 zeeklog
相关免费在线工具 curl 转代码 解析常见 curl 参数并生成 fetch、axios、PHP curl 或 Python requests 示例代码。 在线工具,curl 转代码在线工具,online
Base64 字符串编码/解码 将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
Base64 文件转换器 将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online
Markdown转HTML 将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online
HTML转Markdown 将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online
JSON 压缩 通过删除不必要的空白来缩小和压缩JSON。 在线工具,JSON 压缩在线工具,online
0
1
if
0
2
1
2
1
2
1
2
1
inrange
0
inrange
0
8
8
8
append
info
list
map
inrange
len
1
len
1
1
0
2
1
RisingEdge
0
RisingEdge
0
asyncdefaxis_rx
8
randint
0
1
RisingEdge
0
inrange
RisingEdge
asyncdefaxis_monitor_byte
len
8
len
RisingEdge
if
1
and
1
inrange
0
if
1
1
append
8
8
0
if
1
append
8
0
1
append
AXIS接口发送(axis_tx_byte)
初始化信号状态为无效(tvalid=0),清空tkeep和tlast。将输入字节数组复制到临时变量tx_data中。
计算tkeep宽度(基于tdata信号位宽)和数据总宽度。根据输入数据长度计算需要多少个时钟周期传输完整数据。
处理数据对齐问题:若数据长度不是tkeep_width的整数倍,填充0并计算tkeep掩码。根据LSB(最低有效位)配置方向生成不同的掩码模式。
将字节数组转换为AXIS数据格式:每个时钟周期填充tkeep_width字节数据,按LSB配置决定字节序。在最后一个周期置tlast信号,并根据数据对齐情况设置tkeep。
AXIS接口接收(axis_rx)
模拟从设备就绪信号(tready)行为:随机间隔后置tready为1,保持一个周期后随机延迟0到tready_interval_max个周期。这种随机性模拟真实硬件中的背压情况。
AXIS数据监控(axis_monitor_byte)
持续监控AXIS总线:当tvalid和tready同时有效时,根据tkeep信号提取有效字节数据。根据LSB配置决定字节提取顺序,使用tlast信号判断数据包边界。完整数据包存入axis_monitor_data_byte列表。
调用VIP库仿Xilinx IP
1. VIVIDO生成IP,完成设计。
2. 写python仿真代码 代码功能分析
该代码是一个基于Cocotb框架的AXIS(AXI Stream)接口测试环境,用于验证AXIS FIFO模块的功能。主要包含测试平台搭建、数据生成、参考模型、监视器和计分板等组件。
初始化时钟、复位、AXIS从接口和主接口
提供复位控制、数据生成、参考模型等功能
包含监视器和计分板用于自动验证
classTB:def__init__ (self, dut): self.dut = dut self.log = logging.getLogger ("cocotb.tb" ) self.log.setLevel (logging.DEBUG) cocotb.start_soon (Clock (dut.s_axis_aclk,10 , units="ns" ).start ()) self.axis_slave_if = axis (signal_axis_slave (dut)) self.axis_master_if = axis (signal_axis_master (dut)) self.data_drv=[] self.data_mon=[]
关键测试流程
复位序列 通过控制复位信号确保DUT处于已知状态:
asyncdefreset(self ): self .dut.s_axis_tvalid.value =0 self .dut.s_axis_tdata .value =0 self .dut.s_axis_tkeep .value =0 self .dut.s_axis_tlast .value =0 self .dut.m_axis_tready.value =0 self .dut.s_axis_aresetn.setimmediatevalue(1 )await RisingEdge(self .dut.s_axis_aclk)await RisingEdge(self .dut.s_axis_aclk) self .dut.s_axis_aresetn.value =0 await Timer(1 , units="us" )await RisingEdge(self .dut.s_axis_aclk)await RisingEdge(self .dut.s_axis_aclk) self .dut.s_axis_aresetn.value =1
defseq_model(self,length): data =[]for i inrange(length): data .append(random.randint(0 ,2 **8 -1 ))return data
asyncdefscoreboard(self ): i=0 whileTrue:await RisingEdge(self .dut.s_axis_aclk)if (len (self .data_drv)>0 )and (len (self .data_mon)>0 ):if (self .data_drv[0 ]!=self .data_mon[0 ]):print ("data_drv[%d]=" %(i),list (map (hex ,self .data_drv[0 ])))print ("data_mon[%d]=" %(i),list (map (hex ,self .data_mon[0 ])))assert self .data_drv[0 ]==self .data_mon[0 ] self .log.info("The test %d is success,length is %d !!!" ,i,len (self .data_mon[0 ])) self .data_drv.pop(0 ) self .data_mon.pop(0 ) i=i+1
测试用例执行
run_test 函数组织完整的测试流程:
初始化测试环境
执行复位
启动监视和验证协程
发送测试数据
等待验证完成
asyncdefrun_test (dut,length,tready_interval_max): tb=TB (dut)await tb.reset () cocotb.start_soon (tb.axis_slave_if.axis_rx (tready_interval_max=tready_interval_max))await Timer (1 , units="us" )await RisingEdge (dut.s_axis_aclk) cocotb.start_soon (tb.axis_slave_if.axis_monitor_byte ()) cocotb.start_soon (tb.monitor ()) cocotb.start_soon (tb.scoreboard ())for i inrange (len (length)): temp = tb.seq_model (length[i])await tb.axis_master_if.axis_tx_byte (temp) tb.data_drv.append (tb.ref_model (temp))
测试工厂配置
通过TestFactory创建参数化测试:
factory = TestFactory(run_test) factory.add_option("length" ,[[2000,2001,2002,2003]] ) factory.add_option("tready_interval_max" ,[0 ,1 ])
该配置会生成多个测试用例,覆盖不同数据长度和tready间隔的组合。
完整代码 如下:
import random import cocotb import logging from cocotb.clock import Clock from cocotb.triggers import RisingEdge from cocotb.triggers import Timer from cocotb.regression import TestFactory from axis import axis classTB:#初始化搭建测试端口,时钟,复位,axis_slave_if,axis_master_ifdef__init__ (self, dut): self.dut = dut self.log = logging.getLogger ("cocotb.tb" ) self.log.setLevel (logging.DEBUG) cocotb.start_soon (Clock (dut.s_axis_aclk,10 , units="ns" ).start ()) self.axis_slave_if = axis (signal_axis_slave (dut)) self.axis_master_if = axis (signal_axis_master (dut)) self.data_drv=[] self.data_mon=[]#复位axis_fifo_0asyncdefreset (self): self.dut.s_axis_tvalid.value =0 self.dut.s_axis_tdata .value =0 self.dut.s_axis_tkeep .value =0 self.dut.s_axis_tlast .value =0 self.dut.m_axis_tready.value =0 self.dut.s_axis_aresetn.setimmediatevalue (1 )await RisingEdge (self.dut.s_axis_aclk)await RisingEdge (self.dut.s_axis_aclk) self.dut.s_axis_aresetn.value =0 await Timer (1 , units="us" )await RisingEdge (self.dut.s_axis_aclk)await RisingEdge (self.dut.s_axis_aclk) self.dut.s_axis_aresetn.value =1 await RisingEdge (self.dut.s_axis_aclk)await RisingEdge (self.dut.s_axis_aclk)#生成随机数据defseq_model (self,length): data =[]for i inrange (length): data.append (random.randint (0 ,2 **8 -1 ))return data #参考模型,直接将输入数据赋值给输出数据defref_model (self,din): dout = din return dout #监视器,用axis_vip中的axis_monitor_data_byte将输出数据存储到data_mon中asyncdefmonitor (self):whileTrue:await RisingEdge (self.dut.s_axis_aclk)iflen (self.axis_slave_if.axis_monitor_data_byte)>0 : self.data_mon.append (self.axis_slave_if.axis_monitor_data_byte[0 ]) self.axis_slave_if.axis_monitor_data_byte.pop (0 )## 计分板,比较data_drv和data_mon是否相等asyncdefscoreboard (self): i=0 whileTrue:await RisingEdge (self.dut.s_axis_aclk)if (len (self.data_drv)>0 )and (len (self.data_mon)>0 ):if (self.data_drv[0 ]!=self.data_mon[0 ]):print ("data_drv[%d]=" %(i),list (map (hex,self.data_drv[0 ])))print ("data_mon[%d]=" %(i),list (map (hex,self.data_mon[0 ])))assert self.data_drv[0 ]==self.data_mon[0 ] self.log.info ("The test %d is success,length is %d !!!" ,i,len (self.data_mon[0 ])) self.data_drv.pop (0 ) self.data_mon.pop (0 ) i=i+1 #端口信号映射,将axis_fifo_0的端口映射到axis_vip的端口classsignal_axis_master:def__init__ (self,dut): self.aclk = dut.s_axis_aclk self.tvalid = dut.s_axis_tvalid self.tready = dut.s_axis_tready self.tdata = dut.s_axis_tdata self.tkeep = dut.s_axis_tkeep self.tlast = dut.s_axis_tlast #端口信号映射,将axis_fifo_0的端口映射到axis_vip的端口classsignal_axis_slave:def__init__ (self,dut): self.aclk = dut.s_axis_aclk self.tvalid = dut.m_axis_tvalid self.tready = dut.m_axis_tready self.tdata = dut.m_axis_tdata self.tkeep = dut.m_axis_tkeep self.tlast = dut.m_axis_tlast asyncdefrun_test (dut,length,tready_interval_max): tb=TB (dut)#搭建测试环境 tb.log.info ("Running test!" )#开始测试await tb.reset ()#复位 cocotb.start_soon (tb.axis_slave_if.axis_rx (tready_interval_max=tready_interval_max))#启动axis_slave_if的接收进程await Timer (1 , units="us" )await RisingEdge (dut.s_axis_aclk) cocotb.start_soon (tb.axis_slave_if.axis_monitor_byte ())#启动axis_slave_if的监视进程 cocotb.start_soon (tb.monitor ())#启动monitor进程 cocotb.start_soon (tb.scoreboard ())#启动scoreboard进程#分别用两种方式向axis_master_if发送数据并存储到data_drv中for i inrange (len (length)): temp = tb.seq_model (length[i])await tb.axis_master_if.axis_tx_byte (temp) tb.data_drv.append (tb.ref_model (temp))for i inrange (len (length)): temp = tb.seq_model (length[i])await tb.axis_master_if.axis_tx_tvalid_interval_byte (temp,1 ) tb.data_drv.append (tb.ref_model (temp))await Timer (1 , units="us" )await RisingEdge (dut.s_axis_aclk)if cocotb.SIM_NAME: factory = TestFactory (run_test) factory.add_option ("length" ,[[2000 ,2001 ,2002 ,2003 ]])#定义测试长度,用于构建不同长度的测试数据 factory.add_option ("tready_interval_max" ,[0 ,1 ])#定义tready_interval_max,用于控制接收tready的间隔时间 factory.generate_tests ()
3、编写Makefile
TOPLEVEL_LANG 指定设计语言为Verilog
SIM 指定仿真工具为Synopsys VCS
WAVES 控制波形生成,默认启用
SEED 使用当前时间作为随机种子
XILINX_VIVADO 指向Vivado工具安装路径
PYTHONPATH 添加cocotb VIP库路径
COMP_OPTS 包含SV支持(-sverilog)、64位模式(-full64)和时间精度设置
COMPILE_ARGS 指定编译器版本(g+±4.8/gcc-4.8)和调试选项
COV_OPT 定义覆盖率收集类型(line/cond/tgl/fsm)
VERILOG_SRC 包含:
Xilinx IP核生成的axis_data_fifo_0.v
波形dump模块iverilog_dump.v
Xilinx全局仿真模块glbl.v
VHDL_SRC 当前为空
clean_all 清理所有生成文件
comp 创建构建目录并执行Verilog编译
iverilog_dump.v 动态生成波形dump模块
run 完整流程:清理->设置->编译->仿真
cov 生成覆盖率报告并启动DVE查看器
synopsys_sim.setup 动态生成仿真配置文件
通过$(shell cocotb-config --makefiles)引入cocotb的Makefile规则
顶层模块设置为axis_data_fifo_0
测试平台模块为axis_data_fifo_0_tb
该Makefile实现了完整的VCS仿真流程,包含编译、仿真、波形收集和覆盖率分析功能,针对Xilinx IP核的验证需求进行了专门配置。
TOPLEVEL_LANG ?= verilog SIM ?= vcs PWD = $(shell pwd) WAVES ?= 1 SEED = `date "+%H%M%S" ` TEST_SEED = $(SEED ) RUN_OPTS = COMP_OPTS = - sverilog - full64 + v2k - override_timescale= 1ps/1ps #+define+VCS export PYTHONPATH := $(PWD)/ ../../../../../ cocotb_vip:$(PYTHONPATH ) XILINX_VIVADO = /home/ jacen/tools/ xilinx/Vivado/ 2022.2 SIM_ARGS += $(RUN_OPTS ) SIM_CMD_PREFIX += RANDOM_SEED = $(TEST_SEED ) COMPILE_ARGS += - cpp g++- 4.8 - cc gcc- 4.8 - LDFLAGS - Wl ,-- no- as - needed - l debug.log COMPILE_ARGS += - top iverilog_dump - top glbl - lca # 覆盖率选项 COV_OPT = line #+ cond+ tgl+ fsm SIM_ARGS += - cm $(COV_OPT ) COMPILE_ARGS += - cm $(COV_OPT ) COV_DIR = sim_build/coverage include $(shell cocotb-config --makefiles)/ Makefile .sim TOPLEVEL := axis_data_fifo_0 MODULE := axis_data_fifo_0_tb COMP_INCDIR = VHDL_SRC = VERILOG_SRC += $(PWD )/../ ../ vivado_prj/vivado_prj.gen/ sources_1/ip/ axis_data_fifo_0/sim/ axis_data_fifo_0.v\ $(PWD )/iverilog_dump.v \ $(XILINX_VIVADO)/ data/verilog/ src/glbl.v #WORDS = 4 #COMPILE_ARGS += -pvalue+WORDS=$(WORDS) clean_all: rm -rf DVEfiles *.vpd *.fst __pycache__ results.xml sim_build \ ucli.key stack.info.* dve_report.* *.vcd log/ * iverilog_dump.v * .dump * .log synopsys_sim.setup comp: mkdir sim_build mkdir sim_build/64 # vhdlan $(VHDL_SRC) -l comp_vhd.log vlogan $(COMP_OPTS) $(COMP_INCDIR) $(VERILOG_SRC) -l comp_vlog.log # vhdlan -full64 -f oran_low_phy_uvm_vhd_file_list -l comp_vhd.log mv synopsys_sim.setup sim_build/ synopsys_sim.setup: echo 'DEFAULT : $(PWD )/sim_build' >> $@ echo 'OTHERS=/ home/jacen/ tools/xilinx/ vcs_lib/synopsys_sim.setup' >> $@ iverilog_dump.v: echo 'module iverilog_dump();' >> $@ echo 'initial begin' >> $@ echo ' $$vcdplusfile("wave.vpd");' >> $@ echo ' $$vcdpluson();' >> $@ echo ' $$dumpvars(0, $(TOPLEVEL));' >> $@ echo 'end' >> $@ echo 'endmodule' >> $@ run: clean_all synopsys_sim.setup iverilog_dump.v comp sim @echo "done..." # 添加覆盖率报告生成 cov:run urg -dir sim_build/ simv.vdb - report $(COV_DIR ) dve - covdir sim_build/ simv.vdb&
4、运行仿真,看波形 打开VCS软件,导入波形,选择信号加入波形图
dve&
Makefile中需要注意:
1、synopsys_sim.setup 指示Xilinx IP VCS仿真库的位置
2、需要指定编译器版本(g+±4.8/gcc-4.8)