Gemini 3.8 Flash got the same brief as every P25 audit on bench: find why this firmware struggles to decode P25 Phase 1 and Phase 2 voice on the radio’s HR-C6000, and what to do about it.
It worked in a clean room, with a fresh copy of the repository, no earlier reviews and no network, and it finished in ten minutes. I checked its claims against the code, the manual and the repository’s notes, compiled the parts it questioned, and used the firmware-in-the-loop model and CPU emulation from the earlier clean-room audits.
Overall grade
D
64 / 100 weighted
FDCBA
A buildable plan, on an invented number.
This is the review to hand an engineer. It gets both hardware causes: the I2S stream is the microphone, and the analog chain that feeds it is filtered
for speech. It is the only audit to prescribe the fix as one register the firmware already writes — 0x58 = 0x9CDD, the DMR filter set — and it
follows through with a tap, an anti-alias network, a timer-triggered ADC and DMA design, and the PLL configuration to go with it.
Then it decides how much CPU the vocoder needs by writing the number down.
15–18 ms of CPU time per 20 ms voice frame appears nowhere in the repository. The project’s own measurement is
4.36M instructions per frame, and the decoder emulates at 11–16× the 72 MHz budget, or 220–320 ms. On the invented
figure, 168 MHz leaves over 65% of CPU time available; on the real one it leaves the vocoder at five to seven times over.
It also certifies the instruments it needed to distrust. 23 of 35 claims hold, which puts it at D.
Claims that check out
23 / 35
8 overstated, mislabelled or miscounted · 4 wrong
Decode-critical issues found
2 of 4
both halves of the analog chain, with the register fix
The vocoder cost it used
15–18 ms
per 20 ms frame; measured, it is 220–320
The Phase 2 vocoder it says is missing
in the tree
mbelib’s AMBE+2 3600×2450, twice
The run, not graded: 10 min · 75 agent steps · 77 tool calls · 40K output tokens (26K reasoning) · 7.3M tokens re-read · OpenRouter API, one-shot
Headline findings
Six things to know about this review
Holds up
Both halves of the analog chain, and the register that fixes them
It is the only audit to name the fix as a register the firmware already writes. Analog FM sets the AT1846S filters with 0x58 = 0xBC85; P25 should use the DMR set, {0x58, 0x9C, 0xDD}, which is what AT1846S.c writes for DMR today. It names the HR-C6000 de-emphasis at 0x34 = 0x3C alongside it, and explains both effects correctly: baseline wander from the 300 Hz high-pass, inter-symbol interference from the de-emphasis.
One slip: it says the AT1846S applies de-emphasis too. The firmware’s own comment says that is turned off here because the C6000 does it.
AT1846S.c:99, 196, 215 · HR-C6000.c:2851
Holds up
The sample source, argued from three directions
0xE0 = 0xC9 sets bit 6, and the driver’s own line says that bit enables the Mic Input to Line in 1. The manual gives I2S to external codecs and vocoders and never routes the IF ADC to it. The bring-up log camped on −80 dBm carriers without a single sync detection.
Its conclusion — the demodulator is being fed room audio — is the right one, and it draws the right consequence: stop working on the I2S path.
The vocoder cost is invented, and the plan rests on it
Floating-point mbelib IMBE decoding consumes approximately 15–18 ms of CPU time per 20 ms voice frame. No such measurement exists in the repository. What does exist is STAGE4-TX-FEASIBILITY.md:36: 4.36M instructions per 20 ms frame, against the 1.44M a 72 MHz core can issue.
Emulated, the decoder needs 11–16× that budget. So the 2.33× from 168 MHz does not leave over 65% of CPU time available; it leaves the vocoder at five to seven times over. The one real blocker in software is written out of the review.
Neither the stock firmware’s DMR vocoder blob nor open-source mbelib supports P25 Phase 2 AMBE+2 vocoding. The blob half is right. The mbelib half is not: ambe3600x2450.c is the enhanced half-rate AMBE+2 decoder, it ships in both copies of mbelib in this tree, and the bundled DSD-FME decodes Phase 2 voice with it.
It also gives the Phase 2 vocoder rate as 4900 bps. It is 3600: 2450 bps of voice plus FEC — the number in the filename it missed.
It certifies the instruments instead of checking them
Its verdict on the software is that it is technically sound, highly compliant, and thoroughly tested because the host tests pass. The tests pass because they never compile the monitor callback that stages capture samples: the export ships the contiguous first 80 halfwords of each row while the decoder reads [j][i*2], so 158 of 160 positions differ. The parser certifies incomplete captures, pcm_starve never increments, and the test oracle shapes its waveform with the receiver’s own filter.
It also certifies the muting logic, where a group call with a non-standard manufacturer ID is never opened.
The review is certain the capture is microphone audio, and the repository is not: the stock analysis says the question remains open. The plan goes straight to a hardware modification without the carrier-keyed listening test that would settle it in an afternoon, and without fixing the capture export that any such test would run through.
It is also the second audit to rule the chip out without trying the one documented mode that emits 4FSK symbols at P25’s own rate.
Same rubric, weights and scale as every P25 audit on bench, so scores compare directly. Gemini 3.8 Flash scores high on clarity and sits mid-table on root cause, held down by coverage.
Dimension scores
Hover or focus a bar for the reasoning. Weighted total 64, grade D.
Rubric table
Dimension
Weight
Score
Points
Why
Accuracy & evidence
30%
65
19.5
23 of 35 claims hold, and every register value, map symbol and clock configuration it quotes is exact. Four are wrong, and two of those — the vocoder’s CPU cost and the missing Phase 2 codec — are the ones its conclusions rest on.
Coverage of decode problems
25%
46
11.5
The analog chain and the sample source, both complete, plus the clock gating, the RAM ceiling and one of the strongest Phase 2 sections on this task. It finds no tooling defect at all, and certifies the capture path, the test suite and the muting logic as correct.
Root cause & prioritisation
15%
74
11.1
The causal chain is the clearest any audit has written: no route from the discriminator to I2S, so the samples are microphone audio, and the front end is filtered for speech anyway. It then removes the CPU from the causal chain on a number it made up.
Fix plan & acceptance gates
15%
70
10.5
Concrete enough to build from: tap, filter network, ADC channel, DMA design, filter register, playback routing. But it has no acceptance gate before an irreversible hardware modification, and its CPU step is a clock change that doesn’t close the gap.
Originality & attribution
10%
70
7.0
No new defect, but more implementation engineering than any audit before it: the sampling chain, the PLL and wait-state configuration, the microphone-mute register, and a fixed-point vocoder proposal from outside the repository.
Clarity & calibration
5%
84
4.2
The best-presented review of the audits up to it: a failure-mode taxonomy, architecture diagrams, a comparison matrix, and a checklist a person could work through. Its calibration is the weak part — the wrong claims are stated as flatly as the verified ones.
Weighted total
100%
64
Grade scale: A ≥ 90 · B 80–89 · C+ 75–79 · C 70–74 · C− 65–69 · D 50–64 · F < 50
Receive chain
Where its findings sit
Gemini 3.8 Flash covers the front of the chain completely — the source, the filters, the clock — and the RAM ceiling at the back. The red tags are the vocoder it discounted and the instruments it certified.
S raised by Gemini 3.8 FlashN1 missed or certified as correctDashed edge: no documented connection
SThe I2S stream is microphone audio; take the discriminator tap
F1AT1846S speech filters; write the DMR set 0x58=0x9CDD
F2HR-C6000 de-emphasis at 0x34=0x3C
KSlot engine and sync hunter gate the I2S clock
N1mbelib needs 11–16× the CPU (put at 15–18 ms per frame)
N2The capture stages the wrong halfwords
N3The parser certifies incomplete captures
N4pcm_starve never counts
N5The test oracle shares the receiver’s RRC filter
N6The 8 kHz frame-clock rule in the manual
The fix it prescribes
What the DMR filter set buys
Gemini 3.8 Flash is right that the front end is configured for speech, and right about which register changes it. This is what that register is worth, modelled through the firmware’s own demodulator.
Register state for a P25 session
What trxSetModeAndBandwidth(RADIO_MODE_ANALOG, true) leaves in place, next to OpenGD77’s own settings for 4800-baud 4FSK.
Register
Monitor today (analog FM)
OpenGD77 DMR mode
Effect on C4FM
AT1846S 0x40
0x0030, low-frequency bit cleared: “so it should be cleared to receive FM”
0x0031, “allows low freq audio”
Removes the near-DC content that runs of equal symbols depend on
AT1846S 0x58
0xBC85, “enable some filters for FM e.g. high and low pass filters”
0x9CDD, “disable all filters in DMR mode”
High- and low-pass shaping of the symbol stream
AT1846S 0x44
0x06CC, AF gain 80%
0x07FF, 100%; the DM-1701 “needs higher AF gain” to decode 4FSK
Lower discriminator level into the next stage
AT1846S bandwidth
25 kHz
12.5 kHz, forced for digital
Wider noise bandwidth and deviation scaling set for ±5 kHz FM
HR-C6000 0x34
0x3C, “compressor off, de-emph on, 3 kHz audio filter”
not written; the FM value persists
−6 dB/octave from 300 Hz and a 3 kHz low-pass on the FM receive path (manual §6.2)
Sources: AT1846S.c:191–217, HR-C6000.c:2846–2854 and 2858–2875, trx.c:228–279, p25mon.c:724. Register comments are OpenGD77’s; the manual translation documents 0x34.
What the inherited 0x34=0x3C does to C4FM
Standard C4FM through each HR-C6000 receive stage alone, then the firmware’s live demodulator and framer. 20 LDUs: 180 IMBE frames possible.
De-emphasis alone, −6 dB per octave from 300 Hz, leaves no frames at all. Its 0x58 = 0x9CDD clears the AT1846S half; the 0x34 bit it names is the other half, and its plan does not write it.Data table
Audio stage
Noise-free frames · NIDs
Symbol errors
20 dB frames · NIDs
no audio filtering
180/180 · 20/20
0.0%
180/180 · 20/20
3 kHz low-pass only
180/180 · 20/20
0.0%
180/180 · 20/20
de-emphasis only
0/180 · 0/20
51.3%
0/180 · 0/20
de-emphasis + 3 kHz low-pass (0x34=0x3C)
0/180 · 0/20
49.8%
0/180 · 0/20
The CPU budget
The number it wrote, and the number the repository measured
Its case for 168 MHz depends entirely on the vocoder costing 15–18 ms per frame. A 72 MHz core issues about 1.44M instructions in 20 ms; the project’s own figure for the encoder is 4.36M, and the decoder emulates worse.
Where one second of decoding goes
The firmware’s streaming loop, compiled with its own GCC flags and newlib and run in an emulated Cortex-M4F over 2.2 s of input (12 LDUs, 108 IMBE frames, all decoded). Instruction counts become time by assuming one clock cycle per instruction, the optimistic end.
The demodulator fits: about 40% of the core, 60% at 1.5 cycles per instruction. The vocoder needs roughly 11 times the whole CPU, 16 times at 1.5. Building with -O2 changes these by under 1%, because the time is spent inside newlib’s prebuilt libm.Per-stage table
Stage
-Os, M instr/s
Share at 1.0 · 1.5 c/i
-O2, M instr/s
Resampler ×1.6
1.40
1.9% · 2.9%
1.32
RRC FIR, timing and level fit
28.00
38.9% · 58.3%
27.70
Framing, NID and FEC
0.74
1.0% · 1.5%
0.73
mbelib IMBE decode and synthesis
781.90
1,086.0% · 1,629.0%
781.60
Total
812.1
1,128% · 1,692%
811.4
CPU time for each 20 ms of speech
One call to mbelib’s IMBE decoder per frame, uvquality 1, FEC-valid codewords with chosen pitch and voicing, mean over 100 consecutive frames.
Every frame type misses its 20 ms deadline, from about 2× for the simplest frames to more than 20× for loud voiced ones. The cause is mbelib’s per-harmonic phase, which accumulates without wrapping (mbelib.c:306). Once a call has run for about half a second, nearly every voiced-band cosf argument exceeds about 201 radians, and newlib’s cosf takes its __kernel_rem_pio2f path at roughly 1,700 instructions per call instead of 40 to 100. The real firmware links that same path.Per-frame table
Frame type
Mean, M instr
Worst, M instr
Mean ms at 1.0 · 1.5 c/i
L=20, all bands unvoiced
3.00
5.02
42 · 62
L=20, typical (15 of 20 voiced)
9.68
10.23
134 · 202
L=30, typical (21 of 30 voiced)
13.97
14.76
194 · 291
Random pitch and voicing
15.86
26.17
220 · 330
L=56, all bands voiced
31.25
32.97
434 · 651
Claim check
35 claims, checked one by one
Twenty-three hold, including every register value, map symbol and clock configuration. Eight are overstated, mislabelled or miscounted. Four are wrong, and the CPU figure and the Phase 2 vocoder are load-bearing.
Item
What Gemini 3.8 Flash claims
Where
Result
Notes
§1
The HR-C6000 is a fixed-function DMR ASIC whose hardware modem is hard-coded to 4800 baud 4FSK with DMR sync words, and it cannot decode P25 in silicon
manual §5.4; HR-C6000.c:131
Holds
True for P25 framing and FEC. It does not mention that the documented one-layer mode (manual 2553–2635) demodulates 4FSK into 36-byte frames at exactly 4,800 sym/s, which is the untried bit-level tap (M5).
§1
0xE0=0xC9 sets bit 6, which connects the codec ADC to LineIn1, the microphone capsule; the I2S stream is room audio, not RF
HR-C6000.c:2964
Holds
Its one line-numbered citation, and it is exact: SPI0ClearPageRegByteWithMask(0x04, 0xE0, 0xBF, isOn ? 0x40 : 0x00) “Enable or disable the Mic Input to Line in 1”. 0xC9 has bit 6 set.
§1
The manual defines I2S only as external-codec or vocoder PCM plumbing, and Figure 6.1 never routes the IF ADC or discriminator to the I2S transmit registers
manual §4.5, §4.6, §6
Holds
Matches the manual and the stock analysis, which reaches the same conclusion from V02.03’s register use.
§1
This “fails completely at the hardware level”, as “established by” the manual and the disassembly
analysis/stock-p25/REPORT.md:175–176
Overstated
The repository’s own wording is that the question “remains open and now has stronger grounds for skepticism”. The reading is very likely right, but it is an inference from absence, not an established routing fact, and the review’s own acceptance test is missing because of it.
§1
0x36=0x02 enables analog FM feedthrough to the speaker amplifier; the monitor’s 0x36=0x00 mutes it
p25mon.c:647; HR-C6000.c:2969–2972
Holds
§1
0x26=0xFD turns the IF ADC on
p25mon.c:649
Holds
§1
The DMR AMBE+2 blob lives at 0x0807537C in STM32 flash and cannot decode IMBE
dmr_codec/codec.h:38; STM32F405VGTX_FLASH.ld:156
Holds
The address is exact and appears in the linker script, the codec header and the stock analysis.
§2
P25 Phase 1 IMBE is 7200 bps total (4400 voice + 2800 FEC), 88 bits of voice parameters in 144 bits per 20 ms frame
mbelib/imbe7200x4400.c
Holds
§2
Neither the flash blob nor mbelib supports the P25 Phase 2 half-rate AMBE+2 vocoder
mbelib/ambe3600x2450.c
Wrong
mbelib’s AMBE+2 3600×2450 is the Phase 2 vocoder, and it is in the tree twice (dm1701-p25-demo and the monitor). The bundled DSD-FME reference decodes Phase 2 voice with mbe_processAmbe3600x2450Framef (dsd_mbe2.c:67). The blob half of the claim holds; the mbelib half is backwards, and it repeats in §3.4, §5.2 and the comparison table.
The enhanced half-rate vocoder is 3600 bps: 2450 bps of voice plus 1150 bps of FEC. The file it should have found is named ambe3600x2450.c.
§3.2
The stock clock is 8 kHz (0x2F=0x0B, 0x32:0x33=0x02FF); 24 kHz needs 0x2F=0x03 with 0x32:0x33=0x00FF
p25mon.c:82–85
Holds
Both configurations are quoted exactly, including that LRCK alone is a no-op.
§3.2
The 24 kHz write must happen live on a settled codec; at startup it leaves the clock at an unrecoverable 4.5 kHz or 13 kHz
STAGE3-BRINGUP.md:208–232
Overstated
The behaviour is exactly what the bring-up log reports, but the log attributes the 4.5 kHz reading to ~56% receive-window duty rather than a corrupted PLL divider (line 250). Right symptom, invented mechanism.
§3.2
The DMR slot engine (0x40=0x43, 0x41=0x40) gates the I2S clock to about 54% duty, and the sync hunter at 0x5F re-locks onto any 4800-baud 4FSK energy
p25mon.c:650–652; HR-C6000.c:131, 416
Holds
All four register values and the duty figure match the code and its comments, including that P25 C4FM is itself 4,800 baud.
§3.2
OpenGD77’s power saving drops the C6000 PWD pin during idle; SetState(INACTIVE) only grants a 10-second reprieve, and only rxPowerSavingSetLevel(0) suppresses it
rxPowerSaving.c:106; p25mon.c:665–685
Mislabelled
The API behaviour is right (the reprieve is 12 − 2×level seconds, so 10 s at level 1), but this is listed as a live failure mode when the monitor already saves the eco level and calls rxPowerSavingSetLevel(0) at start, restoring it at stop.
§3.2
SPI0 register writes fail silently when the C6000 ISR holds SPI0inUse, so writes are dropped and clock state is mismatched
p25mon.c:622–623
Holds
The same mechanism as M2, though it does not reach the two unverified writes the audio sink makes per decoded frame.
§3.3
Analog FM sets the AT1846S 300 Hz high-pass filter with 0x58=0xBC85; C4FM through that filter causes baseline wander and eye closure
AT1846S.c:196
Holds
The register value is exact, and the modelled effect is the same direction as the firmware-in-the-loop result.
§3.3
The AT1846S and the HR-C6000 (0x34=0x3C) both apply de-emphasis
AT1846S.c:196; HR-C6000.c:2851
Mislabelled
The C6000 half is right and is the decode blocker. The AT1846S half is not: the FM register set’s own comment says “De-emphasis turned off as this is done by the HRC6000 on the MDUV380”. Its fix still removes the right filters.
§3.3
The AT1846S limiter strips the envelope, “preventing CQPSK demodulation” of simulcast systems
AT1846S.c
Overstated
A discriminator receives CQPSK/LSM with degradation rather than not at all; simulcast distortion, not the limiter, is what usually defeats it. Not decision-relevant here.
§3.4
Phase 2 is 6000 sym/s H-DQPSK outbound and H-CPM inbound in 30 ms two-slot TDMA, which the 4800-baud DMR modem cannot track
§3.4
Holds
The most accurate Phase 2 statement of the audits up to it; it avoids the 4800-baud and BPTC traps others fell into.
Floating-point mbelib IMBE decoding takes about 15–18 ms of CPU per 20 ms voice frame at 72 MHz, so utilisation approaches 100%
p25mon.c; mbelib
Wrong
No such measurement exists in the tree; the repository’s own figure is 4.36M instructions per 20 ms frame for the encoder (STAGE4-TX-FEASIBILITY.md:36), which is 303% of a 72 MHz core’s 1.44M. Emulated, the mbelib decoder needs 11–16× the CPU, i.e. 220–320 ms per frame, not 15–18.
§3.5
Static SRAM ends at _ebss = 0x2001F9FC, consuming 129,532 bytes and leaving 1,540; CCM ends at 0x1000FB78 with 1,160 bytes left
MDUV380_FW.map:31416, 36961, 38286
Holds
Every number checks against the map. The 1,540 bytes are the FreeRTOS heap and stack reservation, so the practical headroom is zero, which is how the review uses it.
§3.5
p25monRx is about 3.7 KB of CCM
p25mon.c:112
Miscounted
It repeats the source comment; sizeof(p25rx_t) compiles to 4,136 bytes.
§4.1
The demodulator is a 65-tap Q12 RRC matched filter with α = 0.2
p25_4fsk.c:13, 50
Miscounted
RRC_TAPS is 81, and both the float and Q12 tables have 81 entries. The 65 appears to come from a comment about phase wraps emitting 63/65 symbols.
§4.1
Timing recovery is a damped loop across overlapping windows holding −1000 to +1000 ppm, with a 3-iteration decision-directed regression for DC offset and symbol scale
p25_4fsk.c:190–210, 236–241; tests/p25/run.py:247
Holds
The regression, the iteration count and the ppm range are all real. It reads the loop as sound where it is ad hoc (gains 0.5 and 0.001, no lock test, no carrier AFC), which is M14.
§4.1
The NID decoder is an allocation-free BCH(63,16,23) over GF(2⁶) with p(x)=x⁶+x+1 that corrects up to 11 changed bits and is validated across all 65,536 data combinations
p25_nid.c:58–70; tests/p25/run.py:117
Holds
§4.1
FEC: Golay(24,12,8) syndrome table to 3 errors, Hamming(10,6,3), RS(36,20,17) to 8 symbols in the HDU and RS(24,12,13) to 6 in the LDU
p25_fec.c; tests/p25/signalling.py
Incomplete
Each code and bound it names is right, but it omits RS(24,16,9), the LDU1 code that corrects 4.
§4.1
The framer parses HDU/LDU1/LDU2/TDU/TDULC/TSBK with the 20-dibit link-control spacing, and “correctly mutes encrypted traffic and unknown calls, opening audio only upon validated clear-call signalling”
p25rx.c:12, 159–163, 190
Incomplete
The geometry and the 0x80 clear test are right, but the muting it certifies is the defect: a group link control with a non-standard manufacturer ID never establishes a clear call (M9), and a single failed signalling word mutes at LDU cadence (M8).
§4
The pure software stack is “technically sound, highly compliant, and thoroughly tested”, passing all host tests
tests/p25/run.py
Incomplete
The suite does pass, but it never compiles the monitor callback that stages capture samples: the export ships the contiguous first 80 halfwords of each row while the decoder reads [j][i*2], so 158 of 160 positions differ (T1), the parser certifies incomplete captures (T2), pcm_starve never increments (T3) and the test oracle shapes with the receiver’s own RRC filter (M1). Certifying the instruments is what leaves the four defects in place.
§5.1
Tap AT1846S pin 9 through a 1 µF DC block and a 1 kΩ/10 nF low-pass into STM32F405 pin 18 (PC2, ADC123_IN12), sampled by TIM2-triggered ADC1 DMA at 38.4 kHz
analysis/stock-p25/REPORT.md:175–176
Holds
The repository names the M17 mod and pin 18; the anti-alias values, the ADC channel mapping and the DMA design are the review’s own and are correct for this part.
§5.1
In P25 mode write {0x58, 0x9C, 0xDD}, the DMR filter configuration, to bypass the 300 Hz high-pass and de-emphasis
AT1846S.c:99, 215
Holds
Exactly the register set the firmware already uses for DMR, and the clearest single-line fix of any audit for the front end.
§5.1
Raising the clock to 168 MHz gives 2.33× throughput, cutting IMBE synthesis from ~16 ms to ~6.8 ms and leaving over 65% of the CPU free
Core/Src/main.c:269–274
Wrong
It rests on the 15–18 ms figure. Against the measured 11–16×, 2.33× leaves the vocoder at 4.7–6.9× the budget, so the headroom it promises does not exist. The clock change is worth making, after the vocoder is fixed.
§5.1
Replace floating-point mbelib with Pavel Yazev’s fixed-point IMBE from OP25, using SMLALD/SMUAD, in about 5 KB of RAM and 24 KB of flash
external
Unverified
The right instinct and the strongest vocoder proposal on this task, but nothing in the tree supports the footprint figures, and even a 4× fixed-point win with the clock change only reaches parity with the 11–16× gap.
§5.1
Keep the C6000 for playback: 8 kHz I2S transmit and 0xE0=0x89 to disable the microphone input
HR-C6000.c:2778, 2964
Holds
0xC9 with bit 6 cleared is 0x89, which is what the mask at 2964 does.
§5.2
Phase 2 is infeasible on this hardware: no linear I/Q path, no 6000-baud modem, no RAM headroom
§5.2
Holds
The conclusion is right for the receive path, though the vocoder row of its table is the wrong claim above.
Provenance
What was already known, and what it added
Every clean-room audit could see only the repository: its notes, code comments and the HR-C6000 manual, summarised in the first column. Gemini 3.8 Flash is the first audit to raise both halves of the analog chain and the sample source together, and one of two to raise no tooling defect at all.
Issue
Project docs in the repository
Grok 4.6 Sep 17
DeepSeek V4.1 Flash Sep 17
Muse Spark 1.3 Contributor Sep 17
GLM 5.3 Flash Sep 17
UNIONALPHA Sep 17
Qwen3.8 Flash Sep 17
Qwen3.8 Max Sep 17
GLM 5.3 Sep 17
DeepSeek V4 Pro Sep 17
Gemini 3.8 Flash Sep 17
Capture export stages the wrong I2S halfwords
absent
raised §1, first code change
absent its first experiment uses that capture
absent trusts the capture
absent relies on the capture
raised F3, fixed and verified
raised P1-2, tests miss the adapter
absent calls the capture validated
raised §2, proven with a reproducer
absent calls the capture module sound
absent certifies the capture path
Capture parser certifies an incomplete stream
absent
absent
absent
absent trusts the parser
absent relies on the parser
absent
absent
absent relies on the parser
absent
absent
absent
pcm_starve never increments
absent documented as working
absent
absent
absent relies on it
absent
raised F7
raised P1-7
absent
raised §2
absent
absent named only as a predicted symptom
No static RAM margin
partly margins still to measure
absent
raised M6, 0 bytes free
partly “nearly full”, from the docs
raised F5, byte-exact map audit
raised F6, byte-exact
partly calls the reservation headroom
absent
partly “~zero headroom”
partly from the docs, not the map
raised §3.5, exact map symbols
The I2S stream is most likely microphone audio
partly open, leaning sceptical
raised §1, 0x89 versus 0xC9
raised B1, four-value test
raised P1-1
partly open, leans towards RF
partly blocking, but never says microphone
raised P1-4, 0xE0 mic bit
raised A, the clearest case yet
raised §1, three sources
raised §1–§2, with the tap as the fix
raised §1 and §3.1
HR-C6000 de-emphasis on the capture path
absent
raised §2, bit 5 of 0x34
absent calls it benign
raised P1-2, 0x34=0x3C
raised F2, closes the eye
partly cited, called unmeasured
raised P1-3, 0x34=0x1C
raised C, with the 0x34 bit 5 fix
raised §1, tilts the eye
absent
raised §3.3, 0x34=0x3C
AT1846S FM filters, low-frequency bit, 25 kHz
partly “require characterization”
raised §2, register level
raised B2, filter register
raised P1-2, 0x58 filters
partly “voice filtering”; wrong bandwidth premise
partly cited, called unmeasured
raised P1-3, DMR 0x58 probe
absent
absent
absent
raised §3.3, with the DMR fix
0x10=0x6E hybrid state; 0x36 dual role
partly bring-up clock rules
partly misses 0x6E and the 0x36 clock gate
partly “undocumented hybrid state”
partly quiet-chip registers
raised F4, 0x10=0x80 kills the clock
partly F1, 0x6E against 0x80
partly “hybrid I2S state”
partly slot engine and eco only
partly §4.3, 0x10=0x80 hazard
partly “inconsistent hybrid state”
partly 0x36 and 0xE0, not 0x10
Manual: I2S frame clock “must be 8KHz” missed
absent
raised §3
absent quotes the paragraph, not the rule
absent
absent
raised F4
absent quotes the formulas, not the rule
absent cites the section, not the rule
absent
absent
absent
One-layer 4FSK test mode as a P25 tap
partly stock BER-test block only
raised Gate D
raised B5, exact recipe
absent dismissed
absent
partly worth a bounded test
absent ruled out at “9600 Bd”
raised step 2, a symbol source
absent “not a P25 symbol source”
absent “no raw modem mode”
absent “zero internal silicon capability”
±10% health gate versus ±1% timing clamp
absent
absent
raised B3, impact overstated
raised P1-3
raised F3
absent
absent
absent
absent
absent
absent
Fail-closed muting at LDU cadence
absent
absent late-entry mute only
absent
absent calls it an asset
absent
absent
partly “keep it”
absent
absent calls it tested
absent
absent certifies it as correct
Non-standard MFID mutes clear calls
absent
absent
absent
absent
absent
absent
absent
absent
absent
absent
absent certifies it as correct
Test waveform shares the receiver’s RRC filter
partly “synthetic RRC/AWGN” caveat
absent
partly tested it, says not to fix
partly synthetic only, wants recordings
absent would extend that model
raised F5, unquantified
absent would extend that model
partly synthetic only, not the circularity
partly synthetic only
partly “ideal RRC-shaped signal”
absent
MCU runs at 72 MHz
raised
raised in passing
absent
raised P1-5
absent
raised F7
absent
absent
raised §5
raised §7, with the OpenRTX precedent
raised §3.5, with the PLL settings
Vocoder needs 11–16× the 72 MHz CPU missed
partly decode timing unmeasured
absent “fine on a 1 ms tick”
absent “vocoder question settled”
partly unmeasured; fix order backwards
absent “in good shape”
partly deadlines “unproven”
absent “not the problem”
absent “not the problem”
partly inline, unmeasured
partly cites 4.36M, calls it 60%
absent puts it at 15–18 ms per frame
Direct discriminator tap (M17 mod)
raised
raised uncredited
raised pins, timer ADC, 48 kS/s
raised fallback, pin 9
raised fallback
raised fallback
raised fallback
raised step 5, the likely answer
raised §1 pivot
raised its central recommendation
raised §5.1, with the ADC and DMA design
Phase 2 architecture and scope
partly not implemented
partly misplaces the AMBE+2 decoder
partly voice via mbelib AMBE+2
raised with RF band limits
partly says mbelib has no AMBE+2
raised most accurate section
partly DMR and X2-TDMA parts
raised AMBE+2 present but uncalled
raised with a reference map
partly no symbol rate or sync
raised accurate on rate and slots
Two unverified SPI writes per decoded 20 ms frame
absent
absent
absent
absent
absent treats them as protection
absent
absent
absent
absent
absent
partly the SPI0inUse mechanism
Capture sessions lack epochs
absent
absent
partly measured=0 only
absent
absent
raised F9, 8 kHz under a 24 kHz header
absent
absent
absent
absent
absent
Ring and tick real-time budget
partly deadlines unproven
partly calls it fine
absent
partly overruns look like weak RF
absent
raised F7, 1 ms is a minimum
partly register stalls against the ring
absent
partly 1 ms tick as a constraint
absent
absent
Clock config 3 assumes 12,288 Hz; the codec formula gives 12,000
absent
absent
raised B3, clock model
absent
partly “guessed semantics”
absent
raised P1-6, for a different reason
absent
partly “unvalidated on hardware”
absent
absent
Clock-config writes bypass the verified SPI writer
partly SPI retry note
absent
raised M1
absent
absent
raised F9
absent
absent
raised §4.2
absent
absent
Stock squelch re-arms FM audio (0x10=0x80) during monitoring
absent assumes it can’t re-arm
absent
absent
absent
partly names squelch logic as a risk
raised F1, new
absent “fixed” by forcing squelch open
partly names the squelch path as a writer
partly hazard flagged, “unlikely”
absent
absent
Stale clear-call state releases a new call’s first frames
absent
absent
absent
absent
absent
raised F8, probe
absent
absent
absent
absent
absent
No frequency tracking; ad-hoc timing loop gains
partly a code comment calls the DC estimate biased
absent
absent
absent
absent
absent
absent
absent
absent
raised §4, new
absent reads the loop as sound
Unknown talkgroup opens audio (fail-open gating)
absent
absent
absent
absent
absent
absent
absent
absent
absent
absent
absent
Project docs: README.md, CAPABILITY-REPORT.md, STAGE3-BRINGUP.md, STAGE4-TX-FEASIBILITY.md, analysis/stock-p25/REPORT.md, analysis/capture-milestone-REPORT.md, dm1701-p25-demo/README.md and source comments. The clock-config rows were first raised by DeepSeek V4.1 Flash, the squelch and call-state rows by UNIONALPHA, and the control-loop row by DeepSeek V4 Pro.
Its plan
What to keep, change and add
Seven checklist items, most of them buildable as written. The gap is at both ends: nothing verifies the premise before the soldering iron comes out, and nothing fixes the instruments that would do the verifying.
Keep
Worth doing as written
The discriminator tap with its analog front end: AT1846S pin 9 through a DC block and an RC anti-alias filter into PC2.
The sampling chain: TIM2 at 38.4 kHz triggering ADC1 with circular DMA and a ping-pong buffer, straight into the demodulator.
0x58 = 0x9CDD for P25 receive, the firmware’s own DMR filter set.
0xE0 = 0x89 for playback, which is 0xC9 with the microphone bit cleared. The arithmetic checks out.
Change
Would cause new problems
168 MHz as the CPU answer: 2.3× against an 11–16× gap. Worth doing, after the vocoder.
Deprecating Phase 2 on the vocoder argument: the receive path is the real obstacle; the half-rate decoder is already in the tree.
“The software stack is sound”: the capture export, the parser and the starvation counter are all broken.
Add
Missing from its plan
A carrier-keyed acceptance test before the modification, so the hypothesis is measured rather than assumed.
The 0x34 write its own analysis calls for, which the checklist leaves out.
A real-time vocoder beyond the clock change: wrapped per-harmonic phase and a cosine table, or the fixed-point port it suggests.
The one-layer path, and what it would take
Manual Table 5.6 (verified line by line), with the questions that decide whether it can carry P25.
Register
Value
Manual description
0x01
xxxx 0000
IF receive mode
0x07 0x08 0x09
0x0B 0xD9 0x54
24-bit IF word, 455 kHz by default (OpenGD77 boots at 450 kHz)
0x10
0x02
One-layer mode, continuous receive; bit 5 for slot receive
0x40
0x40
Receive enable, test mode
0x41
0x41
Receive test enable
Output: 36 demodulated bytes per frame in RX RAM from 0x30, frame type in 0x51, system interrupt bit 0 when ready.
Rate fits exactly: 36 bytes every 30 ms is 9,600 bit/s, which is 4,800 symbols per second. Contiguous frames would lose nothing.
Open: Figure 5.15 is DMR burst geometry, so the mode may only emit frames after DMR sync. That’s the first thing to test.
Also needed: the AT1846S DMR register set, since the demodulator is fed by the AT1846S. And the vocoder still has to run in real time.
Phase 2
A strong section, with one wrong row
Its Phase 2 analysis is the most accurate any audit has produced: 6,000 symbols per second, H-DQPSK outbound and H-CPM inbound, 30 ms two-slot TDMA with its own sync and control channels, a limiter that removes the envelope a linear format needs, and the trunking code’s 0x33 rejection quoted at its line. Its advice — do not spend engineering effort here, use an SDR — is the honest answer.
The row that fails is the vocoder. It says neither the licensed blob nor mbelib can decode Phase 2 voice, at 4900 bps. The rate is 3600 bps, and mbelib’s ambe3600x2450.c is the decoder for it, sitting in both copies of mbelib in this repository and used by the bundled DSD-FME reference. The conclusion survives — the receive path, not the vocoder, is what makes Phase 2 infeasible here — but it reaches it through the one part that is wrong.
Against the other clean-room audits
The best-written review, and the thinnest evidence base
Gemini 3.8 Flash reads like a design document, and it is the only audit to name the register that changes both halves of the analog chain, alongside the register that changes them. It is also the only one to raise no tooling defect at all, and the only one to state a CPU measurement that does not exist. Ten minutes and 75 steps, against five hours for Qwen3.8 Max.
Same rubric and weights. Hover or focus a bar for the score.
Grok 4.6
DeepSeek V4 Pro
GLM 5.3
Gemini 3.8 Flash
Method and limits
How this was checked
I read GEMINI38FLASH_REVIEW.md in full and checked each claim against the firmware sources, the OpenGD77 drivers, the translated HR-C6000 manual and the repository’s notes. Every register value, map symbol and clock configuration it quotes was compared with the file it came from, and the figures it gave without a citation were re-derived: the CPU budget from the project’s own measurement, the Phase 2 vocoder from the code in the tree.
Before grading I audited the run: one model for all 75 steps, no network, no skills or memory, and nothing read outside its copy of the repository. It ran one-shot through OpenRouter with the same prompt, tools and sandbox as every other run. The de-emphasis, filter and CPU results come from the model and emulator used for the Grok 4.6 audit.
Filter responses are modelled, not measured.
CPU figures come from instruction counts in an emulator, not timing on the radio.
Its fixed-point vocoder footprint figures come from outside the repository and were not verified.
No firmware was flashed, and no radio was touched.
python3 -c "print(72e6*0.02)"
# 1,440,000 instructions per 20 ms at 72 MHz — the repo measured 4.36M for the encoder
ls src/p25/mbelib/ambe3600x2450.c ref-sources/mbelib-master/ambe3600x2450.c
# the Phase 2 vocoder it says is absent, in the tree twice
DM1701_REPO=/path/to/DM-1701 python3 deemph_check.py
# de-emphasis alone: 0 of 180 frames decode