This thread has been locked.

If you have a related question, please click the "Ask a related question" button in the top right corner. The newly created question will be automatically linked to this question.

J784S4XEVM: MCSPI FIFO transfer > 32 bytes: TXx_EMPTY does not re-assert (J784S4, controller mode)

Part Number: J784S4XEVM
Other Parts Discussed in Thread: AM69, SYSCONFIG

# MCSPI FIFO transfer > 32 bytes: TXx_EMPTY does not re-assert (J784S4, controller mode)

Device: J784S4 (AM69), MCSPI5, MAIN-domain R5F, FreeRTOS, interrupt-driven.
TRM: SPRUJ52E, MCSPI chapter 12.1.5.

## Problem statement

We are implementing an interrupt-driven, full-duplex MCSPI transfer in controller
mode using the built-in FIFO, for transfers larger than one FIFO load (> 32 bytes
at 8-bit words). The FIFO is 64 bytes, split 32 TX / 32 RX. The intent: prime the
TX FIFO, then service TXx_EMPTY (§12.1.5.3.7.1.1) to refill TX and RXx_FULL
(§12.1.5.3.7.1.3) to drain RX in bursts until the transfer completes, using EOW
(§12.1.5.3.7.1.4) as the completion signal.

Transfers <= 32 bytes (one FIFO load) work correctly. Transfers > 32 bytes stall.

## Anomaly

For a 40-byte transfer, only one TXx_EMPTY interrupt is ever generated. After the
initial TX FIFO contents clock out, TXx_EMPTY does not re-assert, so the TX FIFO is
never refilled, the transfer stalls, and EOW never fires.

RXx_FULL re-asserts and is serviced correctly (observed 2x for a 32-byte-clocked
transfer). The asymmetry — RXx_FULL re-fires, TXx_EMPTY does not — is the core
anomaly. No TXx_UNDERFLOW is generated. CHSTAT[2] EOT is set with the transfer
incomplete.

## Configuration (verified by reading back the control registers)

Per §12.1.5.3.6 (FIFO buffer management) and §12.1.5.3.7 (interrupts):

- MCSPI_XFERLEVEL: AFL = 15, AEL = 15 (almost-full/almost-empty trip at 16 bytes).
  WCNT was tested both = 0 (counter unused) and = transfer word count (40); no
  difference in the TXx_EMPTY behavior.
- MCSPI_CHCONF_0 read back as 0x17027: FORCE=1 (manual CS asserted), FFEW=1 (TX FIFO
  enabled), FFER=1 (RX FIFO enabled), TURBO=0, SBE=0, IS/DPE per full-duplex,
  TRM=00 (transmit-and-receive), WL=0x7 (8-bit).
- MCSPI_CHCTRL_0[0] EN = 1 (channel enabled, confirmed still set after the stall).
- MCSPI_MODULCTRL: MS=0 (controller), SINGLE=1, PIN34=0, MOA=0, FDAA=0,
  INITDLY=8 cycles.
- MCSPI_SYSCONFIG: AUTOIDLE=0, SIDLEMODE=NO_IDLE, CLOCKACTIVITY=BOTH, ENAWAKEUP=1.
- MCSPI_IRQENABLE: TX0_EMPTY, RX0_FULL, EOW enabled.

## ISR design (brief)

Single ISR per MCSPI instance. On entry it reads MCSPI_IRQSTATUS, then:
- If EOW: drain the RX tail and complete the transfer (single completion authority).
- Else: 
  - on TXx_EMPTY, write up to AEL+1 (16) words to MCSPI_TX (bounded by words
  still owed); on first write, up to 32 words to fill the FIFO.
  - on RXx_FULL, read AFL+1 (16) words from MCSPI_RX. 
- Each serviced flag is written back to MCSPI_IRQSTATUS (write-1-to-clear) at the end of the ISR.

The transfer is started (§12.1.5.4.1.1.8.1) by asserting the channel and CS. This is when we
observe the first (and only) TX EMPTY interrupt.

## Results (40-byte transfer, 8-bit words, loopback, SPI clock 375 kHz)

Two priming strategies were tested, same result:

1. Prime 16 words, refill via TXx_EMPTY:
   `tx_empty=1 rx_full=1 eow=0 rx=16 tx=16` — 16 bytes clock on the bus, then stall.
   Post-stall CHSTAT = 0xD5: TXFFE=1 (TX FIFO empty), RXFFE=1 (RX FIFO empty),
   TXS=1 (TX register empty), RXS=0 (no RX back-pressure), EOT=1, EN=1.
   MCSPI_IRQSTATUS read back = 0x0 (no flags pending; the write-1-to-clear
   succeeded and no new TXx_EMPTY is asserted).

2. Prime the full FIFO (32 words):
   `tx_empty=1 rx_full=2 eow=0 rx=32 tx=32` — 32 bytes clock (the primed FIFO
   contents), then stall. CHSTAT = 0x800D5 (same low byte 0xD5, WCNT high half = 8,
   i.e. 40 - 32 = 8 words remaining, confirming WCNT decremented correctly).
   TXx_EMPTY still fired only once; RXx_FULL fired twice and re-armed correctly.

In both cases the bytes that were placed in the TX FIFO are transmitted on the bus
correctly. The transfer stops exactly when the primed TX FIFO contents are
exhausted, with CHSTAT[2] EOT set, MCSPI_IRQSTATUS = 0, TX FIFO empty, and no
further TXx_EMPTY event.

## Question

In controller, full-duplex, single-channel, manual-CS (FORCE) FIFO mode with the
configuration above, why does MCSPI_IRQSTATUS TXx_EMPTY not re-assert after the TX
FIFO drains below AEL, given that the processor performed the AEL+1 writes required
by §12.1.5.3.7.1.1 to re-arm the event, while RXx_FULL re-asserts normally under the
symmetric AFL rule? Specifically: does CHSTAT[2] EOT being set on TX-FIFO-empty
suppress further TXx_EMPTY generation, and if so, what is the required sequence to
continue a > FIFO-load transfer without terminating at EOT?

On a similar vein, if the status EOT is indicated (presumably when the FIFO empties), 
why isn't the EOT interrupt generated in this case?

  • Hi Kevin,

    From the usage of markdown and "§", I assume AI is being used to create a McSPI driver. We have support for Linux and RTOS drivers in our PSDK Linux and PSDK RTOS respectively. Any issues in using those drivers?

    In any case, whether you choose to use the existing drivers or not, I would recommend installing the SDK. If going down the custom driver path, I recommend pointing the AI agent to use them as reference along with the TRM (which I think is already fed into your AI). PSDK RTOS would probably be better for the AI to understand compared to Linux. The file that has the driver is mcusw/mcal_drv/mcal/Spi/src/Spi_Mcspi.c. And the PSDK RTOS source package is downloadable from here: https://www.ti.com/tool/download/PROCESSOR-SDK-RTOS-J784S4

    The SDK code should give an example of how the EOW and TX0_EMPTY are handled.

    Regards,

    Takuma

  • I'm dealing with some existing libraries that I'm required to use. Part of that library set does not support the J784S in some cases.  But honestly, I don't think it's that aspect that's non-functional. The TX EMPTY interrupt is enabled, and it triggers when the channel is enabled (correct, per the TRM). The code fills the FIFO with 32 bytes and clears the TX_EMPTY IRQ status (exactly what the TRM requires to clear the TX EMPTY signal). The TX EMPTY interrupt never occurs again - not when the FIFO has the TX EMPTY setpoint, and not when it's empty. If everything is set up correctly for the first one, all subsequent interrupts should occur. But they don't. The question is: why? I don't have this problem with EOT, nor RX FULL. Just TX EMPTY.

    If it matters any, I'm having to use SPI5, which is on MAIN rather than directly accessible by the R5 (we will change this in the near future; just the EVM we have doesn't have the best access to SPI0/SPI1 buses).

    Thanks for digging into this issue a bit more. If you need more register settings at certain points in the the transmission, let me know. I'll be happy to provide additional information.

  • Hi Kevin,

    SPI5 should be fine, and it should be accessible by Main domain as well as MCU domain. 

    Otherwise, it sounds like the EOT is terminating further TX_EMPTY signals. EOT is usually the signal to stop the channel, so maybe this is an unhandled state that you have hit.

    • What SDK code does is that the AFL and AEL are computed dynamically instead of a fixed size, and when both TX and RX FIFO are used, it is effectively a quarter of the full FIFO (since FIFO is split by two for TX and RX, and each TX and RX FIFO has the FifoTrigLvl set to half of each FIFO).
    • SDK also does some cleaning between transfers by disabling the FIFO buffer, so perhaps there is something dirty between your experiments that is causing the EOT to fire while transfer is ongoing.
    • There is also a corner case handling in SDK for when the transfer is less than the FIFO level, which is handled through shrinking the trigger levels so that small transfers still generate a TX_EMPTY.

    Otherwise, perhaps there is some initialization that is missed, or some difference in flow. You could compare the call stack between our reference code and your code. Should be relatively easy to do a static analysis using tools. 

    Regards,

    Takuma