micropython / micropython

MicroPython - a lean and efficient Python implementation for microcontrollers and constrained systems
https://micropython.org
Other
19.15k stars 7.67k forks source link

rp2 PIO StateMachine jump table to produce instructions #13439

Open jrmoserbaltimore opened 8 months ago

jrmoserbaltimore commented 8 months ago

An enhancement to the Pi Pico StateMachine, add a method producing jmp instructions referencing labels in the state machine. This could then be used to direct a jump to various places without decisions in the PIO program. This is already possible, just semi-difficult: as far as I know, there is no way to get the address of a label besides manually counting instructions.

If this is already doable, it's not well documented that I could find, so this becomes just a documentation bug.

For example, consider communicating with the General Instruments AY-3-8930. This has a simple enough interface and, for the most part, requires writing 8 bits to an address/data bus and 2 bits to a control bus indicating whether the bus is idle, read, write, or address. The system can communicate with this through the TX FIFO by writing words of [data][operation][address], each 8 bits, but this gets complex:

Instead of these 24 bits, one could use the whole 32 bits in the FIFO, making [operation] a jmp instruction:

This replaces the coded jump table with a single out instruction.

To get the jmp instruction, the StateMachine object would provide a member function to produce a jmp instruction using the particular label as a destination.

As for the example, this is the output section of a write-only PIO driver for the AY-3-8930. This uses 16-bit input of an address followed by data, and always writes.

# Set address
label("handle_output")
jmp(x_dec, "set_data")
out(pins,8) .side(0b11)
set(x, 0b1)
jmp("handle_clock")

# Set data
label("set_data")
out(pins,8) .side(0b10) [1]
jmp("handle_clock")

To expand this to read/write, I'd need to send another flag, copy it into y, and evaluate it against x:

# Set address
label("handle_output")
jmp(x_dec, "getset_data")
out(pins,8) .side(0b11)
set(x, 0b1)
jmp("handle_clock")

label("getset_data")
out(y,8)
set(x,0b0)
jmp(x_not_y, "get_data")

# Set data
label("set_data")
out(pins,8) .side(0b10) [1]
jmp("handle_clock")

# Get data
label("get_data")
out(pins,8) .side(0b01)
in(pins,8)
jmp("handle_clock")

Or by injecting a jmp:

# Set address
label("handle_output")
jmp(x_dec, "getset_data")
out(pins,8) .side(0b11)
set(x, 0b1)
jmp("handle_clock")

label("getset_data")
out(exec,16)

# Set data
label("set_data")
out(pins,8) .side(0b10) [1]
jmp("handle_clock")

# Get data
label("get_data")
out(pins,8) .side(0b01)
in(pins,8)
jmp("handle_clock")

This saves 2 instructions. For more complex protocols with multiple possible branch destinations, it saves 2 instructions per possible branch destination.

wodr commented 4 months ago

Have you tried to print(piogram) ? The instructions are just an array of uint16. You can modify them as you like. Have you though about having 2 programs , one for write one for read? For me this often made the piograms smaller.