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

60
tools/CMakeLists.txt Normal file
View File

@ -0,0 +1,60 @@
function(_pico_init_pioasm)
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${PICO_SDK_PATH}/tools)
# todo CMAKE_CURRENT_FUNCTION_LIST_DIR ... what version?
find_package(Pioasm REQUIRED)
endfunction()
function(pico_generate_pio_header TARGET PIO)
_pico_init_pioasm()
cmake_parse_arguments(pico_generate_pio_header "" "OUTPUT_DIR" "" ${ARGN} )
if (pico_generate_pio_header_OUTPUT_DIR)
get_filename_component(HEADER_DIR ${pico_generate_pio_header_OUTPUT_DIR} ABSOLUTE)
else()
set(HEADER_DIR "${CMAKE_CURRENT_BINARY_DIR}")
endif()
get_filename_component(PIO_NAME ${PIO} NAME)
set(HEADER "${HEADER_DIR}/${PIO_NAME}.h")
#message("Will generate ${HEADER}")
get_filename_component(HEADER_GEN_TARGET ${PIO} NAME_WE)
set(HEADER_GEN_TARGET "${TARGET}_${HEADER_GEN_TARGET}_pio_h")
add_custom_target(${HEADER_GEN_TARGET} DEPENDS ${HEADER} Pioasm)
add_custom_command(OUTPUT ${HEADER}
DEPENDS ${PIO}
COMMAND Pioasm -o c-sdk ${PIO} ${HEADER}
)
add_dependencies(${TARGET} ${HEADER_GEN_TARGET})
get_target_property(target_type ${TARGET} TYPE)
if ("EXECUTABLE" STREQUAL "${target_type}")
target_include_directories(${TARGET} PRIVATE ${HEADER_DIR})
else()
target_include_directories(${TARGET} INTERFACE ${HEADER_DIR})
endif()
endfunction()
function(pico_add_uf2_output TARGET)
if (NOT ELF2UF2_FOUND)
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${PICO_SDK_PATH}/tools)
find_package(ELF2UF2)
endif()
if (ELF2UF2_FOUND)
add_custom_command(TARGET ${TARGET} POST_BUILD
COMMAND ELF2UF2 ${TARGET}${CMAKE_EXECUTABLE_SUFFIX} ${TARGET}.uf2)
endif()
endfunction()
if (NOT DEFINED PICO_BUILD_PICOFLASH)
if (DEFINED ENV{PICO_BUILD_PICOFLASH})
set(PICO_BUILD_PICOFLASH $ENV{PICO_BUILD_PICOFLASH})
else()
# for now
set(PICO_BUILD_PICOFLASH 1)
endif()
endif()
if (PICO_BUILD_PICOTOOL)
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${PICO_SDK_PATH}/tools)
find_package(Picotool REQUIRED)
endif()

43
tools/FindELF2UF2.cmake Normal file
View File

@ -0,0 +1,43 @@
# Finds (or builds) the ELF2UF2 executable
#
# This will define the following variables
#
# ELF2UF2_FOUND
#
# and the following imported targets
#
# ELF2UF2
#
if (NOT ELF2UF2_FOUND)
# todo we would like to use pckgconfig to look for it first
# see https://pabloariasal.github.io/2018/02/19/its-time-to-do-cmake-right/
include(ExternalProject)
set(ELF2UF2_SOURCE_DIR ${PICO_SDK_PATH}/tools/elf2uf2)
set(ELF2UF2_BINARY_DIR ${CMAKE_BINARY_DIR}/elf2uf2)
set(ELF2UF2_BUILD_TARGET ELF2UF2Build)
set(ELF2UF2_TARGET ELF2UF2)
if (NOT TARGET ${ELF2UF2_BUILD_TARGET})
message("ELF2UF2 will need to be built")
ExternalProject_Add(${ELF2UF2_BUILD_TARGET}
PREFIX elf2uf2 SOURCE_DIR ${ELF2UF2_SOURCE_DIR}
BINARY_DIR ${ELF2UF2_BINARY_DIR}
BUILD_ALWAYS 1 # force dependency checking
INSTALL_COMMAND ""
)
endif()
set(ELF2UF2_EXECUTABLE ${ELF2UF2_BINARY_DIR}/elf2uf2)
if(NOT TARGET ${ELF2UF2_TARGET})
add_executable(${ELF2UF2_TARGET} IMPORTED)
endif()
set_property(TARGET ${ELF2UF2_TARGET} PROPERTY IMPORTED_LOCATION
${ELF2UF2_EXECUTABLE})
add_dependencies(${ELF2UF2_TARGET} ${ELF2UF2_BUILD_TARGET})
set(ELF2UF2_FOUND 1)
endif()

43
tools/FindPicotool.cmake Normal file
View File

@ -0,0 +1,43 @@
# Finds (or builds) the PICOTOOL executable
#
# This will define the following variables
#
# PICOTOOL_FOUND
#
# and the following imported targets
#
# PICOTOOL
#
if (NOT PICOTOOL_FOUND)
# todo we would like to use pckgconfig to look for it first
# see https://pabloariasal.github.io/2018/02/19/its-time-to-do-cmake-right/
include(ExternalProject)
set(PICOTOOL_SOURCE_DIR ${PICO_SDK_PATH}/tools/picotool)
set(PICOTOOL_BINARY_DIR ${CMAKE_BINARY_DIR}/picotool)
set(PICOTOOL_BUILD_TARGET PicotoolBuild)
set(PICOTOOL_TARGET Picotool)
if (NOT TARGET ${PICOTOOL_BUILD_TARGET})
message("PICOTOOL will need to be built")
ExternalProject_Add(${PICOTOOL_BUILD_TARGET}
PREFIX picotool SOURCE_DIR ${PICOTOOL_SOURCE_DIR}
BINARY_DIR ${PICOTOOL_BINARY_DIR}
BUILD_ALWAYS 1 # force dependency checking
INSTALL_COMMAND ""
)
endif()
set(PICOTOOL_EXECUTABLE ${PICOTOOL_BINARY_DIR}/picotool)
if(NOT TARGET ${PICOTOOL_TARGET})
add_executable(${PICOTOOL_TARGET} IMPORTED)
endif()
set_property(TARGET ${PICOTOOL_TARGET} PROPERTY IMPORTED_LOCATION
${PICOTOOL_EXECUTABLE})
add_dependencies(${PICOTOOL_TARGET} ${PICOTOOL_BUILD_TARGET})
set(PICOTOOL_FOUND 1)
endif()

50
tools/FindPioasm.cmake Normal file
View File

@ -0,0 +1,50 @@
# Finds (or builds) the Pioasm executable
#
# This will define the following variables
#
# Pioasm_FOUND
#
# and the following imported targets
#
# Pioasm
#
if (NOT Pioasm_FOUND)
# todo we would like to use pckgconfig to look for it first
# see https://pabloariasal.github.io/2018/02/19/its-time-to-do-cmake-right/
include(ExternalProject)
set(PIOASM_SOURCE_DIR ${PICO_SDK_PATH}/tools/pioasm)
set(PIOASM_BINARY_DIR ${CMAKE_BINARY_DIR}/pioasm)
set(PioasmBuild_TARGET PioasmBuild)
set(Pioasm_TARGET Pioasm)
if (NOT TARGET ${PioasmBuild_TARGET})
message("PIOASM will need to be built")
# message("Adding external project ${PioasmBuild_Target} in ${CMAKE_CURRENT_LIST_DIR}}")
ExternalProject_Add(${PioasmBuild_TARGET}
PREFIX pioasm SOURCE_DIR ${PIOASM_SOURCE_DIR}
BINARY_DIR ${PIOASM_BINARY_DIR}
BUILD_ALWAYS 1 # force dependency checking
INSTALL_COMMAND ""
)
endif()
if (CMAKE_HOST_WIN32)
set(Pioasm_EXECUTABLE ${PIOASM_BINARY_DIR}/pioasm.exe)
else()
set(Pioasm_EXECUTABLE ${PIOASM_BINARY_DIR}/pioasm)
endif()
if(NOT TARGET ${Pioasm_TARGET})
# message("Adding executable ${Pioasm_Target} in ${CMAKE_CURRENT_LIST_DIR}")
add_executable(${Pioasm_TARGET} IMPORTED)
endif()
set_property(TARGET ${Pioasm_TARGET} PROPERTY IMPORTED_LOCATION
${Pioasm_EXECUTABLE})
# message("EXE is ${Pioasm_EXECUTABLE}")
add_dependencies(${Pioasm_TARGET} ${PioasmBuild_TARGET})
set(Pioasm_FOUND 1)
endif()

49
tools/check_doxygen_groups.py Executable file
View File

@ -0,0 +1,49 @@
#!/usr/bin/env python3
#
# Copyright (c) 2021 Raspberry Pi (Trading) Ltd.
#
# SPDX-License-Identifier: BSD-3-Clause
#
#
# Little script to check that every \ingroup has a matching \defgroup
#
# Usage:
#
# Run from the root of the tree to check
import subprocess
import re
import sys
import os
groups = {}
any_errors = False
res = subprocess.run(['git', 'grep', '\\defgroup'], check=True, stdout=subprocess.PIPE)
for line in res.stdout.decode('utf8').split('\n'):
m = re.match(r'^(\S+):.*\\defgroup\s+(\w+)', line)
if m:
filename = m.group(1)
group = m.group(2)
if os.path.basename(filename) in ('check_doxygen_groups.py', 'index.h'):
continue
if group in groups:
any_errors = True
print("{} uses \\defgroup {} but so does {}".format(groups[group], group, filename))
else:
groups[group] = filename
res = subprocess.run(['git', 'grep', '\\ingroup'], check=True, stdout=subprocess.PIPE)
for line in res.stdout.decode('utf8').split('\n'):
m = re.match(r'^(\S+):.*\\ingroup\s+(\w+)', line)
if m:
filename = m.group(1)
group = m.group(2)
if os.path.basename(filename) in ('check_doxygen_groups.py', 'index.h'):
continue
if group not in groups:
any_errors = True
print("{} uses \\ingroup {} which was never defined".format(filename, group))
sys.exit(any_errors)

View File

@ -0,0 +1,9 @@
cmake_minimum_required(VERSION 3.12)
project(elf2uf2)
set(CMAKE_CXX_STANDARD 14)
add_subdirectory(../../src/common/boot_uf2 boot_uf2_headers)
add_executable(elf2uf2 main.cpp)
target_link_libraries(elf2uf2 boot_uf2_headers)

60
tools/elf2uf2/elf.h Normal file
View File

@ -0,0 +1,60 @@
/*
* Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#ifndef _ELF_H
#define _ELF_H
#include <stdint.h>
#define ELF_MAGIC 0x464c457fu
#define EM_ARM 0x28u
#define EF_ARM_ABI_FLOAT_HARD 0x00000400u
#define PT_LOAD 0x00000001u
#pragma pack(push, 1)
struct elf_header {
uint32_t magic;
uint8_t arch_class;
uint8_t endianness;
uint8_t version;
uint8_t abi;
uint8_t abi_version;
uint8_t _pad[7];
uint16_t type;
uint16_t machine;
uint32_t version2;
};
struct elf32_header {
struct elf_header common;
uint32_t entry;
uint32_t ph_offset;
uint32_t sh_offset;
uint32_t flags;
uint16_t eh_size;
uint16_t ph_entry_size;
uint16_t ph_num;
uint16_t sh_entry_size;
uint16_t sh_num;
uint16_t sh_str_index;
};
struct elf32_ph_entry {
uint32_t type;
uint32_t offset;
uint32_t vaddr;
uint32_t paddr;
uint32_t filez;
uint32_t memsz;
uint32_t flags;
uint32_t align;
};
#pragma pack(pop)
#endif

324
tools/elf2uf2/main.cpp Normal file
View File

@ -0,0 +1,324 @@
/*
* Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#include <cstdio>
#include <map>
#include <vector>
#include <cstring>
#include <cstdarg>
#include <algorithm>
#include "boot/uf2.h"
#include "elf.h"
typedef unsigned int uint;
#define ERROR_ARGS -1
#define ERROR_FORMAT -2
#define ERROR_INCOMPATIBLE -3
#define ERROR_READ_FAILED -4
#define ERROR_WRITE_FAILED -5
static char error_msg[512];
static bool verbose;
static int fail(int code, const char *format, ...) {
va_list args;
va_start(args, format);
vsnprintf(error_msg, sizeof(error_msg), format, args);
va_end(args);
return code;
}
static int fail_read_error() {
return fail(ERROR_READ_FAILED, "Failed to read input file");
}
static int fail_write_error() {
return fail(ERROR_WRITE_FAILED, "Failed to write output file");
}
// we require 256 (as this is the page size supported by the device)
#define LOG2_PAGE_SIZE 8u
#define PAGE_SIZE (1u << LOG2_PAGE_SIZE)
struct address_range {
enum type {
CONTENTS, // may have contents
NO_CONTENTS, // must be uninitialized
IGNORE // will be ignored
};
address_range(uint32_t from, uint32_t to, type type) : from(from), to(to), type(type) {}
address_range() : address_range(0, 0, IGNORE) {}
type type;
uint32_t to;
uint32_t from;
};
typedef std::vector<address_range> address_ranges;
#define MAIN_RAM_START 0x20000000u
#define MAIN_RAM_END 0x20042000u
#define FLASH_START 0x10000000u
#define FLASH_END 0x15000000u
const address_ranges rp2040_address_ranges_flash {
address_range(FLASH_START, FLASH_END, address_range::type::CONTENTS),
address_range(MAIN_RAM_START, MAIN_RAM_END, address_range::type::NO_CONTENTS)
};
const address_ranges rp2040_address_ranges_ram {
address_range(MAIN_RAM_START, MAIN_RAM_END, address_range::type::CONTENTS),
address_range(0x00000000u, 0x00002000u, address_range::type::IGNORE) // for now we ignore the bootrom if present
};
struct page_fragment {
page_fragment(uint32_t file_offset, uint32_t page_offset, uint32_t bytes) : file_offset(file_offset), page_offset(page_offset), bytes(bytes) {}
uint32_t file_offset;
uint32_t page_offset;
uint32_t bytes;
};
static int usage() {
fprintf(stderr, "Usage: elf2uf2 (-v) <input ELF file> <output UF2 file>\n");
return ERROR_ARGS;
}
static int read_and_check_elf32_header(FILE *in, elf32_header& eh_out) {
if (1 != fread(&eh_out, sizeof(eh_out), 1, in)) {
return fail(ERROR_READ_FAILED, "Unable to read ELF header");
}
if (eh_out.common.magic != ELF_MAGIC) {
return fail(ERROR_FORMAT, "Not an ELF file");
}
if (eh_out.common.version != 1 || eh_out.common.version2 != 1) {
return fail(ERROR_FORMAT, "Unrecognized ELF version");
}
if (eh_out.common.arch_class != 1 || eh_out.common.endianness != 1) {
return fail(ERROR_INCOMPATIBLE, "Require 32 bit little-endian ELF");
}
if (eh_out.eh_size != sizeof(struct elf32_header)) {
return fail(ERROR_FORMAT, "Invalid ELF32 format");
}
if (eh_out.common.machine != EM_ARM) {
return fail(ERROR_FORMAT, "Not an ARM executable");
}
if (eh_out.common.abi != 0) {
return fail(ERROR_INCOMPATIBLE, "Unrecognized ABI");
}
if (eh_out.flags & EF_ARM_ABI_FLOAT_HARD) {
return fail(ERROR_INCOMPATIBLE, "HARD-FLOAT not supported");
}
return 0;
}
int check_address_range(const address_ranges& valid_ranges, uint32_t addr, uint32_t vaddr, uint32_t size, bool uninitialized, address_range &ar) {
for(const auto& range : valid_ranges) {
if (range.from <= addr && range.to >= addr + size) {
if (range.type == address_range::type::NO_CONTENTS && !uninitialized) {
return fail(ERROR_INCOMPATIBLE, "ELF contains memory contents for uninitialized memory");
}
ar = range;
if (verbose) {
printf("%s segment %08x->%08x (%08x->%08x)\n", uninitialized ? "Uninitialized" : "Mapped", addr,
addr + size, vaddr, vaddr+size);
}
return 0;
}
}
return fail(ERROR_INCOMPATIBLE, "Memory segment %08x->%08x is outside of valid address range for device", addr, addr+size);
}
int read_and_check_elf32_ph_entries(FILE *in, const elf32_header &eh, const address_ranges& valid_ranges, std::map<uint32_t, std::vector<page_fragment>>& pages) {
if (eh.ph_entry_size != sizeof(elf32_ph_entry)) {
return fail(ERROR_FORMAT, "Invalid ELF32 program header");
}
if (eh.ph_num) {
std::vector<elf32_ph_entry> entries(eh.ph_num);
if (eh.ph_num != fread(&entries[0], sizeof(struct elf32_ph_entry), eh.ph_num, in)) {
return fail_read_error();
}
for(uint i=0;i<eh.ph_num;i++) {
elf32_ph_entry& entry = entries[i];
if (entry.type == PT_LOAD && entry.memsz) {
address_range ar;
int rc;
uint mapped_size = std::min(entry.filez, entry.memsz);
if (mapped_size) {
rc = check_address_range(valid_ranges, entry.paddr, entry.vaddr, mapped_size, false, ar);
if (rc) return rc;
// we don't download uninitialized, generally it is BSS and should be zero-ed by crt0.S, or it may be COPY areas which are undefined
if (ar.type != address_range::type::CONTENTS) {
if (verbose) printf(" ignored\n");
continue;
}
uint addr = entry.paddr;
uint remaining = mapped_size;
uint file_offset = entry.offset;
while (remaining) {
uint off = addr & (PAGE_SIZE - 1);
uint len = std::min(remaining, PAGE_SIZE - off);
auto &fragments = pages[addr - off]; // list of fragments
// note if filesz is zero, we want zero init which is handled because the
// statement above creates an empty page fragment list
// check overlap with any existing fragments
for (const auto &fragment : fragments) {
if ((off < fragment.page_offset + fragment.bytes) !=
((off + len) <= fragment.page_offset)) {
fail(ERROR_FORMAT, "In memory segments overlap");
}
}
fragments.push_back(
page_fragment{file_offset,off,len});
addr += len;
file_offset += len;
remaining -= len;
}
}
if (entry.memsz > entry.filez) {
// we have some uninitialized data too
rc = check_address_range(valid_ranges, entry.paddr + entry.filez, entry.vaddr + entry.filez, entry.memsz - entry.filez, true,
ar);
if (rc) return rc;
}
}
}
}
return 0;
}
int realize_page(FILE *in, const std::vector<page_fragment> &fragments, uint8_t *buf, uint buf_len) {
assert(buf_len >= PAGE_SIZE);
for(auto& frag : fragments) {
assert(frag.page_offset >= 0 && frag.page_offset < PAGE_SIZE && frag.page_offset + frag.bytes <= PAGE_SIZE);
if (fseek(in, frag.file_offset, SEEK_SET)) {
return fail_read_error();
}
if (1 != fread(buf + frag.page_offset, frag.bytes, 1, in)) {
return fail_read_error();
}
}
return 0;
}
static bool is_address_valid(const address_ranges& valid_ranges, uint32_t addr) {
for(const auto& range : valid_ranges) {
if (range.from <= addr && range.to > addr) {
return true;
}
}
return false;
}
static bool is_address_mapped(const std::map<uint32_t, std::vector<page_fragment>>& pages, uint32_t addr) {
uint32_t page = addr & ~(PAGE_SIZE - 1);
if (!pages.count(page)) return false;
// todo check actual address within page
return true;
}
int elf2uf2(FILE *in, FILE *out) {
elf32_header eh;
std::map<uint32_t, std::vector<page_fragment>> pages;
int rc = read_and_check_elf32_header(in, eh);
bool ram_style = false;
address_ranges valid_ranges = {};
if (!rc) {
ram_style = 0x2 == eh.entry >> 28u;
if (verbose) {
if (ram_style) {
printf("Detected RAM binary\n");
} else {
printf("Detected FLASH binary\n");
}
}
valid_ranges = ram_style ? rp2040_address_ranges_ram : rp2040_address_ranges_flash;
rc = read_and_check_elf32_ph_entries(in, eh, valid_ranges, pages);
}
if (rc) return rc;
if (pages.empty()) {
return fail(ERROR_INCOMPATIBLE, "The input file has no memory pages");
}
uint page_num = 0;
if (ram_style) {
uint32_t expected_ep = pages.begin()->first | 0x1;
if (eh.entry != expected_ep) {
return fail(ERROR_INCOMPATIBLE, "A RAM binary should have an entry point at the beginning: %08x (not %08x)\n", expected_ep, eh.entry);
}
static_assert(0 == (MAIN_RAM_START & (PAGE_SIZE - 1)), "");
// currently don't require this as entry point is now at the start, we don't know where reset vector is
#if 0
uint8_t buf[PAGE_SIZE];
rc = realize_page(in, pages[MAIN_RAM_START], buf, sizeof(buf));
if (rc) return rc;
uint32_t sp = ((uint32_t *)buf)[0];
uint32_t ip = ((uint32_t *)buf)[1];
if (!is_address_mapped(pages, ip)) {
return fail(ERROR_INCOMPATIBLE, "Vector table at %08x is invalid: reset vector %08x is not in mapped memory",
MAIN_RAM_START, ip);
}
if (!is_address_valid(valid_ranges, sp - 4)) {
return fail(ERROR_INCOMPATIBLE, "Vector table at %08x is invalid: stack pointer %08x is not in RAM",
MAIN_RAM_START, sp);
}
#endif
}
uf2_block block;
block.magic_start0 = UF2_MAGIC_START0;
block.magic_start1 = UF2_MAGIC_START1;
block.flags = UF2_FLAG_FAMILY_ID_PRESENT;
block.payload_size = PAGE_SIZE;
block.num_blocks = (uint32_t)pages.size();
block.file_size = RP2040_FAMILY_ID;
block.magic_end = UF2_MAGIC_END;
for(auto& page_entry : pages) {
block.target_addr = page_entry.first;
block.block_no = page_num++;
if (verbose) {
printf("Page %d / %d %08x\n", block.block_no, block.num_blocks, block.target_addr);
}
memset(block.data, 0, sizeof(block.data));
rc = realize_page(in, page_entry.second, block.data, sizeof(block.data));
if (rc) return rc;
if (1 != fwrite(&block, sizeof(uf2_block), 1, out)) {
return fail_write_error();
}
}
return 0;
}
int main(int argc, char **argv) {
int arg = 1;
if (arg < argc && !strcmp(argv[arg], "-v")) {
verbose = true;
arg++;
}
if (argc < arg + 2) {
return usage();
}
const char *in_filename = argv[arg++];
FILE *in = fopen(in_filename, "rb");
if (!in) {
fprintf(stderr, "Can't open input file '%s'\n", in_filename);
return ERROR_ARGS;
}
const char *out_filename = argv[arg++];
FILE *out = fopen(out_filename, "wb");
if (!out) {
fprintf(stderr, "Can't open output file '%s'\n", out_filename);
return ERROR_ARGS;
}
int rc = elf2uf2(in, out);
fclose(in);
fclose(out);
if (rc) {
remove(out_filename);
if (error_msg[0]) {
fprintf(stderr, "ERROR: %s\n", error_msg);
}
}
return rc;
}

201
tools/extract_configs.py Executable file
View File

@ -0,0 +1,201 @@
#!/usr/bin/env python3
#
# Copyright (c) 2021 Raspberry Pi (Trading) Ltd.
#
# SPDX-License-Identifier: BSD-3-Clause
#
#
# Script to scan the Raspberry Pi PICO SDK tree searching for configuration items
# Outputs a tab separated file of the configuration item:
# name location description type advanced default depends enumvalues group max min
#
# Usage:
#
# ./extract_configs.py <root of source tree> [output file]
#
# If not specified, output file will be `pico_configs.tsv`
import os
import sys
import re
import csv
import logging
logger = logging.getLogger(__name__)
logging.basicConfig(level=logging.INFO)
scandir = sys.argv[1]
outfile = sys.argv[2] if len(sys.argv) > 2 else 'pico_configs.tsv'
CONFIG_RE = re.compile(r'//\s+PICO_CONFIG:\s+(\w+),\s+([^,]+)(?:,\s+(.*))?$')
DEFINE_RE = re.compile(r'#define\s+(\w+)\s+(\w+)$')
all_configs = {}
all_attrs = set()
all_descriptions = {}
all_defines = {}
def ValidateAttrs(config_attrs):
_type = config_attrs.get('type', 'int')
# Validate attrs
if _type == 'int':
assert 'enumvalues' not in config_attrs
_min = _max = _default = None
if config_attrs.get('min', None) is not None:
value = config_attrs['min']
m = re.match(r'^(\d+)e(\d+)$', value.lower())
if m:
_min = int(m.group(1)) * 10**int(m.group(2))
else:
_min = int(value, 0)
if config_attrs.get('max', None) is not None:
value = config_attrs['max']
m = re.match(r'^(\d+)e(\d+)$', value.lower())
if m:
_max = int(m.group(1)) * 10**int(m.group(2))
else:
_max = int(value, 0)
if config_attrs.get('default', None) is not None:
if '/' not in config_attrs['default']:
try:
value = config_attrs['default']
m = re.match(r'^(\d+)e(\d+)$', value.lower())
if m:
_default = int(m.group(1)) * 10**int(m.group(2))
else:
_default = int(value, 0)
except ValueError:
pass
if _min is not None and _max is not None:
if _min > _max:
raise Exception('{} at {}:{} has min {} > max {}'.format(config_name, file_path, linenum, config_attrs['min'], config_attrs['max']))
if _min is not None and _default is not None:
if _min > _default:
raise Exception('{} at {}:{} has min {} > default {}'.format(config_name, file_path, linenum, config_attrs['min'], config_attrs['default']))
if _default is not None and _max is not None:
if _default > _max:
raise Exception('{} at {}:{} has default {} > max {}'.format(config_name, file_path, linenum, config_attrs['default'], config_attrs['max']))
elif _type == 'bool':
assert 'min' not in config_attrs
assert 'max' not in config_attrs
assert 'enumvalues' not in config_attrs
_default = config_attrs.get('default', None)
if _default is not None:
if '/' not in _default:
if (_default.lower() != '0') and (config_attrs['default'].lower() != '1') and ( _default not in all_configs):
logger.info('{} at {}:{} has non-integer default value "{}"'.format(config_name, file_path, linenum, config_attrs['default']))
elif _type == 'enum':
assert 'min' not in config_attrs
assert 'max' not in config_attrs
assert 'enumvalues' in config_attrs
_enumvalues = tuple(config_attrs['enumvalues'].split('|'))
_default = None
if config_attrs.get('default', None) is not None:
_default = config_attrs['default']
if _default is not None:
if _default not in _enumvalues:
raise Exception('{} at {}:{} has default value {} which isn\'t in list of enumvalues {}'.format(config_name, file_path, linenum, config_attrs['default'], config_attrs['enumvalues']))
else:
raise Exception("Found unknown PICO_CONFIG type {} at {}:{}".format(_type, file_path, linenum))
# Scan all .c and .h files in the specific path, recursively.
for dirpath, dirnames, filenames in os.walk(scandir):
for filename in filenames:
file_ext = os.path.splitext(filename)[1]
if file_ext in ('.c', '.h'):
file_path = os.path.join(dirpath, filename)
with open(file_path, encoding="ISO-8859-1") as fh:
linenum = 0
for line in fh.readlines():
linenum += 1
line = line.strip()
m = CONFIG_RE.match(line)
if m:
config_name = m.group(1)
config_description = m.group(2)
_attrs = m.group(3)
# allow commas to appear inside brackets by converting them to and from NULL chars
_attrs = re.sub(r'(\(.+\))', lambda m: m.group(1).replace(',', '\0'), _attrs)
if '=' in config_description:
raise Exception("For {} at {}:{} the description was set to '{}' - has the description field been omitted?".format(config_name, file_path, linenum, config_description))
if config_description in all_descriptions:
raise Exception("Found description {} at {}:{} but it was already used at {}:{}".format(config_description, file_path, linenum, os.path.join(scandir, all_descriptions[config_description]['filename']), all_descriptions[config_description]['line_number']))
else:
all_descriptions[config_description] = {'config_name': config_name, 'filename': os.path.relpath(file_path, scandir), 'line_number': linenum}
config_attrs = {}
prev = None
# Handle case where attr value contains a comma
for item in _attrs.split(','):
if "=" not in item:
assert(prev)
item = prev + "," + item
try:
k, v = (i.strip() for i in item.split('='))
except ValueError:
raise Exception('{} at {}:{} has malformed value {}'.format(config_name, file_path, linenum, item))
config_attrs[k] = v.replace('\0', ',') if v != 'undefined' else None
all_attrs.add(k)
prev = item
#print(file_path, config_name, config_attrs)
if 'group' not in config_attrs:
raise Exception('{} at {}:{} has no group attribute'.format(config_name, file_path, linenum))
#print(file_path, config_name, config_attrs)
if config_name in all_configs:
raise Exception("Found {} at {}:{} but it was already declared at {}:{}".format(config_name, file_path, linenum, os.path.join(scandir, all_configs[config_name]['filename']), all_configs[config_name]['line_number']))
else:
all_configs[config_name] = {'attrs': config_attrs, 'filename': os.path.relpath(file_path, scandir), 'line_number': linenum, 'description': config_description}
else:
m = DEFINE_RE.match(line)
if m:
name = m.group(1)
value = m.group(2)
# discard any 'u' qualifier
m = re.match(r'^((0x)?\d+)u$', value.lower())
if m:
value = m.group(1)
if name not in all_defines:
all_defines[name] = dict()
if value not in all_defines[name]:
all_defines[name][value] = set()
all_defines[name][value] = (file_path, linenum)
# Check for defines with missing PICO_CONFIG entries
for d in all_defines:
if d not in all_configs and d.startswith("PICO_"):
logger.warning("Potential unmarked PICO define {}".format(d))
for config_name in all_configs:
ValidateAttrs(all_configs[config_name]['attrs'])
if 'default' in all_configs[config_name]['attrs'] and config_name in all_defines:
if all_configs[config_name]['attrs']['default'] not in all_defines[config_name] and (all_configs[config_name]['attrs'].get('type', 'int') != 'bool'):
if config_name in ['USB_DPRAM_MAX'] or '/' in all_configs[config_name]['attrs']['default']:
continue
raise Exception('Found {} at {}:{} with a default of {}, but #define says {}'.format(config_name, all_configs[config_name]['filename'], all_configs[config_name]['line_number'], all_configs[config_name]['attrs']['default'], all_defines[config_name]))
with open(outfile, 'w', newline='') as csvfile:
fieldnames = ('name', 'location', 'description', 'type') + tuple(sorted(all_attrs - set(['type'])))
writer = csv.DictWriter(csvfile, fieldnames=fieldnames, extrasaction='ignore', dialect='excel-tab')
writer.writeheader()
for config_name in sorted(all_configs):
writer.writerow({'name': config_name, 'location': '{}:{}'.format(all_configs[config_name]['filename'], all_configs[config_name]['line_number']), 'description': all_configs[config_name]['description'], **all_configs[config_name]['attrs']})

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;