Description
I've noticed a strange PIO behavior which I can't find documented anywhere. I've created a small project to isolate and reproduce this issue.
I'm using RX FIFO in PUTGET mode to give the state machine extra scratch registers. The RP2350 data sheet describes this use case, and the .fifo putget
pioasm directive makes it easy to configure this. I'm able to use the mov OSR, RXFIFO[y]
and mov RXFIFO[y], ISR
instructions to read and write from RX FIFO as expected.
The problem occurs when I then use the PIO DREQ to pace word transfers from DMA to TX FIFO. From testing, I've found that mov OSR, RXFIFO[y]
triggers DREQ_PIO0_TX0 and mov RXFIFO[y], ISR
triggers DREQ_PIO0_RX0. This means that every time a word is loaded from RX FIFO into OSR, the DMA overwrites data in TX FIFO which never makes it to the state machine.
To test this, I've written a small program which records how long a DMA transfer takes to complete. If DREQ is being called extra times, then the DMA transfer will finish sooner than expected (which is how I noticed this behavior). Further below, I've included the C program which does the configuration and performs the test.
test.pio (normal transfer):
.program test
.pio_version 1
.fifo putget
.wrap_target
pull block ; First DREQ
nop ; Placeholder instruction
nop [31] ; Delay
.wrap
Output: DMA transfer took 475 ms
test.pio (data loss):
.program test
.pio_version 1
.fifo putget
.wrap_target
pull block ; First DREQ
mov OSR, RXFIFO[0] ; Second, unexpected DREQ
nop [31] ; Delay
.wrap
Output: DMA transfer took 237 ms
dma_test.c:
#include <stdio.h>
#include "pico/stdlib.h"
#include "hardware/dma.h"
#include "hardware/pio.h"
#include "hardware/powman.h"
#include "test.pio.h"
static uint32_t SOME_VALUE = 0xdeadbeef;
int main() {
stdio_init_all();
// Configure DMA
int dma_channel = dma_claim_unused_channel(true);
dma_channel_config dma_config = dma_channel_get_default_config(dma_channel);
channel_config_set_transfer_data_size(&dma_config, DMA_SIZE_32);
channel_config_set_read_increment(&dma_config, false);
channel_config_set_write_increment(&dma_config, false);
channel_config_set_dreq(&dma_config, DREQ_PIO0_TX0);
dma_channel_configure(
dma_channel,
&dma_config,
&pio0_hw->txf[0], // Write address
&SOME_VALUE, // Read address
(2 << 20), // Transfer count (a bunch so it can be timed in milliseconds)
false
);
// Configure PIO
const uint state_machine = 0;
uint prgm_offset = pio_add_program(pio0, &test_program);
pio_sm_config pio_config = test_program_get_default_config(prgm_offset);
pio_sm_init(pio0, state_machine, prgm_offset, &pio_config);
// Start recording duration
powman_timer_set_1khz_tick_source_xosc();
powman_timer_set_ms(0);
powman_timer_start();
// Start DMA and PIO
dma_channel_start(dma_channel);
pio_sm_set_enabled(pio0, state_machine, true);
// Wait for DMA to complete transfer
dma_channel_wait_for_finish_blocking(dma_channel);
// Print results
uint64_t end_time = powman_timer_get_ms();
printf("DMA transfer took %lld ms\n", end_time);
}
The PIO program with the mov instruction completes the transfer in half the time as the original. I believe this shows that there must be two DMA transfers in each iteration of the loop. I don't believe this effect is intentional because any read from the RX FIFO "registers" causes the DMA to throw away data. I spent more than a week re-reading data sheets because I assumed I must have been missing something, but I cannot for the life of me find any information which corroborates what I'm observing.
I think I can avoid this issue by either triggering DMA with an IRQ or padding the data buffer with empty slots so the important indices line up with pull block
instructions, but both of these workarounds have drawbacks.
Is anyone able to reproduce this issue? Is this somehow an intended/expected behavior?
Please forgive me if there's an obvious answer, I'm new to RP2350 but eager to learn :)
Misc info
- Board: Pico 2 W
- Project files generated using VS Code Pico Extension
- Using UART for stdio
- Compiling on MacOS
- Using pico-sdk v2.1.1
Activity