/ 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)