feat(esp_riscv_trace): add riscv trace encoder driver with hal/ll layer

This commit is contained in:
Erhan Kurubas
2026-07-17 12:42:13 +02:00
parent 62317f4dee
commit 2c11810ad9
28 changed files with 2358 additions and 0 deletions
@@ -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));
}