summaryrefslogtreecommitdiff
path: root/src/pardes.zig
diff options
context:
space:
mode:
authorGabriel Schneider <[email protected]>2026-08-26 12:40:03 -0300
committerGabriel Schneider <[email protected]>2026-08-26 12:40:03 -0300
commitfbc194068687e49a8490c85c9f1257a2f2bb9079 (patch)
tree4fdb314f9e2c0e25bb1faca7e93fefe108023a66 /src/pardes.zig
parent3b8ee10301e0b83013a38d93516a345622a93aaa (diff)
downloadpardes-fbc194068687e49a8490c85c9f1257a2f2bb9079.tar.gz
pardes-fbc194068687e49a8490c85c9f1257a2f2bb9079.zip
A Gpio word that flips one pin, JP1 drawn in ASCII, and these words only on the P4
## Gpio `Gpio 33` flips one pad and answers on the message row with what it did: GPIO 33: 0->1 GPIO 33: 1->0 Bare `Gpio` draws the header instead, because the first question about a header is which pins it has. The pin number is DECIMAL and it is the only literal in board_memory.zig that is - every other one is an address, and addresses come off datasheets and linker maps that print hex, which is why that file made everything hex two commits ago. A GPIO number is not an address, it is part of a NAME: the schematic says GPIO47, the datasheet's pin table says 47, and `Gpio 20` meaning pin 32 would be a trap laid for the one argument anybody types from memory. ## The toggle is the host's, not the editor's New `Host.VTable.pull_gpio_toggle`, and a `GpioFn` in the p4 ABI (hence version 2), rather than board_memory reaching for GPIO_OUT the way `Poke` two functions above it would happily do. Writing that register is not the job. A pad has to be pointed at the GPIO function in the IO MUX, routed in the GPIO matrix, given drive strength and an input buffer with its pulls cleared, and only then driven - four register files behind a per-pin table. That code already exists in `05-zig-p4/src/hal/gpio.zig`, it is the same `configureOutput` the blink demo has always used, and its register numbers are checked against ESP-IDF's own headers on the die by `zig build diff`. A second copy inside the editor object would be a second copy under no test, and getting it wrong on a pin that boots as something else is how you lose the console you are typing on. Reported levels are the OUTPUT bits, before and after, because that is what a toggle means: the level this board is driving. A pad's input buffer on an unconnected header pin reads the air. ## JP1, read off the schematic rather than remembered The diagram is the vendor's own wiring, from sheet 2 "Expand IO" of `01-esp32p4-m3/docs/JC-ESP32P4-M3_schematic.pdf` - the only document that carries this mapping. The specification PDF's "Interface Description" page turned out to be a marketing render, and there is no board user guide; the chip datasheet has a package pinout, which is not a header. That sheet is a 872x1168 raster (`pdfimages -list` - the PDF embeds no vectors, so rendering it larger adds nothing), and at that size the rows around pin 14 are genuinely ambiguous by eye. So the mapping came from the drawing's geometry instead: thirteen wires leave each side of the symbol, a net wire runs ~100 px to its label and a power stub ~21 px. Pin 8's wire is 21 px, which is what identifies it as unconnected rather than as the first of the GPIO4x labels - the reading that had GPIO47 one row higher and shorted GPIO45 to the ground bracket. Cross-checked against a second source that has been in the tree all along: `05-zig-p4/build.zig` documents `-Dled=20` as "JP1 pin 17", and GPIO20 lands on pin 17 here. Both facts are asserted in the test, so the diagram cannot drift from either. ## Peek, Poke, Hexdump and Gpio are now the P4 build's alone `board_memory.enabled` was `os.tag == .freestanding and !isWasm()`, on the argument that these words are a property of having no operating system rather than a product configuration, and that a predicate spelled out of `builtin` cannot drift the way a hand-maintained enum can. Tidy, and it answered the wrong question. A word only exists if some shell offers it, and the shells are the platforms. `Gpio` settles it beyond argument: its whole content is one board's header, and a second freestanding port would need its own pinout rather than inheriting this one. "Bare metal" was never the requirement, "this board" was, and the two only looked identical because there is currently one of them. The old predicate's real work was excluding wasm - `freestanding` too, where an address is an offset into a linear memory the engine owns - and naming `p4` excludes it by construction instead of by a term somebody has to keep remembering. The target is now the witness rather than the gate. Absent means not compiled: the tty binary contains no `+Gpio`, no `+Hexdump`, no `ES_I2C_SDA` and no `MisalignedAddress`. ## The boot buffer's lines are checked, not eyeballed Three times now a line in that tour has been one or two characters too long for a 56-column grid, and every time it was found by reading the die's screen - the expensive way to measure a string literal. The text is a named `boot_buffer` with a test over it, six lines came down to fit with margin, and the tour gained `Gpio`. Tests: the pinout's width, its thirteen aligned pin rows, GPIO20-on-17 and pin-8-unconnected; the decimal-versus-hex distinction; every boot-buffer line. Full suite green - unit-test, snap 95/95, hxdiff 481/0, hxparity 561/0, image-harness, pdf-harness, mupdf-check - and tty, p4, gui, p4 at 80x24, p4 with the fade forced on. On the die `p4-bench --check` is 5/5, the fifth being a new one: three `Gpio 33` runs must report 0->1, 1->0, 0->1, because the alternation is the only oracle a hardcoded string could not fake.
Diffstat (limited to 'src/pardes.zig')
-rw-r--r--src/pardes.zig79
1 files changed, 59 insertions, 20 deletions
diff --git a/src/pardes.zig b/src/pardes.zig
index 9f20e0a3..4da64c3f 100644
--- a/src/pardes.zig
+++ b/src/pardes.zig
@@ -2418,6 +2418,64 @@ comptime {
/// syntax, terminal ANSI palettes, and PDF tint colors deliberately are not
/// here: those switch to `theme()` immediately while this small palette moves
/// between themes over a handful of display frames.
+/// WHAT THE BOARD BOOTS WITH. An empty buffer is honest and useless: the three words that make
+/// this board interesting take an address, and a board's address space is precisely the thing you
+/// cannot guess. So the buffer is a tour of it - every address below comes from this repository
+/// rather than from memory, which is why they are worth trusting: the two flash figures and the two
+/// RAM ones are the linker script's own ORIGINs (`05-zig-p4/build.zig`'s MEMORY block), and the
+/// peripheral bases are `DR_REG_*` from ESP-IDF's headers as `05-zig-p4/src/hal` uses them.
+///
+/// Each command sits alone on its line because an argument list ends at the last argument - a
+/// trailing comment would be `ExtraArgument` - so the notes go above the lines they describe. Run
+/// one by putting the cursor on it, `x` to select the line, Tab to execute.
+///
+/// EVERY LINE IS SHORT ENOUGH TO RENDER WHOLE, which is asserted rather than eyeballed: see the
+/// test below. A tour whose lines wrap is a worse first screen than no tour.
+const boot_buffer =
+ \\x selects a line, Tab runs it. 0x optional.
+ \\
+ \\-- flash: this image's rodata, then its code
+ \\Hexdump 40000020 40
+ \\Hexdump 40050000 40
+ \\-- L2MEM: firmware data, then the heap
+ \\Hexdump 4ff00000 40
+ \\Hexdump 4ff40000 40
+ \\-- the mask ROM
+ \\Hexdump 4fc00000 20
+ \\-- UART0, the console you are reading on
+ \\Peek 500ca000 4
+ \\-- LP_STORE0: write a word, read it back
+ \\Poke 5011002c deadbeef
+ \\Peek 5011002c
+ \\-- RNG_DATA: not memory. Run it twice.
+ \\Peek 501101a4
+ \\Peek 501101a4
+ \\-- the pins: Gpio draws JP1, Gpio 33 flips
+ \\Gpio
+ \\Gpio 33
+;
+
+// Three times in this port a line in that buffer has been one or two characters too long for the
+// board's 56-column grid, and every time it was found by reading the die's screen rather than by
+// reading the source - which is the expensive way to find a string literal's length. The bound is
+// the grid minus the line-number gutter minus a column, and the margin below it is deliberate:
+// pinning the exact gutter width would make this test a restatement of the renderer instead of a
+// statement about the text.
+test "every line of the board's boot buffer renders whole" {
+ const cols: usize = @import("pardes_config").p4_cols;
+ var it = std.mem.splitScalar(u8, boot_buffer, '\n');
+ while (it.next()) |line| {
+ std.testing.expect(line.len + 8 <= cols) catch |err| {
+ std.debug.print("boot buffer line is {d} of {d} usable: \"{s}\"\n", .{ line.len, cols - 8, line });
+ return err;
+ };
+ }
+ // and the tour still visits what it says it visits
+ try std.testing.expect(std.mem.indexOf(u8, boot_buffer, "Hexdump 40000020") != null);
+ try std.testing.expect(std.mem.indexOf(u8, boot_buffer, "Poke 5011002c deadbeef") != null);
+ try std.testing.expect(std.mem.indexOf(u8, boot_buffer, "\nGpio\n") != null);
+}
+
pub const ChromeTheme = struct {
tag_bg: [3]u8,
tag_fg: [3]u8,
@@ -5785,26 +5843,7 @@ pub const Pardes = struct {
// LP_SYSTEM_REG_RNG_DATA, the hardware random generator. Between them they demonstrate
// the whole point of a volatile read: one address gives back what was written and the
// other never gives the same answer twice. Both verified on this die.
- const content = try p.gpa.dupe(u8,
- \\x selects a line, Tab runs it. Hex, 0x optional.
- \\
- \\-- flash: this image's rodata, then its code
- \\Hexdump 40000020 40
- \\Hexdump 40050000 40
- \\-- L2MEM: firmware data, then the editor's heap
- \\Hexdump 4ff00000 40
- \\Hexdump 4ff40000 40
- \\-- the mask ROM
- \\Hexdump 4fc00000 20
- \\-- UART0, the console you are reading this on
- \\Peek 500ca000 4
- \\-- LP_STORE0: write a word, then read it back
- \\Poke 5011002c deadbeef
- \\Peek 5011002c
- \\-- RNG_DATA: a register is not memory. Run twice.
- \\Peek 501101a4
- \\Peek 501101a4
- );
+ const content = try p.gpa.dupe(u8, boot_buffer);
errdefer p.gpa.free(content);
_ = try output_pane.open(p, 0, "", .{ .cmd = .New }, "", content);
p.ncol = 1;