Review audit · firmware & RF
17 September 2026
DM-1701 · HR-C6000 · AT1846S · STM32F405 · P25

Grok 4.6’s P25 review, graded

Grok 4.6 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. I checked its claims against the code, the HR-C6000 manual and the repository’s own notes, and put two of them through the firmware’s demodulator.

Overall grade
C
70 / 100 weighted

Finds what erases the signal. Misses what stalls the voice.

Grok 4.6 reads the radio front end better than any clean-room audit so far. It traces what analog mode leaves switched on: the AT1846S voice filters with the low-frequency bit cleared, and HR-C6000 register 0x34 with de-emphasis on, which alone leaves zero decodable voice frames in my model. It builds the microphone case from the driver itself: analog setup writes 0xE0=0x89, “Turn off Microphone input”, and the monitor overwrites it with the mic-enable value. It also finds the manual’s 8 kHz frame-clock rule and the capture staging bug, and its plan starts with the right experiment.

It stops where the samples become voice. It calls the sample ring “fine on a 1 ms tick” and mbelib “the right vocoder”, yet emulated instruction counts put IMBE synthesis at 11–16 times the radio’s 72 MHz CPU. It also misses the circular test oracle and both muting policies, and places an AMBE+2 vocoder on a chip that has none. 21 of 30 claims hold and nothing is fabricated. That puts it at the top of the clean-room table, just into a C: two points ahead of Muse Spark 1.3 Contributor and five ahead of DeepSeek V4.1 Flash.

Claims that check out
21 / 30
7 overstated, miscited or doubtful · 2 wrong
Decode-critical issues found
3 of 4
misses the vocoder CPU wall
Voice frames with de-emphasis on
0 / 180
the 0x34 setting it flags, in my model
CPU the vocoder needs
11–16×
of 72 MHz, emulated; it calls the budget fine
Headline findings

Six things to know about this review

Holds up

It names the settings that erase C4FM

Analog mode leaves the AT1846S in its FM register set: high- and low-pass filters on (0x58=0xBC85), the low-frequency audio bit cleared (0x40=0x0030), 25 kHz. OpenGD77’s DMR set for the same 4800-baud 4FSK does the opposite. It adds the HR-C6000 side: 0x34=0x3C keeps de-emphasis on, and the monitor never clears it. Both go into its fix. In my model de-emphasis alone leaves 0 of 180 voice frames, and a high-pass stops every frame from 75 Hz.

AT1846S.c:191–217 · HR-C6000.c:2846–2854 · manual 2806 · model: standard C4FM through the firmware demodulator

Holds up

It builds the microphone case from the driver itself

trxSetModeAndBandwidth(RADIO_MODE_ANALOG) runs HRC6000SetFMRx(), which writes 0xE0=0x89, commented “Turn off Microphone input”. The monitor then writes 0xC9, the bit HRC6000SetMic() sets. So Grok 4.6 assumes I2S carries the mic until proven otherwise, predicts that its first gate will show exactly that, and stops tuning the demodulator if it does. It also sets the manual’s “must be 8KHz” frame clock against the monitor’s 24 kHz.

HR-C6000.c:2849, 2962–2964 · p25mon.c:648, 733 · sound.c:444 · manual 1217–1219

Critical miss

It waves through a vocoder that needs 11–16× the CPU

It calls the 512-sample ring “fine on a 1 ms tick; UI stalls will overrun” and software mbelib “the right vocoder”, and leaves CPU watermarks for after RF works. The 1 ms loop is a minimum, and mbelib runs inside the same pass. Emulated, one voiced 20 ms frame takes 134–458 ms of CPU at 72 MHz, so the 21 ms ring overruns on every voiced frame. Fixing the front end alone would still produce no real-time audio.

GROK46_REVIEW.md §5 and Gate C · p25mon.c:93–95, 583–610 · mbelib.c:306

Major gap

Past the front end, the review thins out

It misses the circular test oracle: the project’s channel model shapes the test transmitter with the receiver’s own RRC, so standard C4FM needs about 2 dB more than the tests show. It also misses that one failed signalling decode mutes a clear call, that a non-standard MFID never plays, the ±10% rate gate against the ±1% timing clamp, two unverified SPI writes per decoded frame, and four of the six tooling issues.

p25_4fsk.c:295 · p25rx.c:159–181 · p25mon.c:490, 574–577 · tools/p25_capture.py

Holds up

Its plan starts with the right experiment

Gate A fixes the capture to export the decoder’s own samples, then compares a keyed carrier with a mic tap. Gate B flattens the front end with the AT1846S DMR set and de-emphasis off, and judges it by the four-level histogram, not sync counts. The fallbacks are the M17 discriminator tap, DC-coupled, and the chip’s one-layer 4FSK test mode. Two values need fixing: Table 5.6 gives 0x41=0x41, not 0x01, and 0x34=0x00 may select the narrower 2.55 kHz filter. Clearing bit 5 alone is safer.

Gates A–E · manual 2589–2635, 2714–2719 · analysis/stock-p25/REPORT.md:88

Overstated

Some confident claims don’t survive checking

“Linear upsample aliases”: through the firmware’s own resampler, 24 kHz C4FM decodes exactly the frames the direct path does, with no extra symbol errors. “The on-chip AMBE+2 vocoder”: the HR-C6000 docks external vocoders and has none; DMR voice runs as DVSI software on the STM32, as the stock report it cites says. “I/Q would be required” for Phase 2 is too strong, and a quiet channel during bring-up says nothing about the sample path.

rerun: resampler_check.py · analysis/stock-p25/REPORT.md:217–225 · STAGE3-BRINGUP.md:328–331

Scorecard

How the 70 breaks down

Same rubric, weights and scale as every P25 audit on bench, so scores compare directly. Grok 4.6 leads the clean-room audits on coverage and fix plan and ties Muse Spark 1.3 Contributor on accuracy; Muse Spark edges it on root cause and clarity.

Dimension scores

Hover or focus a bar for the reasoning. Weighted total 70, grade C.

0255075100Score out of 100Accuracy & evidenceweight 30%74Coverage of decode problemsweight 25%59Root cause & prioritisationweight 15%70Fix plan & acceptance gatesweight 15%75Originality & attributionweight 10%72Clarity & calibrationweight 5%78
Rubric table
DimensionWeightScorePointsWhy
Accuracy & evidence30%7422.221 of 30 claims hold, with register values exact across HR-C6000.c, AT1846S.c and p25mon.c and nothing fabricated. Two claims are wrong, one of them misdirecting (“fine on a 1 ms tick” for a loop that overruns on every voiced frame), seven are qualified, and it gives no line numbers.
Coverage of decode problems25%5914.8The only clean-room audit to find three of the four decode-critical issues, with the fullest analog-chain finding (AT1846S filters and low-frequency bit plus the 0x34 de-emphasis bit), and adds the one-layer mode, the M17 tap and the capture staging bug. It misses the vocoder CPU wall, the circular RRC oracle, both muting policies, the rate-gate mismatch and most tooling issues.
Root cause & prioritisation15%7010.5Puts the receive chain first, in the right order: prove microphone versus RF, flatten the filters, then fix the rate, with nothing unreachable first. Held back because it waves the vocoder through and leaves CPU watermarks for last.
Fix plan & acceptance gates15%7511.2Gates A–E are ordered, with expected outcomes, stop rules and fallbacks (the M17 ADC tap, one-layer mode), and include a complete flat receive path. It has no real-time vocoder work and no independent C4FM oracle, and writes 0x34=0x00 where clearing bit 5 alone is safer.
Originality & attribution10%727.2From a clean room it adds what no repository document states: the AT1846S and 0x34 register culprits, the manual’s 8 kHz clock rule, the capture staging mismatch and one-layer mode as a symbol tap. It credits the stock-firmware report and bring-up notes, but repeats STAGE4’s 5-samples-per-symbol line and the repository’s M17 tap without credit.
Clarity & calibration5%783.9Crisp and decisive, with expected outcomes and falsification tests at every gate. Held back by confident overreach (“I/Q would be required”, “linear upsample aliases”, “the on-chip AMBE+2 vocoder”) and by 16 citations without a single line number.
Weighted total100%70Grade 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

Grok 4.6’s findings sit in the radio front end: the AT1846S, the HR-C6000, the microphone input and the I2S clock all carry amber tags. The dashed amber route is its optional one-layer path, where the HR-C6000 demodulates the 4FSK and the MCU reads bits over SPI. Everything it missed sits on the MCU side, where the samples become voice.

RADIO · ANALOG FRONT ENDSTM32F405 · 72 MHz · -OsMicrophoneLineIn1 · enabled by E0 bit 6P25 C4FM carrier12.5 kHz channel±1.8 kHz peak deviationAT1846S receiveranalog FM register setHPF + LPF on · LF audio off · 25 kHzHR-C6000 codec ADCE0 = 0xC9 · 0x34 de-emphasis onno documented RF input to I2SI2S to the MCULRCK 24 kHz (manual: 8 kHz)left slot, 16-bitHR-C6000 4FSK demodulatorone-layer test mode → RX RAM 0x30RFaudio?ADCDMA half-buffer ISRp25monI2sHalfBuffer160 frames per callbackSample ring512 samples ≈ 21 ms≤128 drained per tickResampler ×1.624 → 38.4 kHz, nominalhealth gate ±10 %RRC FIR + timing81 taps · 768/512 windowperiod clamp ±1 %p25rx framingsync · NID · LDU · FECfail-closed mutembelib IMBE≈16 M instructions/framecosf slow pathPCM ring → I2S TXradioSetAudioPath() per frame→ speaker amplifierSPI RX-RAM reader36 B per 30 ms = 4,800 sym/sproposed, not builtCapture export ‘c’ISR stages [j][0..79]→ USB → p25_capture.pydiagnostic tapdemodulated bits over SPIdibits straight to p25rx§2§2§1§3§1§4M1M2M3M4M5M6
§1 raised by Grok 4.6M1 missed by Grok 4.6path its proposed one-layer routeGrey dashed edge: no documented connection
§1I2S most likely carries the microphone: analog setup turns the mic off, the monitor turns it back on
M1IMBE synthesis needs 11–16× the 72 MHz CPU; it calls mbelib “the right vocoder”
§2AT1846S FM filters, low-frequency bit and 25 kHz; HR-C6000 0x34 de-emphasis
M2The 21 ms ring overruns on every voiced frame; it calls the tick budget fine
§3Manual: the I2S frame clock must be 8 kHz; the monitor runs 24 kHz
M3The test waveform shares the receiver’s RRC filter
§4One-layer 4FSK test mode as a symbol tap (Gate D)
M4Fail-closed muting and non-standard MFID muting
§1Capture export stages the wrong halfwords
M5±10% health gate versus ±1% timing clamp
M6Two unverified SPI writes per decoded frame
The front end, checked

Its strongest ground holds up

Grok 4.6’s register-by-register reading of the analog chain matches the code and the manual. My model shows why it matters: the de-emphasis it flags is fatal on its own, and so is any audio high-pass above a few tens of hertz, which is also why its advice to DC-couple a direct discriminator tap is right.

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.

RegisterMonitor today (analog FM)OpenGD77 DMR modeEffect on C4FM
AT1846S 0x400x0030, 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 0x580xBC85, “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 0x440x06CC, AF gain 80%0x07FF, 100%; the DM-1701 “needs higher AF gain” to decode 4FSKLower discriminator level into the next stage
AT1846S bandwidth25 kHz12.5 kHz, forced for digitalWider noise bandwidth and deviation scaling set for ±5 kHz FM
HR-C6000 0x340x3C, “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.

04590135180IMBE frames decoded, noise-free (of 180)no audio filtering180 / 1803 kHz low-pass only180 / 180de-emphasis only0 / 180de-emphasis + 3 kHz low-pass (0x34=0x3C)0 / 180
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. Grok 4.6 names both stages; whether de-emphasis sits on the I2S capture path is still unmeasured.
Data table
Audio stageNoise-free frames · NIDsSymbol errors20 dB frames · NIDs
no audio filtering180/180 · 20/200.0%180/180 · 20/20
3 kHz low-pass only180/180 · 20/200.0%180/180 · 20/20
de-emphasis only0/180 · 0/2051.3%0/180 · 0/20
de-emphasis + 3 kHz low-pass (0x34=0x3C)0/180 · 0/2049.8%0/180 · 0/20

Voice frames decoded versus audio high-pass corner

Standard C4FM through a 2nd-order Butterworth high-pass, then the firmware’s live demodulator and framer. 20 back-to-back LDUs: 180 IMBE frames and 20 NIDs possible.

  • Noise-free
  • 20 dB SNR
  • Typical voice high-pass range
VOICE HIGH-PASS RANGE0%25%50%75%100%10203550751001502003002nd-order high-pass corner (Hz, log scale)IMBE frames decodednoise-free20 dB SNR
Decoding holds up to 20 Hz, loses a quarter of its frames at 35 Hz, and fails completely from 75 Hz. Filters that reject CTCSS tones (67–254 Hz) sit well past that point, so the exact AT1846S corner, which is undocumented, doesn’t change the conclusion.
Data table
Corner (Hz)Noise-free frames · NIDsNoise-free SER20 dB frames · NIDs20 dB SER
off100% · 20/200.00%100% · 20/200.00%
10100% · 20/200.00%100% · 20/200.01%
20100% · 20/200.13%100% · 20/200.23%
3575% · 15/202.31%60% · 12/202.80%
5010% · 2/207.04%20% · 4/207.45%
750% · 0/2015.17%0% · 0/2015.37%
1000% · 0/2021.29%0% · 0/2021.70%
1500% · 0/2030.21%0% · 0/2030.58%
2000% · 0/2036.59%0% · 0/2036.19%
3000% · 0/2044.46%0% · 0/2044.67%
Two claims, rerun

A harmless resampler, and a risky register value

Grok 4.6 ran nothing, so there was no harness of its own to rerun. Instead I put two of its claims through the firmware’s demodulator and framer: that the 24 → 38.4 kHz linear resampler aliases, and that writing 0x34=0x00 simply removes the voice low-pass.

What the linear resampler costs

Standard C4FM into the firmware’s demodulator and framer, once directly at 38.4 kHz and once band-limited to the codec’s 24 kHz, then upsampled by the firmware’s own p25_resample() in 32-sample chunks, as the monitor does.

  • Direct at 38.4 kHz
  • 24 kHz through p25_resample
0100200300400500Symbol errors per 100,000 symbolsNoise-free0016 dB SNR4413 dB SNR996811 dB SNR448372
Both paths decode the same frames in every trial, and the resampled path never has more symbol errors. Its images sit 41 dB below the signal, far outside the band the matched filter passes. Dropping the resampler would save 2–3% of the CPU, not any symbols.
Data table
NoiseDirect: frames per trialDirect: symbol errorsVia 24 kHz: frames per trialVia 24 kHz: symbol errors
Noise-free1800 / 17,2001800 / 17,200
16 dB SNR180, 180, 1802 / 51,600180, 180, 1802 / 51,600
13 dB SNR180, 180, 18051 / 51,600180, 180, 18035 / 51,600
11 dB SNR180, 180, 171231 / 51,600180, 180, 171192 / 51,600

180 IMBE frames possible per trial. Three noise seeds per SNR; one noise-free trial.

What writing 0x34=0x00 could cost

The manual lets 0x34 choose a 2.55 kHz or a 3 kHz low-pass on the FM receive path and documents no bypass. Standard C4FM through each 4th-order Butterworth low-pass, then the firmware’s demodulator and framer. Corners and orders are assumptions.

  • Noise-free
  • 13 dB SNR
050010001500200025003000Symbol errors per 100,000 symbolsNo low-pass0993 kHz low-pass04302.55 kHz low-pass5812636
The 3 kHz filter that today’s 0x3C selects costs no frames. If clearing the register selects the 2.55 kHz filter, the eye starts to close: 0.6% symbol errors with no noise, and 45 of 540 frames lost at 13 dB. Clearing bit 5 alone turns de-emphasis off without that risk.
Data table
Low-passNoise-free framesNoise-free symbol errors13 dB frames per trial13 dB symbol errors
No low-pass1800 / 17,200180, 180, 18051 / 51,600
3 kHz low-pass1800 / 17,200180, 180, 180222 / 51,600
2.55 kHz low-pass180100 / 17,200162, 180, 1531360 / 51,600
The CPU budget

The wall it waved through

Grok 4.6 does think about CPU, but only about the demodulator, which fits. The emulated budget shows where the time actually goes once voice starts.

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.

0%200%400%600%800%1000%1200%Share of one CPU second at 72 MHz (-Os, 1.0 cycles per instruction)Resampler ×1.61.4 M instructions/s1.9%RRC FIR, timing and level fit28.0 M instructions/s39%Framing, NID and FEC0.7 M instructions/s1.0%mbelib IMBE decode and synthesis781.9 M instructions/s1,086%whole CPU
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/sShare at 1.0 · 1.5 c/i-O2, M instr/s
Resampler ×1.61.401.9% · 2.9%1.32
RRC FIR, timing and level fit28.0038.9% · 58.3%27.70
Framing, NID and FEC0.741.0% · 1.5%0.73
mbelib IMBE decode and synthesis781.901,086.0% · 1,629.0%781.60
Total812.11,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.

0 ms100 ms200 ms300 ms400 ms500 msCPU time per decoded 20 ms frame at 72 MHz (mean, 1.0 cycles per instruction)L=20, all bands unvoiced3.0 M instructions42 msL=20, typical (15 of 20 voiced)9.7 M instructions134 msL=30, typical (21 of 30 voiced)14.0 M instructions194 msRandom pitch and voicing15.9 M instructions220 msL=56, all bands voiced31.2 M instructions434 ms20 ms deadline
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 typeMean, M instrWorst, M instrMean ms at 1.0 · 1.5 c/i
L=20, all bands unvoiced3.005.0242 · 62
L=20, typical (15 of 20 voiced)9.6810.23134 · 202
L=30, typical (21 of 30 voiced)13.9714.76194 · 291
Random pitch and voicing15.8626.17220 · 330
L=56, all bands voiced31.2532.97434 · 651
Claim check

30 claims, checked one by one

Twenty-one hold, three of them with gaps that matter. Seven are overstated, miscited or doubtful. Two are wrong, and one of those steers the reader away from the vocoder, the blocker it misses.

ItemWhat Grok 4.6 claimsWhereResultNotes
VerdictThe HR-C6000 is a DMR baseband/codec chip with no P25 engine, no IMBE vocoder and no CQPSK/H-DQPSK modemmanual 43–49, 1181–1200HoldsThe manual never mentions P25. It docks external vocoders.
VerdictBring-up measured a stable 24 kHz clock and found no validated NIDs on air, which “matches a broken RF-to-sample path”STAGE3-BRINGUP.md:328–331OverstatedThe clock result is right: 300/300 reports at exactly 24,000 Hz. But the same note says no P25 sync had been caught while it waited for sporadic traffic. That silence says nothing about the sample path.
ChipManual Figure 6.1: FM receive runs discriminator → audio filters → de-emphasis and decompression → Audio_out, with no route to I2Smanual 2638–2676, 2794–2806Holdsanalysis/stock-p25/REPORT.md §4 reads it the same way.
ChipLRCK defaults to 8 kHz, and the vocoder frame clock I2S_FS_M “must be 8 kHz”manual 1127, 1217–1219HoldsSet against the monitor’s 24 kHz configuration in §3. No repo document mentions it.
ChipRegister 0xE0 is undocumented in the translation; HRC6000SetMic() treats bit 6 as the mic / LineIn1 enablemanual; HR-C6000.c:2962–2964Holds
ChipStock V02.03 has no discriminator→I2S setup; 0xE0=0xC9 sits in the speaker path, not a capture pathanalysis/stock-p25/REPORT.md:164–177HoldsCredited to the stock report.
§1Analog mode runs HRC6000SetFMRx (0xE0=0x89, 0x10=0x80, 0x34=0x3C, 0x26=0xFD); the monitor then writes 0xE0=0xC9, the bit HRC6000SetMic sets, plus 0x36=0x00 and 0x40/0x41/0x5F=0, and skips 0x10=0x80 because it “starves the codec clock”p25mon.c:642–656, 724, 732–734; radioHardwareInterface.c:183–185; HR-C6000.c:2846–2854HoldsExact values. The driver’s own comment on 0x89 is “Turn off Microphone input”, so the monitor undoes the mic-off setting.
§1“FM mode is not refreshed”p25mon.c:490, 810; HR-C6000.c:2967–2977IncompleteTrue as far as it goes, but radioSetAudioPath(false) writes 0x10=0x6E, a DMR value, at start and on every decoded frame. The chip ends in a hybrid state the review never names.
§1The monitor keeps HRC6000SetFmAudio(true) (0x36=0x02 + 0x10=0x80) off “to mute FM noise”p25mon.c:644–646, 711–716MiscitedThe code gives a different reason: that pair “poisons the hybrid I2S state”, and 0x10=0x80 starves the codec clock. Nothing mentions noise.
§1Until a keyed-RF versus mic-tap contrast is done, assume I2S carries codec/mic PCM, not C4FMp25mon.c:6–7, 519, 699; STAGE3-BRINGUP.md:99–101, 153–176HoldsThe stock mic path reads the same [j][i*2] slot as “Mic Audio” (sound.c:444). The contrast test is STAGE3-BRINGUP.md’s, and the review credits it.
§1The capture stages the first 80 interleaved halfwords of each half-buffer, while the decoder takes [j][i*2]p25mon.c:443, 460–466Holds158 of 160 staged positions differ from what the decoder consumed.
§2AT1846FMSettings turns on the high- and low-pass filters (0x58=0xBC85) and clears the low-frequency bit (0x40=0x0030); the DMR set (0x58=0x9CDD, 0x40=0x0031) is the front end 4FSK needs; the 25 kHz IF is not the fatal partAT1846S.c:191–217; p25mon.c:724HoldsIn the firmware-in-the-loop model, a high-pass alone stops every frame from 75 Hz. IF width isn’t modelled.
§20x34=0x3C leaves bit 5, de-emphasis (−6 dB/oct over 300–3000 Hz), on, and the monitor never clears itHR-C6000.c:2851; manual 2806; p25mon.cHoldsModel: de-emphasis alone leaves 0 of 180 voice frames.
§3Stock 8 kHz gives 1.67 samples per symbol and is gated off (nominal >= 12000); config 2 (0x2F=0x03, LRCK 0x00FF) measures 24 kHz, exactly 5 samples per symbol, the rate MMDVM’s P25 modulator usesp25mon.c:84, 575, 896, 1066; STAGE3-BRINGUP.md:328–329HoldsSTAGE4-TX-FEASIBILITY.md:24–25 already makes the 5-samples and MMDVM point; the review doesn’t credit it.
§3The monitor resamples 24 → 38.4 kHz linearly (P25_SPS=8) and runs an 81-tap RRC on 768-sample windows with a 512-sample hopp25_4fsk.h:19; p25_4fsk.c:13; Makefile:49; p25mon.c:97Holds
§3“Linear upsample aliases”, listed as a problem for the eyep25_resample.c; rerun resampler_check.pyOverstatedRerun: standard C4FM band-limited to 24 kHz, then the firmware’s own resampler in 32-sample chunks, decodes exactly the frames the direct 38.4 kHz path does, with equal or fewer symbol errors from 11 to 16 dB SNR. Its images sit 41 dB below the signal.
§3Extra CPU at 72 MHz: 38.4 kS/s through 81 taps plus a 4-level fit every 512-sample hopp25_4fsk.c; p25mon.c:597–615HoldsEmulated, the filter, timing search and level fit take 28 M instructions per second of input, 39–58% of the CPU. IMBE synthesis takes 28 times that, and the review never looks at it.
§4The monitor turns off the chip’s DMR engine (0x40=0x00, 0x5F=0x00) because slot timing gates the I2S clock (~54% duty)p25mon.c:650–652; STAGE3-BRINGUP.md:341–342Holds
§4Table 5.6 one-layer recipe: 0x10=0x02 continuous receive, 0x40 receive+test, 0x41=0x01manual 2589–2635MiscitedTable 5.6 gives 0x41 = 2’b0100 0001 (0x41). 0x41=0x01 is the stock firmware’s BER-test value (analysis/stock-p25/REPORT.md:88). The recipe also leaves out 0x01 and the 0x07–0x09 IF word.
§4One-layer mode is “the documented way to get modem symbols without DMR framing”, and the firmware never implements itmanual 2552–2586OverstatedThe firmware never writes 0x10=0x02, and the mode is a real, untried opening. But the manual documents 36-byte frames typed by DMR sync class in DMR burst geometry (Figure 5.15), not raw symbols. Whether it emits anything without DMR sync is the open question.
§5Audio plays only in P25_CALL_CLEAR; late entry mutes the first seven IMBE frames until signalling completesp25rx.c:121–123; STAGE3-BRINGUP.md:129–130HoldsIt doesn’t mention that one failed signalling decode demotes a clear call (p25rx.c:177–181).
§5512-sample ring (~21 ms), 4×32 samples per p25monTick: “Fine on a 1 ms tick; UI stalls will overrun”p25mon.c:93–95, 570–591; applicationMain.c:1451WrongThe arithmetic holds only if the loop runs every millisecond. IMBE synthesis runs inside the same pass and takes 134–458 ms of CPU per voiced frame in emulation, so the ring overruns on every voiced frame, not just on UI stalls.
§5“Software mbelib is the right vocoder”; don’t feed IMBE into the DMR AMBE pathp25rx.c:105–108; mbelib.c:306Incompletembelib is the only IMBE decoder in the tree, but as shipped it needs 11–16× the 72 MHz CPU (unwrapped harmonic phase sends newlib’s cosf down its slow path). It must be reworked before any voice plays in real time.
Phase 2Phase 2 is two-slot TDMA at 6000 sym/s, H-DQPSK outbound and H-CPM inbound, AMBE+2 3600 voice, 2V/4V bursts with ISCHPhase 2 sectionHolds
Phase 2The tree has no Phase 2 demodulator or 2V/4V parser; p25rx knows only Phase 1 DUIDs; the trunk follower rejects TDMA identifiersp25_tables.h:85–92; p25rx.c:105; p25_trunk.c:26Holds
Phase 2Discriminator audio of CQPSK is not a 4-level eye, so I/Q sampling or a CQPSK-capable modem “would be required”Phase 2 sectionOverstatedA C4FM slicer won’t work, but a discriminator can recover H-DQPSK phase steps with less margin. DSD-FME in ref-sources carries CQPSK options for FM-demodulated input. It is harder, not impossible.
Phase 2“The on-chip AMBE+2 vocoder” matches Phase 2 voice; use “mbelib AMBE+2 or the C6000 vocoder SPI”manual 43–49, 1181–1200; analysis/stock-p25/REPORT.md:217–225WrongThe HR-C6000 has no vocoder. It docks external ones such as the HR_V3000, and this radio has none. DMR voice runs through DVSI AMBE+2 software on the STM32, as the stock report the review cites says. mbelib’s AMBE+2 path shares the synthesis routine that is 11–16× too slow.
Not the bugAlready handled: DUIDs, NID status dibits and LDU length, the DMA stop flag, CCM zeroing, eco power-cycling, DMR slot gating, SPI0 silent failures, the 24 kHz latch sequencep25_tables.h:85–92; p25rx.c:347–355; p25mon.c:32, 622–623, 671, 757–768, 797, 1418IncompleteEach holds, but the SPI retry covers only p25monWriteReg. radioSetAudioPath(false) still writes 0x36 and 0x10 with no readback on every decoded frame (p25mon.c:490).
Gate B0x34=0x00 turns off the band-pass filter, compressor, de-emphasis and the “3 kHz voice LPF”manual 2714–2719, 2798–2824; rerun resampler_check.pyDoubtfulThe manual documents bit 7 (band-pass), bit 6 (compressor), bit 5 (de-emphasis) and a choice between 2.55 and 3 kHz low-pass filters, not a bypass. If 0x00 selects the 2.55 kHz filter, the model shows 0.6% symbol errors with no noise and loses 45 of 540 frames at 13 dB. Clearing bit 5 alone is the safe first step.
Gate BThe M17 DM-1701 precedent (AT1846S discriminator pin → STM32 ADC) is the honest fallback; sample ≥24 kHz, DC-coupled, no de-emphasisanalysis/stock-p25/REPORT.md:175–176, 236; CAPABILITY-REPORT.md:33, 43HoldsDC coupling matters: in the model a high-pass costs frames from about 35 Hz. The repo already proposes the tap; not credited here.
Provenance

What was already known, and what it added

Grok 4.6 worked in a clean room: no earlier reviews, only the repository’s own notes, code comments and the HR-C6000 manual. The first column shows what those already say, so the second shows what it added. It credits the stock-firmware report and the bring-up notes where it uses them, but repeats two repository points without credit. This is the first clean-room audit, so there are no earlier columns.

IssueProject docs
in the repository
Grok 4.6
Sep 17
Capture export stages the wrong I2S halfwordsabsentraised
§1, first code change
Capture parser certifies an incomplete streamabsentabsent
pcm_starve never incrementsabsent
documented as working
absent
No static RAM marginpartly
margins still to measure
absent
The I2S stream is most likely microphone audiopartly
open, leaning sceptical
raised
§1, 0x89 versus 0xC9
HR-C6000 de-emphasis on the capture pathabsentraised
§2, bit 5 of 0x34
AT1846S FM filters, low-frequency bit, 25 kHzpartly
“require characterization”
raised
§2, register level
0x10=0x6E hybrid state; 0x36 dual rolepartly
bring-up clock rules
partly
misses 0x6E and the 0x36 clock gate
Manual: I2S frame clock “must be 8KHz”absentraised
§3
One-layer 4FSK test mode as a P25 tappartly
stock BER-test block only
raised
Gate D
±10% health gate versus ±1% timing clampabsentabsent
Fail-closed muting at LDU cadenceabsentabsent
late-entry mute only
Non-standard MFID mutes clear callsabsentabsent
Test waveform shares the receiver’s RRC filterpartly
“synthetic RRC/AWGN” caveat
absent
MCU runs at 72 MHzraisedraised
in passing
Vocoder needs 11–16× the 72 MHz CPU missedpartly
decode timing unmeasured
absent
“fine on a 1 ms tick”
Direct discriminator tap (M17 mod)raisedraised
uncredited
Phase 2 architecture and scopepartly
not implemented
partly
misplaces the AMBE+2 decoder
Two unverified SPI writes per decoded 20 ms frameabsentabsent
Capture sessions lack epochsabsentabsent
Ring and tick real-time budgetpartly
deadlines unproven
partly
calls it fine

Project docs: README.md, CAPABILITY-REPORT.md, STAGE3-BRINGUP.md, STAGE4-TX-FEASIBILITY.md, analysis/stock-p25/REPORT.md, dm1701-p25-demo/README.md and source comments.

Its plan

What to keep, change and add

Grok 4.6’s gates run in the right order: prove what the I2S stream carries, flatten the front end, then demodulate. Most steps survive checking. Two register values need correcting, and the plan stops before the part that decides whether anyone hears voice.

Keep

Worth doing as written

  • Gate A first: export the decoder’s own [j][i*2] samples, then compare a keyed carrier with a mic tap. Its prediction, that the stream follows the mic, and its stop rule are right.
  • The flat front end: the AT1846S DMR register set at 12.5 kHz instead of analog FM, de-emphasis off, and 0xE0 back to the driver’s mic-off value. Judge it by a four-level histogram, not sync counts.
  • The fallbacks: the M17 discriminator tap into an STM32 ADC, DC-coupled with no de-emphasis, and the one-layer 4FSK test mode as a symbol source.
  • Leave the parser alone until nid_ok moves on a known C4FM source, and treat Phase 2 as its own project.
Change

Would cause new problems

  • 0x34=0x00: the manual offers a 2.55 or 3 kHz low-pass, not a bypass, and the narrower one costs symbols. Clear bit 5 alone, change one register at a time, and watch the codec clock.
  • Table 5.6 values: 0x41 is 0x41, not 0x01, and the recipe also needs 0x01 and the 0x07–0x09 IF word. Test first whether the mode emits frames without DMR sync.
  • CPU watermarks last: measure max_process_cycles against 20 ms on the first decoded frame, not after RF works. Emulation says it will miss by 11–16×.
Add

Missing from its plan

  • A real-time vocoder: a per-harmonic phase accumulator with wrapping and a cosine table instead of per-sample cosf. Without it, even a perfect front end yields no audio.
  • Standard C4FM test vectors (raised cosine × inverse sinc) with de-emphasis and high-pass regressions, instead of a generator that shares the receiver’s RRC.
  • Set the audio path once at start, not with two unverified SPI writes on every decoded frame.
  • Call handling: latch a clear call through one failed signalling decode, and handle non-standard MFID link control instead of muting.

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.

RegisterValueManual description
0x01xxxx 0000IF receive mode
0x07 0x08 0x090x0B 0xD9 0x5424-bit IF word, 455 kHz by default (OpenGD77 boots at 450 kHz)
0x100x02One-layer mode, continuous receive; bit 5 for slot receive
0x400x40Receive enable, test mode
0x410x41Receive 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 air interface, wrong about the vocoder

Its Phase 2 architecture holds: two-slot TDMA at 6,000 symbols per second, H-DQPSK outbound and H-CPM inbound, AMBE+2 half-rate voice, and 2V and 4V bursts with ISCH. It is right that the tree has no Phase 2 demodulator or burst parser, that the trunk follower rejects TDMA identifiers (p25_trunk.c:26), and that Phase 2 should wait until Phase 1 speaks.

Two details are off. It places “the on-chip AMBE+2 vocoder” on the HR-C6000, which docks external vocoders and has none. On this radio DMR voice runs through DVSI AMBE+2 software on the STM32, as the stock-firmware report it cites says, and that decoder already runs in real time; its other option, mbelib’s AMBE+2 path, shares the synthesis routine that is 11–16 times too slow. And “I/Q sampling would be required” is too strong: DSD-FME, bundled in ref-sources/, carries CQPSK options for FM-demodulated input, at a cost in margin.

Against the other clean-room audits

The most decode-critical findings of the three

All three clean-room audits find the microphone route and the AT1846S filters, and none finds the vocoder CPU wall. Grok 4.6 is the only one to find three of the four decode-critical issues, adding the 8 kHz rule and the capture staging bug. DeepSeek V4.1 Flash runs more code and derives the clock path but clears de-emphasis; Muse Spark 1.3 Contributor also finds de-emphasis and writes the best-calibrated prose, but trusts the broken instruments.

Muse Spark 1.3 Contributor review, graded C− (68) →

Scores by dimension

Same rubric and weights. Hover or focus a bar for the score.

  • DeepSeek V4.1 Flash
  • Muse Spark 1.3 Contributor
  • Grok 4.6
0255075100Score out of 100Accuracy & evidence727474Coverage of decode problems495459Root cause & prioritisation667270Fix plan & acceptance gates707175Originality & attribution747072Clarity & calibration748078Weighted total656870
Method and limits

How this was checked

I read GROK46_REVIEW.md, the only file the run added, and checked each claim against the firmware sources, the OpenGD77 radio drivers, the translated HR-C6000 manual, the stock-firmware analysis and the repository’s bring-up notes. Before grading I audited the run itself: one model throughout, no network, no skills or memory, and nothing read outside its copy of the repository.

The review executed nothing, so there was no harness to rerun. I put two of its claims through the firmware-in-the-loop model instead, and reused the model’s de-emphasis, high-pass and CPU results built for earlier audits of this firmware.

  • The AT1846S and HR-C6000 filter responses are modelled, not measured, and the manual doesn’t say which 0x34 bits select the 2.55 kHz filter.
  • CPU figures come from instruction counts in an emulator, not timing on the radio.
  • No firmware was edited or flashed, and no radio was touched.
DM1701_REPO=/path/to/DM-1701 python3 resampler_check.py
# via 24 kHz + p25_resample: same frames as direct, no extra symbol errors
# 2.55 kHz low-pass: 100/17,200 symbol errors noise-free, 495/540 frames at 13 dB

DM1701_REPO=/path/to/DM-1701 python3 deemph_check.py
# 3 kHz low-pass alone 180/180 · de-emphasis alone 0/180

grep -n "Turn off Microphone input\|Mic Input to Line in 1" \
  opengd77-rt3s-experiments/MDUV380_firmware/application/source/hardware/HR-C6000.c

Scripts: resampler_check.py, deemph_check.py, c4fm_sim.py and metrics.json. They need numpy and gcc.