From 6a1fb5bd0c557a0353529b7f7722da9e5bb0ae4e Mon Sep 17 00:00:00 2001 From: Graziano Misuraca Date: Fri, 17 Jul 2026 15:00:19 -0400 Subject: [PATCH 1/3] wch: Add chip-specific wfi/wfe implementations --- core/src/cpus/riscv32.zig | 6 ----- port/wch/ch32v/src/cpus/main.zig | 9 ++----- port/wch/ch32v/src/cpus/qingkev2-rv32ec.zig | 24 +++++++++++++++++++ port/wch/ch32v/src/cpus/qingkev3-rv32imac.zig | 19 +++++++++++++++ port/wch/ch32v/src/cpus/qingkev4-rv32imac.zig | 19 +++++++++++++++ .../wch/ch32v/src/cpus/qingkev4-rv32imacf.zig | 19 +++++++++++++++ 6 files changed, 83 insertions(+), 13 deletions(-) diff --git a/core/src/cpus/riscv32.zig b/core/src/cpus/riscv32.zig index 10aa6794b..f9da193da 100644 --- a/core/src/cpus/riscv32.zig +++ b/core/src/cpus/riscv32.zig @@ -8,12 +8,6 @@ pub const interrupt = riscv32_common.interrupt; pub const nop = riscv32_common.nop; pub const wfi = riscv32_common.wfi; -pub fn wfe() void { - asm volatile ("csrs 0x810, 0x1"); - wfi(); - asm volatile ("csrs 0x810, 0x1"); -} - pub const startup_logic = struct { extern fn microzig_main() noreturn; diff --git a/port/wch/ch32v/src/cpus/main.zig b/port/wch/ch32v/src/cpus/main.zig index 443c7ac13..91cc7940a 100644 --- a/port/wch/ch32v/src/cpus/main.zig +++ b/port/wch/ch32v/src/cpus/main.zig @@ -190,16 +190,11 @@ pub const interrupt = struct { }; pub inline fn wfi() void { - PFIC.SCTLR.modify(.{ .WFITOWFE = 0 }); - asm volatile ("wfi"); + cpu_impl.wfi(microzig.chip); } pub inline fn wfe() void { - // We SETEVENT so the wfe immediately wakes and clears any pending events. - // This ensures the second one actually stays asleep. - PFIC.SCTLR.modify(.{ .WFITOWFE = 1 }); - asm volatile ("wfi"); - asm volatile ("wfi"); + cpu_impl.wfe(microzig.chip); } pub fn unhandled() callconv(riscv_calling_convention) void { diff --git a/port/wch/ch32v/src/cpus/qingkev2-rv32ec.zig b/port/wch/ch32v/src/cpus/qingkev2-rv32ec.zig index 7ae681cee..e6b6cc56f 100644 --- a/port/wch/ch32v/src/cpus/qingkev2-rv32ec.zig +++ b/port/wch/ch32v/src/cpus/qingkev2-rv32ec.zig @@ -105,6 +105,30 @@ pub inline fn system_init(comptime chip: anytype) void { RCC.CTLR.modify(.{ .HSITRIM = 0x10 }); } +/// Wait for interrupt. Clears WFITOWFE so the wfi instruction behaves as +/// a true wait-for-interrupt (not wait-for-event). +pub inline fn wfi(comptime chip: anytype) void { + const PFIC = chip.peripherals.PFIC; + PFIC.SCTLR.modify(.{ .WFITOWFE = 0 }); + asm volatile ("wfi"); +} + +/// Wait for event. Sets SETEVENT to clear any stale event latch, enables +/// WFITOWFE so the wfi instruction acts as wfe, then executes wfi twice: +/// the first clears the just-set event, the second sleeps until a real event. +/// +/// NOTE: OpenWCH's CH32V003 reference implementation has a more complex WFE +/// that handles deep sleep (SLEEPDEEP) mode by manipulating EXTI.EVENR and +/// EXTI.INTENR registers, and the non-deep-sleep path sets SEVONPEND. +/// This simplified version does not handle those cases. See: +/// https://github.com/openwch/ch32v003/blob/main/EVT/EXAM/SRC/Core/core_riscv.h +pub inline fn wfe(comptime chip: anytype) void { + const PFIC = chip.peripherals.PFIC; + PFIC.SCTLR.modify(.{ .SETEVENT = 1, .WFITOWFE = 1 }); + asm volatile ("wfi"); + asm volatile ("wfi"); +} + pub const csr_types = struct { pub const intsyscr = packed struct(u32) { /// [0] Hardware Prologue/Epilogue (HPE) enable diff --git a/port/wch/ch32v/src/cpus/qingkev3-rv32imac.zig b/port/wch/ch32v/src/cpus/qingkev3-rv32imac.zig index e839eb843..4aa3b87d9 100644 --- a/port/wch/ch32v/src/cpus/qingkev3-rv32imac.zig +++ b/port/wch/ch32v/src/cpus/qingkev3-rv32imac.zig @@ -138,6 +138,25 @@ pub inline fn system_init(comptime chip: anytype) void { }); } +/// Wait for interrupt. Clears WFITOWFE so the wfi instruction behaves as +/// a true wait-for-interrupt. +pub inline fn wfi(comptime chip: anytype) void { + const PFIC = chip.peripherals.PFIC; + PFIC.SCTLR.modify(.{ .WFITOWFE = 0 }); + asm volatile ("wfi"); +} + +/// Wait for event. Sets SETEVENT to clear any stale event latch, enables +/// WFITOWFE so the wfi instruction acts as wfe, then executes wfi twice: +/// the first clears the just-set event, the second sleeps until a real event. +/// Matches OpenWCH reference: _SEV() + _WFE() + _WFE(). +pub inline fn wfe(comptime chip: anytype) void { + const PFIC = chip.peripherals.PFIC; + PFIC.SCTLR.modify(.{ .SETEVENT = 1, .WFITOWFE = 1 }); + asm volatile ("wfi"); + asm volatile ("wfi"); +} + pub const csr_types = struct { pub const intsyscr = packed struct(u32) { /// [0] Hardware stack enable diff --git a/port/wch/ch32v/src/cpus/qingkev4-rv32imac.zig b/port/wch/ch32v/src/cpus/qingkev4-rv32imac.zig index c0e246b8b..2a18e4dce 100644 --- a/port/wch/ch32v/src/cpus/qingkev4-rv32imac.zig +++ b/port/wch/ch32v/src/cpus/qingkev4-rv32imac.zig @@ -202,6 +202,25 @@ pub inline fn system_init(comptime chip: anytype) void { }); } +/// Wait for interrupt. Clears WFITOWFE so the wfi instruction behaves as +/// a true wait-for-interrupt. +pub inline fn wfi(comptime chip: anytype) void { + const PFIC = chip.peripherals.PFIC; + PFIC.SCTLR.modify(.{ .WFITOWFE = 0 }); + asm volatile ("wfi"); +} + +/// Wait for event. Sets SETEVENT to clear any stale event latch, enables +/// WFITOWFE so the wfi instruction acts as wfe, then executes wfi twice: +/// the first clears the just-set event, the second sleeps until a real event. +/// Matches OpenWCH reference: _SEV() + _WFE() + _WFE(). +pub inline fn wfe(comptime chip: anytype) void { + const PFIC = chip.peripherals.PFIC; + PFIC.SCTLR.modify(.{ .SETEVENT = 1, .WFITOWFE = 1 }); + asm volatile ("wfi"); + asm volatile ("wfi"); +} + pub const csr_types = struct { pub const intsyscr = packed struct(u32) { /// [0] Hardware Prologue/Epilogue (HPE) enable diff --git a/port/wch/ch32v/src/cpus/qingkev4-rv32imacf.zig b/port/wch/ch32v/src/cpus/qingkev4-rv32imacf.zig index e64dbbb1b..b2758e827 100644 --- a/port/wch/ch32v/src/cpus/qingkev4-rv32imacf.zig +++ b/port/wch/ch32v/src/cpus/qingkev4-rv32imacf.zig @@ -250,6 +250,25 @@ pub inline fn system_init(comptime chip: anytype) void { RCC.CFGR2.raw = 0; } +/// Wait for interrupt. Clears WFITOWFE so the wfi instruction behaves as +/// a true wait-for-interrupt. +pub inline fn wfi(comptime chip: anytype) void { + const PFIC = chip.peripherals.PFIC; + PFIC.SCTLR.modify(.{ .WFITOWFE = 0 }); + asm volatile ("wfi"); +} + +/// Wait for event. Sets SETEVENT to clear any stale event latch, enables +/// WFITOWFE so the wfi instruction acts as wfe, then executes wfi twice: +/// the first clears the just-set event, the second sleeps until a real event. +/// Matches OpenWCH reference: _SEV() + _WFE() + _WFE(). +pub inline fn wfe(comptime chip: anytype) void { + const PFIC = chip.peripherals.PFIC; + PFIC.SCTLR.modify(.{ .SETEVENT = 1, .WFITOWFE = 1 }); + asm volatile ("wfi"); + asm volatile ("wfi"); +} + pub const csr_types = struct { pub const intsyscr = packed struct(u32) { /// [0] Hardware Prologue/Epilogue (HPE) enable From b0e9c937974ae4fa58d17f8ae529fde72f265838 Mon Sep 17 00:00:00 2001 From: Graziano Misuraca Date: Fri, 17 Jul 2026 15:00:22 -0400 Subject: [PATCH 2/3] wch: Add blinky_wfe example --- examples/wch/ch32v/build.zig | 1 + examples/wch/ch32v/src/blinky_wfe.zig | 57 +++++++++++++++++++++++++++ 2 files changed, 58 insertions(+) create mode 100644 examples/wch/ch32v/src/blinky_wfe.zig diff --git a/examples/wch/ch32v/build.zig b/examples/wch/ch32v/build.zig index 73fb0c6de..162ef3703 100644 --- a/examples/wch/ch32v/build.zig +++ b/examples/wch/ch32v/build.zig @@ -36,6 +36,7 @@ pub fn build(b: *std.Build) void { .{ .target = mb.ports.ch32v.chips.ch32v203x6, .name = "blinky_ch32v203", .file = "src/blinky.zig" }, .{ .target = mb.ports.ch32v.chips.ch32v203x6, .name = "blinky_systick_ch32v203", .file = "src/blinky_systick.zig" }, .{ .target = mb.ports.ch32v.boards.ch32v203.nano_ch32v203, .name = "nano_ch32v203_blinky", .file = "src/board_blinky.zig" }, + .{ .target = mb.ports.ch32v.boards.ch32v203.nano_ch32v203, .name = "nano_ch32v203_blinky_wfe", .file = "src/blinky_wfe.zig" }, .{ .target = mb.ports.ch32v.boards.ch32v203.suzuduino_uno_v1b, .name = "suzuduino_blinky", .file = "src/board_blinky.zig" }, .{ .target = mb.ports.ch32v.boards.ch32v203.lana_tny, .name = "lana_tny_dma", .file = "src/dma.zig" }, .{ .target = mb.ports.ch32v.boards.ch32v203.lana_tny, .name = "lana_tny_ws2812", .file = "src/ws2812.zig" }, diff --git a/examples/wch/ch32v/src/blinky_wfe.zig b/examples/wch/ch32v/src/blinky_wfe.zig new file mode 100644 index 000000000..9db73d1db --- /dev/null +++ b/examples/wch/ch32v/src/blinky_wfe.zig @@ -0,0 +1,57 @@ +const microzig = @import("microzig"); +const hal = microzig.hal; +const cpu = microzig.cpu; +const board = microzig.board; +const peripherals = microzig.chip.peripherals; + +const Pins = hal.pins.Pins(board.pin_config); +var pins: Pins = undefined; + +pub const panic = microzig.panic; + +pub const std_options = microzig.std_options(.{}); + +comptime { + _ = microzig.export_startup(); +} + +pub const microzig_options: microzig.Options = .{ + .overwrite_hal_interrupts = true, + .interrupts = .{ + .SysTick = sys_tick_handler, + }, +}; + +fn sys_tick_handler() callconv(cpu.riscv_calling_convention) void { + pins.led.toggle(); + peripherals.PFIC.STK_SR.modify(.{ .CNTIF = 0 }); +} + +pub fn main() !void { + board.init(); + pins = board.pin_config.apply(); + + // Configure SysTick for 1 Hz interrupt. + const PFIC = peripherals.PFIC; + // Reset configuration. + PFIC.STK_CTLR.raw = 0; + // Reset the Count Register. + PFIC.STK_CNTL.raw = 0; + // Set the compare register to trigger once per second. + PFIC.STK_CMPLR.raw = board.cpu_frequency - 1; + // Set the SysTick Configuration. + PFIC.STK_CTLR.modify(.{ + .STE = 1, + .STIE = 1, + .STCLK = 1, + .STRE = 1, + }); + // Clear the trigger state for the next interrupt. + PFIC.STK_SR.modify(.{ .CNTIF = 0 }); + + cpu.interrupt.enable(.SysTick); + + while (true) { + cpu.wfe(); + } +} From fa967fe8d90064bd5426dc4bbde87fcee89c36ec Mon Sep 17 00:00:00 2001 From: Graziano Misuraca Date: Fri, 17 Jul 2026 15:11:31 -0400 Subject: [PATCH 3/3] Remove unused std import --- core/src/cpus/riscv32.zig | 1 - 1 file changed, 1 deletion(-) diff --git a/core/src/cpus/riscv32.zig b/core/src/cpus/riscv32.zig index f9da193da..6a7c4aff6 100644 --- a/core/src/cpus/riscv32.zig +++ b/core/src/cpus/riscv32.zig @@ -1,4 +1,3 @@ -const std = @import("std"); const microzig = @import("microzig"); const riscv32_common = @import("riscv32-common");