diff options
| -rw-r--r-- | src/pardes/app.zig | 34 | ||||
| -rw-r--r-- | tools/bench_main.zig | 28 |
2 files changed, 60 insertions, 2 deletions
diff --git a/src/pardes/app.zig b/src/pardes/app.zig index 8b43791..17bef83 100644 --- a/src/pardes/app.zig +++ b/src/pardes/app.zig @@ -64,6 +64,10 @@ const uart = @import("uart.zig"); /// How the editor emits bytes. Called with finished runs of ANSI, many times per frame. const WriteFn = *const fn (ctx: ?*anyopaque, ptr: [*]const u8, len: usize) callconv(.c) void; +/// The board's pads, offered to the editor. Optional on the wire so a firmware with nothing to +/// toggle passes null and the `Gpio` word reports that rather than the object guessing. +const GpioFn = *const fn (ctx: ?*anyopaque, pin: u16, was: *u8, now: *u8) callconv(.c) bool; + /// This board's allocator, handed across as plain function pointers. `log2_align` is a log2 value, /// which is exactly how `std.mem.Alignment` represents itself, so neither side needs a conversion /// table. @@ -81,7 +85,7 @@ const Allocator = extern struct { /// The one number both sides must agree on. Linkers do not type-check C symbols, so a signature /// that drifts on one side of this seam links cleanly and then corrupts the stack; checking this /// before calling anything else turns that into a refusal to boot. -const abi_version: u32 = 1; +const abi_version: u32 = 2; extern fn pardes_p4_abi_version() callconv(.c) u32; /// Hand over the allocator and the output sink, and state the initial window size. Returns 0, or a @@ -89,6 +93,7 @@ extern fn pardes_p4_abi_version() callconv(.c) u32; extern fn pardes_p4_init( alloc: *const Allocator, write: WriteFn, + gpio: ?GpioFn, ctx: ?*anyopaque, cols: u16, rows: u16, @@ -124,6 +129,31 @@ fn writeOut(_: ?*anyopaque, ptr: [*]const u8, len: usize) callconv(.c) void { uart.write(ptr[0..len]); } +/// Flip one pad and report the level before and after. The editor's `Gpio` word calls this; the +/// editor has no register of its own for it, deliberately. +/// +/// THIS IS WHY THE SEAM IS HERE. A toggle is not a write to GPIO_OUT: `configureOutput` points the +/// pad's IO MUX at the GPIO function, routes the GPIO matrix's output to it, sets the drive strength +/// and input buffer and clears the pulls, and only then enables the driver - four register files, +/// indexed by a per-pin table. That code already exists in `hal/gpio.zig`, it is the same call +/// `src/main.zig` blinks with, and its register numbers are checked against ESP-IDF's own headers by +/// `zig build diff`. A second copy inside the editor object would be a second copy under no test. +/// +/// `getDrivenLevel` rather than `getLevel`: the answer is the level this board is DRIVING, which is +/// defined for every pin. The pad's own level is what the outside world says, and on an unconnected +/// header pin that is noise. The input buffer is enabled anyway, so `Peek` of GPIO_IN_REG shows the +/// pad for anyone who wants to compare the two. +fn gpioToggle(_: ?*anyopaque, pin: u16, was: *u8, now: *u8) callconv(.c) bool { + if (pin > hal.gpio.max_pin) return false; + const p: u8 = @intCast(pin); + hal.gpio.configureOutput(p, .{ .readback = true }); + const before = hal.gpio.getDrivenLevel(p); + if (before == 1) hal.gpio.setLow(p) else hal.gpio.setHigh(p); + was.* = before; + now.* = hal.gpio.getDrivenLevel(p); + return true; +} + // ------------------------------------------------------------------------------------- the heap /// The span the linker script hands over, from `l2high`'s ORIGIN and LENGTH. @@ -221,7 +251,7 @@ export fn zig_main() noreturn { // Ask for more than any grid this board will ever render, so the SHELL's own ceiling is what // governs - it clamps to `-Dp4-cols`/`-Dp4-rows` and reports the result. Naming 80x24 here made // the firmware a second opinion about the geometry, which is one opinion too many. - const rc = pardes_p4_init(&editor_allocator, writeOut, null, 255, 255); + const rc = pardes_p4_init(&editor_allocator, writeOut, gpioToggle, null, 255, 255); if (rc != 0) { soc.rom.print("MARK PARDES_INIT_FAIL rc=%u\r\n", .{rc}); diff --git a/tools/bench_main.zig b/tools/bench_main.zig index 18b204e..973c004 100644 --- a/tools/bench_main.zig +++ b/tools/bench_main.zig @@ -961,6 +961,34 @@ fn check(port: *serial.Port, o: Options, r: *Report) !void { r.print(" and a later Peek still finds it {s}\n", .{if (still) "ok" else "FAILED"}); if (!still) failures += 1; + // 4. A PAD ACTUALLY FLIPS, AND THE FIRMWARE READS IT BACK RATHER THAN ASSUMING IT. + // + // `Gpio <pin>` crosses the whole seam this check exists for: the editor's word, the C ABI + // (`GpioFn`, which is why the ABI version is 2), the firmware's `hal.gpio` configure-and-drive, + // the pad, and the level read back out of the output register onto the message row. Nothing + // smaller exercises the ABI at all. + // + // The ORACLE IS THE ALTERNATION, not either answer alone. `0->1` on its own is what a firmware + // that printed a hardcoded string would also say; two runs reporting `0->1` then `1->0` can only + // come from a level that was stored somewhere and read again. Three runs, so the third confirms + // the second was not a coincidence of ordering. + // + // GPIO33 because `src/oracle/ledc_cases.zig` already documents it as a free pin on this board's + // JP1 header - pin 21. GPIO20 is the blink demo's pin and may have a wire on it. + var flips: [3][]const u8 = undefined; + var flip_text: [3][4096]u8 = undefined; + for (&flips, &flip_text) |*f, *dst| { + const raw = try runWord(port, &buf, "Gpio 33"); + f.* = stripAnsi(dst, raw); + } + const first_low = std.mem.indexOf(u8, flips[0], "33: 0->1") != null; + const alternates = for (flips, 0..) |f, i| { + const want = if ((i % 2 == 0) == first_low) "33: 0->1" else "33: 1->0"; + if (std.mem.indexOf(u8, f, want) == null) break false; + } else true; + r.print(" a pad flips and reads back {s}\n", .{if (alternates) "ok" else "FAILED"}); + if (!alternates) failures += 1; + // A BURST IS NOT CHECKED HERE, deliberately. The bug it would cover - input lost while the // transmitter was full - has a deterministic host test in `src/pardes/input_rescue.zig` that // loses 67 bytes with the rescue removed and needs no board at all. Every hardware oracle for it |
