mirror of
https://github.com/fpganinja/taxi.git
synced 2026-04-08 13:08:42 -07:00
1208 lines
33 KiB
Python
1208 lines
33 KiB
Python
# 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("<HHLLLLLLLQQLLLL",
|
|
0, # rsvd
|
|
CNDM_CMD_OP_CREATE_EQ, # opcode
|
|
0x00000000, # flags
|
|
self.port.index, # port
|
|
0, # eqn
|
|
self.irqn, # irqn
|
|
0, # pd
|
|
self.log_size, # size
|
|
0, # dboffs
|
|
self.buf_dma, # base addr
|
|
0, # ptr2
|
|
0, # prod_ptr
|
|
0, # cons_ptr
|
|
0, # rsvd
|
|
0, # rsvd
|
|
))
|
|
|
|
rsp_unpacked = struct.unpack("<HHLLLLLLLQQLLLL", rsp)
|
|
print(rsp_unpacked)
|
|
self.eqn = rsp_unpacked[4]
|
|
self.db_offset = rsp_unpacked[8]
|
|
|
|
if self.db_offset == 0:
|
|
self.eqn = None
|
|
self.db_offset = None
|
|
self.log.error("Failed to allocate EQ")
|
|
return
|
|
|
|
await self.write_cons_ptr_arm()
|
|
|
|
self.log.info("Opened EQ %d", self.eqn)
|
|
self.log.info("Using doorbell at offset 0x%08x", self.db_offset)
|
|
|
|
self.enabled = True
|
|
|
|
async def close(self):
|
|
if self.eqn is None:
|
|
return
|
|
|
|
self.enabled = False
|
|
|
|
rsp = await self.driver.exec_cmd(struct.pack("<HHLLLLLLLQQLLLL",
|
|
0, # rsvd
|
|
CNDM_CMD_OP_DESTROY_EQ, # opcode
|
|
0x00000000, # flags
|
|
self.port.index, # port
|
|
self.eqn, # eqn
|
|
0, # irqn
|
|
0, # pd
|
|
0, # size
|
|
0, # dboffs
|
|
0, # base addr
|
|
0, # ptr2
|
|
0, # prod_ptr
|
|
0, # cons_ptr
|
|
0, # rsvd
|
|
0, # rsvd
|
|
))
|
|
|
|
self.eqn = None
|
|
|
|
# TODO free buffer
|
|
|
|
def attach_cq(self, cq):
|
|
self.cq_table[cq.cqn] = cq
|
|
|
|
def detach_cq(self, cq):
|
|
del self.cq_table[cq.cqn]
|
|
|
|
async def write_cons_ptr(self):
|
|
await self.hw_regs.write_dword(self.db_offset, self.cons_ptr & 0xffff)
|
|
|
|
async def write_cons_ptr_arm(self):
|
|
await self.hw_regs.write_dword(self.db_offset, (self.cons_ptr & 0xffff) | 0x80000000)
|
|
|
|
async def process_eq(self):
|
|
self.log.info("Process EQ")
|
|
|
|
eq_cons_ptr = self.cons_ptr
|
|
eq_index = eq_cons_ptr & self.size_mask
|
|
|
|
while True:
|
|
# event_data = struct.unpack_from("<HHLLLLLLL", self.buf, eq_index*self.stride)
|
|
event_data = struct.unpack_from("<HHLLL", self.buf, eq_index*self.stride)
|
|
|
|
self.log.info("EQ %d index %d data: %s", self.eqn, eq_index, repr(event_data))
|
|
|
|
if bool(event_data[-1] & 0x80000000) == bool(eq_cons_ptr & self.size):
|
|
self.log.info("EQ %d empty", self.eqn)
|
|
break
|
|
|
|
if event_data[1] == 0x0000:
|
|
# completion
|
|
self.log.info("Event from CQ %d", event_data[2])
|
|
cq = self.cq_table[event_data[2]]
|
|
await cq.handler(cq)
|
|
|
|
eq_cons_ptr += 1
|
|
eq_index = eq_cons_ptr & self.size_mask
|
|
|
|
self.cons_ptr = eq_cons_ptr
|
|
await self.write_cons_ptr_arm()
|
|
|
|
|
|
class Cq:
|
|
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.cqn = None
|
|
self.enabled = False
|
|
|
|
self.buf_size = 0
|
|
self.buf_region = None
|
|
self.buf_dma = 0
|
|
self.buf = None
|
|
|
|
self.eq = None
|
|
self.irqn = None
|
|
|
|
self.src_ring = None
|
|
self.handler = None
|
|
|
|
self.cons_ptr = None
|
|
|
|
self.db_offset = None
|
|
self.hw_regs = self.driver.hw_regs
|
|
|
|
async def open(self, eq, size):
|
|
if self.cqn 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
|
|
|
|
if isinstance(eq, Eq):
|
|
self.eq = eq
|
|
dqn = eq.eqn
|
|
else:
|
|
self.irqn = eq
|
|
dqn = eq | 0x80000000
|
|
|
|
rsp = await self.driver.exec_cmd(struct.pack("<HHLLLLLLLQQLLLL",
|
|
0, # rsvd
|
|
CNDM_CMD_OP_CREATE_CQ, # opcode
|
|
0x00000000, # flags
|
|
self.port.index, # port
|
|
0, # cqn
|
|
dqn, # eqn
|
|
0, # pd
|
|
self.log_size, # size
|
|
0, # dboffs
|
|
self.buf_dma, # base addr
|
|
0, # ptr2
|
|
0, # prod_ptr
|
|
0, # cons_ptr
|
|
0, # rsvd
|
|
0, # rsvd
|
|
))
|
|
|
|
rsp_unpacked = struct.unpack("<HHLLLLLLLQQLLLL", rsp)
|
|
print(rsp_unpacked)
|
|
self.cqn = rsp_unpacked[4]
|
|
self.db_offset = rsp_unpacked[8]
|
|
|
|
if self.db_offset == 0:
|
|
self.cqn = None
|
|
self.db_offset = None
|
|
self.log.error("Failed to allocate CQ")
|
|
return
|
|
|
|
if self.eq:
|
|
self.eq.attach_cq(self)
|
|
|
|
await self.write_cons_ptr_arm()
|
|
|
|
self.log.info("Opened CQ %d", self.cqn)
|
|
self.log.info("Using doorbell at offset 0x%08x", self.db_offset)
|
|
|
|
self.enabled = True
|
|
|
|
async def close(self):
|
|
if self.cqn is None:
|
|
return
|
|
|
|
self.enabled = False
|
|
|
|
rsp = await self.driver.exec_cmd(struct.pack("<HHLLLLLLLQQLLLL",
|
|
0, # rsvd
|
|
CNDM_CMD_OP_DESTROY_CQ, # opcode
|
|
0x00000000, # flags
|
|
self.port.index, # port
|
|
self.cqn, # cqn
|
|
0, # eqn
|
|
0, # pd
|
|
0, # size
|
|
0, # dboffs
|
|
0, # base addr
|
|
0, # ptr2
|
|
0, # prod_ptr
|
|
0, # cons_ptr
|
|
0, # rsvd
|
|
0, # rsvd
|
|
))
|
|
|
|
self.cqn = None
|
|
|
|
# TODO free buffer
|
|
|
|
async def write_cons_ptr(self):
|
|
await self.hw_regs.write_dword(self.db_offset, self.cons_ptr & 0xffff)
|
|
|
|
async def write_cons_ptr_arm(self):
|
|
await self.hw_regs.write_dword(self.db_offset, (self.cons_ptr & 0xffff) | 0x80000000)
|
|
|
|
|
|
class Sq:
|
|
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.full_size = 0
|
|
self.stride = 0
|
|
self.sqn = None
|
|
self.enabled = False
|
|
|
|
self.buf_size = 0
|
|
self.buf_region = None
|
|
self.buf_dma = 0
|
|
self.buf = None
|
|
|
|
self.cq = None
|
|
|
|
self.prod_ptr = None
|
|
self.cons_ptr = None
|
|
|
|
self.packets = 0
|
|
self.bytes = 0
|
|
|
|
self.db_offset = None
|
|
self.hw_regs = self.driver.hw_regs
|
|
|
|
async def open(self, cq, size):
|
|
if self.sqn 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.tx_info = [None]*self.size
|
|
|
|
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.prod_ptr = 0
|
|
self.cons_ptr = 0
|
|
|
|
self.cq = cq
|
|
self.cq.src_ring = self
|
|
self.cq.handler = Sq.process_tx_cq
|
|
|
|
rsp = await self.driver.exec_cmd(struct.pack("<HHLLLLLLLQQLLLL",
|
|
0, # rsvd
|
|
CNDM_CMD_OP_CREATE_SQ, # opcode
|
|
0x00000000, # flags
|
|
self.port.index, # port
|
|
0, # sqn
|
|
self.cq.cqn, # cqn
|
|
0, # pd
|
|
self.log_size, # size
|
|
0, # dboffs
|
|
self.buf_dma, # base addr
|
|
0, # ptr2
|
|
0, # prod_ptr
|
|
0, # cons_ptr
|
|
0, # rsvd
|
|
0, # rsvd
|
|
))
|
|
|
|
rsp_unpacked = struct.unpack("<HHLLLLLLLQQLLLL", rsp)
|
|
print(rsp_unpacked)
|
|
self.sqn = rsp_unpacked[4]
|
|
self.db_offset = rsp_unpacked[8]
|
|
|
|
if self.db_offset == 0:
|
|
self.sqn = None
|
|
self.db_offset = None
|
|
self.log.error("Failed to allocate SQ")
|
|
return
|
|
|
|
self.log.info("Opened SQ %d (CQ %d)", self.sqn, cq.cqn)
|
|
self.log.info("Using doorbell at offset 0x%08x", self.db_offset)
|
|
|
|
self.enabled = True
|
|
|
|
async def close(self):
|
|
if self.sqn is None:
|
|
return
|
|
|
|
self.enabled = False
|
|
|
|
rsp = await self.driver.exec_cmd(struct.pack("<HHLLLLLLLQQLLLL",
|
|
0, # rsvd
|
|
CNDM_CMD_OP_DESTROY_SQ, # opcode
|
|
0x00000000, # flags
|
|
self.port.index, # port
|
|
self.sqn, # sqn
|
|
0, # eqn
|
|
0, # pd
|
|
0, # size
|
|
0, # dboffs
|
|
0, # base addr
|
|
0, # ptr2
|
|
0, # prod_ptr
|
|
0, # cons_ptr
|
|
0, # rsvd
|
|
0, # rsvd
|
|
))
|
|
|
|
self.sqn = None
|
|
|
|
# TODO free buffer
|
|
|
|
def is_ring_empty(self):
|
|
return self.prod_ptr == self.cons_ptr
|
|
|
|
def is_ring_full(self):
|
|
return ((self.prod_ptr - self.cons_ptr) & 0xffffffff) > 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('<xxxxLQ', self.buf, 16*index, len(data), ptr+headroom)
|
|
self.tx_info[index] = tx_buf
|
|
self.prod_ptr += 1
|
|
await self.write_prod_ptr()
|
|
|
|
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 not self.is_ring_empty():
|
|
index = self.cons_ptr & self.size_mask
|
|
self.free_tx_desc(index)
|
|
self.cons_ptr += 1
|
|
|
|
@staticmethod
|
|
async def process_tx_cq(cq):
|
|
sq = cq.src_ring
|
|
|
|
cq.log.info("Process CQ %d for SQ %d", cq.cqn, sq.sqn)
|
|
|
|
cq_cons_ptr = cq.cons_ptr
|
|
cons_ptr = sq.cons_ptr
|
|
|
|
while True:
|
|
cq_index = cq_cons_ptr & cq.size_mask
|
|
index = cons_ptr & sq.size_mask
|
|
|
|
cpl_data = struct.unpack_from("<LLLL", cq.buf, cq_index*16)
|
|
|
|
cq.log.info("TX CQ index %d data %s", cq_index, cpl_data)
|
|
|
|
if bool(cpl_data[-1] & 0x80000000) == bool(cq_cons_ptr & cq.size):
|
|
cq.log.info("CQ empty")
|
|
break
|
|
|
|
pkt = sq.tx_info[index]
|
|
|
|
sq.free_tx_desc(index)
|
|
|
|
cq_cons_ptr += 1
|
|
cons_ptr += 1
|
|
|
|
cq.cons_ptr = cq_cons_ptr
|
|
sq.cons_ptr = cons_ptr
|
|
|
|
await cq.write_cons_ptr_arm()
|
|
|
|
|
|
class Rq:
|
|
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.full_size = 0
|
|
self.stride = 0
|
|
self.rqn = None
|
|
self.enabled = False
|
|
|
|
self.buf_size = 0
|
|
self.buf_region = None
|
|
self.buf_dma = 0
|
|
self.buf = None
|
|
|
|
self.cq = None
|
|
|
|
self.prod_ptr = None
|
|
self.cons_ptr = None
|
|
|
|
self.packets = 0
|
|
self.bytes = 0
|
|
|
|
self.db_offset = None
|
|
self.hw_regs = self.driver.hw_regs
|
|
|
|
async def open(self, cq, size):
|
|
if self.rqn 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.rx_info = [None]*self.size
|
|
|
|
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.prod_ptr = 0
|
|
self.cons_ptr = 0
|
|
|
|
self.cq = cq
|
|
self.cq.src_ring = self
|
|
self.cq.handler = Rq.process_rx_cq
|
|
|
|
rsp = await self.driver.exec_cmd(struct.pack("<HHLLLLLLLQQLLLL",
|
|
0, # rsvd
|
|
CNDM_CMD_OP_CREATE_RQ, # opcode
|
|
0x00000000, # flags
|
|
self.port.index, # port
|
|
0, # rqn
|
|
self.cq.cqn, # cqn
|
|
0, # pd
|
|
self.log_size, # size
|
|
0, # dboffs
|
|
self.buf_dma, # base addr
|
|
0, # ptr2
|
|
0, # prod_ptr
|
|
0, # cons_ptr
|
|
0, # rsvd
|
|
0, # rsvd
|
|
))
|
|
|
|
rsp_unpacked = struct.unpack("<HHLLLLLLLQQLLLL", rsp)
|
|
print(rsp_unpacked)
|
|
self.rqn = rsp_unpacked[4]
|
|
self.db_offset = rsp_unpacked[8]
|
|
|
|
if self.db_offset == 0:
|
|
self.rqn = None
|
|
self.db_offset = None
|
|
self.log.error("Failed to allocate RQ")
|
|
return
|
|
|
|
self.log.info("Opened RQ %d (CQ %d)", self.rqn, cq.cqn)
|
|
self.log.info("Using doorbell at offset 0x%08x", self.db_offset)
|
|
|
|
self.enabled = True
|
|
|
|
await self.refill_rx_buffers()
|
|
|
|
async def close(self):
|
|
if self.rqn is None:
|
|
return
|
|
|
|
self.enabled = False
|
|
|
|
rsp = await self.driver.exec_cmd(struct.pack("<HHLLLLLLLQQLLLL",
|
|
0, # rsvd
|
|
CNDM_CMD_OP_DESTROY_RQ, # opcode
|
|
0x00000000, # flags
|
|
self.port.index, # port
|
|
self.rqn, # rqn
|
|
0, # eqn
|
|
0, # pd
|
|
0, # size
|
|
0, # dboffs
|
|
0, # base addr
|
|
0, # ptr2
|
|
0, # prod_ptr
|
|
0, # cons_ptr
|
|
0, # rsvd
|
|
0, # rsvd
|
|
))
|
|
|
|
self.rqn = None
|
|
|
|
# TODO free buffer
|
|
|
|
def is_ring_empty(self):
|
|
return self.prod_ptr == self.cons_ptr
|
|
|
|
def is_ring_full(self):
|
|
return ((self.prod_ptr - self.cons_ptr) & 0xffffffff) > 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('<xxxxLQ', self.buf, 16*index, length, ptr)
|
|
|
|
async def refill_rx_buffers(self):
|
|
missing = self.size - (self.prod_ptr - self.cons_ptr)
|
|
|
|
if missing < 8:
|
|
return
|
|
|
|
for k in range(missing):
|
|
self.prepare_rx_desc(self.prod_ptr & self.size_mask)
|
|
self.prod_ptr += 1
|
|
|
|
await self.write_prod_ptr()
|
|
|
|
@staticmethod
|
|
async def process_rx_cq(cq):
|
|
rq = cq.src_ring
|
|
|
|
cq.log.info("Process CQ %d for RQ %d", cq.cqn, rq.rqn)
|
|
|
|
cq_cons_ptr = cq.cons_ptr
|
|
cons_ptr = rq.cons_ptr
|
|
|
|
while True:
|
|
cq_index = cq_cons_ptr & cq.size_mask
|
|
index = cons_ptr & rq.size_mask
|
|
|
|
cpl_data = struct.unpack_from("<LLLL", cq.buf, cq_index*16)
|
|
|
|
rq.log.info("RX CQ index %d data %s", cq_index, cpl_data)
|
|
|
|
if bool(cpl_data[-1] & 0x80000000) == bool(cq_cons_ptr & cq.size):
|
|
rq.log.info("CQ empty")
|
|
break
|
|
|
|
pkt = rq.rx_info[index]
|
|
length = cpl_data[1]
|
|
|
|
data = pkt[:length]
|
|
|
|
rq.log.info("Packet: %s", data)
|
|
|
|
rq.port.rx_queue.put_nowait(data)
|
|
|
|
rq.free_rx_desc(index)
|
|
|
|
cq_cons_ptr += 1
|
|
cons_ptr += 1
|
|
|
|
cq.cons_ptr = cq_cons_ptr
|
|
rq.cons_ptr = cons_ptr
|
|
|
|
await rq.refill_rx_buffers()
|
|
await cq.write_cons_ptr_arm()
|
|
|
|
|
|
class Port:
|
|
def __init__(self, driver, index):
|
|
self.driver = driver
|
|
self.log = driver.log
|
|
self.index = index
|
|
self.hw_regs = driver.hw_regs
|
|
|
|
self.eq_count = 1
|
|
self.eq = []
|
|
|
|
self.rxq_count = 1
|
|
self.rxq = []
|
|
|
|
self.txq_count = 1
|
|
self.txq = []
|
|
|
|
self.rx_queue = Queue()
|
|
|
|
async def init(self):
|
|
for k in range(self.eq_count):
|
|
eq = Eq(self.driver, self)
|
|
await eq.open(self.index, 256)
|
|
self.eq.append(eq)
|
|
|
|
await self.open()
|
|
|
|
async def open(self):
|
|
for k in range(self.rxq_count):
|
|
cq = Cq(self.driver, self)
|
|
await cq.open(self.eq[0], 256)
|
|
|
|
q = Rq(self.driver, self)
|
|
await q.open(cq, 256)
|
|
|
|
self.rxq.append(q)
|
|
|
|
for k in range(self.txq_count):
|
|
cq = Cq(self.driver, self)
|
|
await cq.open(self.eq[0], 256)
|
|
|
|
q = Sq(self.driver, self)
|
|
await q.open(cq, 256)
|
|
|
|
self.txq.append(q)
|
|
|
|
async def start_xmit(self, data, tx_ring=0):
|
|
await self.txq[tx_ring].start_xmit(data)
|
|
|
|
async def recv(self):
|
|
return await self.rx_queue.get()
|
|
|
|
async def recv_nowait(self):
|
|
return self.rx_queue.get_nowait()
|
|
|
|
|
|
class Interrupt:
|
|
def __init__(self, index, handler=None):
|
|
self.index = index
|
|
self.queue = Queue()
|
|
self.handler = handler
|
|
self.signal = None
|
|
|
|
cocotb.start_soon(self._run())
|
|
|
|
@classmethod
|
|
def from_edge(cls, index, signal, handler=None):
|
|
obj = cls(index, handler)
|
|
obj.signal = signal
|
|
cocotb.start_soon(obj._run_edge())
|
|
return obj
|
|
|
|
async def interrupt(self):
|
|
self.queue.put_nowait(None)
|
|
|
|
async def _run(self):
|
|
while True:
|
|
await self.queue.get()
|
|
if self.handler:
|
|
await self.handler(self.index)
|
|
|
|
async def _run_edge(self):
|
|
while True:
|
|
await RisingEdge(self.signal)
|
|
await self.interrupt()
|
|
|
|
|
|
class Driver:
|
|
def __init__(self):
|
|
self.log = logging.getLogger("cocotb.cndm")
|
|
|
|
self.dev = None
|
|
self.pool = None
|
|
self.hw_regs = None
|
|
|
|
self.irq_list = []
|
|
|
|
self.port_count = None
|
|
|
|
self.ports = []
|
|
|
|
self.free_packets = deque()
|
|
self.allocated_packets = []
|
|
|
|
# config
|
|
self.cfg_page_max = None
|
|
self.cmd_ver = None
|
|
|
|
# FW ID
|
|
self.fpga_id = None
|
|
self.fw_id = None
|
|
self.fw_ver = None
|
|
self.board_id = None
|
|
self.board_ver = None
|
|
self.build_date = None
|
|
self.git_hash = None
|
|
self.release_info = None
|
|
|
|
# HW config
|
|
self.sys_clk_per_ns_num = None
|
|
self.sys_clk_per_ns_den = None
|
|
self.ptp_clk_per_ns_num = None
|
|
self.ptp_clk_per_ns_den = None
|
|
|
|
# Resources
|
|
self.log_max_eq = None
|
|
self.log_max_eq_sz = None
|
|
self.eq_pool = None
|
|
self.eqe_ver = None
|
|
self.log_max_cq = None
|
|
self.log_max_cq_sz = None
|
|
self.cq_pool = None
|
|
self.cqe_ver = None
|
|
self.log_max_sq = None
|
|
self.log_max_sq_sz = None
|
|
self.sq_pool = None
|
|
self.sqe_ver = None
|
|
self.log_max_rq = None
|
|
self.log_max_rq_sz = None
|
|
self.rq_pool = None
|
|
self.rqe_ver = None
|
|
|
|
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]
|
|
|
|
# set up MSI
|
|
for index in range(32):
|
|
irq = Interrupt(index, self.interrupt_handler)
|
|
self.dev.request_irq(index, irq.interrupt)
|
|
self.irq_list.append(irq)
|
|
|
|
await self.init_common()
|
|
|
|
async def init_common(self):
|
|
|
|
# Get config information
|
|
rsp = await self.exec_cmd(struct.pack("<HHLHHLLLLLLLLLLLLL",
|
|
0, # rsvd
|
|
CNDM_CMD_OP_CFG, # opcode
|
|
0x00000000, # flags
|
|
0, # cfg_page
|
|
0, # num_cfg_pages
|
|
0,
|
|
0,
|
|
0,
|
|
0,
|
|
0,
|
|
0,
|
|
0,
|
|
0,
|
|
0,
|
|
0,
|
|
0,
|
|
0,
|
|
0,
|
|
))
|
|
|
|
rsp_unpacked = struct.unpack("<HHLHHLLLLLLLLLLLLL", rsp)
|
|
print(rsp_unpacked)
|
|
|
|
self.cfg_page_max = rsp_unpacked[4]
|
|
self.cmd_ver = rsp_unpacked[5]
|
|
|
|
self.log.info("Config pages: %d", self.cfg_page_max+1)
|
|
self.log.info("Command version: %d.%d.%d", self.cmd_ver >> 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("<HHLHHLLLLLLLLLLLLL",
|
|
0, # rsvd
|
|
CNDM_CMD_OP_CFG, # opcode
|
|
0x00000000, # flags
|
|
1, # cfg_page
|
|
0, # num_cfg_pages
|
|
0,
|
|
0,
|
|
0,
|
|
0,
|
|
0,
|
|
0,
|
|
0,
|
|
0,
|
|
0,
|
|
0,
|
|
0,
|
|
0,
|
|
0,
|
|
))
|
|
|
|
rsp_unpacked = struct.unpack("<HHLHHLLLLLLLLLLLLL", rsp)
|
|
print(rsp_unpacked)
|
|
|
|
self.port_count = rsp_unpacked[10] & 0xff
|
|
self.sys_clk_per_ns_den = rsp_unpacked[14] & 0xffff
|
|
self.sys_clk_per_ns_num = rsp_unpacked[14] >> 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("<HHLHHLLLLLLLLLLLLL",
|
|
0, # rsvd
|
|
CNDM_CMD_OP_CFG, # opcode
|
|
0x00000000, # flags
|
|
2, # cfg_page
|
|
0, # num_cfg_pages
|
|
0,
|
|
0,
|
|
0,
|
|
0,
|
|
0,
|
|
0,
|
|
0,
|
|
0,
|
|
0,
|
|
0,
|
|
0,
|
|
0,
|
|
0,
|
|
))
|
|
|
|
rsp_unpacked = struct.unpack("<HHLHHLLLLLLLLLLLLL", rsp)
|
|
print(rsp_unpacked)
|
|
|
|
# Resources
|
|
self.log_max_eq = rsp_unpacked[10] & 0xff
|
|
self.log_max_eq_sz = (rsp_unpacked[10] >> 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("<HHLLLQQQQQLL",
|
|
0, # rsvd
|
|
CNDM_CMD_OP_PTP, # opcode
|
|
0x00000000, # flags
|
|
0, # fns
|
|
0, # tod_ns
|
|
0, # tod_sec
|
|
0, # rel_ns
|
|
0, # ptm
|
|
0, # nom_period
|
|
0, # period
|
|
0, # rsvd
|
|
0, # rsvd
|
|
))
|
|
|
|
rsp_unpacked = struct.unpack("<HHLLLQQQQQLL", rsp)
|
|
print(rsp_unpacked)
|
|
|
|
nom_period = rsp_unpacked[8]
|
|
self.log.info("PHC nominal period: %.09f ns (raw 0x%x)", nom_period / 2**32, nom_period)
|
|
|
|
# Test setting PTP time
|
|
rsp = await self.exec_cmd(struct.pack("<HHLLLQQQQQLL",
|
|
0, # rsvd
|
|
CNDM_CMD_OP_PTP, # opcode
|
|
CNDM_CMD_PTP_FLG_SET_TOD | CNDM_CMD_PTP_FLG_SET_REL | CNDM_CMD_PTP_FLG_SET_PERIOD, # flags
|
|
0, # fns
|
|
0x12345678, # tod_ns
|
|
0x123456654321, # tod_sec
|
|
0x112233445566, # rel_ns
|
|
0, # ptm
|
|
0, # nom_period
|
|
nom_period, # period
|
|
0, # rsvd
|
|
0, # rsvd
|
|
))
|
|
|
|
rsp_unpacked = struct.unpack("<HHLLLQQQQQLL", rsp)
|
|
print(rsp_unpacked)
|
|
|
|
for k in range(self.port_count):
|
|
port = Port(self, k)
|
|
await port.init()
|
|
|
|
self.ports.append(port)
|
|
|
|
async def access_reg(self, reg, raw, write=False, data=0):
|
|
flags = 0
|
|
if raw:
|
|
flags |= CNDM_CMD_REG_FLG_RAW
|
|
if write:
|
|
flags |= CNDM_CMD_REG_FLG_WRITE
|
|
|
|
rsp = await self.exec_cmd(struct.pack("<HHLLLLLLLQQLLLL",
|
|
0, # rsvd
|
|
CNDM_CMD_OP_ACCESS_REG, # opcode
|
|
flags, # flags
|
|
0, # rsvd
|
|
0, # rsvd
|
|
0, # rsvd
|
|
0, # rsvd
|
|
0, # rsvd
|
|
reg, # reg
|
|
data, # write data
|
|
0, # read data
|
|
0, # rsvd
|
|
0, # rsvd
|
|
0, # rsvd
|
|
0, # rsvd
|
|
))
|
|
|
|
return struct.unpack_from("<Q", rsp, 10*4)[0]
|
|
|
|
async def exec_cmd(self, cmd):
|
|
return await self.exec_mbox_cmd(cmd)
|
|
|
|
async def exec_mbox_cmd(self, cmd):
|
|
cmd = bytes(cmd)
|
|
cmd = cmd.ljust(64, b'\x00')
|
|
|
|
if len(cmd) != 64:
|
|
raise ValueError("Invalid command length")
|
|
|
|
# write command to mailbox
|
|
a = array.array("I")
|
|
a.frombytes(cmd)
|
|
for k, dw in enumerate(a):
|
|
await self.hw_regs.write_dword(0x10000+k*4, dw)
|
|
|
|
# execute it
|
|
await self.hw_regs.write_dword(0x0200, 0x00000001)
|
|
|
|
# wait for completion
|
|
while await self.hw_regs.read_dword(0x0200) & 0x00000001:
|
|
pass
|
|
|
|
# read response from mailbox
|
|
for k in range(16):
|
|
a[k] = await self.hw_regs.read_dword(0x10040+k*4)
|
|
return a.tobytes()
|
|
|
|
async def interrupt_handler(self, irqn):
|
|
self.log.info("Interrupt handler start (IRQ %d)", irqn)
|
|
for p in self.ports:
|
|
if p.eq:
|
|
# using EQs
|
|
for eq in p.eq:
|
|
if eq.irqn == irqn:
|
|
await eq.process_eq()
|
|
else:
|
|
# using IRQs directly from CQs
|
|
for q in p.rxq:
|
|
if q.cq.irqn == irqn:
|
|
await q.cq.handler(q.cq)
|
|
for q in p.txq:
|
|
if q.cq.irqn == irqn:
|
|
await q.cq.handler(q.cq)
|
|
self.log.info("Interrupt handler end (IRQ %d)", irqn)
|
|
|
|
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)
|