Skip to content

Commit 7c48ce9

Browse files
ccattutoclaude
andauthored
MicroPython: added headless and UART REPL build modes. Added support for frozen scripts. (#6)
MicroPython: implemented compile-time selectable modes for the MicroPython port. - REPL_SYSCALL (default): Interactive REPL with over stdio. uses Newlib and read()/write() syscalls. - HEADLESS: Frozen script, no stdio. Executes frozen script at startup. - UART: REPL over UART, no stdio, optional frozen startup script. Co-authored-by: Claude <noreply@anthropic.com>
1 parent 12c0142 commit 7c48ce9

10 files changed

Lines changed: 274 additions & 75 deletions

File tree

advanced/micropython/README.md

Lines changed: 61 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,70 @@
1-
## Compiling MicroPython
1+
# MicroPython port for riscv-emu.py
2+
3+
This is a MicroPython port that runs on the riscv-emu.py RISC-V emulator.
4+
5+
## Prerequisites
6+
7+
Before building, ensure the micropython-lib submodule is initialized:
8+
9+
```bash
10+
cd micropython
11+
git submodule update --init lib/micropython-lib
212
```
3-
cd port-riscv-emu.py
413

5-
# Build with default (RV32IM)
14+
## Building
15+
16+
The port supports three build modes:
17+
18+
### 1. REPL_SYSCALL (default)
19+
Interactive REPL on host's stdio:
20+
```bash
621
make
22+
# or explicitly
23+
make MODE=REPL_SYSCALL
24+
```
725

8-
# Build with all extensions (RV32IMAC)
9-
make RVM=1 RVA=1 RVC=1
26+
### 2. HEADLESS
27+
Executes a frozen Python script with no stdio (requires `FROZEN_SCRIPT`, defaults to `startup.py`):
28+
```bash
29+
make MODE=HEADLESS FROZEN_SCRIPT=startup.py
1030
```
1131

12-
## Running MicroPython
32+
### 3. UART
33+
REPL over emulated UART, with an optional frozen initialization script (requires `FROZEN_SCRIPT`, defaults to `startup.py`):
34+
```bash
35+
make MODE=UART FROZEN_SCRIPT=startup.py
1336
```
37+
38+
## Build Options
39+
40+
- `MODE` - Build mode: `REPL_SYSCALL` (default), `HEADLESS`, or `UART`
41+
- `FROZEN_SCRIPT` - Python script to freeze into firmware (required for HEADLESS, optional for UART, defaults to `startup.py`)
42+
- `RVM=1` - Enable RISC-V M extension (multiply/divide, default: enabled)
43+
- `RVA=0` - Enable RISC-V A extension (atomics, default: disabled)
44+
- `RVC=0` - Enable RISC-V C extension (compressed instructions, default: disabled)
45+
46+
## Running
47+
48+
In the emulator's root directory. Prebuilt binary:
49+
```bash
1450
./riscv-emu.py --raw-tty --ram-size=4096 prebuilt/micropython.elf
1551
```
52+
53+
Compiled binary, REPL over stdio (REPL_SYSCALL default build mode):
54+
```bash
55+
./riscv-emu.py --raw-tty --ram-size=4096 advanced/micropython/port-riscv-emu.py/build/firmware.elf
56+
```
57+
58+
Compiled binary, headless (HEADLESS build mode):
59+
```bash
60+
./riscv-emu.py ---ram-size=4096 advanced/micropython/port-riscv-emu.py/build/firmware.elf
61+
```
62+
63+
Compiled binary, REPL over UART (UART build mode):
64+
```bash
65+
./riscv-emu.py ---ram-size=4096 --uart advanced/micropython/port-riscv-emu.py/build/firmware.elf
66+
```
67+
And connect to MicroPython using your favorite terminal program:
68+
```bash
69+
screen /dev/ttys007 115200
70+
```

advanced/micropython/port-riscv-emu.py/Makefile

Lines changed: 78 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,59 @@ QSTR_DEFS = qstrdefsport.h
88
# MicroPython feature configurations
99
MICROPY_ROM_TEXT_COMPRESSION ?= 1
1010

11+
# Mode selection (default to REPL_SYSCALL for backwards compatibility)
12+
# Valid values: REPL_SYSCALL, HEADLESS, UART
13+
MODE ?= REPL_SYSCALL
14+
15+
# Script to embed (for HEADLESS and UART modes)
16+
FROZEN_SCRIPT ?= startup.py
17+
18+
# Frozen module support for HEADLESS and UART modes
19+
# HEADLESS mode requires a frozen script
20+
# UART mode optionally accepts a frozen script
21+
ifeq ($(MODE),HEADLESS)
22+
ifeq ($(FROZEN_SCRIPT),)
23+
$(error HEADLESS mode requires FROZEN_SCRIPT to be set)
24+
endif
25+
FROZEN_ENABLED = 1
26+
else ifeq ($(MODE),UART)
27+
ifneq ($(FROZEN_SCRIPT),)
28+
FROZEN_ENABLED = 1
29+
else
30+
FROZEN_ENABLED = 0
31+
endif
32+
else ifeq ($(MODE),REPL_SYSCALL)
33+
FROZEN_ENABLED = 0
34+
else
35+
$(error Invalid MODE=$(MODE). Valid values: REPL_SYSCALL, HEADLESS, UART)
36+
endif
37+
38+
ifeq ($(FROZEN_ENABLED),1)
39+
# Module name includes .py extension (manifest system registers with full filename)
40+
FROZEN_MODULE_NAME = $(FROZEN_SCRIPT)
41+
42+
# Use manifest.py for freezing (idiomatic MicroPython approach)
43+
# The manifest file will handle compilation via makemanifest.py
44+
FROZEN_MANIFEST = $(CURDIR)/manifest.py
45+
46+
# Pass FROZEN_SCRIPT to manifest as a variable
47+
MICROPY_MANIFEST_FROZEN_SCRIPT = $(FROZEN_SCRIPT)
48+
endif
49+
1150
# include py core make definitions
1251
include $(TOP)/py/py.mk
1352

1453
ifeq ($(CROSS), 1)
1554
CROSS_COMPILE ?= riscv64-unknown-elf-
1655
endif
1756

57+
# MicroPython build tools
58+
MPY_CROSS ?= $(TOP)/mpy-cross/build/mpy-cross
59+
MPY_TOOL ?= $(PYTHON) $(TOP)/tools/mpy-tool.py
60+
# Flags for mpy-cross and mpy-tool to match port configuration
61+
MPY_CROSS_FLAGS += -msmall-int-bits=31
62+
MPY_TOOL_FLAGS += -mlongint-impl=longlong
63+
1864
# Extension options - set to 1 to enable, 0 to disable
1965
# Note: the toolchain might not support all combinations
2066
RVM ?= 1 # Multiply/Divide (M extension)
@@ -34,12 +80,12 @@ ifeq ($(CROSS), 1)
3480
DFU = $(TOP)/tools/dfu.py
3581
PYDFU = $(TOP)/tools/pydfu.py
3682
CFLAGS_RISCV = -march=$(MARCH) -mabi=ilp32 -D_REENT_SMALL
37-
CFLAGS += $(INC) -Wall -Werror -std=c99 $(CFLAGS_RISCV) $(COPT) #-O2
83+
CFLAGS += $(INC) -Wall -Werror -std=c99 $(CFLAGS_RISCV) $(COPT) -DMICROPY_PORT_MODE=MODE_$(MODE)
3884
LDFLAGS += -nostartfiles -static -Tlinker_newlib.ld --specs=nosys.specs
3985
else
4086
UNAME_S := $(shell uname -s)
4187
LD = $(CC)
42-
CFLAGS += $(INC) -Wall -Werror -Wdouble-promotion -Wfloat-conversion -std=c99 $(COPT)
88+
CFLAGS += $(INC) -Wall -Werror -Wdouble-promotion -Wfloat-conversion -std=c99 $(COPT) -DMICROPY_PORT_MODE=MODE_$(MODE)
4389
ifeq ($(UNAME_S),Linux)
4490
LDFLAGS += -nostartfiles -Wl,--gc-sections -static -Tlinker_newlib.ld --specs=nano.specs -Wl,-map,$@.map -Wl,-dead_strip
4591
else ifeq ($(UNAME_S),Darwin)
@@ -58,17 +104,23 @@ CFLAGS += -O2 -DNDEBUG
58104
CFLAGS += -fdata-sections -ffunction-sections
59105
endif
60106

107+
# Add frozen module defines when frozen modules are enabled
108+
ifeq ($(FROZEN_ENABLED),1)
109+
CFLAGS += -DFROZEN_MODULE_NAME=\"$(FROZEN_MODULE_NAME)\"
110+
CFLAGS += -DMICROPY_HAS_FROZEN_MODULES=1
111+
endif
112+
61113
# Flags for optional C++ source code
62114
CXXFLAGS += $(filter-out -std=c99,$(CFLAGS))
63115

64116
LIBS = -lm
65117

118+
# Common source files for all build modes
66119
SRC_C = \
67120
main.c \
68-
minimal_stubs.c \
69121
gccollect.c \
70-
mphalport.c \
71-
shared/libc/printf.c \
122+
minimal_stubs.c \
123+
shared/libc/printf.c \
72124
shared/readline/readline.c \
73125
shared/runtime/pyexec.c \
74126
shared/runtime/gchelper_native.c \
@@ -77,18 +129,30 @@ SRC_C = \
77129
extmod/machine_mem.c \
78130
extmod/moductypes.c
79131

132+
SRC_S = \
133+
start_newlib.S \
134+
syscalls_newlib.S \
135+
gchelper_rv32i.S
136+
137+
# Select HAL based on build mode
138+
ifeq ($(MODE),REPL_SYSCALL)
139+
# Mode 1: syscall-based I/O
140+
SRC_C += mphalport.c
141+
else ifeq ($(MODE),HEADLESS)
142+
# Mode 2: headless (no stdio)
143+
SRC_C += mphalport_headless.c
144+
else ifeq ($(MODE),UART)
145+
# Mode 3: UART MMIO I/O
146+
SRC_C += mphalport_uart.c
147+
endif
148+
80149
ifeq ($(CROSS), 1)
81150
SRC_C += shared/libc/string0.c
82151
endif
83152

84153
SRC_QSTR += shared/readline/readline.c shared/runtime/pyexec.c extmod/modmachine.c extmod/machine_mem.c extmod/moductypes.c
85154

86-
SRC_S = \
87-
start_newlib.S \
88-
syscalls_newlib.S \
89-
gchelper_rv32i.S
90-
91-
OBJ += $(PY_CORE_O)
155+
OBJ += $(PY_O)
92156
OBJ += $(addprefix $(BUILD)/, $(SRC_C:.c=.o))
93157
OBJ += $(addprefix $(BUILD)/, $(SRC_S:.S=.o))
94158
OBJ += $(addprefix $(BUILD)/, $(SRC_CXX:.cpp=.o))
@@ -99,9 +163,9 @@ else
99163
all: $(BUILD)/firmware.elf
100164
endif
101165

102-
$(BUILD)/_frozen_mpy.c: $(TOP)/tests/frozen/frozentest.mpy $(BUILD)/genhdr/qstrdefs.generated.h
103-
$(ECHO) "MISC freezing bytecode"
104-
$(Q)$(TOP)/tools/mpy-tool.py -f -q $(BUILD)/genhdr/qstrdefs.preprocessed.h -mlongint-impl=none $< > $@
166+
# Print build configuration
167+
$(info Building MicroPython port in MODE=$(MODE))
168+
105169

106170
$(BUILD)/firmware.elf: $(OBJ)
107171
$(ECHO) "LINK $@"

advanced/micropython/port-riscv-emu.py/main.c

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
#include "py/objlist.h"
66
#include "shared/runtime/pyexec.h"
77
#include "shared/runtime/gchelper.h"
8+
#include "mpconfigport.h"
89

910
extern uint8_t _gc_heap_start, _gc_heap_end;
1011

@@ -19,9 +20,21 @@ int main(int argc, char *argv[]) {
1920
mp_obj_list_append(mp_sys_argv, mp_obj_new_str(argv[i], strlen(argv[i])));
2021
}
2122

23+
#if (MICROPY_PORT_MODE == MODE_REPL_SYSCALL)
24+
// Welcome message for syscall REPL mode
2225
mp_printf(&mp_plat_print, "Welcome to MicroPython on RISC-V!\n");
26+
#endif
2327

28+
#ifdef FROZEN_MODULE_NAME
29+
// Execute frozen script (module name set by Makefile)
30+
pyexec_frozen_module(FROZEN_MODULE_NAME, false);
31+
#endif
32+
33+
#if (MICROPY_PORT_MODE == MODE_REPL_SYSCALL) || \
34+
(MICROPY_PORT_MODE == MODE_UART)
35+
// Start REPL
2436
pyexec_friendly_repl();
37+
#endif
2538

2639
gc_sweep_all();
2740
mp_deinit();
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
# Manifest for freezing Python scripts into firmware
2+
# This file defines which Python modules should be compiled and frozen into the firmware
3+
4+
# Freeze startup.py (the default embedded script)
5+
# To freeze a different script, modify this file or specify FROZEN_SCRIPT in your Makefile
6+
freeze("$(PORT_DIR)", "startup.py")

advanced/micropython/port-riscv-emu.py/mpconfigport.h

Lines changed: 35 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,28 @@
11
#include <stdint.h>
22

3+
// Force object representation A (default for 32-bit systems)
4+
// Use numeric value 0 since MICROPY_OBJ_REPR_A isn't defined yet
5+
#define MICROPY_OBJ_REPR (0)
6+
37
// options to control how MicroPython is built
48

5-
#define MICROPY_CONFIG_ROM_LEVEL (MICROPY_CONFIG_ROM_LEVEL_EXTRA_FEATURES)
9+
// Mode definitions (set via Makefile)
10+
#define MODE_REPL_SYSCALL 1 // Interactive REPL with syscalls
11+
#define MODE_HEADLESS 2 // Frozen script execution, no stdio
12+
#define MODE_UART 3 // Frozen init script + UART REPL
13+
14+
#ifndef MICROPY_PORT_MODE
15+
#define MICROPY_PORT_MODE MODE_REPL_SYSCALL
16+
#endif
17+
18+
// Use CORE_FEATURES ROM level as base, then explicitly enable what we need
19+
#define MICROPY_CONFIG_ROM_LEVEL (MICROPY_CONFIG_ROM_LEVEL_CORE_FEATURES)
20+
21+
// Float support enabled for all modes
22+
#define MICROPY_PY_BUILTINS_FLOAT (1)
23+
#define MICROPY_FLOAT_IMPL (MICROPY_FLOAT_IMPL_FLOAT)
24+
#define MICROPY_PY_MATH (1)
25+
#define MICROPY_PY_CMATH (0)
626

727
#define MICROPY_ENABLE_COMPILER (1)
828
#define MICROPY_ENAVLE_REPL (1)
@@ -12,30 +32,40 @@
1232
#define MICROPY_ENABLE_GC (1)
1333
#define MICROPY_HELPER_REPL (1)
1434
#define MICROPY_ENABLE_REPL_HELPERS (1)
15-
#define MICROPY_MODULE_FROZEN_MPY (0)
35+
1636
#define MICROPY_ENABLE_EXTERNAL_IMPORT (0)
1737
#define MICROPY_KBD_EXCEPTION (1)
1838

39+
// MICROPY_MODULE_FROZEN_MPY is automatically defined by the manifest system, when needed
40+
1941
// Enable core modules
2042
#define MICROPY_PY_MICROPYTHON (1)
2143
#define MICROPY_PY_BUILTINS_HELP (1)
2244
#define MICROPY_PY_BUILTINS_HELP_MODULES (1)
2345
#define MICROPY_PY_GC (1)
2446
#define MICROPY_PY_BUILTINS_STR_UNICODE (1)
25-
#define MICROPY_PY_BUILTINS_FLOAT (1)
47+
2648
#define MICROPY_LONGINT_IMPL (MICROPY_LONGINT_IMPL_LONGLONG)
27-
#define MICROPY_FLOAT_IMPL (MICROPY_FLOAT_IMPL_FLOAT)
2849
#define MICROPY_PY_BUILTINS_COMPLEX (0)
2950
#define MICROPY_PY_IO (0) // no file system or streams
51+
52+
// Explicitly enable modules we need (some may not be in CORE_FEATURES)
3053
#define MICROPY_PY_ARRAY (1)
3154
#define MICROPY_PY_COLLECTIONS (1)
32-
#define MICROPY_PY_MATH (1)
55+
#define MICROPY_PY_COLLECTIONS_DEQUE (1)
56+
#define MICROPY_PY_COLLECTIONS_ORDEREDDICT (1)
3357
#define MICROPY_PY_URANDOM (1)
58+
#define MICROPY_PY_URANDOM_SEED_INIT_FUNC (0)
3459
#define MICROPY_PY_STRUCT (1)
3560
#define MICROPY_PY_ERRNO (1)
3661
#define MICROPY_PY_BINASCII (1)
3762
#define MICROPY_PY_RE (1)
63+
#define MICROPY_PY_HEAPQ (1)
64+
#define MICROPY_PY_HASHLIB (0)
65+
#define MICROPY_PY_JSON (1)
3866
#define MICROPY_PY_UCTYPES (1)
67+
68+
// Enable machine module for MMIO access (mem8, mem16, mem32)
3969
#define MICROPY_PY_MACHINE (1)
4070
#define MICROPY_PY_MACHINE_INCLUDEFILE "modmachine_port.c"
4171
#define MICROPY_PY_MACHINE_MEMX (1)
@@ -50,7 +80,6 @@
5080
#define MICROPY_PY_SYS_ARGV (1)
5181

5282
#define MICROPY_PY_BUILTINS_SLICE (1)
53-
#define MICROPY_PY_ALL_FEATURES (1)
5483

5584
#define MICROPY_ALLOC_PATH_MAX (256)
5685

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
/*
2+
* HAL implementation for HEADLESS mode
3+
* Provides no-op stdio functions (print/input have no effect)
4+
*/
5+
6+
#include "py/mphal.h"
7+
8+
// stdin: Always return immediately with 0
9+
int mp_hal_stdin_rx_chr(void) {
10+
return 0;
11+
}
12+
13+
// stdout: Discard all output
14+
mp_uint_t mp_hal_stdout_tx_strn(const char *str, size_t len) {
15+
(void)str;
16+
(void)len;
17+
return len; // Pretend we wrote everything
18+
}
19+
20+
void mp_hal_delay_ms(mp_uint_t ms) {
21+
// No-op for headless mode
22+
(void)ms;
23+
}

0 commit comments

Comments
 (0)