diff options
| author | Gabriel Schneider <[email protected]> | 2026-08-26 09:56:04 -0300 |
|---|---|---|
| committer | Gabriel Schneider <[email protected]> | 2026-08-26 09:56:04 -0300 |
| commit | 27a749d3a2155419fb72bab66b1e78bcd99882ae (patch) | |
| tree | 45659155877f017acae955d64ef789f0d1bd3c7d /src/board_memory.zig | |
| parent | fc263fc4b1ee36a3c4bfd7d730cccb57ebe1064a (diff) | |
| download | pardes-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/board_memory.zig')
| -rw-r--r-- | src/board_memory.zig | 69 |
1 files changed, 51 insertions, 18 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); |
