cndm: Initial commit of corundum-micro

Signed-off-by: Alex Forencich <alex@alexforencich.com>
This commit is contained in:
Alex Forencich
2025-12-31 18:53:30 -08:00
parent 3fcb32f232
commit d2f56bb932
22 changed files with 4401 additions and 0 deletions

276
src/cndm/tb/cndm.py Normal file
View File

@@ -0,0 +1,276 @@
# SPDX-License-Identifier: CERN-OHL-S-2.0
"""
Copyright (c) 2025 FPGA Ninja, LLC
Authors:
- Alex Forencich
"""
import logging
import struct
from collections import deque
from cocotb.queue import Queue
class Port:
def __init__(self, driver, index, hw_regs):
self.driver = driver
self.log = driver.log
self.index = index
self.hw_regs = hw_regs
self.rxq_log_size = (256).bit_length()-1
self.rxq_size = 2**self.rxq_log_size
self.rxq_mask = self.rxq_size-1
self.rxq = None
self.rxq_prod = 0
self.rxq_cons = 0
self.rx_info = [None] * self.rxq_size
self.rxcq_log_size = (256).bit_length()-1
self.rxcq_size = 2**self.rxcq_log_size
self.rxcq_mask = self.rxcq_size-1
self.rxcq = None
self.rxcq_prod = 0
self.rxcq_cons = 0
self.txq_log_size = (256).bit_length()-1
self.txq_size = 2**self.txq_log_size
self.txq_mask = self.txq_size-1
self.txq = None
self.txq_prod = 0
self.txq_cons = 0
self.tx_info = [None] * self.txq_size
self.txcq_log_size = (256).bit_length()-1
self.txcq_size = 2**self.txcq_log_size
self.txcq_mask = self.txcq_size-1
self.txcq = None
self.txcq_prod = 0
self.txcq_cons = 0
self.rx_queue = Queue()
async def init(self):
self.rxq = self.driver.pool.alloc_region(self.rxq_size*16)
addr = self.rxq.get_absolute_address(0)
await self.hw_regs.write_dword(0x0200, 0x00000000)
await self.hw_regs.write_dword(0x0204, 0x00000000)
await self.hw_regs.write_dword(0x0208, addr & 0xffffffff)
await self.hw_regs.write_dword(0x020c, addr >> 32)
await self.hw_regs.write_dword(0x0200, 0x00000001 | (self.rxq_log_size << 16))
self.rxcq = self.driver.pool.alloc_region(self.rxcq_size*16)
addr = self.rxcq.get_absolute_address(0)
await self.hw_regs.write_dword(0x0400, 0x00000000)
await self.hw_regs.write_dword(0x0408, addr & 0xffffffff)
await self.hw_regs.write_dword(0x040c, addr >> 32)
await self.hw_regs.write_dword(0x0400, 0x00000001 | (self.rxcq_log_size << 16))
self.txq = self.driver.pool.alloc_region(self.txq_size*16)
addr = self.txq.get_absolute_address(0)
await self.hw_regs.write_dword(0x0100, 0x00000000)
await self.hw_regs.write_dword(0x0104, 0x00000000)
await self.hw_regs.write_dword(0x0108, addr & 0xffffffff)
await self.hw_regs.write_dword(0x010c, addr >> 32)
await self.hw_regs.write_dword(0x0100, 0x00000001 | (self.txq_log_size << 16))
self.txcq = self.driver.pool.alloc_region(self.txcq_size*16)
addr = self.txcq.get_absolute_address(0)
await self.hw_regs.write_dword(0x0300, 0x00000000)
await self.hw_regs.write_dword(0x0308, addr & 0xffffffff)
await self.hw_regs.write_dword(0x030c, addr >> 32)
await self.hw_regs.write_dword(0x0300, 0x00000001 | (self.txcq_log_size << 16))
# wait for writes to complete
await self.hw_regs.read_dword(0)
await self.refill_rx_buffers()
async def start_xmit(self, data):
headroom = 10
tx_buf = self.driver.alloc_pkt()
await tx_buf.write(headroom, data)
index = self.txq_prod & self.txq_mask
ptr = tx_buf.get_absolute_address(0)
struct.pack_into('<xxxxLQ', self.txq.mem, 16*index, len(data), ptr+headroom)
self.tx_info[index] = tx_buf
self.txq_prod += 1
await self.hw_regs.write_dword(0x0104, self.txq_prod & 0xffff)
async def recv(self):
return await self.rx_queue.get()
async def recv_nowait(self):
return self.rx_queue.get_nowait()
def free_tx_desc(self, index):
pkt = self.tx_info[index]
self.driver.free_pkt(pkt)
self.tx_info[index] = None
def free_tx_buf(self):
while self.txq_cons != self.txq_prod:
index = self.txq_cons & self.txq_mask
self.free_tx_desc(index)
self.txq_cons += 1
async def process_tx_cq(self):
cq_cons_ptr = self.txcq_cons
cons_ptr = self.txq_cons
while True:
cq_index = cq_cons_ptr & self.txcq_mask
index = cons_ptr & self.txq_mask
cpl_data = struct.unpack_from("<LLLL", self.txcq.mem, cq_index*16)
self.log.info("TX CQ index %d data %s", cq_index, cpl_data)
if bool(cpl_data[-1] & 0x80000000) == bool(cq_cons_ptr & self.txcq_size):
self.log.info("CQ empty")
break
pkt = self.tx_info[index]
self.free_tx_desc(index)
cq_cons_ptr += 1
cons_ptr += 1
self.txcq_cons = cq_cons_ptr
self.txq_cons = cons_ptr
def free_rx_desc(self, index):
pkt = self.rx_info[index]
self.driver.free_pkt(pkt)
self.rx_info[index] = None
def free_rx_buf(self):
while self.rxq_cons != self.rxq_prod:
index = self.rxq_cons & self.rxq_mask
self.free_rx_desc(index)
self.rxq_cons += 1
def prepare_rx_desc(self, index):
pkt = self.driver.alloc_pkt()
self.rx_info[index] = pkt
length = pkt.size
ptr = pkt.get_absolute_address(0)
struct.pack_into('<xxxxLQ', self.rxq.mem, 16*index, length, ptr)
async def refill_rx_buffers(self):
missing = self.rxq_size - (self.rxq_prod - self.rxq_cons)
if missing < 8:
return
for k in range(missing):
self.prepare_rx_desc(self.rxq_prod & self.rxq_mask)
self.rxq_prod += 1
await self.hw_regs.write_dword(0x0204, self.rxq_prod & 0xffff)
async def process_rx_cq(self):
cq_cons_ptr = self.rxcq_cons
cons_ptr = self.rxq_cons
while True:
cq_index = cq_cons_ptr & self.rxcq_mask
index = cons_ptr & self.rxq_mask
cpl_data = struct.unpack_from("<LLLL", self.rxcq.mem, cq_index*16)
self.log.info("RX CQ index %d data %s", cq_index, cpl_data)
if bool(cpl_data[-1] & 0x80000000) == bool(cq_cons_ptr & self.rxcq_size):
self.log.info("CQ empty")
break
pkt = self.rx_info[index]
length = cpl_data[1]
data = pkt[:length]
self.log.info("Packet: %s", data)
self.rx_queue.put_nowait(data)
self.free_rx_desc(index)
cq_cons_ptr += 1
cons_ptr += 1
self.rxcq_cons = cq_cons_ptr
self.rxq_cons = cons_ptr
await self.refill_rx_buffers()
async def interrupt_handler(self):
self.log.info("Interrupt")
await self.process_rx_cq()
await self.process_tx_cq()
class Driver:
def __init__(self):
self.log = logging.getLogger("cocotb.cndm")
self.dev = None
self.pool = None
self.hw_regs = None
self.ports = []
self.free_packets = deque()
self.allocated_packets = []
async def init_pcie_dev(self, dev):
self.dev = dev
self.pool = dev.rc.mem_pool
await dev.enable_device()
await dev.set_master()
await dev.alloc_irq_vectors(32, 32)
self.hw_regs = dev.bar_window[0]
await self.init_common()
async def init_common(self):
self.port_count = await self.hw_regs.read_dword(0x0100)
self.port_offset = await self.hw_regs.read_dword(0x0104)
self.port_stride = await self.hw_regs.read_dword(0x0108)
self.log.info("Port count: %d", self.port_count)
self.log.info("Port offset: 0x%x", self.port_offset)
self.log.info("Port stride: 0x%x", self.port_stride)
for k in range(self.port_count):
port = Port(self, k, self.hw_regs.create_window(self.port_offset + self.port_stride*k))
await port.init()
self.dev.request_irq(k, port.interrupt_handler)
self.ports.append(port)
def alloc_pkt(self):
if self.free_packets:
return self.free_packets.popleft()
pkt = self.pool.alloc_region(4096)
self.allocated_packets.append(pkt)
return pkt
def free_pkt(self, pkt):
assert pkt is not None
assert pkt in self.allocated_packets
self.free_packets.append(pkt)

View File

@@ -0,0 +1,59 @@
# SPDX-License-Identifier: CERN-OHL-S-2.0
#
# Copyright (c) 2020-2025 FPGA Ninja, LLC
#
# Authors:
# - Alex Forencich
TOPLEVEL_LANG = verilog
SIM ?= verilator
WAVES ?= 0
COCOTB_HDL_TIMEUNIT = 1ns
COCOTB_HDL_TIMEPRECISION = 1ps
RTL_DIR = ../../rtl
LIB_DIR = ../../lib
TAXI_SRC_DIR = $(LIB_DIR)/taxi/src
DUT = cndm_micro_pcie_us
COCOTB_TEST_MODULES = test_$(DUT)
COCOTB_TOPLEVEL = test_$(DUT)
MODULE = $(COCOTB_TEST_MODULES)
TOPLEVEL = $(COCOTB_TOPLEVEL)
VERILOG_SOURCES += $(COCOTB_TOPLEVEL).sv
VERILOG_SOURCES += $(RTL_DIR)/$(DUT).f
VERILOG_SOURCES += $(TAXI_SRC_DIR)/axis/rtl/taxi_axis_async_fifo.f
VERILOG_SOURCES += $(TAXI_SRC_DIR)/sync/rtl/taxi_sync_reset.sv
VERILOG_SOURCES += $(TAXI_SRC_DIR)/sync/rtl/taxi_sync_signal.sv
# handle file list files
process_f_file = $(call process_f_files,$(addprefix $(dir $1),$(shell cat $1)))
process_f_files = $(foreach f,$1,$(if $(filter %.f,$f),$(call process_f_file,$f),$f))
uniq_base = $(if $1,$(call uniq_base,$(foreach f,$1,$(if $(filter-out $(notdir $(lastword $1)),$(notdir $f)),$f,))) $(lastword $1))
VERILOG_SOURCES := $(call uniq_base,$(call process_f_files,$(VERILOG_SOURCES)))
# module parameters
export PARAM_SIM := "1'b1"
export PARAM_VENDOR := "\"XILINX\""
export PARAM_FAMILY := "\"virtexuplus\""
export PARAM_PORTS := 2
export PARAM_MAC_DATA_W := 32
export PARAM_AXIS_PCIE_DATA_W := 256
export PARAM_BAR0_APERTURE := 24
ifeq ($(SIM), icarus)
PLUSARGS += -fst
COMPILE_ARGS += $(foreach v,$(filter PARAM_%,$(.VARIABLES)),-P $(COCOTB_TOPLEVEL).$(subst PARAM_,,$(v))=$($(v)))
else ifeq ($(SIM), verilator)
COMPILE_ARGS += $(foreach v,$(filter PARAM_%,$(.VARIABLES)),-G$(subst PARAM_,,$(v))=$($(v)))
ifeq ($(WAVES), 1)
COMPILE_ARGS += --trace-fst
VERILATOR_TRACE = 1
endif
endif
include $(shell cocotb-config --makefiles)/Makefile.sim

View File

@@ -0,0 +1 @@
../cndm.py

View File

@@ -0,0 +1,520 @@
#!/usr/bin/env python
# SPDX-License-Identifier: CERN-OHL-S-2.0
"""
Copyright (c) 2020-2025 FPGA Ninja, LLC
Authors:
- Alex Forencich
"""
import logging
import os
import sys
import pytest
import cocotb_test.simulator
import cocotb
from cocotb.clock import Clock
from cocotb.triggers import RisingEdge, FallingEdge, Timer
from cocotbext.axi import AxiStreamBus
from cocotbext.eth import EthMac
from cocotbext.pcie.core import RootComplex
from cocotbext.pcie.xilinx.us import UltraScalePlusPcieDevice
try:
import cndm
except ImportError:
# attempt import from current directory
sys.path.insert(0, os.path.join(os.path.dirname(__file__)))
try:
import cndm
finally:
del sys.path[0]
class TB:
def __init__(self, dut):
self.dut = dut
self.log = logging.getLogger("cocotb.tb")
self.log.setLevel(logging.DEBUG)
# PCIe
self.rc = RootComplex()
self.rc.max_payload_size = 0x1 # 256 bytes
self.rc.max_read_request_size = 0x2 # 512 bytes
self.dev = UltraScalePlusPcieDevice(
# configuration options
pcie_generation=3,
pcie_link_width=8,
user_clk_frequency=250e6,
alignment="dword",
cq_straddle=False,
cc_straddle=False,
rq_straddle=False,
rc_straddle=False,
rc_4tlp_straddle=False,
pf_count=1,
max_payload_size=1024,
enable_client_tag=True,
enable_extended_tag=True,
enable_parity=False,
enable_rx_msg_interface=False,
enable_sriov=False,
enable_extended_configuration=False,
pf0_msi_enable=True,
pf0_msi_count=32,
pf1_msi_enable=False,
pf1_msi_count=1,
pf2_msi_enable=False,
pf2_msi_count=1,
pf3_msi_enable=False,
pf3_msi_count=1,
pf0_msix_enable=False,
pf0_msix_table_size=31,
pf0_msix_table_bir=4,
pf0_msix_table_offset=0x00000000,
pf0_msix_pba_bir=4,
pf0_msix_pba_offset=0x00008000,
pf1_msix_enable=False,
pf1_msix_table_size=0,
pf1_msix_table_bir=0,
pf1_msix_table_offset=0x00000000,
pf1_msix_pba_bir=0,
pf1_msix_pba_offset=0x00000000,
pf2_msix_enable=False,
pf2_msix_table_size=0,
pf2_msix_table_bir=0,
pf2_msix_table_offset=0x00000000,
pf2_msix_pba_bir=0,
pf2_msix_pba_offset=0x00000000,
pf3_msix_enable=False,
pf3_msix_table_size=0,
pf3_msix_table_bir=0,
pf3_msix_table_offset=0x00000000,
pf3_msix_pba_bir=0,
pf3_msix_pba_offset=0x00000000,
# signals
# Clock and Reset Interface
user_clk=dut.pcie_clk,
user_reset=dut.pcie_rst,
# user_lnk_up
# sys_clk
# sys_clk_gt
# sys_reset
# phy_rdy_out
# Requester reQuest Interface
rq_bus=AxiStreamBus.from_entity(dut.m_axis_pcie_rq),
pcie_rq_seq_num0=dut.pcie_rq_seq_num0,
pcie_rq_seq_num_vld0=dut.pcie_rq_seq_num_vld0,
pcie_rq_seq_num1=dut.pcie_rq_seq_num1,
pcie_rq_seq_num_vld1=dut.pcie_rq_seq_num_vld1,
# pcie_rq_tag0
# pcie_rq_tag1
# pcie_rq_tag_av
# pcie_rq_tag_vld0
# pcie_rq_tag_vld1
# Requester Completion Interface
rc_bus=AxiStreamBus.from_entity(dut.s_axis_pcie_rc),
# Completer reQuest Interface
cq_bus=AxiStreamBus.from_entity(dut.s_axis_pcie_cq),
# pcie_cq_np_req
# pcie_cq_np_req_count
# Completer Completion Interface
cc_bus=AxiStreamBus.from_entity(dut.m_axis_pcie_cc),
# Transmit Flow Control Interface
# pcie_tfc_nph_av=dut.pcie_tfc_nph_av,
# pcie_tfc_npd_av=dut.pcie_tfc_npd_av,
# Configuration Management Interface
# cfg_mgmt_addr=dut.cfg_mgmt_addr,
# cfg_mgmt_function_number=dut.cfg_mgmt_function_number,
# cfg_mgmt_write=dut.cfg_mgmt_write,
# cfg_mgmt_write_data=dut.cfg_mgmt_write_data,
# cfg_mgmt_byte_enable=dut.cfg_mgmt_byte_enable,
# cfg_mgmt_read=dut.cfg_mgmt_read,
# cfg_mgmt_read_data=dut.cfg_mgmt_read_data,
# cfg_mgmt_read_write_done=dut.cfg_mgmt_read_write_done,
# cfg_mgmt_debug_access
# Configuration Status Interface
# cfg_phy_link_down
# cfg_phy_link_status
# cfg_negotiated_width
# cfg_current_speed
# cfg_max_payload=dut.cfg_max_payload,
# cfg_max_read_req=dut.cfg_max_read_req,
# cfg_function_status
# cfg_vf_status
# cfg_function_power_state
# cfg_vf_power_state
# cfg_link_power_state
# cfg_err_cor_out
# cfg_err_nonfatal_out
# cfg_err_fatal_out
# cfg_local_error_out
# cfg_local_error_valid
# cfg_rx_pm_state
# cfg_tx_pm_state
# cfg_ltssm_state
# cfg_rcb_status=dut.cfg_rcb_status,
# cfg_obff_enable
# cfg_pl_status_change
# cfg_tph_requester_enable
# cfg_tph_st_mode
# cfg_vf_tph_requester_enable
# cfg_vf_tph_st_mode
# Configuration Received Message Interface
# cfg_msg_received
# cfg_msg_received_data
# cfg_msg_received_type
# Configuration Transmit Message Interface
# cfg_msg_transmit
# cfg_msg_transmit_type
# cfg_msg_transmit_data
# cfg_msg_transmit_done
# Configuration Flow Control Interface
cfg_fc_ph=dut.cfg_fc_ph,
cfg_fc_pd=dut.cfg_fc_pd,
cfg_fc_nph=dut.cfg_fc_nph,
cfg_fc_npd=dut.cfg_fc_npd,
cfg_fc_cplh=dut.cfg_fc_cplh,
cfg_fc_cpld=dut.cfg_fc_cpld,
cfg_fc_sel=dut.cfg_fc_sel,
# Configuration Control Interface
# cfg_hot_reset_in
# cfg_hot_reset_out
# cfg_config_space_enable
# cfg_dsn
# cfg_bus_number
# cfg_ds_port_number
# cfg_ds_bus_number
# cfg_ds_device_number
# cfg_ds_function_number
# cfg_power_state_change_ack
# cfg_power_state_change_interrupt
# cfg_err_cor_in=dut.status_error_cor,
# cfg_err_uncor_in=dut.status_error_uncor,
# cfg_flr_in_process
# cfg_flr_done
# cfg_vf_flr_in_process
# cfg_vf_flr_func_num
# cfg_vf_flr_done
# cfg_pm_aspm_l1_entry_reject
# cfg_pm_aspm_tx_l0s_entry_disable
# cfg_req_pm_transition_l23_ready
# cfg_link_training_enable
# Configuration Interrupt Controller Interface
# cfg_interrupt_int
# cfg_interrupt_sent
# cfg_interrupt_pending
cfg_interrupt_msi_enable=dut.cfg_interrupt_msi_enable,
cfg_interrupt_msi_mmenable=dut.cfg_interrupt_msi_mmenable,
cfg_interrupt_msi_mask_update=dut.cfg_interrupt_msi_mask_update,
cfg_interrupt_msi_data=dut.cfg_interrupt_msi_data,
cfg_interrupt_msi_select=dut.cfg_interrupt_msi_select,
cfg_interrupt_msi_int=dut.cfg_interrupt_msi_int,
cfg_interrupt_msi_pending_status=dut.cfg_interrupt_msi_pending_status,
cfg_interrupt_msi_pending_status_data_enable=dut.cfg_interrupt_msi_pending_status_data_enable,
cfg_interrupt_msi_pending_status_function_num=dut.cfg_interrupt_msi_pending_status_function_num,
cfg_interrupt_msi_sent=dut.cfg_interrupt_msi_sent,
cfg_interrupt_msi_fail=dut.cfg_interrupt_msi_fail,
# cfg_interrupt_msix_enable=dut.cfg_interrupt_msix_enable,
# cfg_interrupt_msix_mask=dut.cfg_interrupt_msix_mask,
# cfg_interrupt_msix_vf_enable=dut.cfg_interrupt_msix_vf_enable,
# cfg_interrupt_msix_vf_mask=dut.cfg_interrupt_msix_vf_mask,
# cfg_interrupt_msix_address=dut.cfg_interrupt_msix_address,
# cfg_interrupt_msix_data=dut.cfg_interrupt_msix_data,
# cfg_interrupt_msix_int=dut.cfg_interrupt_msix_int,
# cfg_interrupt_msix_vec_pending=dut.cfg_interrupt_msix_vec_pending,
# cfg_interrupt_msix_vec_pending_status=dut.cfg_interrupt_msix_vec_pending_status,
# cfg_interrupt_msix_sent=dut.cfg_interrupt_msix_sent,
# cfg_interrupt_msix_fail=dut.cfg_interrupt_msix_fail,
cfg_interrupt_msi_attr=dut.cfg_interrupt_msi_attr,
cfg_interrupt_msi_tph_present=dut.cfg_interrupt_msi_tph_present,
cfg_interrupt_msi_tph_type=dut.cfg_interrupt_msi_tph_type,
cfg_interrupt_msi_tph_st_tag=dut.cfg_interrupt_msi_tph_st_tag,
cfg_interrupt_msi_function_number=dut.cfg_interrupt_msi_function_number,
# Configuration Extend Interface
# cfg_ext_read_received
# cfg_ext_write_received
# cfg_ext_register_number
# cfg_ext_function_number
# cfg_ext_write_data
# cfg_ext_write_byte_enable
# cfg_ext_read_data
# cfg_ext_read_data_valid
)
# self.dev.log.setLevel(logging.DEBUG)
self.rc.make_port().connect(self.dev)
self.dev.functions[0].configure_bar(0, 2**int(dut.uut.axil_ctrl_bar.ADDR_W))
# Ethernet
self.port_mac = []
eth_clock_period = 3.2
eth_speed = 10e9
for k in range(len(dut.mac_axis_tx)):
cocotb.start_soon(Clock(dut.mac_tx_clk[k], eth_clock_period, units="ns").start())
cocotb.start_soon(Clock(dut.mac_rx_clk[k], eth_clock_period, units="ns").start())
dut.mac_tx_rst[k].setimmediatevalue(0)
dut.mac_rx_rst[k].setimmediatevalue(0)
mac = EthMac(
tx_clk=dut.mac_tx_clk[k],
tx_rst=dut.mac_tx_rst[k],
tx_bus=AxiStreamBus.from_entity(dut.mac_axis_tx[k]),
rx_clk=dut.mac_rx_clk[k],
rx_rst=dut.mac_rx_rst[k],
rx_bus=AxiStreamBus.from_entity(dut.mac_axis_rx[k]),
ifg=12, speed=eth_speed
)
self.port_mac.append(mac)
# cocotb.start_soon(Clock(dut.sfp_mgt_refclk_p, 6.4, units="ns").start())
# self.sfp_sources = []
# self.sfp_sinks = []
# for ch in dut.uut.sfp_mac_inst.ch:
# gt_inst = ch.ch_inst.gt.gt_inst
# if ch.ch_inst.DATA_W.value == 64:
# if ch.ch_inst.CFG_LOW_LATENCY.value:
# clk = 2.482
# gbx_cfg = (66, [64, 65])
# else:
# clk = 2.56
# gbx_cfg = None
# else:
# if ch.ch_inst.CFG_LOW_LATENCY.value:
# clk = 3.102
# gbx_cfg = (66, [64, 65])
# else:
# clk = 3.2
# gbx_cfg = None
# cocotb.start_soon(Clock(gt_inst.tx_clk, clk, units="ns").start())
# cocotb.start_soon(Clock(gt_inst.rx_clk, clk, units="ns").start())
# self.sfp_sources.append(BaseRSerdesSource(
# data=gt_inst.serdes_rx_data,
# data_valid=gt_inst.serdes_rx_data_valid,
# hdr=gt_inst.serdes_rx_hdr,
# hdr_valid=gt_inst.serdes_rx_hdr_valid,
# clock=gt_inst.rx_clk,
# slip=gt_inst.serdes_rx_bitslip,
# reverse=True,
# gbx_cfg=gbx_cfg
# ))
# self.sfp_sinks.append(BaseRSerdesSink(
# data=gt_inst.serdes_tx_data,
# data_valid=gt_inst.serdes_tx_data_valid,
# hdr=gt_inst.serdes_tx_hdr,
# hdr_valid=gt_inst.serdes_tx_hdr_valid,
# gbx_sync=gt_inst.serdes_tx_gbx_sync,
# clock=gt_inst.tx_clk,
# reverse=True,
# gbx_cfg=gbx_cfg
# ))
#
self.loopback_enable = False
cocotb.start_soon(self._run_loopback())
async def init(self):
for mac in self.port_mac:
mac.rx.reset.setimmediatevalue(0)
mac.tx.reset.setimmediatevalue(0)
await FallingEdge(self.dut.pcie_rst)
await Timer(100, 'ns')
for k in range(10):
await RisingEdge(self.dut.pcie_clk)
for mac in self.port_mac:
mac.rx.reset.value = 1
mac.tx.reset.value = 1
for k in range(10):
await RisingEdge(self.dut.pcie_clk)
for mac in self.port_mac:
mac.rx.reset.value = 0
mac.tx.reset.value = 0
for k in range(10):
await RisingEdge(self.dut.pcie_clk)
await self.rc.enumerate()
async def _run_loopback(self):
while True:
await RisingEdge(self.dut.pcie_clk)
if self.loopback_enable:
for mac in self.port_mac:
while not mac.tx.empty():
await mac.rx.send(await mac.tx.recv())
@cocotb.test()
async def run_test(dut):
tb = TB(dut)
await tb.init()
tb.log.info("Init driver model")
driver = cndm.Driver()
await driver.init_pcie_dev(tb.rc.find_device(tb.dev.functions[0].pcie_id))
tb.log.info("Init complete")
tb.log.info("Send and receive single packet on each port")
for k in range(len(driver.ports)):
data = f"Corundum rocks on port {k}!".encode('ascii')
await driver.ports[k].start_xmit(data)
pkt = await tb.port_mac[k].tx.recv()
tb.log.info("Got TX packet: %s", pkt)
assert bytes(pkt) == data
await tb.port_mac[k].rx.send(pkt)
pkt = await driver.ports[k].recv()
tb.log.info("Got RX packet: %s", pkt)
assert bytes(pkt) == data
tb.log.info("Multiple small packets")
count = 64
pkts = [bytearray([(x+k) % 256 for x in range(60)]) for k in range(count)]
tb.loopback_enable = True
for p in pkts:
await driver.ports[0].start_xmit(p)
for k in range(count):
pkt = await driver.ports[0].recv()
tb.log.info("Got RX packet: %s", pkt)
assert bytes(pkt) == pkts[k]
tb.loopback_enable = False
tb.log.info("Multiple large packets")
count = 64
pkts = [bytearray([(x+k) % 256 for x in range(1514)]) for k in range(count)]
tb.loopback_enable = True
for p in pkts:
await driver.ports[0].start_xmit(p)
for k in range(count):
pkt = await driver.ports[0].recv()
tb.log.info("Got RX packet: %s", pkt)
assert bytes(pkt) == pkts[k]
tb.loopback_enable = False
await RisingEdge(dut.pcie_clk)
await RisingEdge(dut.pcie_clk)
# cocotb-test
tests_dir = os.path.abspath(os.path.dirname(__file__))
rtl_dir = os.path.abspath(os.path.join(tests_dir, '..', '..', 'rtl'))
lib_dir = os.path.abspath(os.path.join(tests_dir, '..', '..', 'lib'))
taxi_src_dir = os.path.abspath(os.path.join(lib_dir, 'taxi', 'src'))
def process_f_files(files):
lst = {}
for f in files:
if f[-2:].lower() == '.f':
with open(f, 'r') as fp:
l = fp.read().split()
for f in process_f_files([os.path.join(os.path.dirname(f), x) for x in l]):
lst[os.path.basename(f)] = f
else:
lst[os.path.basename(f)] = f
return list(lst.values())
@pytest.mark.parametrize("mac_data_w", [32, 64])
def test_cndm_micro_pcie_us(request, mac_data_w):
dut = "cndm_micro_pcie_us"
module = os.path.splitext(os.path.basename(__file__))[0]
toplevel = module
verilog_sources = [
os.path.join(tests_dir, f"{toplevel}.sv"),
os.path.join(rtl_dir, f"{dut}.f"),
os.path.join(taxi_src_dir, "axis", "rtl", "taxi_axis_async_fifo.f"),
os.path.join(taxi_src_dir, "sync", "rtl", "taxi_sync_reset.sv"),
os.path.join(taxi_src_dir, "sync", "rtl", "taxi_sync_signal.sv"),
]
verilog_sources = process_f_files(verilog_sources)
parameters = {}
parameters['SIM'] = "1'b1"
parameters['VENDOR'] = "\"XILINX\""
parameters['FAMILY'] = "\"virtexuplus\""
parameters['PORTS'] = 2
parameters['MAC_DATA_W'] = mac_data_w
parameters['AXIS_PCIE_DATA_W'] = 256
parameters['BAR0_APERTURE'] = 24
extra_env = {f'PARAM_{k}': str(v) for k, v in parameters.items()}
sim_build = os.path.join(tests_dir, "sim_build",
request.node.name.replace('[', '-').replace(']', ''))
cocotb_test.simulator.run(
simulator="verilator",
python_search=[tests_dir],
verilog_sources=verilog_sources,
toplevel=toplevel,
module=module,
parameters=parameters,
sim_build=sim_build,
extra_env=extra_env,
)

View File

@@ -0,0 +1,228 @@
// SPDX-License-Identifier: CERN-OHL-S-2.0
/*
Copyright (c) 2025 FPGA Ninja, LLC
Authors:
- Alex Forencich
*/
`resetall
`timescale 1ns / 1ps
`default_nettype none
/*
* FPGA core logic testbench
*/
module test_cndm_micro_pcie_us #
(
/* verilator lint_off WIDTHTRUNC */
parameter logic SIM = 1'b0,
parameter string VENDOR = "XILINX",
parameter string FAMILY = "virtexuplus",
parameter PORTS = 2,
parameter MAC_DATA_W = 32,
parameter AXIS_PCIE_DATA_W = 256,
parameter AXIS_PCIE_RC_USER_W = AXIS_PCIE_DATA_W < 512 ? 75 : 161,
parameter AXIS_PCIE_RQ_USER_W = AXIS_PCIE_DATA_W < 512 ? 62 : 137,
parameter AXIS_PCIE_CQ_USER_W = AXIS_PCIE_DATA_W < 512 ? 85 : 183,
parameter AXIS_PCIE_CC_USER_W = AXIS_PCIE_DATA_W < 512 ? 33 : 81,
parameter BAR0_APERTURE = 24
/* verilator lint_on WIDTHTRUNC */
)
();
localparam AXIS_PCIE_KEEP_W = (AXIS_PCIE_DATA_W/32);
localparam RQ_SEQ_NUM_W = AXIS_PCIE_RQ_USER_W == 60 ? 4 : 6;
logic sfp_mgt_refclk_p;
logic sfp_mgt_refclk_n;
logic sfp_mgt_refclk_out;
logic [1:0] sfp_npres;
logic [1:0] sfp_tx_fault;
logic [1:0] sfp_los;
logic pcie_clk;
logic pcie_rst;
taxi_axis_if #(
.DATA_W(AXIS_PCIE_DATA_W),
.KEEP_EN(1),
.KEEP_W(AXIS_PCIE_KEEP_W),
.USER_EN(1),
.USER_W(AXIS_PCIE_CQ_USER_W)
) s_axis_pcie_cq();
taxi_axis_if #(
.DATA_W(AXIS_PCIE_DATA_W),
.KEEP_EN(1),
.KEEP_W(AXIS_PCIE_KEEP_W),
.USER_EN(1),
.USER_W(AXIS_PCIE_CC_USER_W)
) m_axis_pcie_cc();
taxi_axis_if #(
.DATA_W(AXIS_PCIE_DATA_W),
.KEEP_EN(1),
.KEEP_W(AXIS_PCIE_KEEP_W),
.USER_EN(1),
.USER_W(AXIS_PCIE_RQ_USER_W)
) m_axis_pcie_rq();
taxi_axis_if #(
.DATA_W(AXIS_PCIE_DATA_W),
.KEEP_EN(1),
.KEEP_W(AXIS_PCIE_KEEP_W),
.USER_EN(1),
.USER_W(AXIS_PCIE_RC_USER_W)
) s_axis_pcie_rc();
logic [RQ_SEQ_NUM_W-1:0] pcie_rq_seq_num0;
logic pcie_rq_seq_num_vld0;
logic [RQ_SEQ_NUM_W-1:0] pcie_rq_seq_num1;
logic pcie_rq_seq_num_vld1;
logic [2:0] cfg_max_payload;
logic [2:0] cfg_max_read_req;
logic [3:0] cfg_rcb_status;
logic [9:0] cfg_mgmt_addr;
logic [7:0] cfg_mgmt_function_number;
logic cfg_mgmt_write;
logic [31:0] cfg_mgmt_write_data;
logic [3:0] cfg_mgmt_byte_enable;
logic cfg_mgmt_read;
logic [31:0] cfg_mgmt_read_data;
logic cfg_mgmt_read_write_done;
logic [7:0] cfg_fc_ph;
logic [11:0] cfg_fc_pd;
logic [7:0] cfg_fc_nph;
logic [11:0] cfg_fc_npd;
logic [7:0] cfg_fc_cplh;
logic [11:0] cfg_fc_cpld;
logic [2:0] cfg_fc_sel;
logic [3:0] cfg_interrupt_msi_enable;
logic [11:0] cfg_interrupt_msi_mmenable;
logic cfg_interrupt_msi_mask_update;
logic [31:0] cfg_interrupt_msi_data;
logic [1:0] cfg_interrupt_msi_select;
logic [31:0] cfg_interrupt_msi_int;
logic [31:0] cfg_interrupt_msi_pending_status;
logic cfg_interrupt_msi_pending_status_data_enable;
logic [1:0] cfg_interrupt_msi_pending_status_function_num;
logic cfg_interrupt_msi_sent;
logic cfg_interrupt_msi_fail;
logic [2:0] cfg_interrupt_msi_attr;
logic cfg_interrupt_msi_tph_present;
logic [1:0] cfg_interrupt_msi_tph_type;
logic [7:0] cfg_interrupt_msi_tph_st_tag;
logic [7:0] cfg_interrupt_msi_function_number;
logic mac_tx_clk[PORTS];
logic mac_tx_rst[PORTS];
taxi_axis_if #(
.DATA_W(MAC_DATA_W),
.ID_W(8),
.USER_EN(1),
.USER_W(1)
) mac_axis_tx[PORTS]();
logic mac_rx_clk[PORTS];
logic mac_rx_rst[PORTS];
taxi_axis_if #(
.DATA_W(96),
.KEEP_W(1),
.ID_W(8)
) mac_axis_tx_cpl[PORTS]();
taxi_axis_if #(
.DATA_W(MAC_DATA_W),
.ID_W(8),
.USER_EN(1),
.USER_W(1)
) mac_axis_rx[PORTS]();
cndm_micro_pcie_us #(
.SIM(SIM),
.VENDOR(VENDOR),
.FAMILY(FAMILY),
.PORTS(PORTS),
.RQ_SEQ_NUM_W(RQ_SEQ_NUM_W),
.BAR0_APERTURE(BAR0_APERTURE)
)
uut (
/*
* PCIe
*/
.pcie_clk(pcie_clk),
.pcie_rst(pcie_rst),
.s_axis_pcie_cq(s_axis_pcie_cq),
.m_axis_pcie_cc(m_axis_pcie_cc),
.m_axis_pcie_rq(m_axis_pcie_rq),
.s_axis_pcie_rc(s_axis_pcie_rc),
.pcie_rq_seq_num0(pcie_rq_seq_num0),
.pcie_rq_seq_num_vld0(pcie_rq_seq_num_vld0),
.pcie_rq_seq_num1(pcie_rq_seq_num1),
.pcie_rq_seq_num_vld1(pcie_rq_seq_num_vld1),
.cfg_max_payload(cfg_max_payload),
.cfg_max_read_req(cfg_max_read_req),
.cfg_rcb_status(cfg_rcb_status),
.cfg_mgmt_addr(cfg_mgmt_addr),
.cfg_mgmt_function_number(cfg_mgmt_function_number),
.cfg_mgmt_write(cfg_mgmt_write),
.cfg_mgmt_write_data(cfg_mgmt_write_data),
.cfg_mgmt_byte_enable(cfg_mgmt_byte_enable),
.cfg_mgmt_read(cfg_mgmt_read),
.cfg_mgmt_read_data(cfg_mgmt_read_data),
.cfg_mgmt_read_write_done(cfg_mgmt_read_write_done),
.cfg_fc_ph(cfg_fc_ph),
.cfg_fc_pd(cfg_fc_pd),
.cfg_fc_nph(cfg_fc_nph),
.cfg_fc_npd(cfg_fc_npd),
.cfg_fc_cplh(cfg_fc_cplh),
.cfg_fc_cpld(cfg_fc_cpld),
.cfg_fc_sel(cfg_fc_sel),
.cfg_interrupt_msi_enable(cfg_interrupt_msi_enable),
.cfg_interrupt_msi_mmenable(cfg_interrupt_msi_mmenable),
.cfg_interrupt_msi_mask_update(cfg_interrupt_msi_mask_update),
.cfg_interrupt_msi_data(cfg_interrupt_msi_data),
.cfg_interrupt_msi_select(cfg_interrupt_msi_select),
.cfg_interrupt_msi_int(cfg_interrupt_msi_int),
.cfg_interrupt_msi_pending_status(cfg_interrupt_msi_pending_status),
.cfg_interrupt_msi_pending_status_data_enable(cfg_interrupt_msi_pending_status_data_enable),
.cfg_interrupt_msi_pending_status_function_num(cfg_interrupt_msi_pending_status_function_num),
.cfg_interrupt_msi_sent(cfg_interrupt_msi_sent),
.cfg_interrupt_msi_fail(cfg_interrupt_msi_fail),
.cfg_interrupt_msi_attr(cfg_interrupt_msi_attr),
.cfg_interrupt_msi_tph_present(cfg_interrupt_msi_tph_present),
.cfg_interrupt_msi_tph_type(cfg_interrupt_msi_tph_type),
.cfg_interrupt_msi_tph_st_tag(cfg_interrupt_msi_tph_st_tag),
.cfg_interrupt_msi_function_number(cfg_interrupt_msi_function_number),
/*
* Ethernet: SFP+
*/
.mac_tx_clk(mac_tx_clk),
.mac_tx_rst(mac_tx_rst),
.mac_axis_tx(mac_axis_tx),
.mac_axis_tx_cpl(mac_axis_tx_cpl),
.mac_rx_clk(mac_rx_clk),
.mac_rx_rst(mac_rx_rst),
.mac_axis_rx(mac_axis_rx)
);
endmodule
`resetall