Skip to content

jtagstream: fix JTAGPHY and JTAGUART for reliable operation#2410

Open
chiralitie wants to merge 3 commits intoenjoy-digital:masterfrom
chiralitie:fix-jtagphy-rx_ready
Open

jtagstream: fix JTAGPHY and JTAGUART for reliable operation#2410
chiralitie wants to merge 3 commits intoenjoy-digital:masterfrom
chiralitie:fix-jtagphy-rx_ready

Conversation

@chiralitie
Copy link

@chiralitie chiralitie commented Feb 5, 2026

Summary

This PR fixes several issues with the JTAGPHY and JTAG UART functionality:

  1. rx_ready always zero on ECP5 - The original issue
  2. litex_server --jtag crashes in background - New fix
  3. Wire format timing issues - New fix

Problem 1: rx_ready always zero

The JTAGPHY rx_ready signal (bit 0 of Target-to-Host response) was always returning 0 on ECP5 devices, even when the CDC FIFO was writable. This broke JTAGBone CSR access.

Root Cause

Two issues in the current implementation:

  1. FSM reset on capture: jtag.capture event fires at the start of every drscan, triggering ResetInserter which clears ALL NextValue registers, including ready.

  2. NextValue cleared by FSM reset: The JTAG TAP transitions through Test-Logic-Reset between drscans, asserting jtag.reset and clearing ready.

Solution

  1. Only reset FSM on jtag.reset, not jtag.capture
  2. Move ready update outside FSM control using a separate sync.jtag statement

Problem 2: litex_server --jtag crashes in background

When running litex_server --jtag as a background process, it would crash with:

Exception in thread tcp2pty:
  ConnectionResetError: [Errno 104] Connection reset by peer

Root Cause

  1. stdin readable callback in jtagstream_serve caused immediate exit when stdin was closed
  2. No error handling for TCP connection failures in JTAGUART
  3. pty2tcp/tcp2pty threads didn't handle connection errors gracefully

Solution

  1. Remove stdin readable callback from jtagstream_serve
  2. Add ConnectionError if OpenOCD connection fails after 50 retries
  3. Add error handling for connection errors in pty2tcp/tcp2pty threads
  4. Properly close TCP socket and PTY in close() method

Problem 3: Wire format timing issues

The wire format documentation was incorrect (10 bits instead of 11), and the RX valid bit was being captured at the wrong time.

Solution

  1. Fix wire format to use data_width + 3 shift cycles for data_width + 2 bits
  2. Add XFER-PADDING state to handle the extra shift cycle
  3. Capture RX valid bit on shift falling edge
  4. Update rx_valid/rx_data via sync.jtag instead of NextValue

Changes

litex/soc/cores/jtag.py

  • Only reset FSM on jtag.reset, not jtag.capture
  • Move ready update outside FSM control
  • Fix wire format to use data_width + 3 shift cycles
  • Add XFER-PADDING state for extra shift cycle
  • Capture RX valid bit on shift falling edge
  • Update rx_valid/rx_data via sync.jtag

litex/build/openocd.py

  • Remove stdin readable callback from jtagstream_serve
  • Add get_openocd_cmd() to respect OPENOCD environment variable
  • Fix wire format documentation (11 bits, not 10)
  • Handle newline-separated drscan output in newer OpenOCD

litex/tools/litex_term.py

  • Raise ConnectionError if OpenOCD connection fails
  • Add error handling for connection errors in pty2tcp/tcp2pty
  • Properly close TCP socket and PTY in close() method
  • Check for empty reads to detect connection closure

Testing

Tested on ECP5-VIP board with FT2232H JTAG adapter:

  • litex_server --jtag now starts and listens on both ports (1234 and 20000)
  • JTAGPHY loopback test passes (all bytes echo correctly)
  • No more tcp2pty exceptions when running in background

Compatibility

These changes should be backwards compatible. The FSM behavior is unchanged except:

  • FSM no longer resets on capture (which was incorrect behavior)
  • ready and rx_valid registers survive FSM resets (expected behavior)
  • Wire format timing is now correct

Two issues were causing JTAGBone to fail on ECP5 devices:

Issue 1: TDO Timing
-------------------
The JTAGG primitive samples JTDO1 on the FALLING edge of TCK, but the
FSM state changes on the RISING edge. By the falling edge, the FSM has
already transitioned to the next state, so the wrong TDO value was
being sampled.

Fix: Use a registered TDO output (tdo_reg) that captures the value on
the rising edge, so it's stable when sampled on the falling edge.

Issue 2: Ready Signal Persistence
---------------------------------
The FSM was being reset on jtag.capture, which cleared the ready signal
before it could be shifted out. Additionally, NextValue registers are
cleared by ResetInserter when the JTAG TAP goes through Test-Logic-Reset.

Fix: Only reset FSM on jtag.reset (not jtag.capture), and update ready
via sync.jtag outside the FSM so it survives FSM resets.

Testing
-------
Tested on ECP5-VIP board (LFE5UM-85F) and LimeSDR Mini v2 (LFE5U-45F):

Before fix:
  > drscan ecp5.tap 10 0x000
  0000   # rx_ready=0 (always zero)

After fix:
  > drscan ecp5.tap 10 0x000
  0001   # rx_ready=1 (correct)

CSR reads via litex_cli work correctly after the fix.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@chiralitie chiralitie force-pushed the fix-jtagphy-rx_ready branch from f5a3506 to 2eef758 Compare February 6, 2026 04:16
@chiralitie
Copy link
Author

Update: Added TDO Timing Fix

I've updated this PR with an additional critical fix for the TDO timing issue.

The Problem

The JTAGG primitive samples JTDO1 on the falling edge of TCK, but the FSM state changes on the rising edge. By the falling edge, the FSM has already transitioned to the next state, so the wrong TDO value was being sampled.

The Fix

Use a registered TDO output (tdo_reg) that captures the value on the rising edge, so it's stable when sampled on the falling edge:

tdo_reg = Signal(reset=1)
self.comb += jtag_tdo.eq(tdo_reg)

# In FSM states, use NextValue to update tdo_reg:
NextValue(tdo_reg, ready)   # In XFER-READY
NextValue(tdo_reg, data[0]) # In XFER-DATA
NextValue(tdo_reg, valid)   # In XFER-VALID

Testing

Verified working on ECP5-VIP board (LFE5UM-85F):

  • Direct JTAG test shows rx_ready=1 consistently across all drscans
  • CSR reads via litex_cli work correctly

The branch has been force-pushed with the complete fix.

chiralitie and others added 2 commits February 7, 2026 04:21
- litex_term.py: Add error handling for TCP connection failures
  - Raise ConnectionError if OpenOCD jtagstream connection fails
  - Handle ConnectionResetError, BrokenPipeError, OSError in pty2tcp/tcp2pty
  - Properly close TCP socket and PTY in close() method
  - Check for empty reads to detect connection closure

- openocd.py: Fix jtagstream for background operation
  - Remove stdin readable callback that caused immediate exit when running in background
  - Use 11-bit wire format (was incorrectly documented as 10-bit)
  - Handle newline-separated drscan output in newer OpenOCD versions
  - Add get_openocd_cmd() to respect OPENOCD environment variable

These fixes allow litex_server --jtag to work reliably when spawned
as a background process or from a daemon thread.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Use data_width + 3 shift cycles for data_width + 2 bits of information
  (JTAGG only captures TDO on falling edge in Shift-DR, last falling edge
  is in Exit1-DR which doesn't capture)
- Add XFER-PADDING state to handle the extra shift cycle
- Capture RX valid bit on shift falling edge (transition from Shift-DR to Exit1-DR)
- Update rx_valid/rx_data via sync.jtag instead of NextValue to survive FSM resets
- Fix documentation to reflect actual wire format

This fixes the issue where the JTAGPHY would miss the valid bit from the host
because it was being captured at the wrong time in the JTAG state machine.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@chiralitie chiralitie changed the title jtag: fix JTAGPHY rx_ready always zero on ECP5 jtagstream: fix JTAGPHY and JTAGUART for reliable operation Feb 7, 2026
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