diff --git a/docs/changelog.md b/docs/changelog.md index d198095..8018409 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -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 ` (CLI) or `top=""` + (`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 diff --git a/docs/examples/index.md b/docs/examples/index.md index 933b728..f14e062 100644 --- a/docs/examples/index.md +++ b/docs/examples/index.md @@ -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 diff --git a/docs/examples/verilog-hello.md b/docs/examples/verilog-hello.md new file mode 100644 index 0000000..d00512c --- /dev/null +++ b/docs/examples/verilog-hello.md @@ -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 +` (CLI) or `top=""` (`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. diff --git a/docs/guides/cli.md b/docs/guides/cli.md index 99c184e..f06ef49 100644 --- a/docs/guides/cli.md +++ b/docs/guides/cli.md @@ -31,20 +31,29 @@ no-scope personal access token, or just set `$MRG_API_KEY` directly. ## Local builds: no login required ```bash -mrg synth # resource util -mrg pnr [--target-mhz] [--sys-clk-mhz] [--timing-target-mhz] # Fmax + timing +mrg synth [--top NAME] # resource util +mrg pnr [--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 [--fpga-id N] [--no-program] [--sys-clk HZ] [--timing-target-mhz MHZ] +mrg run [--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 diff --git a/docs/guides/sdk.md b/docs/guides/sdk.md index 8e109f5..23c5977 100644 --- a/docs/guides/sdk.md +++ b/docs/guides/sdk.md @@ -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 @@ -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` @@ -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 @@ -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, @@ -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`, diff --git a/mkdocs.yml b/mkdocs.yml index 264ed17..216818c 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -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