# SPDX-License-Identifier: CERN-OHL-S-2.0 """ Copyright (c) 2025 FPGA Ninja, LLC Authors: - Alex Forencich """ import array import datetime import logging import struct from collections import deque import cocotb from cocotb.queue import Queue from cocotb.triggers import RisingEdge # Command opcodes CNDM_CMD_OP_NOP = 0x0000 CNDM_CMD_OP_CFG = 0x0100 CNDM_CMD_OP_ACCESS_REG = 0x0180 CNDM_CMD_OP_PTP = 0x0190 CNDM_CMD_OP_HWID = 0x01A0 CNDM_CMD_OP_HWMON = 0x01B0 CNDM_CMD_OP_PLL = 0x01C0 CNDM_CMD_OP_CREATE_EQ = 0x0200 CNDM_CMD_OP_MODIFY_EQ = 0x0201 CNDM_CMD_OP_QUERY_EQ = 0x0202 CNDM_CMD_OP_DESTROY_EQ = 0x0203 CNDM_CMD_OP_CREATE_CQ = 0x0210 CNDM_CMD_OP_MODIFY_CQ = 0x0211 CNDM_CMD_OP_QUERY_CQ = 0x0212 CNDM_CMD_OP_DESTROY_CQ = 0x0213 CNDM_CMD_OP_CREATE_SQ = 0x0220 CNDM_CMD_OP_MODIFY_SQ = 0x0221 CNDM_CMD_OP_QUERY_SQ = 0x0222 CNDM_CMD_OP_DESTROY_SQ = 0x0223 CNDM_CMD_OP_CREATE_RQ = 0x0230 CNDM_CMD_OP_MODIFY_RQ = 0x0231 CNDM_CMD_OP_QUERY_RQ = 0x0232 CNDM_CMD_OP_DESTROY_RQ = 0x0233 CNDM_CMD_OP_CREATE_QP = 0x0240 CNDM_CMD_OP_MODIFY_QP = 0x0241 CNDM_CMD_OP_QUERY_QP = 0x0242 CNDM_CMD_OP_DESTROY_QP = 0x0243 CNDM_CMD_REG_FLG_WRITE = 0x00000001 CNDM_CMD_REG_FLG_RAW = 0x00000100 CNDM_CMD_PTP_FLG_SET_TOD = 0x00000001 CNDM_CMD_PTP_FLG_OFFSET_TOD = 0x00000002 CNDM_CMD_PTP_FLG_SET_REL = 0x00000004 CNDM_CMD_PTP_FLG_OFFSET_REL = 0x00000008 CNDM_CMD_PTP_FLG_OFFSET_FNS = 0x00000010 CNDM_CMD_PTP_FLG_SET_PERIOD = 0x00000080 # Board operation commands CNDM_CMD_BRD_OP_NOP = 0x0000 CNDM_CMD_BRD_OP_FLASH_RD = 0x0100 CNDM_CMD_BRD_OP_FLASH_WR = 0x0101 CNDM_CMD_BRD_OP_FLASH_CMD = 0x0108 CNDM_CMD_BRD_OP_EEPROM_RD = 0x0200 CNDM_CMD_BRD_OP_EEPROM_WR = 0x0201 CNDM_CMD_BRD_OP_OPTIC_RD = 0x0300 CNDM_CMD_BRD_OP_OPTIC_WR = 0x0301 CNDM_CMD_BRD_OP_HWID_SN_RD = 0x0400 CNDM_CMD_BRD_OP_HWID_VPD_RD = 0x0410 CNDM_CMD_BRD_OP_HWID_MAC_RD = 0x0480 CNDM_CMD_BRD_OP_PLL_STATUS_RD = 0x0500 CNDM_CMD_BRD_OP_PLL_TUNE_RAW_RD = 0x0502 CNDM_CMD_BRD_OP_PLL_TUNE_RAW_WR = 0x0503 CNDM_CMD_BRD_OP_PLL_TUNE_PPT_RD = 0x0504 CNDM_CMD_BRD_OP_PLL_TUNE_PPT_WR = 0x0505 CNDM_CMD_BRD_OP_I2C_RD = 0x8100 CNDM_CMD_BRD_OP_I2C_WR = 0x8101 class Eq: def __init__(self, driver, port): self.driver = driver self.log = driver.log self.port = port self.log_size = 0 self.size = 0 self.size_mask = 0 self.stride = 0 self.eqn = None self.enabled = False self.buf_size = 0 self.buf_region = None self.buf_dma = 0 self.buf = None self.irqn = None self.cq_table = {} self.cons_ptr = None self.db_offset = None self.hw_regs = self.driver.hw_regs async def open(self, irqn, size): if self.eqn is not None: raise Exception("Already open") self.log_size = size.bit_length() - 1 self.size = 2**self.log_size self.size_mask = self.size-1 self.stride = 16 self.buf_size = self.size*self.stride self.buf_region = self.driver.pool.alloc_region(self.buf_size) self.buf_dma = self.buf_region.get_absolute_address(0) self.buf = self.buf_region.mem self.buf[0:self.buf_size] = b'\x00'*self.buf_size self.cons_ptr = 0 self.irqn = irqn self.cq_table = {} rsp = await self.driver.exec_cmd(struct.pack(" self.size async def write_prod_ptr(self): await self.hw_regs.write_dword(self.db_offset, self.prod_ptr & 0xffff) async def start_xmit(self, data): headroom = 10 tx_buf = self.driver.alloc_pkt() await tx_buf.write(headroom, data) index = self.prod_ptr & self.size_mask ptr = tx_buf.get_absolute_address(0) struct.pack_into(' self.size async def write_prod_ptr(self): await self.hw_regs.write_dword(self.db_offset, self.prod_ptr & 0xffff) 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 not self.is_ring_empty(): index = self.cons_ptr & self.size_mask self.free_rx_desc(index) self.cons_ptr += 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('> 20, (self.cmd_ver >> 12) & 0xff, self.cmd_ver & 0xfff) self.fpga_id = rsp_unpacked[10] self.fw_id = rsp_unpacked[11] self.fw_ver = rsp_unpacked[12] self.board_id = rsp_unpacked[13] self.board_ver = rsp_unpacked[14] self.build_date = rsp_unpacked[15] self.git_hash = rsp_unpacked[16] self.release_info = rsp_unpacked[17] self.log.info("FPGA JTAG ID: 0x%08x", self.fpga_id) self.log.info("FW ID: 0x%08x", self.fw_id) self.log.info("FW version: %d.%d.%d", self.fw_ver >> 20, (self.fw_ver >> 12) & 0xff, self.fw_ver & 0xfff) self.log.info("Board ID: 0x%08x", self.board_id) self.log.info("Board version: %d.%d.%d", self.board_ver >> 20, (self.board_ver >> 12) & 0xff, self.board_ver & 0xfff) self.log.info("Build date: %s UTC (raw: 0x%08x)", datetime.datetime.fromtimestamp(self.build_date, datetime.timezone.utc).isoformat(' '), self.build_date) self.log.info("Git hash: %08x", self.git_hash) self.log.info("Release info: %08x", self.release_info) # Get config information rsp = await self.exec_cmd(struct.pack("> 16 self.ptp_clk_per_ns_den = rsp_unpacked[15] & 0xffff self.ptp_clk_per_ns_num = rsp_unpacked[15] >> 16 self.log.info("Port count: %d", self.port_count) self.log.info("Sys clock period: %f MHz (raw %d/%d ns)", 1000/(self.sys_clk_per_ns_num/self.sys_clk_per_ns_den), self.sys_clk_per_ns_num, self.sys_clk_per_ns_den) self.log.info("PTP clock period: %f MHz (raw %d/%d ns)", 1000/(self.ptp_clk_per_ns_num/self.ptp_clk_per_ns_den), self.ptp_clk_per_ns_num, self.ptp_clk_per_ns_den) # Get config information rsp = await self.exec_cmd(struct.pack("> 8) & 0xff self.eq_pool = (rsp_unpacked[10] >> 16) & 0xff self.eqe_ver = (rsp_unpacked[10] >> 24) & 0xff self.log_max_cq = rsp_unpacked[11] & 0xff self.log_max_cq_sz = (rsp_unpacked[11] >> 8) & 0xff self.cq_pool = (rsp_unpacked[11] >> 16) & 0xff self.cqe_ver = (rsp_unpacked[11] >> 24) & 0xff self.log_max_sq = rsp_unpacked[12] & 0xff self.log_max_sq_sz = (rsp_unpacked[12] >> 8) & 0xff self.sq_pool = (rsp_unpacked[12] >> 16) & 0xff self.sqe_ver = (rsp_unpacked[12] >> 24) & 0xff self.log_max_rq = rsp_unpacked[13] & 0xff self.log_max_rq_sz = (rsp_unpacked[13] >> 8) & 0xff self.rq_pool = (rsp_unpacked[13] >> 16) & 0xff self.rqe_ver = (rsp_unpacked[13] >> 24) & 0xff self.log.info("Max EQ count: %d (log %d)", 2**self.log_max_eq, self.log_max_eq) self.log.info("Max EQ size: %d (log %d)", 2**self.log_max_eq_sz, self.log_max_eq_sz) self.log.info("EQ pool: %d", self.eq_pool) self.log.info("EQE version: %d", self.eqe_ver) self.log.info("Max CQ count: %d (log %d)", 2**self.log_max_cq, self.log_max_cq) self.log.info("Max CQ size: %d (log %d)", 2**self.log_max_cq_sz, self.log_max_cq_sz) self.log.info("CQ pool: %d", self.cq_pool) self.log.info("CQE version: %d", self.cqe_ver) self.log.info("Max SQ count: %d (log %d)", 2**self.log_max_sq, self.log_max_sq) self.log.info("Max SQ size: %d (log %d)", 2**self.log_max_sq_sz, self.log_max_sq_sz) self.log.info("SQ pool: %d", self.sq_pool) self.log.info("SQE version: %d", self.sqe_ver) self.log.info("Max RQ count: %d (log %d)", 2**self.log_max_rq, self.log_max_rq) self.log.info("Max RQ size: %d (log %d)", 2**self.log_max_rq_sz, self.log_max_rq_sz) self.log.info("RQ pool: %d", self.rq_pool) self.log.info("RQE version: %d", self.rqe_ver) # Get PTP information rsp = await self.exec_cmd(struct.pack("