Verilog Hello

The same design as Hello Wishbone, 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/ in the manhattan-reasoning-gym repo.

Run it

# 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
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's EchoSlave:

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

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/ 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.