circuitpython/ports/rp2/modules/rp2.py
root 769453c750 rp2/rp2_pio: Fix use of PIO2 in prog data structure.
The RP2350 PIO2 State Machines (8, 9, 10, 11) did not work.  The data
structure used to pass the PIO arguments was missing an entry for PIO2,
thus causing the PIO2 instances to write wrong data to wrong locations.

Fixes issue #17509.

Signed-off-by: Matt Westveld <github@intergalacticmicro.com>
2025-08-01 22:38:00 +10:00

297 lines
7.6 KiB
Python

# rp2 module: uses C code from _rp2, plus asm_pio decorator implemented in Python.
# MIT license; Copyright (c) 2020-2021 Damien P. George
from _rp2 import *
from micropython import const
_PROG_DATA = const(0)
_PROG_OFFSET_PIO0 = const(1)
_PROG_OFFSET_PIO1 = const(2)
_PROG_OFFSET_PIO2 = const(3)
_PROG_EXECCTRL = const(4)
_PROG_SHIFTCTRL = const(5)
_PROG_OUT_PINS = const(6)
_PROG_SET_PINS = const(7)
_PROG_SIDESET_PINS = const(8)
_PROG_MAX_FIELDS = const(9)
class PIOASMError(Exception):
pass
class PIOASMEmit:
def __init__(
self,
*,
out_init=None,
set_init=None,
sideset_init=None,
side_pindir=False,
in_shiftdir=PIO.SHIFT_LEFT,
out_shiftdir=PIO.SHIFT_LEFT,
autopush=False,
autopull=False,
push_thresh=32,
pull_thresh=32,
fifo_join=PIO.JOIN_NONE,
):
# array is a built-in module so importing it here won't require
# scanning the filesystem.
from array import array
self.labels = {}
execctrl = side_pindir << 29
shiftctrl = (
fifo_join << 30
| (pull_thresh & 0x1F) << 25
| (push_thresh & 0x1F) << 20
| out_shiftdir << 19
| in_shiftdir << 18
| autopull << 17
| autopush << 16
)
self.prog = [array("H"), -1, -1, -1, execctrl, shiftctrl, out_init, set_init, sideset_init]
self.wrap_used = False
if sideset_init is None:
self.sideset_count = 0
elif isinstance(sideset_init, int):
self.sideset_count = 1
else:
self.sideset_count = len(sideset_init)
def start_pass(self, pass_):
if pass_ == 1:
if not self.wrap_used and self.num_instr:
self.wrap()
self.delay_max = 31
if self.sideset_count:
self.sideset_opt = self.num_sideset != self.num_instr
if self.sideset_opt:
self.prog[_PROG_EXECCTRL] |= 1 << 30
self.sideset_count += 1
self.delay_max >>= self.sideset_count
self.pass_ = pass_
self.num_instr = 0
self.num_sideset = 0
def __getitem__(self, key):
return self.delay(key)
def delay(self, delay):
if self.pass_ > 0:
if delay > self.delay_max:
raise PIOASMError("delay too large")
self.prog[_PROG_DATA][-1] |= delay << 8
return self
def side(self, value):
self.num_sideset += 1
if self.pass_ > 0:
if self.sideset_count == 0:
raise PIOASMError("no sideset")
elif value >= (1 << self.sideset_count):
raise PIOASMError("sideset too large")
set_bit = 13 - self.sideset_count
self.prog[_PROG_DATA][-1] |= self.sideset_opt << 12 | value << set_bit
return self
def wrap_target(self):
self.prog[_PROG_EXECCTRL] |= self.num_instr << 7
def wrap(self):
assert self.num_instr
self.prog[_PROG_EXECCTRL] |= (self.num_instr - 1) << 12
self.wrap_used = True
def label(self, label):
if self.pass_ == 0:
if label in self.labels:
raise PIOASMError("duplicate label {}".format(label))
self.labels[label] = self.num_instr
def word(self, instr, label=None):
self.num_instr += 1
if self.pass_ > 0:
if label is None:
label = 0
else:
if label not in self.labels:
raise PIOASMError("unknown label {}".format(label))
label = self.labels[label]
self.prog[_PROG_DATA].append(instr | label)
return self
def nop(self):
return self.word(0xA042)
def jmp(self, cond, label=None):
if label is None:
label = cond
cond = 0 # always
return self.word(0x0000 | cond << 5, label)
def wait(self, polarity, src, index):
if src == 6:
src = 1 # "pin"
elif src != 0:
src = 2 # "irq"
return self.word(0x2000 | polarity << 7 | src << 5 | index)
def in_(self, src, data):
if not 0 < data <= 32:
raise PIOASMError("invalid bit count {}".format(data))
return self.word(0x4000 | src << 5 | data & 0x1F)
def out(self, dest, data):
if dest == 8:
dest = 7 # exec
if not 0 < data <= 32:
raise PIOASMError("invalid bit count {}".format(data))
return self.word(0x6000 | dest << 5 | data & 0x1F)
def push(self, value=0, value2=0):
value |= value2
if not value & 1:
value |= 0x20 # block by default
return self.word(0x8000 | (value & 0x60))
def pull(self, value=0, value2=0):
value |= value2
if not value & 1:
value |= 0x20 # block by default
return self.word(0x8080 | (value & 0x60))
def mov(self, dest, src):
if dest == 8:
dest = 4 # exec
return self.word(0xA000 | dest << 5 | src)
def irq(self, mod, index=None):
if index is None:
index = mod
mod = 0 # no modifiers
return self.word(0xC000 | (mod & 0x60) | index)
def set(self, dest, data):
return self.word(0xE000 | dest << 5 | data)
_pio_funcs = {
# source constants for wait
"gpio": 0,
# "pin": see below, translated to 1
# "irq": see below function, translated to 2
# source/dest constants for in_, out, mov, set
"pins": 0,
"x": 1,
"y": 2,
"null": 3,
"pindirs": 4,
"pc": 5,
"status": 5,
"isr": 6,
"osr": 7,
"exec": 8, # translated to 4 for mov, 7 for out
# operation functions for mov's src
"invert": lambda x: x | 0x08,
"reverse": lambda x: x | 0x10,
# jmp condition constants
"not_x": 1,
"x_dec": 2,
"not_y": 3,
"y_dec": 4,
"x_not_y": 5,
"pin": 6,
"not_osre": 7,
# constants for push, pull
"noblock": 0x01,
"block": 0x21,
"iffull": 0x40,
"ifempty": 0x40,
# constants and modifiers for irq
# "noblock": see above
# "block": see above
"clear": 0x40,
"rel": lambda x: x | 0x10,
}
_pio_directives = (
"wrap_target",
"wrap",
"label",
)
_pio_instructions = (
"word",
"nop",
"jmp",
"wait",
"in_",
"out",
"push",
"pull",
"mov",
"irq",
"set",
)
def asm_pio(**kw):
emit = PIOASMEmit(**kw)
def dec(f):
nonlocal emit
gl = f.__globals__
old_gl = gl.copy()
gl.clear()
gl.update(_pio_funcs)
for name in _pio_directives:
gl[name] = getattr(emit, name)
for name in _pio_instructions:
gl[name] = getattr(emit, name)
emit.start_pass(0)
f()
emit.start_pass(1)
f()
gl.clear()
gl.update(old_gl)
return emit.prog
return dec
# sideset_count is inclusive of enable bit
def asm_pio_encode(instr, sideset_count, sideset_opt=False):
emit = PIOASMEmit()
emit.sideset_count = sideset_count
emit.sideset_opt = sideset_opt != 0
emit.delay_max = 31 >> (emit.sideset_count + emit.sideset_opt)
emit.pass_ = 1
emit.num_instr = 0
emit.num_sideset = 0
gl = _pio_funcs
for name in _pio_instructions:
gl[name] = getattr(emit, name)
gl["jmp"] = None # emit.jmp currently not supported
try:
exec(instr, gl)
finally:
for name in _pio_instructions:
del gl[name]
if len(emit.prog[_PROG_DATA]) != 1:
raise PIOASMError("expecting exactly 1 instruction")
return emit.prog[_PROG_DATA][0]