forked from adafruit/Adafruit_CircuitPython_PIOASM
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathadafruit_pioasm.py
More file actions
188 lines (174 loc) · 7.75 KB
/
Copy pathadafruit_pioasm.py
File metadata and controls
188 lines (174 loc) · 7.75 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
# 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
mov_splitter = re.compile("!|~|::").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"]
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
source = instruction[-1]
source_split = mov_splitter(source)
if len(source_split) == 1:
assembled[-1] |= MOV_SOURCES.index(source)
else:
assembled[-1] |= MOV_SOURCES.index(source_split[1])
if source[:1] == "!":
assembled[-1] |= 0x08
elif source[:1] == "~":
assembled[-1] |= 0x08
elif source[:2] == "::":
assembled[-1] |= 0x10
else:
raise RuntimeError("Invalid mov operator:", source[:1])
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)