Initial Release
This commit is contained in:
60
tools/CMakeLists.txt
Normal file
60
tools/CMakeLists.txt
Normal 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
43
tools/FindELF2UF2.cmake
Normal 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
43
tools/FindPicotool.cmake
Normal 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
50
tools/FindPioasm.cmake
Normal 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
49
tools/check_doxygen_groups.py
Executable 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)
|
9
tools/elf2uf2/CMakeLists.txt
Normal file
9
tools/elf2uf2/CMakeLists.txt
Normal 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
60
tools/elf2uf2/elf.h
Normal 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
324
tools/elf2uf2/main.cpp
Normal 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
201
tools/extract_configs.py
Executable 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']})
|
42
tools/pioasm/CMakeLists.txt
Normal file
42
tools/pioasm/CMakeLists.txt
Normal 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()
|
||||
|
142
tools/pioasm/c_sdk_output.cpp
Normal file
142
tools/pioasm/c_sdk_output.cpp
Normal 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
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
302
tools/pioasm/gen/location.h
Normal 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
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
2894
tools/pioasm/gen/parser.hpp
Normal file
File diff suppressed because it is too large
Load Diff
41
tools/pioasm/hex_output.cpp
Normal file
41
tools/pioasm/hex_output.cpp
Normal 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
236
tools/pioasm/lexer.ll
Normal 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
100
tools/pioasm/main.cpp
Normal 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;
|
||||
}
|
100
tools/pioasm/output_format.h
Normal file
100
tools/pioasm/output_format.h
Normal 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
353
tools/pioasm/parser.yy
Normal 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';
|
||||
}
|
||||
}
|
||||
|
391
tools/pioasm/pio_assembler.cpp
Normal file
391
tools/pioasm/pio_assembler.cpp
Normal 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;
|
||||
}
|
103
tools/pioasm/pio_assembler.h
Normal file
103
tools/pioasm/pio_assembler.h
Normal 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
|
171
tools/pioasm/pio_disassembler.cpp
Normal file
171
tools/pioasm/pio_disassembler.cpp
Normal 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();
|
||||
}
|
||||
|
22
tools/pioasm/pio_disassembler.h
Normal file
22
tools/pioasm/pio_disassembler.h
Normal 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
396
tools/pioasm/pio_types.h
Normal 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
|
323
tools/pioasm/python_output.cpp
Normal file
323
tools/pioasm/python_output.cpp
Normal 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;
|
Reference in New Issue
Block a user