mirror of
https://github.com/espressif/esp-idf.git
synced 2026-10-02 03:00:34 +03:00
feat(esp_riscv_trace): add riscv trace encoder driver with hal/ll layer
This commit is contained in:
@@ -0,0 +1,12 @@
|
||||
set(srcs "test_app_main.c"
|
||||
"test_riscv_trace_common.c"
|
||||
"test_riscv_trace_basic.c")
|
||||
|
||||
if(CONFIG_SOC_RISCV_TRACE_FILTER_SUPPORTED)
|
||||
list(APPEND srcs "test_riscv_trace_filter.c")
|
||||
endif()
|
||||
|
||||
idf_component_register(SRCS ${srcs}
|
||||
INCLUDE_DIRS "."
|
||||
PRIV_REQUIRES unity esp_riscv_trace esp_psram
|
||||
WHOLE_ARCHIVE)
|
||||
@@ -0,0 +1,57 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include "unity.h"
|
||||
#include "unity_test_runner.h"
|
||||
#include "esp_heap_caps.h"
|
||||
#include "soc/soc_caps.h"
|
||||
#include "esp_riscv_trace.h"
|
||||
|
||||
#define TEST_MEMORY_LEAK_THRESHOLD (-400)
|
||||
|
||||
static size_t before_free_8bit;
|
||||
static size_t before_free_32bit;
|
||||
|
||||
/* Clear any filter left set by a previous (possibly failed) test case, so each case starts with
|
||||
* the encoder tracing everything. Only the configured cores have an initialized handle.
|
||||
* No-op (ESP_ERR_NOT_SUPPORTED) on targets without a filter unit. */
|
||||
static void clear_all_filters(void)
|
||||
{
|
||||
esp_riscv_trace_filter_config_t filter_off = { .enable = false };
|
||||
for (int core = 0; core < SOC_CPU_CORES_NUM; core++) {
|
||||
if (CONFIG_ESP_RISCV_TRACE_CORE_MASK & (1 << core)) {
|
||||
esp_riscv_trace_set_filter((esp_riscv_trace_core_t)core, &filter_off);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void check_leak(size_t before_free, size_t after_free, const char *type)
|
||||
{
|
||||
ssize_t delta = after_free - before_free;
|
||||
printf("MALLOC_CAP_%s: Before %u bytes free, After %u bytes free (delta %d)\n",
|
||||
type, before_free, after_free, delta);
|
||||
TEST_ASSERT_MESSAGE(delta >= TEST_MEMORY_LEAK_THRESHOLD, "memory leak");
|
||||
}
|
||||
|
||||
void setUp(void)
|
||||
{
|
||||
clear_all_filters();
|
||||
before_free_8bit = heap_caps_get_free_size(MALLOC_CAP_8BIT);
|
||||
before_free_32bit = heap_caps_get_free_size(MALLOC_CAP_32BIT);
|
||||
}
|
||||
|
||||
void tearDown(void)
|
||||
{
|
||||
size_t after_free_8bit = heap_caps_get_free_size(MALLOC_CAP_8BIT);
|
||||
size_t after_free_32bit = heap_caps_get_free_size(MALLOC_CAP_32BIT);
|
||||
check_leak(before_free_8bit, after_free_8bit, "8BIT");
|
||||
check_leak(before_free_32bit, after_free_32bit, "32BIT");
|
||||
}
|
||||
|
||||
void app_main(void)
|
||||
{
|
||||
unity_run_menu();
|
||||
}
|
||||
@@ -0,0 +1,283 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "esp_log.h"
|
||||
#include "soc/soc.h"
|
||||
#include "unity.h"
|
||||
#include "esp_riscv_trace.h"
|
||||
#include "test_riscv_trace_common.h"
|
||||
|
||||
static const char *TAG = "riscv_trace_basic";
|
||||
|
||||
/* Large enough to wrap each core's buffer several times, so each physical side carries anchor tags. */
|
||||
#define TRACE_TEST_WORKLOAD_ITERATIONS 8192
|
||||
|
||||
/* Per-core buffer sizes, applied via the strong esp_riscv_trace_get_user_config() below to
|
||||
* demonstrate (and test) that each core's encoder is configured independently. */
|
||||
#define TRACE_CORE0_BUFFER_SIZE 2048
|
||||
#define TRACE_CORE1_BUFFER_SIZE 4096
|
||||
|
||||
static volatile uint32_t s_trace_sink;
|
||||
|
||||
/* Strong override of the weak startup hook: configure each core's encoder independently. Only the
|
||||
* buffer size differs here; everything else stays at the Kconfig defaults. */
|
||||
esp_riscv_trace_config_t esp_riscv_trace_get_user_config(int core_id)
|
||||
{
|
||||
esp_riscv_trace_config_t config = ESP_RISCV_TRACE_DEFAULT_CONFIG();
|
||||
config.buffer_size = (core_id == ESP_RISCV_TRACE_CORE_1) ? TRACE_CORE1_BUFFER_SIZE
|
||||
: TRACE_CORE0_BUFFER_SIZE;
|
||||
return config;
|
||||
}
|
||||
|
||||
typedef struct {
|
||||
size_t packets;
|
||||
size_t syncs;
|
||||
} trace_walk_result_t;
|
||||
|
||||
/* A branchy workload so the encoder emits many Format 1 (branch) packets. */
|
||||
static void IRAM_ATTR branchy_work(void)
|
||||
{
|
||||
uint32_t acc = s_trace_sink;
|
||||
for (volatile int i = 0; i < TRACE_TEST_WORKLOAD_ITERATIONS; i++) {
|
||||
if ((i & 3) == 0) {
|
||||
acc += (uint32_t)i;
|
||||
} else {
|
||||
acc ^= ((uint32_t)i << 1);
|
||||
}
|
||||
}
|
||||
s_trace_sink = acc;
|
||||
}
|
||||
|
||||
static bool addr_in_code(uint32_t addr)
|
||||
{
|
||||
/* A periodic sync can be taken anywhere the core executes */
|
||||
return (addr >= SOC_IROM_LOW && addr < SOC_IROM_HIGH) ||
|
||||
(addr >= SOC_IRAM_LOW && addr < SOC_IRAM_HIGH) ||
|
||||
(addr >= SOC_IROM_MASK_LOW && addr < SOC_IROM_MASK_HIGH);
|
||||
}
|
||||
|
||||
/* If payload is a sync packet, validate its full address is in a code region and return true. */
|
||||
static bool sync_addr_ok(esp_riscv_trace_core_t core, const uint8_t *payload, size_t payload_len)
|
||||
{
|
||||
uint32_t addr = 0;
|
||||
if (!decode_sync_pc(payload, payload_len, &addr)) {
|
||||
return false;
|
||||
}
|
||||
bool in_code = addr_in_code(addr);
|
||||
if (!in_code) {
|
||||
ESP_LOGE(TAG, "core %d: sync address 0x%08x not in a code region", (int)core, (unsigned)addr);
|
||||
}
|
||||
TEST_ASSERT_TRUE_MESSAGE(in_code, "sync address not in a code region");
|
||||
return true;
|
||||
}
|
||||
|
||||
static uint8_t *linearize_ring(const uint8_t *buf, size_t capacity, size_t head)
|
||||
{
|
||||
/* Linearize the ring into chronological order: [head..capacity) then [0..head). */
|
||||
uint8_t *lin = malloc(capacity);
|
||||
TEST_ASSERT_NOT_NULL(lin);
|
||||
memcpy(lin, buf + head, capacity - head);
|
||||
memcpy(lin + (capacity - head), buf, head);
|
||||
return lin;
|
||||
}
|
||||
|
||||
static bool find_anchor_boundary(const uint8_t *lin, size_t start, size_t end, size_t *offset)
|
||||
{
|
||||
/* Find the first anchor tag (>=14 zeros); the next byte is a clean packet boundary. */
|
||||
size_t zero_run = 0;
|
||||
bool at_boundary = false;
|
||||
*offset = start;
|
||||
while (*offset < end) {
|
||||
if (lin[*offset] == 0) {
|
||||
zero_run++;
|
||||
(*offset)++;
|
||||
if (zero_run >= TRACE_ANCHOR_ZERO_RUN_BYTES) {
|
||||
at_boundary = true;
|
||||
}
|
||||
} else if (at_boundary) {
|
||||
return true;
|
||||
} else {
|
||||
zero_run = 0;
|
||||
(*offset)++;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static trace_walk_result_t walk_packets(esp_riscv_trace_core_t core, const uint8_t *lin,
|
||||
size_t offset, size_t end)
|
||||
{
|
||||
trace_walk_result_t result = { 0 };
|
||||
bool have_index = false;
|
||||
uint16_t prev_index = 0;
|
||||
|
||||
while (offset < end) {
|
||||
if (lin[offset] == 0) {
|
||||
while (offset < end && lin[offset] == 0) {
|
||||
offset++;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
uint8_t plen = lin[offset] & 0x1F;
|
||||
if (plen < TRACE_PACKET_MIN_LEN || plen > TRACE_PACKET_MAX_LEN || offset + plen > end) {
|
||||
break; /* reached this side's newest end; stop cleanly */
|
||||
}
|
||||
TEST_ASSERT_EQUAL_UINT8_MESSAGE(0, lin[offset] >> 5, "reserved header bits set");
|
||||
uint16_t index = (uint16_t)(lin[offset + 1] | (lin[offset + 2] << 8));
|
||||
const uint8_t *payload = &lin[offset + TRACE_PACKET_PREFIX_SIZE];
|
||||
uint8_t format = payload[0] & 0x3;
|
||||
TEST_ASSERT_TRUE_MESSAGE(format == 1 || format == 2 || format == 3, "invalid packet format");
|
||||
if (have_index) {
|
||||
TEST_ASSERT_EQUAL_UINT16_MESSAGE((uint16_t)(prev_index + 1), index, "index not monotonic");
|
||||
}
|
||||
prev_index = index;
|
||||
have_index = true;
|
||||
if (sync_addr_ok(core, payload, plen - TRACE_PACKET_PREFIX_SIZE)) {
|
||||
result.syncs++;
|
||||
}
|
||||
offset += plen;
|
||||
result.packets++;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static trace_walk_result_t walk_side_from_anchor(esp_riscv_trace_core_t core, const uint8_t *lin,
|
||||
size_t start, size_t end)
|
||||
{
|
||||
trace_walk_result_t result = { 0 };
|
||||
size_t off = 0;
|
||||
if (find_anchor_boundary(lin, start, end, &off)) {
|
||||
result = walk_packets(core, lin, off, end);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
static trace_walk_result_t add_walk_results(trace_walk_result_t a, trace_walk_result_t b)
|
||||
{
|
||||
trace_walk_result_t result = {
|
||||
.packets = a.packets + b.packets,
|
||||
.syncs = a.syncs + b.syncs,
|
||||
};
|
||||
return result;
|
||||
}
|
||||
|
||||
/* Loop mode has no "wrapped" HW flag, so infer the wrap from data */
|
||||
static void validate_wrapped_stream(esp_riscv_trace_core_t core)
|
||||
{
|
||||
TEST_ASSERT_EQUAL_MESSAGE(ESP_RISCV_TRACE_MEM_LOOP, CONFIG_ESP_RISCV_TRACE_MEM_MODE,
|
||||
"test expects loop memory mode");
|
||||
TEST_ASSERT_EQUAL_MESSAGE(ESP_RISCV_TRACE_RESYNC_PACKET, CONFIG_ESP_RISCV_TRACE_RESYNC_MODE,
|
||||
"test expects packet resync mode");
|
||||
TEST_ASSERT_GREATER_THAN_UINT_MESSAGE(0, CONFIG_ESP_RISCV_TRACE_RESYNC_THRESHOLD,
|
||||
"resync threshold must be nonzero");
|
||||
|
||||
uint8_t *buf = NULL;
|
||||
size_t capacity = 0;
|
||||
size_t head = 0;
|
||||
TEST_ESP_OK(esp_riscv_trace_get_buffer(core, &buf, &capacity, &head));
|
||||
TEST_ASSERT_GREATER_THAN_UINT_MESSAGE(0, capacity, "trace buffer capacity is zero");
|
||||
TEST_ASSERT_LESS_OR_EQUAL_MESSAGE(capacity, head, "trace buffer head is out of range");
|
||||
TEST_ASSERT_TRUE_MESSAGE(buffer_has_wrapped(buf, capacity, head),
|
||||
"buffer did not wrap (shrink buffer or grow workload)");
|
||||
|
||||
/* The encoder keeps no packet/index continuity across the physical wrap (at capacity - head),
|
||||
* so decode each side from its own anchor tag instead of walking across it. */
|
||||
uint8_t *lin = linearize_ring(buf, capacity, head);
|
||||
const size_t mem_wrap = capacity - head;
|
||||
trace_walk_result_t before_wrap = walk_side_from_anchor(core, lin, 0, mem_wrap);
|
||||
trace_walk_result_t after_wrap = walk_side_from_anchor(core, lin, mem_wrap, capacity);
|
||||
trace_walk_result_t walk = add_walk_results(before_wrap, after_wrap);
|
||||
free(lin);
|
||||
|
||||
/* ~1 sync per `threshold` packets; each side may miss its first/last interval, so allow margin. */
|
||||
const size_t threshold = CONFIG_ESP_RISCV_TRACE_RESYNC_THRESHOLD;
|
||||
const size_t expected = walk.packets / threshold;
|
||||
ESP_LOGI(TAG, "core %d wrap: capacity %u, head %u, %u packets, %u sync (approx %u, tolerance -2/+1)",
|
||||
(int)core, (unsigned)capacity, (unsigned)head,
|
||||
(unsigned)walk.packets, (unsigned)walk.syncs, (unsigned)expected);
|
||||
TEST_ASSERT_GREATER_THAN_UINT_MESSAGE(0, walk.packets, "no packets walked from wrapped buffer");
|
||||
TEST_ASSERT_GREATER_THAN_UINT_MESSAGE(0, walk.syncs, "no sync survived in the wrapped buffer");
|
||||
TEST_ASSERT_TRUE_MESSAGE(walk.syncs + 2 >= expected && walk.syncs <= expected + 1,
|
||||
"packet-resync sync count does not match packets / threshold");
|
||||
}
|
||||
|
||||
static void __attribute__((unused)) core1_work_task(void *arg)
|
||||
{
|
||||
TaskHandle_t notify_to = (TaskHandle_t)arg;
|
||||
ulTaskNotifyTake(pdTRUE, portMAX_DELAY); /* wait until tracing is started */
|
||||
branchy_work();
|
||||
xTaskNotifyGive(notify_to); /* signal work done */
|
||||
vTaskSuspend(NULL); /* park until the test deletes us */
|
||||
}
|
||||
|
||||
/* End-to-end loop-buffer capture: start tracing, run a branchy workload that wraps the ring buffer
|
||||
* several times, then stop and check the captured stream (buffer wrapped, packets well-formed with
|
||||
* monotonic indices, sync addresses land in code, sync count ~ packets/threshold). Runs on core 0,
|
||||
* and on core 1 in parallel if it is in the configured core mask. */
|
||||
TEST_CASE("RISC-V trace loop capture", "[riscv_trace]")
|
||||
{
|
||||
TEST_ASSERT_EQUAL_MESSAGE(ESP_RISCV_TRACE_CORE_0, xPortGetCoreID(), "test must run on core 0");
|
||||
TEST_ASSERT_TRUE_MESSAGE(CONFIG_ESP_RISCV_TRACE_CORE_MASK & (1 << ESP_RISCV_TRACE_CORE_0),
|
||||
"core 0 must be in the configured trace core mask");
|
||||
const bool trace_core1 = (CONFIG_ESP_RISCV_TRACE_CORE_MASK & (1 << ESP_RISCV_TRACE_CORE_1)) != 0;
|
||||
|
||||
TaskHandle_t worker = NULL;
|
||||
if (trace_core1) {
|
||||
TEST_ASSERT_EQUAL(pdPASS, xTaskCreatePinnedToCore(core1_work_task, "trc_c1", 4096,
|
||||
xTaskGetCurrentTaskHandle(), 5, &worker, 1));
|
||||
}
|
||||
|
||||
TEST_ESP_OK(esp_riscv_trace_start(ESP_RISCV_TRACE_CORE_0));
|
||||
if (trace_core1) {
|
||||
TEST_ESP_OK(esp_riscv_trace_start(ESP_RISCV_TRACE_CORE_1));
|
||||
xTaskNotifyGive(worker); /* let core 1 run its workload */
|
||||
}
|
||||
|
||||
branchy_work(); /* core 0 workload */
|
||||
|
||||
if (trace_core1) {
|
||||
TEST_ASSERT_EQUAL(1, ulTaskNotifyTake(pdTRUE, pdMS_TO_TICKS(1000)));
|
||||
TEST_ESP_OK(esp_riscv_trace_stop(ESP_RISCV_TRACE_CORE_1, TRACE_TEST_STOP_TIMEOUT_US));
|
||||
}
|
||||
TEST_ESP_OK(esp_riscv_trace_stop(ESP_RISCV_TRACE_CORE_0, TRACE_TEST_STOP_TIMEOUT_US));
|
||||
|
||||
validate_trace_status(ESP_RISCV_TRACE_CORE_0);
|
||||
validate_wrapped_stream(ESP_RISCV_TRACE_CORE_0);
|
||||
if (trace_core1) {
|
||||
validate_trace_status(ESP_RISCV_TRACE_CORE_1);
|
||||
validate_wrapped_stream(ESP_RISCV_TRACE_CORE_1);
|
||||
vTaskDelete(worker);
|
||||
}
|
||||
}
|
||||
|
||||
/* Each core was set up by esp_riscv_trace_get_user_config() with a different buffer size, proving
|
||||
* the two encoders are configured independently rather than sharing one config. */
|
||||
TEST_CASE("RISC-V trace per-core independent config", "[riscv_trace]")
|
||||
{
|
||||
TEST_ASSERT_TRUE_MESSAGE(CONFIG_ESP_RISCV_TRACE_CORE_MASK & (1 << ESP_RISCV_TRACE_CORE_0),
|
||||
"core 0 must be in the configured trace core mask");
|
||||
if ((CONFIG_ESP_RISCV_TRACE_CORE_MASK & (1 << ESP_RISCV_TRACE_CORE_1)) == 0) {
|
||||
TEST_IGNORE_MESSAGE("per-core comparison needs core 1 in the trace core mask");
|
||||
}
|
||||
|
||||
uint8_t *buf0 = NULL, *buf1 = NULL;
|
||||
size_t cap0 = 0, cap1 = 0, head0 = 0, head1 = 0;
|
||||
TEST_ESP_OK(esp_riscv_trace_get_buffer(ESP_RISCV_TRACE_CORE_0, &buf0, &cap0, &head0));
|
||||
TEST_ESP_OK(esp_riscv_trace_get_buffer(ESP_RISCV_TRACE_CORE_1, &buf1, &cap1, &head1));
|
||||
|
||||
ESP_LOGI(TAG, "core 0 buffer %p cap %u, core 1 buffer %p cap %u",
|
||||
buf0, (unsigned)cap0, buf1, (unsigned)cap1);
|
||||
|
||||
TEST_ASSERT_EQUAL_UINT_MESSAGE(TRACE_CORE0_BUFFER_SIZE, cap0, "core 0 buffer size mismatch");
|
||||
TEST_ASSERT_EQUAL_UINT_MESSAGE(TRACE_CORE1_BUFFER_SIZE, cap1, "core 1 buffer size mismatch");
|
||||
TEST_ASSERT_NOT_EQUAL_MESSAGE(cap0, cap1, "cores ended up with identical config");
|
||||
TEST_ASSERT_NOT_EQUAL_MESSAGE(buf0, buf1, "cores share the same trace buffer");
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include "unity.h"
|
||||
#include "esp_riscv_trace.h"
|
||||
#include "test_riscv_trace_common.h"
|
||||
|
||||
bool decode_sync_pc(const uint8_t *payload, size_t payload_len, uint32_t *pc)
|
||||
{
|
||||
if (payload_len < TRACE_SYNC_PAYLOAD_BYTES) {
|
||||
return false;
|
||||
}
|
||||
if ((payload[0] & 0x3) != 3 || ((payload[0] >> 2) & 0x3) != 0) {
|
||||
return false;
|
||||
}
|
||||
/* Address is 31 bits starting at payload bit 6; byte address is addr << 1. */
|
||||
uint64_t v = 0;
|
||||
for (int i = 0; i < TRACE_SYNC_PAYLOAD_BYTES; i++) {
|
||||
v |= (uint64_t)payload[i] << (8 * i);
|
||||
}
|
||||
*pc = (uint32_t)((v >> 6) & 0x7FFFFFFFu) << 1;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool buffer_has_wrapped(const uint8_t *buf, size_t capacity, size_t head)
|
||||
{
|
||||
for (size_t i = head; i < capacity; i++) {
|
||||
if (buf[i] != 0) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void validate_trace_status(esp_riscv_trace_core_t core)
|
||||
{
|
||||
esp_riscv_trace_status_t status = { 0 };
|
||||
TEST_ESP_OK(esp_riscv_trace_get_status(core, &status));
|
||||
TEST_ASSERT_TRUE_MESSAGE(status.fifo_empty, "trace FIFO is not empty after stop");
|
||||
TEST_ASSERT_FALSE_MESSAGE(status.fifo_overflowed, "trace FIFO overflowed");
|
||||
TEST_ASSERT_NOT_EQUAL_MESSAGE(ESP_RISCV_TRACE_WORK_STATUS_LOST, status.work_status,
|
||||
"trace encoder entered lost state");
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
#include <stdbool.h>
|
||||
#include "esp_riscv_trace.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/* RISC-V trace packet framing: 1-byte header + 2-byte index, then payload (1-10B). */
|
||||
#define TRACE_PACKET_HEADER_SIZE 1 /* length + format/reserved byte */
|
||||
#define TRACE_PACKET_INDEX_SIZE 2 /* monotonic packet index */
|
||||
#define TRACE_PACKET_PREFIX_SIZE (TRACE_PACKET_HEADER_SIZE + TRACE_PACKET_INDEX_SIZE) /* bytes before payload */
|
||||
#define TRACE_PACKET_MIN_LEN 4 /* prefix (3) + >=1 payload byte */
|
||||
#define TRACE_PACKET_MAX_LEN 13 /* prefix (3) + <=10 payload bytes */
|
||||
#define TRACE_SYNC_PAYLOAD_BYTES 5
|
||||
#define TRACE_ANCHOR_ZERO_RUN_BYTES 14 /* an anchor tag is >=14 zero bytes */
|
||||
#define TRACE_TEST_STOP_TIMEOUT_US 1000000
|
||||
|
||||
/* If payload is a Format 3 / Subformat 0 (sync) packet with at least TRACE_SYNC_PAYLOAD_BYTES
|
||||
* payload bytes, decode its full PC into *pc and return true. Otherwise return false.
|
||||
* payload_len is the number of valid payload bytes (packet length minus the 3-byte header). */
|
||||
bool decode_sync_pc(const uint8_t *payload, size_t payload_len, uint32_t *pc);
|
||||
|
||||
/* True if any byte after the write head is non-zero, i.e. a wrap overwrote that region (the
|
||||
* driver pre-zeros the buffer, so there is no hardware "wrapped" flag in loop mode). */
|
||||
bool buffer_has_wrapped(const uint8_t *buf, size_t capacity, size_t head);
|
||||
|
||||
/* Assert the encoder stopped cleanly: FIFO empty, no overflow, not in the lost state. */
|
||||
void validate_trace_status(esp_riscv_trace_core_t core);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,179 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include <stdint.h>
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "esp_attr.h"
|
||||
#include "esp_log.h"
|
||||
#include "unity.h"
|
||||
#include "esp_riscv_trace.h"
|
||||
#include "test_riscv_trace_common.h"
|
||||
|
||||
static const char *TAG = "riscv_trace_filter";
|
||||
|
||||
/* Iterations per call. High enough that the filtered window alone still emits at least one sync
|
||||
* packet, but low enough that the full baseline workload (the three calls in capture_and_count:
|
||||
* noise, filtered, noise) fits the buffer without wrapping. */
|
||||
#define TRACE_FILTER_WORKLOAD_ITERATIONS 512
|
||||
|
||||
static volatile uint32_t s_filter_sink;
|
||||
static uintptr_t s_filtered_end;
|
||||
static uintptr_t s_noise_end;
|
||||
|
||||
static void __attribute__((noinline)) IRAM_ATTR filtered_work(void)
|
||||
{
|
||||
uint32_t acc = s_filter_sink;
|
||||
for (volatile int i = 0; i < TRACE_FILTER_WORKLOAD_ITERATIONS; i++) {
|
||||
if ((i & 3) == 0) {
|
||||
acc += (uint32_t)i;
|
||||
} else {
|
||||
acc ^= ((uint32_t)i << 1);
|
||||
}
|
||||
}
|
||||
s_filter_sink = acc;
|
||||
filtered_end:
|
||||
s_filtered_end = (uintptr_t) && filtered_end;
|
||||
}
|
||||
|
||||
static void __attribute__((noinline)) noise_work(void)
|
||||
{
|
||||
uint32_t acc = s_filter_sink;
|
||||
for (volatile int i = 0; i < TRACE_FILTER_WORKLOAD_ITERATIONS; i++) {
|
||||
if ((i & 3) == 0) {
|
||||
acc += (uint32_t)i + 0x55u;
|
||||
} else {
|
||||
acc ^= ((uint32_t)i << 1);
|
||||
}
|
||||
}
|
||||
s_filter_sink = acc;
|
||||
noise_end:
|
||||
s_noise_end = (uintptr_t) && noise_end;
|
||||
}
|
||||
|
||||
typedef struct {
|
||||
size_t packets; /* all framed packets */
|
||||
size_t total; /* sync packets */
|
||||
size_t in_filtered; /* sync PCs inside the filtered_work window */
|
||||
size_t in_noise; /* sync PCs inside the noise_work window */
|
||||
} filter_sync_counts_t;
|
||||
|
||||
static bool pc_in_window(uint32_t pc, uintptr_t start, uintptr_t end)
|
||||
{
|
||||
return pc >= start && pc <= end;
|
||||
}
|
||||
|
||||
/* Walk a non-wrapped snapshot [0, head) and classify each sync packet's PC by window. */
|
||||
static filter_sync_counts_t count_syncs_in_windows(const uint8_t *buf, size_t head)
|
||||
{
|
||||
filter_sync_counts_t c = { 0 };
|
||||
size_t off = 0;
|
||||
while (off < head) {
|
||||
if (buf[off] == 0) { /* skip anchor zero-runs between packets */
|
||||
while (off < head && buf[off] == 0) {
|
||||
off++;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
uint8_t plen = buf[off] & 0x1F;
|
||||
if (plen < TRACE_PACKET_MIN_LEN || plen > TRACE_PACKET_MAX_LEN || off + plen > head) {
|
||||
break;
|
||||
}
|
||||
c.packets++;
|
||||
uint32_t pc = 0;
|
||||
if (decode_sync_pc(&buf[off + TRACE_PACKET_PREFIX_SIZE], plen - TRACE_PACKET_PREFIX_SIZE, &pc)) {
|
||||
c.total++;
|
||||
if (pc_in_window(pc, (uintptr_t)&filtered_work, s_filtered_end)) {
|
||||
c.in_filtered++;
|
||||
} else if (pc_in_window(pc, (uintptr_t)&noise_work, s_noise_end)) {
|
||||
c.in_noise++;
|
||||
}
|
||||
}
|
||||
off += plen;
|
||||
}
|
||||
return c;
|
||||
}
|
||||
|
||||
static filter_sync_counts_t capture_and_count(esp_riscv_trace_core_t core)
|
||||
{
|
||||
TEST_ESP_OK(esp_riscv_trace_start(core));
|
||||
noise_work();
|
||||
filtered_work();
|
||||
noise_work();
|
||||
TEST_ESP_OK(esp_riscv_trace_stop(core, TRACE_TEST_STOP_TIMEOUT_US));
|
||||
validate_trace_status(core);
|
||||
|
||||
uint8_t *buf = NULL;
|
||||
size_t capacity = 0;
|
||||
size_t head = 0;
|
||||
TEST_ESP_OK(esp_riscv_trace_get_buffer(core, &buf, &capacity, &head));
|
||||
TEST_ASSERT_FALSE_MESSAGE(buffer_has_wrapped(buf, capacity, head),
|
||||
"filter capture wrapped. Reduce TRACE_FILTER_WORKLOAD_ITERATIONS.");
|
||||
return count_syncs_in_windows(buf, head);
|
||||
}
|
||||
|
||||
/* Verifies the address-window filter restricts tracing to a chosen PC range. Captures the same
|
||||
* workload twice. Filter off (baseline), then a window over filtered_work only. Checks the
|
||||
* filtered run produced fewer packets, with every sync inside the filtered window and none in the
|
||||
* noise window. */
|
||||
TEST_CASE("RISC-V trace address-window filter", "[riscv_trace]")
|
||||
{
|
||||
const esp_riscv_trace_core_t core = ESP_RISCV_TRACE_CORE_0;
|
||||
TEST_ASSERT_EQUAL_MESSAGE(ESP_RISCV_TRACE_CORE_0, xPortGetCoreID(), "test must run on core 0");
|
||||
|
||||
/* Prime: run each function once (not traced) to record its end-marker PC. */
|
||||
filtered_work();
|
||||
noise_work();
|
||||
TEST_ASSERT_GREATER_THAN_UINT(0, s_filtered_end - (uintptr_t)&filtered_work);
|
||||
TEST_ASSERT_GREATER_THAN_UINT(0, s_noise_end - (uintptr_t)&noise_work);
|
||||
|
||||
/* Baseline (filter off): all three calls traced. Gives the reference packet count that the
|
||||
* filtered run is compared against below. */
|
||||
esp_riscv_trace_filter_config_t filter_off = { .enable = false };
|
||||
TEST_ESP_OK(esp_riscv_trace_set_filter(core, &filter_off));
|
||||
filter_sync_counts_t base = capture_and_count(core);
|
||||
ESP_LOGI(TAG, "baseline: %u packets, %u sync (filtered %u, noise %u)",
|
||||
(unsigned)base.packets, (unsigned)base.total,
|
||||
(unsigned)base.in_filtered, (unsigned)base.in_noise);
|
||||
TEST_ASSERT_GREATER_THAN_UINT_MESSAGE(0, base.packets, "baseline produced no trace");
|
||||
|
||||
/* Filtered: AND-window over filtered_work only (P: iaddr>=start, S: iaddr<=end). */
|
||||
esp_riscv_trace_filter_config_t filter_window = {
|
||||
.enable = true,
|
||||
.match_comparators = true,
|
||||
.primary = {
|
||||
.input = ESP_RISCV_TRACE_FILTER_INPUT_IADDR,
|
||||
.function = ESP_RISCV_TRACE_FILTER_COMPARATOR_GE,
|
||||
.match_value = (uint32_t)(uintptr_t) &filtered_work,
|
||||
},
|
||||
.secondary = {
|
||||
.input = ESP_RISCV_TRACE_FILTER_INPUT_IADDR,
|
||||
.function = ESP_RISCV_TRACE_FILTER_COMPARATOR_LE,
|
||||
.match_value = (uint32_t)s_filtered_end,
|
||||
},
|
||||
.mode = ESP_RISCV_TRACE_FILTER_MODE_AND,
|
||||
};
|
||||
TEST_ESP_OK(esp_riscv_trace_set_filter(core, &filter_window));
|
||||
filter_sync_counts_t filtered = capture_and_count(core);
|
||||
ESP_LOGI(TAG, "filtered: %u packets, %u sync (filtered %u, noise %u)",
|
||||
(unsigned)filtered.packets, (unsigned)filtered.total,
|
||||
(unsigned)filtered.in_filtered, (unsigned)filtered.in_noise);
|
||||
/* The filter dropped the noise calls, so the filtered capture has fewer packets than the baseline. */
|
||||
TEST_ASSERT_GREATER_THAN_UINT_MESSAGE(0, filtered.packets, "filtered window produced no trace");
|
||||
TEST_ASSERT_LESS_THAN_UINT_MESSAGE(base.packets, filtered.packets,
|
||||
"filter did not reduce trace volume");
|
||||
/*
|
||||
Every sync is in the filtered window and none in the noise window.
|
||||
The workload guarantees >=1 sync in the window.
|
||||
*/
|
||||
TEST_ASSERT_GREATER_THAN_UINT_MESSAGE(0, filtered.in_filtered, "filtered: no sync located in filtered window");
|
||||
TEST_ASSERT_EQUAL_UINT_MESSAGE(filtered.in_filtered, filtered.total,
|
||||
"filtered: a sync was located outside the filtered window");
|
||||
TEST_ASSERT_EQUAL_UINT_MESSAGE(0, filtered.in_noise, "filtered: a noise-window sync was observed");
|
||||
|
||||
/* Clear the filter so it does not affect other test cases or re-runs. */
|
||||
TEST_ESP_OK(esp_riscv_trace_set_filter(core, &filter_off));
|
||||
}
|
||||
Reference in New Issue
Block a user