1 /*
2  * Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
3  *
4  * SPDX-License-Identifier: BSD-3-Clause
5  */
6 
7 #include <cstdio>
8 #include <iterator>
9 #include "pio_assembler.h"
10 #include "parser.hpp"
11 
12 #ifdef _MSC_VER
13 #pragma warning(disable : 4996) // fopen
14 #endif
15 
16 using syntax_error = yy::parser::syntax_error;
17 
18 std::string output_format::default_name = "c-sdk";
19 
pio_assembler()20 pio_assembler::pio_assembler() {
21 }
22 
generate(std::shared_ptr<output_format> _format,const std::string & _source,const std::string & _dest,const std::vector<std::string> & _options)23 int pio_assembler::generate(std::shared_ptr<output_format> _format, const std::string &_source,
24                             const std::string &_dest, const std::vector<std::string> &_options) {
25     format = _format;
26     source = _source;
27     dest = _dest;
28     options = _options;
29     location.initialize(&source);
30     scan_begin();
31     yy::parser parse(*this);
32 //    parse.set_debug_level(false);
33     int res = parse();
34     scan_end();
35     return res;
36 }
37 
add_instruction(std::shared_ptr<instruction> inst)38 void program::add_instruction(std::shared_ptr<instruction> inst) {
39     uint limit = MAX_INSTRUCTIONS;
40     if (instructions.size() >= limit) {
41         // todo take offset into account
42         std::stringstream msg;
43         msg << "program instruction limit of " << limit << " instruction(s) exceeded";
44         throw syntax_error(inst->location, msg.str());
45     }
46     if (!sideset_opt && !inst->sideset) {
47         std::stringstream msg;
48         msg << "instruction requires 'side' to specify side set value for the instruction because non optional sideset was specified for the program at " << sideset.location;
49         throw syntax_error(inst->location, msg.str());
50     }
51     inst->pre_validate(*this);
52     instructions.push_back(inst);
53 }
54 
55 using syntax_error = syntax_error;
56 
set_pio_version(const yy::location & l,int version)57 void program::set_pio_version(const yy::location &l, int version) {
58     if (version < 0 || version > 1) {
59         throw syntax_error(l, "only PIO versions 0 (rp2040) and 1 (rp2350) are supported");
60     }
61     pio_version = version;
62 }
63 
set_clock_div(const yy::location & l,float clock_div)64 void program::set_clock_div(const yy::location &l, float clock_div) {
65     if (clock_div < 1.0f || clock_div >= 65536.0f) {
66         throw syntax_error(l, "clock divider must be between 1 and 65546");
67     }
68     clock_div_int = (uint16_t)clock_div;
69     if (clock_div_int == 0) {
70         clock_div_frac = 0;
71     } else {
72         clock_div_frac = (uint8_t)((clock_div - (float)clock_div_frac) * (1u << 8u));
73     }
74 }
75 
set_fifo_config(const yy::location & l,fifo_config config)76 void program::set_fifo_config(const yy::location &l, fifo_config config) {
77     fifo_loc = l;
78     fifo = config;
79 }
80 
add_symbol(std::shared_ptr<symbol> symbol)81 void program::add_symbol(std::shared_ptr<symbol> symbol) {
82     const auto &existing = pioasm->get_symbol(symbol->name, this);
83     if (existing) {
84         std::stringstream msg;
85         if (symbol->is_label != existing->is_label) {
86             msg << "'" << symbol->name << "' was already defined as a " << (existing->is_label ? "label" : "value")
87                 << " at " << existing->location;
88         } else if (symbol->is_label) {
89             msg << "label '" << symbol->name << "' was already defined at " << existing->location;
90         } else {
91             msg << "'" << symbol->name << "' was already defined at " << existing->location;
92         }
93         throw syntax_error(symbol->location, msg.str());
94     }
95     symbols.insert(std::pair<std::string, std::shared_ptr<::symbol>>(symbol->name, symbol));
96     ordered_symbols.push_back(symbol);
97 }
98 
resolve(const program & program)99 int resolvable::resolve(const program &program) {
100     return resolve(program.pioasm, &program);
101 }
102 
resolve(pio_assembler * pioasm,const program * program,const resolvable & scope)103 int unary_operation::resolve(pio_assembler *pioasm, const program *program, const resolvable &scope) {
104     int value = arg->resolve(pioasm, program, scope);
105     switch (op) {
106         case negate:
107             return -value;
108         case reverse: {
109             // slow is fine
110             uint result = 0;
111             for (uint i = 0; i < 32; i++) {
112                 result <<= 1u;
113                 if (value & 1u) {
114                     result |= 1u;
115                 }
116                 value >>= 1u;
117             }
118             return result;
119         }
120         default:
121             throw syntax_error(location, "internal error");
122     }
123 }
124 
resolve(pio_assembler * pioasm,const program * program,const resolvable & scope)125 int binary_operation::resolve(pio_assembler *pioasm, const program *program, const resolvable &scope) {
126     int lvalue = left->resolve(pioasm, program, scope);
127     int rvalue = right->resolve(pioasm, program, scope);
128     switch (op) {
129         case add:
130             return lvalue + rvalue;
131         case subtract:
132             return lvalue - rvalue;
133         case multiply:
134             return lvalue * rvalue;
135         case divide:
136             return lvalue / rvalue;
137         case and_:
138             return lvalue & rvalue;
139         case or_:
140             return lvalue | rvalue;
141         case xor_:
142             return lvalue ^ rvalue;
143         case shl_:
144             return lvalue << rvalue;
145         case shr_:
146             return lvalue >> rvalue;
147         default:
148             throw syntax_error(location, "internal error");
149     }
150 }
151 
set_wrap(const yy::location & l)152 void program::set_wrap(const yy::location &l) {
153     if (wrap) {
154         std::stringstream msg;
155         msg << ".wrap was already specified at " << wrap->location;
156         throw syntax_error(l, msg.str());
157     }
158     if (instructions.empty()) {
159         throw syntax_error(l, ".wrap cannot be placed before the first program instruction");
160     }
161     wrap = resolvable_int(l, instructions.size() - 1);
162 }
163 
set_wrap_target(const yy::location & l)164 void program::set_wrap_target(const yy::location &l) {
165     if (wrap_target) {
166         std::stringstream msg;
167         msg << ".wrap_target was already specified at " << wrap_target->location;
168         throw syntax_error(l, msg.str());
169     }
170     wrap_target = resolvable_int(l, instructions.size());
171 }
172 
add_code_block(const code_block & block)173 void program::add_code_block(const code_block &block) {
174     code_blocks[block.lang].push_back(block);
175 }
176 
add_lang_opt(std::string lang,std::string name,std::string value)177 void program::add_lang_opt(std::string lang, std::string name, std::string value) {
178     lang_opts[lang].emplace_back(name, value);
179 }
180 
finalize()181 void program::finalize() {
182     if (mov_status.type != mov_status_type::unspecified) {
183         uint n = mov_status.n->resolve(*this);
184         if (mov_status.type == mov_status_type::irq_set) {
185             if (n > 7) throw syntax_error(mov_status.n->location, "irq number should be >= 0 and <= 7");
186             mov_status.final_n = mov_status.param * 8 + n;
187         } else {
188             if (n > 31) throw syntax_error(mov_status.n->location, "fido depth should be >= 0 and <= 31");
189             mov_status.final_n = n;
190         }
191     }
192     if (in.pin_count) {
193         in.final_pin_count = in.pin_count->resolve(*this);
194         if (!pio_version && in.final_pin_count != 32) throw syntax_error(in.pin_count->location, "in pin count must be 32 for PIO version 0");
195         if (in.final_pin_count < 1 || in.final_pin_count > 32) throw syntax_error(in.pin_count->location, "in pin count should be >= 1 and <= 32");
196         in.final_threshold = in.threshold->resolve(*this);
197         if (in.final_threshold < 1 || in.final_threshold > 32) throw syntax_error(in.threshold->location, "threshold should be >= 1 and <= 32");
198     }
199     if (out.pin_count) {
200         out.final_pin_count = out.pin_count->resolve(*this);
201         if (out.final_pin_count < 0 || out.final_pin_count > 32) throw syntax_error(out.pin_count->location, "out pin count should be >= 0 and <= 32");
202         out.final_threshold = out.threshold->resolve(*this);
203         if (out.final_threshold < 1 || out.final_threshold > 32) throw syntax_error(out.threshold->location, "threshold should be >= 1 and <= 32");
204     }
205     if (set_count.value) {
206         final_set_count = set_count.value->resolve(*this);
207         if (final_set_count < 0 || final_set_count > 5) throw syntax_error(set_count.location, "set pin count should be >= 0 and <= 5");
208     }
209     if (sideset.value) {
210         int bits = sideset.value->resolve(*this);
211         if (bits < 0) {
212             throw syntax_error(sideset.value->location, "number of side set bits must be positive");
213         }
214         sideset_max = (1u << bits) - 1;
215         if (sideset_opt) bits++;
216         sideset_bits_including_opt = bits;
217         if (bits > 5) {
218             if (sideset_opt)
219                 throw syntax_error(sideset.value->location, "maximum number of side set bits with optional is 4");
220             else
221                 throw syntax_error(sideset.value->location, "maximum number of side set bits is 5");
222         }
223         delay_max = (1u << (5 - bits)) - 1;
224     } else {
225         sideset_max = 0;
226         delay_max = 31;
227     }
228     if (fifo != fifo_config::rx && fifo != fifo_config::tx && fifo != fifo_config::txrx) {
229         std::stringstream msg;
230         if (in.pin_count && in.autop) {
231             msg << "autopush is incompatible with your selected FIFO configuration specified at " << fifo_loc;
232             throw syntax_error(in.location, msg.str());
233         }
234     }
235 }
236 
resolve(pio_assembler * pioasm,const program * program,const resolvable & scope)237 int name_ref::resolve(pio_assembler *pioasm, const program *program, const resolvable &scope) {
238     auto symbol = pioasm->get_symbol(name, program);
239     if (symbol) {
240         if (symbol->resolve_started) {
241             std::stringstream msg;
242             msg << "circular dependency in definition of '" << name << "'; detected at " << location << ")";
243             throw syntax_error(scope.location, msg.str());
244         }
245         try {
246             symbol->resolve_started++;
247             int rc = symbol->value->resolve(pioasm, program, scope);
248             symbol->resolve_started--;
249             return rc;
250         } catch (syntax_error &e) {
251             symbol->resolve_started--;
252             throw e;
253         }
254     } else {
255         std::stringstream msg;
256         msg << "undefined symbol '" << name << "'";
257         throw syntax_error(location, msg.str());
258     }
259 }
260 
encode(program & program)261 uint instruction::encode(program &program) {
262     raw_encoding raw = raw_encode(program);
263     int _delay = delay->resolve(program);
264     if (_delay < 0) {
265         throw syntax_error(delay->location, "instruction delay must be positive");
266     }
267     if (_delay > program.delay_max) {
268         if (program.delay_max == 31) {
269             throw syntax_error(delay->location, "instruction delay must be <= 31");
270         } else {
271             std::stringstream msg;
272             msg << "the instruction delay limit is " << program.delay_max << " because of the side set specified at "
273                 << program.sideset.location;
274             throw syntax_error(delay->location, msg.str());
275         }
276     }
277     int _sideset = 0;
278     if (sideset) {
279         _sideset = sideset->resolve(program);
280         if (_sideset < 0) {
281             throw syntax_error(sideset->location, "side set value must be >=0");
282         }
283         if (_sideset > program.sideset_max) {
284             std::stringstream msg;
285             msg << "the maximum side set value is " << program.sideset_max << " based on the configuration specified at "
286                 << program.sideset.location;
287             throw syntax_error(sideset->location, msg.str());
288         }
289         _sideset <<= (5u - program.sideset_bits_including_opt);
290         if (program.sideset_opt) {
291             _sideset |= 0x10u;
292         }
293     }
294     // note we store the 6th bit of arg2 above the 16 bits of instruction
295     return (((uint) raw.type) << 13u) | (((uint) _delay | (uint) _sideset) << 8u) | (raw.arg1 << 5u) | raw.arg2 | ((raw.arg2 >> 5) << 16);
296 }
297 
raw_encode(program & program)298 raw_encoding instruction::raw_encode(program& program) {
299     throw syntax_error(location, "internal error");
300 }
301 
encode(program & program)302 uint instr_word::encode(program &program) {
303     uint value = encoding->resolve(program);
304     if (value > 0xffffu) {
305         throw syntax_error(location, ".word value must be a positive 16 bit value");
306     }
307     return value;
308 }
309 
get_push_get_index(const program & program,extended_mov index)310 uint instr_mov::get_push_get_index(const program &program, extended_mov index) {
311     if (index.loc == mov::fifo_y) {
312         return 0;
313     } else {
314         uint v = index.fifo_index->resolve(program);
315         if (v > 7) {
316             throw syntax_error(index.fifo_index->location, "FIFO index myst be between 0 and 7");
317         }
318         return v | 8;
319     }
320 }
321 
pre_validate(program & program)322 void instr_push::pre_validate(program& program) {
323     if (program.fifo != fifo_config::rx && program.fifo != fifo_config::txrx) {
324         throw syntax_error(location, "FIFO must be configured for 'txrx' or 'rx' to use this instruction");
325     }
326 }
327 
pre_validate(program & program)328 void instr_mov::pre_validate(program &program) {
329     if (dest.uses_fifo()) {
330         if (src.loc != mov::isr) {
331             throw syntax_error(location, "mov rxfifo[] source must be isr");
332         }
333         if (program.fifo != fifo_config::txput && program.fifo != fifo_config::putget) {
334             throw syntax_error(location, "FIFO must be configured for 'txput' or 'putget' to use this instruction");
335         }
336     } else if (src.uses_fifo()) {
337         if (dest.loc != mov::osr) {
338             throw syntax_error(location, "mov ,txfifo[] target must be osr");
339         }
340         if (program.fifo != fifo_config::txget && program.fifo != fifo_config::putget) {
341             throw syntax_error(location, "FIFO must be configured for 'txget' or 'putget' to use this instruction");
342         }
343     }
344 }
345 
raw_encode(program & program)346 raw_encoding instr_mov::raw_encode(program& program) {
347     if (!dest.uses_fifo() && !src.uses_fifo()) {
348         // regular mov
349         return {inst_type::mov, (uint) dest.loc, (uint) src.loc | ((uint) op << 3u)};
350     }
351     if (dest.uses_fifo()) {
352         return {inst_type::push_pull, 0, 0x10 | get_push_get_index(program, dest) };
353     } else {
354         return {inst_type::push_pull, 0x4, 0x10 | get_push_get_index(program, src) };
355     }
356 }
357 
raw_encode(program & program)358 raw_encoding instr_jmp::raw_encode(program& program) {
359     int dest = target->resolve(program);
360     if (dest < 0) {
361         throw syntax_error(target->location, "jmp target address must be positive");
362     } else if (dest >= (int)program.instructions.size()) {
363         std::stringstream msg;
364         msg << "jmp target address " << dest << " is beyond the end of the program";
365         throw syntax_error(target->location, msg.str());
366     }
367     return {inst_type::jmp, (uint) cond, (uint) dest};
368 }
369 
raw_encode(program & program)370 raw_encoding instr_in::raw_encode(program& program) {
371     int v = value->resolve(program);
372     if (v < 1 || v > 32) {
373         throw syntax_error(value->location, "'in' bit count must be >= 1 and <= 32");
374     }
375     return {inst_type::in, (uint) src, (uint) v & 0x1fu};
376 }
377 
raw_encode(program & program)378 raw_encoding instr_out::raw_encode(program& program) {
379     int v = value->resolve(program);
380     if (v < 1 || v > 32) {
381         throw syntax_error(value->location, "'out' bit count must be >= 1 and <= 32");
382     }
383     return {inst_type::out, (uint) dest, (uint) v & 0x1fu};
384 }
385 
raw_encode(program & program)386 raw_encoding instr_set::raw_encode(program& program) {
387     int v = value->resolve(program);
388     if (v < 0 || v > 31) {
389         throw syntax_error(value->location, "'set' bit count must be >= 0 and <= 31");
390     }
391     return {inst_type::set, (uint) dest, (uint) v};
392 }
393 
raw_encode(program & program)394 raw_encoding instr_wait::raw_encode(program& program) {
395     uint pol = polarity->resolve(program);
396     if (pol > 1) {
397         throw syntax_error(polarity->location, "'wait' polarity must be 0 or 1");
398     }
399     uint arg2 = source->param->resolve(program);
400     switch (source->target) {
401         case wait_source::irq:
402             if (arg2 > 7) throw syntax_error(source->param->location, "irq number must be must be >= 0 and <= 7");
403             break;
404         case wait_source::gpio: {
405             if (!program.pio_version) {
406                 if (arg2 > 31)
407                     throw syntax_error(source->param->location, "absolute GPIO number must be must be >= 0 and <= 31");
408             } else {
409                 if (arg2 > 47)
410                     throw syntax_error(source->param->location, "absolute GPIO number must be must be >= 0 and <= 47");
411             }
412             int bitmap = 1u << (arg2 >> 4);
413             if (bitmap == 4 && program.used_gpio_ranges & 1) {
414                 throw syntax_error(source->param->location, "absolute GPIO number must be must be >= 0 and <= 31 as a GPIO number <16 has already been used");
415             }
416             if (bitmap == 1 && program.used_gpio_ranges & 4) {
417                 throw syntax_error(source->param->location, "absolute GPIO number must be must be >= 16 and <= 47 as a GPIO number >32 has already been used");
418             }
419             program.used_gpio_ranges |= bitmap;
420             break;
421         }
422         case wait_source::pin:
423             if (arg2 > 31) throw syntax_error(source->param->location, "pin number must be must be >= 0 and <= 31");
424             break;
425         case wait_source::jmppin:
426             if (arg2 > 3) throw syntax_error(source->param->location, "jmppin offset must be must be >= 0 and <= 3");
427             break;
428     }
429     return {inst_type::wait, (pol << 2u) | (uint) source->target, arg2 | (source->irq_type << 3)};
430 }
431 
raw_encode(program & program)432 raw_encoding instr_irq::raw_encode(program& program) {
433     uint arg2 = num->resolve(program);
434     if (arg2 > 7) throw syntax_error(num->location, "irq number must be must be >= 0 and <= 7");
435     arg2 |= irq_type << 3;
436     return {inst_type::irq, (uint)modifiers, arg2};
437 }
438 
public_symbols(program & program)439 std::vector<compiled_source::symbol> pio_assembler::public_symbols(program &program) {
440     std::vector<std::shared_ptr<symbol>> public_symbols;
441     std::remove_copy_if(program.ordered_symbols.begin(), program.ordered_symbols.end(),
442                         std::inserter(public_symbols, public_symbols.end()),
443                         [](const std::shared_ptr<symbol> &s) { return !s->is_public; });
444 
445     std::vector<compiled_source::symbol> rc;
446     std::transform(public_symbols.begin(), public_symbols.end(), std::back_inserter(rc),
447                    [&](const std::shared_ptr<symbol> &s) {
448                        return compiled_source::symbol(s->name, s->value->resolve(program), s->is_label);
449                    });
450     return rc;
451 }
452 
write_output()453 int pio_assembler::write_output() {
454     std::set<std::string> known_output_formats;
455     std::transform(output_format::all().begin(), output_format::all().end(),
456                    std::inserter(known_output_formats, known_output_formats.begin()),
457                    [&](std::shared_ptr<output_format> &f) {
458                        return f->name;
459                    });
460 
461     compiled_source source;
462     source.global_symbols = public_symbols(get_dummy_global_program());
463     for (auto &program : programs) {
464         program.finalize();
465         source.programs.emplace_back(program.name);
466         auto &cprogram = source.programs[source.programs.size() - 1];
467         cprogram.pio_version = program.pio_version;
468 
469         // encode the instructions
470         std::transform(program.instructions.begin(), program.instructions.end(),
471                        std::back_inserter(cprogram.instructions), [&](std::shared_ptr<instruction> &inst) {
472                     return inst->encode(program);
473                 });
474 
475         for (const auto &e : program.code_blocks) {
476             bool ok = false;
477             for(const auto &o : known_output_formats) {
478                 if (o == e.first || 0 == e.first.find(o+"-")) {
479                     ok = true;
480                     break;
481                 }
482             }
483             if (!ok) {
484                 std::cerr << e.second[0].location << ": warning, unknown code block output type '" << e.first << "'\n";
485                 known_output_formats.insert(e.first);
486             }
487         }
488 
489         if (program.wrap) cprogram.wrap = program.wrap->resolve(program); else cprogram.wrap = std::max((int)program.instructions.size() - 1, 0);
490         cprogram.clock_div_int = program.clock_div_int;
491         cprogram.clock_div_frac = program.clock_div_frac;
492         if (program.wrap_target) {
493             cprogram.wrap_target = program.wrap_target->resolve(program);
494             if (cprogram.wrap_target >= program.instructions.size()) {
495                 throw syntax_error(program.wrap_target->location, ".wrap_target cannot be placed after the last program instruction");
496             }
497         } else {
498             cprogram.wrap_target = 0;
499         }
500         if (program.origin.value) cprogram.origin = program.origin.value->resolve(program);
501         cprogram.mov_status_type = program.mov_status.type == mov_status_type::unspecified ? -1 : (int)program.mov_status.type;
502         cprogram.mov_status_n = program.mov_status.final_n;
503         cprogram.fifo = program.fifo;
504         cprogram.used_gpio_ranges = program.used_gpio_ranges;
505         auto in_out_convert = [](const in_out &io) {
506             return compiled_source::in_out{
507                 .pin_count = io.final_pin_count,
508                 .right = io.right,
509                 .autop = io.autop,
510                 .threshold = io.final_threshold,
511             };
512         };
513         cprogram.in = in_out_convert(program.in);
514         cprogram.out = in_out_convert(program.out);
515         cprogram.set_count = program.final_set_count;
516         if (program.sideset.value) {
517             cprogram.sideset_bits_including_opt = program.sideset_bits_including_opt;
518             cprogram.sideset_opt = program.sideset_opt;
519             cprogram.sideset_pindirs = program.sideset_pindirs;
520         }
521         std::transform(program.code_blocks.begin(), program.code_blocks.end(), std::inserter(cprogram.code_blocks, cprogram.code_blocks.begin()), [](const std::pair<std::string, std::vector<code_block>>&e) {
522             std::vector<std::string> blocks;
523             std::transform(e.second.begin(), e.second.end(), std::back_inserter(blocks), [&](const code_block& block) {
524                 return block.contents;
525             });
526             return std::pair<std::string, std::vector<std::string>>(e.first, blocks);
527         });
528         cprogram.lang_opts = program.lang_opts;
529         cprogram.symbols = public_symbols(program);
530     }
531     if (programs.empty()) {
532         std::cout << "warning: input contained no programs" << std::endl;
533     }
534     return format->output(dest, options, source);
535 }
536 
open_single_output(std::string destination)537 FILE *output_format::open_single_output(std::string destination) {
538     FILE *out = destination == "-" ? stdout : fopen(destination.c_str(), "w");
539     if (!out) {
540         std::cerr << "Can't open output file '" << destination << "'" << std::endl;
541     }
542     return out;
543 }
544