/ adafruit_pioasm.py
adafruit_pioasm.py
1 # SPDX-FileCopyrightText: Copyright (c) 2021 Scott Shawcroft for Adafruit Industries LLC 2 # 3 # SPDX-License-Identifier: MIT 4 """ 5 `adafruit_pioasm` 6 ================================================================================ 7 8 Simple assembler to convert pioasm to bytes 9 10 11 * Author(s): Scott Shawcroft 12 """ 13 14 import array 15 16 __version__ = "0.0.0-auto.0" 17 __repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_PIOASM.git" 18 19 CONDITIONS = ["", "!x", "x--", "!y", "y--", "x!=y", "pin", "!osre"] 20 IN_SOURCES = ["pins", "x", "y", "null", None, None, "isr", "osr"] 21 OUT_DESTINATIONS = ["pins", "x", "y", "null", "pindirs", "pc", "isr", "exec"] 22 WAIT_SOURCES = ["gpio", "pin", "irq", None] 23 MOV_DESTINATIONS = ["pins", "x", "y", None, "exec", "pc", "isr", "osr"] 24 MOV_SOURCES = ["pins", "x", "y", "null", None, "status", "isr", "osr"] 25 MOV_OPS = [None, "~", "::", None] 26 SET_DESTINATIONS = ["pins", "x", "y", None, "pindirs", None, None, None] 27 28 29 def assemble(text_program): 30 """Converts pioasm text to encoded instruction bytes""" 31 # pylint: disable=too-many-branches,too-many-statements 32 assembled = [] 33 program_name = None 34 labels = {} 35 instructions = [] 36 sideset_count = 0 37 for line in text_program.split("\n"): 38 line = line.strip() 39 if not line: 40 continue 41 if ";" in line: 42 line = line.split(";")[0].strip() 43 if line.startswith(".program"): 44 if program_name: 45 raise RuntimeError("Multiple programs not supported") 46 program_name = line.split()[1] 47 elif line.startswith(".wrap_target"): 48 if len(instructions) > 0: 49 raise RuntimeError("wrap_target not supported") 50 elif line.startswith(".wrap"): 51 pass 52 elif line.startswith(".side_set"): 53 sideset_count = int(line.split()[1]) 54 elif line.endswith(":"): 55 labels[line[:-1]] = len(instructions) 56 elif line: 57 # Only add as an instruction if the line isn't empty 58 instructions.append(line) 59 60 max_delay = 2 ** (5 - sideset_count) - 1 61 assembled = [] 62 for instruction in instructions: 63 # print(instruction) 64 instruction = instruction.split() 65 delay = 0 66 if instruction[-1].endswith("]"): # Delay 67 delay = int(instruction[-1].strip("[]")) 68 if delay > max_delay: 69 raise RuntimeError("Delay too long:", delay) 70 instruction.pop() 71 if len(instruction) > 1 and instruction[-2] == "side": 72 sideset_value = int(instruction[-1]) 73 if sideset_value > 2 ** sideset_count: 74 raise RuntimeError("Sideset value too large") 75 delay |= sideset_value << (5 - sideset_count) 76 instruction.pop() 77 instruction.pop() 78 79 if instruction[0] == "nop": 80 # mov delay y op y 81 assembled.append(0b101_00000_010_00_010) 82 elif instruction[0] == "jmp": 83 # instr delay cnd addr 84 assembled.append(0b000_00000_000_00000) 85 if instruction[-1] in labels: 86 assembled[-1] |= labels[instruction[-1]] 87 else: 88 assembled[-1] |= int(instruction[-1]) 89 90 if len(instruction) > 2: 91 assembled[-1] |= CONDITIONS.index(instruction[1]) << 5 92 93 elif instruction[0] == "wait": 94 # instr delay p sr index 95 assembled.append(0b001_00000_0_00_00000) 96 polarity = int(instruction[1]) 97 if not 0 <= polarity <= 1: 98 raise RuntimeError("Invalid polarity") 99 assembled[-1] |= polarity << 7 100 assembled[-1] |= WAIT_SOURCES.index(instruction[2]) << 5 101 num = int(instruction[3]) 102 if not 0 <= num <= 31: 103 raise RuntimeError("Wait num out of range") 104 assembled[-1] |= num 105 if instruction[-1] == "rel": 106 assembled[-1] |= 0x10 # Set the high bit of the irq value 107 elif instruction[0] == "in": 108 # instr delay src count 109 assembled.append(0b010_00000_000_00000) 110 assembled[-1] |= IN_SOURCES.index(instruction[1]) << 5 111 count = int(instruction[-1]) 112 if not 1 <= count <= 32: 113 raise RuntimeError("Count out of range") 114 assembled[-1] |= count & 0x1F # 32 is 00000 so we mask the top 115 elif instruction[0] == "out": 116 # instr delay dst count 117 assembled.append(0b011_00000_000_00000) 118 assembled[-1] |= OUT_DESTINATIONS.index(instruction[1]) << 5 119 count = int(instruction[-1]) 120 if not 1 <= count <= 32: 121 raise RuntimeError("Count out of range") 122 assembled[-1] |= count & 0x1F # 32 is 00000 so we mask the top 123 elif instruction[0] == "push" or instruction[0] == "pull": 124 # instr delay d i b zero 125 assembled.append(0b100_00000_0_0_0_00000) 126 if instruction[0] == "pull": 127 assembled[-1] |= 0x80 128 if instruction[-1] == "block" or not instruction[-1].endswith("block"): 129 assembled[-1] |= 0x20 130 if len(instruction) > 1 and instruction[1] in ("ifempty", "iffull"): 131 assembled[-1] |= 0x40 132 elif instruction[0] == "mov": 133 # instr delay dst op src 134 assembled.append(0b101_00000_000_00_000) 135 assembled[-1] |= MOV_DESTINATIONS.index(instruction[1]) << 5 136 assembled[-1] |= MOV_SOURCES.index(instruction[-1]) 137 if len(instruction) > 3: 138 assembled[-1] |= MOV_OPS.index(instruction[-2]) << 3 139 elif instruction[0] == "irq": 140 # instr delay z c w index 141 assembled.append(0b110_00000_0_0_0_00000) 142 if instruction[-1] == "rel": 143 assembled[-1] |= 0x10 # Set the high bit of the irq value 144 instruction.pop() 145 num = int(instruction[-1]) 146 if not 0 <= num <= 7: 147 raise RuntimeError("Interrupt index out of range") 148 assembled[-1] |= num 149 if len(instruction) == 3: # after rel has been removed 150 if instruction[1] == "wait": 151 assembled[-1] |= 0x20 152 elif instruction[1] == "clear": 153 assembled[-1] |= 0x40 154 # All other values are the default of set without waiting 155 elif instruction[0] == "set": 156 # instr delay dst data 157 assembled.append(0b111_00000_000_00000) 158 assembled[-1] |= SET_DESTINATIONS.index(instruction[1]) << 5 159 value = int(instruction[-1]) 160 if not 0 <= value <= 31: 161 raise RuntimeError("Set value out of range") 162 assembled[-1] |= value 163 else: 164 raise RuntimeError("Unknown instruction:" + instruction) 165 assembled[-1] |= delay << 8 166 # print(hex(assembled[-1])) 167 168 return array.array("H", assembled)