Skip to content

Data acquisition and streaming

Acquire a DAQ window

DAQ channels expose signals from the FPGA design. A channel number is not necessarily an analog-input connector number. This example uses the repository's DAQ design; run it from the repository root:

import asyncio
import numpy as np
from noa.noa import Noa
from noa.daq import DaqTrigger, TriggerKind

async def main():
    async with Noa("arch1-00001.local") as device:
        await device.load_bitstream(
            "examples/daq/design_1_wrapper.bit",
            "examples/daq/design_1.hwh",
        )
        try:
            await device.daq.setup_data(window_size=1000, decimation=1, delay=0)
            await device.daq.enable_channel(1, dtype="fixdt(1,32,29)")
            await device.daq.trigger(DaqTrigger(kind=TriggerKind.Immediate))
            samples = await asyncio.wait_for(device.daq.read(1), timeout=5)
            np.savetxt("channel1.csv", samples, delimiter=",")
        finally:
            await device.daq.reset()

if __name__ == "__main__":
    asyncio.run(main())

Match the timing and data type to your design. A host timeout prevents an indefinite wait; it does not reconfigure the FPGA trigger.

Queues and callbacks

Without a callback, enable_channel() buffers windows in a queue. await read(channel) waits for one window; read_ready(channel) checks whether one is already available. The default capacity is 100 windows. When full, new windows are dropped with a warning; consume data promptly.

Alternatively, use a short asynchronous callback:

async def on_window(channel, samples):
    print(channel, len(samples))

await device.daq.enable_channel(0, dtype="fixdt(1,32,29)", cb=on_window)

Use queue reads or a callback for a given channel. disable_channel() stops its subscription; reset() resets acquisition.

Triggers

await device.daq.trigger(DaqTrigger(
    kind=TriggerKind.RisingEdge, channel=0, threshold=0, hysteresis=10,
))

The repository example notes that trigger logic treats signals as signed 32-bit integers. Thresholds are raw design values, not automatically converted to volts or the decoded dtype. See the API reference for trigger names.

Stream data to the FPGA

With a design exposing the expected stream-input interface:

import numpy as np

await device.stream_in.write(np.arange(100, dtype=np.uint32), dtype="uint32")

Samples are encoded as 32-bit words. Provide a NumPy array with the representation appropriate to dtype. write() splits large transfers into chunks and marks the final chunk. Consumption and timing depend on FPGA logic.

For raw data, write_chunk(data, last=True) accepts a multiple of four bytes, at most 1,000,000 bytes per call. Prefer write() unless you manage encoding yourself.

See the DAQ and stream-input examples.