summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorGabriel Schneider <[email protected]>2026-08-26 09:56:04 -0300
committerGabriel Schneider <[email protected]>2026-08-26 09:56:04 -0300
commit27a749d3a2155419fb72bab66b1e78bcd99882ae (patch)
tree45659155877f017acae955d64ef789f0d1bd3c7d /src
parentfc263fc4b1ee36a3c4bfd7d730cccb57ebe1064a (diff)
downloadpardes-27a749d3a2155419fb72bab66b1e78bcd99882ae.tar.gz
pardes-27a749d3a2155419fb72bab66b1e78bcd99882ae.zip
Every literal in Peek, Poke and Hexdump is hex; boot the P4 into a tour of the bus
## Hex, always Base-0 parsing accepted `0x4ff40000` and `1341390848` and refused a bare `4ff40000`, on the grounds that guessing between hex and decimal would let one typo address somewhere else entirely. The reasoning was sound and the conclusion was still wrong: the ambiguity it guarded against is not a real one. Every address anybody has ever typed at these three words is hex - it came off a datasheet, a linker map, or a previous dump's own output, all of which print hex - so the base was never in doubt, and demanding `0x` on every one of them was a toll on the common case to protect a case that does not arise. The COUNTS go with them, and that is the part worth saying out loud rather than leaving as a surprise: `Hexdump 4ff40000 100` shows 0x100 bytes, which is 256, not one hundred. One rule for every literal beats two rules that each fit their own argument better, because the second kind has to be remembered at the moment you are concentrating on something else. What these words PRINT is hex too now, clamp notes included, so a number can go back in where it came out. ## And the board boots into somewhere worth looking The empty output buffer was honest and useless. The three words that make this port interesting all take an address, and a board's address space is precisely the thing you cannot guess - so the boot buffer is now a tour of it: the image's own rodata and code in flash, the firmware's data and the editor's heap in L2MEM, the mask ROM, UART0, the systimer, GPIO_OUT and an IO_MUX pad, and one harmless Poke. Every address comes from this repository rather than from memory, which is what makes them worth trusting: the flash and RAM figures are the linker script's own ORIGINs in `05-zig-p4/build.zig`, and the peripheral bases are the `DR_REG_*` values `05-zig-p4/src/hal` uses. 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. Lines are kept inside 48 columns because the first draft wrapped every one of them at the 56-column grid, which reads like a bug. Verified on the die: the buffer renders one line per line, and putting the cursor on `Hexdump 40000020 60`, selecting with `x` and pressing Tab opens a dump whose first bytes are `32 54 cd ab` - 0xABCD5432, the ESP app-descriptor magic - with the version string right behind it. Bare hex, no prefix, reading real flash. snap 95/95, hxdiff 481/0, hxparity 561/0, unit-test, tty/p4/gui.
Diffstat (limited to 'src')
-rw-r--r--src/board_memory.zig69
-rw-r--r--src/pardes.zig49
2 files changed, 98 insertions, 20 deletions
diff --git a/src/board_memory.zig b/src/board_memory.zig
index 853ae5da..f99fc106 100644
--- a/src/board_memory.zig
+++ b/src/board_memory.zig
@@ -85,20 +85,41 @@ pub const Error = error{
ExtraArgument,
};
-/// hex (`0x4ff40000`), decimal (`1341718528`), and — for free, from base 0 —
-/// binary and octal. A bare `4ff40000` is deliberately NOT hex: it is a
-/// legal-looking decimal number, so guessing the base would make one typo
-/// silently address somewhere else entirely.
+/// EVERY literal these three words take is HEX, with or without an `0x`, and there is no way to
+/// write a decimal one.
+///
+/// This replaces base-0 parsing, which accepted `0x4ff40000` and `1341390848` and refused a bare
+/// `4ff40000` on the grounds that guessing between hex and decimal would make one typo address
+/// somewhere else entirely. That reasoning was sound and the conclusion was still wrong: the
+/// ambiguity it protected against is not a real one. Every address anybody has ever typed at these
+/// three words is hex - it came off a datasheet, a linker map, or a previous dump's own output, all
+/// of which print hex - so the base was never in doubt, and demanding `0x` on every one of them was
+/// a toll on the common case to guard a case that does not arise.
+///
+/// The COUNTS go with them, and that is the part worth stating out loud rather than leaving as a
+/// surprise: `Hexdump 4ff40000 100` shows 0x100 bytes, which is 256, not one hundred. One rule for
+/// every literal in the word is worth more than two rules that each fit their argument better,
+/// because the second kind is the sort of thing you have to remember at the moment you are already
+/// concentrating on something else. Everything these words PRINT is hex too, including the clamp
+/// notes, so a number can go back in where it came out.
+fn parseHex(comptime T: type, tok: []const u8, bad: Error) Error!T {
+ // `parseInt` only honours an `0x` when its base is 0, so with base 16 the prefix has to come off
+ // here. A bare `0x` leaves nothing behind and `parseInt` rejects the empty string, which is the
+ // answer that wants giving.
+ const body = if (tok.len > 2 and tok[0] == '0' and (tok[1] | 0x20) == 'x') tok[2..] else tok;
+ return std.fmt.parseInt(T, body, 16) catch bad;
+}
+
fn parseAddr(tok: []const u8) Error!u32 {
- return std.fmt.parseInt(u32, tok, 0) catch return Error.BadAddress;
+ return parseHex(u32, tok, Error.BadAddress);
}
fn parseCount(tok: []const u8) Error!u64 {
- return std.fmt.parseInt(u64, tok, 0) catch return Error.BadCount;
+ return parseHex(u64, tok, Error.BadCount);
}
fn parseValue(tok: []const u8) Error!u32 {
- return std.fmt.parseInt(u32, tok, 0) catch return Error.BadValue;
+ return parseHex(u32, tok, Error.BadValue);
}
/// A 32-bit peripheral or RAM read that the compiler may neither elide,
@@ -152,12 +173,14 @@ fn extent(addr: u32, requested: u64, unit: u32, cap: u32) Extent {
fn writeNote(w: *std.Io.Writer, e: Extent, requested: u64, unit_name: []const u8) !void {
switch (e.limit orelse return) {
+ // Hex, like everything else these words read and print, so the number in a clamp note can go
+ // straight back into the command that produced it.
.console => try w.print(
- "clamped: {d} {s} requested, {d} shown ({d}-byte cap, one 115200-baud console)\n",
+ "clamped: 0x{x} {s} requested, 0x{x} shown (0x{x}-byte cap, one 115200-baud console)\n",
.{ requested, unit_name, e.count, max_bytes },
),
.space => try w.print(
- "clamped: {d} {s} requested, {d} shown (the 32-bit address space ends at 0x100000000)\n",
+ "clamped: 0x{x} {s} requested, 0x{x} shown (the 32-bit address space ends at 0x100000000)\n",
.{ requested, unit_name, e.count },
),
}
@@ -192,24 +215,34 @@ test "a clamp note is written exactly when something was clamped" {
try writeNote(&w, extent(0x4ff40000, 99_999, 4, max_words), 99_999, "words");
try std.testing.expectEqualStrings(
- "clamped: 99999 words requested, 1024 shown (4096-byte cap, one 115200-baud console)\n",
+ "clamped: 0x1869f words requested, 0x400 shown (0x1000-byte cap, one 115200-baud console)\n",
w.buffered(),
);
w = .fixed(&buf);
try writeNote(&w, extent(0xfffffff0, 64, 1, max_bytes), 64, "bytes");
try std.testing.expectEqualStrings(
- "clamped: 64 bytes requested, 16 shown (the 32-bit address space ends at 0x100000000)\n",
+ "clamped: 0x40 bytes requested, 0x10 shown (the 32-bit address space ends at 0x100000000)\n",
w.buffered(),
);
}
-test "an address is hex or decimal, and a bare hex-looking token is decimal" {
- try std.testing.expectEqual(0x4ff40000, parseAddr("0x4ff40000"));
- try std.testing.expectEqual(0x4ff40000, parseAddr("1341390848"));
- // a bare hex-looking token is a decimal number, never a guess
- try std.testing.expectError(Error.BadAddress, parseAddr("4ff40000"));
+test "every literal is hex, with or without the prefix" {
+ const eq = std.testing.expectEqual;
+ // the prefix is optional, never required, and never changes the answer
+ try eq(0x4ff40000, parseAddr("0x4ff40000"));
+ try eq(0x4ff40000, parseAddr("4ff40000"));
+ try eq(0x4ff40000, parseAddr("0X4FF40000"));
+ try eq(0x4ff40000, parseAddr("4FF40000"));
+ // a token that looks decimal is hex too - the whole point, and the thing to remember
+ try eq(0x100, parseCount("100"));
+ try eq(0x256, parseCount("256"));
+ try eq(0xdeadbeef, parseValue("deadbeef"));
+ // and the refusals still refuse
try std.testing.expectError(Error.BadAddress, parseAddr("0x100000000"));
+ try std.testing.expectError(Error.BadAddress, parseAddr("0x"));
+ try std.testing.expectError(Error.BadAddress, parseAddr("nope"));
+ try std.testing.expectError(Error.BadAddress, parseAddr("12g4"));
try std.testing.expectError(Error.BadCount, parseCount("-1"));
try std.testing.expectError(Error.BadValue, parseValue("0x1_0000_0000"));
}
@@ -221,7 +254,7 @@ test "an address is hex or decimal, and a bare hex-looking token is decimal" {
pub fn peek(p: *Pardes, id: usize, argument: []const u8) !void {
var it = std.mem.tokenizeAny(u8, argument, " \t\r\n");
const addr = try parseAddr(it.next() orelse return Error.MissingAddress);
- const requested = if (it.next()) |tok| try parseCount(tok) else 1;
+ const requested = if (it.next()) |tok| try parseCount(tok) else 0x1;
if (it.next() != null) return Error.ExtraArgument;
if (addr % 4 != 0) return Error.MisalignedAddress;
@@ -271,7 +304,7 @@ pub fn poke(p: *Pardes, id: usize, argument: []const u8) !void {
pub fn hexdump(p: *Pardes, id: usize, argument: []const u8) !void {
var it = std.mem.tokenizeAny(u8, argument, " \t\r\n");
const addr = try parseAddr(it.next() orelse return Error.MissingAddress);
- const requested = if (it.next()) |tok| try parseCount(tok) else 256;
+ const requested = if (it.next()) |tok| try parseCount(tok) else 0x100;
if (it.next() != null) return Error.ExtraArgument;
const e = extent(addr, requested, 1, max_bytes);
diff --git a/src/pardes.zig b/src/pardes.zig
index dcc99aea..8d68cf22 100644
--- a/src/pardes.zig
+++ b/src/pardes.zig
@@ -5745,7 +5745,53 @@ pub const Pardes = struct {
// output buffer, so booting into one means the first dump lands in the same kind of
// pane the boot pane already is. It is editable text with no file behind it, which is
// the honest description of a buffer on a board with no filesystem.
- const content = try p.gpa.dupe(u8, "");
+ // AND IT BOOTS WITH SOMETHING IN IT. 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 boot 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 -
+ // the same two keystrokes as any other selection, because an output buffer is a file pane
+ // and these are ordinary words.
+ const content = try p.gpa.dupe(u8,
+ \\pardes on an ESP32-P4. No OS, no filesystem:
+ \\this buffer is text with nothing behind it.
+ \\Peek, Poke and Hexdump read the bus itself.
+ \\Numbers are hex, with or without 0x.
+ \\
+ \\Select a line with x, then Tab to run it.
+ \\
+ \\-- this image in flash: rodata, then code
+ \\Hexdump 40000020 60
+ \\Hexdump 40050000 60
+ \\
+ \\-- L2MEM: firmware data, then editor heap
+ \\Hexdump 4ff00000 60
+ \\Hexdump 4ff40000 60
+ \\
+ \\-- the mask ROM, where ets_printf lives
+ \\Hexdump 4fc00000 40
+ \\
+ \\-- UART0: the console you are reading on
+ \\Peek 500ca000 4
+ \\
+ \\-- systimer. Run twice: a register is not
+ \\-- memory, and the two reads differ.
+ \\Peek 500e2000 4
+ \\
+ \\-- GPIO_OUT, and pad 0's IO_MUX entry
+ \\Peek 500e0004
+ \\Peek 500e1004
+ \\
+ \\-- Poke needs a value. Harmless: a W1TC
+ \\-- register clears nothing given no bits.
+ \\Poke 500e000c 0
+ );
errdefer p.gpa.free(content);
_ = try output_pane.open(p, 0, "", .{ .cmd = .New }, "", content);
p.ncol = 1;
@@ -15940,4 +15986,3 @@ test "entering tty walks the shell cursor to the column clicked past the prompt"
try std.testing.expectEqual(@as(usize, 0), rights);
try std.testing.expectEqual(@as(usize, 7), lefts);
}
-