mirror of
https://github.com/espressif/esp-idf.git
synced 2026-09-22 13:01:16 +03:00
Merge branch 'feature/cache_access_counters' into 'master'
feat(esp_hw_support): add cache access counter API See merge request espressif/esp-idf!50600
This commit is contained in:
@@ -69,7 +69,8 @@ if(NOT non_os_build)
|
||||
"port/${target}/esp_clk_tree.c"
|
||||
"spi_bus_lock.c"
|
||||
"heap_align_hw.c"
|
||||
"clk_utils.c")
|
||||
"clk_utils.c"
|
||||
"esp_cache_cnt.c")
|
||||
if(CONFIG_SOC_USB_OTG_SUPPORTED)
|
||||
list(APPEND srcs "usb_phy/usb_phy.c")
|
||||
endif()
|
||||
|
||||
164
components/esp_hw_support/esp_cache_cnt.c
Normal file
164
components/esp_hw_support/esp_cache_cnt.c
Normal file
@@ -0,0 +1,164 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/*
|
||||
* Chip-agnostic implementation of the cache access counter API, built on
|
||||
* top of the cache profile counter functions of hal/cache_ll.h and the
|
||||
* unit descriptor table in hal/cache_periph.h.
|
||||
*/
|
||||
|
||||
#include <inttypes.h>
|
||||
#include "esp_cache_cnt.h"
|
||||
#include "soc/soc_caps.h"
|
||||
|
||||
float esp_cache_cnt_miss_ratio(const esp_cache_cnt_data_t *data)
|
||||
{
|
||||
const uint32_t needed = ESP_CACHE_CNT_VALID_ACCESSES | ESP_CACHE_CNT_VALID_LINE_FILLS;
|
||||
if ((data->valid_mask & needed) != needed || data->accesses == 0) {
|
||||
return 0.0f;
|
||||
}
|
||||
return (float)data->line_fills / (float)data->accesses;
|
||||
}
|
||||
|
||||
#if SOC_CACHE_CNT_SUPPORTED
|
||||
|
||||
#include "hal/cache_ll.h"
|
||||
|
||||
size_t esp_cache_cnt_num_units(void)
|
||||
{
|
||||
return SOC_CACHE_CNT_UNITS_NUM;
|
||||
}
|
||||
|
||||
esp_err_t esp_cache_cnt_get_unit_info(size_t unit, esp_cache_cnt_unit_info_t *out)
|
||||
{
|
||||
if (unit >= SOC_CACHE_CNT_UNITS_NUM || out == NULL) {
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
const cache_profile_counter_unit_t *desc = &cache_periph_profile_counter_units[unit];
|
||||
out->name = desc->name;
|
||||
out->cache_level = desc->level;
|
||||
out->core_id = desc->core_id;
|
||||
out->traffic_type = desc->traffic;
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t esp_cache_cnt_start(void)
|
||||
{
|
||||
cache_ll_clear_profile_counter();
|
||||
cache_ll_enable_profile_counter(true);
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t esp_cache_cnt_stop(void)
|
||||
{
|
||||
cache_ll_enable_profile_counter(false);
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t esp_cache_cnt_clear(void)
|
||||
{
|
||||
cache_ll_clear_profile_counter();
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t esp_cache_cnt_get(size_t unit, esp_cache_cnt_data_t *out)
|
||||
{
|
||||
if (unit >= SOC_CACHE_CNT_UNITS_NUM || out == NULL) {
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
*out = (esp_cache_cnt_data_t) { 0 };
|
||||
if (cache_ll_get_profile_counter(unit, CACHE_PROFILE_COUNTER_HIT, &out->accesses)) {
|
||||
out->valid_mask |= ESP_CACHE_CNT_VALID_ACCESSES;
|
||||
}
|
||||
if (cache_ll_get_profile_counter(unit, CACHE_PROFILE_COUNTER_MISS, &out->stall_events)) {
|
||||
out->valid_mask |= ESP_CACHE_CNT_VALID_STALL_EVENTS;
|
||||
}
|
||||
if (cache_ll_get_profile_counter(unit, CACHE_PROFILE_COUNTER_CONFLICT, &out->conflicts)) {
|
||||
out->valid_mask |= ESP_CACHE_CNT_VALID_CONFLICTS;
|
||||
}
|
||||
if (cache_ll_get_profile_counter(unit, CACHE_PROFILE_COUNTER_NXTLVL_RD, &out->line_fills)) {
|
||||
out->valid_mask |= ESP_CACHE_CNT_VALID_LINE_FILLS;
|
||||
}
|
||||
if (cache_ll_get_profile_counter(unit, CACHE_PROFILE_COUNTER_NXTLVL_WR, &out->writebacks)) {
|
||||
out->valid_mask |= ESP_CACHE_CNT_VALID_WRITEBACKS;
|
||||
}
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
static void print_counter(FILE *out, const esp_cache_cnt_data_t *data, uint32_t flag, uint32_t value)
|
||||
{
|
||||
if (data->valid_mask & flag) {
|
||||
fprintf(out, " %12" PRIu32, value);
|
||||
} else {
|
||||
fprintf(out, " %12s", "-");
|
||||
}
|
||||
}
|
||||
|
||||
esp_err_t esp_cache_cnt_dump(FILE *out)
|
||||
{
|
||||
if (out == NULL) {
|
||||
out = stdout;
|
||||
}
|
||||
fprintf(out, "%-20s %12s %12s %12s %12s %10s\n",
|
||||
"unit", "accesses", "line fills", "writebacks", "conflicts", "miss rate");
|
||||
for (size_t unit = 0; unit < SOC_CACHE_CNT_UNITS_NUM; unit++) {
|
||||
esp_cache_cnt_unit_info_t info;
|
||||
esp_cache_cnt_data_t data;
|
||||
esp_cache_cnt_get_unit_info(unit, &info);
|
||||
esp_cache_cnt_get(unit, &data);
|
||||
fprintf(out, "%-20s", info.name);
|
||||
print_counter(out, &data, ESP_CACHE_CNT_VALID_ACCESSES, data.accesses);
|
||||
print_counter(out, &data, ESP_CACHE_CNT_VALID_LINE_FILLS, data.line_fills);
|
||||
print_counter(out, &data, ESP_CACHE_CNT_VALID_WRITEBACKS, data.writebacks);
|
||||
print_counter(out, &data, ESP_CACHE_CNT_VALID_CONFLICTS, data.conflicts);
|
||||
fprintf(out, " %9.2f%%\n", 100.0f * esp_cache_cnt_miss_ratio(&data));
|
||||
}
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
#else // !SOC_CACHE_CNT_SUPPORTED
|
||||
|
||||
size_t esp_cache_cnt_num_units(void)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
esp_err_t esp_cache_cnt_get_unit_info(size_t unit, esp_cache_cnt_unit_info_t *out)
|
||||
{
|
||||
(void) unit;
|
||||
(void) out;
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
|
||||
esp_err_t esp_cache_cnt_start(void)
|
||||
{
|
||||
return ESP_ERR_NOT_SUPPORTED;
|
||||
}
|
||||
|
||||
esp_err_t esp_cache_cnt_stop(void)
|
||||
{
|
||||
return ESP_ERR_NOT_SUPPORTED;
|
||||
}
|
||||
|
||||
esp_err_t esp_cache_cnt_clear(void)
|
||||
{
|
||||
return ESP_ERR_NOT_SUPPORTED;
|
||||
}
|
||||
|
||||
esp_err_t esp_cache_cnt_get(size_t unit, esp_cache_cnt_data_t *out)
|
||||
{
|
||||
(void) unit;
|
||||
(void) out;
|
||||
return ESP_ERR_NOT_SUPPORTED;
|
||||
}
|
||||
|
||||
esp_err_t esp_cache_cnt_dump(FILE *out)
|
||||
{
|
||||
(void) out;
|
||||
return ESP_ERR_NOT_SUPPORTED;
|
||||
}
|
||||
|
||||
#endif // !SOC_CACHE_CNT_SUPPORTED
|
||||
168
components/esp_hw_support/include/esp_cache_cnt.h
Normal file
168
components/esp_hw_support/include/esp_cache_cnt.h
Normal file
@@ -0,0 +1,168 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
#include <stdio.h>
|
||||
#include "esp_err.h"
|
||||
#include "hal/cache_types.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Cache access counter API.
|
||||
*
|
||||
* The set of counters differs between chips: the number of cache levels, the
|
||||
* number of request buses per cache, and which counters exist per bus all
|
||||
* vary. Instead of a fixed list of caches, the API exposes a chip-defined
|
||||
* list of counter "units". Each unit is one set of counters observing one
|
||||
* traffic stream (e.g. "instruction fetches from core 0 into the L1 cache").
|
||||
* Applications enumerate the units at runtime with esp_cache_cnt_num_units()
|
||||
* and esp_cache_cnt_get_unit_info(), so they keep working when a new chip
|
||||
* adds or removes units.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @brief Description of one counter unit.
|
||||
*/
|
||||
typedef struct {
|
||||
const char *name; /*!< Short human-readable name, e.g. "l1-icache-core0" */
|
||||
uint8_t cache_level; /*!< Cache level the counters belong to, counting from the CPU.
|
||||
On most chips there is a single level (the flash/PSRAM cache);
|
||||
on the ESP32-P4, level 1 is the L1 cache in front of internal
|
||||
memory and level 2 is the flash/PSRAM cache. */
|
||||
cache_profile_traffic_t traffic_type; /*!< Kind of traffic observed */
|
||||
int8_t core_id; /*!< Core the traffic originates from, or -1 if unknown/mixed */
|
||||
} esp_cache_cnt_unit_info_t;
|
||||
|
||||
/**
|
||||
* @name Flags for esp_cache_cnt_data_t::valid_mask
|
||||
*
|
||||
* Not every counter exists for every unit (e.g. instruction buses have no
|
||||
* write-back counter). A field of esp_cache_cnt_data_t is only meaningful if
|
||||
* the corresponding flag is set.
|
||||
* @{
|
||||
*/
|
||||
#define ESP_CACHE_CNT_VALID_ACCESSES (1 << 0)
|
||||
#define ESP_CACHE_CNT_VALID_STALL_EVENTS (1 << 1)
|
||||
#define ESP_CACHE_CNT_VALID_CONFLICTS (1 << 2)
|
||||
#define ESP_CACHE_CNT_VALID_LINE_FILLS (1 << 3)
|
||||
#define ESP_CACHE_CNT_VALID_WRITEBACKS (1 << 4)
|
||||
/** @} */
|
||||
|
||||
/**
|
||||
* @brief Counter values for one unit.
|
||||
*
|
||||
* Note that the hardware "hit" and "miss" counters do not directly hold the
|
||||
* number of hit and missed accesses:
|
||||
* - accesses: the hit counter increments once for every access that completes,
|
||||
* whether or not it had to wait for a line fill first.
|
||||
* - stall_events: the miss counter increments repeatedly while an access is
|
||||
* stalled on a miss, so it grows roughly with the total miss latency. This is
|
||||
* only useful as a relative measure.
|
||||
* - line_fills: the next-level read counter increments once per line fetched
|
||||
* from the next level, so it is the true miss count.
|
||||
*
|
||||
* The miss ratio of a cache is therefore line_fills / accesses.
|
||||
*/
|
||||
typedef struct {
|
||||
uint32_t valid_mask; /*!< Bitwise OR of ESP_CACHE_CNT_VALID_* flags for the fields below */
|
||||
uint32_t accesses; /*!< Completed accesses (hardware hit counter) */
|
||||
uint32_t stall_events; /*!< Miss stall events; grows with total miss latency,
|
||||
NOT the number of missed accesses */
|
||||
uint32_t conflicts; /*!< Conflicts between requesters on this cache */
|
||||
uint32_t line_fills; /*!< Lines fetched from the next level (true miss count) */
|
||||
uint32_t writebacks; /*!< Lines written back to the next level. Only present for data
|
||||
traffic on chips with a write-back cache (PSRAM support,
|
||||
see SOC_CACHE_WRITEBACK_SUPPORTED). */
|
||||
} esp_cache_cnt_data_t;
|
||||
|
||||
/**
|
||||
* @brief Number of counter units on this chip.
|
||||
*
|
||||
* @return Number of units; 0 if the chip has no cache access counters.
|
||||
*/
|
||||
size_t esp_cache_cnt_num_units(void);
|
||||
|
||||
/**
|
||||
* @brief Get the description of a counter unit.
|
||||
*
|
||||
* @param unit Unit index, 0 to esp_cache_cnt_num_units() - 1
|
||||
* @param[out] out Unit description
|
||||
*
|
||||
* @return
|
||||
* - ESP_OK on success
|
||||
* - ESP_ERR_INVALID_ARG if unit is out of range or out is NULL
|
||||
*/
|
||||
esp_err_t esp_cache_cnt_get_unit_info(size_t unit, esp_cache_cnt_unit_info_t *out);
|
||||
|
||||
/**
|
||||
* @brief Clear and enable all cache access counters.
|
||||
*
|
||||
* @return
|
||||
* - ESP_OK on success
|
||||
* - ESP_ERR_NOT_SUPPORTED if the target has no cache access counters
|
||||
*/
|
||||
esp_err_t esp_cache_cnt_start(void);
|
||||
|
||||
/**
|
||||
* @brief Disable all cache access counters. Counter values are retained.
|
||||
*
|
||||
* @return
|
||||
* - ESP_OK on success
|
||||
* - ESP_ERR_NOT_SUPPORTED if the target has no cache access counters
|
||||
*/
|
||||
esp_err_t esp_cache_cnt_stop(void);
|
||||
|
||||
/**
|
||||
* @brief Reset all cache access counters to zero. Counting state is not changed.
|
||||
*
|
||||
* @return
|
||||
* - ESP_OK on success
|
||||
* - ESP_ERR_NOT_SUPPORTED if the target has no cache access counters
|
||||
*/
|
||||
esp_err_t esp_cache_cnt_clear(void);
|
||||
|
||||
/**
|
||||
* @brief Read the current counter values for the given unit.
|
||||
*
|
||||
* @param unit Unit index, 0 to esp_cache_cnt_num_units() - 1
|
||||
* @param[out] out Counter values
|
||||
*
|
||||
* @return
|
||||
* - ESP_OK on success
|
||||
* - ESP_ERR_INVALID_ARG if unit is out of range or out is NULL
|
||||
* - ESP_ERR_NOT_SUPPORTED if the target has no cache access counters
|
||||
*/
|
||||
esp_err_t esp_cache_cnt_get(size_t unit, esp_cache_cnt_data_t *out);
|
||||
|
||||
/**
|
||||
* @brief Miss ratio (0.0 to 1.0) computed from a set of counter values.
|
||||
*
|
||||
* @param data Counter values obtained from esp_cache_cnt_get()
|
||||
*
|
||||
* @return Miss ratio; 0.0 if the unit does not provide the counters needed
|
||||
* to compute it.
|
||||
*/
|
||||
float esp_cache_cnt_miss_ratio(const esp_cache_cnt_data_t *data);
|
||||
|
||||
/**
|
||||
* @brief Print a table with the current values of all cache access counters.
|
||||
*
|
||||
* @param out Output stream; if NULL, print to stdout
|
||||
*
|
||||
* @return
|
||||
* - ESP_OK on success
|
||||
* - ESP_ERR_NOT_SUPPORTED if the target has no cache access counters
|
||||
*/
|
||||
esp_err_t esp_cache_cnt_dump(FILE *out);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -46,6 +46,10 @@ else()
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if(EXISTS "${CMAKE_CURRENT_LIST_DIR}/${target}/cache_periph.c")
|
||||
list(APPEND srcs "${target}/cache_periph.c")
|
||||
endif()
|
||||
|
||||
if(NOT esp_tee_build AND NOT BOOTLOADER_BUILD)
|
||||
list(APPEND srcs "color_hal.c")
|
||||
|
||||
|
||||
41
components/hal/esp32c5/cache_periph.c
Normal file
41
components/hal/esp32c5/cache_periph.c
Normal file
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include "soc/cache_reg.h"
|
||||
#include "hal/cache_periph.h"
|
||||
|
||||
/*
|
||||
* ESP32-C5 cache profile counter units.
|
||||
*
|
||||
* Single cache level shared by instructions and data (the flash/PSRAM
|
||||
* cache, 32-byte lines), with two request buses: bus0 carries instruction
|
||||
* fetches, bus1 carries data accesses. Counter semantics match the
|
||||
* ESP32-P4. The hit/miss/conflict counters use the BUS0/BUS1 register
|
||||
* names while the corresponding next-level counters use the DBUS0/DBUS1
|
||||
* names. Only the CPU request buses are exposed here.
|
||||
*/
|
||||
|
||||
const cache_profile_counter_unit_t cache_periph_profile_counter_units[SOC_CACHE_CNT_UNITS_NUM] = {
|
||||
{
|
||||
.name = "l1-cache-ibus", .level = 1, .traffic = CACHE_PROFILE_TRAFFIC_INST, .core_id = 0,
|
||||
.counter_reg = {
|
||||
[CACHE_PROFILE_COUNTER_HIT] = CACHE_L1_BUS0_ACS_HIT_CNT_REG,
|
||||
[CACHE_PROFILE_COUNTER_MISS] = CACHE_L1_BUS0_ACS_MISS_CNT_REG,
|
||||
[CACHE_PROFILE_COUNTER_CONFLICT] = CACHE_L1_BUS0_ACS_CONFLICT_CNT_REG,
|
||||
[CACHE_PROFILE_COUNTER_NXTLVL_RD] = CACHE_L1_DBUS0_ACS_NXTLVL_RD_CNT_REG,
|
||||
},
|
||||
},
|
||||
{
|
||||
.name = "l1-cache-dbus", .level = 1, .traffic = CACHE_PROFILE_TRAFFIC_DATA, .core_id = 0,
|
||||
.counter_reg = {
|
||||
[CACHE_PROFILE_COUNTER_HIT] = CACHE_L1_BUS1_ACS_HIT_CNT_REG,
|
||||
[CACHE_PROFILE_COUNTER_MISS] = CACHE_L1_BUS1_ACS_MISS_CNT_REG,
|
||||
[CACHE_PROFILE_COUNTER_CONFLICT] = CACHE_L1_BUS1_ACS_CONFLICT_CNT_REG,
|
||||
[CACHE_PROFILE_COUNTER_NXTLVL_RD] = CACHE_L1_DBUS1_ACS_NXTLVL_RD_CNT_REG,
|
||||
[CACHE_PROFILE_COUNTER_NXTLVL_WR] = CACHE_L1_DBUS1_ACS_NXTLVL_WR_CNT_REG,
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -13,6 +13,7 @@
|
||||
#include "soc/cache_struct.h"
|
||||
#include "soc/ext_mem_defs.h"
|
||||
#include "rom/cache.h"
|
||||
#include "hal/cache_periph.h"
|
||||
#include "hal/cache_types.h"
|
||||
#include "hal/assert.h"
|
||||
#include "esp32c5/rom/cache.h"
|
||||
@@ -414,6 +415,58 @@ static inline uint32_t cache_ll_l1_get_access_error_intr_status(uint32_t cache_i
|
||||
return CACHE.l1_cache_acs_fail_int_st.val & mask;
|
||||
}
|
||||
|
||||
/*----------------------------------------------------------------------------
|
||||
Cache Profile Counter Related
|
||||
-----------------------------------------------------------------------------*/
|
||||
#define CACHE_LL_PROFILE_CNT_ENA_MASK (CACHE_L1_BUS0_CNT_ENA | CACHE_L1_BUS1_CNT_ENA)
|
||||
#define CACHE_LL_PROFILE_CNT_CLR_MASK (CACHE_L1_BUS0_CNT_CLR | CACHE_L1_BUS1_CNT_CLR)
|
||||
|
||||
/**
|
||||
* @brief Enable or disable the cache profile counters
|
||||
*
|
||||
* @param ena True to enable, false to disable
|
||||
*/
|
||||
__attribute__((always_inline))
|
||||
static inline void cache_ll_enable_profile_counter(bool ena)
|
||||
{
|
||||
if (ena) {
|
||||
REG_SET_BIT(CACHE_L1_CACHE_ACS_CNT_CTRL_REG, CACHE_LL_PROFILE_CNT_ENA_MASK);
|
||||
} else {
|
||||
REG_CLR_BIT(CACHE_L1_CACHE_ACS_CNT_CTRL_REG, CACHE_LL_PROFILE_CNT_ENA_MASK);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Reset all cache profile counters to zero
|
||||
*/
|
||||
__attribute__((always_inline))
|
||||
static inline void cache_ll_clear_profile_counter(void)
|
||||
{
|
||||
/* clear bits are write-to-trigger and self-clearing */
|
||||
REG_SET_BIT(CACHE_L1_CACHE_ACS_CNT_CTRL_REG, CACHE_LL_PROFILE_CNT_CLR_MASK);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Read one counter of a cache profile counter unit
|
||||
*
|
||||
* @param unit Unit index, 0 to SOC_CACHE_CNT_UNITS_NUM - 1
|
||||
* @param counter Counter to read
|
||||
* @param[out] value Counter value, only written if the counter exists
|
||||
*
|
||||
* @return True if the unit has this counter, false otherwise
|
||||
*/
|
||||
__attribute__((always_inline))
|
||||
static inline bool cache_ll_get_profile_counter(int unit, cache_profile_counter_t counter, uint32_t *value)
|
||||
{
|
||||
HAL_ASSERT(unit < SOC_CACHE_CNT_UNITS_NUM);
|
||||
uint32_t reg = cache_periph_profile_counter_units[unit].counter_reg[counter];
|
||||
if (reg == 0) {
|
||||
return false;
|
||||
}
|
||||
*value = REG_READ(reg);
|
||||
return true;
|
||||
}
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
40
components/hal/esp32c6/cache_periph.c
Normal file
40
components/hal/esp32c6/cache_periph.c
Normal file
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include "soc/extmem_reg.h"
|
||||
#include "hal/cache_periph.h"
|
||||
|
||||
/*
|
||||
* ESP32-C6 cache profile counter units.
|
||||
*
|
||||
* Single cache level shared by instructions and data (the flash cache,
|
||||
* 32-byte lines), with one instruction bus and one data bus. Counter
|
||||
* semantics match the ESP32-P4. There is one next-level counter per bus
|
||||
* (no read/write split), mapped here to line_fills. The cache is
|
||||
* read-only (no PSRAM, no write-back — see SOC_CACHE_WRITEBACK_SUPPORTED),
|
||||
* so there are no write-back counters.
|
||||
*/
|
||||
|
||||
const cache_profile_counter_unit_t cache_periph_profile_counter_units[SOC_CACHE_CNT_UNITS_NUM] = {
|
||||
{
|
||||
.name = "l1-cache-ibus", .level = 1, .traffic = CACHE_PROFILE_TRAFFIC_INST, .core_id = 0,
|
||||
.counter_reg = {
|
||||
[CACHE_PROFILE_COUNTER_HIT] = EXTMEM_L1_IBUS_ACS_HIT_CNT_REG,
|
||||
[CACHE_PROFILE_COUNTER_MISS] = EXTMEM_L1_IBUS_ACS_MISS_CNT_REG,
|
||||
[CACHE_PROFILE_COUNTER_CONFLICT] = EXTMEM_L1_IBUS_ACS_CONFLICT_CNT_REG,
|
||||
[CACHE_PROFILE_COUNTER_NXTLVL_RD] = EXTMEM_L1_IBUS_ACS_NXTLVL_CNT_REG,
|
||||
},
|
||||
},
|
||||
{
|
||||
.name = "l1-cache-dbus", .level = 1, .traffic = CACHE_PROFILE_TRAFFIC_DATA, .core_id = 0,
|
||||
.counter_reg = {
|
||||
[CACHE_PROFILE_COUNTER_HIT] = EXTMEM_L1_DBUS_ACS_HIT_CNT_REG,
|
||||
[CACHE_PROFILE_COUNTER_MISS] = EXTMEM_L1_DBUS_ACS_MISS_CNT_REG,
|
||||
[CACHE_PROFILE_COUNTER_CONFLICT] = EXTMEM_L1_DBUS_ACS_CONFLICT_CNT_REG,
|
||||
[CACHE_PROFILE_COUNTER_NXTLVL_RD] = EXTMEM_L1_DBUS_ACS_NXTLVL_CNT_REG,
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -12,6 +12,7 @@
|
||||
#include <stdbool.h>
|
||||
#include "soc/extmem_reg.h"
|
||||
#include "soc/ext_mem_defs.h"
|
||||
#include "hal/cache_periph.h"
|
||||
#include "hal/cache_types.h"
|
||||
#include "hal/assert.h"
|
||||
#include "esp32c6/rom/cache.h"
|
||||
@@ -391,6 +392,58 @@ static inline uint32_t cache_ll_l1_get_access_error_intr_status(uint32_t cache_i
|
||||
return GET_PERI_REG_MASK(EXTMEM_L1_CACHE_ACS_FAIL_INT_ST_REG, mask);
|
||||
}
|
||||
|
||||
/*----------------------------------------------------------------------------
|
||||
Cache Profile Counter Related
|
||||
-----------------------------------------------------------------------------*/
|
||||
#define CACHE_LL_PROFILE_CNT_ENA_MASK (EXTMEM_L1_IBUS_CNT_ENA | EXTMEM_L1_DBUS_CNT_ENA)
|
||||
#define CACHE_LL_PROFILE_CNT_CLR_MASK (EXTMEM_L1_IBUS_CNT_CLR | EXTMEM_L1_DBUS_CNT_CLR)
|
||||
|
||||
/**
|
||||
* @brief Enable or disable the cache profile counters
|
||||
*
|
||||
* @param ena True to enable, false to disable
|
||||
*/
|
||||
__attribute__((always_inline))
|
||||
static inline void cache_ll_enable_profile_counter(bool ena)
|
||||
{
|
||||
if (ena) {
|
||||
REG_SET_BIT(EXTMEM_L1_CACHE_ACS_CNT_CTRL_REG, CACHE_LL_PROFILE_CNT_ENA_MASK);
|
||||
} else {
|
||||
REG_CLR_BIT(EXTMEM_L1_CACHE_ACS_CNT_CTRL_REG, CACHE_LL_PROFILE_CNT_ENA_MASK);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Reset all cache profile counters to zero
|
||||
*/
|
||||
__attribute__((always_inline))
|
||||
static inline void cache_ll_clear_profile_counter(void)
|
||||
{
|
||||
/* clear bits are write-to-trigger and self-clearing */
|
||||
REG_SET_BIT(EXTMEM_L1_CACHE_ACS_CNT_CTRL_REG, CACHE_LL_PROFILE_CNT_CLR_MASK);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Read one counter of a cache profile counter unit
|
||||
*
|
||||
* @param unit Unit index, 0 to SOC_CACHE_CNT_UNITS_NUM - 1
|
||||
* @param counter Counter to read
|
||||
* @param[out] value Counter value, only written if the counter exists
|
||||
*
|
||||
* @return True if the unit has this counter, false otherwise
|
||||
*/
|
||||
__attribute__((always_inline))
|
||||
static inline bool cache_ll_get_profile_counter(int unit, cache_profile_counter_t counter, uint32_t *value)
|
||||
{
|
||||
HAL_ASSERT(unit < SOC_CACHE_CNT_UNITS_NUM);
|
||||
uint32_t reg = cache_periph_profile_counter_units[unit].counter_reg[counter];
|
||||
if (reg == 0) {
|
||||
return false;
|
||||
}
|
||||
*value = REG_READ(reg);
|
||||
return true;
|
||||
}
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
40
components/hal/esp32c61/cache_periph.c
Normal file
40
components/hal/esp32c61/cache_periph.c
Normal file
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include "soc/cache_reg.h"
|
||||
#include "hal/cache_periph.h"
|
||||
|
||||
/*
|
||||
* ESP32-C61 cache profile counter units.
|
||||
*
|
||||
* Single cache level shared by instructions and data (the flash/PSRAM
|
||||
* cache), with two request buses: bus0 carries instruction fetches, bus1
|
||||
* carries data accesses (same bus arrangement as the ESP32-C5). bus0's
|
||||
* next-level counters use the BUS0 register names while bus1's use the
|
||||
* DBUS1 names.
|
||||
*/
|
||||
|
||||
const cache_profile_counter_unit_t cache_periph_profile_counter_units[SOC_CACHE_CNT_UNITS_NUM] = {
|
||||
{
|
||||
.name = "l1-cache-ibus", .level = 1, .traffic = CACHE_PROFILE_TRAFFIC_INST, .core_id = 0,
|
||||
.counter_reg = {
|
||||
[CACHE_PROFILE_COUNTER_HIT] = CACHE_L1_BUS0_ACS_HIT_CNT_REG,
|
||||
[CACHE_PROFILE_COUNTER_MISS] = CACHE_L1_BUS0_ACS_MISS_CNT_REG,
|
||||
[CACHE_PROFILE_COUNTER_CONFLICT] = CACHE_L1_BUS0_ACS_CONFLICT_CNT_REG,
|
||||
[CACHE_PROFILE_COUNTER_NXTLVL_RD] = CACHE_L1_BUS0_ACS_NXTLVL_RD_CNT_REG,
|
||||
},
|
||||
},
|
||||
{
|
||||
.name = "l1-cache-dbus", .level = 1, .traffic = CACHE_PROFILE_TRAFFIC_DATA, .core_id = 0,
|
||||
.counter_reg = {
|
||||
[CACHE_PROFILE_COUNTER_HIT] = CACHE_L1_BUS1_ACS_HIT_CNT_REG,
|
||||
[CACHE_PROFILE_COUNTER_MISS] = CACHE_L1_BUS1_ACS_MISS_CNT_REG,
|
||||
[CACHE_PROFILE_COUNTER_CONFLICT] = CACHE_L1_BUS1_ACS_CONFLICT_CNT_REG,
|
||||
[CACHE_PROFILE_COUNTER_NXTLVL_RD] = CACHE_L1_DBUS1_ACS_NXTLVL_RD_CNT_REG,
|
||||
[CACHE_PROFILE_COUNTER_NXTLVL_WR] = CACHE_L1_DBUS1_ACS_NXTLVL_WR_CNT_REG,
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -12,6 +12,7 @@
|
||||
#include "soc/cache_reg.h"
|
||||
#include "soc/cache_struct.h"
|
||||
#include "soc/ext_mem_defs.h"
|
||||
#include "hal/cache_periph.h"
|
||||
#include "hal/cache_types.h"
|
||||
#include "hal/assert.h"
|
||||
#include "esp32c61/rom/cache.h"
|
||||
@@ -413,6 +414,58 @@ static inline uint32_t cache_ll_l1_get_access_error_intr_status(uint32_t cache_i
|
||||
return CACHE.l1_cache_acs_fail_int_st.val & mask;
|
||||
}
|
||||
|
||||
/*----------------------------------------------------------------------------
|
||||
Cache Profile Counter Related
|
||||
-----------------------------------------------------------------------------*/
|
||||
#define CACHE_LL_PROFILE_CNT_ENA_MASK (CACHE_L1_BUS0_CNT_ENA | CACHE_L1_BUS1_CNT_ENA)
|
||||
#define CACHE_LL_PROFILE_CNT_CLR_MASK (CACHE_L1_BUS0_CNT_CLR | CACHE_L1_BUS1_CNT_CLR)
|
||||
|
||||
/**
|
||||
* @brief Enable or disable the cache profile counters
|
||||
*
|
||||
* @param ena True to enable, false to disable
|
||||
*/
|
||||
__attribute__((always_inline))
|
||||
static inline void cache_ll_enable_profile_counter(bool ena)
|
||||
{
|
||||
if (ena) {
|
||||
REG_SET_BIT(CACHE_L1_CACHE_ACS_CNT_CTRL_REG, CACHE_LL_PROFILE_CNT_ENA_MASK);
|
||||
} else {
|
||||
REG_CLR_BIT(CACHE_L1_CACHE_ACS_CNT_CTRL_REG, CACHE_LL_PROFILE_CNT_ENA_MASK);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Reset all cache profile counters to zero
|
||||
*/
|
||||
__attribute__((always_inline))
|
||||
static inline void cache_ll_clear_profile_counter(void)
|
||||
{
|
||||
/* clear bits are write-to-trigger and self-clearing */
|
||||
REG_SET_BIT(CACHE_L1_CACHE_ACS_CNT_CTRL_REG, CACHE_LL_PROFILE_CNT_CLR_MASK);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Read one counter of a cache profile counter unit
|
||||
*
|
||||
* @param unit Unit index, 0 to SOC_CACHE_CNT_UNITS_NUM - 1
|
||||
* @param counter Counter to read
|
||||
* @param[out] value Counter value, only written if the counter exists
|
||||
*
|
||||
* @return True if the unit has this counter, false otherwise
|
||||
*/
|
||||
__attribute__((always_inline))
|
||||
static inline bool cache_ll_get_profile_counter(int unit, cache_profile_counter_t counter, uint32_t *value)
|
||||
{
|
||||
HAL_ASSERT(unit < SOC_CACHE_CNT_UNITS_NUM);
|
||||
uint32_t reg = cache_periph_profile_counter_units[unit].counter_reg[counter];
|
||||
if (reg == 0) {
|
||||
return false;
|
||||
}
|
||||
*value = REG_READ(reg);
|
||||
return true;
|
||||
}
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
40
components/hal/esp32h2/cache_periph.c
Normal file
40
components/hal/esp32h2/cache_periph.c
Normal file
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include "soc/cache_reg.h"
|
||||
#include "hal/cache_periph.h"
|
||||
|
||||
/*
|
||||
* ESP32-H2 cache profile counter units.
|
||||
*
|
||||
* Single cache level shared by instructions and data (the flash cache,
|
||||
* 32-byte lines), with two request buses: bus0 carries instruction
|
||||
* fetches, bus1 carries data accesses. There is one next-level counter
|
||||
* per bus (no read/write split), mapped here to line_fills. The cache is
|
||||
* read-only (no PSRAM, no write-back — see SOC_CACHE_WRITEBACK_SUPPORTED),
|
||||
* so there are no write-back counters.
|
||||
*/
|
||||
|
||||
const cache_profile_counter_unit_t cache_periph_profile_counter_units[SOC_CACHE_CNT_UNITS_NUM] = {
|
||||
{
|
||||
.name = "l1-cache-ibus", .level = 1, .traffic = CACHE_PROFILE_TRAFFIC_INST, .core_id = 0,
|
||||
.counter_reg = {
|
||||
[CACHE_PROFILE_COUNTER_HIT] = CACHE_L1_BUS0_ACS_HIT_CNT_REG,
|
||||
[CACHE_PROFILE_COUNTER_MISS] = CACHE_L1_BUS0_ACS_MISS_CNT_REG,
|
||||
[CACHE_PROFILE_COUNTER_CONFLICT] = CACHE_L1_BUS0_ACS_CONFLICT_CNT_REG,
|
||||
[CACHE_PROFILE_COUNTER_NXTLVL_RD] = CACHE_L1_BUS0_ACS_NXTLVL_CNT_REG,
|
||||
},
|
||||
},
|
||||
{
|
||||
.name = "l1-cache-dbus", .level = 1, .traffic = CACHE_PROFILE_TRAFFIC_DATA, .core_id = 0,
|
||||
.counter_reg = {
|
||||
[CACHE_PROFILE_COUNTER_HIT] = CACHE_L1_BUS1_ACS_HIT_CNT_REG,
|
||||
[CACHE_PROFILE_COUNTER_MISS] = CACHE_L1_BUS1_ACS_MISS_CNT_REG,
|
||||
[CACHE_PROFILE_COUNTER_CONFLICT] = CACHE_L1_BUS1_ACS_CONFLICT_CNT_REG,
|
||||
[CACHE_PROFILE_COUNTER_NXTLVL_RD] = CACHE_L1_BUS1_ACS_NXTLVL_CNT_REG,
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -11,6 +11,7 @@
|
||||
#include <stdbool.h>
|
||||
#include "soc/extmem_reg.h"
|
||||
#include "soc/ext_mem_defs.h"
|
||||
#include "hal/cache_periph.h"
|
||||
#include "hal/cache_types.h"
|
||||
#include "hal/assert.h"
|
||||
#include "esp32h2/rom/cache.h"
|
||||
@@ -389,6 +390,58 @@ static inline uint32_t cache_ll_l1_get_access_error_intr_status(uint32_t cache_i
|
||||
return GET_PERI_REG_MASK(CACHE_L1_CACHE_ACS_FAIL_INT_ST_REG, mask);
|
||||
}
|
||||
|
||||
/*----------------------------------------------------------------------------
|
||||
Cache Profile Counter Related
|
||||
-----------------------------------------------------------------------------*/
|
||||
#define CACHE_LL_PROFILE_CNT_ENA_MASK (CACHE_L1_BUS0_CNT_ENA | CACHE_L1_BUS1_CNT_ENA)
|
||||
#define CACHE_LL_PROFILE_CNT_CLR_MASK (CACHE_L1_BUS0_CNT_CLR | CACHE_L1_BUS1_CNT_CLR)
|
||||
|
||||
/**
|
||||
* @brief Enable or disable the cache profile counters
|
||||
*
|
||||
* @param ena True to enable, false to disable
|
||||
*/
|
||||
__attribute__((always_inline))
|
||||
static inline void cache_ll_enable_profile_counter(bool ena)
|
||||
{
|
||||
if (ena) {
|
||||
REG_SET_BIT(CACHE_L1_CACHE_ACS_CNT_CTRL_REG, CACHE_LL_PROFILE_CNT_ENA_MASK);
|
||||
} else {
|
||||
REG_CLR_BIT(CACHE_L1_CACHE_ACS_CNT_CTRL_REG, CACHE_LL_PROFILE_CNT_ENA_MASK);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Reset all cache profile counters to zero
|
||||
*/
|
||||
__attribute__((always_inline))
|
||||
static inline void cache_ll_clear_profile_counter(void)
|
||||
{
|
||||
/* clear bits are write-to-trigger and self-clearing */
|
||||
REG_SET_BIT(CACHE_L1_CACHE_ACS_CNT_CTRL_REG, CACHE_LL_PROFILE_CNT_CLR_MASK);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Read one counter of a cache profile counter unit
|
||||
*
|
||||
* @param unit Unit index, 0 to SOC_CACHE_CNT_UNITS_NUM - 1
|
||||
* @param counter Counter to read
|
||||
* @param[out] value Counter value, only written if the counter exists
|
||||
*
|
||||
* @return True if the unit has this counter, false otherwise
|
||||
*/
|
||||
__attribute__((always_inline))
|
||||
static inline bool cache_ll_get_profile_counter(int unit, cache_profile_counter_t counter, uint32_t *value)
|
||||
{
|
||||
HAL_ASSERT(unit < SOC_CACHE_CNT_UNITS_NUM);
|
||||
uint32_t reg = cache_periph_profile_counter_units[unit].counter_reg[counter];
|
||||
if (reg == 0) {
|
||||
return false;
|
||||
}
|
||||
*value = REG_READ(reg);
|
||||
return true;
|
||||
}
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
39
components/hal/esp32h21/cache_periph.c
Normal file
39
components/hal/esp32h21/cache_periph.c
Normal file
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include "soc/cache_reg.h"
|
||||
#include "hal/cache_periph.h"
|
||||
|
||||
/*
|
||||
* ESP32-H21 cache profile counter units.
|
||||
*
|
||||
* Single cache level shared by instructions and data (the flash cache),
|
||||
* with two request buses: bus0 carries instruction fetches, bus1 carries
|
||||
* data accesses (same bus arrangement as the ESP32-C5). The cache is
|
||||
* read-only (no PSRAM, no write-back — see SOC_CACHE_WRITEBACK_SUPPORTED),
|
||||
* so no write-back counters are exposed.
|
||||
*/
|
||||
|
||||
const cache_profile_counter_unit_t cache_periph_profile_counter_units[SOC_CACHE_CNT_UNITS_NUM] = {
|
||||
{
|
||||
.name = "l1-cache-ibus", .level = 1, .traffic = CACHE_PROFILE_TRAFFIC_INST, .core_id = 0,
|
||||
.counter_reg = {
|
||||
[CACHE_PROFILE_COUNTER_HIT] = CACHE_L1_BUS0_ACS_HIT_CNT_REG,
|
||||
[CACHE_PROFILE_COUNTER_MISS] = CACHE_L1_BUS0_ACS_MISS_CNT_REG,
|
||||
[CACHE_PROFILE_COUNTER_CONFLICT] = CACHE_L1_BUS0_ACS_CONFLICT_CNT_REG,
|
||||
[CACHE_PROFILE_COUNTER_NXTLVL_RD] = CACHE_L1_BUS0_ACS_NXTLVL_RD_CNT_REG,
|
||||
},
|
||||
},
|
||||
{
|
||||
.name = "l1-cache-dbus", .level = 1, .traffic = CACHE_PROFILE_TRAFFIC_DATA, .core_id = 0,
|
||||
.counter_reg = {
|
||||
[CACHE_PROFILE_COUNTER_HIT] = CACHE_L1_BUS1_ACS_HIT_CNT_REG,
|
||||
[CACHE_PROFILE_COUNTER_MISS] = CACHE_L1_BUS1_ACS_MISS_CNT_REG,
|
||||
[CACHE_PROFILE_COUNTER_CONFLICT] = CACHE_L1_BUS1_ACS_CONFLICT_CNT_REG,
|
||||
[CACHE_PROFILE_COUNTER_NXTLVL_RD] = CACHE_L1_BUS1_ACS_NXTLVL_RD_CNT_REG,
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -12,6 +12,7 @@
|
||||
#include "soc/cache_reg.h"
|
||||
#include "soc/cache_struct.h"
|
||||
#include "soc/ext_mem_defs.h"
|
||||
#include "hal/cache_periph.h"
|
||||
#include "hal/cache_types.h"
|
||||
#include "hal/assert.h"
|
||||
#include "rom/cache.h"
|
||||
@@ -401,6 +402,58 @@ static inline uint32_t cache_ll_l1_get_access_error_intr_status(uint32_t cache_i
|
||||
return CACHE.l1_cache_acs_fail_int_st.val & mask;
|
||||
}
|
||||
|
||||
/*----------------------------------------------------------------------------
|
||||
Cache Profile Counter Related
|
||||
-----------------------------------------------------------------------------*/
|
||||
#define CACHE_LL_PROFILE_CNT_ENA_MASK (CACHE_L1_BUS0_CNT_ENA | CACHE_L1_BUS1_CNT_ENA)
|
||||
#define CACHE_LL_PROFILE_CNT_CLR_MASK (CACHE_L1_BUS0_CNT_CLR | CACHE_L1_BUS1_CNT_CLR)
|
||||
|
||||
/**
|
||||
* @brief Enable or disable the cache profile counters
|
||||
*
|
||||
* @param ena True to enable, false to disable
|
||||
*/
|
||||
__attribute__((always_inline))
|
||||
static inline void cache_ll_enable_profile_counter(bool ena)
|
||||
{
|
||||
if (ena) {
|
||||
REG_SET_BIT(CACHE_L1_CACHE_ACS_CNT_CTRL_REG, CACHE_LL_PROFILE_CNT_ENA_MASK);
|
||||
} else {
|
||||
REG_CLR_BIT(CACHE_L1_CACHE_ACS_CNT_CTRL_REG, CACHE_LL_PROFILE_CNT_ENA_MASK);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Reset all cache profile counters to zero
|
||||
*/
|
||||
__attribute__((always_inline))
|
||||
static inline void cache_ll_clear_profile_counter(void)
|
||||
{
|
||||
/* clear bits are write-to-trigger and self-clearing */
|
||||
REG_SET_BIT(CACHE_L1_CACHE_ACS_CNT_CTRL_REG, CACHE_LL_PROFILE_CNT_CLR_MASK);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Read one counter of a cache profile counter unit
|
||||
*
|
||||
* @param unit Unit index, 0 to SOC_CACHE_CNT_UNITS_NUM - 1
|
||||
* @param counter Counter to read
|
||||
* @param[out] value Counter value, only written if the counter exists
|
||||
*
|
||||
* @return True if the unit has this counter, false otherwise
|
||||
*/
|
||||
__attribute__((always_inline))
|
||||
static inline bool cache_ll_get_profile_counter(int unit, cache_profile_counter_t counter, uint32_t *value)
|
||||
{
|
||||
HAL_ASSERT(unit < SOC_CACHE_CNT_UNITS_NUM);
|
||||
uint32_t reg = cache_periph_profile_counter_units[unit].counter_reg[counter];
|
||||
if (reg == 0) {
|
||||
return false;
|
||||
}
|
||||
*value = REG_READ(reg);
|
||||
return true;
|
||||
}
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
58
components/hal/esp32h4/cache_periph.c
Normal file
58
components/hal/esp32h4/cache_periph.c
Normal file
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include "soc/cache_reg.h"
|
||||
#include "hal/cache_periph.h"
|
||||
|
||||
/*
|
||||
* ESP32-H4 cache profile counter units.
|
||||
*
|
||||
* Single cache level (the flash/PSRAM cache); each core accesses it
|
||||
* through an instruction bus (ibus0/ibus1) and a data bus (dbus0/dbus1),
|
||||
* each with hit/miss/conflict/next-level counters (next-level write
|
||||
* counters on the data buses only).
|
||||
*/
|
||||
|
||||
const cache_profile_counter_unit_t cache_periph_profile_counter_units[SOC_CACHE_CNT_UNITS_NUM] = {
|
||||
{
|
||||
.name = "l1-cache-inst-core0", .level = 1, .traffic = CACHE_PROFILE_TRAFFIC_INST, .core_id = 0,
|
||||
.counter_reg = {
|
||||
[CACHE_PROFILE_COUNTER_HIT] = CACHE_L1_IBUS0_ACS_HIT_CNT_REG,
|
||||
[CACHE_PROFILE_COUNTER_MISS] = CACHE_L1_IBUS0_ACS_MISS_CNT_REG,
|
||||
[CACHE_PROFILE_COUNTER_CONFLICT] = CACHE_L1_IBUS0_ACS_CONFLICT_CNT_REG,
|
||||
[CACHE_PROFILE_COUNTER_NXTLVL_RD] = CACHE_L1_IBUS0_ACS_NXTLVL_RD_CNT_REG,
|
||||
},
|
||||
},
|
||||
{
|
||||
.name = "l1-cache-inst-core1", .level = 1, .traffic = CACHE_PROFILE_TRAFFIC_INST, .core_id = 1,
|
||||
.counter_reg = {
|
||||
[CACHE_PROFILE_COUNTER_HIT] = CACHE_L1_IBUS1_ACS_HIT_CNT_REG,
|
||||
[CACHE_PROFILE_COUNTER_MISS] = CACHE_L1_IBUS1_ACS_MISS_CNT_REG,
|
||||
[CACHE_PROFILE_COUNTER_CONFLICT] = CACHE_L1_IBUS1_ACS_CONFLICT_CNT_REG,
|
||||
[CACHE_PROFILE_COUNTER_NXTLVL_RD] = CACHE_L1_IBUS1_ACS_NXTLVL_RD_CNT_REG,
|
||||
},
|
||||
},
|
||||
{
|
||||
.name = "l1-cache-data-core0", .level = 1, .traffic = CACHE_PROFILE_TRAFFIC_DATA, .core_id = 0,
|
||||
.counter_reg = {
|
||||
[CACHE_PROFILE_COUNTER_HIT] = CACHE_L1_DBUS0_ACS_HIT_CNT_REG,
|
||||
[CACHE_PROFILE_COUNTER_MISS] = CACHE_L1_DBUS0_ACS_MISS_CNT_REG,
|
||||
[CACHE_PROFILE_COUNTER_CONFLICT] = CACHE_L1_DBUS0_ACS_CONFLICT_CNT_REG,
|
||||
[CACHE_PROFILE_COUNTER_NXTLVL_RD] = CACHE_L1_DBUS0_ACS_NXTLVL_RD_CNT_REG,
|
||||
[CACHE_PROFILE_COUNTER_NXTLVL_WR] = CACHE_L1_DBUS0_ACS_NXTLVL_WR_CNT_REG,
|
||||
},
|
||||
},
|
||||
{
|
||||
.name = "l1-cache-data-core1", .level = 1, .traffic = CACHE_PROFILE_TRAFFIC_DATA, .core_id = 1,
|
||||
.counter_reg = {
|
||||
[CACHE_PROFILE_COUNTER_HIT] = CACHE_L1_DBUS1_ACS_HIT_CNT_REG,
|
||||
[CACHE_PROFILE_COUNTER_MISS] = CACHE_L1_DBUS1_ACS_MISS_CNT_REG,
|
||||
[CACHE_PROFILE_COUNTER_CONFLICT] = CACHE_L1_DBUS1_ACS_CONFLICT_CNT_REG,
|
||||
[CACHE_PROFILE_COUNTER_NXTLVL_RD] = CACHE_L1_DBUS1_ACS_NXTLVL_RD_CNT_REG,
|
||||
[CACHE_PROFILE_COUNTER_NXTLVL_WR] = CACHE_L1_DBUS1_ACS_NXTLVL_WR_CNT_REG,
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -12,6 +12,7 @@
|
||||
#include "soc/cache_reg.h"
|
||||
#include "soc/cache_struct.h"
|
||||
#include "soc/ext_mem_defs.h"
|
||||
#include "hal/cache_periph.h"
|
||||
#include "hal/cache_types.h"
|
||||
#include "hal/assert.h"
|
||||
#include "rom/cache.h"
|
||||
@@ -982,6 +983,60 @@ static inline uint32_t cache_ll_l1_get_access_error_intr_status(uint32_t cache_i
|
||||
return CACHE.l1_cache_acs_fail_int_st.val & mask;
|
||||
}
|
||||
|
||||
/*----------------------------------------------------------------------------
|
||||
Cache Profile Counter Related
|
||||
-----------------------------------------------------------------------------*/
|
||||
#define CACHE_LL_PROFILE_CNT_ENA_MASK (CACHE_L1_IBUS0_CNT_ENA | CACHE_L1_IBUS1_CNT_ENA | \
|
||||
CACHE_L1_DBUS0_CNT_ENA | CACHE_L1_DBUS1_CNT_ENA)
|
||||
#define CACHE_LL_PROFILE_CNT_CLR_MASK (CACHE_L1_IBUS0_CNT_CLR | CACHE_L1_IBUS1_CNT_CLR | \
|
||||
CACHE_L1_DBUS0_CNT_CLR | CACHE_L1_DBUS1_CNT_CLR)
|
||||
|
||||
/**
|
||||
* @brief Enable or disable the cache profile counters
|
||||
*
|
||||
* @param ena True to enable, false to disable
|
||||
*/
|
||||
__attribute__((always_inline))
|
||||
static inline void cache_ll_enable_profile_counter(bool ena)
|
||||
{
|
||||
if (ena) {
|
||||
REG_SET_BIT(CACHE_L1_CACHE_ACS_CNT_CTRL_REG, CACHE_LL_PROFILE_CNT_ENA_MASK);
|
||||
} else {
|
||||
REG_CLR_BIT(CACHE_L1_CACHE_ACS_CNT_CTRL_REG, CACHE_LL_PROFILE_CNT_ENA_MASK);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Reset all cache profile counters to zero
|
||||
*/
|
||||
__attribute__((always_inline))
|
||||
static inline void cache_ll_clear_profile_counter(void)
|
||||
{
|
||||
/* clear bits are write-to-trigger and self-clearing */
|
||||
REG_SET_BIT(CACHE_L1_CACHE_ACS_CNT_CTRL_REG, CACHE_LL_PROFILE_CNT_CLR_MASK);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Read one counter of a cache profile counter unit
|
||||
*
|
||||
* @param unit Unit index, 0 to SOC_CACHE_CNT_UNITS_NUM - 1
|
||||
* @param counter Counter to read
|
||||
* @param[out] value Counter value, only written if the counter exists
|
||||
*
|
||||
* @return True if the unit has this counter, false otherwise
|
||||
*/
|
||||
__attribute__((always_inline))
|
||||
static inline bool cache_ll_get_profile_counter(int unit, cache_profile_counter_t counter, uint32_t *value)
|
||||
{
|
||||
HAL_ASSERT(unit < SOC_CACHE_CNT_UNITS_NUM);
|
||||
uint32_t reg = cache_periph_profile_counter_units[unit].counter_reg[counter];
|
||||
if (reg == 0) {
|
||||
return false;
|
||||
}
|
||||
*value = REG_READ(reg);
|
||||
return true;
|
||||
}
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
77
components/hal/esp32p4/cache_periph.c
Normal file
77
components/hal/esp32p4/cache_periph.c
Normal file
@@ -0,0 +1,77 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include "soc/cache_reg.h"
|
||||
#include "hal/cache_periph.h"
|
||||
|
||||
/*
|
||||
* ESP32-P4 cache profile counter units.
|
||||
*
|
||||
* Topology: per-core L1 instruction caches (ibus0/ibus1) and a shared L1
|
||||
* data cache with one port per core (dbus0/dbus1). The L2 (flash/PSRAM)
|
||||
* cache reports all instruction traffic on its ibus0 counters and all data
|
||||
* traffic on its dbus0 counters, regardless of the originating core.
|
||||
*/
|
||||
|
||||
const cache_profile_counter_unit_t cache_periph_profile_counter_units[SOC_CACHE_CNT_UNITS_NUM] = {
|
||||
{
|
||||
.name = "l1-icache-core0", .level = 1, .traffic = CACHE_PROFILE_TRAFFIC_INST, .core_id = 0,
|
||||
.counter_reg = {
|
||||
[CACHE_PROFILE_COUNTER_HIT] = CACHE_L1_IBUS0_ACS_HIT_CNT_REG,
|
||||
[CACHE_PROFILE_COUNTER_MISS] = CACHE_L1_IBUS0_ACS_MISS_CNT_REG,
|
||||
[CACHE_PROFILE_COUNTER_CONFLICT] = CACHE_L1_IBUS0_ACS_CONFLICT_CNT_REG,
|
||||
[CACHE_PROFILE_COUNTER_NXTLVL_RD] = CACHE_L1_IBUS0_ACS_NXTLVL_RD_CNT_REG,
|
||||
},
|
||||
},
|
||||
{
|
||||
.name = "l1-icache-core1", .level = 1, .traffic = CACHE_PROFILE_TRAFFIC_INST, .core_id = 1,
|
||||
.counter_reg = {
|
||||
[CACHE_PROFILE_COUNTER_HIT] = CACHE_L1_IBUS1_ACS_HIT_CNT_REG,
|
||||
[CACHE_PROFILE_COUNTER_MISS] = CACHE_L1_IBUS1_ACS_MISS_CNT_REG,
|
||||
[CACHE_PROFILE_COUNTER_CONFLICT] = CACHE_L1_IBUS1_ACS_CONFLICT_CNT_REG,
|
||||
[CACHE_PROFILE_COUNTER_NXTLVL_RD] = CACHE_L1_IBUS1_ACS_NXTLVL_RD_CNT_REG,
|
||||
},
|
||||
},
|
||||
{
|
||||
.name = "l1-dcache-core0", .level = 1, .traffic = CACHE_PROFILE_TRAFFIC_DATA, .core_id = 0,
|
||||
.counter_reg = {
|
||||
[CACHE_PROFILE_COUNTER_HIT] = CACHE_L1_DBUS0_ACS_HIT_CNT_REG,
|
||||
[CACHE_PROFILE_COUNTER_MISS] = CACHE_L1_DBUS0_ACS_MISS_CNT_REG,
|
||||
[CACHE_PROFILE_COUNTER_CONFLICT] = CACHE_L1_DBUS0_ACS_CONFLICT_CNT_REG,
|
||||
[CACHE_PROFILE_COUNTER_NXTLVL_RD] = CACHE_L1_DBUS0_ACS_NXTLVL_RD_CNT_REG,
|
||||
[CACHE_PROFILE_COUNTER_NXTLVL_WR] = CACHE_L1_DBUS0_ACS_NXTLVL_WR_CNT_REG,
|
||||
},
|
||||
},
|
||||
{
|
||||
.name = "l1-dcache-core1", .level = 1, .traffic = CACHE_PROFILE_TRAFFIC_DATA, .core_id = 1,
|
||||
.counter_reg = {
|
||||
[CACHE_PROFILE_COUNTER_HIT] = CACHE_L1_DBUS1_ACS_HIT_CNT_REG,
|
||||
[CACHE_PROFILE_COUNTER_MISS] = CACHE_L1_DBUS1_ACS_MISS_CNT_REG,
|
||||
[CACHE_PROFILE_COUNTER_CONFLICT] = CACHE_L1_DBUS1_ACS_CONFLICT_CNT_REG,
|
||||
[CACHE_PROFILE_COUNTER_NXTLVL_RD] = CACHE_L1_DBUS1_ACS_NXTLVL_RD_CNT_REG,
|
||||
[CACHE_PROFILE_COUNTER_NXTLVL_WR] = CACHE_L1_DBUS1_ACS_NXTLVL_WR_CNT_REG,
|
||||
},
|
||||
},
|
||||
{
|
||||
.name = "l2-cache-inst", .level = 2, .traffic = CACHE_PROFILE_TRAFFIC_INST, .core_id = -1,
|
||||
.counter_reg = {
|
||||
[CACHE_PROFILE_COUNTER_HIT] = CACHE_L2_IBUS0_ACS_HIT_CNT_REG,
|
||||
[CACHE_PROFILE_COUNTER_MISS] = CACHE_L2_IBUS0_ACS_MISS_CNT_REG,
|
||||
[CACHE_PROFILE_COUNTER_CONFLICT] = CACHE_L2_IBUS0_ACS_CONFLICT_CNT_REG,
|
||||
[CACHE_PROFILE_COUNTER_NXTLVL_RD] = CACHE_L2_IBUS0_ACS_NXTLVL_RD_CNT_REG,
|
||||
},
|
||||
},
|
||||
{
|
||||
.name = "l2-cache-data", .level = 2, .traffic = CACHE_PROFILE_TRAFFIC_DATA, .core_id = -1,
|
||||
.counter_reg = {
|
||||
[CACHE_PROFILE_COUNTER_HIT] = CACHE_L2_DBUS0_ACS_HIT_CNT_REG,
|
||||
[CACHE_PROFILE_COUNTER_MISS] = CACHE_L2_DBUS0_ACS_MISS_CNT_REG,
|
||||
[CACHE_PROFILE_COUNTER_CONFLICT] = CACHE_L2_DBUS0_ACS_CONFLICT_CNT_REG,
|
||||
[CACHE_PROFILE_COUNTER_NXTLVL_RD] = CACHE_L2_DBUS0_ACS_NXTLVL_RD_CNT_REG,
|
||||
[CACHE_PROFILE_COUNTER_NXTLVL_WR] = CACHE_L2_DBUS0_ACS_NXTLVL_WR_CNT_REG,
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -12,6 +12,7 @@
|
||||
#include "soc/cache_reg.h"
|
||||
#include "soc/cache_struct.h"
|
||||
#include "soc/ext_mem_defs.h"
|
||||
#include "hal/cache_periph.h"
|
||||
#include "hal/cache_types.h"
|
||||
#include "hal/config.h"
|
||||
#include "hal/assert.h"
|
||||
@@ -1397,6 +1398,67 @@ static inline uint32_t cache_ll_l2_get_access_error_intr_status(uint32_t cache_i
|
||||
return CACHE.l2_cache_acs_fail_int_st.val & mask;
|
||||
}
|
||||
|
||||
/*----------------------------------------------------------------------------
|
||||
Cache Profile Counter Related
|
||||
-----------------------------------------------------------------------------*/
|
||||
#define CACHE_LL_PROFILE_CNT_L1_ENA_MASK (CACHE_L1_IBUS0_CNT_ENA | CACHE_L1_IBUS1_CNT_ENA | \
|
||||
CACHE_L1_DBUS0_CNT_ENA | CACHE_L1_DBUS1_CNT_ENA)
|
||||
#define CACHE_LL_PROFILE_CNT_L1_CLR_MASK (CACHE_L1_IBUS0_CNT_CLR | CACHE_L1_IBUS1_CNT_CLR | \
|
||||
CACHE_L1_DBUS0_CNT_CLR | CACHE_L1_DBUS1_CNT_CLR)
|
||||
#define CACHE_LL_PROFILE_CNT_L2_ENA_MASK (CACHE_L2_IBUS0_CNT_ENA | CACHE_L2_IBUS1_CNT_ENA | \
|
||||
CACHE_L2_DBUS0_CNT_ENA | CACHE_L2_DBUS1_CNT_ENA)
|
||||
#define CACHE_LL_PROFILE_CNT_L2_CLR_MASK (CACHE_L2_IBUS0_CNT_CLR | CACHE_L2_IBUS1_CNT_CLR | \
|
||||
CACHE_L2_DBUS0_CNT_CLR | CACHE_L2_DBUS1_CNT_CLR)
|
||||
|
||||
/**
|
||||
* @brief Enable or disable the cache profile counters
|
||||
*
|
||||
* @param ena True to enable, false to disable
|
||||
*/
|
||||
__attribute__((always_inline))
|
||||
static inline void cache_ll_enable_profile_counter(bool ena)
|
||||
{
|
||||
if (ena) {
|
||||
REG_SET_BIT(CACHE_L1_CACHE_ACS_CNT_CTRL_REG, CACHE_LL_PROFILE_CNT_L1_ENA_MASK);
|
||||
REG_SET_BIT(CACHE_L2_CACHE_ACS_CNT_CTRL_REG, CACHE_LL_PROFILE_CNT_L2_ENA_MASK);
|
||||
} else {
|
||||
REG_CLR_BIT(CACHE_L1_CACHE_ACS_CNT_CTRL_REG, CACHE_LL_PROFILE_CNT_L1_ENA_MASK);
|
||||
REG_CLR_BIT(CACHE_L2_CACHE_ACS_CNT_CTRL_REG, CACHE_LL_PROFILE_CNT_L2_ENA_MASK);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Reset all cache profile counters to zero
|
||||
*/
|
||||
__attribute__((always_inline))
|
||||
static inline void cache_ll_clear_profile_counter(void)
|
||||
{
|
||||
/* clear bits are write-to-trigger and self-clearing */
|
||||
REG_SET_BIT(CACHE_L1_CACHE_ACS_CNT_CTRL_REG, CACHE_LL_PROFILE_CNT_L1_CLR_MASK);
|
||||
REG_SET_BIT(CACHE_L2_CACHE_ACS_CNT_CTRL_REG, CACHE_LL_PROFILE_CNT_L2_CLR_MASK);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Read one counter of a cache profile counter unit
|
||||
*
|
||||
* @param unit Unit index, 0 to SOC_CACHE_CNT_UNITS_NUM - 1
|
||||
* @param counter Counter to read
|
||||
* @param[out] value Counter value, only written if the counter exists
|
||||
*
|
||||
* @return True if the unit has this counter, false otherwise
|
||||
*/
|
||||
__attribute__((always_inline))
|
||||
static inline bool cache_ll_get_profile_counter(int unit, cache_profile_counter_t counter, uint32_t *value)
|
||||
{
|
||||
HAL_ASSERT(unit < SOC_CACHE_CNT_UNITS_NUM);
|
||||
uint32_t reg = cache_periph_profile_counter_units[unit].counter_reg[counter];
|
||||
if (reg == 0) {
|
||||
return false;
|
||||
}
|
||||
*value = REG_READ(reg);
|
||||
return true;
|
||||
}
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
59
components/hal/esp32s31/cache_periph.c
Normal file
59
components/hal/esp32s31/cache_periph.c
Normal file
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include "soc/cache_reg.h"
|
||||
#include "hal/cache_periph.h"
|
||||
|
||||
/*
|
||||
* ESP32-S31 cache profile counter units.
|
||||
*
|
||||
* The register layout matches the ESP32-P4: per-bus hit/miss/conflict and
|
||||
* next-level read/write counters, with the same counter semantics. There
|
||||
* is a single cache level (the flash/PSRAM cache, 64-byte lines); each
|
||||
* core accesses it through an instruction bus (ibus0/ibus1) and a data
|
||||
* bus (dbus0/dbus1). Only the CPU request buses are exposed here.
|
||||
*/
|
||||
|
||||
const cache_profile_counter_unit_t cache_periph_profile_counter_units[SOC_CACHE_CNT_UNITS_NUM] = {
|
||||
{
|
||||
.name = "l1-cache-inst-core0", .level = 1, .traffic = CACHE_PROFILE_TRAFFIC_INST, .core_id = 0,
|
||||
.counter_reg = {
|
||||
[CACHE_PROFILE_COUNTER_HIT] = CACHE_L1_IBUS0_ACS_HIT_CNT_REG,
|
||||
[CACHE_PROFILE_COUNTER_MISS] = CACHE_L1_IBUS0_ACS_MISS_CNT_REG,
|
||||
[CACHE_PROFILE_COUNTER_CONFLICT] = CACHE_L1_IBUS0_ACS_CONFLICT_CNT_REG,
|
||||
[CACHE_PROFILE_COUNTER_NXTLVL_RD] = CACHE_L1_IBUS0_ACS_NXTLVL_RD_CNT_REG,
|
||||
},
|
||||
},
|
||||
{
|
||||
.name = "l1-cache-inst-core1", .level = 1, .traffic = CACHE_PROFILE_TRAFFIC_INST, .core_id = 1,
|
||||
.counter_reg = {
|
||||
[CACHE_PROFILE_COUNTER_HIT] = CACHE_L1_IBUS1_ACS_HIT_CNT_REG,
|
||||
[CACHE_PROFILE_COUNTER_MISS] = CACHE_L1_IBUS1_ACS_MISS_CNT_REG,
|
||||
[CACHE_PROFILE_COUNTER_CONFLICT] = CACHE_L1_IBUS1_ACS_CONFLICT_CNT_REG,
|
||||
[CACHE_PROFILE_COUNTER_NXTLVL_RD] = CACHE_L1_IBUS1_ACS_NXTLVL_RD_CNT_REG,
|
||||
},
|
||||
},
|
||||
{
|
||||
.name = "l1-cache-data-core0", .level = 1, .traffic = CACHE_PROFILE_TRAFFIC_DATA, .core_id = 0,
|
||||
.counter_reg = {
|
||||
[CACHE_PROFILE_COUNTER_HIT] = CACHE_L1_DBUS0_ACS_HIT_CNT_REG,
|
||||
[CACHE_PROFILE_COUNTER_MISS] = CACHE_L1_DBUS0_ACS_MISS_CNT_REG,
|
||||
[CACHE_PROFILE_COUNTER_CONFLICT] = CACHE_L1_DBUS0_ACS_CONFLICT_CNT_REG,
|
||||
[CACHE_PROFILE_COUNTER_NXTLVL_RD] = CACHE_L1_DBUS0_ACS_NXTLVL_RD_CNT_REG,
|
||||
[CACHE_PROFILE_COUNTER_NXTLVL_WR] = CACHE_L1_DBUS0_ACS_NXTLVL_WR_CNT_REG,
|
||||
},
|
||||
},
|
||||
{
|
||||
.name = "l1-cache-data-core1", .level = 1, .traffic = CACHE_PROFILE_TRAFFIC_DATA, .core_id = 1,
|
||||
.counter_reg = {
|
||||
[CACHE_PROFILE_COUNTER_HIT] = CACHE_L1_DBUS1_ACS_HIT_CNT_REG,
|
||||
[CACHE_PROFILE_COUNTER_MISS] = CACHE_L1_DBUS1_ACS_MISS_CNT_REG,
|
||||
[CACHE_PROFILE_COUNTER_CONFLICT] = CACHE_L1_DBUS1_ACS_CONFLICT_CNT_REG,
|
||||
[CACHE_PROFILE_COUNTER_NXTLVL_RD] = CACHE_L1_DBUS1_ACS_NXTLVL_RD_CNT_REG,
|
||||
[CACHE_PROFILE_COUNTER_NXTLVL_WR] = CACHE_L1_DBUS1_ACS_NXTLVL_WR_CNT_REG,
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -12,6 +12,7 @@
|
||||
#include "soc/cache_reg.h"
|
||||
#include "soc/cache_struct.h"
|
||||
#include "soc/ext_mem_defs.h"
|
||||
#include "hal/cache_periph.h"
|
||||
#include "hal/cache_types.h"
|
||||
#include "hal/assert.h"
|
||||
#include "esp32s31/rom/cache.h"
|
||||
@@ -1088,6 +1089,60 @@ static inline uint32_t cache_ll_l1_get_access_error_intr_status(uint32_t cache_i
|
||||
return CACHE.l1_cache_acs_fail_int_st.val & mask;
|
||||
}
|
||||
|
||||
/*----------------------------------------------------------------------------
|
||||
Cache Profile Counter Related
|
||||
-----------------------------------------------------------------------------*/
|
||||
#define CACHE_LL_PROFILE_CNT_L1_ENA_MASK (CACHE_L1_IBUS0_CNT_ENA | CACHE_L1_IBUS1_CNT_ENA | \
|
||||
CACHE_L1_DBUS0_CNT_ENA | CACHE_L1_DBUS1_CNT_ENA)
|
||||
#define CACHE_LL_PROFILE_CNT_L1_CLR_MASK (CACHE_L1_IBUS0_CNT_CLR | CACHE_L1_IBUS1_CNT_CLR | \
|
||||
CACHE_L1_DBUS0_CNT_CLR | CACHE_L1_DBUS1_CNT_CLR)
|
||||
|
||||
/**
|
||||
* @brief Enable or disable the cache profile counters
|
||||
*
|
||||
* @param ena True to enable, false to disable
|
||||
*/
|
||||
__attribute__((always_inline))
|
||||
static inline void cache_ll_enable_profile_counter(bool ena)
|
||||
{
|
||||
if (ena) {
|
||||
REG_SET_BIT(CACHE_L1_CACHE_ACS_CNT_CTRL_REG, CACHE_LL_PROFILE_CNT_L1_ENA_MASK);
|
||||
} else {
|
||||
REG_CLR_BIT(CACHE_L1_CACHE_ACS_CNT_CTRL_REG, CACHE_LL_PROFILE_CNT_L1_ENA_MASK);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Reset all cache profile counters to zero
|
||||
*/
|
||||
__attribute__((always_inline))
|
||||
static inline void cache_ll_clear_profile_counter(void)
|
||||
{
|
||||
/* clear bits are write-to-trigger and self-clearing */
|
||||
REG_SET_BIT(CACHE_L1_CACHE_ACS_CNT_CTRL_REG, CACHE_LL_PROFILE_CNT_L1_CLR_MASK);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Read one counter of a cache profile counter unit
|
||||
*
|
||||
* @param unit Unit index, 0 to SOC_CACHE_CNT_UNITS_NUM - 1
|
||||
* @param counter Counter to read
|
||||
* @param[out] value Counter value, only written if the counter exists
|
||||
*
|
||||
* @return True if the unit has this counter, false otherwise
|
||||
*/
|
||||
__attribute__((always_inline))
|
||||
static inline bool cache_ll_get_profile_counter(int unit, cache_profile_counter_t counter, uint32_t *value)
|
||||
{
|
||||
HAL_ASSERT(unit < SOC_CACHE_CNT_UNITS_NUM);
|
||||
uint32_t reg = cache_periph_profile_counter_units[unit].counter_reg[counter];
|
||||
if (reg == 0) {
|
||||
return false;
|
||||
}
|
||||
*value = REG_READ(reg);
|
||||
return true;
|
||||
}
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
38
components/hal/include/hal/cache_periph.h
Normal file
38
components/hal/include/hal/cache_periph.h
Normal file
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include "soc/soc_caps.h"
|
||||
#include "hal/cache_types.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#if SOC_CACHE_CNT_SUPPORTED
|
||||
/**
|
||||
* @brief Description of one cache profile counter unit
|
||||
*
|
||||
* A unit is one set of counters observing one traffic stream, e.g. the
|
||||
* instruction fetches of core 0 into the L1 cache.
|
||||
*/
|
||||
typedef struct {
|
||||
const char *name; /*!< Short human-readable name */
|
||||
uint8_t level; /*!< Cache level (1 or 2) */
|
||||
cache_profile_traffic_t traffic; /*!< Kind of traffic observed */
|
||||
int8_t core_id; /*!< Originating core, -1 if unknown/mixed */
|
||||
uint32_t counter_reg[CACHE_PROFILE_COUNTER_MAX]; /*!< Counter registers; 0 if the unit
|
||||
does not have that counter */
|
||||
} cache_profile_counter_unit_t;
|
||||
|
||||
extern const cache_profile_counter_unit_t cache_periph_profile_counter_units[SOC_CACHE_CNT_UNITS_NUM];
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -6,12 +6,34 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include "esp_bit_defs.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Kind of traffic observed by a cache profile counter unit
|
||||
*/
|
||||
typedef enum {
|
||||
CACHE_PROFILE_TRAFFIC_INST, /*!< Instruction fetches */
|
||||
CACHE_PROFILE_TRAFFIC_DATA, /*!< Data accesses */
|
||||
CACHE_PROFILE_TRAFFIC_UNIFIED, /*!< Mixed/unknown (unified request bus) */
|
||||
} cache_profile_traffic_t;
|
||||
|
||||
/**
|
||||
* @brief One of the counters of a cache profile counter unit
|
||||
*/
|
||||
typedef enum {
|
||||
CACHE_PROFILE_COUNTER_HIT, /*!< Completed accesses ("hit" counter) */
|
||||
CACHE_PROFILE_COUNTER_MISS, /*!< Miss stall events ("miss" counter) */
|
||||
CACHE_PROFILE_COUNTER_CONFLICT, /*!< Requester conflicts */
|
||||
CACHE_PROFILE_COUNTER_NXTLVL_RD, /*!< Line fills from the next level */
|
||||
CACHE_PROFILE_COUNTER_NXTLVL_WR, /*!< Write-backs to the next level */
|
||||
CACHE_PROFILE_COUNTER_MAX,
|
||||
} cache_profile_counter_t;
|
||||
|
||||
typedef enum {
|
||||
CACHE_TYPE_DATA,
|
||||
CACHE_TYPE_INSTRUCTION,
|
||||
|
||||
@@ -423,6 +423,14 @@ config SOC_CACHE_FREEZE_SUPPORTED
|
||||
bool
|
||||
default y
|
||||
|
||||
config SOC_CACHE_CNT_SUPPORTED
|
||||
bool
|
||||
default y
|
||||
|
||||
config SOC_CACHE_CNT_UNITS_NUM
|
||||
int
|
||||
default 2
|
||||
|
||||
config SOC_CPU_CORES_NUM
|
||||
int
|
||||
default 1
|
||||
|
||||
@@ -155,6 +155,8 @@
|
||||
#define SOC_SHARED_IDCACHE_SUPPORTED 1 //Shared Cache for both instructions and data
|
||||
#define SOC_CACHE_WRITEBACK_SUPPORTED 1
|
||||
#define SOC_CACHE_FREEZE_SUPPORTED 1
|
||||
#define SOC_CACHE_CNT_SUPPORTED 1
|
||||
#define SOC_CACHE_CNT_UNITS_NUM 2 //Number of cache profile counter units
|
||||
|
||||
/*-------------------------- CPU CAPS ----------------------------------------*/
|
||||
#define SOC_CPU_CORES_NUM (1U)
|
||||
|
||||
@@ -375,6 +375,14 @@ config SOC_CACHE_FREEZE_SUPPORTED
|
||||
bool
|
||||
default y
|
||||
|
||||
config SOC_CACHE_CNT_SUPPORTED
|
||||
bool
|
||||
default y
|
||||
|
||||
config SOC_CACHE_CNT_UNITS_NUM
|
||||
int
|
||||
default 2
|
||||
|
||||
config SOC_CPU_CORES_NUM
|
||||
int
|
||||
default 1
|
||||
|
||||
@@ -140,6 +140,8 @@
|
||||
/*-------------------------- CACHE CAPS --------------------------------------*/
|
||||
#define SOC_SHARED_IDCACHE_SUPPORTED 1 //Shared Cache for both instructions and data
|
||||
#define SOC_CACHE_FREEZE_SUPPORTED 1
|
||||
#define SOC_CACHE_CNT_SUPPORTED 1
|
||||
#define SOC_CACHE_CNT_UNITS_NUM 2 //Number of cache profile counter units
|
||||
|
||||
/*-------------------------- CPU CAPS ----------------------------------------*/
|
||||
#define SOC_CPU_CORES_NUM (1U)
|
||||
|
||||
@@ -303,6 +303,14 @@ config SOC_CACHE_FREEZE_SUPPORTED
|
||||
bool
|
||||
default y
|
||||
|
||||
config SOC_CACHE_CNT_SUPPORTED
|
||||
bool
|
||||
default y
|
||||
|
||||
config SOC_CACHE_CNT_UNITS_NUM
|
||||
int
|
||||
default 2
|
||||
|
||||
config SOC_CPU_CORES_NUM
|
||||
int
|
||||
default 1
|
||||
|
||||
@@ -117,6 +117,8 @@
|
||||
#define SOC_SHARED_IDCACHE_SUPPORTED 1 //Shared Cache for both instructions and data
|
||||
#define SOC_CACHE_WRITEBACK_SUPPORTED 1
|
||||
#define SOC_CACHE_FREEZE_SUPPORTED 1
|
||||
#define SOC_CACHE_CNT_SUPPORTED 1
|
||||
#define SOC_CACHE_CNT_UNITS_NUM 2 //Number of cache profile counter units
|
||||
|
||||
/*-------------------------- CPU CAPS ----------------------------------------*/
|
||||
#define SOC_CPU_CORES_NUM (1U)
|
||||
|
||||
@@ -379,6 +379,14 @@ config SOC_CACHE_FREEZE_SUPPORTED
|
||||
bool
|
||||
default y
|
||||
|
||||
config SOC_CACHE_CNT_SUPPORTED
|
||||
bool
|
||||
default y
|
||||
|
||||
config SOC_CACHE_CNT_UNITS_NUM
|
||||
int
|
||||
default 2
|
||||
|
||||
config SOC_CPU_CORES_NUM
|
||||
int
|
||||
default 1
|
||||
|
||||
@@ -161,6 +161,8 @@
|
||||
/*-------------------------- CACHE CAPS --------------------------------------*/
|
||||
#define SOC_SHARED_IDCACHE_SUPPORTED 1 //Shared Cache for both instructions and data
|
||||
#define SOC_CACHE_FREEZE_SUPPORTED 1
|
||||
#define SOC_CACHE_CNT_SUPPORTED 1
|
||||
#define SOC_CACHE_CNT_UNITS_NUM 2 //Number of cache profile counter units
|
||||
|
||||
/*-------------------------- CPU CAPS ----------------------------------------*/
|
||||
#define SOC_CPU_CORES_NUM (1U)
|
||||
|
||||
@@ -351,6 +351,14 @@ config SOC_CACHE_FREEZE_SUPPORTED
|
||||
bool
|
||||
default y
|
||||
|
||||
config SOC_CACHE_CNT_SUPPORTED
|
||||
bool
|
||||
default y
|
||||
|
||||
config SOC_CACHE_CNT_UNITS_NUM
|
||||
int
|
||||
default 2
|
||||
|
||||
config SOC_CPU_CORES_NUM
|
||||
int
|
||||
default 1
|
||||
|
||||
@@ -145,6 +145,8 @@
|
||||
/*-------------------------- CACHE CAPS --------------------------------------*/
|
||||
#define SOC_SHARED_IDCACHE_SUPPORTED 1 //Shared Cache for both instructions and data
|
||||
#define SOC_CACHE_FREEZE_SUPPORTED 1
|
||||
#define SOC_CACHE_CNT_SUPPORTED 1
|
||||
#define SOC_CACHE_CNT_UNITS_NUM 2 //Number of cache profile counter units
|
||||
|
||||
/*-------------------------- CPU CAPS ----------------------------------------*/
|
||||
#define SOC_CPU_CORES_NUM (1U)
|
||||
|
||||
@@ -355,6 +355,14 @@ config SOC_CACHE_FREEZE_SUPPORTED
|
||||
bool
|
||||
default y
|
||||
|
||||
config SOC_CACHE_CNT_SUPPORTED
|
||||
bool
|
||||
default y
|
||||
|
||||
config SOC_CACHE_CNT_UNITS_NUM
|
||||
int
|
||||
default 4
|
||||
|
||||
config SOC_CPU_CORES_NUM
|
||||
int
|
||||
default 2
|
||||
|
||||
@@ -152,6 +152,8 @@
|
||||
/*-------------------------- CACHE CAPS --------------------------------------*/
|
||||
#define SOC_CACHE_WRITEBACK_SUPPORTED 1
|
||||
#define SOC_CACHE_FREEZE_SUPPORTED 1
|
||||
#define SOC_CACHE_CNT_SUPPORTED 1
|
||||
#define SOC_CACHE_CNT_UNITS_NUM 4 //Number of cache profile counter units
|
||||
|
||||
/*-------------------------- CPU CAPS ----------------------------------------*/
|
||||
#define SOC_CPU_CORES_NUM (2U)
|
||||
|
||||
@@ -507,6 +507,14 @@ config SOC_CACHE_FREEZE_SUPPORTED
|
||||
bool
|
||||
default y
|
||||
|
||||
config SOC_CACHE_CNT_SUPPORTED
|
||||
bool
|
||||
default y
|
||||
|
||||
config SOC_CACHE_CNT_UNITS_NUM
|
||||
int
|
||||
default 6
|
||||
|
||||
config SOC_CACHE_INTERNAL_MEM_VIA_L1CACHE
|
||||
bool
|
||||
default y
|
||||
|
||||
@@ -174,6 +174,8 @@
|
||||
#define SOC_SHARED_IDCACHE_SUPPORTED 1 //Shared Cache for both instructions and data
|
||||
#define SOC_CACHE_WRITEBACK_SUPPORTED 1
|
||||
#define SOC_CACHE_FREEZE_SUPPORTED 1
|
||||
#define SOC_CACHE_CNT_SUPPORTED 1
|
||||
#define SOC_CACHE_CNT_UNITS_NUM 6 //Number of cache profile counter units
|
||||
#define SOC_CACHE_INTERNAL_MEM_VIA_L1CACHE 1
|
||||
|
||||
/*-------------------------- CPU CAPS ----------------------------------------*/
|
||||
|
||||
@@ -491,6 +491,14 @@ config SOC_CACHE_FREEZE_SUPPORTED
|
||||
bool
|
||||
default y
|
||||
|
||||
config SOC_CACHE_CNT_SUPPORTED
|
||||
bool
|
||||
default y
|
||||
|
||||
config SOC_CACHE_CNT_UNITS_NUM
|
||||
int
|
||||
default 4
|
||||
|
||||
config SOC_CPU_CORES_NUM
|
||||
int
|
||||
default 2
|
||||
|
||||
@@ -164,6 +164,8 @@
|
||||
/*-------------------------- CACHE CAPS --------------------------------------*/
|
||||
#define SOC_CACHE_WRITEBACK_SUPPORTED 1
|
||||
#define SOC_CACHE_FREEZE_SUPPORTED 1
|
||||
#define SOC_CACHE_CNT_SUPPORTED 1
|
||||
#define SOC_CACHE_CNT_UNITS_NUM 4 //Number of cache profile counter units
|
||||
|
||||
/*-------------------------- CPU CAPS ----------------------------------------*/
|
||||
#define SOC_CPU_CORES_NUM (2U)
|
||||
|
||||
@@ -133,6 +133,8 @@ COEXISTENCE_DOCS = ['api-guides/coexist.rst']
|
||||
|
||||
MM_SYNC_DOCS = ['api-reference/system/mm_sync.rst']
|
||||
|
||||
CACHE_CNT_DOCS = ['api-reference/system/cache_cnt.rst']
|
||||
|
||||
CAMERA_DOCS = ['api-reference/peripherals/camera_driver.rst']
|
||||
|
||||
BITSCRAMBLER_DOCS = ['api-reference/peripherals/bitscrambler.rst']
|
||||
@@ -378,6 +380,7 @@ conditional_include_dict = {
|
||||
'SOC_SUPPORT_COEXISTENCE': COEXISTENCE_DOCS,
|
||||
'SOC_PSRAM_DMA_CAPABLE': MM_SYNC_DOCS,
|
||||
'SOC_CACHE_INTERNAL_MEM_VIA_L1CACHE': MM_SYNC_DOCS,
|
||||
'SOC_CACHE_CNT_SUPPORTED': CACHE_CNT_DOCS,
|
||||
'SOC_CLK_TREE_SUPPORTED': CLK_TREE_DOCS,
|
||||
'SOC_UART_SUPPORTED': UART_DOCS,
|
||||
'SOC_UHCI_SUPPORTED': UHCI_DOCS,
|
||||
|
||||
@@ -236,6 +236,7 @@ INPUT = \
|
||||
$(PROJECT_PATH)/components/esp_https_ota/include/esp_https_ota.h \
|
||||
$(PROJECT_PATH)/components/esp_https_server/include/esp_https_server.h \
|
||||
$(PROJECT_PATH)/components/esp_hw_support/etm/include/esp_etm.h \
|
||||
$(PROJECT_PATH)/components/esp_hw_support/include/esp_cache_cnt.h \
|
||||
$(PROJECT_PATH)/components/esp_hw_support/include/esp_clk_tree.h \
|
||||
$(PROJECT_PATH)/components/esp_hw_support/include/esp_chip_info.h \
|
||||
$(PROJECT_PATH)/components/esp_hw_support/include/esp_cpu.h \
|
||||
|
||||
47
docs/en/api-reference/system/cache_cnt.rst
Normal file
47
docs/en/api-reference/system/cache_cnt.rst
Normal file
@@ -0,0 +1,47 @@
|
||||
Cache Access Counters
|
||||
=====================
|
||||
|
||||
:link_to_translation:`zh_CN:[中文]`
|
||||
|
||||
Introduction
|
||||
------------
|
||||
|
||||
{IDF_TARGET_NAME} has hardware counters attached to the cache request buses. They record the number of completed cache accesses, miss stall events, requester conflicts, and cache lines transferred to and from the next level of the memory hierarchy. These counters can be used to measure the cache hit/miss behavior of a piece of code, for example to choose the placement of data in memory, or to find out why some code runs slower than expected.
|
||||
|
||||
Counter Units
|
||||
-------------
|
||||
|
||||
The set of counters differs between chips: the number of cache levels, the number of request buses per cache, and which counters exist per bus all vary. Instead of a fixed list of caches, the API exposes a chip-defined list of counter *units*. Each unit is one set of counters observing one traffic stream, for example instruction fetches from core 0 into the L1 cache. Applications enumerate the units at runtime using :cpp:func:`esp_cache_cnt_num_units` and :cpp:func:`esp_cache_cnt_get_unit_info`, so they keep working when a new chip adds or removes units.
|
||||
|
||||
Usage
|
||||
-----
|
||||
|
||||
1. Call :cpp:func:`esp_cache_cnt_start` to clear and enable all counters.
|
||||
2. Run the code to be measured.
|
||||
3. Call :cpp:func:`esp_cache_cnt_stop` to disable the counters, so that reading out and reporting the results is not counted as well.
|
||||
4. Call :cpp:func:`esp_cache_cnt_dump` to print a table of all counter values, or read the values of individual units with :cpp:func:`esp_cache_cnt_get`.
|
||||
|
||||
:cpp:func:`esp_cache_cnt_clear` resets the counters without changing whether they are running, which is useful when the counters have to stay enabled across measurement phases.
|
||||
|
||||
Counter Semantics
|
||||
-----------------
|
||||
|
||||
For each unit, :cpp:func:`esp_cache_cnt_get` returns:
|
||||
|
||||
- ``accesses``: the number of completed accesses. The hardware "hit" counter increments once for every access that completes, whether or not the access had to wait for a line fill first, so it is reported as the total access count.
|
||||
- ``stall_events``: incremented repeatedly while an access is stalled on a miss. This value grows with the total miss latency, not with the number of missed accesses, so it is only useful as a relative measure.
|
||||
- ``conflicts``: the number of conflicts between requesters on the cache.
|
||||
- ``line_fills``: the number of lines fetched from the next level of the memory hierarchy. This is the true miss count.
|
||||
- ``writebacks``: the number of lines written back to the next level. Only present for data traffic on chips with a write-back cache.
|
||||
|
||||
The miss ratio of a unit is therefore ``line_fills / accesses``, available as :cpp:func:`esp_cache_cnt_miss_ratio`. Not every counter exists for every unit; a field of :cpp:struct:`esp_cache_cnt_data_t` is only meaningful if the corresponding bit of the ``valid_mask`` member is set.
|
||||
|
||||
Application Examples
|
||||
--------------------
|
||||
|
||||
- :example:`system/cache_counters` runs a read workload over working sets of different sizes and placements, and prints the counter values after each run, showing how the working set size determines which level of the memory hierarchy serves the accesses.
|
||||
|
||||
API Reference
|
||||
-------------
|
||||
|
||||
.. include-build-file:: inc/esp_cache_cnt.inc
|
||||
@@ -11,6 +11,7 @@ System API
|
||||
app_trace
|
||||
esp_trace
|
||||
esp_function_with_shared_stack
|
||||
:SOC_CACHE_CNT_SUPPORTED: cache_cnt
|
||||
chip_revision
|
||||
console
|
||||
efuse
|
||||
|
||||
47
docs/zh_CN/api-reference/system/cache_cnt.rst
Normal file
47
docs/zh_CN/api-reference/system/cache_cnt.rst
Normal file
@@ -0,0 +1,47 @@
|
||||
缓存访问计数器
|
||||
==============
|
||||
|
||||
:link_to_translation:`en:[English]`
|
||||
|
||||
简介
|
||||
----
|
||||
|
||||
{IDF_TARGET_NAME} 的缓存请求总线上带有硬件计数器,用于记录已完成的缓存访问次数、未命中停顿事件、请求方冲突次数,以及与下一级存储之间传输的缓存行数量。借助这些计数器,可以测量某段代码的缓存命中/未命中情况,例如用于选择数据在内存中的放置位置,或分析某段代码运行速度不及预期的原因。
|
||||
|
||||
计数器单元
|
||||
----------
|
||||
|
||||
不同芯片提供的计数器有所不同:缓存层级数、每个缓存的请求总线数量,以及每条总线上存在哪些计数器都可能不同。因此,API 没有定义固定的缓存列表,而是提供由芯片定义的计数器 **单元** 列表。每个单元是观察同一数据流的一组计数器,例如 CPU0 向 L1 缓存发出的取指请求。应用程序可在运行时通过 :cpp:func:`esp_cache_cnt_num_units` 和 :cpp:func:`esp_cache_cnt_get_unit_info` 枚举这些单元,因此当新芯片增加或减少单元时,应用程序无需修改。
|
||||
|
||||
使用方法
|
||||
--------
|
||||
|
||||
1. 调用 :cpp:func:`esp_cache_cnt_start`,清零并使能所有计数器。
|
||||
2. 运行需要测量的代码。
|
||||
3. 调用 :cpp:func:`esp_cache_cnt_stop` 停止计数,这样读取和打印结果本身不会被计入。
|
||||
4. 调用 :cpp:func:`esp_cache_cnt_dump` 打印所有计数器数值的表格,或通过 :cpp:func:`esp_cache_cnt_get` 读取单个单元的计数值。
|
||||
|
||||
:cpp:func:`esp_cache_cnt_clear` 在不改变计数使能状态的情况下将计数器清零,适用于需要在多个测量阶段之间保持计数器使能的场景。
|
||||
|
||||
计数器含义
|
||||
----------
|
||||
|
||||
对于每个单元,:cpp:func:`esp_cache_cnt_get` 返回以下计数值:
|
||||
|
||||
- ``accesses``:已完成的访问次数。硬件的“命中”计数器在每次访问完成时加一,无论该访问是否先等待了缓存行填充,因此这里将其报告为总访问次数。
|
||||
- ``stall_events``:访问因未命中而停顿期间会反复递增。该数值随未命中总延迟增长,而不是未命中的访问次数,因此只能用作相对指标。
|
||||
- ``conflicts``:该缓存上请求方之间的冲突次数。
|
||||
- ``line_fills``:从下一级存储取回的缓存行数量。这是真正的未命中次数。
|
||||
- ``writebacks``:写回到下一级存储的缓存行数量。仅在具有写回型缓存的芯片上对数据流量提供。
|
||||
|
||||
因此,一个单元的未命中率为 ``line_fills / accesses``,可通过 :cpp:func:`esp_cache_cnt_miss_ratio` 计算。并非每个单元都具有全部计数器;只有当 :cpp:struct:`esp_cache_cnt_data_t` 的 ``valid_mask`` 成员中相应位被置位时,对应字段才有意义。
|
||||
|
||||
应用示例
|
||||
--------
|
||||
|
||||
- :example:`system/cache_counters` 对不同大小和位置的工作集运行读取负载,并在每次运行后打印计数器数值,展示工作集大小如何决定由存储层级中的哪一级来响应访问。
|
||||
|
||||
API 参考
|
||||
--------
|
||||
|
||||
.. include-build-file:: inc/esp_cache_cnt.inc
|
||||
@@ -11,6 +11,7 @@
|
||||
app_trace
|
||||
esp_trace
|
||||
esp_function_with_shared_stack
|
||||
:SOC_CACHE_CNT_SUPPORTED: cache_cnt
|
||||
chip_revision
|
||||
console
|
||||
efuse
|
||||
|
||||
@@ -5,6 +5,22 @@ examples/system/base_mac_address:
|
||||
- *common_components
|
||||
- esp_hw_support
|
||||
|
||||
examples/system/cache_counters:
|
||||
enable:
|
||||
- if: SOC_CACHE_CNT_SUPPORTED == 1
|
||||
disable:
|
||||
- if: CONFIG_NAME == "psram" and SOC_SPIRAM_SUPPORTED != 1
|
||||
disable_test:
|
||||
- if: IDF_TARGET == "esp32h21"
|
||||
temporary: true
|
||||
reason: lack of runners
|
||||
- if: CONFIG_NAME == "default" and SOC_SPIRAM_SUPPORTED == 1
|
||||
reason: the psram config subsumes the default config
|
||||
depends_components:
|
||||
- esp_hw_support
|
||||
- hal
|
||||
- soc
|
||||
|
||||
examples/system/deep_sleep:
|
||||
disable:
|
||||
- if: SOC_DEEP_SLEEP_SUPPORTED != 1
|
||||
|
||||
8
examples/system/cache_counters/CMakeLists.txt
Normal file
8
examples/system/cache_counters/CMakeLists.txt
Normal file
@@ -0,0 +1,8 @@
|
||||
# The following lines of boilerplate have to be in your project's CMakeLists
|
||||
# in this exact order for cmake to work correctly
|
||||
cmake_minimum_required(VERSION 3.22)
|
||||
|
||||
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
|
||||
# "Trim" the build. Include the minimal set of components, main, and anything it depends on.
|
||||
idf_build_set_property(MINIMAL_BUILD ON)
|
||||
project(cache_counters)
|
||||
69
examples/system/cache_counters/README.md
Normal file
69
examples/system/cache_counters/README.md
Normal file
@@ -0,0 +1,69 @@
|
||||
| Supported Targets | ESP32-C5 | ESP32-C6 | ESP32-C61 | ESP32-H2 | ESP32-H21 | ESP32-H4 | ESP32-P4 | ESP32-S31 |
|
||||
| ----------------- | -------- | -------- | --------- | -------- | --------- | -------- | -------- | --------- |
|
||||
|
||||
# Cache Access Counters Example
|
||||
|
||||
(See the README.md file in the upper level 'examples' directory for more information about examples.)
|
||||
|
||||
This example shows how to measure cache hit/miss statistics using the cache access counters present on chips with `SOC_CACHE_CNT_SUPPORTED`, via the `esp_cache_cnt.h` API (`esp_hw_support` component).
|
||||
|
||||
The API is chip-agnostic. Each chip defines a list of counter "units" — one unit is the set of counters observing one traffic stream, for example instruction fetches from core 0 into the L1 cache. The typical flow, which this example follows, is:
|
||||
|
||||
1. `esp_cache_cnt_start()` — clear and enable all counters.
|
||||
2. Run the code to be measured.
|
||||
3. `esp_cache_cnt_stop()` — disable the counters, so that reading out and reporting the results is not counted as well.
|
||||
4. `esp_cache_cnt_dump(NULL)` — print a table of all counter values; or, for programmatic access, enumerate the units with `esp_cache_cnt_num_units()` / `esp_cache_cnt_get_unit_info()` and read them with `esp_cache_cnt_get()`.
|
||||
|
||||
For each unit, the hardware counts completed accesses, miss stall events, requester conflicts, and lines transferred to/from the next level of the memory hierarchy. The number of line fills is the true miss count, so the miss ratio of a unit is `line_fills / accesses` (available as `esp_cache_cnt_miss_ratio()`). See the `esp_cache_cnt.h` documentation for the exact semantics of each counter.
|
||||
|
||||
The example runs the same read loop over buffers with different sizes and placements, and prints the counter values after each run. The working set determines which level of the memory hierarchy serves the accesses, which is visible in the counter values. The set of workloads depends on the target.
|
||||
|
||||
## How to use example
|
||||
|
||||
### Hardware Required
|
||||
|
||||
Any development board with a supported chip. To run the PSRAM workloads, the board must have PSRAM.
|
||||
|
||||
### Build and Flash
|
||||
|
||||
```
|
||||
idf.py set-target <target>
|
||||
idf.py -p PORT flash monitor
|
||||
```
|
||||
|
||||
(To exit the serial monitor, type ``Ctrl-]``.)
|
||||
|
||||
## Example Output
|
||||
|
||||
Output for the ESP32-P4 (a chip with a two-level cache hierarchy):
|
||||
|
||||
```
|
||||
Workload: flash rodata, 128 KB x 10 passes
|
||||
unit accesses line fills writebacks conflicts miss rate
|
||||
l1-icache-core0 132724 45 - 0 0.03%
|
||||
l1-icache-core1 2792 13 - 0 0.47%
|
||||
l1-dcache-core0 20714 20481 307 7 98.88%
|
||||
l1-dcache-core1 374 25 0 24606 6.68%
|
||||
l2-cache-inst 8 1 - 0 12.50%
|
||||
l2-cache-data 40960 1025 0 0 2.50%
|
||||
|
||||
Workload: PSRAM, 192 KB x 20 passes (fits in L2)
|
||||
unit accesses line fills writebacks conflicts miss rate
|
||||
l1-icache-core0 379266 24 - 0 0.01%
|
||||
l1-icache-core1 0 0 - 0 0.00%
|
||||
l1-dcache-core0 61674 60747 263 0 98.50%
|
||||
l1-dcache-core1 0 0 0 0 0.00%
|
||||
l2-cache-inst 3 1 - 0 33.33%
|
||||
l2-cache-data 121880 1152 1152 0 0.95%
|
||||
|
||||
Workload: PSRAM, 2 MB x 5 passes (exceeds L2)
|
||||
unit accesses line fills writebacks conflicts miss rate
|
||||
l1-icache-core0 1107734 4 - 0 0.00%
|
||||
l1-icache-core1 9302 25 - 0 0.27%
|
||||
l1-dcache-core0 164746 163921 167 154 99.50%
|
||||
l1-dcache-core1 1870 125 0 10600 6.68%
|
||||
l2-cache-inst 2 0 - 0 0.00%
|
||||
l2-cache-data 327680 80384 2481 0 24.53%
|
||||
|
||||
Cache counters example done
|
||||
```
|
||||
3
examples/system/cache_counters/main/CMakeLists.txt
Normal file
3
examples/system/cache_counters/main/CMakeLists.txt
Normal file
@@ -0,0 +1,3 @@
|
||||
idf_component_register(SRCS "cache_counters_example_main.c"
|
||||
PRIV_REQUIRES esp_psram
|
||||
INCLUDE_DIRS "")
|
||||
@@ -0,0 +1,114 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Unlicense OR CC0-1.0
|
||||
*/
|
||||
|
||||
/*
|
||||
* Cache access counters example.
|
||||
*
|
||||
* Runs several memory access workloads with a known cache footprint and uses
|
||||
* the esp_cache_cnt API to show how the cache responds. Which workloads run
|
||||
* depends on the target: reading flash goes through the cache on every chip,
|
||||
* internal SRAM only on chips with SOC_CACHE_INTERNAL_MEM_VIA_L1CACHE, and
|
||||
* the PSRAM workloads need PSRAM. Where there are two cache levels, the PSRAM
|
||||
* working sets are sized against both of them; where there is one, against
|
||||
* that one cache.
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <assert.h>
|
||||
#include "sdkconfig.h"
|
||||
#include "esp_err.h"
|
||||
#include "esp_heap_caps.h"
|
||||
#include "esp_memory_utils.h"
|
||||
#include "esp_cache_cnt.h"
|
||||
#include "hal/cache_hal.h"
|
||||
#include "soc/soc_caps.h"
|
||||
|
||||
/* Read-only data, so it is placed in .rodata and read from flash through the
|
||||
* cache. Larger than the data cache of any current chip, so that every pass
|
||||
* has to fetch the lines from flash again. */
|
||||
#define FLASH_BUF_SIZE (128 * 1024)
|
||||
static const uint32_t s_flash_buf[FLASH_BUF_SIZE / sizeof(uint32_t)];
|
||||
|
||||
/* Read one word from every cache line of the buffer, 'passes' times */
|
||||
static uint32_t read_workload(const uint32_t *buf, size_t size_bytes, int passes)
|
||||
{
|
||||
const size_t line_size = cache_hal_get_cache_line_size(1, CACHE_TYPE_DATA);
|
||||
const size_t words_per_line = line_size / sizeof(uint32_t);
|
||||
const volatile uint32_t *p = buf;
|
||||
uint32_t acc = 0;
|
||||
for (int pass = 0; pass < passes; pass++) {
|
||||
for (size_t i = 0; i < size_bytes / sizeof(uint32_t); i += words_per_line) {
|
||||
acc += p[i];
|
||||
}
|
||||
}
|
||||
return acc;
|
||||
}
|
||||
|
||||
/* Counting is stopped before the results are printed, so that the printing
|
||||
* itself does not show up in the counters. */
|
||||
static void run_phase(const char *name, const uint32_t *buf, size_t size, int passes)
|
||||
{
|
||||
ESP_ERROR_CHECK(esp_cache_cnt_start());
|
||||
read_workload(buf, size, passes);
|
||||
ESP_ERROR_CHECK(esp_cache_cnt_stop());
|
||||
|
||||
printf("Workload: %s\n", name);
|
||||
ESP_ERROR_CHECK(esp_cache_cnt_dump(NULL));
|
||||
printf("\n");
|
||||
}
|
||||
|
||||
void app_main(void)
|
||||
{
|
||||
/* Reading data out of flash always goes through the cache, on every chip. */
|
||||
assert(esp_ptr_in_drom(s_flash_buf));
|
||||
run_phase("flash rodata, 128 KB x 10 passes", s_flash_buf, FLASH_BUF_SIZE, 10);
|
||||
|
||||
#if SOC_CACHE_INTERNAL_MEM_VIA_L1CACHE
|
||||
/* Only on chips which have a cache in front of internal memory. Elsewhere
|
||||
* the CPU reaches internal SRAM directly and this workload would produce
|
||||
* no data cache traffic at all. */
|
||||
const size_t internal_size = 8 * 1024;
|
||||
uint32_t *internal_buf = heap_caps_malloc(internal_size, MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT);
|
||||
assert(internal_buf != NULL);
|
||||
memset(internal_buf, 0xA5, internal_size);
|
||||
run_phase("internal SRAM, 8 KB x 1000 passes", internal_buf, internal_size, 1000);
|
||||
free(internal_buf);
|
||||
#endif
|
||||
|
||||
#if SOC_CACHE_INTERNAL_MEM_VIA_L1CACHE && CONFIG_SPIRAM
|
||||
/* One PSRAM allocation, used with three different working set sizes:
|
||||
* smaller than the L1 data cache (64 KB), between L1 and L2 size,
|
||||
* and larger than the L2 cache. */
|
||||
const size_t small_size = 48 * 1024;
|
||||
const size_t mid_size = CONFIG_CACHE_L2_CACHE_SIZE * 3 / 8;
|
||||
const size_t big_size = CONFIG_CACHE_L2_CACHE_SIZE * 4;
|
||||
|
||||
uint32_t *psram_buf = heap_caps_malloc(big_size, MALLOC_CAP_SPIRAM);
|
||||
assert(psram_buf != NULL);
|
||||
memset(psram_buf, 0x5A, big_size);
|
||||
|
||||
run_phase("PSRAM, 48 KB x 100 passes (fits in L1)", psram_buf, small_size, 100);
|
||||
run_phase("PSRAM, 192 KB x 20 passes (fits in L2)", psram_buf, mid_size, 20);
|
||||
run_phase("PSRAM, 2 MB x 5 passes (exceeds L2)", psram_buf, big_size, 5);
|
||||
free(psram_buf);
|
||||
#elif CONFIG_SPIRAM
|
||||
/* Single cache level: one working set smaller than the cache and one
|
||||
* larger than it. */
|
||||
const size_t small_size = 8 * 1024;
|
||||
const size_t big_size = 512 * 1024;
|
||||
|
||||
uint32_t *psram_buf = heap_caps_malloc(big_size, MALLOC_CAP_SPIRAM);
|
||||
assert(psram_buf != NULL);
|
||||
memset(psram_buf, 0x5A, big_size);
|
||||
|
||||
run_phase("PSRAM, 8 KB x 500 passes (fits in cache)", psram_buf, small_size, 500);
|
||||
run_phase("PSRAM, 512 KB x 5 passes (exceeds cache)", psram_buf, big_size, 5);
|
||||
free(psram_buf);
|
||||
#endif
|
||||
|
||||
printf("Cache counters example done\n");
|
||||
}
|
||||
147
examples/system/cache_counters/pytest_cache_counters.py
Normal file
147
examples/system/cache_counters/pytest_cache_counters.py
Normal file
@@ -0,0 +1,147 @@
|
||||
# SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
|
||||
# SPDX-License-Identifier: CC0-1.0
|
||||
import pytest
|
||||
from pytest_embedded import Dut
|
||||
from pytest_embedded_idf.utils import idf_parametrize
|
||||
|
||||
# Names of the units observing core 0 instruction and data traffic, per target
|
||||
INST_UNIT = {
|
||||
'esp32c5': 'l1-cache-ibus',
|
||||
'esp32c6': 'l1-cache-ibus',
|
||||
'esp32c61': 'l1-cache-ibus',
|
||||
'esp32h2': 'l1-cache-ibus',
|
||||
'esp32h4': 'l1-cache-inst-core0',
|
||||
'esp32p4': 'l1-icache-core0',
|
||||
'esp32s31': 'l1-cache-inst-core0',
|
||||
}
|
||||
DATA_UNIT = {
|
||||
'esp32c5': 'l1-cache-dbus',
|
||||
'esp32c6': 'l1-cache-dbus',
|
||||
'esp32c61': 'l1-cache-dbus',
|
||||
'esp32h2': 'l1-cache-dbus',
|
||||
'esp32h4': 'l1-cache-data-core0',
|
||||
'esp32p4': 'l1-dcache-core0',
|
||||
'esp32s31': 'l1-cache-data-core0',
|
||||
}
|
||||
|
||||
# The one workload every target runs. Reading .rodata goes through the cache on
|
||||
# every chip, and 128 KB exceeds the data cache everywhere, so each pass has to
|
||||
# fetch the lines from flash again.
|
||||
FLASH_PHASE = 'flash rodata, 128 KB x 10 passes'
|
||||
|
||||
|
||||
def expect_counter_row(dut: Dut, unit: str) -> dict:
|
||||
"""Match one row of the esp_cache_cnt_dump() table, return the counter values.
|
||||
|
||||
Counters which the unit does not provide (printed as '-') are returned as None.
|
||||
"""
|
||||
num = r'(\d+|-)'
|
||||
match = dut.expect(rf'{unit} +{num} +{num} +{num} +{num} +[0-9.]+%')
|
||||
|
||||
def field(idx: int) -> int | None:
|
||||
value = match.group(idx).decode()
|
||||
return None if value == '-' else int(value)
|
||||
|
||||
return {
|
||||
'accesses': field(1),
|
||||
'line_fills': field(2),
|
||||
'writebacks': field(3),
|
||||
'conflicts': field(4),
|
||||
}
|
||||
|
||||
|
||||
def check_phase(dut: Dut, workload: str, nonzero: dict) -> None:
|
||||
"""Expect one 'Workload: ...' line followed by a counter table.
|
||||
|
||||
nonzero maps unit names (in dump order) to the list of counters which
|
||||
must read non-zero after the workload.
|
||||
"""
|
||||
dut.expect_exact(f'Workload: {workload}')
|
||||
for unit, counters in nonzero.items():
|
||||
row = expect_counter_row(dut, unit)
|
||||
for counter in counters:
|
||||
assert row[counter], f'{workload}: expected {unit} {counter} > 0, got {row[counter]}'
|
||||
|
||||
|
||||
@pytest.mark.generic
|
||||
@idf_parametrize('config', ['default'], indirect=['config'])
|
||||
@idf_parametrize('target', ['esp32c6', 'esp32h2'], indirect=['target'])
|
||||
def test_cache_counters(dut: Dut) -> None:
|
||||
# No PSRAM: only the flash workload runs. Its code is fetched from flash as
|
||||
# well, so the instruction unit counts accesses too.
|
||||
check_phase(
|
||||
dut,
|
||||
FLASH_PHASE,
|
||||
{
|
||||
INST_UNIT[dut.target]: ['accesses'],
|
||||
DATA_UNIT[dut.target]: ['accesses', 'line_fills'],
|
||||
},
|
||||
)
|
||||
dut.expect_exact('Cache counters example done')
|
||||
|
||||
|
||||
@pytest.mark.generic
|
||||
@idf_parametrize('config', ['psram'], indirect=['config'])
|
||||
@idf_parametrize('target', ['esp32c5', 'esp32c61', 'esp32h4', 'esp32s31'], indirect=['target'])
|
||||
def test_cache_counters_psram(dut: Dut) -> None:
|
||||
inst = INST_UNIT[dut.target]
|
||||
data = DATA_UNIT[dut.target]
|
||||
check_phase(dut, FLASH_PHASE, {inst: ['accesses'], data: ['accesses', 'line_fills']})
|
||||
check_phase(dut, 'PSRAM, 8 KB x 500 passes (fits in cache)', {inst: ['accesses'], data: ['accesses']})
|
||||
# A working set larger than the cache must be fetched from PSRAM on every pass
|
||||
check_phase(dut, 'PSRAM, 512 KB x 5 passes (exceeds cache)', {inst: ['accesses'], data: ['accesses', 'line_fills']})
|
||||
dut.expect_exact('Cache counters example done')
|
||||
|
||||
|
||||
@pytest.mark.generic
|
||||
@idf_parametrize('config', ['psram'], indirect=['config'])
|
||||
@idf_parametrize('target', ['esp32p4'], indirect=['target'])
|
||||
def test_cache_counters_psram_esp32p4(dut: Dut) -> None:
|
||||
# 128 KB is larger than the L1 data cache but fits in L2, so L1 refills on
|
||||
# every pass while L2 only has to fill once
|
||||
check_phase(
|
||||
dut,
|
||||
FLASH_PHASE,
|
||||
{
|
||||
'l1-icache-core0': ['accesses'],
|
||||
'l1-dcache-core0': ['accesses', 'line_fills'],
|
||||
'l2-cache-data': ['accesses'],
|
||||
},
|
||||
)
|
||||
# Internal SRAM is reached through the L1 cache on this chip, and the whole
|
||||
# working set stays in it, so the L1 data unit counts accesses but no fills
|
||||
check_phase(
|
||||
dut,
|
||||
'internal SRAM, 8 KB x 1000 passes',
|
||||
{
|
||||
'l1-icache-core0': ['accesses'],
|
||||
'l1-dcache-core0': ['accesses'],
|
||||
},
|
||||
)
|
||||
check_phase(
|
||||
dut,
|
||||
'PSRAM, 48 KB x 100 passes (fits in L1)',
|
||||
{
|
||||
'l1-icache-core0': ['accesses'],
|
||||
'l1-dcache-core0': ['accesses'],
|
||||
},
|
||||
)
|
||||
# Larger than the L1 data cache: every pass refills L1 from L2
|
||||
check_phase(
|
||||
dut,
|
||||
'PSRAM, 192 KB x 20 passes (fits in L2)',
|
||||
{
|
||||
'l1-dcache-core0': ['accesses', 'line_fills'],
|
||||
'l2-cache-data': ['accesses'],
|
||||
},
|
||||
)
|
||||
# Larger than the L2 cache: lines are continuously fetched from PSRAM
|
||||
check_phase(
|
||||
dut,
|
||||
'PSRAM, 2 MB x 5 passes (exceeds L2)',
|
||||
{
|
||||
'l1-dcache-core0': ['accesses', 'line_fills'],
|
||||
'l2-cache-data': ['accesses', 'line_fills'],
|
||||
},
|
||||
)
|
||||
dut.expect_exact('Cache counters example done')
|
||||
1
examples/system/cache_counters/sdkconfig.ci.default
Normal file
1
examples/system/cache_counters/sdkconfig.ci.default
Normal file
@@ -0,0 +1 @@
|
||||
# Build with sdkconfig.defaults only (no PSRAM)
|
||||
1
examples/system/cache_counters/sdkconfig.ci.psram
Normal file
1
examples/system/cache_counters/sdkconfig.ci.psram
Normal file
@@ -0,0 +1 @@
|
||||
CONFIG_SPIRAM=y
|
||||
2
examples/system/cache_counters/sdkconfig.defaults
Normal file
2
examples/system/cache_counters/sdkconfig.defaults
Normal file
@@ -0,0 +1,2 @@
|
||||
# No target-independent default options; per-target options are in
|
||||
# sdkconfig.defaults.<target>. This file must exist for those to be applied.
|
||||
@@ -0,0 +1,2 @@
|
||||
CONFIG_CACHE_L2_CACHE_512KB=y
|
||||
CONFIG_CACHE_L2_CACHE_LINE_128B=y
|
||||
Reference in New Issue
Block a user