跳到主要内容基于 cocotb 与 VCS 验证 Xilinx FPGA IP 核实战 | 极客日志Python
基于 cocotb 与 VCS 验证 Xilinx FPGA IP 核实战
使用 cocotb 框架结合 Synopsys VCS 仿真器对 Xilinx AXIS FIFO IP 核进行功能验证。通过构建自定义 VIP 库实现 AXI Stream 接口的驱动与监控,支持字节级数据发送、随机接收及总线监控。测试环境包含时钟复位控制、参考模型和计分板,利用 Makefile 管理编译流程与覆盖率收集。方案模拟真实硬件背压情况,覆盖不同数据长度与 tready 间隔组合,确保接口逻辑正确性。
落日余晖1 浏览 概要
本文分享基于 cocotb 框架的 AXI Stream 接口验证方法。主要内容包括开发 AXIS VIP 库,实现字节级数据发送、随机接收和总线监控功能;以 Xilinx AXIS FIFO 为例,展示 VIP 库的调用方法,涵盖测试平台搭建、数据生成和自动验证机制。该方案支持 LSB 配置,能模拟真实硬件背压情况,适用于 AXIS 接口模块的功能验证。
建立 cocotb 仿真 VIP 库
首先创建一个 axis.py 文件,实现 AXI Stream(AXIS)接口的驱动和监控功能。核心功能包括数据发送、接收和监控,支持字节级操作。
import logging
import math
import copy
import random
from cocotb.triggers import RisingEdge
class axis:
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 = []
async def axis_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 > (tx_data):
i (temp * tkeep_width - (tx_data)):
tx_data.append()
tkeep = tkeep +
.lsb == :
tkeep = ((**tkeep_width - ) << tkeep) & (**tkeep_width - )
:
tkeep = ((**tkeep_width - ) >> tkeep) & (**tkeep_width - )
data = []
i (temp):
data_temp =
m (tkeep_width):
.lsb == :
data_temp = data_temp + (tx_data[i * tkeep_width + m] << (data_width - - m * ))
:
data_temp = data_temp + (tx_data[i * tkeep_width + m] << (m * ))
data.append(data_temp)
i ((data)):
.signal.tvalid.value =
.signal.tdata.value = data[i]
i == (data) - :
.signal.tlast.value =
.signal.tkeep.value = tkeep
:
.signal.tlast.value =
.signal.tkeep.value = **tkeep_width -
RisingEdge(.signal.aclk)
.signal.tready.value == :
RisingEdge(.signal.aclk)
.signal.tvalid.value =
():
:
tready_interval = random.randint(, tready_interval_max)
.signal.tready.value =
RisingEdge(.signal.aclk)
.signal.tready.value =
i (tready_interval):
RisingEdge(.signal.aclk)
():
data = []
tkeep_width = (.signal.tdata) //
data_width = (.signal.tdata)
:
RisingEdge(.signal.aclk)
.signal.tready.value == .signal.tvalid.value == :
temp = .signal.tdata.value
m (tkeep_width):
.lsb == :
.signal.tkeep.value & ( << (tkeep_width - - m)):
data.append((temp >> (data_width - - m * )) & )
:
.signal.tkeep.value & ( << m):
data.append((temp >> (m * )) & )
.signal.tlast.value == :
.axis_monitor_data_byte.append(data)
data = []
len
for
in
range
len
0
1
if
self
0
2
1
2
1
else
2
1
2
1
for
in
range
0
for
in
range
if
self
0
8
8
else
8
for
in
range
len
self
1
self
if
len
1
self
1
self
else
self
0
self
2
1
await
self
while
self
0
await
self
self
0
async
def
axis_rx
self, tready_interval_max=8
while
True
0
self
1
await
self
self
0
for
in
range
await
self
async
def
axis_monitor_byte
self
len
self
8
len
self
while
True
await
self
if
self
1
and
self
1
self
for
in
range
if
self
0
if
self
1
1
8
8
0xff
else
if
self
1
8
0xff
if
self
1
self
AXIS 接口发送(axis_tx_byte)
初始化信号状态为无效,清空 tkeep 和 tlast。将输入字节数组复制并计算传输周期。若数据长度不是 tkeep_width 的整数倍,填充 0 并计算 tkeep 掩码。根据 LSB 配置方向生成不同的掩码模式,将字节数组转换为 AXIS 数据格式,在最后一个周期置 tlast 信号。
AXIS 接口接收(axis_rx)
模拟从设备就绪信号行为:随机间隔后置 tready 为 1,保持一个周期后随机延迟。这种随机性用于模拟真实硬件中的背压情况。
AXIS 数据监控(axis_monitor_byte)
持续监控总线:当 tvalid 和 tready 同时有效时,根据 tkeep 信号提取有效字节数据。根据 LSB 配置决定字节提取顺序,使用 tlast 信号判断数据包边界。
调用 VIP 库仿 Xilinx IP
Vivado 生成 IP
在 Vivado 中生成所需的 IP 核并完成设计配置,确保端口映射正确。
编写 Python 仿真代码
测试平台结构
TB 类是测试平台的核心,负责初始化时钟、复位、AXIS 从接口和主接口,提供复位控制、数据生成、参考模型等功能,并包含监视器和计分板用于自动验证。
class TB:
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 处于已知状态,随后启动协程。
async def reset(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
数据生成与验证
数据生成使用随机数创建测试激励,计分板比较驱动数据和监视数据是否一致。
def seq_model(self, length):
data = []
for i in range(length):
data.append(random.randint(0, 2**8 - 1))
return data
async def scoreboard(self):
i = 0
while True:
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 += 1
完整测试代码
整合上述组件,通过 TestFactory 创建参数化测试,覆盖不同数据长度和 tready 间隔的组合。
import random
import cocotb
import logging
from cocotb.clock import Clock
from cocotb.triggers import RisingEdge, Timer
from cocotb.regression import TestFactory
from axis import axis
class TB:
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 = []
async def reset(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)
def seq_model(self, length):
data = []
for i in range(length):
data.append(random.randint(0, 2**8 - 1))
return data
def ref_model(self, din):
return din
async def monitor(self):
while True:
await RisingEdge(self.dut.s_axis_aclk)
if len(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)
async def scoreboard(self):
i = 0
while True:
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 += 1
class signal_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
class signal_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
async def run_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))
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 in range(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 in range(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])
factory.generate_tests()
编写 Makefile
Makefile 负责管理编译、仿真及覆盖率收集。关键点如下:
- 变量定义:指定顶层语言为 Verilog,仿真工具为 VCS,设置随机种子和时间精度。
- 编译选项:包含 SV 支持、64 位模式及调试选项,指定编译器版本。
- 源文件配置:包含 Xilinx IP 核生成的 Verilog 文件、波形 dump 模块及全局仿真模块 glbl.v。
- 特殊处理:动态生成 synopsys_sim.setup 配置文件,引入 cocotb 的 Makefile 规则。
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
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
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
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
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&
运行仿真与查看波形
进入子系统仿真目录,执行 make run 命令。完成后打开 VCS 软件,导入波形文件,选择信号加入波形图进行分析。注意 Makefile 中需正确指示 Xilinx IP VCS 仿真库的位置,并指定匹配的编译器版本。
相关免费在线工具
- 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