0a9335ecaa
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.
303 lines
8.0 KiB
Python
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]
|