The rust_tlplib and rtlp-tool libraries had not seen a release in a long time. After getting Gen 4 TLP parsing to a point where it covered everything I needed day-to-day, I left it there. Good enough was good enough.

Then Gen 6 started showing up everywhere — conferences, hardware announcements, specs dropping into the mailbox. Flit mode is not a minor revision; it is a different framing model entirely, and the TLP format that goes inside it was reworked to match. I had to spend a real amount of time working through the spec before I understood what had actually changed and what had stayed the same. The PCIe 6.0 spec calls the relevant section “TLP Format Revisited.” That name carries more weight than it looks.

The other thing that slows you down when you return to old code is the old code itself. I remembered writing some of it under time pressure and taking shortcuts that made sense at the time. Seeing them again — as someone who had to then build on top of them — was educational in the way that finding your own bugs always is. So the API rename pass in this release is partly Gen 6 groundwork and partly old debts getting paid.

Both tracks took longer than expected. The release is the result.

If you have been using either library for Gen 1–5 parsing, nothing changes — the existing paths are untouched. What follows is what is new.

Flit Mode: What Actually Changed

The previous post on parsing TLPs covers the traditional format: DW0 encodes FMT[2:0] + TYPE[4:0], the rest of the header follows a layout determined by that combination, and parsing is a matter of reading the right bits from the right dwords.

PCIe 6.0 changes the framing entirely. To support PAM4 signaling at 64 GT/s, the spec introduced flits — fixed 256-byte containers with their own forward error correction and CRC. TLPs are no longer delimited by STP/END sequences at the data link layer; they are packed into flits. The TLP format itself was revised to match: DW0 in flit mode carries a different set of fields, there are thirteen type codes rather than the FMT+TYPE matrix of earlier generations, and Optional Header Components (OHC) replace TLP prefixes as the extension mechanism.

None of this is obvious from a quick read. The spec’s framing of it as “revisited” undersells how much the mental model needs to shift.

Library Changes

The new types in rust_tlplib:

  • FlitDW0 — flit-mode DW0 with the Gen 6 field layout
  • FlitTlpType — the thirteen flit type codes (NOPs, memory, atomics, messages)
  • FlitOhcA — Optional Header Component type A, the primary OHC variant
  • FlitStreamWalker — walks a byte stream extracting TLPs packed inside a flit

Mode dispatch is now on TlpPacket directly:

match packet.mode() {
    TlpMode::Standard(tlp) => { /* Gen 1–5 handling */ }
    TlpMode::Flit(flit)    => { /* Gen 6 handling */   }
}

The release also includes the API rename pass mentioned above. Existing get_* accessors are deprecated in favor of canonical names — the library ships a migration table in the release notes, and the old names still compile with deprecation warnings for now. 212 tests pass on Rust 1.85.

rtlp-tool

The command-line tool gains a --flit flag:

$ rtlp-tool --flit -i "03 00 00 01 01 00 0A FF AB CD 12 34"

All output modes — table, JSON, CSV — support the flag. JSON output now includes "flit_mode": true to make it unambiguous which parser was used.

This release also cleans up several bugs that had been sitting in the non-flit path: a DW0 byte-slice extraction reading from the wrong offset, a crash on odd-nibble hex input, missing mutual exclusion between --aer and --lspci, and JSON output that was generating malformed escapes for certain field values.

Installing

cargo install rtlp_tool

Binaries for Linux (x86_64, aarch64), macOS (Intel and Apple Silicon), Windows, and FreeBSD are on the release page. Debian/Ubuntu .deb and RPM packages are also available if you would rather not go through Cargo.