# SPDX-FileCopyrightText: Copyright (c) 2021 Scott Shawcroft for Adafruit Industries LLC # # SPDX-License-Identifier: MIT """ `adafruit_pioasm` ================================================================================ Simple assembler to convert pioasm to bytes * Author(s): Scott Shawcroft """ import array import re splitter = re.compile(r",\s*|\s+(?:,\s*)?").split __version__ = "0.0.0-auto.0" __repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_PIOASM.git" CONDITIONS = ["", "!x", "x--", "!y", "y--", "x!=y", "pin", "!osre"] IN_SOURCES = ["pins", "x", "y", "null", None, None, "isr", "osr"] OUT_DESTINATIONS = ["pins", "x", "y", "null", "pindirs", "pc", "isr", "exec"] WAIT_SOURCES = ["gpio", "pin", "irq", None] MOV_DESTINATIONS = ["pins", "x", "y", None, "exec", "pc", "isr", "osr"] MOV_SOURCES = ["pins", "x", "y", "null", None, "status", "isr", "osr"] MOV_OPS = [None, "~", "::", None] SET_DESTINATIONS = ["pins", "x", "y", None, "pindirs", None, None, None] def assemble(text_program): """Converts pioasm text to encoded instruction bytes""" # pylint: disable=too-many-branches,too-many-statements,too-many-locals assembled = [] program_name = None labels = {} instructions = [] sideset_count = 0 for line in text_program.split("\n"): line = line.strip() if not line: continue if ";" in line: line = line.split(";")[0].strip() if line.startswith(".program"): if program_name: raise RuntimeError("Multiple programs not supported") program_name = line.split()[1] elif line.startswith(".wrap_target"): if len(instructions) > 0: raise RuntimeError("wrap_target not supported") elif line.startswith(".wrap"): pass elif line.startswith(".side_set"): sideset_count = int(line.split()[1]) elif line.endswith(":"): label = line[:-1] if label in labels: raise SyntaxError(f"Duplicate label {repr(label)}") labels[label] = len(instructions) elif line: # Only add as an instruction if the line isn't empty instructions.append(line) max_delay = 2 ** (5 - sideset_count) - 1 assembled = [] for instruction in instructions: # print(instruction) instruction = splitter(instruction.strip()) delay = 0 if instruction[-1].endswith("]"): # Delay delay = int(instruction[-1].strip("[]")) if delay > max_delay: raise RuntimeError("Delay too long:", delay) instruction.pop() if len(instruction) > 1 and instruction[-2] == "side": sideset_value = int(instruction[-1]) if sideset_value > 2 ** sideset_count: raise RuntimeError("Sideset value too large") delay |= sideset_value << (5 - sideset_count) instruction.pop() instruction.pop() if instruction[0] == "nop": # mov delay y op y assembled.append(0b101_00000_010_00_010) elif instruction[0] == "jmp": # instr delay cnd addr assembled.append(0b000_00000_000_00000) target = instruction[-1] if target[:1] in "0123456789": assembled[-1] |= int(target) elif instruction[-1] in labels: assembled[-1] |= labels[target] else: raise SyntaxError(f"Invalid jmp target {repr(target)}") if len(instruction) > 2: assembled[-1] |= CONDITIONS.index(instruction[1]) << 5 elif instruction[0] == "wait": # instr delay p sr index assembled.append(0b001_00000_0_00_00000) polarity = int(instruction[1]) if not 0 <= polarity <= 1: raise RuntimeError("Invalid polarity") assembled[-1] |= polarity << 7 assembled[-1] |= WAIT_SOURCES.index(instruction[2]) << 5 num = int(instruction[3]) if not 0 <= num <= 31: raise RuntimeError("Wait num out of range") assembled[-1] |= num if instruction[-1] == "rel": assembled[-1] |= 0x10 # Set the high bit of the irq value elif instruction[0] == "in": # instr delay src count assembled.append(0b010_00000_000_00000) assembled[-1] |= IN_SOURCES.index(instruction[1]) << 5 count = int(instruction[-1]) if not 1 <= count <= 32: raise RuntimeError("Count out of range") assembled[-1] |= count & 0x1F # 32 is 00000 so we mask the top elif instruction[0] == "out": # instr delay dst count assembled.append(0b011_00000_000_00000) assembled[-1] |= OUT_DESTINATIONS.index(instruction[1]) << 5 count = int(instruction[-1]) if not 1 <= count <= 32: raise RuntimeError("Count out of range") assembled[-1] |= count & 0x1F # 32 is 00000 so we mask the top elif instruction[0] == "push" or instruction[0] == "pull": # instr delay d i b zero assembled.append(0b100_00000_0_0_0_00000) if instruction[0] == "pull": assembled[-1] |= 0x80 if instruction[-1] == "block" or not instruction[-1].endswith("block"): assembled[-1] |= 0x20 if len(instruction) > 1 and instruction[1] in ("ifempty", "iffull"): assembled[-1] |= 0x40 elif instruction[0] == "mov": # instr delay dst op src assembled.append(0b101_00000_000_00_000) assembled[-1] |= MOV_DESTINATIONS.index(instruction[1]) << 5 assembled[-1] |= MOV_SOURCES.index(instruction[-1]) if len(instruction) > 3: assembled[-1] |= MOV_OPS.index(instruction[-2]) << 3 elif instruction[0] == "irq": # instr delay z c w index assembled.append(0b110_00000_0_0_0_00000) if instruction[-1] == "rel": assembled[-1] |= 0x10 # Set the high bit of the irq value instruction.pop() num = int(instruction[-1]) if not 0 <= num <= 7: raise RuntimeError("Interrupt index out of range") assembled[-1] |= num if len(instruction) == 3: # after rel has been removed if instruction[1] == "wait": assembled[-1] |= 0x20 elif instruction[1] == "clear": assembled[-1] |= 0x40 # All other values are the default of set without waiting elif instruction[0] == "set": # instr delay dst data assembled.append(0b111_00000_000_00000) assembled[-1] |= SET_DESTINATIONS.index(instruction[1]) << 5 value = int(instruction[-1]) if not 0 <= value <= 31: raise RuntimeError("Set value out of range") assembled[-1] |= value else: raise RuntimeError("Unknown instruction:" + instruction) assembled[-1] |= delay << 8 # print(hex(assembled[-1])) return array.array("H", assembled)