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>
297 lines
7.6 KiB
Python
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]
|