|
30 | 30 | SET_DESTINATIONS = ["pins", "x", "y", None, "pindirs", None, None, None]
|
31 | 31 |
|
32 | 32 |
|
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)}") |
104 | 116 |
|
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) |
106 | 206 | try:
|
107 |
| - assembled[-1] |= CONDITIONS.index(instruction[1]) << 5 |
| 207 | + assembled[-1] |= SET_DESTINATIONS.index(instruction[1]) << 5 |
108 | 208 | except ValueError as exc:
|
109 | 209 | raise ValueError(
|
110 |
| - f"Invalid jmp condition '{instruction[1]}'" |
| 210 | + f"Invalid set destination '{instruction[1]}'" |
111 | 211 | ) 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 |
163 | 216 | 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