Skip to content

Commit 00e6216

Browse files
authored
Merge pull request #29 from jepler/program-object
Add `Program` class, type information
2 parents fbafe00 + 68a5881 commit 00e6216

File tree

3 files changed

+225
-173
lines changed

3 files changed

+225
-173
lines changed

adafruit_pioasm.py

Lines changed: 197 additions & 169 deletions
Original file line numberDiff line numberDiff line change
@@ -30,178 +30,206 @@
3030
SET_DESTINATIONS = ["pins", "x", "y", None, "pindirs", None, None, None]
3131

3232

33-
def assemble(text_program):
34-
"""Converts pioasm text to encoded instruction bytes"""
35-
# pylint: disable=too-many-branches,too-many-statements,too-many-locals
36-
assembled = []
37-
program_name = None
38-
labels = {}
39-
instructions = []
40-
sideset_count = 0
41-
sideset_enable = 0
42-
for line in text_program.split("\n"):
43-
line = line.strip()
44-
if not line:
45-
continue
46-
if ";" in line:
47-
line = line.split(";")[0].strip()
48-
if line.startswith(".program"):
49-
if program_name:
50-
raise RuntimeError("Multiple programs not supported")
51-
program_name = line.split()[1]
52-
elif line.startswith(".wrap_target"):
53-
if len(instructions) > 0:
54-
raise RuntimeError("wrap_target not supported")
55-
elif line.startswith(".wrap"):
56-
pass
57-
elif line.startswith(".side_set"):
58-
sideset_count = int(line.split()[1])
59-
sideset_enable = 1 if "opt" in line else 0
60-
elif line.endswith(":"):
61-
label = line[:-1]
62-
if label in labels:
63-
raise SyntaxError(f"Duplicate label {repr(label)}")
64-
labels[label] = len(instructions)
65-
elif line:
66-
# Only add as an instruction if the line isn't empty
67-
instructions.append(line)
68-
69-
max_delay = 2 ** (5 - sideset_count - sideset_enable) - 1
70-
assembled = []
71-
for instruction in instructions:
72-
# print(instruction)
73-
instruction = splitter(instruction.strip())
74-
delay = 0
75-
if instruction[-1].endswith("]"): # Delay
76-
delay = int(instruction[-1].strip("[]"))
77-
if delay > max_delay:
78-
raise RuntimeError("Delay too long:", delay)
79-
instruction.pop()
80-
if len(instruction) > 1 and instruction[-2] == "side":
81-
if sideset_count == 0:
82-
raise RuntimeError("No side_set count set")
83-
sideset_value = int(instruction[-1])
84-
if sideset_value > 2 ** sideset_count:
85-
raise RuntimeError("Sideset value too large")
86-
delay |= sideset_value << (5 - sideset_count - sideset_enable)
87-
delay |= sideset_enable << 4
88-
instruction.pop()
89-
instruction.pop()
90-
91-
if instruction[0] == "nop":
92-
# mov delay y op y
93-
assembled.append(0b101_00000_010_00_010)
94-
elif instruction[0] == "jmp":
95-
# instr delay cnd addr
96-
assembled.append(0b000_00000_000_00000)
97-
target = instruction[-1]
98-
if target[:1] in "0123456789":
99-
assembled[-1] |= int(target)
100-
elif instruction[-1] in labels:
101-
assembled[-1] |= labels[target]
102-
else:
103-
raise SyntaxError(f"Invalid jmp target {repr(target)}")
33+
class Program: # pylint: disable=too-few-public-methods
34+
"""Encapsulates a program's instruction stream and configuration flags
35+
36+
Example::
37+
38+
program = adafruit_pioasm.Program(...)
39+
state_machine = rp2pio.StateMachine(program.assembled, ..., **program.pio_kwargs)
40+
41+
"""
42+
43+
def __init__(self, text_program: str) -> None:
44+
"""Converts pioasm text to encoded instruction bytes"""
45+
# pylint: disable=too-many-branches,too-many-statements,too-many-locals
46+
assembled = []
47+
program_name = None
48+
labels = {}
49+
instructions = []
50+
sideset_count = 0
51+
sideset_enable = 0
52+
for line in text_program.split("\n"):
53+
line = line.strip()
54+
if not line:
55+
continue
56+
if ";" in line:
57+
line = line.split(";")[0].strip()
58+
if line.startswith(".program"):
59+
if program_name:
60+
raise RuntimeError("Multiple programs not supported")
61+
program_name = line.split()[1]
62+
elif line.startswith(".wrap_target"):
63+
if len(instructions) > 0:
64+
raise RuntimeError("wrap_target not supported")
65+
elif line.startswith(".wrap"):
66+
pass
67+
elif line.startswith(".side_set"):
68+
sideset_count = int(line.split()[1])
69+
sideset_enable = 1 if "opt" in line else 0
70+
elif line.endswith(":"):
71+
label = line[:-1]
72+
if label in labels:
73+
raise SyntaxError(f"Duplicate label {repr(label)}")
74+
labels[label] = len(instructions)
75+
elif line:
76+
# Only add as an instruction if the line isn't empty
77+
instructions.append(line)
78+
79+
max_delay = 2 ** (5 - sideset_count - sideset_enable) - 1
80+
assembled = []
81+
for instruction in instructions:
82+
# print(instruction)
83+
instruction = splitter(instruction.strip())
84+
delay = 0
85+
if instruction[-1].endswith("]"): # Delay
86+
delay = int(instruction[-1].strip("[]"))
87+
if delay < 0:
88+
raise RuntimeError("Delay negative:", delay)
89+
if delay > max_delay:
90+
raise RuntimeError("Delay too long:", delay)
91+
instruction.pop()
92+
if len(instruction) > 1 and instruction[-2] == "side":
93+
if sideset_count == 0:
94+
raise RuntimeError("No side_set count set")
95+
sideset_value = int(instruction[-1])
96+
if sideset_value >= 2 ** sideset_count:
97+
raise RuntimeError("Sideset value too large")
98+
delay |= sideset_value << (5 - sideset_count - sideset_enable)
99+
delay |= sideset_enable << 4
100+
instruction.pop()
101+
instruction.pop()
102+
103+
if instruction[0] == "nop":
104+
# mov delay y op y
105+
assembled.append(0b101_00000_010_00_010)
106+
elif instruction[0] == "jmp":
107+
# instr delay cnd addr
108+
assembled.append(0b000_00000_000_00000)
109+
target = instruction[-1]
110+
if target[:1] in "0123456789":
111+
assembled[-1] |= int(target)
112+
elif instruction[-1] in labels:
113+
assembled[-1] |= labels[target]
114+
else:
115+
raise SyntaxError(f"Invalid jmp target {repr(target)}")
104116

105-
if len(instruction) > 2:
117+
if len(instruction) > 2:
118+
try:
119+
assembled[-1] |= CONDITIONS.index(instruction[1]) << 5
120+
except ValueError as exc:
121+
raise ValueError(
122+
f"Invalid jmp condition '{instruction[1]}'"
123+
) from exc
124+
125+
elif instruction[0] == "wait":
126+
# instr delay p sr index
127+
assembled.append(0b001_00000_0_00_00000)
128+
polarity = int(instruction[1])
129+
if not 0 <= polarity <= 1:
130+
raise RuntimeError("Invalid polarity")
131+
assembled[-1] |= polarity << 7
132+
assembled[-1] |= WAIT_SOURCES.index(instruction[2]) << 5
133+
num = int(instruction[3])
134+
if not 0 <= num <= 31:
135+
raise RuntimeError("Wait num out of range")
136+
assembled[-1] |= num
137+
if instruction[-1] == "rel":
138+
assembled[-1] |= 0x10 # Set the high bit of the irq value
139+
elif instruction[0] == "in":
140+
# instr delay src count
141+
assembled.append(0b010_00000_000_00000)
142+
assembled[-1] |= IN_SOURCES.index(instruction[1]) << 5
143+
count = int(instruction[-1])
144+
if not 1 <= count <= 32:
145+
raise RuntimeError("Count out of range")
146+
assembled[-1] |= count & 0x1F # 32 is 00000 so we mask the top
147+
elif instruction[0] == "out":
148+
# instr delay dst count
149+
assembled.append(0b011_00000_000_00000)
150+
assembled[-1] |= OUT_DESTINATIONS.index(instruction[1]) << 5
151+
count = int(instruction[-1])
152+
if not 1 <= count <= 32:
153+
raise RuntimeError("Count out of range")
154+
assembled[-1] |= count & 0x1F # 32 is 00000 so we mask the top
155+
elif instruction[0] == "push" or instruction[0] == "pull":
156+
# instr delay d i b zero
157+
assembled.append(0b100_00000_0_0_0_00000)
158+
if instruction[0] == "pull":
159+
assembled[-1] |= 0x80
160+
if instruction[-1] == "block" or not instruction[-1].endswith("block"):
161+
assembled[-1] |= 0x20
162+
if len(instruction) > 1 and instruction[1] in ("ifempty", "iffull"):
163+
assembled[-1] |= 0x40
164+
elif instruction[0] == "mov":
165+
# instr delay dst op src
166+
assembled.append(0b101_00000_000_00_000)
167+
assembled[-1] |= MOV_DESTINATIONS.index(instruction[1]) << 5
168+
source = instruction[-1]
169+
source_split = mov_splitter(source)
170+
if len(source_split) == 1:
171+
try:
172+
assembled[-1] |= MOV_SOURCES.index(source)
173+
except ValueError as exc:
174+
raise ValueError(f"Invalid mov source '{source}'") from exc
175+
else:
176+
assembled[-1] |= MOV_SOURCES.index(source_split[1])
177+
if source[:1] == "!":
178+
assembled[-1] |= 0x08
179+
elif source[:1] == "~":
180+
assembled[-1] |= 0x08
181+
elif source[:2] == "::":
182+
assembled[-1] |= 0x10
183+
else:
184+
raise RuntimeError("Invalid mov operator:", source[:1])
185+
if len(instruction) > 3:
186+
assembled[-1] |= MOV_OPS.index(instruction[-2]) << 3
187+
elif instruction[0] == "irq":
188+
# instr delay z c w index
189+
assembled.append(0b110_00000_0_0_0_00000)
190+
if instruction[-1] == "rel":
191+
assembled[-1] |= 0x10 # Set the high bit of the irq value
192+
instruction.pop()
193+
num = int(instruction[-1])
194+
if not 0 <= num <= 7:
195+
raise RuntimeError("Interrupt index out of range")
196+
assembled[-1] |= num
197+
if len(instruction) == 3: # after rel has been removed
198+
if instruction[1] == "wait":
199+
assembled[-1] |= 0x20
200+
elif instruction[1] == "clear":
201+
assembled[-1] |= 0x40
202+
# All other values are the default of set without waiting
203+
elif instruction[0] == "set":
204+
# instr delay dst data
205+
assembled.append(0b111_00000_000_00000)
106206
try:
107-
assembled[-1] |= CONDITIONS.index(instruction[1]) << 5
207+
assembled[-1] |= SET_DESTINATIONS.index(instruction[1]) << 5
108208
except ValueError as exc:
109209
raise ValueError(
110-
f"Invalid jmp condition '{instruction[1]}'"
210+
f"Invalid set destination '{instruction[1]}'"
111211
) from exc
112-
113-
elif instruction[0] == "wait":
114-
# instr delay p sr index
115-
assembled.append(0b001_00000_0_00_00000)
116-
polarity = int(instruction[1])
117-
if not 0 <= polarity <= 1:
118-
raise RuntimeError("Invalid polarity")
119-
assembled[-1] |= polarity << 7
120-
assembled[-1] |= WAIT_SOURCES.index(instruction[2]) << 5
121-
num = int(instruction[3])
122-
if not 0 <= num <= 31:
123-
raise RuntimeError("Wait num out of range")
124-
assembled[-1] |= num
125-
if instruction[-1] == "rel":
126-
assembled[-1] |= 0x10 # Set the high bit of the irq value
127-
elif instruction[0] == "in":
128-
# instr delay src count
129-
assembled.append(0b010_00000_000_00000)
130-
assembled[-1] |= IN_SOURCES.index(instruction[1]) << 5
131-
count = int(instruction[-1])
132-
if not 1 <= count <= 32:
133-
raise RuntimeError("Count out of range")
134-
assembled[-1] |= count & 0x1F # 32 is 00000 so we mask the top
135-
elif instruction[0] == "out":
136-
# instr delay dst count
137-
assembled.append(0b011_00000_000_00000)
138-
assembled[-1] |= OUT_DESTINATIONS.index(instruction[1]) << 5
139-
count = int(instruction[-1])
140-
if not 1 <= count <= 32:
141-
raise RuntimeError("Count out of range")
142-
assembled[-1] |= count & 0x1F # 32 is 00000 so we mask the top
143-
elif instruction[0] == "push" or instruction[0] == "pull":
144-
# instr delay d i b zero
145-
assembled.append(0b100_00000_0_0_0_00000)
146-
if instruction[0] == "pull":
147-
assembled[-1] |= 0x80
148-
if instruction[-1] == "block" or not instruction[-1].endswith("block"):
149-
assembled[-1] |= 0x20
150-
if len(instruction) > 1 and instruction[1] in ("ifempty", "iffull"):
151-
assembled[-1] |= 0x40
152-
elif instruction[0] == "mov":
153-
# instr delay dst op src
154-
assembled.append(0b101_00000_000_00_000)
155-
assembled[-1] |= MOV_DESTINATIONS.index(instruction[1]) << 5
156-
source = instruction[-1]
157-
source_split = mov_splitter(source)
158-
if len(source_split) == 1:
159-
try:
160-
assembled[-1] |= MOV_SOURCES.index(source)
161-
except ValueError as exc:
162-
raise ValueError(f"Invalid mov source '{source}'") from exc
212+
value = int(instruction[-1])
213+
if not 0 <= value <= 31:
214+
raise RuntimeError("Set value out of range")
215+
assembled[-1] |= value
163216
else:
164-
assembled[-1] |= MOV_SOURCES.index(source_split[1])
165-
if source[:1] == "!":
166-
assembled[-1] |= 0x08
167-
elif source[:1] == "~":
168-
assembled[-1] |= 0x08
169-
elif source[:2] == "::":
170-
assembled[-1] |= 0x10
171-
else:
172-
raise RuntimeError("Invalid mov operator:", source[:1])
173-
if len(instruction) > 3:
174-
assembled[-1] |= MOV_OPS.index(instruction[-2]) << 3
175-
elif instruction[0] == "irq":
176-
# instr delay z c w index
177-
assembled.append(0b110_00000_0_0_0_00000)
178-
if instruction[-1] == "rel":
179-
assembled[-1] |= 0x10 # Set the high bit of the irq value
180-
instruction.pop()
181-
num = int(instruction[-1])
182-
if not 0 <= num <= 7:
183-
raise RuntimeError("Interrupt index out of range")
184-
assembled[-1] |= num
185-
if len(instruction) == 3: # after rel has been removed
186-
if instruction[1] == "wait":
187-
assembled[-1] |= 0x20
188-
elif instruction[1] == "clear":
189-
assembled[-1] |= 0x40
190-
# All other values are the default of set without waiting
191-
elif instruction[0] == "set":
192-
# instr delay dst data
193-
assembled.append(0b111_00000_000_00000)
194-
try:
195-
assembled[-1] |= SET_DESTINATIONS.index(instruction[1]) << 5
196-
except ValueError as exc:
197-
raise ValueError(f"Invalid set destination '{instruction[1]}'") from exc
198-
value = int(instruction[-1])
199-
if not 0 <= value <= 31:
200-
raise RuntimeError("Set value out of range")
201-
assembled[-1] |= value
202-
else:
203-
raise RuntimeError("Unknown instruction:" + instruction[0])
204-
assembled[-1] |= delay << 8
205-
# print(bin(assembled[-1]))
206-
207-
return array.array("H", assembled)
217+
raise RuntimeError("Unknown instruction:" + instruction[0])
218+
assembled[-1] |= delay << 8
219+
# print(bin(assembled[-1]))
220+
221+
self.pio_kwargs = {
222+
"sideset_count": sideset_count,
223+
"sideset_enable": sideset_enable,
224+
}
225+
226+
self.assembled = array.array("H", assembled)
227+
228+
229+
def assemble(program_text: str) -> array.array:
230+
"""Converts pioasm text to encoded instruction bytes
231+
232+
In new code, prefer to use the `Program` class so that the extra arguments
233+
such as the details about side-set pins can be easily passsed to the
234+
``StateMachine`` constructor."""
235+
return Program(program_text).assembled

0 commit comments

Comments
 (0)