summaryrefslogtreecommitdiff
path: root/src/oracle/differ_types.zig
blob: 6f8e03c7cbbaab201318962edb3e7455f8d33fc1 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
//! 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/<name>_ref.c      external-linkage wrappers over ESP-IDF's `*_ll.h` functions
//!   src/oracle/<name>_cases.zig  a `descriptor` and a `cases` array, both of the types below
//!   src/hal/<name>.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 `<name>_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,
};