Skip to content

dw_downsizer: Emit only the sub-transfers TKEEP marks valid - #8

Open
dngr2 wants to merge 1 commit into
pulp-platform:masterfrom
dngr2:fix/dw-downsizer-honour-tkeep
Open

dngr2 wants to merge 1 commit into
pulp-platform:masterfrom
dngr2:fix/dw-downsizer-honour-tkeep

Conversation

@dngr2

@dngr2 dngr2 commented Aug 15, 2026

Copy link
Copy Markdown

The bug

axi_stream_dw_downsizer emits TotalSubTransfers sub-transfers for every input beat regardless of TKEEP, so the lanes a partial beat marks invalid are pushed downstream as real data.

Surfaced as pulp-platform/pulp-ethernet#5: a 52-byte Ethernet payload produced an 84-byte frame instead of 78. The excess of exactly six is the invalid lanes of the final 64-bit beat (8 − 2 valid bytes). pulp-ethernet instantiates this module to go 64→8 for the MAC (eth_top.sv:123) and does not connect TKEEP/TSTRB onward (framing_top.sv:214-218), so the null lanes reach the wire.

Reproduced in Verilator against the unmodified module — a single beat with TKEEP=0x03, TLAST=1:

sub-beats emitted      : 8
of which keep=1        : 2
spurious bytes on wire : 6

The change

The last sub-transfer of a beat is now the index of its highest asserted TKEEP bit, latched when the beat is accepted.

It cannot be derived from tkeep_received_q — that register is shifted right on every sub-transfer, so the target index would shrink as the counter climbs and a full beat would terminate early. I made exactly that mistake first; a full 0xFF beat emitted 5 bytes instead of 8 and hung the testbench. Hence the latch, and hence the full-beat check below.

Two edge cases:

  • A beat whose only valid lane is 0 has no DataOut phase — that byte is itself the last sub-transfer and must carry TLAST.
  • A beat with TKEEP all-zero still yields one sub-transfer, so it is consumed rather than stalling the stream.

Scope: trailing invalid lanes are dropped. Non-contiguous null lanes in the middle of a beat are not compacted — that needs real compaction logic and a wider behavioural decision, so I left it out rather than half-doing it.

Also in this commit

tstrb_received_d = tstrb_received_d >> StrbWidthOut read the next-state variable where both neighbouring lines use _q. It happens to produce the right value today only because of the default assignment above it. Changed to _q for consistency.

Testing

New self-contained testbench, test/tb_axi_stream_dw_downsizer_tkeep.sv, registered in Bender.yml. It states exact byte counts rather than using the randomized drivers, so the checks read as the specification.

Check Original With fix
final beat TKEEP=0x03 8 ✗ 2
full beat TKEEP=0xFF 8 ✓ 8 ✓
single valid lane 8 ✗ 1
TKEEP=0x7F 8 ✗ 7
full beat + 2-byte tail 16 ✗ 10

Four of the ten checks fail against the unfixed RTL, so they are load-bearing. The full-beat case passes both before and after — that is the one showing fully valid beats are unchanged.

Verified with Verilator 5.006. I note that CI runs license, commit and verible lint but no simulation, so these results are from a local run rather than from the pipeline — worth an independent check on your side. I did not modify the existing tb_axi_stream_dw_downsizer, which uses the randomized driver framework and cannot easily express a specific TKEEP pattern.

Unrelated observation

pulp-ethernet pins this package at 472751f550e3918215603e21734fe0ece3c66f79 in its Bender.lock, and that revision is not reachable in this repository — I fetched all refs. So that consumer currently pins a commit nobody can fetch, and this fix will not reach it until the pin moves. Flagging it here since it affects whether the fix actually lands for the reporter; happy to raise it separately.

The downsizer emitted TotalSubTransfers sub-transfers for every input beat
regardless of TKEEP, so the lanes a partial beat marks invalid were pushed
downstream as real data.

Reported in pulp-platform/pulp-ethernet#5: a 52-byte Ethernet payload
produced an 84-byte frame instead of 78. The excess of exactly six is the
invalid lanes of the final 64-bit beat, 8 - 2 valid bytes. That design
does not connect TKEEP or TSTRB onward to the MAC, so the null lanes
reached the wire.

The last sub-transfer of a beat is now the index of its highest asserted
TKEEP bit, latched when the beat is accepted. It cannot be derived from
tkeep_received_q, which is shifted right on every sub-transfer: the target
index would shrink as the counter climbs and a full beat would end early.
A beat whose only valid byte is lane 0 has no DataOut phase, since that
byte is itself the last sub-transfer and must carry TLAST. A beat with
TKEEP all-zero still yields one sub-transfer, so it is consumed rather
than stalling the stream.

Trailing invalid lanes are dropped; non-contiguous null lanes in the
middle of a beat are not compacted.

Also read tstrb_received_q, not tstrb_received_d, when shifting TSTRB.
The two neighbouring lines use _q; this one aliased the next-state
variable and only matched because of the default assignment above it.

Adds a self-contained testbench stating exact byte counts, including a
full-beat case that passes both before and after, so the change is shown
not to alter fully valid beats. Against the unfixed RTL four of its ten
checks fail.

Verified with Verilator 5.006; CI does not run simulation.
@dngr2

dngr2 commented Aug 16, 2026

Copy link
Copy Markdown
Author

Note on the force-push you will see on this branch: the commit author email was wrong, and I corrected it. The tree is unchanged — same diff, same tests, only the author metadata differs.

I know CONTRIBUTING.md asks contributors not to force-push. The alternative was leaving a commit whose author is not a linked GitHub account, and util/lint-commits.py rejects that, so amending seemed the lesser of the two. It will not happen again on this branch; any review feedback will come as new commits on top.

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