From 34a048ed252f31c2bd9aef743581843b7cfc97ac Mon Sep 17 00:00:00 2001 From: cscaff Date: Sun, 12 Jul 2026 14:20:25 -0400 Subject: [PATCH 1/2] Document app.stream() write's ~2KB per-message payload limit Discovered while sweeping a SAT-solver design at real hardware scale: a single stream write() burst above ~500 32-bit words gets its connection closed by the relay before a response arrives, surfaced client-side as ConnectionClosedError with reason "FPGA is busy with a run job" -- misleading, since it reproduces deterministically by size alone regardless of session/concurrency state (504 words: fine, 576 words: fails, every time). Undocumented previously; callers streaming large arrays need to chunk writes themselves. --- docs/guides/sdk.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/docs/guides/sdk.md b/docs/guides/sdk.md index 94ff596..6426ad1 100644 --- a/docs/guides/sdk.md +++ b/docs/guides/sdk.md @@ -122,6 +122,21 @@ the same FPGA at the same time -- the bridge firmware only safely serves one open connection per board, so opening a stream holds an exclusive lock on the FPGA's link for the life of the `with` block. +**Per-write payload limit:** a single `write(addr, value, ...)` call where +`value` is a list is sent as one relay message (an 8-byte header plus 4 bytes +per word). Messages larger than roughly **2KB (~500 32-bit words)** get the +connection closed by the relay before a response arrives, raised client-side +as `websockets.exceptions.ConnectionClosedError`. The close reason text is +currently `"FPGA is busy with a run job"`, which is misleading -- this is a +payload-size rejection, not a concurrency or session-lifetime issue, and it +reproduces deterministically (same size, same failure, every time) regardless +of whether any other session is active. Confirmed by bisection: a 504-word +burst succeeds, a 576-word burst fails. If your design streams in a large +array (a big CNF instance's literal list, a large weight tensor, ...), chunk +`value` into sub-2KB writes yourself -- `fixed_address=True` composes fine +across multiple chunked calls, since the design's own write-index tracks +position across them. + #### `app.release()` Release the active session, returning the board to `idle`. Returns the reset From 291b1b0c202ff6284ab27faa33a5fdfb898e8ba4 Mon Sep 17 00:00:00 2001 From: cscaff Date: Sun, 12 Jul 2026 14:35:54 -0400 Subject: [PATCH 2/2] Trim payload-limit note to just the constraint and workaround Reference docs should tell readers what the limit is and how to stay under it, not how it was found. --- docs/guides/sdk.md | 21 +++++++-------------- 1 file changed, 7 insertions(+), 14 deletions(-) diff --git a/docs/guides/sdk.md b/docs/guides/sdk.md index 6426ad1..562b911 100644 --- a/docs/guides/sdk.md +++ b/docs/guides/sdk.md @@ -122,20 +122,13 @@ the same FPGA at the same time -- the bridge firmware only safely serves one open connection per board, so opening a stream holds an exclusive lock on the FPGA's link for the life of the `with` block. -**Per-write payload limit:** a single `write(addr, value, ...)` call where -`value` is a list is sent as one relay message (an 8-byte header plus 4 bytes -per word). Messages larger than roughly **2KB (~500 32-bit words)** get the -connection closed by the relay before a response arrives, raised client-side -as `websockets.exceptions.ConnectionClosedError`. The close reason text is -currently `"FPGA is busy with a run job"`, which is misleading -- this is a -payload-size rejection, not a concurrency or session-lifetime issue, and it -reproduces deterministically (same size, same failure, every time) regardless -of whether any other session is active. Confirmed by bisection: a 504-word -burst succeeds, a 576-word burst fails. If your design streams in a large -array (a big CNF instance's literal list, a large weight tensor, ...), chunk -`value` into sub-2KB writes yourself -- `fixed_address=True` composes fine -across multiple chunked calls, since the design's own write-index tracks -position across them. +**Per-write payload limit:** a burst `write(addr, value, ...)` is capped at +roughly **2KB (~500 32-bit words)**. Going over it closes the connection, +raised client-side as `websockets.exceptions.ConnectionClosedError` with the +reason `"FPGA is busy with a run job"` -- ignore that text, it's a payload +size limit, not a concurrency/session issue. If you need to send more than +that, split `value` into chunks under ~500 words and call `write()` once per +chunk; `fixed_address=True` composes fine across chunked calls. #### `app.release()`