From b7dd8b15f27ebc15ea65241f9160ba303a6b931e Mon Sep 17 00:00:00 2001 From: cscaff Date: Sat, 11 Jul 2026 21:26:55 -0400 Subject: [PATCH 1/2] Document app.write(fixed_address=) and app.stream() --- docs/guides/sdk.md | 41 +++++++++++++++++++++++++++++++++++++++-- 1 file changed, 39 insertions(+), 2 deletions(-) diff --git a/docs/guides/sdk.md b/docs/guides/sdk.md index 14ce0d5..94ff596 100644 --- a/docs/guides/sdk.md +++ b/docs/guides/sdk.md @@ -80,10 +80,47 @@ Read `count` 32-bit words starting at byte address `addr`. Returns a single `int` when `count == 1`, otherwise a `list[int]`. Programs the FPGA first if it hasn't been programmed yet. -#### `app.write(addr, value)` +#### `app.write(addr, value, fixed_address=False)` Write one or more 32-bit words to byte address `addr`. `value` may be a single -`int` or a `list[int]` for a burst write. +`int` or a `list[int]` for a burst write, which increments the address by 4 +per word (loading a register array, like a RAM). Pass `fixed_address=True` to +instead repeat `addr` for every word in the burst -- for a FIFO or +push-register port where your design keeps its own internal write index +(a common streaming-load pattern: writing a sequence of words one at a time to +a single register, with the RTL auto-advancing into the next clause/slot). A +plain burst would scatter those words across whatever registers happen to sit +at `addr+4`, `addr+8`, ... instead of pushing them all through the one port. + +#### `app.stream()` + +```python +with app: + with app.stream() as s: + for word in words: + s.write(LITERAL_IN, word, fixed_address=True) + s.write(REG_CTRL, 1) + while not (s.read(REG_CTRL) & 1): + pass +``` + +Opens a persistent, low-latency session for many small `read`/`write` ops. It +is a context manager exposing the same `write(addr, value, fixed_address=False)` +and `read(addr, count=1)` methods as `App`, but skips the per-call job queue: +every op on `app.write()`/`app.read()` directly dispatches its own job against +the cloud API and then polls for completion every 0.5s, so each individual +call costs roughly that much wall-clock time no matter how small the payload +is. That's fine for a handful of calls, but it dominates for a tight loop -- +loading a CNF instance one literal per write, or an RL reward loop that needs +to load and grade many episodes per training step. A `Stream` instead holds +one WebSocket open for the whole `with` block, relayed straight through to the +FPGA's Wishbone bus, and pays that connection cost once instead of once per +operation. + +A stream and `app.write()`/`app.read()` (or a second stream) can't be used on +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. #### `app.release()` From 1b21f9a2514f466549afbbc8a25d54c770a43b05 Mon Sep 17 00:00:00 2001 From: cscaff Date: Sun, 12 Jul 2026 00:04:10 -0400 Subject: [PATCH 2/2] Add 0.1.5 changelog entry: App.stream() and fixed_address burst writes manhattan-reasoning-gym 0.1.5 is now live on PyPI with these two additions; this changelog entry documents them alongside the sdk.md guide changes already in this PR. Co-Authored-By: Claude Sonnet 5 --- docs/changelog.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/docs/changelog.md b/docs/changelog.md index 7136d15..fb4bc93 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -7,6 +7,21 @@ description: Release notes for the manhattan-reasoning-gym SDK and CLI. Notable changes to the `manhattan-reasoning-gym` SDK and CLI. The project is in private beta, so surfaces may still change between `0.1.x` releases. +## 0.1.5 (private beta) + +### Added +- `App.write(addr, value, fixed_address=True)`: repeats `addr` for every word + in a burst instead of incrementing it, for a FIFO or push-register port + where a design keeps its own internal write index (a common streaming-load + pattern — writing a sequence of words one at a time to a single register, + with the RTL auto-advancing into the next clause/slot). +- `App.stream()`: a persistent, low-latency session for many small + `read`/`write` ops. Unlike `App.write()`/`App.read()`, which each dispatch + their own job against the cloud API and poll for completion every 0.5s, a + `Stream` holds one WebSocket connection open for the life of a `with` + block — useful for tight loops like loading a CNF instance one literal per + write, or an RL reward loop grading many episodes per training step. + ## 0.1.4 (private beta) The current beta surface: