No description
Find a file
2026-02-06 08:51:41 +01:00
core Initial commit: event-driven RISC-V kernel for CH32V003 2026-02-06 08:05:40 +01:00
drivers/ch32v003 Initial commit: event-driven RISC-V kernel for CH32V003 2026-02-06 08:05:40 +01:00
handlers Initial commit: event-driven RISC-V kernel for CH32V003 2026-02-06 08:05:40 +01:00
link Remove unused CH32V317 linker script 2026-02-06 08:40:22 +01:00
subfsm Fix FSM instance layout comments: +5=event_bits, +7=num_events 2026-02-06 08:51:41 +01:00
README.md Add Quick Start section with handler and FSM examples 2026-02-06 08:33:04 +01:00
run.sh Initial commit: event-driven RISC-V kernel for CH32V003 2026-02-06 08:05:40 +01:00

OS3 - RISC-V Event Kernel

Bare-metal event-driven kernel for CH32V003 (RV32EC, 48MHz, 16KB flash, 2KB RAM)

Features

  • Table-driven FSM engine with explicit state transitions
  • Event queue with O(1) dispatch
  • Tickless software timers (SysTick)
  • UART console (115200 8N1)
  • Debounced EXTI edge detection
  • Heartbeat LED

Build

./run.sh
minichlink -w build/kernel.bin flash -b

Requires: riscv32-unknown-elf-gcc toolchain

Directory Structure

core/      - Event loop, FSM engine, timer, console services
drivers/   - CH32V003 hardware (UART, EXTI, GPIO, clock)
subfsm/    - Table-driven finite state machines
handlers/  - Stateless event handlers
link/      - Linker scripts

Hardware Pins

Pin Function
PD3 Button input (EXTI, falling edge, pull-up)
PD5 UART TX (115200 8N1)
PD6 UART RX
PC1 Heartbeat LED (1Hz toggle)

Console Commands

  • stats - Display kernel, timer, console, and edge statistics

Quick Start

Creating a Handler (stateless)

Handlers react to events with immediate action. No state transitions.

# handlers/my_handler.S

.include "core/events.inc"

.section .bss
.align 4
my_counter: .word 0

# Auto-registration
.section .kernel_init, "a"
.align 2
    .word my_handler_init

.section .text
.align 2

my_handler_init:
    addi sp, sp, -4
    sw   ra, 0(sp)

    # Register for EV_KIND_TIMER, id=5
    li   a0, EV_KIND_TIMER
    li   a1, 5
    la   a2, my_handler
    call register_activity

    # Arm timer (1 second = 48000000 ticks)
    li   a0, 5                    # timer id
    li   a1, 48000000             # ticks
    call timer_arm_delta

    lw   ra, 0(sp)
    addi sp, sp, 4
    ret

my_handler:
    # Called on each timer event
    # a0=header, a1=data0, a2=data1

    la   t0, my_counter
    lw   t1, 0(t0)
    addi t1, t1, 1
    sw   t1, 0(t0)

    # Rearm timer
    addi sp, sp, -4
    sw   ra, 0(sp)
    li   a0, 5
    li   a1, 48000000
    call timer_arm_delta
    lw   ra, 0(sp)
    addi sp, sp, 4
    ret

Creating an FSM (with state transitions)

FSMs have explicit states and table-driven transitions.

# subfsm/my_fsm.S

.include "core/events.inc"

# FSM layout offsets
.equ FSM_MAGIC,      0
.equ FSM_STATE,      4
.equ FSM_EVENT_BITS, 5
.equ FSM_NUM_STATES, 6
.equ FSM_NUM_EVENTS, 7
.equ FSM_TABLE,      8
.equ FSM_ACTIONS,   12
.equ FSM_DATA,      16

# States
.equ STATE_OFF, 0
.equ STATE_ON,  1

# Events (must be power of 2 count)
.equ EV_TOGGLE, 0
.equ EV_RESET,  1

.section .bss
.align 4
my_fsm:
    .space 16                     # FSM header
    .word 0                       # +16: custom data

# Transition table: [next_state, action_id]
.section .rodata
.align 2
my_fsm_table:
    # STATE_OFF
    .byte STATE_ON,  1            # EV_TOGGLE -> ON, action_turn_on
    .byte STATE_OFF, 0            # EV_RESET  -> OFF, action_nop
    # STATE_ON
    .byte STATE_OFF, 2            # EV_TOGGLE -> OFF, action_turn_off
    .byte STATE_OFF, 2            # EV_RESET  -> OFF, action_turn_off

my_fsm_actions:
    .word action_nop
    .word action_turn_on
    .word action_turn_off

# Auto-registration
.section .kernel_init, "a"
.align 2
    .word my_fsm_init

.section .text
.align 2

my_fsm_init:
    addi sp, sp, -4
    sw   ra, 0(sp)

    # Initialize FSM
    la   a0, my_fsm
    la   a1, my_fsm_table
    la   a2, my_fsm_actions
    li   a3, 1                    # event_bits (2 events = 2^1)
    li   a4, 2                    # num_states
    li   a5, STATE_OFF            # initial state
    call fsm_init

    # Register dispatcher for your event source
    li   a0, EV_KIND_SOFT
    li   a1, 10                   # your event id
    la   a2, my_fsm_dispatch
    call register_activity

    lw   ra, 0(sp)
    addi sp, sp, 4
    ret

my_fsm_dispatch:
    # Map kernel event to FSM event and call fsm_step
    addi sp, sp, -8
    sw   ra, 0(sp)
    sw   a1, 4(sp)                # save data0

    # Extract flags or data to determine FSM event
    andi t0, a0, 0xFFFF           # flags from header
    li   a1, EV_TOGGLE            # default event

    la   a0, my_fsm
    lw   a2, 4(sp)                # data0
    li   a3, 0                    # data1
    call fsm_step

    lw   ra, 0(sp)
    addi sp, sp, 8
    ret

action_nop:
    ret

action_turn_on:
    # a0 = fsm instance, a1 = data0, a2 = data1
    # Turn on LED, etc.
    ret

action_turn_off:
    # Turn off LED, etc.
    ret

Key Rules

  1. 1 event → 1 step — No loops in handlers, bounded execution
  2. Actions don't write state — Only fsm_step modifies FSM state
  3. Auto-registration — Place init pointer in .kernel_init section
  4. Timer IDs 0-15 — Shared resource, pick unique IDs
  5. Event IDs 0-31 — Per event kind, pick unique IDs

Adding to Build

Edit run.sh:

# Add your handler
riscv32-unknown-elf-as $AS_FLAGS -o $BUILD_DIR/my_handler.o handlers/my_handler.S

# Add to linker
riscv32-unknown-elf-ld ... $BUILD_DIR/my_handler.o ...

Memory Map (RAM: 0x20000000 - 0x20000800)

Event System

Address Symbol Size Description
0x20000000 trap_snapshot_mcause 4 Exception cause code
0x20000004 trap_snapshot_mepc 4 Faulting PC address
0x20000008 trap_snapshot_mtval 4 Trap value (bad addr/insn)
0x2000000C isr_table 256 IRQ handler dispatch table (64 × 4 bytes)
0x2000010C event_queue 384 Event ring buffer (32 × 12 bytes)
0x2000028C event_head 4 Write index (modified by ISRs)
0x20000290 event_tail 4 Read index (modified by event_loop)
0x20000294 event_drop 4 Dropped events counter

Kernel Statistics

Address Symbol Size Description
0x20000298 kernel_stats 16 Kernel statistics block
+0 kernel_stat_dispatched 4 Events successfully dispatched
+4 kernel_stat_unhandled 4 Events with no registered handler
+8 kernel_stat_dropped 4 Events dropped (bounds violation)
+12 kernel_stat_max_depth 4 Maximum queue depth observed

Timer Service

Address Symbol Size Description
0x200002A8 timer_deadlines 64 Timer deadline array (16 × 4 bytes)
0x200002E8 timer_stats 16 Timer statistics block
+0 timer_stat_armed 4 Timers armed
+4 timer_stat_canceled 4 Timers canceled
+8 timer_stat_expired 4 Timers expired
+12 timer_stat_isr_calls 4 SysTick ISR invocations

Console Service

Address Symbol Size Description
0x200002F8 console_stats 12 Console statistics block
+0 console_stat_rx_count 4 RX events processed
+4 console_stat_tx_count 4 TX bytes sent
+8 console_stat_tx_busy_drops 4 TX attempts dropped (busy)

TX Streaming Job

Address Symbol Size Description
0x20000304 tx_job 12 TX streaming state
+0 tx_job_ptr 4 Current buffer pointer
+4 tx_job_remaining 4 Bytes remaining
+8 tx_job_state 4 0=IDLE, 1=STREAMING

FSM: Edge Detection

Address Symbol Size Description
0x20000310 edge_fsm 28 Edge FSM instance
+0 magic 4 "FSM0" signature (0x46534D30)
+4 state 1 Current state (0=IDLE, 1=DEBOUNCE)
+5 event_bits 1 log2(num_events) = 1
+6 num_states 1 Number of states = 2
+7 num_events 1 Number of events = 2
+8 table 4 Pointer to transition table
+12 actions 4 Pointer to action table
+16 edge_count 4 Validated edges (after debounce)
+20 raw_count 4 Raw EXTI events (all)
+24 last_timestamp 4 Timestamp of last edge

Handler: Heartbeat

Address Symbol Size Description
0x2000032C heartbeat_state 8 Heartbeat context
+0 magic 4 "HB00" signature (0x48423030)
+4 counter 4 Heartbeat tick count

Console Handler

Address Symbol Size Description
0x20000334 uart_console_state 44 Console FSM state
+0 magic 4 "UC00" signature (0x55433030)
+4 state 4 FSM state (0=INIT, 1=IDLE)
+8 rx_pos 4 Current position in rx_buffer
+12 rx_buffer 32 Line input buffer

Monitor

Address Symbol Size Description
0x20000360 monitor 12 Debug monitor structure
+0 magic 4 "MON0" signature (0x4D4F4E30)
+4 event_counter 4 Total events processed
+8 debug_mie 4 MIE register snapshot

Event Format (12 bytes)

Word 0: header  = kind:8 | id:8 | flags:16
Word 1: data0   = event-specific data
Word 2: data1   = event-specific data

Event Kinds

Kind Value Description
EV_KIND_NONE 0 Invalid/empty
EV_KIND_IRQ 1 Hardware interrupt
EV_KIND_TIMER 2 Timer expiration
EV_KIND_CONSOLE 3 Console RX/TX
EV_KIND_SOFT 4 Software events

Event IDs

EV_KIND_IRQ:

ID Symbol Description
0 EV_IRQ_SYSTICK SysTick timer
1 EV_IRQ_EXTI7_0 EXTI lines 7-0
2 EV_IRQ_USART1 USART1 interrupt

EV_KIND_TIMER:

ID Description
0 Heartbeat timer
1 Edge debounce timer

EV_KIND_CONSOLE:

ID Symbol Description
0 EV_CONSOLE_RX Character received (data0=byte)
1 EV_CONSOLE_TX TX complete (reserved)

EV_KIND_SOFT:

ID Symbol Description
1 EV_SOFT_TX_KICK TX streaming kick

FSM Transition Tables

Edge FSM

States: IDLE=0, DEBOUNCE=1 Events: EV_EXTI=0, EV_TIMEOUT=1

State Event Next State Action
IDLE EV_EXTI DEBOUNCE arm_debounce (50ms timer)
IDLE EV_TIMEOUT IDLE nop (stale)
DEBOUNCE EV_EXTI DEBOUNCE nop (suppress bounce)
DEBOUNCE EV_TIMEOUT IDLE edge_valid (increment counter)

IRQ Mapping

IRQ Vector Handler Event
12 SysTick timer_isr EV_KIND_TIMER
18 USART1 uart_hw_rx_isr EV_CONSOLE_RX
20 EXTI7_0 exti_isr EV_IRQ_EXTI7_0

GDB Debugging

riscv32-unknown-elf-gdb build/kernel.elf
(gdb) target remote :3333
(gdb) monitor reset halt
(gdb) load
(gdb) continue

Useful Commands

# View kernel stats
x/4w &kernel_stats

# View timer stats  
x/4w &timer_stats

# View console stats
x/3w &console_stats

# View edge FSM state
x/7w &edge_fsm

# View event queue head/tail
x/3w &event_head

License

MIT