The Python SDK¶
The cloud_fpga package wraps the full FPGA lifecycle — build, program, register
I/O, and release — behind a small Modal-style decorator API.
It is the recommended way to use the platform; the REST API is
the lower-level contract underneath.
Install¶
Requires Python 3.12 or 3.13. Installs the cloud_fpga package and the
cloud-fpga CLI.
The mental model¶
You describe an application with three things:
- a design — an Amaranth
.pyfile whose top-level module is a Wishbone B4 slave (see Architecture); - a register map — the byte offsets your design exposes;
- API config — which board (
fpga_id) and your key.
Then you read and write registers. The first access programs the FPGA automatically.
import cloud_fpga
class Regs(cloud_fpga.RegisterMap):
CTRL = 0x0000
DATA_IN = 0x0004
DATA_OUT = 0x0008
app = cloud_fpga.App(
"my_design",
design="design.py",
fpga_id=0,
registers=Regs,
api_key=cloud_fpga.secret("CLOUD_FPGA_API_KEY"),
)
@app.local_entrypoint()
def main():
with app:
app.write(Regs.DATA_IN, 0x1234)
app.write(Regs.CTRL, 1) # kick off
print(hex(app.read(Regs.DATA_OUT)))
Run it with the CLI (which calls the entrypoint after programming):
App¶
cloud_fpga.App(
name,
*,
design, # path to the Amaranth .py design
fpga_id, # which board (0–9)
registers=None, # a RegisterMap subclass (optional)
api_key=None, # defaults to $CLOUD_FPGA_API_KEY
api_url="https://api.manhattanreasoning.com",
)
Creating an App registers it so the CLI can discover it — you don't export
anything. If you define several in one file, cloud-fpga run uses the last one.
app.read(addr, count=1)¶
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.
status = app.read(Regs.CTRL) # one word -> int
block = app.read(0x0020, count=8) # eight words -> list[int]
app.write(addr, value)¶
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. Programs the FPGA first if needed.
app.write(Regs.DATA_IN, 0xDEADBEEF) # single word
app.write(0x0020, [0] * 200) # burst (e.g. clear a region)
app.release()¶
Release the active session, returning the board to idle. Returns the reset
job_id.
Context manager¶
App is a context manager that releases on exit — the idiomatic way to scope a
session:
@app.local_entrypoint()
def main():
with app:
app.write(Regs.DATA_IN, 42)
result = app.read(Regs.DATA_OUT)
# session released here
Releasing currently strands the board in error
Releasing reflashes the base SoC, which currently fails and leaves the FPGA
in error. Until that's fixed, recover with the Redis flush in
Troubleshooting. You can also skip
the auto-release by not using with app: and resetting manually later.
RegisterMap¶
A marker base class for your design's address map. Subclass it and set integer class attributes for the byte offsets:
class Regs(cloud_fpga.RegisterMap):
CTRL = 0x0000 # word 0
N_VARS = 0x0004 # word 1
MODEL = 0x000C # word 3
Byte offset = 4 × word offset. Passing registers=Regs to App is optional
(it's just for your own organization — read/write take raw addresses).
@app.local_entrypoint()¶
Marks the function the CLI runs after programming. It is not called when you
import or python the file — only by cloud-fpga run. This lets the same file
double as an importable module and a runnable app.
secret(env_var)¶
Reads a required environment variable, raising ValueError immediately (at
import time) if it's unset — so a missing key fails loudly before any work
starts.
Module-level functions¶
For session management outside an App:
cloud_fpga.get_session(fpga_id, api_key, api_url) # current session info
cloud_fpga.release_session(fpga_id, api_key, api_url) # release, returns job_id
A complete example¶
The SAT solver example is a good template — a
RegisterMap, helper encoders, an App, and a @local_entrypoint that writes a
formula, starts the solve, polls for completion, and reads back the model.
See also¶
- CLI reference — driving apps from the terminal.
- Examples — runnable apps.
- REST API — what the SDK calls under the hood.