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-cppC++ server (pynq-remote) from git commit3c77124d86bab65ac7bb6beb0aa66e13c83508f2.
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:
-
During
overlay.free(): When we calloverlay.free()in ourfinallyblock, it seems to inspect the allocated buffer and fails when it can’t find the.deviceattribute. -
During
RemoteBufferGarbage Collection: The error also occurs if we try to print a slice of aRemoteBuffer(e.g.,print(read_data[:5])). Our analysis is that this creates a temporaryRemoteBufferslice, which is immediately garbage-collected, triggering a cleanup path that (again) looks for the non-existent.deviceattribute.
Workaround: We have successfully worked around both issues by:
-
Forcing a local copy: Immediately after reading, we force the data into a local
np.ndarrayto prevent the garbage-collection bug:Python
read_data = np.array(buf[:]) -
Isolating cleanup: We wrap
buf.freebuffer()andoverlay.free()in their owntry...exceptblocks withinfinallyto catch the expectedAttributeError.
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!