Issues with best_effort_wfe_or_timeout and WFE in SDK 2.0.0 #1812
Labels
No labels
bazel
bluetooth
board settings
boot_stage2
bug
build
c++
can't reproduce
clang
clib-interface
cmsis
code cleanliness
code size
compiler version issues
cyw43_driver
debug
discussion
documentation
duplicate
dynamic_clock
elf2uf2
enhancement
errata
filesystem
flash layout
floating point
FreeRTOS
hardware_adc
hardware_claim
hardware_clocks
hardware_dma
hardware_exception
hardware_flash
hardware_gpio
hardware_i2c
hardware_irq
hardware_pio
hardware_pwm
hardware_rcp
hardware_rtc
hardware_spi
hardware_sync
hardware_uart
hardware_vreg
hardware_watchdog
hardware_xosc
help wanted
host
IAR
installation
invalid
linker scripts
lto
lwip
mbedtls
missing API
needs reporter detail
networking
newlib
newlib-multicore
non-c
non-cmake
non-gcc
openocd
orphan PR
OS integration
pico_async_context
pico_base
pico_binary_info
pico_flash
picolibc
pico_lwip
pico_malloc
pico_multicore
pico_printf
pico_runtime
pico_stdio
pico_stdio_usb
pico_sync
pico_time
pico_util
pioasm
port
question
review
review2
rtos interop
runtime init
static library
svd
test
tinyusb
vscode
wifi integration
wontfix
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
gh-raspberrypi/pico-sdk#1812
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Background
We are trying to achieve good idle power consumption on RP2350 in MicroPython. Idle here means sitting at the REPL (with either UART or USB serial) doing nothing. In this case the CPU should be able to gate its clock via WFI/WFE to reduce power consumption.
Eg on a standard Pico with RP2040, connected to a USB port and a terminal port program open, the Pico draws about 15.2mA (at 5V). Then running a simple while-True busy loop that increases to about 18mA. This is expected behaviour.
Summary of problems
Things seem to have changed in SDK 2.0.0 (the tag 2.0.0). There are a few issues that seem to be related:
best_effort_wfe_or_timeout()now seems to return immediately, on both RP2040 and RP2350best_effort_wfe_or_timeout()calls__sev(); __wfe()to clear any existing event, which potentially misses eventsldaexbandstrexbexclusive-access instructions, and these seem to set the event flag (equivalent to__sev()), meaning thatspin_lock_blockingsets the eventhardware_alarm_set_targetset the event flag, making these functions unusable for making a timer to wake from WFEDetails
best_effort_wfe_or_timeout()now seems to return immediatelyRunning the following on RP2040:
With pico-sdk 1.5.1 that will consume about 15.2mA on a Pico board. With pico-sdk 2.0.0 that code consumes about 18mA on a Pico board.
Timing how long the
best_effort_wfe_or_timeoutfunction lasts, on pico-sdk 2.0.0 it always returns pretty much immediately (eg within 5us), so the above loop is effectively a busy loop, hence the higher power consumption.This looks like a regression with pico-sdk 2.0.0 on RP2040.
best_effort_wfe_or_timeout()calls__sev(); __wfe()Inspecting the code for
best_effort_wfe_or_timeoutin pico-sdk 2.0.0, there's a new bit:This sev/wfe pair will clear any existing event flag. But what if that event was from a user interrupt, and was the event the user was waiting for? Eg:
In principle (I don't have code to show this behaviour) the
__sev()from themy_irq_handlerwill be cleared by thebest_effort_wfe_or_timeoutand that latter function will wait the entire 10ms if no other IRQs come in.ldaexbandstrexbset the event flagAccording to the datasheet for RP2350:
From my testing it seems that executing a pair of
ldaexbandstrexbinstructions on RP2350 does indeed do an effective__sev(). This meansspin_lock_blocking()sets the event flag, and hence any function that calls this.It would be great to instead use the hardware spin locks on RP2350. According to the errata it's still possible to use some of them, those which don't have aliases for writable registers.
hardware_alarm_set_targetset the event flagIn MicroPython we implement low power idle by setting up a callback using
hardware_alarm_set_target(<id>, <timeout>)and then execute__wfe()to either wait for an event or the timeout. But this does not work on RP2350 becausehardware_alarm_set_target()sets the event flag, meaning that the subsequent__wfe()wakes immediately.It's unclear how to use
__wfe()effectively in the pico-sdk because of this issue of the event flag being set in many locations.Final thoughts
Ideally we'd be able to use
__wfe()to implement low-power idle on RP2350. If anyone has any pointers on how to do this that would be much appreciated.Regardless, I think there are a few bugs with
best_effort_wfe_or_timeoutas mentioned above.Regarding the ldaexb/strexb pair setting the processor event flag: I tested these instructions (the exact
SW_SPIN_LOCK_LOCKcode from this repo) on:On both these MCUs the ldaexb/strexb pair do not set the processor event flag on the processor executing these instructions.
So it seems to be a quirk of the RP2350 -- which has the processor event flags cross-wired between the CPUs -- that the ldaexb/strexb pair set the event flag on the current processor.
Speaking just to the monitor behaviour:
This is specified behaviour on Armv8-M, see DDI0553B.y section B9.3.1 (page 275):
We implemented this (useless) behaviour because it is strictly required by the spec. I haven't looked into the intricacies of the global monitor implementations on the systems you mentioned. It's possible this is their bug, and they missed that part of the spec. It's also possible they don't retire reservations on a PE's exclusive store to its own reservation, which is implementation-defined as per B9.3.2:
We do take this arc, as per 2.1.6.1 "Implementation-defined Monitor Behaviour" in our datasheet:
I think the intended use here is to leave a reservation open and get pinged when someone writes to it, but it causes unnecessary events on RMW.
fixed in develop
@Wren6991 thanks for the details regarding the silicon implementation of the RP2350.
Yes, it seems to be that this is the difference between the other MCUs I tested and the RP2350. In particular it looks like:
is the key difference, that the RP2350 will go from exclusive to open access (and hence generate an event) when the PE stores back to the marked address using
strexbin the spin lock code (thenmeaning that the same PE doing theldaexbgenerates the event).Anyway, thanks for fixing the issues with
best_effort_wfe_or_timeout()!Although it seems that
hardware_alarm_set_target()still sets the event flag (it uses spin locks) and so is still going to cause issues for MicroPython, we still won't be able to go into a CPU-gated mode.The suggestion is to use the alarm pool code which hopefully now works correctly in all cases
Yes, we will do that, move back to using the alarm pool code. But we will need a pico-sdk release for that 😄 do you know when that might be?
The implementation of mp_wfe_or_timeout always sets an alarm (actually it mostly modifies an existing alarm). If we avoid doing that we can avoid generating a sev? I was going to suggest a temporary change like this - but it's not fully tested yet
github.com/peterharperuk/micropython@9ed4a1d35b(inserting a soft timer calls pendsv_suspend which uses a recursive mutex which also uses spinlocks).
some time this month hopefully