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

GLM 5.3 Flash’s P25 review, graded

GLM 5.3 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 built the firmware and read its linker map. I checked its claims against the code, the map, the manual and the repository’s notes, and used the firmware-in-the-loop model and CPU emulation from the earlier clean-room audits.

Overall grade
D
56 / 100 weighted

Audits the memory to the byte. Misreads the receive path.

GLM 5.3 Flash’s strongest work is on memory. It reads the linker map and gets every figure right: 0 bytes of SRAM free, 1,160 bytes of CCM, which stock modules hold it, and about 22 KB of DMR-codec and satellite buffers a P25 build could reclaim. It also keeps the unverified sample source as the first blocker, says de-emphasis closes the 4-level eye, finds the hybrid 0x10=0x6E clock state and the ±10% gate against the ±1% timing clamp.

Its receive-path reasoning is weaker. It says the monitor never sets a bandwidth (it forces 25 kHz), puts the HR-C6000’s 0x34 on the AT1846S, leans towards RF on the microphone question, and would gate decoding at ±1% on a rate report quantised to ±0.67%. It never looks at CPU, where mbelib needs 11–16 times the 72 MHz core. 17 of 28 claims hold. That puts it at D, last of the four clean-room audits.

Claims that check out
17 / 28
7 overstated, mislabelled, miscounted or doubtful · 4 wrong
Decode-critical issues found
1 of 4
microphone question left open; misses CPU and the 8 kHz rule
SRAM free after the link
0 bytes
its map audit, exact to the byte
CPU the vocoder needs
11–16×
of 72 MHz; it calls the chain “in good shape”
Headline findings

Six things to know about this review

Holds up

It audits the memory to the byte

From the linker map: SRAM .data 17,000 + .bss 112,532 + 1,540 bytes of heap and stack reservation fills all 131,072 bytes, and CCM has 1,160 bytes left. It names who holds CCM: uiNotification 38,400, sound 5,824, uiUtilities 4,608, SPI_Flash 4,096, voicePrompts 3,788, and the whole P25 monitor just 6,016. Its reclaim candidates check out too: about 10 KB of DMR codec buffers and 12 KB of satellite state, and the unused AMBE objects are already discarded by the linker.

build/MDUV380_FW.map · STM32F405VGTX_FLASH.ld · Core/Inc/FreeRTOSConfig.h:71

Holds up

It keeps the right question first

The unverified sample source is its first blocker, with the stock analysis credited, the carrier-keyed capture test first and the M17 tap as fallback. It says de-emphasis closes the 4-level eye in a way the demodulator’s linear fit can’t undo, which my model confirms: 0 of 180 frames. It also gets the hybrid clock state right (0x10=0x6E keeps the codec clocking, 0x10=0x80 kills it), and finds the ±10% decode gate against a ±1% timing clamp.

analysis/stock-p25/REPORT.md §4 · p25mon.c:571–581, 650–652 · p25_4fsk.c:187–210, 240–241 · STAGE3-BRINGUP.md:355–357

Critical error

Its analog fixes rest on wrong premises

It says the monitor “inherits whatever the current VFO channel happens to be” and “never asserts a bandwidth”, then proposes forcing 25 kHz. trxSetModeAndBandwidth(RADIO_MODE_ANALOG, true) already sets exactly that. It files the HR-C6000’s 0x34 under the AT1846S, never names 0x34=0x3C or the AT1846S 0x58 filters, and argues the stream is probably RF because mic audio “would not produce plausible 4-level carriers”, though no 4-level signal has ever been seen on it.

p25mon.c:724 · trx.c:262–263 · HR-C6000.c:2851 · AT1846S.c:191–217 · STAGE3-BRINGUP.md:419–424

Critical miss

It never looks at CPU or the manual’s clock rule

“The software decode chain … is in good shape.” 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 the decoder falls behind on every voiced frame whatever the front end does. It also never mentions the manual’s rule that the I2S frame clock “must be 8KHz”, against the monitor’s 24 kHz.

GLM53FLASH_REVIEW.md §1 · mbelib.c:306 · manual 1217–1219

Wrong

Its rate fix and Phase 2 inventory don’t hold

It would gate decoding at ±1% or steer the resampler from the measured rate, which it says has “~0.004% resolution”. That report counts 160-sample DMA blocks, about ±0.67% at 24 kHz, so the gate would flap. It says “the DMA still stops every ~3–15 s”, quoting a build-21 note the same document later resolves. And it says the bundled mbelib has “no Phase 2 AMBE+2 decoder”: ambe3600x2450 is exactly that codec.

p25mon.c:571–581, 1151–1156 · STAGE3-BRINGUP.md:301, 328–329 · mbelib/ambe3600x2450.c

Major gap

It trusts the instruments

Its first step relies on the c capture and tools/p25_capture.py, but the capture stages the wrong halfwords (158 of 160 positions differ from what the decoder reads) and the parser certifies incomplete streams. pcm_starve never counts. It proposes validating fixes against the project’s channel model, which shares the receiver’s own RRC filter, and treats the per-frame radioSetAudioPath(false) writes as protection rather than two unverified SPI writes per frame.

p25mon.c:123, 443, 460–466, 490, 517 · tools/p25_capture.py · p25_4fsk.c:295

Scorecard

How the 56 breaks down

Same rubric, weights and scale as every P25 audit on bench, so scores compare directly. GLM 5.3 Flash trails the other clean-room audits on every dimension, most of all on coverage.

Dimension scores

Hover or focus a bar for the reasoning. Weighted total 56, grade D.

0255075100Score out of 100Accuracy & evidenceweight 30%64Coverage of decode problemsweight 25%34Root cause & prioritisationweight 15%62Fix plan & acceptance gatesweight 15%62Originality & attributionweight 10%58Clarity & calibrationweight 5%68
Rubric table
DimensionWeightScorePointsWhy
Accuracy & evidence30%6419.217 of 28 claims hold, and its linker-map audit is exact to the byte. Four are wrong and they steer the fixes: the monitor “never asserts a bandwidth” (it forces 25 kHz), the rate report has “~0.004% resolution” (it is ±0.67%), mbelib has “no Phase 2 AMBE+2 decoder”, and a DMA stall the bring-up notes later resolved.
Coverage of decode problems25%348.5Finds de-emphasis as an eye-closer, the hybrid 0x10 clock state, the gate/clamp mismatch, the M17 tap and a thorough memory picture, and keeps the microphone question open. It misses the vocoder CPU wall, the 8 kHz rule, one-layer mode, the test oracle, both muting policies and every tooling bug, and never names the AT1846S filter register.
Root cause & prioritisation15%629.3Puts the unverified sample source first and analog conditioning second, both right, but leans towards RF on the microphone question and gives clock robustness and memory high priority while the vocoder that cannot run in real time goes unexamined.
Fix plan & acceptance gates15%629.3The carrier-keyed test first, the M17 tap as fallback and concrete memory reclaims are sound. Its other fixes rest on wrong premises: forcing a 25 kHz bandwidth the code already sets, a ±1% gate on a ±0.67% rate report, and an inverse-emphasis filter instead of clearing 0x34 bit 5, with no vocoder or oracle work.
Originality & attribution10%585.8Most of its case restates the stock report and bring-up notes, with credit. What it adds is the linker-map audit (CCM users, reclaimable buffers, discarded AMBE objects) and the gate/clamp mismatch.
Clarity & calibration5%683.4Clearly structured, with severity labels and an honest “plausible, not proven” on the sample source, but confident about things that are wrong: “never asserts a bandwidth”, “~0.004% resolution, plenty”, “no Phase 2 AMBE+2 decoder”.
Weighted total100%56Grade 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

GLM 5.3 Flash’s findings sit on the HR-C6000 clock and filter state and on the sample-rate gate; its memory audit sits outside the chain. The red tags cover more of the chain than the amber ones: the microphone question it leans away from, the AT1846S filter register it never names, the vocoder, the instruments and the muting policy.

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-bitRFaudio?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 amplifierCapture export ‘c’ISR stages [j][0..79]framed hex over USBUSB CDC consoleshared with 1 Hz reportp25_capture.pyparser on the hostdiagnostic tapF2F4F3N1N2N3N4N5N6N7N8N9
F2 raised by GLM 5.3 FlashN1 missed or misreadDashed edge: no documented connection
F2De-emphasis closes the 4-level eye
N1Leans towards RF on the microphone question
F4Hybrid 0x10=0x6E clock state; 0x10=0x80 kills the clock
N2AT1846S 0x58 filters never named; wrong bandwidth premise
F3±10% decode gate versus ±1% timing clamp
N3Manual: the I2S frame clock must be 8 kHz
F5Memory: 0 bytes of SRAM free, reclaim candidates (outside the chain)
N4mbelib needs 11–16× the 72 MHz CPU
N5The capture stages the wrong halfwords
N6The parser certifies incomplete captures
N7pcm_starve never counts; two unverified SPI writes per frame
N8Fail-closed and non-standard MFID muting
N9The test waveform shares the receiver’s RRC
The front end, checked

Right that de-emphasis matters, wrong about where it lives

GLM 5.3 Flash is right that the analog chain distorts C4FM and that de-emphasis is the damaging stage. The register table shows what it gets wrong: the monitor already forces 25 kHz, de-emphasis is HR-C6000 register 0x34, and the AT1846S filters sit in 0x58.

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, is fatal on its own. Clearing 0x34 bit 5 removes it outright, simpler and cleaner than the inverse-emphasis filter GLM 5.3 Flash proposes.
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
The memory audit

Its strongest ground

GLM 5.3 Flash reads the linker map correctly and turns it into a budget: no SRAM left, 1,160 bytes of CCM, and about 22 KB of buffers a P25-only build could reclaim.

Static memory after linker reservations

Fresh ELF section table: _ebss = 0x2001F9FC, end = 0x2001FA00, plus 0x600 of heap and stack reservations = 0x20020000.

  • .data / .ccmram
  • .bss
  • newlib heap + alignment
  • main stack
0%25%50%75%100%Share of region (%)SRAM128 KiB0 bytes freeCCM RAM64 KiB1,160 bytes free
Link success is not runtime headroom: task stacks come from the 20,480-byte FreeRTOS heap inside .bss, and interrupts share the 1 KiB main stack.
The CPU budget

The wall it never looked for

The review calls the decode chain “in good shape” and budgets only the filter. The emulated budget shows where one second of decoding goes.

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

28 claims, checked one by one

Seventeen hold, two of them with gaps that matter. Seven are overstated, mislabelled, miscounted or doubtful. Four are wrong, and three of those would steer a fix: the bandwidth, the rate resolution and the missing AMBE+2 decoder.

ItemWhat GLM 5.3 Flash claimsWhereResultNotes
§1Host suite passes (selftest=PASS tests=111111) and the firmware builds to an 810,252-byte imagetests/p25/run.py; make allHoldsMatches the baseline run and build.
§2The HR-C6000’s DMR slot engine and sync hunter are turned off (0x40/0x41=0x00, 0x5F=0x00) because the hunter locks onto any 4FSK and gates the I2S clockp25mon.c:650–652Holds
§2The stock AMBE+2 “codec” is a DVSI binary on the MCU and DMR-onlydmr_codec/codec_interface.c; analysis/stock-p25/REPORT.md §7HoldsPlaced on the right chip, which not every audit manages.
F1The manual documents I2S as external-codec and vocoder plumbing with no discriminator route; 0xE0 is undocumented, and stock uses 0xC9 only in the speaker pathanalysis/stock-p25/REPORT.md §4HoldsCredited to the stock report.
F1Bring-up measured a stable 24 kHz capture but never received a P25 sync over the air; the console labels the source UNVERIFIEDSTAGE3-BRINGUP.md:328–331, 419–424; p25mon.c:519Holds
F1Counter-evidence: the stream has “real content” and the decoder “decodes nothing that isn’t P25”, so microphone or silence “would not produce plausible 4-level carriers”F1DoubtfulA non sequitur: no 4-level signal has ever been seen on this stream, and a decoder that rejects non-P25 input says nothing about where the samples come from. The stock mic path reads the same I2S slot, and 0xC9 sets the mic bit.
F1First, run the carrier-keyed capture test with c and tools/p25_capture.py, then demodulate a real capture offline; keep the M17 tap as the fallbackF1 plan; §5 step 1IncompleteThe right order, but the capture stages the first 80 interleaved halfwords rather than the decoder’s [j][i*2] samples, and the parser certifies streams with missing chunks. Both need fixing first.
F2The monitor inherits the VFO channel’s 12.5 or 25 kHz bandwidth and “never asserts a bandwidth”p25mon.c:724WrongtrxSetModeAndBandwidth(RADIO_MODE_ANALOG, true) sets 25 kHz explicitly (trx.c:262–263). Its fix, “force 25 kHz”, is what the code already does.
F2De-emphasis tilts the 4-level eye, and the demodulator’s single linear DC/gain fit cannot undo a frequency-dependent tiltp25_4fsk.c:187–210HoldsIn the firmware model, de-emphasis alone leaves 0 of 180 frames.
F2“A 12.5 kHz IF filter is at the edge of what C4FM needs” and may add inter-symbol interferenceF2OverstatedP25 C4FM is a 12.5 kHz-channel emission, and OpenGD77’s DMR set receives the same 4800-baud 4FSK at 12.5 kHz.
F2Characterise the “AT1846S de-emphasis/filter registers”, for which the stock analog RX helper writes 34=0x98 and aux 0x54=0x78F2 plan; REPORT.md:86Mislabelled0x34 is the HR-C6000’s FM register: bit 5 is the de-emphasis the monitor inherits as 0x3C, and stock’s 0x98 is also an HR-C6000 write. The AT1846S voice filters live in its 0x58 register. It never names either register as the culprit.
F2The capability report already flags that the AT1846S filter settings need characterising for P25CAPABILITY-REPORT.md:14HoldsCredited.
F3Decoding starts within ±10% of the nominal rate, the resampler deliberately uses the nominal ratio, and the timing loop clamps the symbol period at ±1%p25mon.c:571–581; p25_4fsk.c:240–241Holds
F3The documented failure modes (bad latches, heals, “broken for the whole session”) make a 1–10% rate error a realistic stateF3OverstatedDocumented bad latches land at 8 kHz or fail outright, below the 12 kHz decode gate, and the working clock is a crystal divide measured at exactly 24,000 Hz. The mismatch is real but latent.
F3Fix: gate decoding at ±1%, or steer the resampler from the measured rate, since “the 1 Hz measurement has ~0.004% resolution, plenty”F3 fixWrongThe rate report counts 160-sample DMA blocks over about a second, so at 24 kHz it is quantised to about ±0.67%, and the code says it is not a symbol-clock estimate. A ±1% gate on that number would flap and reset the stream.
F4The refresh never re-asserts 0x10=0x6E, which is only rewritten as a side effect of radioSetAudioPath(false) while audio flows, so any stock FM-audio call during silence kills the clockp25mon.c:490, 642–656; HR-C6000.c:2967–2979IncompleteThe hybrid-state facts are right. But 0x6E is also written at start (p25mon.c:810), and the monitor forces squelch open so the stock re-arm can’t fire (p25mon.c:711–716). The per-frame writes it leans on go out with no readback.
F4“The DMA still stops every ~3–15 s” and each heal resets the streamSTAGE3-BRINGUP.md:301WrongOutdated: that is a build-21 note. The same document later verifies 300/300 reports at exactly 24,000 Hz with zero heals over five minutes (lines 328–329).
F4hrc6000Task isn’t suspended, which the bring-up notes list as the next stepSTAGE3-BRINGUP.md:359–363HoldsRestates the bring-up notes, with credit.
F5SRAM .data 17,000 + .bss 112,532 + 1,540 of heap and stack = 131,072 bytes, 0 free; CCM 64,376 of 65,536build/MDUV380_FW.mapHolds
F5The largest CCM users are uiNotification 38,400, sound 5,824, uiUtilities 4,608, SPI_Flash 4,096 and voicePrompts 3,788 bytes; the whole P25 monitor takes 6,016build/MDUV380_FW.mapHoldsExact to the byte.
F5Flash: 810,252 bytes used of about 1,016,064 usable, with the 48 KB bootloader preservedSTM32F405VGTX_FLASH.ld:70MiscountedThe application region is 1024K − 48K = 999,424 bytes.
F5The linker’s --gc-sections drops mbelib’s AMBE decoders (ambe3600x2400.o, ambe3600x2450.o) from the imagebuild/MDUV380_FW.mapHoldsThey appear only among the discarded sections.
F5Reclaim candidates: codec.o holds about 10 KB of DMR buffers and satellite.o about 11,900 bytes of .bssbuild/MDUV380_FW.mapHoldsThe map gives 10,476 and 11,972 bytes.
F6The bundled mbelib has “no Phase 2 AMBE+2 decoder”, so Phase 2 voice needs “new DSP code, no mbelib support”F6Wrongambe3600x2450 is the AMBE+2 half-rate codec P25 Phase 2 uses, and DSD-FME decodes Phase 2 voice with it. The real limits are mbelib’s slow synthesis and the DVSI decoder the radio already runs.
F6No TDMA layer or CQPSK demodulator exists, and the trunk follower rejects TDMA grantsp25_trunk.c:26; p25_4fsk.hHolds
F6“CQPSK requires an I/Q sample path”F6OverstatedDSD-FME, bundled in ref-sources, carries CQPSK options for FM-demodulated input. It is harder, not impossible.
F7Playback converts 8 kHz PCM to the nominal codec rate; the brute-force Golay search is negligible CPU; the linear resampler is adequate at 24 kS/sp25mon.c:394–430; p25_fec.c:62–74; p25_resample.cHoldsThe resampler point matches the firmware-model rerun.
F7The host suite checks NID BCH, Golay/Hamming/RS, HDU/LC/ESS and TSBK/MBT CRCs “against independent reference encoders”tests/p25/OverstatedGolay, Hamming and RS use DSD’s reference classes, but the trellis test re-implements the production table (signalling.py:84) and trunk tests live in trunk.py.
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. GLM 5.3 Flash shares de-emphasis with Grok 4.6 and Muse Spark 1.3 Contributor and the gate mismatch with DeepSeek V4.1 Flash and Muse Spark, and goes furthest on memory, but finds the fewest decode-critical issues of the four.

IssueProject 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
Capture export stages the wrong I2S halfwordsabsentraised
§1, first code change
absent
its first experiment uses that capture
absent
trusts the capture
absent
relies on the capture
Capture parser certifies an incomplete streamabsentabsentabsentabsent
trusts the parser
absent
relies on the parser
pcm_starve never incrementsabsent
documented as working
absentabsentabsent
relies on it
absent
No static RAM marginpartly
margins still to measure
absentraised
M6, 0 bytes free
partly
“nearly full”, from the docs
raised
F5, byte-exact map audit
The I2S stream is most likely microphone audio missedpartly
open, leaning sceptical
raised
§1, 0x89 versus 0xC9
raised
B1, four-value test
raised
P1-1
partly
open, leans towards RF
HR-C6000 de-emphasis on the capture pathabsentraised
§2, bit 5 of 0x34
absent
calls it benign
raised
P1-2, 0x34=0x3C
raised
F2, closes the eye
AT1846S FM filters, low-frequency bit, 25 kHzpartly
“require characterization”
raised
§2, register level
raised
B2, filter register
raised
P1-2, 0x58 filters
partly
“voice filtering”; wrong bandwidth premise
0x10=0x6E hybrid state; 0x36 dual rolepartly
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
Manual: I2S frame clock “must be 8KHz” missedabsentraised
§3
absent
quotes the paragraph, not the rule
absentabsent
One-layer 4FSK test mode as a P25 tappartly
stock BER-test block only
raised
Gate D
raised
B5, exact recipe
absent
dismissed
absent
±10% health gate versus ±1% timing clampabsentabsentraised
B3, impact overstated
raised
P1-3
raised
F3
Fail-closed muting at LDU cadenceabsentabsent
late-entry mute only
absentabsent
calls it an asset
absent
Non-standard MFID mutes clear callsabsentabsentabsentabsentabsent
Test waveform shares the receiver’s RRC filterpartly
“synthetic RRC/AWGN” caveat
absentpartly
tested it, says not to fix
partly
synthetic only, wants recordings
absent
would extend that model
MCU runs at 72 MHzraisedraised
in passing
absentraised
P1-5
absent
Vocoder needs 11–16× the 72 MHz CPU missedpartly
decode timing unmeasured
absent
“fine on a 1 ms tick”
absent
“vocoder question settled”
partly
unmeasured; fix order backwards
absent
“in good shape”
Direct discriminator tap (M17 mod)raisedraised
uncredited
raised
pins, timer ADC, 48 kS/s
raised
fallback, pin 9
raised
fallback
Phase 2 architecture and scopepartly
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
Two unverified SPI writes per decoded 20 ms frameabsentabsentabsentabsentabsent
treats them as protection
Capture sessions lack epochsabsentabsentpartly
measured=0 only
absentabsent
Ring and tick real-time budgetpartly
deadlines unproven
partly
calls it fine
absentpartly
overruns look like weak RF
absent
Clock config 3 assumes 12,288 Hz; the codec formula gives 12,000absentabsentraised
B3, clock model
absentpartly
“guessed semantics”
Clock-config writes bypass the verified SPI writerpartly
SPI retry note
absentraised
M1
absentabsent

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 were first raised by DeepSeek V4.1 Flash.

Its plan

What to keep, change and add

GLM 5.3 Flash’s order starts in the right place: settle the sample source, then the analog conditioning. Several later steps rest on premises that don’t hold, and the plan never reaches the vocoder.

Keep

Worth doing as written

  • The carrier-keyed capture test first, then an offline demodulation of a real P25 capture, with the M17 tap as the fallback.
  • Suspend hrc6000Task while the monitor owns the codec, as the bring-up notes propose.
  • Reclaim memory before adding anything: the DMR codec buffers (about 10 KB) and satellite state (about 12 KB) in a P25 build.
Change

Would cause new problems

  • “Force 25 kHz”: the monitor already does. For P25, use the AT1846S DMR set at 12.5 kHz with its filters off (0x58=0x9CDD, 0x40=0x0031).
  • An inverse-emphasis filter: clear HR-C6000 0x34 bit 5 instead; the de-emphasis is a register setting, not a fixed stage.
  • A ±1% gate or measured-rate resampling: the rate report is quantised to about ±0.67%. Keep the exact nominal ratio; a ±2% gate is fine.
  • Phase 2 voice as new DSP code: mbelib has the AMBE+2 decoder, and the radio’s DVSI decoder already runs it in real time.
Add

Missing from its plan

  • Fix the capture first: stage the decoder’s [j][i*2] samples, and make the parser reject incomplete streams.
  • A real-time vocoder: a wrapped per-harmonic phase and a cosine table in mbelib. Without it, no audio plays.
  • Standard C4FM test vectors (raised cosine × inverse sinc), since the current generator shares the receiver’s RRC.
  • The one-layer probe: a cheap test of whether the chip will hand over demodulated 4FSK directly.

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 that nothing exists, wrong about the vocoder

Its Phase 2 inventory is mostly right: no TDMA layer, no CQPSK demodulator, TDMA grants rejected on purpose (p25_trunk.c:26), and the stock AMBE+2 decoder correctly placed on the MCU as a DMR-only DVSI binary.

Two claims are off. The bundled mbelib does contain the AMBE+2 half-rate decoder Phase 2 uses (ambe3600x2450.c); the linker merely drops it because nothing calls it. And “CQPSK requires an I/Q sample path” 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

Best on memory, last on the rest

All four clean-room audits miss the vocoder CPU wall. GLM 5.3 Flash is the only one to audit memory in detail, and one of three to name de-emphasis, but it finds one decode-critical issue where Grok 4.6 finds three and DeepSeek V4.1 Flash and Muse Spark 1.3 Contributor two each, and it makes more wrong claims about the receive path than any of them.

Grok 4.6 clean-room review, graded C (70) →

Scores by dimension

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

  • Grok 4.6
  • DeepSeek V4.1 Flash
  • Muse Spark 1.3 Contributor
  • GLM 5.3 Flash
0255075100Score out of 100Accuracy & evidence74737464Coverage of decode problems59495434Root cause & prioritisation70667262Fix plan & acceptance gates75707162Originality & attribution72747058Clarity & calibration78748068Weighted total70666856
Method and limits

How this was checked

I read GLM53FLASH_REVIEW.md, the only markdown the run added, and checked each claim against the firmware sources, the linker map and scripts, the OpenGD77 radio drivers, the translated HR-C6000 manual and the repository’s notes. Before grading I audited the run: one model for all its calls, no network, no skills or memory, and nothing read outside its copy of the repository.

The de-emphasis and CPU results come from the firmware-in-the-loop 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.
  • No firmware was edited or flashed, and no radio was touched.
grep -n "trxSetModeAndBandwidth(RADIO_MODE_ANALOG, true)" opengd77-rt3s-experiments/MDUV380_firmware/application/source/p25/p25mon.c
# the monitor forces 25 kHz

grep -n "ORIGIN = 0x800C000" opengd77-rt3s-experiments/MDUV380_firmware/STM32F405VGTX_FLASH.ld
# application flash: 1024K − 48K = 999,424 bytes

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

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