//! The contract between the differential harness and a peripheral under test. //! //! Adding a peripheral to the oracle is three files and no edits to the harness: //! //! src/oracle/_ref.c external-linkage wrappers over ESP-IDF's `*_ll.h` functions //! src/oracle/_cases.zig a `descriptor` and a `cases` array, both of the types below //! src/hal/.zig this project's implementation, which is what is being tested //! //! The harness then, for every case: brings the peripheral to a known state, runs ESP-IDF's version, //! photographs the register block, restores, runs ours, photographs again, and compares. /// Everything the harness needs to test a peripheral without breaking the board. pub const Peripheral = struct { name: [*:0]const u8, /// First address of the register block, and how many 32-bit words to compare. base: u32, words: u32, /// Word offsets that must never be *read*, because reading them changes hardware state. /// /// This cannot be derived from the headers: `UART_FIFO_REG` sits at offset 0 of every UART /// block, its only field is annotated `RO`, and reading it pops the RX FIFO. A generic /// block-snapshot loop over a UART eats received bytes - including on the console. no_read: []const u32 = &.{}, /// Word offsets whose value legitimately changes between two runs: counters, FIFO depths, live /// input levels. Compared they would produce noise, so they are excluded. volatile_words: []const u32 = &.{}, /// The bus-clock enable bit that must read 1 for a snapshot to mean anything. /// /// Reading a clock-gated block does not fault and does not return zeros - it returns the last /// value latched, so two snapshots of a gated peripheral can compare *equal* while describing /// nothing. The harness checks this before every comparison and fails the case if it is clear. clock: ?Bit = null, /// How to return the peripheral to a known state between the two implementations. restore: Restore, pub const Bit = struct { reg: u32, bit: u5 }; pub const Restore = union(enum) { /// Pulse the peripheral's reset bit in HP_SYS_CLKRST. The only sound restore for a block /// with write-to-trigger or write-only fields, because it is what the datasheet defines the /// reset values against. Writing a snapshot back is *not* an option: ~10% of this chip's /// fields perform an action when written, and writing one saved word back to a UART's /// offset 0 transmits a character. reset_bit: Bit, /// A function that configures the block to a fixed state. For peripherals with no reset bit /// of their own (GPIO, IO_MUX) or where resetting would take the console with it (UART0). configure: *const fn () void, }; }; /// One operation, expressed twice: ESP-IDF's way and ours. They must be the same operation with the /// same arguments, or the comparison means nothing. pub const Case = struct { name: [*:0]const u8, /// Printed with the result, so a failure names the arguments that produced it. arg: u32 = 0, idf: *const fn () void, ours: *const fn () void, }; /// What a `_cases.zig` module must expose. pub const Suite = struct { descriptor: Peripheral, cases: []const Case, /// Run once before the suite: bring the peripheral far enough up that its registers are live. setup: ?*const fn () void = null, };