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,
};
|