DeepSeek V4.1 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 ran real code: the host suite, a firmware build, its own FEC harness and filter experiments. I checked its claims against the code, the manual and the repository’s notes, reran its FEC harness, and put its filter claims through the firmware’s demodulator.
Overall grade
C−
66 / 100 weighted
FDCBA
Finds the microphone and the filters. Waves de-emphasis through.
DeepSeek V4.1 Flash does careful work on the radio side. It makes the microphone the default explanation for the I2S stream and designs a half-day test to settle it, it names the AT1846S voice filters as destructive, and it builds a codec-clock model from the manual that reproduces all four measured sample rates and predicts that one untested clock setting is 2.4% off its assumed rate.
Its protocol checks hold up too: exact status-symbol phase against DSD, a clean FEC harness and a reproducible 810,252-byte build.
It stops short in three places. It calls HR-C6000 de-emphasis “by itself, benign”, yet that one stage leaves zero of 180 voice frames in my model, and its own high-pass table doesn’t reproduce. It never looks at CPU, where mbelib needs 11–16 times the 72 MHz core, and it misses the manual’s 8 kHz frame-clock rule and the capture bug its first experiment depends on.
28 of 35 claims hold. That lands it at C−, four points behind Grok 4.6 on the same clean-room protocol.
Claims that check out
28 / 35
4 miscited, overstated or doubtful · 3 wrong
Decode-critical issues found
2 of 4
misses the CPU wall and the 8 kHz rule
Voice frames with de-emphasis on
0 / 180
the stage it calls benign, in my model
Frames through a 30 Hz high-pass
180 / 180
its table says 0; first-order, in my model
Headline findings
Six things to know about this review
Holds up
It sets up the right microphone test
The monitor reads the I2S slot OpenGD77 labels “Mic Audio”, and writes 0xE0=0xC9, the value whose bit 6 feeds the microphone into LineIn1. DeepSeek calls that the default explanation and designs the test: four 0xE0 values, each against a keyed carrier and a mic tap, with a stated decision rule and a half-day time box.
It also names the AT1846S FM register set, 0x58=0xBC85 with its high- and low-pass filters, as destructive, and puts the unfiltered DMR-style setting in its fix.
From the manual’s divider formulas and a 12.288 MHz codec clock, it models the sample rate as the lower of LRCK and BCLK/64. That reproduces all four rates measured on the radio, including the no-op config, and explains that config by the manual’s I2S_CK_M > 34 × I2S_FS_M rule.
It predicts config 3 runs at 12,000 Hz, not the 12,288 Hz the code assumes: 2.4% off, inside the ±10% gate but outside what the demodulator tracks. It also finds the clock writes bypass the verified SPI writer, and that OpenGD77’s one-layer interrupt handler is an empty stub.
“FM de-emphasis is a low-pass and, by itself, benign.” The inherited 0x34=0x3C switches it on, −6 dB per octave from 300 Hz, and in my model that one stage leaves 0 of 180 voice frames. Its plan does turn de-emphasis off, but for the wrong reason.
Its measured high-pass table doesn’t reproduce either: a first-order 30 Hz high-pass decodes 180/180 through the same demodulator, where it reports none. Voice high-passes still kill C4FM, from about 100 Hz.
“Every arrow except the first is implemented and unit-tested,” and “the vocoder question is settled.” Implemented, yes, but emulated instruction counts put mbelib’s IMBE synthesis at 11–16 times the radio’s 72 MHz CPU: one voiced 20 ms frame takes 134–458 ms, so even a perfect front end produces no real-time audio.
It also quotes the manual paragraph that holds the I2S frame-clock rule, “must be 8KHz”, for its divider formula and never mentions the rule.
Its first experiment depends on a broken instrument
Step 0 settles the microphone question with the existing c capture. That capture stages the first 80 interleaved halfwords of each DMA row, while the decoder reads every other one: 158 of 160 positions differ from what the demodulator sees. The review never mentions it.
It also misses the parser that certifies incomplete captures, the pcm_starve counter that never counts, and both muting policies.
The ±10% gate against a ±1% clamp is a real mismatch. But “tolerance ≈ ±0.1%” is too tight: its own sweep decodes 89 of 90 at 0.5%, and my knee rerun decodes every frame within ±0.4%. The default 24 kHz clock is an exact crystal divide.
Its fix would hurt: the 1 Hz rate report is quantised to about ±0.67%, not 0.1%, so a ±0.5% gate would flap and reset the stream, and steering the resampler from that report adds error to a rate that is already exact. Correcting config 3 to 12,000 is right.
Same rubric, weights and scale as every P25 audit on bench, so scores compare directly. DeepSeek V4.1 Flash matches Grok 4.6 on originality and comes close on accuracy, but finds one fewer decode-critical issue and gives most of the difference back on coverage.
Dimension scores
Hover or focus a bar for the reasoning. Weighted total 66, grade C−.
Rubric table
Dimension
Weight
Score
Points
Why
Accuracy & evidence
30%
73
21.9
28 of 35 claims hold, with exact line citations, a reproducible build and FEC harness, and a clock model that matches every measured rate. Three are wrong: de-emphasis “benign” (0/180 frames), a high-pass table that doesn’t reproduce, and a 0.1% rate resolution that is really ±0.67%.
Coverage of decode problems
25%
49
12.2
Finds the microphone route and the AT1846S filter set, the one-layer mode, the M17 tap, the gate/clamp mismatch and RAM exhaustion, plus new clock-path findings. It misses the manual’s 8 kHz rule, the vocoder CPU wall, the capture staging bug its own first experiment depends on, and both muting policies.
Root cause & prioritisation
15%
66
9.9
Puts the microphone question and the filter set first, both right. Then promotes a low-impact rate gate to a third blocker, clears de-emphasis and never considers CPU.
Fix plan & acceptance gates
15%
70
10.5
A time-boxed microphone matrix with a decision rule, a well-specified M17 ADC tap and a cheap one-layer probe are decisive. But its first experiment uses a capture that stages the wrong samples, a ±0.5% gate would flap on a ±0.67% rate report, and it says not to fix the test oracle.
Originality & attribution
10%
74
7.4
Adds work no repository document has: a codec-clock model that predicts config 3’s real rate, unverified clock writes, the empty physical-layer interrupt stub, and one-layer mode as a symbol tap. It credits the stock report, bring-up notes and capability report, with two small miscitations.
Clarity & calibration
5%
74
3.7
Well organised, with severity labels, decision rules and reproduction notes. Undercut by confident overreach: “the vocoder question is settled”, “can never decode”, de-emphasis “by itself, benign”.
Weighted total
100%
66
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
DeepSeek V4.1 Flash’s findings sit in the radio front end and on the clock path: the microphone input, the AT1846S filters, the sample-rate gate and the one-layer route all carry amber tags. The red tags show what it missed or got wrong: de-emphasis on the HR-C6000, the 8 kHz rule, the capture staging bug and everything that happens once samples become voice.
B1 raised by DeepSeek V4.1 FlashN1 missed or wrongpath its proposed one-layer routeGrey dashed edge: no documented connection
B1I2S most likely carries the microphone; a four-value 0xE0 test decides it
N1HR-C6000 de-emphasis: it calls the stage benign; 0/180 frames
B2AT1846S FM filter set (0x58=0xBC85) destroys the symbol waveform
N2Manual: the I2S frame clock must be 8 kHz
B3±10% gate versus ±1% clamp, a codec-clock model, config 3 at 12,000 Hz
N3mbelib needs 11–16× the 72 MHz CPU
B5One-layer 4FSK test mode as a symbol tap; its interrupt is an empty stub
N4The capture its first experiment uses stages the wrong halfwords
M3Capture header hard-codes measured=0
N5The test waveform shares the receiver’s RRC; it says not to fix it
M4The monitor’s 0xE0=0xC9 selects the microphone
N6Fail-closed and non-standard MFID muting
N7Two unverified SPI writes per decoded frame; pcm_starve never counts
The front end, checked
Right about the filters, wrong about de-emphasis
DeepSeek V4.1 Flash reads the AT1846S register sets correctly and draws the right conclusion from them. On the HR-C6000 side it lists de-emphasis among the FM stages but calls it harmless, and its measured high-pass limits are far tighter than the firmware’s demodulator actually is.
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.
The 3 kHz low-pass is harmless because C4FM sits below 2.88 kHz. De-emphasis, −6 dB per octave from 300 Hz as the manual describes, is fatal on its own. DeepSeek V4.1 Flash calls that stage “by itself, benign”.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
Its high-pass table, rerun
Standard C4FM through one-pole filters, then the firmware’s live 768/512 demodulator and framer, 20 LDUs (180 IMBE frames). Its own harness wasn’t saved, so this reruns the claim, not the script.
Its reported table
Firmware model
The direction holds: voice-style high-passes destroy C4FM. The numbers don’t: a first-order high-pass costs nothing until about 100 Hz, where its table already reports total loss at 10–30 Hz. Its “benign” de-emphasis, a one-pole low-pass at 300 Hz, decodes nothing (last table row).Data table
First-order filter
Its table
Model: frames · NIDs
Model: symbol errors
Model at 20 dB
High-pass 10 Hz
27/90
180/180 · 20/20
0.00%
180/180
High-pass 30 Hz
0/90
180/180 · 20/20
0.03%
180/180
High-pass 100 Hz
0/90
18/180 · 2/20
7.07%
27/180
High-pass 300 Hz
0/90
0/180 · 0/20
29.90%
0/180
Low-pass 2 kHz
90/90
180/180 · 20/20
0.00%
180/180
Low-pass 300 Hz (de-emphasis)
“benign”
0/180 · 0/20
51.32%
0/180
The clock path
Its strongest new work
No repository document derives the monitor’s sample rates from the manual. DeepSeek V4.1 Flash does, and its model survives checking against every rate the bring-up notes measured on the radio.
Its codec-clock model against the radio
Codec clock 12.288 MHz. BCLK = clock / (2 × (0x2F + 1)); LRCK = clock / (2 × ({0x32, 0x33} + 1)); modelled rate = the lower of LRCK and BCLK / 64. Measured rates from the bring-up notes.
Config
0x2F
0x32:0x33
BCLK
LRCK (Hz)
Model (Hz)
Code assumes (Hz)
Measured (Hz)
0
0x0B
02FF
512 kHz
8,000
8,000
8,000
8,000 (stock)
1
0x0B
00FF
512 kHz
24,000
8,000
8,000
8,000 (no-op)
2
0x03
00FF
1,536 kHz
24,000
24,000
24,000
24,000
3
0x07
00FF
768 kHz
24,000
12,000
12,288
never measured
4
0x03
02FF
1,536 kHz
8,000
8,000
8,000
8,000
The model matches every measured rate. Configs 1 and 3 also break the manual’s rule that BCLK exceed 34 × LRCK (21× and 32×), which is why config 1 changes nothing. Config 3 was never measured, so 12,000 Hz is a prediction, but a well-founded one. Sources: manual 1126–1133 and 1216–1221, p25mon.c:80–87, STAGE3-BRINGUP.md:376–382.
The CPU budget
The wall it never looked for
The review treats everything after the input as solved. The emulated budget shows where one second of decoding goes, and why voice can’t play in real time on this CPU as the code stands.
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-eight hold, three of them with gaps that matter. Four are miscited, overstated or doubtful. Three are wrong, and two of those would steer a fix: calling de-emphasis benign, and a rate resolution that makes its ±0.5% gate look safe.
Item
What DeepSeek V4.1 Flash claims
Where
Result
Notes
§2
Host suite: python3 tests/p25/run.py exits 0 with selftest=PASS tests=111111
tests/p25/run.py
Holds
Matches the baseline run.
§2
Firmware cross-build exits 0 with no warnings; image 810,252 bytes
_estack sits at 0x2001FFFC, leaving about 1.5 KB of main stack for all nested interrupts
STM32F405VGTX_FLASH.ld:52–55; MDUV380_FW.elf
Holds
The firmware links with STM32F405VGTX_FLASH.ld, where _estack is RAM end − 4 = 0x2001FFFC (confirmed in the ELF). The 1,532 bytes above .bss hold a 512-byte heap reservation and the 1 KiB stack, so interrupts get 1–1.5 KB depending on heap use.
§2
FEC round trips: Golay and Hamming 0 failures; RS(24,8), (24,12), (36,16) 0 encode/decode failures and 0/3600 correction failures
analysis/scratch/fec_rt.c, fec_rt2.c
Holds
Rerun from its scratch files: identical. Its first RS harness reported 600 failures from a comparison bug; the corrected fec_rt2 is what it reports.
Its harness wasn’t saved, but the numbers agree with the knee rerun: every frame within ±0.4%, collapse by ±1%.
B3
Measured tolerance is about ±0.1%; a config 2–10% off passes the ±10% gate and can never decode, a silent blocker
p25mon.c:574–577, 1066–1067
Overstated
Its own sweep decodes 89 of 90 LDUs at 0.5%, and the knee rerun decodes every frame within ±0.4%. The gate/clamp mismatch is real, but the default 24 kHz clock is an exact crystal divide measured at 24,000 Hz.
§2
A first-order high-pass at 30, 100 or 300 Hz leaves 0 frames and one at 10 Hz leaves 27; a 2–4 kHz low-pass is harmless
Python harness (not saved)
Wrong
Doesn’t reproduce: standard C4FM through a first-order high-pass and the same demodulator decodes 180/180 at 10 Hz and at 30 Hz (0.03% symbol errors), and 18/180 at 100 Hz. The 2 kHz low-pass result holds, and so does the conclusion that voice high-passes kill C4FM.
§2
RRC and raised-cosine pulses at α 0.2 and 0.35 all decode 90/90 with no payload errors at 20 dB
live demodulator
Holds
Consistent with the model at 20 dB.
§6
LDU status-symbol phase matches DSD’s status_count rule exactly: NID status at wire dibit 35, then every 35 data symbols, 784 payload dibits
p25rx.c; dsd-master/src/p25p1_ldu.c:80–99
Holds
A verified non-issue, traced symbol by symbol.
B1
The monitor reads i2s_Rx_Buffer[j][i*2]; the only other reader, sound.c:444, labels that slot “Mic Audio”
p25mon.c:443–444; sound.c:444
Holds
B1
0xE0 bit 6 is the mic-into-LineIn1 enable in HRC6000SetMic, and the monitor writes 0xC9 at start and on refresh
HR-C6000.c:2962–2965; p25mon.c:648, 733
Holds
Stock receive also writes 0xC9 (stock report §5), so the bit’s meaning isn’t settled; its probe matrix allows for that.
B1
The manual gives I2S to an external codec, its Built-in Codec and external Codec sections are OMISSIS, and the FM diagram ends at Audio_out
manual 1110–1119, 2638–2676
Holds
B1
analysis/stock-p25/REPORT.md §4 concludes “no evidence found” and “treat the mic as the default hypothesis”
analysis/stock-p25/REPORT.md
Miscited
“No evidence found” is in REPORT.md §6 (line 214); “default hypothesis” is STAGE3-BRINGUP.md:174–175. The substance holds.
Step 0
Settle RF versus mic with a 0xE0 matrix (0xC9, 0x89, 0x8B, 0xC1) against a keyed carrier and a mic tap, using the existing c capture
p25mon.c; tools/p25_capture.py
Incomplete
The right experiment, but the c capture stages the first 80 interleaved halfwords rather than the decoder’s [j][i*2] samples (p25mon.c:460–466), so it needs fixing first; the review never mentions that.
B2
Analog mode loads AT1846FMSettings (0x58=0xBC85, high- and low-pass filters on); the DMR set uses 0x58=0x9CDD, all filters off
p25mon.c:724; AT1846S.c:191–217
Holds
B2
The DMR set runs 100% AF gain “because the DMR signal directly modulates…”
AT1846S.c:204–215
Miscited
That phrase is the 0x3A line’s comment about transmit. The 0x44 comment says full gain is needed to decode 4FSK from some hotspots, and that the DM-1701 needs more AF gain.
B2
The C6000 stays in its FM chain (de-emphasis, compression, 0x34[7] band-pass); de-emphasis is a low-pass and by itself benign, the killers are high-pass stages and companding
HR-C6000.c:2851; manual 2798–2824
Wrong
De-emphasis, −6 dB/oct from 300 Hz, leaves 0 of 180 frames on its own (model and rerun). The inherited 0x34=0x3C has de-emphasis on and the compressor and band-pass off.
B3
The resampler ratio and playback use guessHz, the decode gate accepts ±10%, and the symbol-period clamp is ±1%
p25monMeasuredRate is available once a second with about 0.1% resolution
p25mon.c:1151–1156
Wrong
It counts 160-sample DMA blocks over about a second, so at 24 kHz it is quantised to about ±0.67%. That is why its ±0.5% gate would flap (trap X14).
B3
With a 12.288 MHz codec clock, the manual’s formulas and rate = min(BCLK/64, LRCK) reproduce every measured rate; config 3’s 12,288 should be 12,000, and configs 1 and 3 break the manual’s I2S_CK_M > 34 × I2S_FS_M rule, which explains config 1’s no-op
mbelib exports the AMBE+2 2450 decoder, nothing calls it, and Phase 2 voice should use it
mbelib.h:63–68
Doubtful
The exports and the absent callers check out, but mbelib’s AMBE+2 path shares the synthesis routine that puts IMBE at 11–16× the CPU, and the radio’s DVSI decoder already runs AMBE+2 in real time (trap X20).
B5
Table 5.6 one-layer recipe (0x01, 0x07–0x09 IF word, 0x10=0x02, 0x40=0x40, 0x41=0x41); 36 bytes per 30 ms is 9,600 bit/s, P25’s channel rate; it may need DMR sync
manual 2552–2637
Holds
Exact values and the right caveat.
B5
OpenGD77’s physical-layer interrupt handler is an empty stub
HR-C6000.c:1265–1267, 1438–1444
Holds
New: the doorbell for one-layer mode exists and does nothing.
M1
p25monApplyClockCfg writes 0x2F, 0x32 and 0x33 with raw HRC6000SetPageRegByte, bypassing the verified writer
p25mon.c:541–552, 622–623
Holds
New, and real: those are the writes the 24 kHz latch depends on.
M2
p25monLastHealMs is both the self-heal limiter and the 5 s refresh timer
p25mon.c:1089, 1116–1118
Holds
The code’s own comment says “reused as the refresh timer”.
M3
The capture header hard-codes measured=0
p25mon_capture.c:222–223
Holds
M5
README.md:157 and CAPABILITY-REPORT.md:4 cite P25-HARDWARE-REVIEW.md, which isn’t in the directory
README.md:157; CAPABILITY-REPORT.md:4
Holds
True of the clean-room copy, from which earlier reviews were removed.
M6
The capture’s gap queue was cut from 8 to 2 entries to make the link fit
hi2s3 runs as I2S slave receive, so the C6000’s codec clock owns the sample rate
Core/Src/main.c:463–464
Holds
M8
Two RX audio mux definitions disagree: PORTC pin 5 in gpio.h versus PD9 in main.h
gpio.h:105–113; main.h:177–178
Holds
Real, but unrelated to decoding.
§6
The NID BCH test checks all 65,536 data words plus 1–11-bit corrections against an independently built generator
tests/p25/run.py:36–56, 117, 125
Holds
§6
The channel model reusing the demodulator’s pulse table is not a blocker; record that so nobody “fixes” it
§2 pulse-shape experiment
Incomplete
Not a blocker at 20 dB, but the shared RRC hides about 2 dB against standard C4FM, which is a raised cosine times inverse sinc, not the plain raised cosine it tested. The test oracle still needs fixing.
§1
“Every arrow except the first is implemented and unit-tested”; “the vocoder question is settled”
§3, §7
Incomplete
Implemented and tested, yes, but mbelib IMBE synthesis needs 11–16× the 72 MHz CPU (emulated), so voice can’t play in real time even with a perfect front end. The review never looks at CPU.
Provenance
What was already known, and what it added
Both clean-room audits could see only the repository: its notes, code comments and the HR-C6000 manual, summarised in the first column. Grok 4.6 and DeepSeek V4.1 Flash overlap on the microphone route, the AT1846S filters, one-layer mode and the M17 tap. DeepSeek adds the clock path, RAM exhaustion and the gate mismatch; Grok adds de-emphasis, the 8 kHz rule and the capture bug.
Issue
Project docs in the repository
Grok 4.6 Sep 17
DeepSeek V4.1 Flash Sep 17
Capture export stages the wrong I2S halfwords
absent
raised §1, first code change
absent its first experiment uses that capture
Capture parser certifies an incomplete stream
absent
absent
absent
pcm_starve never increments
absent documented as working
absent
absent
No static RAM margin
partly margins still to measure
absent
raised M6, 0 bytes free
The I2S stream is most likely microphone audio
partly open, leaning sceptical
raised §1, 0x89 versus 0xC9
raised B1, four-value test
HR-C6000 de-emphasis on the capture path missed
absent
raised §2, bit 5 of 0x34
absent calls it benign
AT1846S FM filters, low-frequency bit, 25 kHz
partly “require characterization”
raised §2, register level
raised B2, filter register
0x10=0x6E hybrid state; 0x36 dual role
partly bring-up clock rules
partly misses 0x6E and the 0x36 clock gate
partly “undocumented hybrid state”
Manual: I2S frame clock “must be 8KHz” missed
absent
raised §3
absent quotes the paragraph, not the rule
One-layer 4FSK test mode as a P25 tap
partly stock BER-test block only
raised Gate D
raised B5, exact recipe
±10% health gate versus ±1% timing clamp
absent
absent
raised B3, impact overstated
Fail-closed muting at LDU cadence
absent
absent late-entry mute only
absent
Non-standard MFID mutes clear calls
absent
absent
absent
Test waveform shares the receiver’s RRC filter
partly “synthetic RRC/AWGN” caveat
absent
partly tested it, says not to fix
MCU runs at 72 MHz
raised
raised in passing
absent
Vocoder needs 11–16× the 72 MHz CPU missed
partly decode timing unmeasured
absent “fine on a 1 ms tick”
absent “vocoder question settled”
Direct discriminator tap (M17 mod)
raised
raised uncredited
raised pins, timer ADC, 48 kS/s
Phase 2 architecture and scope
partly not implemented
partly misplaces the AMBE+2 decoder
partly voice via mbelib AMBE+2
Two unverified SPI writes per decoded 20 ms frame
absent
absent
absent
Capture sessions lack epochs
absent
absent
partly measured=0 only
Ring and tick real-time budget
partly deadlines unproven
partly calls it fine
absent
Clock config 3 assumes 12,288 Hz; the codec formula gives 12,000
absent
absent
raised B3, clock model
Clock-config writes bypass the verified SPI writer
partly SPI retry note
absent
raised M1
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 last two rows are new issues first raised here.
Its plan
What to keep, change and add
DeepSeek V4.1 Flash orders its plan well: answer the input question first, fix the front end or replace it with a direct ADC tap, then probe the chip’s own 4FSK mode. Most of that survives checking. Its rate-discipline step would cause new problems, and the plan never reaches the vocoder.
Keep
Worth doing as written
Step 0, the microphone test: four 0xE0 values against a keyed carrier and a mic tap, a decision rule and a half-day limit.
The unfiltered front end: the AT1846S DMR-style filter setting and the HR-C6000 out of its FM voice chain, verified on a recording.
The direct ADC tap if Step 0 fails: AT1846S pin 9 to MCU pin 18, timer-triggered ADC with DMA at 48 kS/s, DC-coupled.
The one-layer probe in parallel, and the small fixes: config 3 to 12,000 Hz, clock writes through the verified writer, measured= in the capture header.
Change
Would cause new problems
The ±0.5% gate: the rate report is quantised to about ±0.67%, so the gate would flap and reset the stream. Keep the exact nominal ratio for config 2 and report the deviation.
A resampler steered by the measured rate: the crystal-derived 24 kHz rate is already exact; a quantised estimate only adds error.
“Record that the shared RRC is fine”: it hides about 2 dB. Test with standard C4FM (raised cosine × inverse sinc) instead.
Phase 2 voice via mbelib AMBE+2: same synthesis routine as the 11–16× CPU wall. Assess the DVSI decoder the radio already runs.
Add
Missing from its plan
Fix the capture first: stage the decoder’s own [j][i*2] samples, or Step 0 measures the wrong stream.
De-emphasis off on purpose: clear 0x34 bit 5, and treat it as a decode killer, not a detail.
A real-time vocoder: a per-harmonic phase accumulator with wrapping and a cosine table. Without it, no audio.
The manual’s 8 kHz rule: 24 kHz runs, but outside the documented operating point; keep that in the Step 0 evidence.
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
Right on the waveform, wrong on the voice route
Its Phase 2 description holds: two-slot TDMA at 6,000 symbols per second, H-DQPSK, AMBE+2 voice, and none of it in the tree. Its DSD-FME citations are exact, and starting Phase 2 with a RAM budget is sensible advice on a radio whose SRAM is full and whose CCM has 1,160 bytes free.
Its voice route is not. The unused AMBE+2 entry points it found in mbelib share the synthesis routine that puts IMBE at 11–16 times this CPU, while the radio already runs DVSI’s AMBE+2 decoder for DMR in real time. That decoder’s entry points and licensing are the real open question.
Against Grok 4.6
Same protocol, a different half of the chain
Both clean-room audits find the microphone route and the AT1846S filters, both propose one-layer mode and the M17 tap, and both miss the vocoder CPU wall. Grok 4.6 also finds de-emphasis, the 8 kHz rule and the capture bug. DeepSeek V4.1 Flash runs more code, derives the clock path from the manual and makes fewer errors per claim, but clears de-emphasis. An earlier DeepSeek V4.1 Flash run on the original task delegated work to Muse Spark 1.3 subagents, so its grade does not represent this model; this run replaces it.
Same rubric and weights. Hover or focus a bar for the score.
Grok 4.6
DeepSeek V4.1 Flash
Method and limits
How this was checked
I read DEEPSEEK41_CLEAN_REVIEW.md and its scratch folder, then checked each claim against the firmware sources, the OpenGD77 radio drivers, the translated HR-C6000 manual, DSD and DSD-FME, and the repository’s notes. Before grading I audited the run: one model for all 88 calls, no network, no skills or memory, and nothing read outside its copy of the repository.
I rebuilt and reran its FEC harness unchanged, and put its filter claims through the same firmware-in-the-loop model used for the Grok 4.6 audit, which also supplies the de-emphasis and CPU results.
Filter responses are modelled, not measured, and its own filter harness was not saved, so the rerun tests its claim rather than its script.
Config 3’s 12,000 Hz rate is a model prediction; nobody has measured it.
CPU figures come from instruction counts in an emulator, not timing on the radio. No firmware was flashed and no radio was touched.