summaryrefslogtreecommitdiff
path: root/src/oracle/clkrst_cases.zig
diff options
context:
space:
mode:
authorGabriel Schneider <[email protected]>2026-08-25 12:40:53 -0300
committerGabriel Schneider <[email protected]>2026-08-25 12:46:51 -0300
commitf5f8068fac59b4f16046c2022c2fc7c7e447ef4c (patch)
tree2731a3ed4e51cae09e184e25778eded5fc37d1f5 /src/oracle/clkrst_cases.zig
downloadesp32p4-f5f8068fac59b4f16046c2022c2fc7c7e447ef4c.tar.gz
esp32p4-f5f8068fac59b4f16046c2022c2fc7c7e447ef4c.zip
zig-p4: pure-Zig ESP32-P4 toolchain
build.zig generates the linker script and drives Zig's own LLD; tools/image.zig turns the ELF into a flashable image and tools/{rom,serial}.zig speak the mask ROM loader over the UART. No CMake, ninja, idf.py, esptool, or external linker. src/soc.zig is a comptime register model over ESP-IDF's own *_reg.h headers; src/hal/ adds peripheral sequences; src/io/ implements std.Io for the chip; src/oracle/ diffs this HAL against ESP-IDF's on the die.
Diffstat (limited to 'src/oracle/clkrst_cases.zig')
-rw-r--r--src/oracle/clkrst_cases.zig131
1 files changed, 131 insertions, 0 deletions
diff --git a/src/oracle/clkrst_cases.zig b/src/oracle/clkrst_cases.zig
new file mode 100644
index 0000000..f004dd4
--- /dev/null
+++ b/src/oracle/clkrst_cases.zig
@@ -0,0 +1,131 @@
+//! HP_SYS_CLKRST's side of the differential: the clock-gate and reset pairing table.
+//!
+//! This suite exists because of a bug that a hardware test failed to catch. `src/hal/clkrst.zig`
+//! maps each peripheral to the register and bit that gate and reset it, and every row of that table
+//! is a transcription from ESP-IDF's LL source - the field macros do not record which register they
+//! live in, so there is nothing to derive it from. Two rows were wrong: timg0, timg1, systimer and
+//! twai0 had their APB clock enables in `PERI_CLK_CTRL21` instead of `SOC_CLK_CTRL2`, so
+//! `setClockEnabled` wrote a bit of an unrelated register.
+//!
+//! `examples/halcheck.zig` printed exactly the expected `twai0 boot=0 on=1 off=0` throughout,
+//! because `isClockEnabled` read back the same wrong bit `setClockEnabled` had just written. A
+//! self-consistent test proves the two halves of your own code agree; it does not prove either one
+//! touches the hardware, and that one would have passed with the chip unplugged.
+//!
+//! ESP-IDF reaches these bits through its own generated struct definitions - a genuinely independent
+//! path - so this comparison is the check a read-back cannot be.
+//!
+//! Not covered here: TWAI0. Its bus clock is the one that is gated off at power-on, which makes it
+//! the interesting case, but ESP-IDF's TWAI bus-clock LL takes a controller handle this suite has no
+//! business constructing. The four rows below share the two registers TWAI0's row uses, so a
+//! transcription error in it would have to be independent of theirs to survive.
+
+const std = @import("std");
+const hal = @import("hal");
+const regs = @import("regs");
+const mmio = @import("mmio");
+const types = @import("differ_types.zig");
+
+extern fn oracle_clkrst_timg_bus_clock(group: c_uint, enable: c_int) void;
+extern fn oracle_clkrst_timg_reset(group: c_uint) void;
+extern fn oracle_clkrst_systimer_bus_clock(enable: c_int) void;
+extern fn oracle_clkrst_systimer_reset() void;
+extern fn oracle_clkrst_uart_bus_clock(port: c_uint, enable: c_int) void;
+
+/// Known state: every peripheral this suite touches with its bus clock on, which is also the state
+/// the chip powers up in ("All peripheral clocks are default enabled after chip is powered on",
+/// esp_system/port/soc/esp32p4/clk.c:200). Nothing else in the block is touched - UART0's gates in
+/// particular, because that is the console this result is printed over.
+/// Restore through ESP-IDF's LL, never through the code under test.
+///
+/// This suite exists to catch a `setClockEnabled` that writes the wrong register. Restoring with
+/// `hal.clkrst.setClockEnabled` defeated exactly that: `differ.zig` runs restore, idf, snapshot,
+/// restore, ours, snapshot, so with the HAL on both the restore and the "ours" side, a
+/// `setClockEnabled` that did nothing at all would leave run B's snapshot equal to run A's and pass
+/// all six clock cases. Which is how the original bug - four peripherals' gate bits in
+/// PERI_CLK_CTRL21 instead of SOC_CLK_CTRL2 - could have survived this suite too.
+fn restore() void {
+ oracle_clkrst_timg_bus_clock(1, 1);
+ oracle_clkrst_systimer_bus_clock(1);
+ oracle_clkrst_uart_bus_clock(1, 1);
+}
+
+pub const suite: types.Suite = .{
+ .descriptor = .{
+ .name = "clkrst",
+ .base = @intCast(regs.HP_SYS_CLKRST_SOC_CLK_CTRL1_REG - 0x18), // block base
+ // 0x00 through HP_RST_EN2 at +0xC8: covers SOC_CLK_CTRL1/2 (+0x18, +0x1c), every
+ // PERI_CLK_CTRL register, and all three HP_RST_EN registers. Everything either
+ // implementation could plausibly hit is inside this window, which is the property that
+ // makes a difference detectable rather than merely absent.
+ .words = 52,
+ .restore = .{ .configure = restore },
+ },
+ .cases = &.{
+ // Disable first in each pair: the restored state has them on, so "disable" is the operation
+ // with an observable effect and "enable" would otherwise be a no-op comparison.
+ .{ .name = "timg1_bus_clock", .arg = 0, .idf = idfTimgOff, .ours = ourTimgOff },
+ .{ .name = "systimer_bus_clock", .arg = 0, .idf = idfSystimerOff, .ours = ourSystimerOff },
+ .{ .name = "uart1_bus_clock", .arg = 0, .idf = idfUartOff, .ours = ourUartOff },
+ .{ .name = "timg1_reset", .idf = idfTimgReset, .ours = ourTimgReset },
+ .{ .name = "systimer_reset", .idf = idfSystimerReset, .ours = ourSystimerReset },
+ // Last, so the block is left with everything on regardless of which side ran last.
+ .{ .name = "timg1_bus_clock", .arg = 1, .idf = idfTimgOn, .ours = ourTimgOn },
+ .{ .name = "systimer_bus_clock", .arg = 1, .idf = idfSystimerOn, .ours = ourSystimerOn },
+ .{ .name = "uart1_bus_clock", .arg = 1, .idf = idfUartOn, .ours = ourUartOn },
+ },
+};
+
+fn idfTimgOff() void {
+ oracle_clkrst_timg_bus_clock(1, 0);
+}
+fn ourTimgOff() void {
+ hal.clkrst.setClockEnabled(.timg1, false);
+}
+fn idfTimgOn() void {
+ oracle_clkrst_timg_bus_clock(1, 1);
+}
+fn ourTimgOn() void {
+ hal.clkrst.setClockEnabled(.timg1, true);
+}
+fn idfSystimerOff() void {
+ oracle_clkrst_systimer_bus_clock(0);
+}
+fn ourSystimerOff() void {
+ hal.clkrst.setClockEnabled(.systimer, false);
+}
+fn idfSystimerOn() void {
+ oracle_clkrst_systimer_bus_clock(1);
+}
+fn ourSystimerOn() void {
+ hal.clkrst.setClockEnabled(.systimer, true);
+}
+fn idfUartOff() void {
+ oracle_clkrst_uart_bus_clock(1, 0);
+}
+fn ourUartOff() void {
+ hal.clkrst.setClockEnabled(.uart1, false);
+}
+fn idfUartOn() void {
+ oracle_clkrst_uart_bus_clock(1, 1);
+}
+fn ourUartOn() void {
+ hal.clkrst.setClockEnabled(.uart1, true);
+}
+
+/// The reset pairing, which is the other half of the table and the half that was right. IDF pulses
+/// the bit and returns; so does ours, except for the timer groups, where it additionally clears the
+/// flash-boot watchdog protection that the reset re-arms - so a difference in the WDT register is
+/// expected and lives outside this window, while HP_RST_EN1 itself must match.
+fn idfTimgReset() void {
+ oracle_clkrst_timg_reset(1);
+}
+fn ourTimgReset() void {
+ hal.clkrst.resetPeripheral(.timg1);
+}
+fn idfSystimerReset() void {
+ oracle_clkrst_systimer_reset();
+}
+fn ourSystimerReset() void {
+ hal.clkrst.resetPeripheral(.systimer);
+}