Skip to content

hl2: decode the TX FIFO status word as the gateware sends it - #5398

Open
on8st wants to merge 2 commits into
aethersdr:mainfrom
on8st:fix/tx-fifo-status-decode
Open

hl2: decode the TX FIFO status word as the gateware sends it#5398
on8st wants to merge 2 commits into
aethersdr:mainfrom
on8st:fix/tx-fifo-status-decode

Conversation

@on8st

@on8st on8st commented Sep 3, 2026

Copy link
Copy Markdown

Hl2Telemetry::apply() decodes RADDR 0's DATA[22:8] as a 15-bit TX FIFO
sample count, and DATA[15:14] as an under/overflow code. Neither field exists
on the wire. The comment above that code says so itself, and asks for the check
this PR carries out:

// THE ORACLE DISAGREES: §6 lists [14:8] as "FIFO count MSBs" and [15:14]
// as an under/overflow code, which overlaps bit 14 and cannot both be
// right. The gateware RTL is the authority and this has NOT been checked
// against it. Do not build FIFO-servoed TX pacing on this field until it
// has been — a pacing loop driven by a misread depth is exactly the kind
// of unverified assumption that wedged a radio once already.

Checked, against Hermes-Lite 2 gateware at
883a338 — the
SHA in the discovery string 20231230_74p2_883a338, gateware 74.2, the current
stable. Board variant hl2b5up_main. The oracle is wrong about both fields, and
so is the code.

What the gateware actually sends

gateware/rtl/control.v:472 builds the RADDR 0 response:

2'b00: iresp <= {3'b000,resp_addr, ext_cwkey, 1'b0, ptt_resp,
                 6'b000111, ~ext_txinhibit, (&clip_cnt), 8'h00,
                 dsiq_status, VERSION_MAJOR};

Eight bits of C0 then 32 of data, so:

bits field
DATA[31:26] constant 6'b000111
DATA[25] ~ext_txinhibit
DATA[24] (&clip_cnt) — the ADC-overload bit
DATA[23:16] constant 8'h00
DATA[15:8] dsiq_status — the entire FIFO field, eight bits
DATA[7:0] VERSION_MAJOR

dsiq_status is composed in gateware/rtl/fifos.v:100-110:

always @ (posedge rd_clk) begin
  if (rd_sample) begin
    rd_count <= rd_tlength[(rdbits-1):(rdbits-7)];   // top 7 bits of the fill level
    recovery_flag <= 1'b0;
    recovery_flag_d1 <= recovery_flag;
  end else if (rd_tvalidn | ~allow_push) begin
    recovery_flag <= 1'b1;
  end
end

assign rd_status = {recovery_flag_d1,rd_count};

Two consequences:

  1. [6:0] is the top 7 bits of the read-side fill level — a coarse
    occupancy, 0–127. There is no sample count anywhere in this word.
  2. [7] is one flag for two faults. rd_tvalidn is the FIFO having run
    empty; ~allow_push is writes being dropped after it filled
    (fifos.v:55-61). The gateware never distinguishes them, so an under/overflow
    code cannot be decoded from this word by anyone.

Why the existing decode looked fine

DATA[23:16] is a constant zero, so (data >> 8) & 0x7FFF returns
dsiq_status itself. The displayed number was the right byte carrying a wrong
name — it never took an absurd value, so nothing ever prompted the read.

What an operator actually saw:

  • A recovery event set bit 7, which the decode read as part of the count, so
    "TX FIFO depth" jumped by 128 on an empty FIFO.
  • The same recovery flag was reported as "underflow" when fill-level bit 6 was
    clear and "overflow" when it was set
    — two opposite diagnoses of one event,
    selected by how full the buffer happened to be.

The change

txFifoCount      (optional<int>)   -> txFifoFillMsbs  (optional<int>)   = DATA[14:8]
txFifoUnderflow  (optional<bool>)  -> txFifoRecovery  (optional<bool>)  = DATA[15]
txFifoOverflow   (optional<bool>)  -> removed

The diagnostics pane goes from three rows to two: TX FIFO fill (0-127, coarse)
and TX pacing fault (under OR overrun). The label carries the ambiguity the
wire has, rather than resolving it in the reader's favour — a consumer that
wants to know which fault occurred cannot get it from this word and should not
be encouraged to guess.

What this PR deliberately does not do

It does not convert the fill level into samples or milliseconds. rdbits is 12
for this board's DSIQ_FIFO_DEPTH of 16384 (hermeslite_core.v:136, not
overridden by variants/hl2b5up_main/hermeslite.v), which makes one unit 32
read-side FIFO words — but the words-to-samples mapping is an inference from the
FIFO's port widths, not something read out of the RTL. The original comment's
warning stands until that is measured: do not servo TX pacing on this field
yet.
This PR makes the field honest; it does not make it sufficient.

Tests

tests/hl2_metis_protocol_test.cpp gains ten checks over hand-built
dsiq_status words. Four of them fail on main before the fix:

FAIL: dsiq_status 0x80: fill level is 0 — the set bit is the flag, not a count
FAIL: dsiq_status 0x80: gateware claims no underflow — it has no such bit
FAIL: dsiq_status 0xC0: fill level is 0x40, not 0xC0
FAIL: dsiq_status 0xC0: gateware claims no overflow — it has no such bit

The 0x40 and 0x7F cases pass both before and after — kept deliberately,
because they are the reason this survived: they are exactly the values where the
old expression's accident holds. An all-ones DATA case pins the field width so
a future widening cannot silently pick up the ADC-overload and TX-inhibit bits
sitting just above the constant zero byte.

Scope

Gateware read at 883a338 and the AetherSDR source. Nothing here is
measured
— no radio was keyed to produce a recovery event, and this PR makes
no claim about how often one occurs or what causes it. It is a decode
correction, and the test is a decode test.


Notes for the orchestrator, not for the PR body

  • Sizes as XS. Touches MetisProtocol.h, MetisProtocol.cpp, four lines of
    Hl2Backend.cpp (a diagnostics put() block and one qCDebug), one line of
    hl2_live_band_filter_probe.cpp, and the test.

  • Hl2Backend.cpp overlap check: both edits are in publishTelemetry and
    the diagnostics table, not in the drive path tx-dynamics holds on
    fix/hl2-initial-drive. Worth a rebase check before either goes up.

  • Verified on the PR branch itself, not only on the feature branch it came
    from — red at ebf6c66, green at 505f575, same four failures:

    $ clang++ -std=c++20 -Isrc -o t tests/hl2_metis_protocol_test.cpp \
          src/core/backends/hl2/MetisProtocol.cpp && ./t
    # at ebf6c66 (test only, fix not applied):
    FAIL: dsiq_status 0x80: fill level is 0 — the set bit is the flag, not a count
    FAIL: dsiq_status 0x80: gateware claims no underflow — it has no such bit
    FAIL: dsiq_status 0xC0: fill level is 0x40, not 0xC0
    FAIL: dsiq_status 0xC0: gateware claims no overflow — it has no such bit
    # at 505f575 (fix applied):
    hl2_metis_protocol_test: all checks passed
    

    The standalone build is the right citation here: the target is two
    translation units with no Qt and no aethercore, which is why it is fast
    enough to run at both commits.

  • Also verified in a full build, on feat/hl2-telemetry where these two
    commits are the base: cmake --build exit 0 over 2952 objects, and
    ctest -R hl2 22/23 with hl2_metis_protocol_test passing in the real
    tree
    . The single failure is hl2_state_restore_test's three pre-existing
    CW-passband-guard assertions (hl2_state_restore_test: three CW-passband assertions fail on main (10a847b) #5394), which fail identically on unmodified
    upstream main. That build used ENABLE_ASR=OFF, the standard on this
    machine, and CMAKE_IGNORE_PREFIX_PATH=/opt/homebrew;/opt/local.

  • The full build has not been re-run on fix/tx-fifo-status-decode itself;
    it carries the same two commits against the same base, and the standalone
    test was run there directly. Say so rather than implying otherwise if asked.

  • gh api user --jq .login must read on8st before any push, ever.

🤖 Generated with Claude Code

https://claude.ai/code/session_01FtHEsQsghFwhUQEZdwVKUz

on8st and others added 2 commits September 3, 2026 16:34
…oday

MetisProtocol.cpp's own comment above txFifoCount asks for this check:
"The gateware RTL is the authority and this has NOT been checked against
it. Do not build FIFO-servoed TX pacing on this field until it has been."

Checked, against Hermes-Lite2 gateware 883a338. The decode is wrong.

control.v:472 puts the whole FIFO field in DATA[15:8] — dsiq_status,
eight bits — with DATA[23:16] a constant zero. fifos.v:100-110 composes
it as {recovery_flag, rd_count[6:0]} where rd_count is the TOP 7 bits of
the read-side fill level, and the recovery flag covers BOTH "ran empty"
and "writes blocked because it filled" (fifos.v:55-61). There is no
15-bit count in this word and no under/overflow distinction anywhere in
the gateware.

Four checks fail:
- dsiq_status 0x80 reports a depth of 128 for an empty FIFO, because
  bit 7 is read as part of a count.
- dsiq_status 0xC0 reports 192 for a fill level of 0x40.
- the same recovery flag is diagnosed as "underflow" at 0x80 and
  "overflow" at 0xC0 — two opposite verdicts on one event, chosen by a
  fill-level bit.

The 0x40 and 0x7F cases pass, which is why this survived: DATA[23:16] is
zero, so the old expression happens to return dsiq_status itself. It read
the right number under a name claiming it was something else.

Not asserted, because not established: what one unit of the fill field is
worth in samples or milliseconds. aethersdr#17's servo needs that; this decode
does not.

Fix follows in the next commit.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017SvGM4eqSng7aX62aVCFyh
Makes the previous commit's test pass. The word is dsiq_status,
DATA[15:8] of RADDR 0, and it carries two things, neither of which the
old decode named correctly:

  txFifoFillMsbs  <- [6:0]  top 7 bits of the read-side fill level, 0-127
  txFifoRecovery  <- [7]    one flag for BOTH underrun and blocked writes

replacing txFifoCount (a 15-bit sample count that does not exist on the
wire), txFifoUnderflow and txFifoOverflow (a distinction the gateware
does not make).

Evidence, at 883a338: control.v:472 places dsiq_status at DATA[15:8]
with DATA[23:16] a constant zero; fifos.v:100-110 composes it as
{recovery_flag_d1, rd_count[6:0]} where rd_count is
rd_tlength[(rdbits-1):(rdbits-7)] and recovery_flag is set by
rd_tvalidn OR ~allow_push (fifos.v:55-61, 105-106).

Why it survived: DATA[23:16] being zero made (data >> 8) & 0x7FFF equal
dsiq_status, so the displayed number was the right byte under a wrong
name. Nothing ever looked absurd enough to prompt the read that
MetisProtocol.cpp's own comment had been asking for.

What the operator saw: a recovery event added 128 to a figure labelled
"TX FIFO depth", and the same event was reported as "underflow" or
"overflow" according to fill-level bit 6 — opposite diagnoses of one
fault. The diagnostics rows are now one level (0-127, labelled coarse)
and one "TX pacing fault (under OR overrun)".

Still not established, so still not servo-safe: what one unit of the
fill field is worth in samples. rdbits is 12 for this board's
DSIQ_FIFO_DEPTH of 16384 (hermeslite_core.v:136), making the unit 32
read-side words, but words-to-samples is an inference. aethersdr#17 must measure
it before pacing on this field.

Verified: hl2_metis_protocol_test passes (10 new checks, 4 of which
failed before this commit); Hl2Backend.cpp and hl2_live_band_filter_probe
.cpp syntax-check clean against Qt 6.8.3. No full build run.

Row B-18.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017SvGM4eqSng7aX62aVCFyh
@on8st
on8st requested a review from a team as a code owner September 3, 2026 15:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant