AttributeError: 'RemoteBuffer' object has no attribute 'device' with pynq.remote

Hello PYNQ Team,

We are using pynq.remote to connect a host (WSL) to a custom ZynqMP board running a Yocto-built image.

Setup:

  • Host: pynq 3.1.2 (from pip) on WSL.

  • Target: Custom Yocto image using the pynq-cpp C++ server (pynq-remote) from git commit 3c77124d86bab65ac7bb6beb0aa66e13c83508f2.

Issue: We have successfully implemented a remote test that loads an overlay, allocates a buffer, and performs a full write/read/verify. The data comparison passes correctly.

However, we encounter a cleanup-related bug. We get an AttributeError: 'RemoteBuffer' object has no attribute 'device'.

This error is triggered in two specific scenarios:

  1. During overlay.free(): When we call overlay.free() in our finally block, it seems to inspect the allocated buffer and fails when it can’t find the .device attribute.

  2. During RemoteBuffer Garbage Collection: The error also occurs if we try to print a slice of a RemoteBuffer (e.g., print(read_data[:5])). Our analysis is that this creates a temporary RemoteBuffer slice, which is immediately garbage-collected, triggering a cleanup path that (again) looks for the non-existent .device attribute.

Workaround: We have successfully worked around both issues by:

  1. Forcing a local copy: Immediately after reading, we force the data into a local np.ndarray to prevent the garbage-collection bug:

    Python

    read_data = np.array(buf[:]) 
    
    
  2. Isolating cleanup: We wrap buf.freebuffer() and overlay.free() in their own try...except blocks within finally to catch the expected AttributeError.

This workaround is effective, but we wanted to report the issue and ask if this is a known limitation of this PYNQ version or if there is a more formal fix we are missing.

#!/usr/bin/env python3

import os
import sys
import numpy as np

# --- Configuration ---

# Set the IP address or hostname of the target ZynqMP board.
os.environ['PYNQ_REMOTE_DEVICES'] = "192.168.42.1"

from pynq import Overlay, allocate

# Bitstream file to load (must be in the same directory as this script)
BITSTREAM_FILE = "block_design.bit"


# Configuration for the test buffer
BUFFER_SIZE = 4096
BUFFER_DTYPE = 'u4'  # (equiv. to np.uint32)


def test_pynq_remote():
    """
    Executes the main remote test sequence:
    Overlay load -> Buffer Allocation -> Write -> Read -> Verify -> Cleanup
    """
    print("====[ PYNQ Remote Test Script ]====")
    overlay = None
    buf = None

    try:
        # Step 1: Load the overlay (remotely)
        print(f"Loading overlay: {BITSTREAM_FILE}...", flush=True)
        overlay = Overlay(BITSTREAM_FILE)
        print("Overlay loaded remotely successfully.", flush=True)

        # Step 2: Allocate the remote buffer (manually, NO 'with')
        print(f"Allocating remote buffer (size={BUFFER_SIZE}, dtype={BUFFER_DTYPE})...", flush=True)
        buf = allocate(shape=(BUFFER_SIZE,), dtype=BUFFER_DTYPE)
        print("Remote buffer allocated successfully.", flush=True)

        # Step 3: Generate test data and perform Write/Read test
        print("Generating local test data...", flush=True)
        expected_data = np.arange(BUFFER_SIZE, dtype=np.uint32)

        print("Writing data to remote buffer (Host -> Target)...", flush=True)
        buf[:] = expected_data
        buf.flush()  # Flush host cache to ensure data is on device

        print("Reading data from remote buffer (Target -> Host)...", flush=True)
        buf.invalidate()  # Invalidate host cache to get fresh data

        # --- MODIFICATION ---
        # Force 'read_data' to be a local np.ndarray copy.
        # This prevents the print() statements from creating temporary
        # RemoteBuffer views, which trigger the garbage collector bug.
        read_data = np.array(buf[:])
        print("Read complete. Data copied to local array.")

        # Step 4: Verify data and show preview
        print("Verifying data integrity...", flush=True)
        if np.array_equal(expected_data, read_data):
            print("âś… Buffer comparison PASSED: Data matches.")
            print(f"  > Data preview (first 5): {read_data[:5]}")
            print(f"  > Data preview (last 5):  {read_data[-5:]}")
        else:
            print("❌ Buffer comparison FAILED: Data mismatch!")
            mismatch_indices = np.where(expected_data != read_data)[0]
            print(f"  > Found {len(mismatch_indices)} mismatch(es).", flush=True)
            print("  > Preview of first 10 mismatches (Index: Expected -> Got):", flush=True)
            for i in mismatch_indices[:10]:
                print(f"    - Index {i}: {expected_data[i]} -> {read_data[i]}", flush=True)

    except RuntimeError as e:
        print(f"\n[ERROR] A runtime error occurred:", file=sys.stderr)
        print(f"Details: {e}", file=sys.stderr)
        # We let 'finally' run, so we don't exit(1) here

    except Exception as e:
        print(f"\n[ERROR] An unexpected error occurred: {e}", file=sys.stderr)
        # We let 'finally' run, so we don't exit(1) here

    finally:
        # Step 5: Manually clean up all resources in isolated blocks
        print("--- Entering Cleanup Phase ---")
        
        # Isolate Buffer cleanup
        if buf is not None:
            try:
                print("Freeing remote buffer...")
                buf.freebuffer()
                print("Remote buffer freed.")
            except Exception as e:
                print(f"[INFO] Error during buffer cleanup: {e}")
        
        # Isolate Overlay cleanup
        if overlay:
            try:
                print("Freeing overlay...")
                overlay.free()
                print("Overlay freed.")
            except Exception as e:
                print(f"[INFO] Error during overlay cleanup (known bug): {e}", flush=True)
        
    print("====[ Remote Test Complete ]====")


if __name__ == '__main__':
    # Check if IP has been set
    if "YOUR_ZYNQMP_BOARD_IP" in os.environ.get('PYNQ_REMOTE_DEVICES', ''):
        print("[WARNING] Please update 'PYNQ_REMOTE_DEVICES' in the script.", file=sys.stderr)
        exit(1)
        
    test_pynq_remote()




Thanks for your support!

Hi @Nicolas_Salmin

I was not able to replicate this issue. Is your script meant to highlight the bug or show your workaround, or both?

When I run your script I get the following output:

====[ PYNQ Remote Test Script ]====
Loading overlay: dma_loopback_rfsoc4x2.xsa...
Overlay loaded remotely successfully.
Allocating remote buffer (size=4096, dtype=u4)...
Remote buffer allocated successfully.
Generating local test data...
Writing data to remote buffer (Host -> Target)...
Reading data from remote buffer (Target -> Host)...
Read complete. Data copied to local array.
Verifying data integrity...
âś… Buffer comparison PASSED: Data matches.
  > Data preview (first 5): [0 1 2 3 4]
  > Data preview (last 5):  [4091 4092 4093 4094 4095]
--- Entering Cleanup Phase ---
Freeing remote buffer...
Remote buffer freed.
Freeing overlay...
Overlay freed.
====[ Remote Test Complete ]====

Just as a side note, your script did uncover a historic bug in PYNQ, which I’ve made a PR for and now pushed into the image_v3.1.3 branch.

This bug is not related to the behaviour you are seeing though.

Nice job @joshgoldsmith !

Tried on my side no more issue, thanks for the really fast support :slight_smile:

No problem @Nicolas_Salmin

Also, in case you didn’t realise, you don’t have to use WSL to use PYNQ.remote. It works just fine on Windows as well.

Thanks for the info @joshgoldsmith I will make a try asap :slight_smile:

1 Like

This topic was automatically closed 3 days after the last reply. New replies are no longer allowed.