circuitpython/ports/rp2/modules/rp2.py
oli 0a9335ecaa rp2/rp2_pio: Support exec with sideset.
The rp2.StateMachine.exec errors when supplying a sideset action.  This
commit passes the sideset_opt from the StateMachine though to the parser.
It also adds some value validation to the sideset operator.

Additionally, the "word" method is added to the exec to allow any other
unsupported opcodes.

Fixes issue #7924.
2021-11-19 13:35:28 +11:00

303 lines
8.0 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_EXECCTRL = const(3)
_PROG_SHIFTCTRL = const(4)
_PROG_OUT_PINS = const(5)
_PROG_SET_PINS = const(6)
_PROG_SIDESET_PINS = const(7)
_PROG_MAX_FIELDS = const(8)
class PIOASMError(Exception):
pass
class PIOASMEmit:
def __init__(
self,
*,
out_init=None,
set_init=None,
sideset_init=None,
in_shiftdir=0,
out_shiftdir=0,
autopush=False,
autopull=False,
push_thresh=32,
pull_thresh=32,
fifo_join=0
):
# uarray is a built-in module so importing it here won't require
# scanning the filesystem.
from uarray import array
self.labels = {}
execctrl = 0
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, 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 not label 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,
# functions
"wrap_target": None,
"wrap": None,
"label": None,
"word": None,
"nop": None,
"jmp": None,
"wait": None,
"in_": None,
"out": None,
"push": None,
"pull": None,
"mov": None,
"irq": None,
"set": None,
}
def asm_pio(**kw):
emit = PIOASMEmit(**kw)
def dec(f):
nonlocal emit
gl = _pio_funcs
gl["wrap_target"] = emit.wrap_target
gl["wrap"] = emit.wrap
gl["label"] = emit.label
gl["word"] = emit.word
gl["nop"] = emit.nop
gl["jmp"] = emit.jmp
gl["wait"] = emit.wait
gl["in_"] = emit.in_
gl["out"] = emit.out
gl["push"] = emit.push
gl["pull"] = emit.pull
gl["mov"] = emit.mov
gl["irq"] = emit.irq
gl["set"] = emit.set
old_gl = f.__globals__.copy()
f.__globals__.clear()
f.__globals__.update(gl)
emit.start_pass(0)
f()
emit.start_pass(1)
f()
f.__globals__.clear()
f.__globals__.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
gl["word"] = emit.word
gl["nop"] = emit.nop
# gl["jmp"] = emit.jmp currently not supported
gl["wait"] = emit.wait
gl["in_"] = emit.in_
gl["out"] = emit.out
gl["push"] = emit.push
gl["pull"] = emit.pull
gl["mov"] = emit.mov
gl["irq"] = emit.irq
gl["set"] = emit.set
exec(instr, gl)
if len(emit.prog[_PROG_DATA]) != 1:
raise PIOASMError("expecting exactly 1 instruction")
return emit.prog[_PROG_DATA][0]