diff options
| author | Gabriel Schneider <[email protected]> | 2026-08-25 12:40:53 -0300 |
|---|---|---|
| committer | Gabriel Schneider <[email protected]> | 2026-08-25 12:46:51 -0300 |
| commit | f5f8068fac59b4f16046c2022c2fc7c7e447ef4c (patch) | |
| tree | 2731a3ed4e51cae09e184e25778eded5fc37d1f5 /src/oracle/gpio_cases.zig | |
| download | esp32p4-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/gpio_cases.zig')
| -rw-r--r-- | src/oracle/gpio_cases.zig | 233 |
1 files changed, 233 insertions, 0 deletions
diff --git a/src/oracle/gpio_cases.zig b/src/oracle/gpio_cases.zig new file mode 100644 index 0000000..ea32eff --- /dev/null +++ b/src/oracle/gpio_cases.zig @@ -0,0 +1,233 @@ +//! GPIO's side of the differential test: the same operations expressed as ESP-IDF's LL calls and as +//! this project's HAL calls. +//! +//! GPIO is restored by configuring rather than by resetting. It has no reset bit of its own in +//! HP_SYS_CLKRST, and the pads are the board's wiring - the console's own pins are in this block, so +//! a reset here would take the console with it. Configuring is sound for GPIO specifically because +//! every field in the block is plain read/write: there is nothing self-clearing to restore. + +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_gpio_uses_rom_api() c_int; +extern fn oracle_gpio_set_level(pin: c_uint, level: c_uint) void; +extern fn oracle_gpio_output_enable(pin: c_uint) void; +extern fn oracle_gpio_output_disable(pin: c_uint) void; +extern fn oracle_gpio_input_enable(pin: c_uint) void; +extern fn oracle_gpio_input_disable(pin: c_uint) void; +extern fn oracle_gpio_func_sel(pin: c_uint, func: c_uint) void; +extern fn oracle_gpio_set_drive(pin: c_uint, strength: c_uint) void; +extern fn oracle_gpio_pullup_en(pin: c_uint) void; +extern fn oracle_gpio_pullup_dis(pin: c_uint) void; +extern fn oracle_gpio_pulldown_en(pin: c_uint) void; +extern fn oracle_gpio_pulldown_dis(pin: c_uint) void; +extern fn oracle_gpio_matrix_out(pin: c_uint, signal: c_uint) void; +extern fn oracle_gpio_od_enable(pin: c_uint) void; +extern fn oracle_gpio_od_disable(pin: c_uint) void; + +/// Whether ESP-IDF's LL was compiled to call the mask ROM instead of writing registers. Must be 0, +/// or the differential is comparing this HAL against `rom_gpio_set_output_level` rather than against +/// IDF's register sequence. Governed by src/oracle/oracle_sdkconfig.h. +pub fn usesRomApi() bool { + return oracle_gpio_uses_rom_api() != 0; +} + +/// The pin under test. A module-level variable because Zig has no closures and the harness stores +/// plain `fn` pointers: a comptime-specialised pair per pin would compare code this project does not +/// ship instead of the code it does. +pub var pin: u8 = 20; + +/// Pins worth testing. 20 is the board's LED pin and 33 is a free header pin above the 32-boundary +/// where this peripheral's bank arithmetic changes. GPIO54 is deliberately absent: it is this +/// board's ESP32-C6 reset line, held high by an external pull-up, and driving it resets the radio. +pub const pins = [_]u8{ 20, 33 }; + +/// Restore, built from register macros only. +/// +/// Nothing here may call the code under test. `differ.zig` runs restore, idf, snapshot, restore, +/// ours, snapshot - so if restore is written with the HAL, run B starts from whatever IDF just wrote +/// and a HAL function that does nothing at all compares equal. This suite used to restore with +/// `hal.gpio.outputDisable` and `hal.gpio.setLow`, which made `output_disable` and `set_level(0)` +/// no-op-versus-no-op: they could not fail. +/// +/// It must also be *total* over everything any case touches. Leaving `GPIO_PIN{n}_REG` alone made +/// both `open_drain` cases vacuous, because run B inherited run A's pad_driver bit. +fn restore() void { + const b: u5 = @intCast(if (pin < 32) pin else pin - 32); + const m = @as(u32, 1) << b; + const enable_w1tc = if (pin < 32) regs.GPIO_ENABLE_W1TC_REG else regs.GPIO_ENABLE1_W1TC_REG; + const out_w1tc = if (pin < 32) regs.GPIO_OUT_W1TC_REG else regs.GPIO_OUT1_W1TC_REG; + mmio.Reg.atAddress(@intCast(enable_w1tc)).writeRaw(m); + mmio.Reg.atAddress(@intCast(out_w1tc)).writeRaw(m); + // The IO MUX pad word, the matrix output selector, and the GPIO block's own per-pin register. + mmio.Reg.atAddress(@as(u32, @intCast(regs.PERIPHS_IO_MUX_U_PAD_GPIO0)) + 4 * @as(u32, pin)).writeRaw(0); + mmio.Reg.atAddress(@as(u32, @intCast(regs.GPIO_FUNC0_OUT_SEL_CFG_REG)) + 4 * @as(u32, pin)) + .writeRaw(@intCast(regs.SIG_GPIO_OUT_IDX)); + mmio.Reg.atAddress(@as(u32, @intCast(regs.GPIO_PIN0_REG)) + 4 * @as(u32, pin)).writeRaw(0); +} + +pub const suite: types.Suite = .{ + .descriptor = .{ + .name = "gpio", + .base = @intCast(regs.GPIO_OUT_REG - 4), // GPIO_BT_SELECT_REG sits at +0x00 + // 0x640 bytes. The window has to reach 0x558 + 4*57, where the matrix's per-pad output + // configuration lives: a first version stopped at 0x1C0 and was blind to a real bug in + // exactly those words - it saw the redundant GPIO_ENABLE write but not the wrong OEN_SEL + // that made it necessary. + .words = 400, + .volatile_words = &.{ + (0x03c - 0x000) / 4, // GPIO_IN - reflects the outside world, which moves + (0x040 - 0x000) / 4, // GPIO_IN1 + }, + .restore = .{ .configure = restore }, + }, + .cases = &.{ + .{ .name = "set_level", .arg = 1, .idf = idfSetHigh, .ours = ourSetHigh }, + .{ .name = "set_level", .arg = 0, .idf = idfSetLow, .ours = ourSetLow }, + .{ .name = "output_enable", .idf = idfOutEnable, .ours = ourOutEnable }, + .{ .name = "output_disable", .idf = idfOutDisable, .ours = ourOutDisable }, + .{ .name = "input_enable", .idf = idfInEnable, .ours = ourInEnable }, + .{ .name = "input_disable", .idf = idfInDisable, .ours = ourInDisable }, + .{ .name = "func_sel_gpio", .arg = 1, .idf = idfFuncGpio, .ours = ourFuncGpio }, + .{ .name = "drive", .arg = 3, .idf = idfDriveStrong, .ours = ourDriveStrong }, + .{ .name = "drive", .arg = 0, .idf = idfDriveWeakest, .ours = ourDriveWeakest }, + .{ .name = "pull_up", .idf = idfPullUp, .ours = ourPullUp }, + .{ .name = "pull_down", .idf = idfPullDown, .ours = ourPullDown }, + .{ .name = "pull_none", .idf = idfPullNone, .ours = ourPullNone }, + .{ .name = "matrix_out", .arg = 43, .idf = idfMatrixOut, .ours = ourMatrixOut }, + // Open drain lives in the GPIO block's per-pin register, not the IO MUX pad register, and + // had no accessor until the I2C port needed one - that bus is wired-AND, and a pin left + // push-pull shorts it against another device's driver. + .{ .name = "open_drain", .arg = 1, .idf = idfOdOn, .ours = ourOdOn }, + .{ .name = "open_drain", .arg = 0, .idf = idfOdOff, .ours = ourOdOff }, + }, +}; + +/// The IO MUX, which the GPIO block's window does not reach. +/// +/// Every pad-configuration function on this chip writes `IO_MUX.gpio[n]` at +/// PERIPHS_IO_MUX_U_PAD_GPIO0 = 0x500E1004 + 4*pin, and the GPIO block's compared window ends at +/// 0x500E063F - 0xC00 bytes short. So `input_enable`, `input_disable`, `func_sel`, both `drive` +/// cases and all three `pull` cases were comparing two identical snapshots of a register file none +/// of them touches: 8 operations across 2 pins, 16 of the suite's cases, structurally unable to +/// fail. They are the same cases; only the window is different. +pub const iomux_suite: types.Suite = .{ + .descriptor = .{ + .name = "iomux", + .base = @intCast(regs.PERIPHS_IO_MUX_U_PAD_GPIO0), + .words = 57, // one per pad, GPIO0..GPIO56 + .restore = .{ .configure = restoreIomux }, + }, + .cases = &.{ + .{ .name = "input_enable", .idf = idfInEnable, .ours = ourInEnable }, + .{ .name = "input_disable", .idf = idfInDisable, .ours = ourInDisable }, + .{ .name = "func_sel_gpio", .arg = 1, .idf = idfFuncGpio, .ours = ourFuncGpio }, + .{ .name = "drive", .arg = 3, .idf = idfDriveStrong, .ours = ourDriveStrong }, + .{ .name = "drive", .arg = 0, .idf = idfDriveWeakest, .ours = ourDriveWeakest }, + .{ .name = "pull_up", .idf = idfPullUp, .ours = ourPullUp }, + .{ .name = "pull_down", .idf = idfPullDown, .ours = ourPullDown }, + .{ .name = "pull_none", .idf = idfPullNone, .ours = ourPullNone }, + }, +}; + +fn restoreIomux() void { + mmio.Reg.atAddress(@as(u32, @intCast(regs.PERIPHS_IO_MUX_U_PAD_GPIO0)) + 4 * @as(u32, pin)).writeRaw(0); +} + +fn idfSetHigh() void { + oracle_gpio_set_level(pin, 1); +} +fn ourSetHigh() void { + hal.gpio.setHigh(pin); +} +fn idfSetLow() void { + oracle_gpio_set_level(pin, 0); +} +fn ourSetLow() void { + hal.gpio.setLow(pin); +} +fn idfOutEnable() void { + oracle_gpio_output_enable(pin); +} +fn ourOutEnable() void { + hal.gpio.outputEnable(pin); +} +fn idfOutDisable() void { + oracle_gpio_output_disable(pin); +} +fn ourOutDisable() void { + hal.gpio.outputDisable(pin); +} +fn idfInEnable() void { + oracle_gpio_input_enable(pin); +} +fn ourInEnable() void { + hal.gpio.setInputEnable(pin, true); +} +fn idfInDisable() void { + oracle_gpio_input_disable(pin); +} +fn ourInDisable() void { + hal.gpio.setInputEnable(pin, false); +} +fn idfFuncGpio() void { + oracle_gpio_func_sel(pin, 1); +} +fn ourFuncGpio() void { + hal.gpio.setFunction(pin, .gpio); +} +fn idfDriveStrong() void { + oracle_gpio_set_drive(pin, 3); +} +fn ourDriveStrong() void { + hal.gpio.setDrive(pin, .strong); +} +fn idfDriveWeakest() void { + oracle_gpio_set_drive(pin, 0); +} +fn ourDriveWeakest() void { + hal.gpio.setDrive(pin, .weakest); +} +fn idfPullUp() void { + oracle_gpio_pullup_en(pin); + oracle_gpio_pulldown_dis(pin); +} +fn ourPullUp() void { + hal.gpio.setPull(pin, .up); +} +fn idfPullDown() void { + oracle_gpio_pulldown_en(pin); + oracle_gpio_pullup_dis(pin); +} +fn ourPullDown() void { + hal.gpio.setPull(pin, .down); +} +fn idfPullNone() void { + oracle_gpio_pullup_dis(pin); + oracle_gpio_pulldown_dis(pin); +} +fn ourPullNone() void { + hal.gpio.setPull(pin, .none); +} +fn idfMatrixOut() void { + oracle_gpio_matrix_out(pin, 43); +} +fn ourMatrixOut() void { + hal.gpio.matrixOut(pin, 43); +} + +fn idfOdOn() void { + oracle_gpio_od_enable(pin); +} +fn ourOdOn() void { + hal.gpio.setOpenDrain(pin, true); +} +fn idfOdOff() void { + oracle_gpio_od_disable(pin); +} +fn ourOdOff() void { + hal.gpio.setOpenDrain(pin, false); +} |
