diff --git a/ports/sh/.gitignore b/ports/sh/.gitignore new file mode 100644 index 000000000..b6d29c07b --- /dev/null +++ b/ports/sh/.gitignore @@ -0,0 +1,2 @@ +/icon.xcf +/PythonExtra.g3a diff --git a/ports/sh/Makefile b/ports/sh/Makefile new file mode 100644 index 000000000..e94db4877 --- /dev/null +++ b/ports/sh/Makefile @@ -0,0 +1,44 @@ +include ../../py/mkenv.mk + +# Use the sh-elf toolchain +CROSS_COMPILE := sh-elf- + +include $(TOP)/py/py.mk +include $(TOP)/extmod/extmod.mk + +CFLAGS += -m4-nofpu -mb -fstrict-volatile-bitfields -I. -I$(BUILD) -I$(TOP) -DFXCG50 -O2 -Wall -Wextra -Wno-unused-parameter +LIBS += -nostdlib -Wl,--no-warn-rwx-segments -T fxcg50.ld -lm -lgint-cg -lc -lgint-cg -lgcc -Wl,-Map=build/map + +# Source files +SRC_C = \ + main.c \ + console.c \ + keymap.c \ + mphalport.c \ + shared/readline/readline.c \ + shared/runtime/gchelper_generic.c \ + shared/runtime/pyexec.c \ + shared/runtime/stdout_helpers.c \ + +SRC_QSTR += \ + shared/readline/readline.c \ + +OBJ = $(PY_CORE_O) $(addprefix $(BUILD)/, $(SRC_C:.c=.o)) + +all: $(BUILD)/firmware.elf PythonExtra.g3a + +PythonExtra.g3a: $(BUILD)/firmware.bin icon-uns.png icon-sel.png + fxgxa --g3a -n PythonExtra --icon-uns=icon-uns.png --icon-sel=icon-sel.png $< -o $@ + +$(BUILD)/firmware.bin: $(BUILD)/firmware.elf + $(Q)$(CC:gcc=objcopy) -O binary -R .bss -R .gint_bss $< $@ + +$(BUILD)/firmware.elf: $(OBJ) + $(ECHO) "LINK $@" + $(Q)$(CC) $(LDFLAGS) -o $@ $^ $(LIBS) + $(Q)$(SIZE) $@ + +send: all + fxlink -sw PythonExtra.g3a + +include $(TOP)/py/mkrules.mk diff --git a/ports/sh/console.c b/ports/sh/console.c new file mode 100644 index 000000000..72c60f778 --- /dev/null +++ b/ports/sh/console.c @@ -0,0 +1,110 @@ +#include +#include +#include +#include +#include +#include "console.h" + +console_t *console_create(int max_lines) +{ + console_t *cons = malloc(sizeof *cons); + cons->max_lines = max_lines; + cons->lines = calloc(max_lines, sizeof *cons->lines); + for(int i = 0; i < max_lines; i++) + cons->lines[i] = NULL; + console_new_line(cons); + return cons; +} + +void console_new_line(console_t *cons) +{ + int n = cons->max_lines; + free(cons->lines[0]); + + for(int i = 0; i < n - 1; i++) + cons->lines[i] = cons->lines[i+1]; + + cons->lines[n - 1] = strdup(""); +} + +static void console_append_to_last_line(console_t *cons, void const *buf, + int size) +{ + int n = cons->max_lines; + char *l = cons->lines[n - 1]; + int prev_size = l ? strlen(l) : 0; + int new_size = prev_size + size + 1; + + l = realloc(l, new_size); + if(!l) + return; + + memcpy(l + prev_size, buf, size); + l[new_size - 1] = 0; + cons->lines[n - 1] = l; +} + +void console_append_str(console_t *cons, char const *str) +{ + console_append_buf(cons, str, strlen(str)); +} + +void console_append_buf(console_t *cons, void const *buf, size_t size_) +{ + int offset = 0, size = size_; + + while(offset < size) { + /* Find the first '\n' (or end of buffer) in the segment */ + void const *endline = memchr(buf + offset, '\n', size - offset); + if(endline == NULL) + endline = buf + size; + + int line_size = endline - (buf + offset); + console_append_to_last_line(cons, buf + offset, line_size); + + offset += line_size; + + /* Found a '\n' */ + if(offset < size) { + console_new_line(cons); + offset++; + } + } +} + +void console_render(int x, int y, console_t *cons, int w, int h) +{ + int dy = 13; + + for(int i = 0; i < cons->max_lines; i++) { + /* Skip initial unallocated lines */ + if(cons->lines[i] == NULL) + continue; + + char const *p = cons->lines[i]; + char const *endline = p + strlen(p); + + while(p < endline) { + if(p[0] == '\n') { + y += dy; + p++; + continue; + } + + char const *endscreen = drsize(p, NULL, w, NULL); + char const *endline = strchrnul(p, '\n'); + int len = min(endscreen - p, endline - p); + + dtext_opt(x, y, C_BLACK, C_NONE, DTEXT_LEFT, DTEXT_TOP, p, len); + y += dy; + p += len + (p[len] == '\n'); + } + } +} + +void console_destroy(console_t *cons) +{ + for(int i = 0; i < cons->max_lines; i++) + free(cons->lines[i]); + free(cons); +} diff --git a/ports/sh/console.h b/ports/sh/console.h new file mode 100644 index 000000000..721df1736 --- /dev/null +++ b/ports/sh/console.h @@ -0,0 +1,28 @@ +#ifndef __PYTHONEXTRA_CONSOLE_H +#define __PYTHONEXTRA_CONSOLE_H + +//=== Console data storage ===// + +typedef struct +{ + /* An array lines[] of size [max_lines], filled from the end (always has a + list of NULL followed by line pointers. */ + int max_lines; + char **lines; + +} console_t; + +console_t *console_create(int max_lines); + +void console_new_line(console_t *cons); + +void console_append_str(console_t *cons, char const *str); + +void console_append_buf(console_t *cons, void const *buf, size_t size); + +/* TODO: Expand this function */ +void console_render(int x, int y, console_t *cons, int w, int h); + +void console_destroy(console_t *cons); + +#endif /* __PYTHONEXTRA_CONSOLE_H */ diff --git a/ports/sh/icon-sel.png b/ports/sh/icon-sel.png new file mode 100644 index 000000000..3e7ae830c Binary files /dev/null and b/ports/sh/icon-sel.png differ diff --git a/ports/sh/icon-uns.png b/ports/sh/icon-uns.png new file mode 100644 index 000000000..0a57f5801 Binary files /dev/null and b/ports/sh/icon-uns.png differ diff --git a/ports/sh/keymap.c b/ports/sh/keymap.c new file mode 100644 index 000000000..7d645d967 --- /dev/null +++ b/ports/sh/keymap.c @@ -0,0 +1,57 @@ +#include "keymap.h" +#include + +static int key_id(int keycode) +{ + uint col = (keycode & 0x0f) - 1; + uint row = 9 - ((keycode & 0xf0) >> 4); + + if(col > 5 || row > 8) return -1; + return 6 * row + col; +} + +static uint8_t map_flat[30] = { + 0, 0, '(', ')', ',', '=', + '7', '8', '9', 0, 0, 0, + '4', '5', '6', '*', '/', 0, + '1', '2', '3', '+', '-', 0, + '0', '.', 'e', '-', 0, 0, +}; +static uint8_t map_alpha[36] = { + 'a', 'b', 'c', 'd', 'e', 'f', + 'g', 'h', 'i', 'j', 'k', 'l', + 'm', 'n', 'o', 0, 0, 0, + 'p', 'q', 'r', 's', 't', 0, + 'u', 'v', 'w', 'x', 'y', 0, + 'z', ' ', '"', 0, 0, 0, +}; + +uint32_t keymap_translate(int key, bool shift, bool alpha) +{ + int id = key_id(key); + if(id < 0) return 0; + + if(!shift && !alpha) { + /* The first 4 rows have no useful characters */ + return (id < 24) ? 0 : map_flat[id - 24]; + } + if(shift && !alpha) { + if(key == KEY_MUL) return '{'; + if(key == KEY_DIV) return '}'; + if(key == KEY_ADD) return '['; + if(key == KEY_SUB) return ']'; + if(key == KEY_DOT) return '='; + if(key == KEY_0) return ':'; + if(key == KEY_EXP) return 0x3c0; // 'π' + } + if(!shift && alpha) { + /* The first 3 rows have no useful characters */ + return (id < 18) ? 0 : map_alpha[id - 18]; + } + if(shift && alpha) { + int c = keymap_translate(key, false, true); + return (c >= 'a' && c <= 'z') ? (c & ~0x20) : c; + } + + return 0; +} diff --git a/ports/sh/keymap.h b/ports/sh/keymap.h new file mode 100644 index 000000000..f88740ad6 --- /dev/null +++ b/ports/sh/keymap.h @@ -0,0 +1,9 @@ +#ifndef __PYTHONEXTRA_KEYMAP_H +#define __PYTHONEXTRA_KEYMAP_H + +#include +#include + +uint32_t keymap_translate(int key, bool shift, bool alpha); + +#endif /* __PYTHONEXTRA_KEYMAP_H */ diff --git a/ports/sh/main.c b/ports/sh/main.c new file mode 100644 index 000000000..78e3caf34 --- /dev/null +++ b/ports/sh/main.c @@ -0,0 +1,116 @@ +#include "py/compile.h" +#include "py/gc.h" +#include "py/mperrno.h" +#include "py/stackctrl.h" +#include "py/builtin.h" +#include "shared/runtime/gchelper.h" +#include "shared/runtime/pyexec.h" + +#include +#include +#include +#include +#include +#include +#include "console.h" + +// Allocate memory for the MicroPython GC heap. +static char heap[32768]; + +int parse_compile_execute(const void *source, mp_parse_input_kind_t input_kind, mp_uint_t exec_flags); + +//=== Console-based standard streams ===// + +ssize_t stdouterr_write(void *data, void const *buf, size_t size) +{ + console_t *cons = data; + console_append_buf(cons, buf, size); + return size; +} + +fs_descriptor_type_t stdouterr_type = { + .read = NULL, + .write = stdouterr_write, + .lseek = NULL, + .close = NULL, +}; + +//=== Main function ===// + +static console_t *cons = NULL; + +void pe_draw(void) +{ + dclear(C_WHITE); + dtext(1, 1, C_BLACK, "PythonExtra, very much WIP :)"); + dline(1, 16, DWIDTH-1, 16, C_BLACK); + console_render(1, 18, cons, DWIDTH-2, -1); + dupdate(); +} + +void pe_exithandler(void) +{ + pe_draw(); + drect(DWIDTH-8, 0, DWIDTH-1, 7, C_RED); + dupdate(); + getkey(); +} + +int main(int argc, char **argv) +{ + /* Set up standard streams */ + cons = console_create(10); + close(STDOUT_FILENO); + close(STDERR_FILENO); + open_generic(&stdouterr_type, cons, STDOUT_FILENO); + open_generic(&stdouterr_type, cons, STDERR_FILENO); + + atexit(pe_exithandler); + + /* Initialize MicroPython */ + mp_stack_ctrl_init(); + gc_init(heap, heap + sizeof(heap)); + mp_init(); + + // Start a normal REPL; will exit when ctrl-D is entered on a blank line. + pyexec_friendly_repl(); + + console_destroy(cons); + + // Deinitialise the runtime. + gc_sweep_all(); + mp_deinit(); + return 0; +} + +// Handle uncaught exceptions (should never be reached in a correct C implementation). +void nlr_jump_fail(void *val) { + dclear(C_BLACK); + dtext(2, 2, C_WHITE, "nlr_jump_fail!"); + dprint(2, 2, C_WHITE, "val = %p", val); + dupdate(); + while(1) + getkey(); +} + +// Do a garbage collection cycle. +void gc_collect(void) { + gc_collect_start(); + gc_helper_collect_regs_and_stack(); + gc_collect_end(); +} + +// There is no filesystem so stat'ing returns nothing. +mp_import_stat_t mp_import_stat(const char *path) { + return MP_IMPORT_STAT_NO_EXIST; +} + +mp_obj_t mp_builtin_open(size_t n_args, const mp_obj_t *args, mp_map_t *kwargs) { + return mp_const_none; +} +MP_DEFINE_CONST_FUN_OBJ_KW(mp_builtin_open_obj, 1, mp_builtin_open); + +// There is no filesystem so opening a file raises an exception. +mp_lexer_t *mp_lexer_new_from_file(const char *filename) { + mp_raise_OSError(MP_ENOENT); +} diff --git a/ports/sh/mpconfigport.h b/ports/sh/mpconfigport.h new file mode 100644 index 000000000..708733832 --- /dev/null +++ b/ports/sh/mpconfigport.h @@ -0,0 +1,81 @@ +#include + +#define MICROPY_CONFIG_ROM_LEVEL (MICROPY_CONFIG_ROM_LEVEL_CORE_FEATURES) +// Other options: BASIC_FEATURES, EXTRA_FEATURES, FULL_FEATURES, EVERYTHING + +#define MICROPY_ENABLE_COMPILER (1) +#define MP_ENDIANNESS_BIG (1) + +#define MICROPY_QSTR_BYTES_IN_HASH (1) +// #define MICROPY_QSTR_EXTRA_POOL mp_qstr_frozen_const_pool +#define MICROPY_ALLOC_PATH_MAX (256) +#define MICROPY_ALLOC_PARSE_CHUNK_INIT (16) +#define MICROPY_EMIT_X64 (0) +#define MICROPY_EMIT_THUMB (0) +#define MICROPY_EMIT_INLINE_THUMB (0) +#define MICROPY_COMP_MODULE_CONST (0) +#define MICROPY_COMP_CONST (0) +#define MICROPY_COMP_DOUBLE_TUPLE_ASSIGN (0) +#define MICROPY_COMP_TRIPLE_TUPLE_ASSIGN (0) +#define MICROPY_MEM_STATS (0) +#define MICROPY_DEBUG_PRINTERS (0) +#define MICROPY_ENABLE_GC (1) +#define MICROPY_GC_ALLOC_THRESHOLD (0) +#define MICROPY_REPL_EVENT_DRIVEN (0) +#define MICROPY_HELPER_REPL (1) +#define MICROPY_HELPER_LEXER_UNIX (0) +#define MICROPY_ENABLE_SOURCE_LINE (1) +#define MICROPY_ENABLE_DOC_STRING (0) +#define MICROPY_ERROR_REPORTING (MICROPY_ERROR_REPORTING_DETAILED) +#define MICROPY_BUILTIN_METHOD_CHECK_SELF_ARG (1) +#define MICROPY_PY_ASYNC_AWAIT (0) +#define MICROPY_PY_BUILTINS_BYTEARRAY (1) +#define MICROPY_PY_BUILTINS_MEMORYVIEW (1) +#define MICROPY_PY_BUILTINS_ENUMERATE (1) +#define MICROPY_PY_BUILTINS_FILTER (1) +// #define MICROPY_PY_BUILTINS_FROZENSET (1) +#define MICROPY_PY_BUILTINS_REVERSED (1) +#define MICROPY_PY_BUILTINS_SET (1) +#define MICROPY_PY_BUILTINS_SLICE (1) +#define MICROPY_PY_BUILTINS_PROPERTY (1) +#define MICROPY_PY_BUILTINS_MIN_MAX (1) +#define MICROPY_PY___FILE__ (0) +#define MICROPY_PY_GC (1) +#define MICROPY_PY_ARRAY (1) +#define MICROPY_PY_ATTRTUPLE (1) +#define MICROPY_PY_COLLECTIONS (1) +#define MICROPY_PY_MATH (1) +#define MICROPY_PY_CMATH (1) +#define MICROPY_PY_IO (1) +#define MICROPY_PY_STRUCT (1) +#define MICROPY_PY_SYS (1) +// #define MICROPY_MODULE_FROZEN_MPY (1) +#define MICROPY_CPYTHON_COMPAT (0) +#define MICROPY_LONGINT_IMPL (MICROPY_LONGINT_IMPL_MPZ) +#define MICROPY_FLOAT_IMPL (MICROPY_FLOAT_IMPL_DOUBLE) +// #define MICROPY_PY_URANDOM (1) +#define MICROPY_PY_URANDOM_EXTRA_FUNCS (1) +#define MP_NEED_LOG2 (1) + +/* #define MP_PLAT_PRINT_STRN(str, len) mp_hal_stdout_tx_strn_cooked(str, len) + +// extra built in names to add to the global namespace +#define MICROPY_PORT_BUILTINS \ + { MP_ROM_QSTR(MP_QSTR_open), MP_ROM_PTR(&mp_builtin_open_obj) }, + +#define MICROPY_MIN_USE_STDOUT (1) */ + +// Type definitions for the specific machine. + +typedef intptr_t mp_int_t; // must be pointer size +typedef uintptr_t mp_uint_t; // must be pointer size +typedef long mp_off_t; + +// We need to provide a declaration/definition of alloca(). +#include + +// Define the port's name and hardware. +#define MICROPY_HW_BOARD_NAME "sh7305" +#define MICROPY_HW_MCU_NAME "sh-4a" + +#define MP_STATE_PORT MP_STATE_VM diff --git a/ports/sh/mphalport.c b/ports/sh/mphalport.c new file mode 100644 index 000000000..6fdf46aa1 --- /dev/null +++ b/ports/sh/mphalport.c @@ -0,0 +1,36 @@ +#include +#include "py/mpconfig.h" +#include "py/mphal.h" +#include "../../shared/readline/readline.h" + +#include "keymap.h" +#include +#include + +// Receive single character, blocking until one is available. +int mp_hal_stdin_rx_chr(void) { + while(1) { + key_event_t ev = getkey(); + int key = ev.key; + + if(key == KEY_ACON) + return CHAR_CTRL_C; + if(key == KEY_EXE) + return '\r'; + if(key == KEY_EXIT) + return CHAR_CTRL_D; + + uint32_t code_point = keymap_translate(key, ev.shift, ev.alpha); + if(code_point != 0) + return code_point; + } +} + +// Send the string of given length. +void mp_hal_stdout_tx_strn(const char *str, mp_uint_t len) { + int r = write(STDOUT_FILENO, str, len); + (void)r; + + extern void pe_draw(void); + pe_draw(); +} diff --git a/ports/sh/mphalport.h b/ports/sh/mphalport.h new file mode 100644 index 000000000..0db02a8c2 --- /dev/null +++ b/ports/sh/mphalport.h @@ -0,0 +1 @@ +static inline void mp_hal_set_interrupt_char(char c) {} diff --git a/shared/runtime/gchelper.h b/shared/runtime/gchelper.h index 645ee837f..10dc04d60 100644 --- a/shared/runtime/gchelper.h +++ b/shared/runtime/gchelper.h @@ -41,6 +41,8 @@ typedef uintptr_t gc_helper_regs_t[4]; typedef uintptr_t gc_helper_regs_t[10]; #elif defined(__aarch64__) typedef uintptr_t gc_helper_regs_t[11]; // x19-x29 +#elif defined(__sh3__) +typedef uintptr_t gc_helper_regs_t[8]; #endif #endif diff --git a/shared/runtime/gchelper_generic.c b/shared/runtime/gchelper_generic.c index dcd35f9c7..e2a8fbf80 100644 --- a/shared/runtime/gchelper_generic.c +++ b/shared/runtime/gchelper_generic.c @@ -150,6 +150,27 @@ STATIC void gc_helper_get_regs(gc_helper_regs_t arr) { arr[10] = x29; } +#elif defined(__sh3__) + +STATIC void gc_helper_get_regs(gc_helper_regs_t arr) { + register const long r8 asm ("r8"); + register const long r9 asm ("r9"); + register const long r10 asm ("r10"); + register const long r11 asm ("r11"); + register const long r12 asm ("r12"); + register const long r13 asm ("r13"); + register const long r14 asm ("r14"); + register const long r15 asm ("r15"); + arr[0] = r8; + arr[1] = r9; + arr[2] = r10; + arr[3] = r11; + arr[4] = r12; + arr[5] = r13; + arr[6] = r14; + arr[7] = r15; +} + #else #error "Architecture not supported for gc_helper_get_regs. Set MICROPY_GCREGS_SETJMP to use the fallback implementation."