rp2: asm_pio_encode() sideset/delay faulty logic
Port, board and/or hardware
RP2040
MicroPython version
MicroPython v1.24.1 on 2024-11-29; Raspberry Pi Pico with RP2040
Reproduction
In a PIO instruction, 5 bits are reserved for sideset/delay. Using one or more sideset pins reduces the amount of bits left for delay. However, the asm_pio_encode() complains about delay too large with the following invocation:
rp2.asm_pio_encode('mov(x,y).side(1) [7]', 2, sideset_opt=True)
The second argument to the function is sideset count and is inclusive of the sideset enable bit which is required when sideset is optional. In this example two bits are reserved for sideset purposes and three are left for delay allowing a maximum of 7.
Expected behaviour
The function is expected to return 48930 equivalent to 0b1011111100100010.
Note the sideset/delay in bits 8-12 (counting up from 0), 11111, with the two most significant bits being the sideset enable and the actual sideset bit and the rest of the bits signifying a delay value of 7.
Observed behaviour
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "rp2.py", line 298, in asm_pio_encode
File "<string>", line 1, in <module>
File "rp2.py", line 79, in getitem
File "rp2.py", line 84, in delay
PIOASMError: delay too large
Additional Information
I came across this bug when figuring out why the asm_pio_encode() function would not seem to work correctly with sideset count equal to 1 while sideset_opt=True.
The documentation did not mention about sideset count being inclusive of sideset enable bit. That information was found in the codebase as a comment. It follows from the SIDESET_COUNT count in SM#_PINCTRL register of RP2040 where the sideset enable bit is included in the count.
Code of Conduct
Yes, I agree
rp2: Add execctrl option for the PIO decorator.
Summary
The rp2 PIO assembler includes support for instructions like
mov(y, status)
which can be used to detect when the TX and RX FIFOs pass a configurable threshold, for example to trigger an interrupt handler to fill/empty them.
However, to actually use these instructions, the relevant threshold needs to be configured in the execctrl register, and at present, only the side_pindir bit can be set.
Expose an execctrl= keyword argument in the PIO decorator to do this, together with constants in rp2.PIO which can be used like
@rp2.asm_pio(execctrl = rp2.PIO.STATUS_TXLEVEL + n)
def program():
[...]
mov(y, status)
jmp(not_y, "skip")
irq(rel(0))
label("skip")
[...]
to generate an interrupt if the TX FIFO has fewer than n words left.
This also fixes a bug with the existing side_pindir option, which is meant to be boolean. Previously, if it was passed an integer other than 0 or 1, unintended high bits in the execctrl value would be set, not just the intended bit 29.
Testing
I've tested (and used) this functionality on a real rp2350 board.
Unfortunately there are no in-tree tests at all for the rp2 PIO assembler yet, so there isn't a natural place to add a simple test for this new option. I propose to write a full set of tests to cover as much as I can of that decorator, but will submit in a separate PR.
Trade-offs and Alternatives
It is possible to work around the lack of assembler execctrl support by patching the word of the program array that corresponds to the execctrl register, for example
program[4] = program[4] & ~127 | 1 # set status if txlevel < 1
but this assumes undocumented internal details of the rp2 pio module.