Initial Release

This commit is contained in:
graham sanderson
2021-01-20 10:44:27 -06:00
commit 26653ea81e
404 changed files with 135614 additions and 0 deletions

View File

@ -0,0 +1,42 @@
cmake_minimum_required(VERSION 3.4)
project(pioasm CXX)
set(CMAKE_CXX_STANDARD 11)
if (PIOASM_GENERATE_PARSER)
find_package(BISON 3.4.2)
find_package(FLEX 2.5.13) # no idea about the version
FLEX_TARGET(pioasm_lexer lexer.ll ${CMAKE_CURRENT_SOURCE_DIR}/gen/lexer.cpp)
BISON_TARGET(pioasm_parser parser.yy ${CMAKE_CURRENT_SOURCE_DIR}/gen/parser.cpp COMPILE_FLAGS "-Wcounterexamples")
ADD_FLEX_BISON_DEPENDENCY(pioasm_lexer pioasm_parser)
endif()
add_executable(pioasm
main.cpp
pio_assembler.cpp
pio_disassembler.cpp
gen/lexer.cpp
gen/parser.cpp
)
target_sources(pioasm PRIVATE c_sdk_output.cpp)
target_sources(pioasm PRIVATE python_output.cpp)
target_sources(pioasm PRIVATE hex_output.cpp)
target_sources(pioasm PRIVATE ${PIOASM_EXTRA_SOURCE_FILES})
if ((CMAKE_CXX_COMPILER_ID STREQUAL "GNU") AND
(CMAKE_CXX_COMPILER_VERSION VERSION_GREATER_EQUAL "7") AND
(CMAKE_CXX_COMPILER_VERSION VERSION_LESS "9") AND
(CMAKE_SYSTEM_PROCESSOR MATCHES "^arm.*$"))
# disable GCC ARM info notice about ABI change
target_compile_options(pioasm PRIVATE -Wno-psabi)
endif()
target_include_directories(pioasm PRIVATE ${CMAKE_CURRENT_LIST_DIR} ${CMAKE_CURRENT_LIST_DIR}/gen)
if (MSVC)
target_compile_definitions(pioasm PRIVATE YY_NO_UNISTD_H)
target_compile_options(pioasm PRIVATE "/std:c++latest")
endif()

View File

@ -0,0 +1,142 @@
/*
* Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#include <algorithm>
#include <iostream>
#include "output_format.h"
#include "pio_disassembler.h"
struct c_sdk_output : public output_format {
struct factory {
factory() {
output_format::add(new c_sdk_output());
}
};
c_sdk_output() : output_format("c-sdk") {}
std::string get_description() override {
return "C header suitable for use with the Pico SDK";
}
void output_symbols(FILE *out, std::string prefix, const std::vector<compiled_source::symbol> &symbols) {
int count = 0;
for (const auto &s : symbols) {
if (!s.is_label) {
fprintf(out, "#define %s%s %d\n", prefix.c_str(), s.name.c_str(), s.value);
count++;
}
}
if (count) {
fprintf(out, "\n");
count = 0;
}
for (const auto &s : symbols) {
if (s.is_label) {
fprintf(out, "#define %soffset_%s %du\n", prefix.c_str(), s.name.c_str(), s.value);
count++;
}
}
if (count) {
fprintf(out, "\n");
}
}
void header(FILE *out, std::string msg) {
std::string dashes = std::string(msg.length(), '-');
fprintf(out, "// %s //\n", dashes.c_str());
fprintf(out, "// %s //\n", msg.c_str());
fprintf(out, "// %s //\n", dashes.c_str());
fprintf(out, "\n");
}
int output(std::string destination, std::vector<std::string> output_options,
const compiled_source &source) override {
for (const auto &program : source.programs) {
for(const auto &p : program.lang_opts) {
if (p.first.size() >= name.size() && p.first.compare(0, name.size(), name) == 0) {
std::cerr << "warning: " << name << " does not support output options; " << p.first << " lang_opt ignored.\n";
}
}
}
FILE *out = open_single_output(destination);
if (!out) return 1;
header(out, "This file is autogenerated by pioasm; do not edit!");
fprintf(out, "#if !PICO_NO_HARDWARE\n");
fprintf(out, "#include \"hardware/pio.h\"\n");
fprintf(out, "#endif\n");
fprintf(out, "\n");
output_symbols(out, "", source.global_symbols);
for (const auto &program : source.programs) {
header(out, program.name);
std::string prefix = program.name + "_";
fprintf(out, "#define %swrap_target %d\n", prefix.c_str(), program.wrap_target);
fprintf(out, "#define %swrap %d\n", prefix.c_str(), program.wrap);
fprintf(out, "\n");
output_symbols(out, prefix, program.symbols);
fprintf(out, "static const uint16_t %sprogram_instructions[] = {\n", prefix.c_str());
for (int i = 0; i < (int)program.instructions.size(); i++) {
const auto &inst = program.instructions[i];
if (i == program.wrap_target) {
fprintf(out, " // .wrap_target\n");
}
fprintf(out, " 0x%04x, // %2d: %s\n", inst, i,
disassemble(inst, program.sideset_bits_including_opt.get(), program.sideset_opt).c_str());
if (i == program.wrap) {
fprintf(out, " // .wrap\n");
}
}
fprintf(out, "};\n");
fprintf(out, "\n");
fprintf(out, "#if !PICO_NO_HARDWARE\n");
fprintf(out, "static const struct pio_program %sprogram = {\n", prefix.c_str());
fprintf(out, " .instructions = %sprogram_instructions,\n", prefix.c_str());
fprintf(out, " .length = %d,\n", (int) program.instructions.size());
fprintf(out, " .origin = %d,\n", program.origin.get());
fprintf(out, "};\n");
fprintf(out, "\n");
fprintf(out, "static inline pio_sm_config %sprogram_get_default_config(uint offset) {\n", prefix.c_str());
fprintf(out, " pio_sm_config c = pio_get_default_sm_config();\n");
fprintf(out, " sm_config_set_wrap(&c, offset + %swrap_target, offset + %swrap);\n", prefix.c_str(),
prefix.c_str());
if (program.sideset_bits_including_opt.is_specified()) {
fprintf(out, " sm_config_set_sideset(&c, %d, %s, %s);\n", program.sideset_bits_including_opt.get(),
program.sideset_opt ? "true" : "false",
program.sideset_pindirs ? "true" : "false");
}
fprintf(out, " return c;\n");
fprintf(out, "}\n");
// todo maybe have some code blocks inside or outside here?
for(const auto& o : program.code_blocks) {
fprintf(out, "\n");
if (o.first == name) {
for(const auto &contents : o.second) {
fprintf(out, "%s", contents.c_str());
fprintf(out, "\n");
}
}
}
fprintf(out, "#endif\n");
fprintf(out, "\n");
}
if (out != stdout) { fclose(out); }
return 0;
}
};
static c_sdk_output::factory creator;

2697
tools/pioasm/gen/lexer.cpp Normal file

File diff suppressed because it is too large Load Diff

302
tools/pioasm/gen/location.h Normal file
View File

@ -0,0 +1,302 @@
// A Bison parser, made by GNU Bison 3.7.2.
// Locations for Bison parsers in C++
// Copyright (C) 2002-2015, 2018-2020 Free Software Foundation, Inc.
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
// As a special exception, you may create a larger work that contains
// part or all of the Bison parser skeleton and distribute that work
// under terms of your choice, so long as that work isn't itself a
// parser generator using the skeleton or a modified version thereof
// as a parser skeleton. Alternatively, if you modify or redistribute
// the parser skeleton itself, you may (at your option) remove this
// special exception, which will cause the skeleton and the resulting
// Bison output files to be licensed under the GNU General Public
// License without this special exception.
// This special exception was added by the Free Software Foundation in
// version 2.2 of Bison.
/**
** \file pico_sdk/tools/pioasm/gen/location.h
** Define the yy::location class.
*/
#ifndef YY_YY_HOME_GRAHAM_DEV_MU_PICO_SDK_TOOLS_PIOASM_GEN_LOCATION_H_INCLUDED
# define YY_YY_HOME_GRAHAM_DEV_MU_PICO_SDK_TOOLS_PIOASM_GEN_LOCATION_H_INCLUDED
# include <iostream>
# include <string>
# ifndef YY_NULLPTR
# if defined __cplusplus
# if 201103L <= __cplusplus
# define YY_NULLPTR nullptr
# else
# define YY_NULLPTR 0
# endif
# else
# define YY_NULLPTR ((void*)0)
# endif
# endif
namespace yy {
/// A point in a source file.
class position
{
public:
/// Type for file name.
typedef const std::string filename_type;
/// Type for line and column numbers.
typedef int counter_type;
/// Construct a position.
explicit position (filename_type* f = YY_NULLPTR,
counter_type l = 1,
counter_type c = 1)
: filename (f)
, line (l)
, column (c)
{}
/// Initialization.
void initialize (filename_type* fn = YY_NULLPTR,
counter_type l = 1,
counter_type c = 1)
{
filename = fn;
line = l;
column = c;
}
/** \name Line and Column related manipulators
** \{ */
/// (line related) Advance to the COUNT next lines.
void lines (counter_type count = 1)
{
if (count)
{
column = 1;
line = add_ (line, count, 1);
}
}
/// (column related) Advance to the COUNT next columns.
void columns (counter_type count = 1)
{
column = add_ (column, count, 1);
}
/** \} */
/// File name to which this position refers.
filename_type* filename;
/// Current line number.
counter_type line;
/// Current column number.
counter_type column;
private:
/// Compute max (min, lhs+rhs).
static counter_type add_ (counter_type lhs, counter_type rhs, counter_type min)
{
return lhs + rhs < min ? min : lhs + rhs;
}
};
/// Add \a width columns, in place.
inline position&
operator+= (position& res, position::counter_type width)
{
res.columns (width);
return res;
}
/// Add \a width columns.
inline position
operator+ (position res, position::counter_type width)
{
return res += width;
}
/// Subtract \a width columns, in place.
inline position&
operator-= (position& res, position::counter_type width)
{
return res += -width;
}
/// Subtract \a width columns.
inline position
operator- (position res, position::counter_type width)
{
return res -= width;
}
/** \brief Intercept output stream redirection.
** \param ostr the destination output stream
** \param pos a reference to the position to redirect
*/
template <typename YYChar>
std::basic_ostream<YYChar>&
operator<< (std::basic_ostream<YYChar>& ostr, const position& pos)
{
if (pos.filename)
ostr << *pos.filename << ':';
return ostr << pos.line << '.' << pos.column;
}
/// Two points in a source file.
class location
{
public:
/// Type for file name.
typedef position::filename_type filename_type;
/// Type for line and column numbers.
typedef position::counter_type counter_type;
/// Construct a location from \a b to \a e.
location (const position& b, const position& e)
: begin (b)
, end (e)
{}
/// Construct a 0-width location in \a p.
explicit location (const position& p = position ())
: begin (p)
, end (p)
{}
/// Construct a 0-width location in \a f, \a l, \a c.
explicit location (filename_type* f,
counter_type l = 1,
counter_type c = 1)
: begin (f, l, c)
, end (f, l, c)
{}
/// Initialization.
void initialize (filename_type* f = YY_NULLPTR,
counter_type l = 1,
counter_type c = 1)
{
begin.initialize (f, l, c);
end = begin;
}
/** \name Line and Column related manipulators
** \{ */
public:
/// Reset initial location to final location.
void step ()
{
begin = end;
}
/// Extend the current location to the COUNT next columns.
void columns (counter_type count = 1)
{
end += count;
}
/// Extend the current location to the COUNT next lines.
void lines (counter_type count = 1)
{
end.lines (count);
}
/** \} */
public:
/// Beginning of the located region.
position begin;
/// End of the located region.
position end;
};
/// Join two locations, in place.
inline location&
operator+= (location& res, const location& end)
{
res.end = end.end;
return res;
}
/// Join two locations.
inline location
operator+ (location res, const location& end)
{
return res += end;
}
/// Add \a width columns to the end position, in place.
inline location&
operator+= (location& res, location::counter_type width)
{
res.columns (width);
return res;
}
/// Add \a width columns to the end position.
inline location
operator+ (location res, location::counter_type width)
{
return res += width;
}
/// Subtract \a width columns to the end position, in place.
inline location&
operator-= (location& res, location::counter_type width)
{
return res += -width;
}
/// Subtract \a width columns to the end position.
inline location
operator- (location res, location::counter_type width)
{
return res -= width;
}
/** \brief Intercept output stream redirection.
** \param ostr the destination output stream
** \param loc a reference to the location to redirect
**
** Avoid duplicate information.
*/
template <typename YYChar>
std::basic_ostream<YYChar>&
operator<< (std::basic_ostream<YYChar>& ostr, const location& loc)
{
location::counter_type end_col
= 0 < loc.end.column ? loc.end.column - 1 : 0;
ostr << loc.begin;
if (loc.end.filename
&& (!loc.begin.filename
|| *loc.begin.filename != *loc.end.filename))
ostr << '-' << loc.end.filename << ':' << loc.end.line << '.' << end_col;
else if (loc.begin.line < loc.end.line)
ostr << '-' << loc.end.line << '.' << end_col;
else if (loc.begin.column < end_col)
ostr << '-' << end_col;
return ostr;
}
} // yy
#endif // !YY_YY_HOME_GRAHAM_DEV_MU_PICO_SDK_TOOLS_PIOASM_GEN_LOCATION_H_INCLUDED

2208
tools/pioasm/gen/parser.cpp Normal file

File diff suppressed because it is too large Load Diff

2894
tools/pioasm/gen/parser.hpp Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,41 @@
/*
* Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#include "output_format.h"
#include <iostream>
struct hex_output : public output_format {
struct factory {
factory() {
output_format::add(new hex_output());
}
};
hex_output() : output_format("hex") {}
std::string get_description() {
return "Raw hex output (only valid for single program inputs)";
}
virtual int output(std::string destination, std::vector<std::string> output_options,
const compiled_source &source) {
FILE *out = open_single_output(destination);
if (!out) return 1;
if (source.programs.size() > 1) {
// todo don't have locations any more!
std::cerr << "error: hex output only supports a single program input\n";
return 1;
}
for (const auto &i : source.programs[0].instructions) {
fprintf(out, "%04x\n", i);
}
if (out != stdout) { fclose(out); }
return 0;
}
};
static hex_output::factory creator;

236
tools/pioasm/lexer.ll Normal file
View File

@ -0,0 +1,236 @@
/*
* Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
%{ /* -*- C++ -*- */
# include <cerrno>
# include <climits>
# include <cstdlib>
# include <cstring>
# include <string>
# include "pio_assembler.h"
# include "parser.hpp"
#ifdef _MSC_VER
#pragma warning(disable : 4996) // fopen
#endif
%}
%option noyywrap nounput noinput batch debug never-interactive case-insensitive noline
%{
yy::parser::symbol_type make_INT(const std::string &s, const yy::parser::location_type& loc);
yy::parser::symbol_type make_HEX(const std::string &s, const yy::parser::location_type& loc);
yy::parser::symbol_type make_BINARY(const std::string &s, const yy::parser::location_type& loc);
%}
blank [ \t]
whitesp {blank}+
comment (";"|"//")[^\n]*
digit [0-9]
id [a-zA-Z_][a-zA-Z0-9_]*
binary "0b"[01]+
int {digit}+
hex "0x"[0-9a-fA-F]+
directive \.{id}
output_fmt [^%\n]+
%{
// Code run each time a pattern is matched.
# define YY_USER_ACTION loc.columns (yyleng);
%}
%x code_block
%x c_comment
%x lang_opt
%%
std::string code_block_contents;
yy::location code_block_start;
%{
// A handy shortcut to the location held by the pio_assembler.
yy::location& loc = pioasm.location;
// Code run each time yylex is called.
loc.step();
%}
{blank}+ loc.step();
\n+ { auto loc_newline = loc; loc_newline.end = loc_newline.begin; loc.lines(yyleng); loc.step(); return yy::parser::make_NEWLINE(loc_newline); }
"%"{blank}*{output_fmt}{blank}*"{" {
BEGIN(code_block);
code_block_contents = "";
code_block_start = loc;
std::string tmp(yytext);
tmp = tmp.substr(1, tmp.length() - 2);
tmp = tmp.erase(0, tmp.find_first_not_of(" \t"));
tmp = tmp.erase(tmp.find_last_not_of(" \t") + 1);
return yy::parser::make_CODE_BLOCK_START( tmp, loc);
}
<code_block>{
{blank}+ loc.step();
\n+ { auto loc_newline = loc; loc_newline.end = loc_newline.begin; loc.lines(yyleng); loc.step(); }
"%}" { BEGIN(INITIAL); auto loc2 = loc; loc2.begin = code_block_start.begin; return yy::parser::make_CODE_BLOCK_CONTENTS(code_block_contents, loc2); }
.* { code_block_contents += std::string(yytext) + "\n"; }
}
<c_comment>{
{blank}+ loc.step();
"*/" { BEGIN(INITIAL); }
"*" { }
[^\n\*]* { }
\n+ { auto loc_newline = loc; loc_newline.end = loc_newline.begin; loc.lines(yyleng); loc.step(); }
}
<lang_opt>{
\"[^\n]*\" return yy::parser::make_STRING(yytext, loc);
{blank}+ loc.step();
"=" return yy::parser::make_EQUAL(loc);
{int} return make_INT(yytext, loc);
{hex} return make_HEX(yytext, loc);
{binary} return make_BINARY(yytext, loc);
[^ \t\n\"=]+ return yy::parser::make_NON_WS(yytext, loc);
\n+ { BEGIN(INITIAL); auto loc_newline = loc; loc_newline.end = loc_newline.begin; loc.lines(yyleng); loc.step(); return yy::parser::make_NEWLINE(loc_newline); }
. { throw yy::parser::syntax_error(loc, "invalid character: " + std::string(yytext)); }
}
"/*" { BEGIN(c_comment); }
"," return yy::parser::make_COMMA(loc);
"::" return yy::parser::make_REVERSE(loc);
":" return yy::parser::make_COLON(loc);
"[" return yy::parser::make_LBRACKET(loc);
"]" return yy::parser::make_RBRACKET(loc);
"(" return yy::parser::make_LPAREN(loc);
")" return yy::parser::make_RPAREN(loc);
"+" return yy::parser::make_PLUS(loc);
"--" return yy::parser::make_POST_DECREMENT(loc);
"" return yy::parser::make_POST_DECREMENT(loc);
"-" return yy::parser::make_MINUS(loc);
"*" return yy::parser::make_MULTIPLY(loc);
"/" return yy::parser::make_DIVIDE(loc);
"|" return yy::parser::make_OR(loc);
"&" return yy::parser::make_AND(loc);
"^" return yy::parser::make_XOR(loc);
"!=" return yy::parser::make_NOT_EQUAL(loc);
"!" return yy::parser::make_NOT(loc);
"~" return yy::parser::make_NOT(loc);
".program" return yy::parser::make_PROGRAM(loc);
".wrap_target" return yy::parser::make_WRAP_TARGET(loc);
".wrap" return yy::parser::make_WRAP(loc);
".word" return yy::parser::make_WORD(loc);
".define" return yy::parser::make_DEFINE(loc);
".side_set" return yy::parser::make_SIDE_SET(loc);
".origin" return yy::parser::make_ORIGIN(loc);
".lang_opt" { BEGIN(lang_opt); return yy::parser::make_LANG_OPT(loc); }
{directive} return yy::parser::make_UNKNOWN_DIRECTIVE(yytext, loc);
"JMP" return yy::parser::make_JMP(loc);
"WAIT" return yy::parser::make_WAIT(loc);
"IN" return yy::parser::make_IN(loc);
"OUT" return yy::parser::make_OUT(loc);
"PUSH" return yy::parser::make_PUSH(loc);
"PULL" return yy::parser::make_PULL(loc);
"MOV" return yy::parser::make_MOV(loc);
"IRQ" return yy::parser::make_IRQ(loc);
"SET" return yy::parser::make_SET(loc);
"NOP" return yy::parser::make_NOP(loc);
"PUBLIC" return yy::parser::make_PUBLIC(loc);
"OPTIONAL" return yy::parser::make_OPTIONAL(loc);
"OPT" return yy::parser::make_OPTIONAL(loc);
"SIDE" return yy::parser::make_SIDE(loc);
"SIDESET" return yy::parser::make_SIDE(loc);
"SIDE_SET" return yy::parser::make_SIDE(loc);
"PIN" return yy::parser::make_PIN(loc);
"GPIO" return yy::parser::make_GPIO(loc);
"OSRE" return yy::parser::make_OSRE(loc);
"PINS" return yy::parser::make_PINS(loc);
"NULL" return yy::parser::make_NULL(loc);
"PINDIRS" return yy::parser::make_PINDIRS(loc);
"X" return yy::parser::make_X(loc);
"Y" return yy::parser::make_Y(loc);
"PC" return yy::parser::make_PC(loc);
"EXEC" return yy::parser::make_EXEC(loc);
"ISR" return yy::parser::make_ISR(loc);
"OSR" return yy::parser::make_OSR(loc);
"STATUS" return yy::parser::make_STATUS(loc);
"BLOCK" return yy::parser::make_BLOCK(loc);
"NOBLOCK" return yy::parser::make_NOBLOCK(loc);
"IFFULL" return yy::parser::make_IFFULL(loc);
"IFEMPTY" return yy::parser::make_IFEMPTY(loc);
"REL" return yy::parser::make_REL(loc);
"CLEAR" return yy::parser::make_CLEAR(loc);
"NOWAIT" return yy::parser::make_NOWAIT(loc);
"ONE" return yy::parser::make_INT(1, loc);
"ZERO" return yy::parser::make_INT(0, loc);
<<EOF>> return yy::parser::make_END(loc);
{int} return make_INT(yytext, loc);
{hex} return make_HEX(yytext, loc);
{binary} return make_BINARY(yytext, loc);
{id} return yy::parser::make_ID(yytext, loc);
{comment} { }
. { throw yy::parser::syntax_error(loc, "invalid character: " + std::string(yytext)); }
%%
yy::parser::symbol_type make_INT(const std::string &s, const yy::parser::location_type& loc)
{
errno = 0;
long n = strtol (s.c_str(), NULL, 10);
if (! (INT_MIN <= n && n <= INT_MAX && errno != ERANGE))
throw yy::parser::syntax_error (loc, "integer is out of range: " + s);
return yy::parser::make_INT((int) n, loc);
}
yy::parser::symbol_type make_HEX(const std::string &s, const yy::parser::location_type& loc)
{
errno = 0;
long n = strtol (s.c_str() + 2, NULL, 16);
if (! (INT_MIN <= n && n <= INT_MAX && errno != ERANGE))
throw yy::parser::syntax_error (loc, "hex is out of range: " + s);
return yy::parser::make_INT((int) n, loc);
}
yy::parser::symbol_type make_BINARY(const std::string &s, const yy::parser::location_type& loc)
{
errno = 0;
long n = strtol (s.c_str()+2, NULL, 2);
if (! (INT_MIN <= n && n <= INT_MAX && errno != ERANGE))
throw yy::parser::syntax_error (loc, "binary is out of range: " + s);
return yy::parser::make_INT((int) n, loc);
}
void pio_assembler::scan_begin ()
{
yy_flex_debug = false;
if (source.empty () || source == "-")
yyin = stdin;
else if (!(yyin = fopen (source.c_str (), "r")))
{
std::cerr << "cannot open " << source << ": " << strerror(errno) << '\n';
exit (EXIT_FAILURE);
}
}
void pio_assembler::scan_end ()
{
fclose (yyin);
}

100
tools/pioasm/main.cpp Normal file
View File

@ -0,0 +1,100 @@
/*
* Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#include <iostream>
#include "pio_assembler.h"
#define DEFAULT_OUTPUT_FORMAT "c-sdk"
void usage() {
std::cerr << "usage: pioasm <options> <input> (<output>)\n\n";
std::cerr << "Assemble file of PIO program(s) for use in applications.\n";
std::cerr << " <input> the input filename\n";
std::cerr << " <output> the output filename (or filename prefix if the output format produces multiple outputs).\n";
std::cerr << " if not specified, a single output will be written to stdout\n";
std::cerr << "\n";
std::cerr << "options:\n";
std::cerr << " -o <output_format> select output_format (default '" << DEFAULT_OUTPUT_FORMAT << "'); available options are:\n";
for(const auto& f : output_format::output_formats) {
std::cerr << " " << f->name << std::endl;
std::cerr << " " << f->get_description() << std::endl;
}
std::cerr << " -p <output_param> add a parameter to be passed to the output format generator" << std::endl;
std::cerr << " -?, --help print this help and exit\n";
}
int main(int argc, char *argv[]) {
int res = 0;
pio_assembler pioasm;
std::string format(DEFAULT_OUTPUT_FORMAT);
const char *input = nullptr;
const char *output = nullptr;
std::vector<std::string> options;
int i = 1;
for (; !res && i < argc; i++) {
if (argv[i][0] != '-') break;
if (argv[i] == std::string("-o")) {
if (i++ < argc) {
format = argv[i];
} else {
std::cerr << "error: -o requires format value" << std::endl;
res = 1;
}
} else if (argv[i] == std::string("-p")) {
if (i++ < argc) {
options.emplace_back(argv[i]);
} else {
std::cerr << "error: -p requires parameter value" << std::endl;
res = 1;
}
} else if (argv[i] == std::string("-?") || argv[i] == std::string("--help")) {
usage();
return 1;
} else {
std::cerr << "error: unknown option " << argv[i] << std::endl;
res = 1;
}
}
if (!res) {
if (i != argc) {
input = argv[i++];
} else {
std::cerr << "error: expected input filename\n";
res = 1;
}
}
if (!res) {
if (i != argc) {
output = argv[i++];
} else {
output = "-";
}
}
if (!res && i != argc) {
std::cerr << "unexpected command line argument " << argv[i] << std::endl;
res = 1;
}
std::shared_ptr<output_format> oformat;
if (!res) {
const auto& e = std::find_if(output_format::output_formats.begin(), output_format::output_formats.end(),
[&](const std::shared_ptr<output_format> &f) {
return f->name == format;
});
if (e == output_format::output_formats.end()) {
std::cerr << "error: unknown output format '" << format << "'" << std::endl;
res = 1;
} else {
oformat = *e;
}
}
if (res) {
std::cerr << std::endl;
usage();
} else {
res = pioasm.generate(oformat, input, output, options);
}
return res;
}

View File

@ -0,0 +1,100 @@
/*
* Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#ifndef _OUTPUT_FORMAT_H
#define _OUTPUT_FORMAT_H
#include <vector>
#include <map>
#include <string>
#include <memory>
typedef unsigned int uint;
// can't use optional because we want to support older compilers
template<typename T>
struct simple_optional {
T value;
T default_value;
bool specified;
simple_optional() : default_value(), specified(false) {}
simple_optional(const T &value) : value(value), specified(true) {}
simple_optional<T> &operator=(const T &v) {
value = v;
specified = true;
return *this;
}
operator bool() = delete; // confusing
const T &get() const { return specified ? value : default_value; }
bool is_specified() const { return specified; }
static simple_optional<T> with_default(const T &default_value) {
simple_optional<T> rc;
rc.default_value = default_value;
return rc;
}
};
typedef simple_optional<int> optional_int;
typedef simple_optional<bool> optional_bool;
struct compiled_source {
struct symbol {
std::string name;
int value;
bool is_label;
symbol(std::string name, int value, bool is_label) : name(std::move(name)), value(value), is_label(is_label) {}
};
struct program {
std::string name;
optional_int origin = optional_int::with_default(-1);
optional_int sideset_bits_including_opt;
bool sideset_opt = false;
bool sideset_pindirs = false;
int wrap;
int wrap_target;
std::vector<uint> instructions;
std::vector<symbol> symbols; // public only
std::map<std::string, std::vector<std::string>> code_blocks;
std::map<std::string, std::vector<std::pair<std::string,std::string>>> lang_opts;
// todo can't have wrap at -1
program(std::string name) : name(std::move(name)) {}
};
std::vector<symbol> global_symbols; // public only
std::vector<program> programs;
};
struct output_format {
static std::vector<std::shared_ptr<output_format>> output_formats;
static std::string default_name;
std::string name;
static void add(output_format *lang) {
output_formats.push_back(std::shared_ptr<output_format>(lang));
}
virtual int output(std::string destination, std::vector<std::string> output_options,
const compiled_source &source) = 0;
virtual std::string get_description() = 0;
FILE *open_single_output(std::string destination);
virtual ~output_format() = default;
protected:
output_format(std::string name) : name(std::move(name)) {}
};
#endif

353
tools/pioasm/parser.yy Normal file
View File

@ -0,0 +1,353 @@
/*
* Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
%skeleton "lalr1.cc" /* -*- C++ -*- */
%require "3.4.2"
%defines
%define api.token.constructor
%define api.value.type variant
/*%define parse.assert*/
%define api.location.file "location.h"
%define parse.lac full
/* define parse.trace*/
%define parse.error verbose
%no-lines
%locations
%code requires {
#include <string>
#include <fstream>
#include <sstream>
#include "pio_types.h"
struct pio_assembler;
#ifdef _MSC_VER
#pragma warning(disable : 4065) // default only switch statement
#endif
}
// The parsing context.
%param { pio_assembler& pioasm }
%code {
#include "pio_assembler.h"
#ifdef _MSC_VER
#pragma warning(disable : 4244) // possible loss of data (valid warning, but there is a software check / missing cast)
#endif
}
%define api.token.prefix {TOK_}
%token
END 0 "end of file"
NEWLINE "end of line"
COMMA ","
COLON ":"
LPAREN "("
RPAREN ")"
LBRACKET "["
RBRACKET "]"
PLUS "+"
MINUS "-"
MULTIPLY "*"
DIVIDE "/"
OR "|"
AND "&"
XOR "^"
POST_DECREMENT "--"
NOT_EQUAL "!="
NOT "!"
REVERSE "::"
EQUAL "="
PROGRAM ".program"
WRAP_TARGET ".wrap_target"
WRAP ".wrap"
DEFINE ".define"
SIDE_SET ".side_set"
WORD ".word"
ORIGIN ".origin"
LANG_OPT ".lang_opt"
JMP "jmp"
WAIT "wait"
IN "in"
OUT "out"
PUSH "push"
PULL "pull"
MOV "mov"
IRQ "irq"
SET "set"
NOP "nop"
PIN "pin"
GPIO "gpio"
OSRE "osre"
PINS "pins"
NULL "null"
PINDIRS "pindirs"
BLOCK "block"
NOBLOCK "noblock"
IFEMPTY "ifempty"
IFFULL "iffull"
NOWAIT "nowait"
CLEAR "clear"
REL "rel"
X "x"
Y "y"
EXEC "exec"
PC "pc"
ISR "isr"
OSR "osr"
OPTIONAL "opt"
SIDE "side"
STATUS "status"
PUBLIC "public"
;
%token
<std::string> ID "identifier"
<std::string> STRING "string"
<std::string> NON_WS "text"
<std::string> CODE_BLOCK_START "code block"
<std::string> CODE_BLOCK_CONTENTS "%}" // bit ugly but if there is no end this is what we will be missing
<std::string> UNKNOWN_DIRECTIVE
<int> INT "integer"
;
%left REVERSE
%left MULTIPLY DIVIDE
%left PLUS MINUS
%left AND OR XOR
%printer { yyo << "..."; } <*>;
%%
file:
lines END { if (pioasm.error_count || pioasm.write_output()) YYABORT; }
;
lines:
line
| lines NEWLINE line;
line:
PROGRAM ID { if (!pioasm.add_program(@$, $2)) { std::stringstream msg; msg << "program " << $2 << " already exists"; error(@$, msg.str()); abort(); } }
| directive
| instruction { pioasm.get_current_program(@1, "instruction").add_instruction($1); }
| label_decl instruction { auto &p = pioasm.get_current_program(@2, "instruction"); p.add_label($1); p.add_instruction($2); }
| label_decl { pioasm.get_current_program(@1, "label").add_label($1); }
| code_block
| %empty
| error { if (pioasm.error_count > 6) { std::cerr << "\ntoo many errors; aborting.\n"; YYABORT; } }
;
code_block:
CODE_BLOCK_START CODE_BLOCK_CONTENTS { std::string of = $1; if (of.empty()) of = output_format::default_name; pioasm.get_current_program(@$, "code block", false, false).add_code_block( code_block(@$, of, $2)); }
%type <std::shared_ptr<symbol>> label_decl;
label_decl:
symbol_def COLON { $1->is_label = true; $$ = $1; }
directive:
DEFINE symbol_def expression { $2->is_label = false; $2->value = $3; pioasm.get_current_program(@1, ".define", false, false).add_symbol($2); }
| ORIGIN value { pioasm.get_current_program(@1, ".origin", true).set_origin(@$, $2); }
| SIDE_SET value OPTIONAL PINDIRS { pioasm.get_current_program(@1, ".side_set", true).set_sideset(@$, $2, true, true); }
| SIDE_SET value OPTIONAL { pioasm.get_current_program(@1, ".side_set", true).set_sideset(@$, $2, true, false); }
| SIDE_SET value PINDIRS { pioasm.get_current_program(@1, ".side_set", true).set_sideset(@$, $2, false, true); }
| SIDE_SET value { pioasm.get_current_program(@1, ".side_set", true).set_sideset(@$, $2, false, false); }
| WRAP_TARGET { pioasm.get_current_program(@1, ".wrap_target").set_wrap_target(@$); }
| WRAP { pioasm.get_current_program(@1, ".wrap").set_wrap(@$); }
| WORD value { pioasm.get_current_program(@1, "instruction").add_instruction(std::shared_ptr<instruction>(new instr_word(@$, $2))); }
| LANG_OPT NON_WS NON_WS EQUAL INT { pioasm.get_current_program(@1, ".lang_opt").add_lang_opt($2, $3, std::to_string($5)); }
| LANG_OPT NON_WS NON_WS EQUAL STRING { pioasm.get_current_program(@1, ".lang_opt").add_lang_opt($2, $3, $5); }
| LANG_OPT NON_WS NON_WS EQUAL NON_WS { pioasm.get_current_program(@1, ".lang_opt").add_lang_opt($2, $3, $5); }
| LANG_OPT error { error(@$, "expected format is .lang_opt language option_name = option_value"); }
| UNKNOWN_DIRECTIVE { std::stringstream msg; msg << "unknown directive " << $1; throw syntax_error(@$, msg.str()); }
;
/* value is a more limited top level expression... requiring parenthesis */
%type <std::shared_ptr<resolvable>> value;
value: INT { $$ = resolvable_int(@$, $1); }
| ID { $$ = std::shared_ptr<resolvable>(new name_ref(@$, $1)); }
| LPAREN expression RPAREN { $$ = $2; }
%type <std::shared_ptr<resolvable>> expression;
expression:
value
| expression PLUS expression { $$ = std::shared_ptr<binary_operation>(new binary_operation(@$, binary_operation::add, $1, $3)); }
| expression MINUS expression { $$ = std::shared_ptr<binary_operation>(new binary_operation(@$, binary_operation::subtract, $1, $3)); }
| expression MULTIPLY expression { $$ = std::shared_ptr<binary_operation>(new binary_operation(@$, binary_operation::multiply, $1, $3)); }
| expression DIVIDE expression { $$ = std::shared_ptr<binary_operation>(new binary_operation(@$, binary_operation::divide, $1, $3)); }
| expression OR expression { $$ = std::shared_ptr<binary_operation>(new binary_operation(@$, binary_operation::or_, $1, $3)); }
| expression AND expression { $$ = std::shared_ptr<binary_operation>(new binary_operation(@$, binary_operation::and_, $1, $3)); }
| expression XOR expression { $$ = std::shared_ptr<binary_operation>(new binary_operation(@$, binary_operation::xor_, $1, $3)); }
| MINUS expression { $$ = std::shared_ptr<unary_operation>(new unary_operation(@$, unary_operation::negate, $2)); }
| REVERSE expression { $$ = std::shared_ptr<unary_operation>(new unary_operation(@$, unary_operation::reverse, $2)); }
%type <std::shared_ptr<instruction>> instruction;
instruction:
base_instruction sideset delay { $$ = $1; $$->sideset = $2; $$->delay = $3; }
| base_instruction delay sideset { $$ = $1; $$->delay = $2; $$->sideset = $3; }
| base_instruction sideset { $$ = $1; $$->sideset = $2; $$->delay = resolvable_int(@$, 0); }
| base_instruction delay { $$ = $1; $$->delay = $2; }
| base_instruction { $$ = $1; $$->delay = resolvable_int(@$, 0); }
%type <std::shared_ptr<instruction>> base_instruction;
base_instruction:
NOP { $$ = std::shared_ptr<instruction>(new instr_nop(@$)); }
| JMP condition comma expression { $$ = std::shared_ptr<instruction>(new instr_jmp(@$, $2, $4)); }
| WAIT value wait_source { $$ = std::shared_ptr<instruction>(new instr_wait(@$, $2, $3)); }
| WAIT value COMMA value { std::stringstream msg; location l; l.begin = @2.end; l.end = @3.end; msg << "expected irq, gpio or pin after the polarity value and before the \",\""; throw yy::parser::syntax_error(l, msg.str()); }
| WAIT wait_source { $$ = std::shared_ptr<instruction>(new instr_wait(@$, resolvable_int(@$, 1), $2)); }
| IN in_source comma value { $$ = std::shared_ptr<instruction>(new instr_in(@$, $2, $4)); }
| OUT out_target comma value { $$ = std::shared_ptr<instruction>(new instr_out(@$, $2, $4)); }
| PUSH if_full blocking { $$ = std::shared_ptr<instruction>(new instr_push(@$, $2, $3)); }
| PULL if_empty blocking { $$ = std::shared_ptr<instruction>(new instr_pull(@$, $2, $3)); }
| MOV mov_target comma mov_op mov_source { $$ = std::shared_ptr<instruction>(new instr_mov(@$, $2, $5, $4)); }
| IRQ irq_modifiers value REL { $$ = std::shared_ptr<instruction>(new instr_irq(@$, $2, $3, true)); }
| IRQ irq_modifiers value { $$ = std::shared_ptr<instruction>(new instr_irq(@$, $2, $3)); }
| SET set_target comma value { $$ = std::shared_ptr<instruction>(new instr_set(@$, $2, $4)); }
;
%type <std::shared_ptr<resolvable>> delay;
delay:
LBRACKET expression RBRACKET { $$ = $2; }
%type <std::shared_ptr<resolvable>> sideset;
sideset:
SIDE value { $$ = $2; }
%type <enum condition> condition;
condition:
NOT X { $$ = condition::xz; }
| X POST_DECREMENT { $$ = condition::xnz__; }
| NOT Y { $$ = condition::yz; }
| Y POST_DECREMENT { $$ = condition::ynz__; }
| X NOT_EQUAL Y { $$ = condition::xney; }
| PIN { $$ = condition::pin; }
| NOT OSRE { $$ = condition::osrez; }
| %empty { $$ = condition::al; }
%type <std::shared_ptr<wait_source>> wait_source;
wait_source:
IRQ comma value REL { $$ = std::shared_ptr<wait_source>(new wait_source(wait_source::irq, $3, true)); }
| IRQ comma value { $$ = std::shared_ptr<wait_source>(new wait_source(wait_source::irq, $3, false)); }
| GPIO comma value { $$ = std::shared_ptr<wait_source>(new wait_source(wait_source::gpio, $3)); }
| PIN comma value { $$ = std::shared_ptr<wait_source>(new wait_source(wait_source::pin, $3)); }
comma: COMMA | %empty /* not a huge fan of forcing commas */
%type <enum in_out_set> in_source;
in_source: PINS { $$ = in_out_set::in_out_set_pins; }
| X { $$ = in_out_set::in_out_set_x; }
| Y { $$ = in_out_set::in_out_set_y; }
| NULL { $$ = in_out_set::in_out_null; }
| ISR { $$ = in_out_set::in_out_isr; }
| OSR { $$ = in_out_set::in_osr; }
| STATUS { $$ = in_out_set::in_status; }
%type <enum in_out_set> out_target;
out_target: PINS { $$ = in_out_set::in_out_set_pins; }
| X { $$ = in_out_set::in_out_set_x; }
| Y { $$ = in_out_set::in_out_set_y; }
| NULL { $$ = in_out_set::in_out_null; }
| PINDIRS { $$ = in_out_set::in_out_set_pindirs; }
| ISR { $$ = in_out_set::in_out_isr; }
| PC { $$ = in_out_set::out_set_pc; }
| EXEC { $$ = in_out_set::out_exec; }
%type <enum mov> mov_target;
mov_target: PINS { $$ = mov::pins; }
| X { $$ = mov::x; }
| Y { $$ = mov::y; }
| EXEC { $$ = mov::exec; }
| PC { $$ = mov::pc; }
| ISR { $$ = mov::isr; }
| OSR { $$ = mov::osr; }
%type <enum mov> mov_source;
mov_source: PINS { $$ = mov::pins; }
| X { $$ = mov::x; }
| Y { $$ = mov::y; }
| NULL { $$ = mov::null; }
| STATUS { $$ = mov::status; }
| ISR { $$ = mov::isr; }
| OSR { $$ = mov::osr; }
%type <enum mov_op> mov_op;
mov_op:
NOT { $$ = mov_op::invert; }
| REVERSE { $$ = mov_op::bit_reverse; }
| %empty { $$ = mov_op::none; }
%type <enum in_out_set> set_target;
set_target:
PINS { $$ = in_out_set::in_out_set_pins; }
| X { $$ = in_out_set::in_out_set_x; }
| Y { $$ = in_out_set::in_out_set_y; }
| PINDIRS { $$ = in_out_set::in_out_set_pindirs; }
%type <bool> if_full;
if_full:
IFFULL { $$ = true; }
| %empty { $$ = false; }
%type <bool> if_empty;
if_empty:
IFEMPTY { $$ = true; }
| %empty { $$ = false; }
%type <bool> blocking;
blocking:
BLOCK { $$ = true; }
| NOBLOCK { $$ = false; }
| %empty { $$ = true; }
%type <enum irq> irq_modifiers;
irq_modifiers:
CLEAR { $$ = irq::clear; }
| WAIT { $$ = irq::set_wait; }
| NOWAIT { $$ = irq::set; }
| SET { $$ = irq::set; }
| %empty { $$ = irq::set; }
%type <std::shared_ptr<symbol>> symbol_def;
symbol_def:
ID { $$ = std::shared_ptr<symbol>(new symbol(@$, $1)); }
| PUBLIC ID { $$ = std::shared_ptr<symbol>(new symbol(@$, $2, true)); }
| MULTIPLY ID { $$ = std::shared_ptr<symbol>(new symbol(@$, $2, true)); }
%%
void yy::parser::error(const location_type& l, const std::string& m)
{
if (l.begin.filename) {
std::cerr << l << ": " << m << '\n';
pioasm.error_count++;
if (l.begin.line == l.end.line && *l.begin.filename == *l.end.filename) {
std::ifstream file(l.begin.filename->c_str());
std::string line;
for(int i = 0; i < l.begin.line; ++i) {
std::getline(file, line);
}
fprintf(stderr, "%5d | %s\n", l.begin.line, line.c_str());
fprintf(stderr, "%5s | %*s", "", l.begin.column, "^");
for (int i = l.begin.column; i < l.end.column - 1; i++) {
putc ('~', stderr);
}
putc ('\n', stderr);
}
} else {
std::cerr << m << '\n';
}
}

View File

@ -0,0 +1,391 @@
/*
* Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#include <cstdio>
#include <iterator>
#include "pio_assembler.h"
#include "parser.hpp"
#ifdef _MSC_VER
#pragma warning(disable : 4996) // fopen
#endif
using syntax_error = yy::parser::syntax_error;
std::vector<std::shared_ptr<output_format>> output_format::output_formats;
std::string output_format::default_name = "c-sdk";
pio_assembler::pio_assembler() {
}
int pio_assembler::generate(std::shared_ptr<output_format> _format, const std::string &_source,
const std::string &_dest, const std::vector<std::string> &_options) {
format = _format;
source = _source;
dest = _dest;
options = _options;
location.initialize(&source);
scan_begin();
yy::parser parse(*this);
// parse.set_debug_level(false);
int res = parse();
scan_end();
return res;
}
void program::add_instruction(std::shared_ptr<instruction> inst) {
uint limit = MAX_INSTRUCTIONS;
if (instructions.size() >= limit) {
// todo take offset into account
std::stringstream msg;
msg << "program instruction limit of " << limit << " instruction(s) exceeded";
throw syntax_error(inst->location, msg.str());
}
if (!sideset_opt && !inst->sideset) {
std::stringstream msg;
msg << "instruction requires 'side' to specify side set value for the instruction because non optional sideset was specified for the program at " << sideset.location;
throw syntax_error(inst->location, msg.str());
}
instructions.push_back(inst);
}
using syntax_error = yy::parser::syntax_error;
void program::add_symbol(std::shared_ptr<symbol> symbol) {
const auto &existing = pioasm->get_symbol(symbol->name, this);
if (existing) {
std::stringstream msg;
if (symbol->is_label != existing->is_label) {
msg << "'" << symbol->name << "' was already defined as a " << (existing->is_label ? "label" : "value")
<< " at " << existing->location;
} else if (symbol->is_label) {
msg << "label '" << symbol->name << "' was already defined at " << existing->location;
} else {
msg << "'" << symbol->name << "' was already defined at " << existing->location;
}
throw syntax_error(symbol->location, msg.str());
}
symbols.insert(std::pair<std::string, std::shared_ptr<::symbol>>(symbol->name, symbol));
ordered_symbols.push_back(symbol);
}
int resolvable::resolve(const program &program) {
return resolve(program.pioasm, &program);
}
int unary_operation::resolve(pio_assembler *pioasm, const program *program, const resolvable &scope) {
int value = arg->resolve(pioasm, program, scope);
switch (op) {
case negate:
return -value;
case reverse: {
// slow is fine
uint result = 0;
for (uint i = 0; i < 32; i++) {
result <<= 1u;
if (value & 1u) {
result |= 1u;
}
value >>= 1u;
}
return result;
}
default:
throw syntax_error(location, "internal error");
}
}
int binary_operation::resolve(pio_assembler *pioasm, const program *program, const resolvable &scope) {
int lvalue = left->resolve(pioasm, program, scope);
int rvalue = right->resolve(pioasm, program, scope);
switch (op) {
case add:
return lvalue + rvalue;
case subtract:
return lvalue - rvalue;
case multiply:
return lvalue * rvalue;
case divide:
return lvalue / rvalue;
case and_:
return lvalue & rvalue;
case or_:
return lvalue | rvalue;
case xor_:
return lvalue ^ rvalue;
default:
throw syntax_error(location, "internal error");
}
}
void program::set_wrap(const yy::location &l) {
if (wrap) {
std::stringstream msg;
msg << ".wrap was already specified at " << wrap->location;
throw syntax_error(l, msg.str());
}
if (instructions.empty()) {
throw syntax_error(l, ".wrap cannot be pleaced before the first program instruction");
}
wrap = resolvable_int(l, instructions.size() - 1);
}
void program::set_wrap_target(const yy::location &l) {
if (wrap_target) {
std::stringstream msg;
msg << ".wrap_target was already specified at " << wrap_target->location;
throw syntax_error(l, msg.str());
}
wrap_target = resolvable_int(l, instructions.size());
}
void program::add_code_block(const code_block &block) {
code_blocks[block.lang].push_back(block);
}
void program::add_lang_opt(std::string lang, std::string name, std::string value) {
lang_opts[lang].emplace_back(name, value);
}
void program::finalize() {
if (sideset.value) {
int bits = sideset.value->resolve(*this);
if (bits < 0) {
throw syntax_error(sideset.value->location, "number of side set bits must be positive");
}
sideset_max = (1u << bits) - 1;
if (sideset_opt) bits++;
sideset_bits_including_opt = bits;
if (bits > 5) {
if (sideset_opt)
throw syntax_error(sideset.value->location, "maximum number of side set bits with optional is 4");
else
throw syntax_error(sideset.value->location, "maximum number of side set bits is 5");
}
delay_max = (1u << (5 - bits)) - 1;
} else {
sideset_max = 0;
delay_max = 31;
}
}
int name_ref::resolve(pio_assembler *pioasm, const program *program, const resolvable &scope) {
auto symbol = pioasm->get_symbol(name, program);
if (symbol) {
if (symbol->resolve_started) {
std::stringstream msg;
msg << "circular dependency in definition of '" << name << "'; detected at " << location << ")";
throw syntax_error(scope.location, msg.str());
}
try {
symbol->resolve_started++;
int rc = symbol->value->resolve(pioasm, program, scope);
symbol->resolve_started--;
return rc;
} catch (syntax_error &e) {
symbol->resolve_started--;
throw e;
}
} else {
std::stringstream msg;
msg << "undefined symbol '" << name << "'";
throw syntax_error(location, msg.str());
}
}
uint instruction::encode(const program &program) {
raw_encoding raw = raw_encode(program);
int _delay = delay->resolve(program);
if (_delay < 0) {
throw syntax_error(delay->location, "instruction delay must be positive");
}
if (_delay > program.delay_max) {
if (program.delay_max == 31) {
throw syntax_error(delay->location, "instruction delay must be <= 31");
} else {
std::stringstream msg;
msg << "the instruction delay limit is " << program.delay_max << " because of the side set specified at "
<< program.sideset.location;
throw syntax_error(delay->location, msg.str());
}
}
int _sideset = 0;
if (sideset) {
_sideset = sideset->resolve(program);
if (_sideset < 0) {
throw syntax_error(sideset->location, "side set value must be >=0");
}
if (_sideset > program.sideset_max) {
std::stringstream msg;
msg << "the maximum side set value is " << program.sideset_max << " based on the configuration specified at "
<< program.sideset.location;
throw syntax_error(sideset->location, msg.str());
}
_sideset <<= (5u - program.sideset_bits_including_opt);
if (program.sideset_opt) {
_sideset |= 0x10u;
}
}
return (((uint) raw.type) << 13u) | (((uint) _delay | (uint) _sideset) << 8u) | (raw.arg1 << 5u) | raw.arg2;
}
raw_encoding instruction::raw_encode(const program &program) {
throw syntax_error(location, "internal error");
}
uint instr_word::encode(const program &program) {
uint value = encoding->resolve(program);
if (value > 0xffffu) {
throw syntax_error(location, ".word value must be a positive 16 bit value");
}
return value;
}
raw_encoding instr_jmp::raw_encode(const program &program) {
int dest = target->resolve(program);
if (dest < 0) {
throw syntax_error(target->location, "jmp target address must be positive");
} else if (dest >= (int)program.instructions.size()) {
std::stringstream msg;
msg << "jmp target address " << dest << " is beyond the end of the program";
throw syntax_error(target->location, msg.str());
}
return {inst_type::jmp, (uint) cond, (uint) dest};
}
raw_encoding instr_in::raw_encode(const program &program) {
int v = value->resolve(program);
if (v < 1 || v > 32) {
throw syntax_error(value->location, "'in' bit count must be >= 1 and <= 32");
}
return {inst_type::in, (uint) src, (uint) v & 0x1fu};
}
raw_encoding instr_out::raw_encode(const program &program) {
int v = value->resolve(program);
if (v < 1 || v > 32) {
throw syntax_error(value->location, "'out' bit count must be >= 1 and <= 32");
}
return {inst_type::out, (uint) dest, (uint) v & 0x1fu};
}
raw_encoding instr_set::raw_encode(const program &program) {
int v = value->resolve(program);
if (v < 0 || v > 31) {
throw syntax_error(value->location, "'set' bit count must be >= 0 and <= 31");
}
return {inst_type::set, (uint) dest, (uint) v};
}
raw_encoding instr_wait::raw_encode(const program &program) {
uint pol = polarity->resolve(program);
if (pol > 1) {
throw syntax_error(polarity->location, "'wait' polarity must be 0 or 1");
}
uint arg2 = source->param->resolve(program);
switch (source->target) {
case wait_source::irq:
if (arg2 > 7) throw syntax_error(source->param->location, "irq number must be must be >= 0 and <= 7");
break;
case wait_source::gpio:
if (arg2 > 31)
throw syntax_error(source->param->location, "absolute GPIO number must be must be >= 0 and <= 31");
break;
case wait_source::pin:
if (arg2 > 31) throw syntax_error(polarity->location, "pin number must be must be >= 0 and <= 31");
break;
}
return {inst_type::wait, (pol << 2u) | (uint) source->target, arg2 | (source->flag ? 0x10u : 0u)};
}
raw_encoding instr_irq::raw_encode(const program &program) {
uint arg2 = num->resolve(program);
if (arg2 > 7) throw syntax_error(num->location, "irq number must be must be >= 0 and <= 7");
if (relative) arg2 |= 0x20u;
return {inst_type::irq, (uint)modifiers, arg2};
}
std::vector<compiled_source::symbol> pio_assembler::public_symbols(program &program) {
std::vector<std::shared_ptr<symbol>> public_symbols;
std::remove_copy_if(program.ordered_symbols.begin(), program.ordered_symbols.end(),
std::inserter(public_symbols, public_symbols.end()),
[](const std::shared_ptr<symbol> &s) { return !s->is_public; });
std::vector<compiled_source::symbol> rc;
std::transform(public_symbols.begin(), public_symbols.end(), std::back_inserter(rc),
[&](const std::shared_ptr<symbol> &s) {
return compiled_source::symbol(s->name, s->value->resolve(program), s->is_label);
});
return rc;
}
int pio_assembler::write_output() {
std::set<std::string> known_output_formats;
std::transform(output_format::output_formats.begin(), output_format::output_formats.end(),
std::inserter(known_output_formats, known_output_formats.begin()),
[&](std::shared_ptr<output_format> &f) {
return f->name;
});
compiled_source source;
source.global_symbols = public_symbols(get_dummy_global_program());
for (auto &program : programs) {
program.finalize();
source.programs.emplace_back(compiled_source::program(program.name));
auto &cprogram = source.programs[source.programs.size() - 1];
cprogram = compiled_source::program(program.name);
// encode the instructions
std::transform(program.instructions.begin(), program.instructions.end(),
std::back_inserter(cprogram.instructions), [&](std::shared_ptr<instruction> &inst) {
return inst->encode(program);
});
for (const auto &e : program.code_blocks) {
bool ok = false;
for(const auto &o : known_output_formats) {
if (o == e.first || 0 == e.first.find(o+"-")) {
ok = true;
break;
}
}
if (!ok) {
std::cerr << e.second[0].location << ": warning, unknown code block output type '" << e.first << "'\n";
known_output_formats.insert(e.first);
}
}
if (program.wrap) cprogram.wrap = program.wrap->resolve(program); else cprogram.wrap = std::max((int)program.instructions.size() - 1, 0);
if (program.wrap_target) cprogram.wrap_target = program.wrap_target->resolve(program); else cprogram.wrap_target = 0;
if (program.origin.value) cprogram.origin = program.origin.value->resolve(program);
if (program.sideset.value) {
cprogram.sideset_bits_including_opt = program.sideset_bits_including_opt;
cprogram.sideset_opt = program.sideset_opt;
cprogram.sideset_pindirs = program.sideset_pindirs;
}
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) {
std::vector<std::string> blocks;
std::transform(e.second.begin(), e.second.end(), std::back_inserter(blocks), [&](const code_block& block) {
return block.contents;
});
return std::pair<std::string, std::vector<std::string>>(e.first, blocks);
});
cprogram.lang_opts = program.lang_opts;
cprogram.symbols = public_symbols(program);
}
if (programs.empty()) {
std::cout << "warning: input contained no programs" << std::endl;
}
return format->output(dest, options, source);
}
FILE *output_format::open_single_output(std::string destination) {
FILE *out = destination == "-" ? stdout : fopen(destination.c_str(), "w");
if (!out) {
std::cerr << "Can't open output file '" << destination << "'" << std::endl;
}
return out;
}

View File

@ -0,0 +1,103 @@
/*
* Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#ifndef _PIO_ASSEMBLER_H
#define _PIO_ASSEMBLER_H
#include <algorithm>
#include "parser.hpp"
#include "output_format.h"
// Give Flex the prototype of yylex we want ...
# define YY_DECL \
yy::parser::symbol_type yylex (pio_assembler& pioasm)
// ... and declare it for the parser's sake.
YY_DECL;
struct pio_assembler {
public:
using syntax_error = yy::parser::syntax_error;
using location_type = yy::parser::location_type;
std::shared_ptr<program> dummy_global_program;
std::vector<program> programs;
int error_count = 0;
pio_assembler();
std::shared_ptr<output_format> format;
// The name of the file being parsed.
std::string source;
// name of the output file or "-" for stdout
std::string dest;
std::vector<std::string> options;
int write_output();
bool add_program(const yy::location &l, const std::string &name) {
if (std::find_if(programs.begin(), programs.end(), [&](const program &p) { return p.name == name; }) ==
programs.end()) {
programs.emplace_back(this, l, name);
return true;
} else {
return false;
}
}
program &get_dummy_global_program() {
if (!dummy_global_program) {
dummy_global_program = std::shared_ptr<program>(new program(this, yy::location(&source), ""));
}
return *dummy_global_program;
}
program &get_current_program(const location_type &l, const std::string &requiring_program,
bool before_any_instructions = false, bool disallow_global = true) {
if (programs.empty()) {
if (disallow_global) {
std::stringstream msg;
msg << requiring_program << " is invalid outside of a program";
throw syntax_error(l, msg.str());
}
return get_dummy_global_program();
}
auto &p = programs[programs.size() - 1];
if (before_any_instructions && !p.instructions.empty()) {
std::stringstream msg;
msg << requiring_program << " must preceed any program instructions";
throw syntax_error(l, msg.str());
}
return p;
}
// note p may be null for global symbols only
std::shared_ptr<symbol> get_symbol(const std::string &name, const program *p) {
const auto &i = get_dummy_global_program().symbols.find(name);
if (i != get_dummy_global_program().symbols.end())
return i->second;
if (p) {
const auto &i2 = p->symbols.find(name);
if (i2 != p->symbols.end())
return i2->second;
}
return nullptr;
}
std::vector<compiled_source::symbol> public_symbols(program &program);
int generate(std::shared_ptr<output_format> _format, const std::string &_source, const std::string &_dest,
const std::vector<std::string> &_options = std::vector<std::string>());
// Handling the scanner.
void scan_begin();
void scan_end();
// The token's location used by the scanner.
yy::location location;
};
#endif

View File

@ -0,0 +1,171 @@
/*
* Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#include <array>
#include <sstream>
#include <iomanip>
#include "pio_disassembler.h"
extern "C" void disassemble(char *buf, int buf_len, uint16_t inst, uint sideset_bits, bool sideset_opt) {
if (buf_len) buf[disassemble(inst, sideset_bits, sideset_opt).copy(buf, buf_len - 1)] = 0;
}
std::string disassemble(uint16_t inst, uint sideset_bits_including_opt, bool sideset_opt) {
std::stringstream ss;
uint major = inst >> 13u;
uint arg1 = ((uint) inst >> 5u) & 0x7u;
uint arg2 = inst & 0x1fu;
auto op = [&](const std::string &s) {
ss << std::left << std::setw(7) << s;
};
auto op_guts = [&](const std::string &s) {
ss << std::left << std::setw(16) << s;
};
bool invalid = false;
switch (major) {
case 0b000: {
static std::array<std::string, 8> conditions{"", "!x, ", "x--, ", "!y, ", "y--, ", "x != y, ", "pin, ",
"!osre, "};
op("jmp");
op_guts(conditions[arg1] + std::to_string(arg2));
break;
}
case 0b001: {
uint source = arg1 & 3u;
std::string guts;
switch (source) {
case 0b00:
guts = "gpio, " + std::to_string(arg2);
break;
case 0b01:
guts = "pin, " + std::to_string(arg2);
break;
case 0b10:
if (arg2 & 0x8u) {
invalid = true;
} else {
guts = "irq, " + std::to_string(arg2 & 7u);
if (arg2 & 0x10u) {
guts += " rel";
}
}
break;
}
if (!invalid) {
guts = ((arg1 & 4u) ? "1 " : "0 ") + guts;
op("wait");
op_guts(guts);
}
break;
}
case 0b010: {
static std::array<std::string, 8> sources { "pins", "x", "y", "null", "", "status", "isr", "osr"};
std::string source = sources[arg1];
if (source.empty()) {
invalid = true;
} else {
op("in");
op_guts(source + ", " + std::to_string(arg2 ? arg2 : 32));
}
break;
}
case 0b011: {
static std::array<std::string, 8> dests { "pins", "x", "y", "null", "pindirs", "pc", "isr", "exec"};
op("out");
op_guts(dests[arg1] + ", " + std::to_string(arg2 ? arg2 : 32));
break;
}
case 0b100: {
if (arg2) {
invalid = true;
} else {
std::string guts = "";
if (arg1 & 4u) {
op("pull");
if (arg1 & 2u) guts = "ifempty";
} else {
op("push");
if (arg1 & 2u) guts = "iffull";
}
guts += (arg1 & 0x1u) ? "block" : "noblock";
op_guts(guts);
}
break;
}
case 0b101: {
static std::array<std::string, 8> dests { "pins", "x", "y", "", "exec", "pc", "isr", "osr"};
static std::array<std::string, 8> sources { "pins", "x", "y", "null", "", "status", "isr", "osr"};
std::string dest = dests[arg1];
std::string source = sources[arg2 & 7u];
uint operation = arg2 >> 3u;
if (source.empty() || dest.empty() || operation == 3) {
invalid = true;
}
if (dest == source && !operation && (arg1 == 1 || arg2 == 2)) {
op("nop");
op_guts("");
} else {
op("mov");
std::string guts = dest + ", ";
if (operation == 1) {
guts += "!";
} else if (operation == 2) {
guts += "::";
}
guts += source;
op_guts(guts);
}
break;
}
case 0b110: {
if ((arg1 & 0x4u) || (arg2 & 0x8u)) {
invalid = true;
} else {
op("irq");
std::string guts;
if (arg1 & 0x2u) {
guts += "clear ";
} else if (arg1 & 0x1u) {
guts += "wait ";
} else {
guts += "nowait ";
}
guts += std::to_string(arg2 & 7u);
if (arg2 & 0x10u) {
guts += " rel";
}
op_guts(guts);
}
break;
}
case 0b111: {
static std::array<std::string, 8> dests{"pins", "x", "y", "", "pindirs", "", "", ""};
std::string dest = dests[arg1];
if (dest.empty()) {
invalid = true;
} else {
op("set");
op_guts(dests[arg1] + ", " + std::to_string(arg2));
}
break;
}
}
if (invalid) {
return "reserved";
}
uint delay = ((uint) inst >> 8u) & 0x1f;
ss << std::left << std::setw(7);
if (sideset_bits_including_opt && (!sideset_opt || (delay & 0x10u))) {
ss << ("side "+ std::to_string((delay & (sideset_opt ? 0xfu : 0x1fu)) >> (5u - sideset_bits_including_opt)));
} else {
ss << "";
}
delay &= ((1u << (5 - sideset_bits_including_opt)) - 1u);
ss << std::left << std::setw(4) << (delay ? ("[" + std::to_string(delay) + "]") : "");
return ss.str();
}

View File

@ -0,0 +1,22 @@
/*
* Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#ifndef _PIO_DISASSEMBLER_H
#define _PIO_DISASSEMBLER_H
#ifdef __cplusplus
#include <string>
typedef unsigned int uint;
std::string disassemble(uint16_t inst, uint sideset_bits, bool sideset_opt);
extern "C" void disassemble(char *buf, int buf_len, uint16_t inst, uint sideset_bits, bool sideset_opt);
#else
void disassemble(char *buf, int buf_len, uint inst, uint sideset_bits, bool sideset_opt);
#endif
#endif

396
tools/pioasm/pio_types.h Normal file
View File

@ -0,0 +1,396 @@
/*
* Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#ifndef _PIO_TYPES_H
#define _PIO_TYPES_H
#include <string>
#include <map>
#include <set>
#include <utility>
#include <vector>
#include <memory>
#include "location.h"
typedef unsigned int uint;
enum struct inst_type {
jmp = 0x0,
wait = 0x1,
in = 0x2,
out = 0x3,
push_pull = 0x4,
mov = 0x5,
irq = 0x6,
set = 0x7,
};
/* condition codes */
enum struct condition {
al = 0x0,
xz = 0x1,
xnz__ = 0x2,
yz = 0x3,
ynz__ = 0x4,
xney = 0x5,
pin = 0x6,
osrez = 0x7,
};
/* in source / out / set target - not all valid */
enum struct in_out_set {
in_out_set_pins = 0x0,
in_out_set_x = 0x1,
in_out_set_y = 0x2,
in_out_null = 0x3,
in_out_set_pindirs = 0x4,
in_status = 0x5,
out_set_pc = 0x5,
in_out_isr = 0x6,
in_osr = 0x7,
out_exec = 0x7,
};
enum struct irq {
set = 0x0,
set_wait = 0x1,
clear = 0x2,
};
// mov src/dest (not all valid)
enum struct mov {
pins = 0x0,
x = 0x1,
y = 0x2,
null = 0x3,
exec = 0x4,
pc = 0x5,
status = 0x5,
isr = 0x6,
osr = 0x7,
};
enum struct mov_op {
none = 0x0,
invert = 0x1,
bit_reverse = 0x2,
};
struct src_item {
yy::location location;
src_item() = default;
explicit src_item(const yy::location &location) : location(location) {}
};
struct program;
struct pio_assembler;
struct resolvable : public src_item {
resolvable(const yy::location &l) : src_item(l) {}
int resolve(const program &program);
int resolve(pio_assembler *pioasm, const program *program) {
return resolve(pioasm, program, *this);
}
virtual int resolve(pio_assembler *pioasm, const program *program, const resolvable &scope) = 0;
};
using rvalue = std::shared_ptr<resolvable>;
struct wait_source {
enum type {
gpio = 0x0,
pin = 0x1,
irq = 0x2
} target;
rvalue param;
bool flag;
wait_source(type target, rvalue param, bool flag = false) : target(target), param(std::move(param)), flag(flag) {}
};
struct name_ref : public resolvable {
std::string name;
name_ref(const yy::location &l, std::string name) : resolvable(l), name(std::move(name)) {}
int resolve(pio_assembler *pioasm, const program *program, const resolvable &scope) override;
};
struct code_block : public resolvable {
std::string lang;
std::string contents;
code_block(const yy::location &l, std::string lang, std::string contents) : resolvable(l), lang(std::move(lang)),
contents(std::move(contents)) {}
int resolve(pio_assembler *pioasm, const program *program, const resolvable &scope) override {
return 0;
}
};
struct int_value : public resolvable {
int value;
int_value(const yy::location &l, int value) : resolvable(l), value(value) {}
int resolve(pio_assembler *pioasm, const program *program, const resolvable &scope) override {
return value;
}
};
static inline rvalue resolvable_int(const yy::location &l, int v) {
return std::shared_ptr<resolvable>(new int_value(l, v));
}
struct binary_operation : public resolvable {
enum op_type {
add,
subtract,
multiply,
divide,
and_, // pesky C++
or_,
xor_
};
op_type op;
rvalue left, right;
binary_operation(const yy::location &l, op_type op, rvalue left, rvalue right) :
resolvable(l), op(op), left(std::move(left)), right(std::move(right)) {}
int resolve(pio_assembler *pioasm, const program *program, const resolvable &scope) override;
};
struct unary_operation : public resolvable {
enum op_type {
negate,
reverse
};
op_type op;
rvalue arg;
unary_operation(const yy::location &l, op_type op, const rvalue &arg) :
resolvable(l), op(op), arg(arg) {}
int resolve(pio_assembler *pioasm, const program *program, const resolvable &scope) override;
};
struct symbol : public src_item {
std::string name;
rvalue value;
bool is_public;
bool is_label;
int resolve_started;
symbol(const yy::location &l, std::string name, bool is_extern = false) : src_item(l), name(std::move(name)),
is_public(is_extern), is_label(false),
resolve_started(false) {}
};
struct raw_encoding {
enum inst_type type;
uint arg1;
uint arg2;
};
struct instruction : public src_item {
rvalue sideset; // possibly null
rvalue delay;
instruction(const yy::location &l) : src_item(l) {}
virtual uint encode(const program &program);
virtual raw_encoding raw_encode(const program &program);
};
struct pio_assembler;
// rvalue with extra encompassing location
struct rvalue_loc {
rvalue value;
yy::location location;
rvalue_loc() = default;
rvalue_loc(const rvalue &v, const yy::location &l) : value(v), location(l) {}
};
struct program : public src_item {
static const int MAX_INSTRUCTIONS = 32;
pio_assembler *pioasm;
std::string name;
rvalue_loc origin;
rvalue_loc sideset;
bool sideset_opt;
bool sideset_pindirs;
rvalue wrap_target;
rvalue wrap;
std::map<std::string, std::shared_ptr<symbol>> symbols;
std::vector<std::shared_ptr<symbol>> ordered_symbols;
std::vector<std::shared_ptr<instruction>> instructions;
std::map<std::string, std::vector<code_block>> code_blocks;
std::map<std::string, std::vector<std::pair<std::string,std::string>>> lang_opts;
// post finalization
int delay_max;
int sideset_bits_including_opt; // specified side set bits + 1 if we need presence flag
int sideset_max;
program(pio_assembler *pioasm, const yy::location &l, std::string name) :
src_item(l), pioasm(pioasm), name(std::move(name)), sideset_opt(true), sideset_pindirs(false) {}
void set_origin(const yy::location &l, const rvalue &_origin) {
origin = rvalue_loc(_origin, l);
}
void set_wrap_target(const yy::location &l);
void set_wrap(const yy::location &l);
void set_sideset(const yy::location &l, rvalue _sideset, bool optional, bool pindirs) {
sideset = rvalue_loc(_sideset, l);
sideset_opt = optional;
sideset_pindirs = pindirs;
}
void add_label(std::shared_ptr<symbol> label) {
label->value = resolvable_int(label->location, instructions.size());
add_symbol(label);
}
void add_symbol(std::shared_ptr<symbol> symbol);
void add_instruction(std::shared_ptr<instruction> inst);
void add_code_block(const code_block &block);
void add_lang_opt(std::string lang, std::string name, std::string value);
void finalize();
};
struct instr_jmp : public instruction {
condition cond;
rvalue target;
instr_jmp(const yy::location &l, condition c, rvalue target) : instruction(l), cond(c), target(std::move(target)) { }
raw_encoding raw_encode(const program &program) override;
};
struct instr_wait : public instruction {
rvalue polarity;
std::shared_ptr<wait_source> source;
instr_wait(const yy::location &l, rvalue polarity, std::shared_ptr<wait_source> source) : instruction(l), polarity(
std::move(polarity)), source(std::move(source)) {}
raw_encoding raw_encode(const program &program) override;
};
struct instr_in : public instruction {
enum in_out_set src;
rvalue value;
instr_in(const yy::location &l, const enum in_out_set &src, rvalue value) : instruction(l), src(src),
value(std::move(value)) {}
raw_encoding raw_encode(const program &program) override;
};
struct instr_out : public instruction {
enum in_out_set dest;
rvalue value;
instr_out(const yy::location &l, const enum in_out_set &dest, rvalue value) : instruction(l), dest(dest),
value(std::move(value)) {}
raw_encoding raw_encode(const program &program) override;
};
struct instr_set : public instruction {
enum in_out_set dest;
rvalue value;
instr_set(const yy::location &l, const enum in_out_set &dest, rvalue value) : instruction(l), dest(dest),
value(std::move(value)) {}
raw_encoding raw_encode(const program &program) override;
};
struct instr_push : public instruction {
bool if_full, blocking;
instr_push(const yy::location &l, bool if_full, bool blocking) : instruction(l), if_full(if_full),
blocking(blocking) {}
raw_encoding raw_encode(const program &program) override {
uint arg1 = (blocking ? 1u : 0u) | (if_full ? 0x2u : 0);
return {inst_type::push_pull, arg1, 0};
}
};
struct instr_pull : public instruction {
bool if_empty, blocking;
instr_pull(const yy::location &l, bool if_empty, bool blocking) : instruction(l), if_empty(if_empty),
blocking(blocking) {}
raw_encoding raw_encode(const program &program) override {
uint arg1 = (blocking ? 1u : 0u) | (if_empty ? 0x2u : 0) | 0x4u;
return {inst_type::push_pull, arg1, 0};
}
};
struct instr_mov : public instruction {
enum mov dest, src;
mov_op op;
instr_mov(const yy::location &l, const enum mov &dest, const enum mov &src, const mov_op& op = mov_op::none) :
instruction(l), dest(dest), src(src), op(op) {}
raw_encoding raw_encode(const program &program) override {
return {inst_type::mov, (uint) dest, (uint)src | ((uint)op << 3u)};
}
};
struct instr_irq : public instruction {
enum irq modifiers;
rvalue num;
bool relative;
instr_irq(const yy::location &l, const enum irq &modifiers, rvalue num, bool relative = false) :
instruction(l), modifiers(modifiers), num(std::move(num)), relative(relative) {}
raw_encoding raw_encode(const program &program) override;
};
struct instr_nop : public instr_mov {
instr_nop(const yy::location &l) : instr_mov(l, mov::y, mov::y) {}
};
struct instr_word : public instruction {
rvalue encoding;
instr_word(const yy::location &l, rvalue encoding) : instruction(l), encoding(std::move(encoding)) {}
uint encode(const program &program) override;
};
#endif

View File

@ -0,0 +1,323 @@
/*
* Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#include <array>
#include <algorithm>
#include <sstream>
#include <iomanip>
#include <iostream>
#include "output_format.h"
#include "pio_disassembler.h"
struct python_output : public output_format {
struct factory {
factory() {
output_format::add(new python_output());
}
};
python_output() : output_format("python") {}
std::string get_description() override {
return "Python file suitable for use with MicroPython";
}
void output_symbols(FILE *out, std::string prefix, const std::vector<compiled_source::symbol> &symbols) {
int count = 0;
for (const auto &s : symbols) {
if (!s.is_label) {
fprintf(out, "%s%s = %d\n", prefix.c_str(), s.name.c_str(), s.value);
count++;
}
}
if (count) {
fprintf(out, "\n");
count = 0;
}
for (const auto &s : symbols) {
if (s.is_label) {
fprintf(out, "%soffset_%s = %d\n", prefix.c_str(), s.name.c_str(), s.value);
count++;
}
}
if (count) {
fprintf(out, "\n");
}
}
void header(FILE *out, std::string msg) {
std::string dashes = std::string(msg.length(), '-');
fprintf(out, "# %s #\n", dashes.c_str());
fprintf(out, "# %s #\n", msg.c_str());
fprintf(out, "# %s #\n", dashes.c_str());
fprintf(out, "\n");
}
int output(std::string destination, std::vector<std::string> output_options,
const compiled_source &source) override {
FILE *out = open_single_output(destination);
if (!out) return 1;
header(out, "This file is autogenerated by pioasm; do not edit!");
fprintf(out, "import rp2\n");
fprintf(out, "from machine import Pin");
fprintf(out, "\n");
output_symbols(out, "", source.global_symbols);
for (const auto &program : source.programs) {
header(out, program.name);
std::string prefix = program.name + "_";
output_symbols(out, prefix, program.symbols);
int param_count = 0;
auto write_opt = [&] (std::string name, std::string value) {
if (param_count++) {
fprintf(out, ", ");
}
fprintf(out, "%s=%s", name.c_str(), value.c_str());
};
fprintf(out, "@rp2.asm_pio(");
for(const auto &p : program.lang_opts) {
if (p.first.size() >= name.size() && p.first.compare(0, name.size(), name) == 0) {
for (const auto &p2 : p.second) {
write_opt(p2.first, p2.second);
}
}
}
fprintf(out, ")\n");
fprintf(out, "def %s():\n", program.name.c_str());
std::map<uint, std::string> jmp_labels;
// for now just use numeric labels
for (int i = 0; i < (int)program.instructions.size(); i++) {
const auto &inst = program.instructions[i];
if (!(inst >> 13u)) {
// a jump
uint target = inst &0x1fu;
jmp_labels.insert(std::pair<uint,std::string>(target, std::to_string(target)));
}
}
for (uint i = 0; i < (int)program.instructions.size(); i++) {
const auto &inst = program.instructions[i];
if (i == program.wrap_target) {
fprintf(out, " wrap_target()\n");
}
auto it = jmp_labels.find(i);
if (it != jmp_labels.end()) {
fprintf(out, " label(\"%s\")\n", it->second.c_str());
}
fprintf(out, " %s # %d\n", disassemble(jmp_labels, inst, program.sideset_bits_including_opt.get(), program.sideset_opt).c_str(), i);
if (i == program.wrap) {
fprintf(out, " wrap()\n");
}
}
fprintf(out, "\n");
/*
fprintf(out, "static inline pio_sm_config %sprogram_default_config(uint offset) {\n", prefix.c_str());
fprintf(out, " pio_sm_config c = pio_sm_default_config();\n");
fprintf(out, " sm_config_wrap(&c, offset + %swrap_target, offset + %swrap);\n", prefix.c_str(),
prefix.c_str());
if (program.sideset_bits_including_opt.is_specified()) {
fprintf(out, " sm_config_sideset(&c, %d, %s, %s);\n", program.sideset_bits_including_opt.get(),
program.sideset_opt ? "true" : "false",
program.sideset_pindirs ? "true" : "false");
}
fprintf(out, " return c;\n");
fprintf(out, "}\n");
*/
// todo maybe have some code blocks inside or outside here?
for(const auto& o : program.code_blocks) {
fprintf(out, "\n");
if (o.first == name) {
for(const auto &contents : o.second) {
fprintf(out, "%s", contents.c_str());
fprintf(out, "\n");
}
}
}
fprintf(out, "\n");
}
if (out != stdout) { fclose(out); }
return 0;
}
static std::string disassemble(const std::map<uint, std::string>& jmp_labels, uint16_t inst, uint sideset_bits_including_opt, bool sideset_opt) {
std::stringstream ss;
uint major = inst >> 13u;
uint arg1 = ((uint) inst >> 5u) & 0x7u;
uint arg2 = inst & 0x1fu;
std::string op_string;
auto op = [&](const std::string &s) {
op_string = s;
};
auto op_guts = [&](const std::string &s) {
ss << std::left << std::setw(24) << (op_string + "(" + s + ")");
};
bool invalid = false;
switch (major) {
case 0b000: {
static std::array<std::string, 8> conditions{"", "not_x", "x_dec", "not_y", "y_dec", "x_not_y", "pin",
"not_osre"};
op("jmp");
auto it = jmp_labels.find(arg2);
std::string label = "?";
if (it != jmp_labels.end()) {
label = it->second;
}
if (arg1)
op_guts(conditions[arg1] + ", \"" + label +"\"");
else
op_guts("\"" + label + "\"");
break;
}
case 0b001: {
uint source = arg1 & 3u;
std::string guts;
switch (source) {
case 0b00:
guts = "gpio, " + std::to_string(arg2);
break;
case 0b01:
guts = "pin, " + std::to_string(arg2);
break;
case 0b10:
if (arg2 & 0x8u) {
invalid = true;
} else {
guts = "irq, " + std::to_string(arg2 & 7u);
if (arg2 & 0x10u) {
guts += " rel";
}
}
break;
}
if (!invalid) {
guts = ((arg1 & 4u) ? "1 " : "0 ") + guts;
op("wait");
op_guts(guts);
}
break;
}
case 0b010: {
static std::array<std::string, 8> sources { "pins", "x", "y", "null", "", "status", "isr", "osr"};
std::string source = sources[arg1];
if (source.empty()) {
invalid = true;
} else {
op("in");
op_guts(source + ", " + std::to_string(arg2 ? arg2 : 32));
}
break;
}
case 0b011: {
static std::array<std::string, 8> dests { "pins", "x", "y", "null", "pindirs", "pc", "isr", "exec"};
op("out");
op_guts(dests[arg1] + ", " + std::to_string(arg2 ? arg2 : 32));
break;
}
case 0b100: {
if (arg2) {
invalid = true;
} else {
std::string guts = "";
if (arg1 & 4u) {
op("pull");
if (arg1 & 2u) guts = "ifempty";
} else {
op("push");
if (arg1 & 2u) guts = "iffull";
}
guts += ", ";
guts += ((arg1 & 0x1u) ? "block" : "noblock");
op_guts(guts);
}
break;
}
case 0b101: {
static std::array<std::string, 8> dests { "pins", "x", "y", "", "exec", "pc", "isr", "osr"};
static std::array<std::string, 8> sources { "pins", "x", "y", "null", "", "status", "isr", "osr"};
std::string dest = dests[arg1];
std::string source = sources[arg2 & 7u];
uint operation = arg2 >> 3u;
if (source.empty() || dest.empty() || operation == 3) {
invalid = true;
}
if (dest == source && (arg1 == 1 || arg2 == 2)) {
op("nop");
op_guts("");
} else {
op("mov");
std::string guts = dest + ", ";
if (operation == 1) {
guts += "not ";
} else if (operation == 2) {
guts += "reverse ";
}
guts += source;
op_guts(guts);
}
break;
}
case 0b110: {
if ((arg1 & 0x4u) || (arg2 & 0x8u)) {
invalid = true;
} else {
op("irq");
std::string guts;
if (arg1 & 0x2u) {
guts += "clear ";
} else if (arg1 & 0x1u) {
guts += "wait ";
} else {
guts += "nowait ";
}
guts += std::to_string(arg2 & 7u);
if (arg2 & 0x10u) {
guts += " rel";
}
op_guts(guts);
}
break;
}
case 0b111: {
static std::array<std::string, 8> dests{"pins", "x", "y", "", "pindirs", "", "", ""};
std::string dest = dests[arg1];
if (dest.empty()) {
invalid = true;
} else {
op("set");
op_guts(dests[arg1] + ", " + std::to_string(arg2));
}
break;
}
}
if (invalid) {
op("word");
ss << std::hex;
op_guts(std::to_string(inst));
}
uint delay = ((uint) inst >> 8u) & 0x1f;
ss << std::left << std::setw(9);
if (sideset_bits_including_opt && (!sideset_opt || (delay & 0x10u))) {
ss << (".side("+ std::to_string((delay & (sideset_opt ? 0xfu : 0x1fu)) >> (5u - sideset_bits_including_opt))+")");
} else {
ss << "";
}
delay &= ((1u << (5 - sideset_bits_including_opt)) - 1u);
ss << std::left << std::setw(4) << (delay ? ("[" + std::to_string(delay) + "]") : "");
return ss.str();
}
};
static python_output::factory creator;