diff --git a/apps/minimal/tests/cocotb/cocotb_grpc.py b/apps/minimal/tests/cocotb/cocotb_grpc.py new file mode 100644 index 000000000..f1fc45e3e --- /dev/null +++ b/apps/minimal/tests/cocotb/cocotb_grpc.py @@ -0,0 +1,80 @@ +#import sys +import logging + +import cocotb +from cocotb.triggers import Timer + +import scapy.all +import scapy.utils +import scapy.volatile +import scapy.contrib.mpls + +from ndk_core import NFBDevice + +import cocotbext.ofm.utils.sim.modelsim as ms +import cocotb.utils + +from cocotbext.ofm.utils.sim.bus import MfbBus, MiBus +from cocotbext.nfb.ext.grpc import RAM, NfbDmaThreadedGrpcServer + + +#logging.basicConfig(stream=sys.stderr, force=True) +#logging.getLogger().setLevel(logging.DEBUG) + +logger = logging.getLogger(__name__) + +e = cocotb.external +st = cocotb.utils.get_sim_time + + +async def get_dev(dut, init=True, **kwargs): + dev = NFBDevice(dut, **kwargs) + if init: + await dev.init() + return dev, dev.nfb + + +@cocotb.test() +async def test_grpc(dut): + ram = RAM() + dev, nfb = await get_dev(dut, ram=ram) + + # Generate packets on RX eth + async def rx_packet(eth, count): + for _ in range(count): + pkt = scapy.all.Ether()/scapy.all.IP(dst="127.0.0.1")/scapy.all.TCP()/"GET /index.html HTTP/1.0 \n\n" + await eth.write_packet(list(bytes(pkt))) + + for rx in dev._eth_rx_driver: + cocotb.start_soon(rx_packet(rx, 50000)) + + # Log packets on TX eth + for i, tx in enumerate(dev._eth_tx_monitor): + def eth_tx_monitor_cb(p): + logger.debug(f"tx_eth{i} packet transmitted: len={len(p)}, data={bytes(p).hex()}") + tx.add_callback(eth_tx_monitor_cb) + + # Run gRPC server with Nfb and Dma services usable for libnfb-ext-grpc + with NfbDmaThreadedGrpcServer(ram, dev): + await Timer(10, units='ms') + + +core = NFBDevice.core_instance_from_top(cocotb.top) + +pcic = core.pcie_i.pcie_core_i +#ms.cmd(f"log -recursive {ms.cocotb2path(core)}/*") + +ms.add_wave(core.pcie_i.MI_RESET) +ms.add_wave(core.pcie_i.MI_CLK) +MiBus(core.pcie_i, 'MI', 0, label='MI_PCIe').add_wave() + +ms.add_wave(core.app_i.MI_CLK) +MiBus(core.app_i, 'MI', label='MI_APP').add_wave() + +ms.add_wave(core.app_i.CLK_ETH[0]) +MfbBus(core.app_i, 'ETH_RX_MFB', 0).add_wave() +MfbBus(core.app_i, 'ETH_TX_MFB', 0).add_wave() + +ms.add_wave(core.app_i.DMA_CLK) +MfbBus(core.app_i, 'DMA_RX_MFB', 0).add_wave() +MfbBus(core.app_i, 'DMA_TX_MFB', 0).add_wave() diff --git a/apps/minimal/tests/cocotb/cocotb_test.py b/apps/minimal/tests/cocotb/cocotb_test.py index 3ed169518..c6af844e8 100644 --- a/apps/minimal/tests/cocotb/cocotb_test.py +++ b/apps/minimal/tests/cocotb/cocotb_test.py @@ -1,6 +1,6 @@ -import sys +#import sys +import logging import cocotb -#import logging from cocotb.triggers import Timer from ndk_core import NFBDevice @@ -11,15 +11,20 @@ from cocotbext.ofm.utils.sim.bus import MfbBus, MiBus, DmaUpMvbBus, DmaDownMvbBus +#logging.basicConfig(stream=sys.stderr, force=True) +#logging.getLogger().setLevel(logging.INFO) + +logger = logging.getLogger(__name__) + +# Shortcuts e = cocotb.external st = cocotb.utils.get_sim_time -async def get_dev(dut, init=True): - dev = NFBDevice(dut) +async def get_dev(dut, init=True, **kwargs): + dev = NFBDevice(dut, **kwargs) if init: await dev.init() - # dev._servicer._log.setLevel(logging.DEBUG) return dev, dev.nfb @@ -89,11 +94,10 @@ async def _test_ndp_sendmsg(dut, dev=None, nfb=None): pkt = bytes([i for i in range(72)]) - def eth_tx_monitor_cb(p): - print(len(p), bytes(p).hex()) - #assert bytes(p) == pkt - - dev._eth_tx_monitor[0].add_callback(eth_tx_monitor_cb) + for i, tx in enumerate(dev._eth_tx_monitor): + def eth_tx_monitor_cb(p): + logger.debug(f"tx_eth{i} packet transmitted: len={len(p)}, data={bytes(p).hex()}") + tx.add_callback(eth_tx_monitor_cb) count = 1 for i in range(count): @@ -113,11 +117,10 @@ async def _test_ndp_sendmsg_burst(dut, dev=None, nfb=None): await e(eth.txmac.reset_stats)() await e(eth.txmac.enable)() - def eth_tx_monitor_cb(p): - print(len(p), bytes(p).hex()) - sys.stdout.flush() - - dev._eth_tx_monitor[0].add_callback(eth_tx_monitor_cb) + for i, tx in enumerate(dev._eth_tx_monitor): + def eth_tx_monitor_cb(p): + logger.debug(f"tx_eth{i} packet transmitted: len={len(p)}, data={bytes(p).hex()}") + tx.add_callback(eth_tx_monitor_cb) pkts = range(20, 28) for i in pkts: diff --git a/apps/minimal/tests/cocotb/pyproject.toml b/apps/minimal/tests/cocotb/pyproject.toml index e19b41831..62dd5f274 100644 --- a/apps/minimal/tests/cocotb/pyproject.toml +++ b/apps/minimal/tests/cocotb/pyproject.toml @@ -5,6 +5,12 @@ dependencies = [ "cocotbext-ofm[nfb] @ ${NDK_FPGA_COCOTBEXT_OFM_URL}", ] +[project.optional-dependencies] +grpc = [ + "scapy", + "cocotbext-ofm[nfb_grpcio] @ ${NDK_FPGA_COCOTBEXT_OFM_URL}", +] + [build-system] requires = ["pdm-backend"] build-backend = "pdm.backend" diff --git a/apps/minimal/tests/cocotb/readme.rst b/apps/minimal/tests/cocotb/readme.rst new file mode 100644 index 000000000..c4349d778 --- /dev/null +++ b/apps/minimal/tests/cocotb/readme.rst @@ -0,0 +1,148 @@ +===================== +Top-level simulations +===================== + +The top-level simulations (TLS) are suitable for: + +- software testing without access to real acceleration card +- whole design compile and basic functionality check +- address space debugging +- resets, clocks, clock domain crossings check + +Basics +====== + +What simulation includes / excludes +----------------------------------- + +The TLS doesn’t simulate IP cores but emulates their I/O signals. The +primary effort is to emulate the Ethernet and PCIe I/O and DMA for the most used FPGA families +(Xilinx US+, Intel P-TILE, Intel E-TILE, ...). + +Common tips for cocotb +---------------------- + +- Use ``sys.stderr`` stream for ModelSim / Questa to achieve instant + display of log: + + .. code:: python + + logging.basicConfig(stream=sys.stderr, force=True) + +- Verbose messages for all loggers except some: + + .. code:: python + + logging.getLogger().setLevel(logging.INFO) + logging.getLogger("cocotbext.nfb.ext.grpc.server").setLevel(logging.WARNING) + +gRPC example +============ + +This variant enables the usage of external processes and is intended for +manual interaction. The TLS doesn’t contain any real test cases. Instead, it +starts the gRPC server, runs for a specified simulation time (e.g., 10ms), and +expects the user to execute an application that uses the acceleration +card through the ``libnfb`` gRPC extension. The application then generates +I/O read and write requests and the simulator translates them to the bus +interface and back. + +A feature in ``libnfb-ext-grpc`` enables simple handling of DMA requests +from the simulated design. If the ``dma_vas`` tag is present in the device +string, the library opens a reverse stream. As soon as the DMA request +arrives in the process, ``libnfb-ext-grpc`` copies data from/to the virtual +address space of the process. Just use the virtual address for +descriptor values. There are no boundary checks and the request can +potentially harm the process, which probably gets killed by ``SIGSEGV`` +signal in the case of an error. + +The simple DMA request handling is most suitable for a DPDK application; +see the section below. + +Prerequisites +------------- + +libnfb-ext-grpc.so from the libnfb-ext-grpc package (RPM) + +Running +------- + +1. Prepare a Python environment and install packages + + .. code:: shell + + . ./prepare.sh + +2. Install specific dependencies for the gRPC-based simulation + + .. code:: shell + + pip install .[grpc] + +3. Run the simulation + + .. code:: shell + + make COCOTB_MODULE=cocotb_grpc + +4. Wait until this message appears in the console + + ``gRPC server started, listening on 50051. Device string: libnfb-ext-grpc.so:grpc:localhost:50051`` + +5. Run your software application and specify the device string + + .. code:: shell + + $ nfb-eth -ri0 -d libnfb-ext-grpc.so:grpc:localhost:50051 + +DPDK usage +---------- + +DPDK needs to be executed with the ``--vdev`` argument: + +.. code:: shell + + sudo dpdk-testpmd --vdev=eth_vdev_nfb,dev=libnfb-ext-grpc.so:grpc+dma_vas:localhost:50051,queue_driver=native --iova-mode=va -- -i + +The ``queue_driver=native`` is currently the only supported mode, for which the +``--iova-mode=va`` is essential. The ``dma_vas`` tag also +must be stated in the device string: +``libnfb-ext-grpc.so:grpc+dma_vas:localhost:50051``. + +Do not forget to alloc hugepages. + +Tips +---- + +Concurrent processes +^^^^^^^^^^^^^^^^^^^^ + +The simulation environment can handle requests from multiple running +applications at once. For example: start the ``dpdk-testpmd`` in +interactive mode, enable MACs with ``nfb-eth -e1`` and then type +``start`` in the DPDK app prompt. Be aware that only one application should use +the ``dma_vas`` tag in the device string at a time. + +*There is an issue with nfb locks: nfb_comp_lock / nfb_comp_unlock is not +implemented. Two processes mixing requests on one lock-aware component +will probably break its function.* + +Locally build libnfb-ext-grpc.so +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +If the gRPC client library is not in the standard system path (``/usr/lib``), +use the full path in the device parameter: + +.. code:: shell + + nfb-info -d /home/joe/ndk-sw/cmake-build/ext/libnfb-ext-grpc/libnfb-ext-grpc.so:grpc:localhost:50051 + +Remote access to TLS +^^^^^^^^^^^^^^^^^^^^ + +Listen on all IP addresses: + +``NfbDmaThreadedGrpcServer(ram, dev, addr='0.0.0.0')`` + +and run the application on another machine with the ``target_addr:port`` string in the device parameter. + diff --git a/python/cocotbext/cocotbext/nfb/ext/grpc/__init__.py b/python/cocotbext/cocotbext/nfb/ext/grpc/__init__.py index 0584c3424..e64d56eb4 100644 --- a/python/cocotbext/cocotbext/nfb/ext/grpc/__init__.py +++ b/python/cocotbext/cocotbext/nfb/ext/grpc/__init__.py @@ -1,3 +1,4 @@ -from .servicer import Servicer +from .server import NfbDmaThreadedGrpcServer +from .dma import RAM -__all__ = ['Servicer'] +__all__ = ["NfbDmaThreadedGrpcServer", "RAM"] diff --git a/python/cocotbext/cocotbext/nfb/ext/grpc/dma.py b/python/cocotbext/cocotbext/nfb/ext/grpc/dma.py new file mode 100644 index 000000000..c786b4b2c --- /dev/null +++ b/python/cocotbext/cocotbext/nfb/ext/grpc/dma.py @@ -0,0 +1,94 @@ +import queue +import logging +from threading import Event + +import cocotbext.ofm.utils + +import nfb.ext.protobuf.v1.dma_pb2 as dma_pb2 +import nfb.ext.protobuf.v1.dma_pb2_grpc as dma_pb2_grpc + + +class DmaRequest(): + def __init__(self, rq): + self.rq = rq + self.event = Event() + + def set(self, data): + self.rq.data = data + self.event.set() + + def wait(self): + self.event.wait() + return self.rq.data + + +class DmaServicer(dma_pb2_grpc.DmaServicer): + def __init__(self, ram): + self._log = logging.getLogger(__name__) + self._ram = ram + self._bind = False + self._req_queue = None + + def RqStream(self, request_iterator, context): + if self._req_queue is not None: + self._log.warning(f"Another context on Dma.RqStream is already active, ignoring current: {context.peer()}") + return + + self._log.info(f"Using new Dma.RqStream context: {context.peer()}") + + self._bind = True + self._req_queue = queue.Queue(10) + self._ram.connect(self._req_queue) + + context.add_callback(self._logout) + + while self._bind: + req = self._req_queue.get() + if req is not None: + yield req.rq + resp = next(request_iterator) + if req.rq.type == dma_pb2.DmaOperation.DMA_READ: + req.set(resp.data) + else: + req.set(bytes()) + + self._req_queue = None + self._ram.close() + self._log.info(f"Closing Dma.RqStream context: {context.peer()}") + + def _logout(self): + self._bind = False + if self._req_queue is not None: + self._req_queue.put(None) + + def Logout(self, request_iterator, context): + self._logout() + + +class RAM(cocotbext.ofm.utils.RAM): + def __init__(self): + self._rq = None + self._log = logging.getLogger(__name__) + + def connect(self, request_queue): + self._rq = request_queue + + def close(self): + self._rq = None + + def w(self, addr, data): + if self._rq is None: + self._log.error(f"Dma client for RAM access not connected: write {len(data)}B to {addr:0x}") + return + + resp = DmaRequest(dma_pb2.DmaRequest(type=dma_pb2.DmaOperation.DMA_WRITE, addr=addr, nbyte=len(data), data=bytes(data))) + self._rq.put(resp) + + def r(self, addr, byte_count): + if self._rq is None: + self._log.error(f"Dma client for RAM access not connected: read {byte_count}B from {addr:0x}") + return list(bytes(byte_count)) + + resp = DmaRequest(dma_pb2.DmaRequest(type=dma_pb2.DmaOperation.DMA_READ, addr=addr, nbyte=byte_count, data=None)) + self._rq.put(resp) + return list(resp.wait()) diff --git a/python/cocotbext/cocotbext/nfb/ext/grpc/nfb.py b/python/cocotbext/cocotbext/nfb/ext/grpc/nfb.py new file mode 100644 index 000000000..7461af438 --- /dev/null +++ b/python/cocotbext/cocotbext/nfb/ext/grpc/nfb.py @@ -0,0 +1,97 @@ +import re +import logging +import queue +import cocotb + +from cocotb.triggers import Timer + +from threading import Event + +import nfb.ext.protobuf.v1.nfb_pb2 as nfb_pb +import nfb.ext.protobuf.v1.nfb_pb2_grpc as nfb_pb_grpc + + +class CompRequest(): + def __init__(self, rd, path, offset, nbyte, data=None): + self.rd = rd + self.path = path + self.offset = offset + self.nbyte = nbyte + self.data = data + self.event = Event() + + def set(self, data): + self.data = data + self.event.set() + + def wait(self): + self.event.wait() + return self.data + + +class NfbServicer(nfb_pb_grpc.NfbServicer): + def __init__(self, dev): + self._log = logging.getLogger(__name__) + self._dev = dev + self._events = queue.Queue() + self._requests = queue.Queue(10) + + cocotb.start_soon(self._mi_req_thread()) + + async def _mi_req_thread(self): + timer = Timer(10, units='ns') + + while True: + while self._requests.empty(): + await timer + + req = self._requests.get(False) + + mi, base = self._comp_addr(req.path) + addr = req.offset + base + req_type = "Read" if req.rd else "Write" + self._log.debug(f"{req_type:<5}: size: {req.nbyte:>4}, offset: {hex(req.offset):>8}, base: {hex(base):>10} {req.path}") + + if req.rd: + data = await mi.read(addr, req.nbyte) + else: + data = await mi.write(addr, req.data) + + req.set(data) + + def _comp_addr(self, path): + node = self._dev.nfb.fdt.get_node(path) + base = node.get_property("reg")[0] + + p = node.parent + while p: + compatible = p.get_property("compatible") + if compatible and compatible.value == "netcope,bus,mi": + m = re.search(r'PCI(?P\d+),BAR(?P\d+)', p.get_property("resource").value) + pci, _ = int(m.group('pci')), int(m.group('bar')) + mi = self._dev.mi[pci] + break + p = p.parent + + return mi, base + + def resp_force(self): + while not self._events.empty(): + self._events.get(False).set() + + def GetFdt(self, req, context): + return nfb_pb.FdtResponse(fdt=bytes(self._dev.nfb.fdt.to_dtb())) + + def ReadComp(self, req, context): + req = CompRequest(True, req.path, req.offset, req.nbyte) + self._requests.put(req) + self._events.put(req.event) + data = req.wait() + self._events.get(False) + return nfb_pb.ReadCompResponse(status=0, data=data) + + def WriteComp(self, req, context): + req = CompRequest(False, req.path, req.offset, req.nbyte, req.data) + self._requests.put(req) + #_ = req.wait() + return nfb_pb.WriteCompResponse(status=0) diff --git a/python/cocotbext/cocotbext/nfb/ext/grpc/server.py b/python/cocotbext/cocotbext/nfb/ext/grpc/server.py new file mode 100644 index 000000000..c789ac83c --- /dev/null +++ b/python/cocotbext/cocotbext/nfb/ext/grpc/server.py @@ -0,0 +1,58 @@ +import time +import logging +import socket + +import grpc +import cocotb + +from concurrent import futures +from threading import Thread + +from .nfb import NfbServicer +from .dma import DmaServicer + +import nfb.ext.protobuf.v1.nfb_pb2_grpc as nfb_pb_grpc +import nfb.ext.protobuf.v1.dma_pb2_grpc as dma_pb_grpc + + +class NfbDmaThreadedGrpcServer: + def __init__(self, ram, dev, addr="127.0.0.1", port=50051): + super().__init__() + self._log = logging.getLogger(__name__) + + self._port = port + + self._mi_reciver = NfbServicer(dev) + self._dma_reciver = DmaServicer(ram) + + self._server = grpc.server(futures.ThreadPoolExecutor()) + self._server.add_insecure_port(f"{addr}:{port}") + nfb_pb_grpc.add_NfbServicer_to_server(self._mi_reciver, self._server) + dma_pb_grpc.add_DmaServicer_to_server(self._dma_reciver, self._server) + + self._thread = Thread(target=self._run) + self._thread_terminate = False + + def _run(self): + self._server.start() + + while not cocotb.regression_manager._tearing_down and not self._thread_terminate: + time.sleep(0.1) + + self._dma_reciver._logout() + self._mi_reciver.resp_force() + self._server.stop(2.0) + self._server.wait_for_termination() + + def start(self): + self._thread.start() + self._log.info(f"gRPC server started, listening on {self._port}. Device string: libnfb-ext-grpc.so:grpc+dma_vas:{socket.gethostname()}:{self._port}") + + def close(self): + self._thread_terminate = True + + def __enter__(self): + self.start() + + def __exit__(self, exc_type, exc_val, exc_tb): + self.close() diff --git a/python/cocotbext/cocotbext/nfb/ext/grpc/servicer.py b/python/cocotbext/cocotbext/nfb/ext/grpc/servicer.py deleted file mode 100644 index 371842b96..000000000 --- a/python/cocotbext/cocotbext/nfb/ext/grpc/servicer.py +++ /dev/null @@ -1,74 +0,0 @@ -import queue -import logging - -import cocotb -from cocotb.triggers import Timer - -import nfb.ext.grpc -from nfb.ext.grpc import nfb_grpc_pb2_grpc -from nfb.ext.grpc import nfb_grpc_pb2 - - -class Servicer(nfb_grpc_pb2_grpc.NfbExtensionServicer): - def __init__(self, device, dtb, server_addr=('127.0.0.1', 63239), *args, **kwargs): - self._log = logging.getLogger("cocotb.nfb.ext.grpc_servicer") - self._log.setLevel(5) - self._qrx = queue.Queue() - self._device = device - self._dtb = dtb - self._server_addr = server_addr - - def start(self): - cocotb.start_soon(self._queue_request_handle()) - self._server = nfb.ext.grpc.Server(self, *self._server_addr) - - def path(self): - return f"libnfb-ext-grpc.so:grpc:{self._server_addr[0]}:{self._server_addr[1]}" - - @cocotb.coroutine - def _queue_request_handle(self): - while True: - try: - item = self._qrx.get(block=False) - except queue.Empty: - yield Timer(10, units='ns') - continue - - node, offset, write, data, q = item - mi_base = node.get_property('reg')[0] - mi = self._device.mi[0] # FIXME - fn = getattr(mi, 'write' if write else 'read') - - try: - d = yield fn(mi_base, data) - q.put(d) - except Exception: - self._log.error("comp access failed") - q.put(None) - - def GetFdt(self, request, context): - return nfb_grpc_pb2.FdtResponse(fdt=self._dtb) - - def ReadComp(self, request, context): - data = self._nfb_comp_read(request.fdt_offset, request.nbyte, request.offset) - data = [0] * request.nbyte - return nfb_grpc_pb2.ReadCompResponse(data=bytes(data), status=0) - - def WriteComp(self, request, context): - self._nfb_comp_write(request.fdt_offset, request.nbyte, request.offset, request.data) - return nfb_grpc_pb2.WriteCompResponse(status=0) - - def _nfb_comp_write(self, fdtoffset, nbyte, offset, data): - node = self._nfb._fdt.get_node(self._nfb.fdt_paths[fdtoffset]) - self._log.debug(f"comp write: size: {nbyte:>2}, offset: {offset:04x}, path: {node.path}/{node.name}, data:", data) - q = queue.Queue() - self._qrx.put((node, offset, True, list(data), q)) - q.get() - - def _nfb_comp_read(self, fdtoffset, nbyte, offset): - node = self._nfb._fdt.get_node(self._nfb.fdt_paths[fdtoffset]) - q = queue.Queue() - self._qrx.put((node, offset, False, nbyte, q)) - data = bytes(q.get()) - self._log.debug(f"comp read: size: {nbyte:>2}, offset: {offset:04x}, path: {node.path}/{node.name}, data:", data) - return data diff --git a/python/cocotbext/cocotbext/nfb/ext/servicer.py b/python/cocotbext/cocotbext/nfb/ext/servicer.py deleted file mode 100644 index 447eb581b..000000000 --- a/python/cocotbext/cocotbext/nfb/ext/servicer.py +++ /dev/null @@ -1,87 +0,0 @@ -import queue -import logging - -import cocotb -from cocotb.triggers import Timer - - -class CommonAsyncServicer(): - def path(self): - raise NotImplementedError() - - def get_node_base(self, bus_node, comp_node): - # TODO: fix mi[0] - return (self._device.mi[0], comp_node.get_property("reg")[0]) - - def start(self): - pass - - @cocotb.function - def read(self, bus_node, comp_node, offset, nbyte): - try: - mi, base = self.get_node_base(bus_node, comp_node) - data = yield mi.read(base + offset, nbyte) - self._log.debug(f"comp read: size: {nbyte:>2}, offset: {offset:04x}, path: {comp_node.path}/{comp_node.name}, data:", data) - except Exception as e: - self._log.error("comp read failed: ", e) - return bytes(data) - - @cocotb.function - def write(self, bus_node, comp_node, offset, data): - try: - mi, base = self.get_node_base(bus_node, comp_node) - nbyte = len(data) - data = list(data) - self._log.debug(f"comp write: size: {nbyte:>2}, offset: {offset:04x}, path: {comp_node.path}/{comp_node.name}, data:", data) - yield mi.write(base + offset, data) - except Exception as e: - self._log.error("comp write failed:", e) - return len(data) - - -class CommonServicer(CommonAsyncServicer): - def __init__(self, device, dtb, server_addr=('127.0.0.1', 63239), *args, **kwargs): - self._log = logging.getLogger("cocotb.nfb.ext.servicer") - #self._log.setLevel(5) - self._qrx = queue.Queue() - self._device = device - self._dtb = dtb - - def start(self): - cocotb.start_soon(self._queue_request_handle()) - - @cocotb.coroutine - def _queue_request_handle(self): - while True: - try: - item = self._qrx.get(block=False) - except queue.Empty: - yield Timer(10, units='ns') - continue - - node, offset, write, data, q = item - mi_base = node.get_property('reg')[0] - mi = self._device.mi[0] # FIXME - fn = getattr(mi, 'write' if write else 'read') - - try: - d = yield fn(mi_base, data) - q.put(d) - except Exception: - self._log.error("comp access failed") - q.put(None) - - def _nfb_comp_write(self, fdtoffset, nbyte, offset, data): - node = self._nfb._fdt.get_node(self._nfb.fdt_paths[fdtoffset]) - self._log.debug(f"comp write: size: {nbyte:>2}, offset: {offset:04x}, path: {node.path}/{node.name}, data:", data) - q = queue.Queue() - self._qrx.put((node, offset, True, list(data), q)) - q.get() - - def _nfb_comp_read(self, fdtoffset, nbyte, offset): - node = self._nfb._fdt.get_node(self._nfb.fdt_paths[fdtoffset]) - q = queue.Queue() - self._qrx.put((node, offset, False, nbyte, q)) - data = bytes(q.get()) - self._log.debug(f"comp read: size: {nbyte:>2}, offset: {offset:04x}, path: {node.path}/{node.name}, data:", data) - return data diff --git a/python/cocotbext/pyproject.toml b/python/cocotbext/pyproject.toml index 6a6d5993e..03c3572ca 100644 --- a/python/cocotbext/pyproject.toml +++ b/python/cocotbext/pyproject.toml @@ -13,6 +13,13 @@ nfb = [ "libnfb-ext-python @ ${NDK_SW_PYTHON_URL}ext/libnfb_ext_python", ] +nfb_grpcio = [ + "grpcio", + "pylibfdt", + "nfb @ ${NDK_SW_PYTHON_URL}pynfb", + "libnfb-ext-grpc @ ${NDK_SW_PYTHON_URL}ext/libnfb_ext_grpc/python", +] + [build-system] requires = [ "pdm-backend",