Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions docs/changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,34 @@ 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.8 (private beta)

### Added
- **Plain Verilog designs.** `design=` on `App`, and the `design` argument to
`mrg.build.synth`/`pnr` and `mrg synth`/`mrg pnr`, now also accept a `.v`
file, not just an Amaranth `.py` module — the extension picks the language
server-side. A Verilog file's top module is found by scanning for the one
module whose port list matches the fixed Wishbone contract (`clk`, `rst`,
`wb_cyc`, `wb_stb`, `wb_we`, `wb_adr`, `wb_dat_w`, `wb_sel`, `wb_dat_r`,
`wb_ack`, exact widths and directions) by name; if a file exposes more than
one matching module, pass `--top <name>` (CLI) or `top="<name>"`
(`App(...)`/`mrg.build.synth`/`pnr`) to disambiguate. See the new
[Verilog Hello](examples/verilog-hello.md) example.
- `mrg synth`/`mrg pnr` and `mrg run` gained a `--top` flag; `App`,
`mrg.build.synth`, and `mrg.build.pnr` gained a matching `top=` keyword
argument. All are ignored for an Amaranth design.

## 0.1.7 (private beta)

### Fixed
- `mrg.bench.CloudSilicon` still called the pre-0.1.6 client API
(`find_idle_fpga`/`NoFPGAAvailableError`, and `submit(fpga_id, ...)`), left
over from the 0.1.6 board-less submit change. It now submits without an
`fpga_id`, treats a full build-slot pool as a `503` (surfaced as
`no_board`) instead of a pre-flight idle-board check, and reads the
assigned `fpga_id` off the completed job record instead of raising on
success.

## 0.1.6 (private beta)

### Changed
Expand Down
5 changes: 5 additions & 0 deletions docs/examples/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,11 @@ repo. Each is a complete `mrg.cloud.App` you launch with `mrg run`.
The minimal smoke test, write a pattern to an echo RAM and read it back.
Start here to verify the interface contract.

- :material-file-code: **[Verilog Hello](verilog-hello.md)**

The same echo RAM as Hello Wishbone, written as plain Verilog instead of
Amaranth, to demonstrate submitting a hand-written `.v` design directly.

- :material-check-decagram: **[SAT solver](sat-solver.md)**

A brute-force boolean satisfiability solver in hardware. Includes a
Expand Down
133 changes: 133 additions & 0 deletions docs/examples/verilog-hello.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
# Verilog Hello

The same design as [Hello Wishbone](hello-wishbone.md), a 512 × 32-bit echo
memory, but written as plain Verilog instead of Amaranth, to demonstrate
submitting a hand-written `.v` design directly.

Source: [`examples/verilog_hello/`](https://github.com/ManhattanReasoning/manhattan-reasoning-gym/tree/main/examples/verilog_hello) in the `manhattan-reasoning-gym` repo.

## Run it

```bash
# Local synth report only (no cloud, no hardware)
mrg synth examples/verilog_hello/design.v

# Against a live node
mrg run examples/verilog_hello/client_sdk.py
```

```text
writing pattern ...
reading back ...
[0] 0xdeadbeef → OK
[1] 0xcafebabe → OK
[2] 0x12345678 → OK
[3] 0xabcdef01 → OK
```

## The design

`echo_slave`: the memory behind the Wishbone B4 slave contract (registered
single-cycle ack), identical timing to
[`hello_wishbone/design.py`](hello-wishbone.md)'s `EchoSlave`:

```verilog
module echo_slave (
input wire clk,
input wire rst,
input wire wb_cyc,
input wire wb_stb,
input wire wb_we,
input wire [8:0] wb_adr, // 512 words = 9 address bits
input wire [31:0] wb_dat_w,
input wire [3:0] wb_sel, // accepted, ignored
output wire [31:0] wb_dat_r,
output reg wb_ack
);
reg [31:0] mem [0:511];
reg [31:0] dat_r_reg;
wire wr_en = wb_cyc & wb_stb & wb_we & ~wb_ack;

always @(posedge clk) begin
if (rst) begin
wb_ack <= 1'b0;
dat_r_reg <= 32'b0;
end else begin
wb_ack <= wb_cyc & wb_stb & ~wb_ack;
if (wr_en)
mem[wb_adr] <= wb_dat_w;
dat_r_reg <= mem[wb_adr];
end
end

assign wb_dat_r = dat_r_reg;
endmodule
```

## The whole app

```python
import manhattan_reasoning_gym as mrg

class Regs(mrg.cloud.RegisterMap):
# echo_slave exposes a 512-word (2 KB) echo RAM starting at byte 0.
ECHO = 0x0000

app = mrg.cloud.App(
"verilog_hello",
design="examples/verilog_hello/design.v",
registers=Regs,
)

@app.local_entrypoint()
def main():
pattern = [0xDEADBEEF, 0xCAFEBABE, 0x12345678, 0xABCDEF01]

print("writing pattern ...")
for i, word in enumerate(pattern):
app.write(Regs.ECHO + i * 4, word)

print("reading back ...")
for i, expected in enumerate(pattern):
got = app.read(Regs.ECHO + i * 4)
status = "OK" if got == expected else f"MISMATCH (got {got:#010x})"
print(f" [{i}] {expected:#010x} → {status}")
```

The only difference from `hello_wishbone/client_sdk.py` is `design=`
pointing at a `.v` file instead of a `.py` module — nothing else about
`App` changes.

## The Wishbone contract, for a Verilog top module

Unlike an Amaranth design, where the top-level class is found by scanning
for the one `Elaboratable` exposing the right port *attributes*, a plain
Verilog file's top module is found by scanning for the one module whose
port list matches this contract by name, width, and direction:

| Port | Width | Direction |
| --- | --- | --- |
| `clk` | 1 | input |
| `rst` | 1 | input |
| `wb_cyc` | 1 | input |
| `wb_stb` | 1 | input |
| `wb_we` | 1 | input |
| `wb_adr` | 9 | input |
| `wb_dat_w` | 32 | input |
| `wb_sel` | 4 | input |
| `wb_dat_r` | 32 | output |
| `wb_ack` | 1 | output |

If a file has more than one module matching this contract, pass `--top
<name>` (CLI) or `top="<name>"` (`App(...)`/`mrg.build.synth`/`pnr`) to
disambiguate, `design.v` here only has one, so it's auto-detected.

## Note: no simulation tests here

[`hello_wishbone/tests/`](hello-wishbone.md) uses Amaranth's own Python
simulator (`amaranth.sim.Simulator`), which only simulates Amaranth's IR, it
can't run a hand-written `.v` file. There's currently no Verilog simulator
wired into `mrg_build` (Icarus Verilog and Verilator ship in the sandbox
image's toolchain bundle already, but nothing calls them yet), so this
example's `tests/` is a synth-report smoke test instead of a real
simulation.
17 changes: 13 additions & 4 deletions docs/guides/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,20 +31,29 @@ no-scope personal access token, or just set `$MRG_API_KEY` directly.
## Local builds: no login required

```bash
mrg synth <design.py> # resource util
mrg pnr <design.py> [--target-mhz] [--sys-clk-mhz] [--timing-target-mhz] # Fmax + timing
mrg synth <design.py|design.v> [--top NAME] # resource util
mrg pnr <design.py|design.v> [--top NAME] [--target-mhz] [--sys-clk-mhz] [--timing-target-mhz] # Fmax + timing
```

Both print a JSON report on stdout and exit non-zero on a failed build. No
API key, no cloud, these run against the local toolchain or the pinned
Docker image.
Docker image. `design` may be an Amaranth `.py` module or a plain Verilog
`.v` file — the extension picks the language. `--top` is a Verilog-only
top-module disambiguator (ignored for Amaranth), only needed when a `.v`
file exposes more than one module matching the Wishbone contract; otherwise
the top module is auto-detected.

## `run`: program and run an app

```bash
mrg run <file.py> [--fpga-id N] [--no-program] [--sys-clk HZ] [--timing-target-mhz MHZ]
mrg run <file.py> [--fpga-id N] [--no-program] [--sys-clk HZ] [--timing-target-mhz MHZ] [--top NAME]
```

`--top` overrides `App(top=...)`; it's the Verilog-only top-module
disambiguator described above (ignored when the `App`'s `design` is
Amaranth), only needed if the app's `.v` file exposes more than one module
matching the Wishbone contract.

Loads the [`mrg.cloud.App`](sdk.md#app) from `file.py`, submits the design and
blocks until it's built and flashed, then calls its
[`@local_entrypoint`](sdk.md#applocal_entrypoint). No board is chosen up
Expand Down
38 changes: 34 additions & 4 deletions docs/guides/sdk.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,9 @@ three ways to use the platform:
## `mrg.cloud`: real hardware

You describe an **application** with three things: a **design** (an Amaranth
`.py` file whose top-level module is a Wishbone B4 slave), an optional
**register map** (the byte offsets your design exposes), and API config.
`.py` file, or a plain Verilog `.v` file, whose top-level module is a
Wishbone B4 slave), an optional **register map** (the byte offsets your
design exposes), and API config.

```python
import manhattan_reasoning_gym as mrg
Expand Down Expand Up @@ -48,7 +49,8 @@ mrg run my_design.py
mrg.cloud.App(
name,
*,
design, # path to the Amaranth .py design
design, # path to the Amaranth .py or plain Verilog .v design
top=None, # Verilog-only top-module disambiguator, see below
fpga_id=None, # for --no-program reconnect only, see below
registers=None, # a RegisterMap subclass (optional)
api_key=None, # explicit arg > $MRG_API_KEY > `mrg login`
Expand All @@ -61,6 +63,29 @@ mrg.cloud.App(
Creating an `App` registers it so the CLI can discover it, you don't export
anything. If a file defines several, `mrg run` uses the last one.

**Verilog designs.** `design=` also accepts a plain Verilog `.v` file — the
extension picks the language server-side, nothing else about `App` changes.
Since a `.v` file has no `Elaboratable` to scan for, its top module is found
by matching the one module whose port list has this Wishbone contract by
name, width, and direction:

| Port | Width | Direction |
| --- | --- | --- |
| `clk` | 1 | input |
| `rst` | 1 | input |
| `wb_cyc` | 1 | input |
| `wb_stb` | 1 | input |
| `wb_we` | 1 | input |
| `wb_adr` | 9 | input |
| `wb_dat_w` | 32 | input |
| `wb_sel` | 4 | input |
| `wb_dat_r` | 32 | output |
| `wb_ack` | 1 | output |

If a file has more than one module matching this contract, pass `top="..."`
(ignored for an Amaranth design) to disambiguate; with only one match it's
auto-detected. See the [Verilog Hello](../examples/verilog-hello.md) example.

**`fpga_id` is normally left unset.** A fresh build never picks a board
itself — the server claims a build slot (a network identity baked into the
bitstream, independent of any physical board) and dispatches the build
Expand Down Expand Up @@ -233,9 +258,10 @@ plain host transparently runs the pinned Docker image and parses its JSON
report. Either way you get a `BuildReport` back.

```python
mrg.build.synth(design, *, work=None) -> BuildReport
mrg.build.synth(design, *, top=None, work=None) -> BuildReport
mrg.build.pnr(
design, *,
top=None, # Verilog-only top-module disambiguator
target_mhz=None, # legacy alias, sets both knobs below
sys_clk_mhz=None,
timing_target_mhz=None,
Expand All @@ -246,6 +272,10 @@ mrg.build.pnr(

- `synth`: resource utilization only, fast, no timing analysis.
- `pnr`: full-SoC place-and-route (Fmax, `timing_met`, SoC-wide utilization).
- `design` accepts an Amaranth `.py` or plain Verilog `.v` file; `top` is the
same Verilog-only disambiguator as `App(top=...)` (ignored for Amaranth),
only needed when a `.v` file exposes more than one module matching the
Wishbone contract.

`BuildReport` fields: `mode`, `ok`, `scope`, `fits`, `fmax_mhz`, `sys_clk_mhz`,
`target_mhz`, `timing_met`, `clock`, `util`, `synth_cells`, `warnings`,
Expand Down
1 change: 1 addition & 0 deletions mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ nav:
- Examples:
- Overview: examples/index.md
- Hello Wishbone: examples/hello-wishbone.md
- Verilog Hello: examples/verilog-hello.md
- SAT solver: examples/sat-solver.md
- BERT feed-forward: examples/bert-ffn.md
- FFN accelerator: examples/ffn-accel.md
Expand Down
Loading