diff options
Diffstat (limited to 'examples/halcheck.zig')
| -rw-r--r-- | examples/halcheck.zig | 194 |
1 files changed, 194 insertions, 0 deletions
diff --git a/examples/halcheck.zig b/examples/halcheck.zig new file mode 100644 index 0000000..94b3a2d --- /dev/null +++ b/examples/halcheck.zig @@ -0,0 +1,194 @@ +//! Hardware check for the register layer and the first three HAL peripherals. +//! +//! Every line this prints is a claim about the silicon that the die itself answers. Nothing here is +//! a unit test of Zig code: the register numbers come from ESP-IDF's own macros, so the only thing +//! left to doubt is whether the *sequences* built on them do what the hardware needs. +//! +//! Run with: zig build -Dapp=examples/halcheck.zig run -Dseconds=8 + +const std = @import("std"); +const soc = @import("soc"); +const hal = @import("hal"); +const regs = @import("regs"); +const mmio = @import("mmio"); +const config = @import("config"); + +pub const panic = std.debug.FullPanic(struct { + fn call(msg: []const u8, _: ?usize) noreturn { + soc.rom.print("MARK HAL_PANIC %s\r\n", .{msg.ptr}); + while (true) {} + } +}.call); + +const led = config.led_pin; + +export fn zig_main() noreturn { + soc.rom.print("\r\nMARK HAL_START hw_ver=%u registers from ESP-IDF headers\r\n", .{ + @as(u32, regs.ZIG_P4_HW_VER), + }); + + // ---------------------------------------------------------------- 1. the register layer itself + // A handful of addresses, printed so a human can check them against the technical reference + // manual without trusting anything in this repo. GPIO_OUT is 0x500E0004 and IO_MUX's pad 0 is + // 0x500E1004 on this part. + soc.rom.print("MARK HAL_ADDR gpio_out=0x%08x iomux_pad0=0x%08x systimer_conf=0x%08x\r\n", .{ + @as(u32, @intCast(regs.GPIO_OUT_REG)), + @as(u32, @intCast(regs.PERIPHS_IO_MUX_U_PAD_GPIO0)), + @as(u32, @intCast(regs.SYSTIMER_CONF_REG)), + }); + // The GPIO matrix constant that the hand-written predecessor got right and the first HAL draft + // got wrong. 256 on the P4, 128 on the S3. + soc.rom.print("MARK HAL_SIGMAP gpio_out_idx=%u expect=256\r\n", .{ + @as(u32, hal.gpio.matrix_gpio_signal), + }); + + // ---------------------------------------------------------------- 2. GPIO + // Same observable behaviour as the hand-written GPIO this replaces: drive a pin, read it back + // through the pad's own input buffer while it is driven. + hal.gpio.configureOutput(led, .{ .readback = true }); + hal.gpio.setHigh(led); + const drove_high = hal.gpio.getLevel(led); + hal.gpio.setLow(led); + const drove_low = hal.gpio.getLevel(led); + soc.rom.print("MARK HAL_GPIO pin=%u high=%u low=%u oe=%u expect=1,0,1\r\n", .{ + @as(u32, led), + @as(u32, drove_high), + @as(u32, drove_low), + @as(u32, @intFromBool(hal.gpio.isOutputEnabled(led))), + }); + + // The second bank. The split at pin 32 is the classic P4 GPIO bug - a write to `out` with a + // shift of 40 lands on pin 8 - and it is only provable above 31. + // + // Pin choice matters here and cost a debugging round. GPIO54 is on JP1 but is this board's + // ESP32-C6 reset line with an external pull-up: it drives correctly (out1 follows 1 -> 0) and + // reads back stuck high, because the pull-up wins at the pad. GPIO33 (JP1 pin 21) is genuinely + // free, and it tracks. + const bank1_pin = 33; + hal.gpio.configureOutput(bank1_pin, .{ .readback = true }); + hal.gpio.setHigh(bank1_pin); + const hi_b1 = hal.gpio.getLevel(bank1_pin); + hal.gpio.setLow(bank1_pin); + const lo_b1 = hal.gpio.getLevel(bank1_pin); + hal.gpio.outputDisable(bank1_pin); + soc.rom.print("MARK HAL_GPIO_BANK1 pin=%u high=%u low=%u expect=1,0\r\n", .{ + @as(u32, bank1_pin), @as(u32, hi_b1), @as(u32, lo_b1), + }); + + // ---------------------------------------------------------------- 3. clock gates + // TWAI0 is the one peripheral whose bus clock is gated OFF at power-on, which makes it the only + // place where enabling a clock is observable rather than merely correct. + // + // Read what this proves narrowly. Both the write and the read-back go through this project's own + // accessor, so agreement shows the two are consistent with each other - not that either touches + // the right register. That is not a hypothetical caveat: an earlier version of clkrst.zig had + // these four peripherals' gate bits in HP_SYS_CLKRST_PERI_CLK_CTRL21 instead of SOC_CLK_CTRL2, + // and this test printed exactly the expected 0,1,0 while poking a bit of an unrelated register. + // It would have passed with the chip unplugged. The real check is `zig build diff`, where the + // reference reaches the hardware through ESP-IDF's LL instead. + const twai_before = hal.clkrst.isClockEnabled(.twai0); + hal.clkrst.setClockEnabled(.twai0, true); + const twai_on = hal.clkrst.isClockEnabled(.twai0); + hal.clkrst.setClockEnabled(.twai0, false); + const twai_off = hal.clkrst.isClockEnabled(.twai0); + soc.rom.print("MARK HAL_GATE twai0 boot=%u on=%u off=%u expect=0,1,0 (self-consistency only; see diff)\r\n", .{ + @as(u32, @intFromBool(twai_before)), + @as(u32, @intFromBool(twai_on)), + @as(u32, @intFromBool(twai_off)), + }); + + // UART0 is the console this text is coming out of, so its gate must read back enabled - and + // this is a read, deliberately: writing it would cut the wire mid-sentence. + soc.rom.print("MARK HAL_GATE uart0=%u expect=1 (console is alive, so it must be)\r\n", .{ + @as(u32, @intFromBool(hal.clkrst.isClockEnabled(.uart0))), + }); + + // ---------------------------------------------------------------- 4. systimer + hal.systimer.init(); + const t0 = hal.systimer.read(.unit0) orelse { + soc.rom.print("MARK HAL_SYSTIMER dead - no valid handshake\r\n", .{}); + hang(); + }; + const c0 = soc.cycles(); + soc.rom.ets_delay_us(50_000); + const t1 = hal.systimer.read(.unit0) orelse unreachable; + const c1 = soc.cycles(); + + const ticks = t1 - t0; + const cycles = c1 - c0; + // The counter must advance, and by roughly 16 MHz * 50 ms = 800,000 ticks. + soc.rom.print("MARK HAL_SYSTIMER ticks=%u expect~800000 advancing=%u\r\n", .{ + @as(u32, @truncate(ticks)), + @as(u32, @intFromBool(t1 > t0)), + }); + // Two independent clocks measuring the same interval: systimer is a fixed 16 MHz, so the CPU + // frequency falls out of the ratio. This is the honest measurement of a number the README has + // only ever estimated. + const cpu_khz: u64 = if (ticks != 0) (cycles * (hal.systimer.hz / 1000)) / ticks else 0; + soc.rom.print("MARK HAL_CPUFREQ %u kHz, from %u cycles per %u systimer ticks\r\n", .{ + @as(u32, @truncate(cpu_khz)), + @as(u32, @truncate(cycles)), + @as(u32, @truncate(ticks)), + }); + + // The 52-bit counter read as a pair of words. If the update/valid handshake were skipped, the + // low word could wrap between the two loads and the value would jump backwards; sample it in a + // tight loop and assert monotonicity, which is the only cheap way to catch that. + var last: u64 = hal.systimer.read(.unit0) orelse unreachable; + var backwards: u32 = 0; + for (0..2000) |_| { + const now = hal.systimer.read(.unit0) orelse unreachable; + if (now < last) backwards += 1; + last = now; + } + soc.rom.print("MARK HAL_SYSTIMER_MONOTONIC 2000 reads, backwards=%u expect=0\r\n", .{backwards}); + + // ---------------------------------------------------------------- 5. the TIMG reset trap + // Resetting a timer group re-arms flash-boot watchdog protection, and the board then reboots a + // moment later with nothing on the console to explain it. resetPeripheral clears that bit as + // part of the reset. The proof is negative and needs time to pass, so print before and after and + // let the rest of this program be the delay. + soc.rom.print("MARK HAL_TIMG_RESET resetting timg0 - a reboot after this line means the flashboot fixup is missing\r\n", .{}); + hal.clkrst.resetPeripheral(.timg0); + hal.systimer.delayMicros(200_000); + soc.rom.print("MARK HAL_TIMG_SURVIVED still running 200 ms after the timg0 reset\r\n", .{}); + + soc.rom.print("MARK HAL_DONE\r\n", .{}); + + // Blink from the HAL, using the systimer for the delay rather than the ROM's. + var beat: u32 = 0; + while (true) : (beat += 1) { + hal.gpio.setHigh(led); + hal.systimer.delayMicros(250_000); + hal.gpio.setLow(led); + hal.systimer.delayMicros(250_000); + if (beat % 4 == 0) { + soc.rom.print("MARK HAL_ALIVE beat=%u t=%u us\r\n", .{ + beat, + @as(u32, @truncate(hal.systimer.micros(.unit0) orelse 0)), + }); + } + } +} + +fn hang() noreturn { + while (true) {} +} + +export fn _start() linksection(".text.entry") callconv(.naked) noreturn { + asm volatile ( + \\ li t0, 1 << 13 + \\ csrs mstatus, t0 + \\ la sp, __stack_top + \\ mv fp, sp + \\ la t0, __bss_start + \\ la t1, __bss_end + \\ bgeu t0, t1, 2f + \\1: + \\ sw zero, 0(t0) + \\ addi t0, t0, 4 + \\ bltu t0, t1, 1b + \\2: + \\ j zig_main + ); +} |
