ARMv8-M atomic bit operations? - Page 1
Forum section: electronics / general

author: mikerj
author_rank: Super Contributor
timestamp: March 09, 2021, 09:37:20 am
content: Is there any feature of the ARMv8-M core than could replace the missing bitband functionality for setting/clearing bits in SRAM atomically (i.e. without need to disable interrupts to prevent conflicts)?

author: gf
author_rank: Super Contributor
timestamp: March 09, 2021, 10:01:21 am
content: What about using atomic operations from the standard library?https://en.cppreference.com/w/c/atomichttps://en.cppreference.com/w/cpp/atomic/atomic[C++]

author: mikerj
author_rank: Super Contributor
timestamp: March 09, 2021, 12:36:17 pm
content: I'll take a look, thanks!

author: martinribelotta
author_rank: Regular Contributor
timestamp: March 09, 2021, 02:01:53 pm
content: In sort, armv8-m deprecates bitbanding and the only way to do atomic operation is via LDREX/STREX:https://developer.arm.com/documentation/dht0008/a/arm-synchronization-primitives/exclusive-accesses/ldrex-and-strexYou can use std::atomic with data types promotionable to int (line int, short or char but not long long):https://godbolt.org/z/YKeEe8Or can use cmsis primitives:https://www.keil.com/pack/doc/CMSIS/Core/html/group__intrinsic__CPU__gr.html#ga3c74d923529f664eda099d1b2668b3c1PD: If you use long long (orint64_t) the compiler need to call libgcc function to operate in large-than-a-register data type.

author: abyrvalg
author_rank: Frequent Contributor
timestamp: March 10, 2021, 08:06:03 am
content: Looks like LDREX/STREX are quite different from “disable interrupts, read-modify-write, enable interrupts”. You’ll need to check the result of STREX every time to see if it succeeded or the sequence was interrupted (and retry it?).

author: Nominal Animal
author_rank: Super Contributor
timestamp: March 10, 2021, 08:47:00 am
content: When a specific atomic operation is not provided natively in the hardware, there are two common approaches for their implementation: Compare-And-Swap (CAS) and Load-Linked Store-Conditional (LL/SC). ARMv8-M implements the latter, using exclusive access loads and stores.The difference is that in CAS, the hardware supports an atomic compare-and-swap operation, where the contents of the memory is modified atomically, if the previous contents match; otherwise the contents are unmodified. In LL/SC, the linked load "reserves" the memory, with the associated store to the same address succeeds only if the memory hasn't been modified since the linked load; and the store updates the memory contents atomically. They are just two different ways to solve the same problem, and both have their benefits and downsides, so I expect to see both in future architectures also.A CAS atomic operation loop in C istypeoldval, newval, *ptr;do {oldval = *ptr;/* This access does not need to be atomic */newval = OP(oldval);} while (!compare_and_swap(ptr, oldval, newval));The equivalent load-linked, store-conditional loop in C istypeoldval, newval, *ptr;do {oldval = load_linked_type(ptr);newval = OP(oldval);} while (!store_conditional(ptr, newval));/* Linked store; may fail */GCC produces the appropriate one given the hardware architecture, but if you want to, you can implement your own. (Emphasis on GCC; not the standard C or C++ library used.)In particular, you can useGCC atomic built-insfor atomic accesses of native word size, and for smaller units on most hardware architectures, and it will use either atomic instructions if provided by the hardware (or hardware guarantees), or one of the above loops, depending on the hardware architecture. That means that such code tends to be quite portable between hardware architectures, which is nice.Simply put, if you want to clear bits inmaskat*ptr, use__atomic_fetch_and(ptr, ~mask, __ATOMIC_SEQ_CST);To set those bits,__atomic_fetch_or(ptr,mask, __ATOMIC_SEQ_CST);and to flip those bits,__atomic_fetch_xor(ptr,mask, __ATOMIC_SEQ_CST);If you do not use the result value for anything,__atomic_fetch_op()are equivalent to__atomic_op_fetch(). The former return the value before the operation, and the latter after the modification (oldvalandnewval, respectively, in the above shown loops).In particular,oldval= __atomic_fetch_and(ptr, 0, __ATOMIC_SEQ_CST);is a very useful way to clear a flag to zero atomically, while obtaining the value it had when it was cleared.

author: newbrain
author_rank: Super Contributor
timestamp: March 10, 2021, 10:22:12 am
content: Quote from: martinribelotta on March 09, 2021, 02:01:53 pmJust as a reference, here is theC version(since C11).Nice to see that exactly the same code is generated.References (C11 standard):6.7.2.4 Atomic type specifiers6.7.3 Type qualifiers7.17 Atomics <stdatomic.h>All theatomic typespecific descriptions (e.g. for ++ or = operators).

author: martinribelotta
author_rank: Regular Contributor
timestamp: March 10, 2021, 02:18:49 pm
content: Quote from: abyrvalg on March 10, 2021, 08:06:03 amExactly, as Nominal Animal explained, the STREX can be fail if another STR(or STREX) was happend between LDREX/STREX (due to an interrupt or task switching). In this case, the "atomic" operation fail and try to restart.In my experience, LL/SC is more general than the LDR/STR exclusive in ARMv7/v8. You can implement an entire algorithm (like fifo, pipes etc) using LL/SC semantic, but LDREX/STREX dont guarantee that the guard is byte (or even word) atomic (you may align the semaphore variable in 4K bounds or simular)You can see the recomendation here:https://developer.arm.com/documentation/dht0008/a/CJAGCFAFQuoteIn theory, the ARMv8m implements word aligned monitor granularity enabling the usage of LDREX/STREX as complete LLSC semantic but I can't confir it.

author: mikerj
author_rank: Super Contributor
timestamp: March 10, 2021, 08:44:59 pm
content: Thanks all for some really useful information, particularly Nominal Animal's description of the CAS and LL/SS process.