Cover image for ARM Cache Coherency

ARM Cache Coherency

Words 3k
Views
Visitors
Timeline

Timeline

2025-10-26

init

This article introduces the basic concepts and evolution of cache coherence under the ARM architecture, discusses different solutions for maintaining coherence in software and hardware, and provides an in-depth analysis of the working principles and operation examples of coherence protocols such as MESI, as well as solutions for the cache false sharing problem.

Reference documents:

Why is cache coherence needed?

  • Different levels of cache in the system have different data backups, for example, each CPU core hasL1 cache

Different levels of cache in the system have different data backups
Different levels of cache in the system have different data backups

  • Cache coherence focuses on the consistency issue of the same data across multiple caches and memory, the methods to solve cache coherence are mainlybus snooping protocol, for exampleMESIprotocol
  • Examples that need to pay attention to cache coherence:
    • Using DMA in drivers(data cache and memory are inconsistent)
    • Self-modifying code(data in the data cache may be newer than in the instruction cache)
    • Modified page tables(data stored in the TLB may be outdated)

ARMofcacheEvolution of coherence

ARM cache coherence evolution
ARM cache coherence evolution

  • Cortex-A8 is a single-core architecture, so there is no inter-core cache coherence issue, but there is a coherence issue between DMA and cache

  • The multi-core version of Cortex-A9 (MPCore) has inter-core cache coherence issues, and the common practice is to implement a hardwareMESIprotocol

  • Cortex-A15 introduced the big.LITTLE architecture (big.LITTLE), for example, one cluster is all big cores and the other is all small cores, thereforecache coherence is also required between clusters, requiringAMBA Coherency Extensionto handle, there are ready-made ones in ARMIP(in IC design , IP core = a circuit module that has been designed and verified, which can be directly reused as a ‘building block’) can be used, such asCCI-400andCCI-500

  • Single-core processor (Cortex-A8)

    • Single core, no cache coherency issues
    • Cache management instructions only act on a single core
  • Multi-core processor (Cortex-A9 MP and later processors)

    • Hardware supports cache coherency
    • Cache management instructions are broadcast to other CPU cores

Cache coherency of multi-core processors
Cache coherency of multi-core processors

Cortex-A72 processor
Cortex-A72 processor

System-level cache coherency

  • System cache coherency requires an internal cache coherency bus (cache coherent interconnect)
    • AMBA 4Protocols includeACE(AXI Coherency Extensions)
    • AMBA 5Protocols includeCHI

System-level cache coherency
System-level cache coherency

Solutions for cache coherency

  1. Disable cache

    • Advantages: Simple
    • Disadvantages: Low performance, increased power consumption
  2. Software-maintained cache coherency

    • Advantages: Simple hardware RTL implementation
    • Disadvantages:
      • Increased software complexity. Software needs to manually clean/flush cache or invalidate cache
      • Increased debugging difficulty
      • degrade performance and increase power consumption
  3. hardware maintaining cache coherence

    MESIprotocol to maintainmulti-core cache coherenceACE interfaceto implement system-level cache coherence

    • Advantages: transparent to software
    • Disadvantages: increases the difficulty and complexity of hardware RTL implementation

Cache coherence between multiple cores

  • Reasons why multi-core CPUs generate cache coherence issues:the same memory data has multiple different copies in the L1 caches of multiple CPU cores, leading to data inconsistency

  • The key to maintaining cache coherence istracking the state of each cache line, and updating the state of the cache line in the caches of different CPU cores based on the processor’s read/write operations and corresponding bus transactions, thereby maintaining cache coherence

Cache Coherence Protocol

  • Snoopy protocol (snooping protocol), each cache must snoop or be snooped on the bus activities of other caches

  • Directory protocol (directory protocol), globally unified management of cache states

  • MESIProtocol:

    • In 1983,James GoodmanproposedWrite-Oncethe bus snoopy protocol, which later evolved into the most popularMESIprotocol
    • All bus transactions are visible to all other units in the system, because the bus is abroadcast-based communicationmedium, and thus can be snooped by each processor’s cache

Bus snooping and broadcasting
Bus snooping and broadcasting

Snoop control unitunit implementationBus snooping and broadcasting

The L1 cache of each CPU also implements the bus snooping function

MESI protocol

  • Each cache line has four states
    • Modified (Modified)
    • Exclusive (Exclusive)
    • Shared (Shared)
    • Invalid (Invalid)

The four states of the MESI protocol
The four states of the MESI protocol

  • ModifyMand Exclusive statesEcache lines, the data is exclusive. The difference is that the data in the Modified state is dirty and inconsistent with memory, while the data in the Exclusive state is clean and consistent with memory. Dirty cache lines are written back to memory, and then the state changes to Exclusive.
  • Shared stateScache lines, the data is shared with other caches, and only clean data can be shared by multiple caches
  • Istate indicates that this cache line is invalid

MESI operations

MESI operations
MESI operations

MESI state diagram

MESI state diagram
MESI state diagram

MESI primarily addressesConsistency between local caches in each CPUProblem

Cache consistency problem of local cache lines in each CPU
Cache consistency problem of local cache lines in each CPU

Explanation of MESI M state
Explanation of MESI M state

Explanation of MESI E state and S state
Explanation of MESI E state and S state

Explanation of MESI I state
Explanation of MESI I state

An example of MESI protocol analysis

  • Assume there are 4 CPUs in the system, each CPU has its own L1 cache, and they all want to access data A at the same address, with a size of 64 bytes.
    • At time T0: The L1 caches of the 4 CPUs have not cached data A, and the cache line state is I (Invalid)
    • At time T1: CPU0 takes the lead in initiating an access operation for data A
    • At time T2: CPU1 also initiates a read operation
    • At time T3: The program on CPU2 wants to modify the data in data A
  • Please analyze the changes in MESI states during the above process

T0 Moment The L1 caches of the 4 CPUs have not cached data A, and the cache line state is I

T0 Moment
T0 Moment

Time T1 CPU0 takes the lead in initiating an access operation for data A

Time T1
Time T1

Time T2 CPU1 also initiates a read operation

Time T2
Time T2

T3 Moment The program on CPU2 wants to modify the data in data A

T3 Moment
T3 Moment

Cache False Sharing

  • If multiple processors simultaneously access different data in a cache line, it brings performance issues
  • For example: Suppose thread 0 on CPU0 wants to access and update the x member in the struct data data structure, and similarly thread 1 on CPU1 wants to access and update the y member in the struct data data structure, where the x and y members are cached in the same cache line.

An example of False Sharing
An example of False Sharing

Analysis:

T0 Moment
T0 Moment

Time T1
Time T1

Time T2
Time T2

T4 Moment
T4 Moment

T5 Moment
T5 Moment

After that, it will continuously repeat between T4 and T5, contending for the cache line, constantly invalidating the other’s cache line, triggering cache write-back to memory.

Solution

  • The solution to cache false sharing isto place data operated by multiple threads in different cache lines, which can usually be achieved bycache line padding technologyorcache line alignment technology, which means aligning data structures to cache lines and filling up a cache line size as much as possible.
  • The following code defines a counter_s data structure, whose starting address is aligned to the size of the cache line, and the members of the data structure are filled via pad[4]. In this way, the counter_s size is exactly the size of a cache line, 64 bytes, and its starting address is also cache line aligned

pad
pad

Try to let counter_s exclusively occupy a cache line without sharing a cache line with other data structures

MOESI protocol

The ARMv8 processors use the MOESI protocol

  • Modified

    • The most up-to-date version of the cache line is within this cache.

    • No other copies of the memory location exist within other caches.

    • The contents of the cache line are no longer coherent with main memory.

  • Owned

    • This describes a line that is dirty and in possibly more than one cache.

    • A cache line in the owned state holds the most recent, correct copy of the data.

    • Only one core can hold the data in the owned state. The other cores can hold the data in the shared state.

  • Exclusive

    • The cache line is present in this cache and coherent with main memory.
    • No other copies of the memory location exist within other caches.
  • Shared

    • The cache line is present in this cache and is not necessarily coherent with memory, given that the definition of Owned allows for a dirty line to be duplicated into shared lines.

    • It will, however, have the most recent version of the data.

    • Copies of it can also exist in other caches in the coherency scheme.

  • Invalid

    • The cache line is invalid.

The following rules apply for the standard implementation of the protocol:

  • A write can only be performed if the cache line is in a Modified or Exclusive state. If it is
    in a Shared state, all other cached copies must be invalidated first. A write moves the line
    into a Modified state.
  • A cache can discard a shared line at any time, changing it to an Invalid state.
  • A Modified line is written back first.
  • If a cache holds a line in a Modified state, reads from other caches in the system receive
    the updated data from the cache. Conventionally, this is achieved by first writing the data
    to main memory and then changing the cache line to a Shared state, before performing a
    read.
  • A cache that has a line in an Exclusive state must move the line to a Shared state when
    another cache reads that line.
  • A Shared state might not be precise. If one cache discards a Shared line, another cache
    might not be aware that it can now move the line to an Exclusive state.

Snoop Control Unit(SCU)

The processor cluster contains a Snoop Control Unit (SCU) that contains duplicate copies of the tags stored in the individual L1 Data Caches. The cache coherency logic therefore:

  • Maintains coherency between L1 data caches.
  • Arbitrates accesses to L2 interfaces, for both instructions and data.
  • Has duplicated Tag RAMs to keep track of what data is allocated in each core’s data.

SCU
SCU

Each core in the figure has its own data and instruction caches. The cache coherence logic contains a local copy of the tags from the D cache. However,the instruction cache does not participate in coherence. There is 2-way communication between the data cache and the coherence logic. ARM multi-core processors also implement optimizations that can directly copy clean data and move dirty data between participating L1 caches without accessing and waiting for external memory. This activity is handled by the SCU in multi-core systems.

The Snoop Control Unit (SCU) maintains coherence between the L1 data caches of each core and is responsible for managing the following interconnect operations:

  • Arbitration.
  • Communication.
  • Cache-2-cache and system memory transfers.

The processor also provides these features to other system accelerators and non-cacheable DMA-driven peripherals to improve performance and reduce system-wide power consumption.

This system coherence also reduces the software complexity involved in maintaining software coherence within each operating system driver. Each core can be individually configured to participate or not participate in the data cache coherence management scheme. The SCU device inside the processor automatically maintains L1 data cache coherence between cores within the cluster.

Since executable code changes much less frequently,this feature does not extend to the L1 instruction cache. Coherence management is implemented using a protocol based on MOESI, optimized to reduce the number of external memory accesses. For coherence management to be effective for memory accesses, all of the following conditions must be true:

  • The SCU is enabled through control registers located in a private memory area. The SCU has configurable access control that limits which processors can
    configure it.
  • The MMU is enabled.
  • The accessed page is marked as Normal Shareable, with a cache policy of write-back, write-allocate. However, device and strongly-ordered memory are non-cacheable, and from the kernel’s perspective, write-through cache behaves similarly to uncached memory.

The SCU can only maintain coherence within a single cluster. If there are additional processors or other bus masters in the system, explicit software synchronization is required when they share memory with the MP block

Inter-system Cache Coherence

Inter-system Cache Coherence
Inter-system Cache Coherence

CoreLink CCI-400
CoreLink CCI-400

ARM’s CCI for the server market is CoreLink CCN

CoreLink Cache Coherent Network Family
CoreLink Cache Coherent Network Family

CCN-512
CCN-512

Example of reading data

T0 Moment
T0 Moment

Time T1
Time T1

Time T2
Time T2

T3 Moment
T3 Moment

Time T3 Another case
Time T3 Another case

Example of writing data

T0 Moment
T0 Moment

Time T1
Time T1

Time T2
Time T2

T3 Moment
T3 Moment

T4 Moment
T4 Moment

Case of Cache Coherence

Case 1: Avoiding cache false sharing

  • Some commonly used data structures are defined such thatData structures are aligned to the L1 cache. For example, use the following macro to align the starting address of the data structure to the L1 cache
1
#define cacheline_aligned __attribute__((__aligned__(L1_CACHE_BYTES)))
  • Frequently accessed members of a data structure can occupy a separate cache line, or related members are offset from each other within thecache lineto improve access efficiency. For example, the struct zone data structure usesZONE_PADDINGTechnique (Padding bytesmethod) to place frequently accessed members in different cache lines

    Frequently accessed members of a data structure can occupy a separate cache line
    Frequently accessed members of a data structure can occupy a separate cache line

Case 2: DMA cache coherency

DMA (Direct Memory Access) is direct memory access, which can read and write data directly from memory without CPU intervention during the transfer process

  • Reasons why DMA causes cache coherency issues:
    • DMA directly operates the system bus to read and write memory, and the CPU is unaware of it
    • IfThe memory address modified by DMA is cached in the CPU’s cache, so the CPU doesn’t know the memory data has been modified, and the CPU still accesses the old data in the cache, causing cache coherency issues

DMA Cache Coherency Issues
DMA Cache Coherency Issues

DMA cache coherency solutions

  • Hardware solution, requiresACE bus support(Consult SoC vendor)

  • Use non-cacheable memory for DMA transfers

    • Disadvantage: When DMA is not in use, CPU access to this buffer will result in performance degradation
  • Software intervention for cache coherency, software maintains cache coherency based on the direction of DMA data transfer

    • Case 1: Memory -> Device FIFO (Device such as network card,Read memory data to device FIFO via DMA)

      • Before DMA transfer, the CPU cache may have cached the memory data, requiring a cache clean/flush operation to write the cache contents to memory, because the CPU cache may contain the latest data.

      The CPU cache has the latest data, but DMA gets old data from memory
      The CPU cache has the latest data, but DMA gets old data from memory

    • Case 2: Device FIFO -> Memory (Device writes data to memory)

      • Before DMA transfer, the cache needs to be invalidated. Because the latest data is in the device FIFO at this time, the CPU cache data is outdated, and new data will be written later, so the invalidation operation is performed

      The CPU cache may still have useless data
      The CPU cache may still have useless data

Case 3: self-modifying code

  • The instruction cache and data cache are separate. The instruction cache is generally read-only.

  • The coherence problem between instruction cache and data cache. Instructions usually cannot be modified, but in some special cases, instructions are modified.

  • Self-modifying code modifies its own instructions during execution (to prevent software cracking, or to dynamically modify the program during gdb debugging), the process is as follows

    • Load the modified instructions into the data cache
    • The program (CPU) modifies the new instructions, and the data cache holds the latest instructions

    Problem:

    • The instruction cache still holds the old instructions, while the new instructions are still in the data cache

    Self modifying code
    Self modifying code

Solution approach

  • Use cache clean operation to write back the cache line data to memory
  • Use DSB instruction to ensure other observers see that the clean operation has completed
  • Invalidate the instruction cache
  • Use DSB instruction to ensure other observers see that the invalidation operation has completed
  • ISB instruction makes the program re-fetch instructions

ARMv8.6 Manual Chapter B2.4.4
ARMv8.6 Manual Chapter B2.4.4

Cache Experiment 2: false sharing

Experiment 2
Experiment 2

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
#include <pthread.h>
#include <stdio.h>
#include <sys/time.h>
#include <time.h>

struct data_with_false_sharing {

unsigned long x;
unsigned long y;

} __attribute__((__align__(64)));

struct padding {

char x[0]
} __attribute__((__align__(64)));

struct data_without_false_sharing {

unsigned long x;
struct padding _pad;
unsigned long y;
} __attribute__((__align__(64)));

#define MAX_LOOP 10000000000
void *access_data(void *param) {
unsigned long *data = (unsigned long *)param;
unsigned long i;
for (i = 0; i < MAX_LOOP; i++) {
*data = i;
}
}

int main(void) {
struct data_with_false_sharing data_wfs = {1, 2};
struct data_without_false_sharing data_wofs = {.x = 1, .y = 2};
pthread_t thread_1;
pthread_t thread_2;
unsigned long total_time;

struct timespec time_start, time_end;
clock_gettime(CLOCK_REALTIME, &time_start);
pthread_create(&thread_1, NULL, &access_data, (void *)&data_wfs.x);
pthread_create(&thread_2, NULL, &access_data, (void *)&data_wfs.y);
pthread_join(thread_1, NULL);
pthread_join(thread_2, NULL);
clock_gettime(CLOCK_REALTIME, &time_end);
total_time = (time_end.tv_sec - time_start.tv_sec) * 1000 +
(time_end.tv_nsec - time_start.tv_nsec) / 1000000;
printf("cache with false sharing: %lu ms \n", total_time);

clock_gettime(CLOCK_REALTIME, &time_start);
pthread_create(&thread_1, NULL, &access_data, (void *)&data_wofs.x);
pthread_create(&thread_2, NULL, &access_data, (void *)&data_wofs.y);
pthread_join(thread_1, NULL);
pthread_join(thread_2, NULL);
clock_gettime(CLOCK_REALTIME, &time_end);
total_time = (time_end.tv_sec - time_start.tv_sec) * 1000 +
(time_end.tv_nsec - time_start.tv_nsec) / 1000000;
printf("cache without false sharing: %lu ms \n", total_time);
}

Experiment 2 results (qemu)
Experiment 2 results (qemu)

Cache Experiment 3: flush cache experiment

Experiment 3
Experiment 3

Result:

Experiment 3 results
Experiment 3 results

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
.global get_cache_line_size
get_cache_line_size:
mrs x0, ctr_el0
ubfm x0, x0, #16, #19
mov x1, #4
lsl x0, x1, x0
ret

/*
flush_cache_range(start, end)
*/
.global flush_cache_range
flush_cache_range:
stp x29, x30, [sp, -16]!
// start
mov x8, x0
// end
mov x9, x1

bl get_cache_line_size
// x3 = cache_line_size -1
sub x3, x0, #1
// bit clear, x4 = x8 & (~x3)
// Align the start address down to the cache line boundary
bic x4, x8, x3
// Loop to clean cachelines
1:
dc civac, x4
add x4, x4, x0
cmp x4, x9
b.lo 1b

dsb ish

ldp x29, x30, [sp], 16

ret

  • ARM Architecture Reference Manual Armv8, for Armv8-A architecture profile

    • B2.4 Caches and memory hierarchy

    • D4.4 Cache Support

    • D5.11 Caches in a VMSAv8-64 implementation

  • ARM Cortex-A Series Programmer’s Guide for ARMv8-A

    • Chapter 11 Cache
  • ARM Cortex-A72 MPCore Processor Technical Reference Manual

    • 6: Level 1 Memory System
    • 7:Level 2 Memory System