summaryrefslogtreecommitdiff
path: root/captures
diff options
context:
space:
mode:
authorGabriel Schneider <[email protected]>2026-08-25 12:40:53 -0300
committerGabriel Schneider <[email protected]>2026-08-25 12:46:51 -0300
commitf5f8068fac59b4f16046c2022c2fc7c7e447ef4c (patch)
tree2731a3ed4e51cae09e184e25778eded5fc37d1f5 /captures
downloadesp32p4-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 'captures')
-rw-r--r--captures/RF-REPRODUCTION.md98
-rw-r--r--captures/idf-recheck-chop.json67
-rw-r--r--captures/zig-tone-chop-chop.json67
3 files changed, 232 insertions, 0 deletions
diff --git a/captures/RF-REPRODUCTION.md b/captures/RF-REPRODUCTION.md
new file mode 100644
index 0000000..dd58204
--- /dev/null
+++ b/captures/RF-REPRODUCTION.md
@@ -0,0 +1,98 @@
+# Reproducing the report's RF experiment from this toolchain
+
+`04-report` section 5 is the only real SDR measurement in that document: a 25 MHz carrier on GPIO20,
+OOK-keyed with `0x4200`, detected at **+12.96 dB ± 0.16 dB** and decoded back to `0x4200` from raw IQ.
+The emitter was ESP-IDF firmware (`02-esp32p4-m3-radio`). This is the same experiment driven by this
+project's own HAL, with the report's analysis tools used unmodified so the comparison is like for
+like.
+
+`examples/rf.zig` is the firmware. `zig build -Dapp=examples/rf.zig flash`, then the report's own
+`tools/rfprobe.py` and `tools/decode_tone.py`.
+
+## What reproduced
+
+**The emitter, exactly.** LEDC on GPIO20, 1-bit duty resolution, duty 1, hpoint 0 — the
+configuration from `02-esp32p4-m3-radio/main/main.c:77-94`. The report derives the carrier from the
+divider: 1-bit resolution off the 80 MHz clock can only synthesise `80e6 × 256 / (410 × 2)` =
+24,975,609 Hz, never a round 25 MHz. This HAL computes divider **410** and reports
+**24,975,610 Hz** — the same number, from IDF's own Q10.8 arithmetic reproduced in Zig.
+
+**The source clock, measured rather than assumed.** 80,104,000 Hz, timed as 100 LEDC output periods
+against the systimer's fixed 16 MHz. Worth checking: this image runs at whatever the bootloader left
+(90.0 MHz CPU), not the 360 MHz the ESP-IDF firmware configures, so its APB being 80 MHz was an
+assumption until measured.
+
+**Register-level equivalence to ESP-IDF, on the die.** The differential harness gained a case that
+performs the whole 25 MHz / 1-bit bring-up both ways — IDF's `ledc_ll_*` on one side, this HAL on the
+other, one image, one boot — and compares the LEDC block:
+
+```
+MARK DIFF ok ledc.full_rf_config_25MHz_1bit(25) 96 words identical
+```
+
+**Behavioural equivalence at every rate the instrument can measure.** Same image, same pad, same edge
+counter:
+
+| resolution | frequency | ESP-IDF LL | this HAL |
+|---|---|---|---|
+| 2-bit | 5 MHz | 112,498 edges | 112,500 |
+| 1-bit | 10 MHz | 75,000 | 75,000 |
+| 1-bit | 25 MHz | 0 | 0 |
+
+## What did not reproduce, and why it is not this toolchain's fault
+
+**The +12.96 dB detection is gone — from the original firmware too.** ESP-IDF's own binary was
+reflashed from `02-esp32p4-m3-radio/build/` and driven by the report's own `rfprobe.py chop` with the
+report's parameters (25 MHz, gain 19.7 dB, 4 cycles, ±2 kHz):
+
+| firmware | ON segments (dB) | OFF segments (dB) | ON − OFF |
+|---|---|---|---|
+| report, section 5.2 | 19.206, 19.169, 19.206, 19.146 | 6.360, 6.252, 6.153, 6.104 | **+12.96 dB** |
+| ESP-IDF, re-run today | 6.396, 6.670, 6.401, 6.403 | 6.457, 6.659, 6.523, 6.482 | **−0.063 dB** |
+| this toolchain, today | 5.046, 5.298, 5.146, 4.999 | 5.120, 5.128, 5.014, 5.027 | **+0.05 dB** |
+
+The receive chain is not at fault: the report's own instrument validation was repeated first and an
+FM broadcast carrier sits **31.8 dB** above the span median at 97.500625 MHz, against the report's
+30.9 dB at 97.497 MHz. The dongle, the antenna and the analysis path all work.
+
+So the report's detection was conditional on a physical coupling that no longer exists — the report
+itself says the emission reached the dongle through whatever wire happened to be on JP1 pin 17. Two
+firmwares now give the same null through the same tool. That is a correction to the report, not a
+difference between toolchains: **a measurement whose apparatus is "incidental coupling" is not
+reproducible, and this one is not.**
+
+## What is out of scope by construction
+
+Wi-Fi, BLE and 802.15.4 — sections 4 and 7 of the report. The P4 has no radio; those went out over an
+ESP32-C6 across SDIO under `esp_hosted` 2.12.12 + `esp_wifi_remote` 1.6.3, a 19,000-line host stack
+plus a prebuilt coprocessor binary. None of that is low-level hardware and none of it exists here.
+Reproducing it would mean porting `esp_hosted`, not porting a HAL.
+
+## The one thing this HAL cannot yet measure, and the fix
+
+Neither implementation shows edges at 25 MHz, and that is the instrument rather than the pad. The
+counter reads `GPIO_IN` in a loop, and both the loop's cadence and the LEDC output descend from the
+same clock: 75,000 edges in 300,000 reads at 10 MHz is exactly 300,000/4, i.e. perfectly
+phase-locked. At 25 MHz the lock lands on one phase and the count is zero. Adding jitter to the loop
+did not break it.
+
+This is exactly why the report used the ADC (§5.1) and read its **min and max** rather than its mean:
+"the ADC cannot track 25 MHz, so its sampling phase is uncorrelated with the pad". Running that same
+witness against the ESP-IDF firmware today still shows both rails while the carrier runs
+(`min=0 max=3228` against `driven_high min=3362`), so the pad does toggle — under IDF, measured with
+IDF's instrument.
+
+To close it from this side, the HAL needs an instrument whose sampling is uncorrelated with the
+signal: a minimal ADC one-shot read, or PCNT. ADC is the harder of the two — a prior review found it
+among the peripherals whose LL needs `regi2c_ctrl.c` and the analog I²C master — so PCNT is the
+cheaper route to a trustworthy pulse count. Until one of them exists, "the pad toggles at 25 MHz" is
+a claim this toolchain can make about its registers and not about its output.
+
+## Incidental finding
+
+ESP-IDF's LEDC **LL** silently programs an out-of-range divider. A 1-bit 25 MHz target from the
+40 MHz XTAL needs divider 205, below the hardware minimum of 256 (`LEDC_IS_DIV_INVALID`,
+`ledc.c:114`). `ledc_ll_set_clock_divider` writes it anyway, because the range check lives one layer
+up in `ledc.c` rather than in the LL. This HAL returns `DividerOutOfRange`. The first version of the
+differential case above hit exactly this and reported four differing registers; the divergence is
+real, and this HAL is on the right side of it.
diff --git a/captures/idf-recheck-chop.json b/captures/idf-recheck-chop.json
new file mode 100644
index 0000000..bbf3eb6
--- /dev/null
+++ b/captures/idf-recheck-chop.json
@@ -0,0 +1,67 @@
+{
+ "tag": "idf-recheck",
+ "mode": "chop",
+ "center_hz": 25000000.0,
+ "rate_hz": 2048000.0,
+ "gain": 19.7,
+ "cmd_on": "T",
+ "cmd_off": "o",
+ "chop_secs": 4.0,
+ "cycles": 4,
+ "guard_s": 0.8,
+ "integration_halfband_hz": 2000.0,
+ "integration_offset_hz": 0.0,
+ "on_segment_db": [
+ 6.396,
+ 6.67,
+ 6.401,
+ 6.403
+ ],
+ "off_segment_db": [
+ 6.457,
+ 6.659,
+ 6.523,
+ 6.482
+ ],
+ "on_minus_off_db": -0.063,
+ "pooled_sd_db": 0.141,
+ "verdict": "no detection above 3 sd",
+ "marks": [
+ {
+ "t": 242717.41154511,
+ "line": "STATUS p4_mac=80:f1:b2:d3:9a:7f wifi_started=0 ap_up=0 tone_gpio=20 tone_hz=25000000 t=7614532us"
+ },
+ {
+ "t": 242721.321932845,
+ "line": "MARK TONE_CONTINUOUS_ON 25MHz square wave on gpio20 t=11527889us"
+ },
+ {
+ "t": 242725.318133708,
+ "line": "MARK TONE_OFF - t=15527901us"
+ },
+ {
+ "t": 242729.327587696,
+ "line": "MARK TONE_CONTINUOUS_ON 25MHz square wave on gpio20 t=19528418us"
+ },
+ {
+ "t": 242733.319070332,
+ "line": "MARK TONE_OFF - t=23528684us"
+ },
+ {
+ "t": 242737.328479421,
+ "line": "MARK TONE_CONTINUOUS_ON 25MHz square wave on gpio20 t=27529126us"
+ },
+ {
+ "t": 242741.320336123,
+ "line": "MARK TONE_OFF - t=31530804us"
+ },
+ {
+ "t": 242745.329732972,
+ "line": "MARK TONE_CONTINUOUS_ON 25MHz square wave on gpio20 t=35531248us"
+ },
+ {
+ "t": 242749.321956982,
+ "line": "MARK TONE_OFF - t=39531493us"
+ }
+ ]
+} \ No newline at end of file
diff --git a/captures/zig-tone-chop-chop.json b/captures/zig-tone-chop-chop.json
new file mode 100644
index 0000000..8049e78
--- /dev/null
+++ b/captures/zig-tone-chop-chop.json
@@ -0,0 +1,67 @@
+{
+ "tag": "zig-tone-chop",
+ "mode": "chop",
+ "center_hz": 25000000.0,
+ "rate_hz": 2048000.0,
+ "gain": 19.7,
+ "cmd_on": "T",
+ "cmd_off": "o",
+ "chop_secs": 4.0,
+ "cycles": 4,
+ "guard_s": 0.8,
+ "integration_halfband_hz": 2000.0,
+ "integration_offset_hz": 0.0,
+ "on_segment_db": [
+ 5.046,
+ 5.298,
+ 5.146,
+ 4.999
+ ],
+ "off_segment_db": [
+ 5.12,
+ 5.128,
+ 5.014,
+ 5.027
+ ],
+ "on_minus_off_db": 0.05,
+ "pooled_sd_db": 0.126,
+ "verdict": "no detection above 3 sd",
+ "marks": [
+ {
+ "t": 242100.648010967,
+ "line": "MARK STATUS carrier=0 divider=410(Q10.8) freq=24975610Hz src=80000000Hz pin=20 cpu_mhz_x1000=89999 t=98510620us"
+ },
+ {
+ "t": 242104.536940576,
+ "line": "MARK TONE_CONTINUOUS_ON gpio20 24975609Hz square t=102406216us"
+ },
+ {
+ "t": 242108.539328207,
+ "line": "MARK TONE_OFF gpio20 released to blink t=106406606us"
+ },
+ {
+ "t": 242112.538504666,
+ "line": "MARK TONE_CONTINUOUS_ON gpio20 24975609Hz square t=110406998us"
+ },
+ {
+ "t": 242116.539028346,
+ "line": "MARK TONE_OFF gpio20 released to blink t=114407349us"
+ },
+ {
+ "t": 242120.537367672,
+ "line": "MARK TONE_CONTINUOUS_ON gpio20 24975609Hz square t=118407744us"
+ },
+ {
+ "t": 242124.538288634,
+ "line": "MARK TONE_OFF gpio20 released to blink t=122408156us"
+ },
+ {
+ "t": 242128.539139364,
+ "line": "MARK TONE_CONTINUOUS_ON gpio20 24975609Hz square t=126408570us"
+ },
+ {
+ "t": 242132.539313173,
+ "line": "MARK TONE_OFF gpio20 released to blink t=130408947us"
+ }
+ ]
+} \ No newline at end of file