Files
MeshCore/test/test_tdeck_spi_clock_timing.py
T
mikecarper e8d1e537e4 Record T-Deck radio2 hardware timing and regression coverage
Add receive-only same-image display, SPI, modem/IQ-cache and scoped-priority experiments with checkpointed collectors. Keep rejected clock-phase correction and elevated-priority experiments out of production.

Publish sanitized timing data, Full Companion/Bluetooth/USB checks and redraw measurements with their limits. Wire the new behavioral checks into CI.
2026-10-18 23:20:50 -07:00

69 lines
2.9 KiB
Python

"""Check the HIL-only ESP32-S3 radio clock phase experiment under C++11."""
from pathlib import Path
import os
import subprocess
import tempfile
import unittest
ROOT = Path(__file__).resolve().parents[1]
class TDeckSpiClockTimingTests(unittest.TestCase):
def test_all_dividers_preserve_frequency_and_correct_only_phase_fields(self):
fixture = r'''
#include <cassert>
#include <cstdint>
#include <initializer_list>
#include "TDeckSpiClockTiming.h"
int main() {
for (uint32_t pre = 0; pre < 16; ++pre) {
for (uint32_t n = 0; n < 64; ++n) {
for (uint32_t h = 0; h < 64; ++h) {
// Vary L independently too. Include reserved bits so a phase repair
// cannot silently modify frequency, source selection or other fields.
const uint32_t l = (h * 19 + 7) & 63;
const uint32_t original = 0x12400000U | (pre << 18) | (n << 12) | (h << 6) | l;
const uint32_t corrected = TDeckSpiClockTiming::corrected(original);
assert((corrected & ~0x0fffU) == (original & ~0x0fffU));
if (n == 0) assert(corrected == original);
else {
assert((corrected & 63) == n);
assert(((corrected >> 6) & 63) == (n + 1) / 2 - 1);
assert(TDeckSpiClockTiming::corrected(corrected) == corrected);
}
// The undivided source-clock mode must remain completely untouched.
const uint32_t sourceClock = original | (1U << 31);
assert(TDeckSpiClockTiming::corrected(sourceClock) == sourceClock);
}
}
}
// 8 MHz and 10 MHz can use period=2 plus a pre-divider. Their original
// H=0,L=1 is already correct, so this correction is not a general speed fix.
for (uint32_t pre : {4U, 3U, 2U}) {
const uint32_t raw = (pre << 18) | (1U << 12) | 1U;
assert(TDeckSpiClockTiming::corrected(raw) == raw);
}
// At 80 MHz source, /5 is 16 MHz but has 2 high and 3 low source ticks:
// 25 ns/37.5 ns. /6 with no pre-divider has symmetric 37.5 ns phases.
const uint32_t divide5 = TDeckSpiClockTiming::corrected((4U << 12) | 2U);
const uint32_t divide6 = TDeckSpiClockTiming::corrected((5U << 12) | 3U);
assert(((divide5 >> 6) & 63) + 1 == 2 && (divide5 & 63) == 4);
assert(((divide6 >> 6) & 63) + 1 == 3 && (divide6 & 63) == 5);
}
'''
with tempfile.TemporaryDirectory() as directory:
cpp = Path(directory) / "clock.cpp"
binary = Path(directory) / ("clock.exe" if os.name == "nt" else "clock")
cpp.write_text(fixture, encoding="ascii")
built = subprocess.run([os.environ.get("CXX", "g++"), "-std=c++11",
"-Wall", "-Wextra", "-I", str(ROOT / "tools/hil"), str(cpp), "-o", str(binary)],
capture_output=True, text=True)
self.assertEqual(built.returncode, 0, built.stderr)
ran = subprocess.run([str(binary)], capture_output=True, text=True)
self.assertEqual(ran.returncode, 0, ran.stderr)
if __name__ == "__main__":
unittest.main()