Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion tools/ctrace/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ For `ctrace .trace --target Board --all`, the supported input produces:
Board.ctf/
metadata
stream_0
Board.SWO.traceanalysis.xml # when retained streams use one clock domain; views are data-driven
Board.SWO.traceanalysis.xml # only with graphical data and one retained clock domain
```

Without an explicit format declaration, ctrace preserves the legacy SWO-only
Expand Down
3 changes: 2 additions & 1 deletion tools/ctrace/docs/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -212,7 +212,8 @@ lazily creates a stream writer for each formatted route that emits selected even
the backends: for example, CSV retains a DWT/PMU counter mask in one row while CTF expands it into individual records.

CTF finalization retains only emitted streams, then generates Trace Compass XML from their observed graphical topics.
This avoids empty views and invented durations for point events. Route identity stays separate from display labels,
Without graphical topics, it omits the XML entirely; point events remain in the CTF event table. This avoids invalid
empty analyses and invented durations. Route identity stays separate from display labels,
so equal processor names cannot merge views. Formatted routes retain distinct clock domains because the input contract
does not establish cross-route synchronization. Multi-clock data remains valid CTF but cannot safely drive the supported
reader's combined XML analysis.
Expand Down
14 changes: 9 additions & 5 deletions tools/ctrace/docs/constraints.md
Original file line number Diff line number Diff line change
Expand Up @@ -100,9 +100,11 @@ and producer integration remain tracked as unfinished work.

- CSV remains one combined file in semantic callback order. The unformatted route has an empty `stream` field;
formatted routes expose their architectural IDs. Type and stream filters affect output, not decoding or diagnostic
reporting. Ctrace deliberately names the seventh CSV column `address`; the currently published CMSIS-Toolbox trace
specification still says `offset`, and must be corrected to match this intended schema before the difference is
treated as standardized.
reporting. The seventh CSV column is `address`, as defined by the CMSIS-Toolbox trace specification.
- PC sampling distinguishes four-byte PC values from the one-byte `0x00` (`CPU Sleeping`) and Armv8-M `0xff`
(`Trace prohibited`) status markers. CSV leaves `pc` empty for both markers and writes their meaning in `note`;
all three remain selectable as `pcsample`. Markers preserve route, timestamp, and quality without creating an error
or data-loss boundary. Unsupported payloads remain errors; arbitrary raw `0xff` bytes are not PC-sampling markers.
- Formatted CTF stream files are created lazily as `stream_<id>` only for routes with selected semantic output. Every
emitted stream class references an explicit clock domain. When selected, the legacy unformatted path retains eager
`stream_0`, its UUID-optional `swo_clock` metadata form, and companion XML compatibility.
Expand All @@ -117,8 +119,10 @@ and producer integration remain tracked as unfinished work.
DWT/PMU overflow events, and processor sleep state as time graphs. Each block is emitted only if the completed stream
contains matching trace data; synthetic exception bootstrap records alone do not enable an exception block, and
`Processor State` specifically requires a sleep indication. ITM payloads, ordinary sampled
PCs, and trace-status records stay available through the CTF event table. Trace Compass XML has no data-driven
table-view type, so ctrace does not model these point records as artificial timelines.
PCs, trace-prohibited markers, and trace-status records stay available through the CTF event table. Trace Compass XML
has no data-driven table-view type, so ctrace does not model these point records as artificial timelines.
If no graphical topic remains after output filtering, no companion XML is generated and stale XML is removed;
an empty analysis is not a valid Trace Compass configuration. The CTF bundle remains available.
- `timestamps.clock` has no ctrace fallback. For every route selected for CTF, missing, null, invalid, zero, or
conflicting frequency is accepted for validation-only and CSV operation but prevents CTF generation with an Error.
A filter selecting no configured route requires no clock because it can emit no CTF stream. With `--all`, valid CSV
Expand Down
43 changes: 33 additions & 10 deletions tools/ctrace/docs/ctf-format.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ authoritative for trace input and `*.ctrace-run.yml` configuration.
- DWT data trace: [`DWT_VALUE`](#dwt_value-event-id-1), [`DWT_ADDR`](#dwt_addr-event-id-2), and
[`DWT_MATCH`](#dwt_match-event-id-9)
- Trace integrity: [`TRACE_STATUS`](#trace_status-event-id-3)
- Execution state: [`EXCEPTION`](#exception-event-id-4) and [`PC_SAMPLE`](#pc_sample-event-id-6)
- Execution state: [`EXCEPTION`](#exception-event-id-4), [`PC_SAMPLE`](#pc_sample-event-id-6), and
[`PC_SAMPLE_PROHIBITED`](#pc_sample_prohibited-event-id-10)
- Time correlation: [`GLOBAL_TIMESTAMP`](#global_timestamp-event-id-5)
- Profiling: [`DWT_EVENT`](#dwt_event-event-id-7) and [`PMU_EVENT`](#pmu_event-event-id-8)

Expand Down Expand Up @@ -71,9 +72,9 @@ The timestamp fields use the stream class's clock mapping. Trace loss is represe
than the CTF `events_discarded` counter, which is currently zero.

The optional `<solution-set>.<channel>.traceanalysis.xml` companion is stored next to the bundle. It is generated
only when the completed metadata retains at least one stream and all retained streams reference one clock domain,
because the supported Trace Compass reader cannot safely combine independent clocks. This limitation affects only
the generated visualization; a metadata-only or multi-clock CTF bundle remains valid.
only when at least one retained stream has graphical data and all retained streams reference one clock domain.
Trace Compass rejects empty analyses and cannot safely combine independent clocks. These restrictions affect only
the generated visualization; a metadata-only, point-event-only, or multi-clock CTF bundle remains valid.

## Event catalogue

Expand All @@ -89,6 +90,7 @@ the generated visualization; a metadata-only or multi-clock CTF bundle remains v
| Profiling | 7 | `DWT_EVENT` | Architectural DWT event-counter overflow |
| Profiling | 8 | `PMU_EVENT` | Programmable PMU counter overflow |
| DWT data trace | 9 | `DWT_MATCH` | Comparator match without additional data |
| Execution state | 10 | `PC_SAMPLE_PROHIBITED` | PC sampling reports that trace is prohibited |

## Common sample fields

Expand Down Expand Up @@ -264,8 +266,27 @@ State `0` denotes processor sleep and leaves the PC array empty. State `1` denot

PC samples are point observations and are available in the CTF event table. Trace Compass does not invent execution
duration between sampled PCs. If a sleep indication was emitted, the generated `Processor State` view opens a
`Sleep` interval; the next PC sample, overflow, or data loss closes it. Ordinary PC samples alone do not create this
view.
`Sleep` interval; the next PC sample, trace-prohibited marker, overflow, or data loss closes it. Ordinary PC samples
alone do not create this view.

### PC_SAMPLE_PROHIBITED (event ID 10)

```text
uint8_t cmsis_sample_flags
uint32_t cmsis_overflow_count
```

The Armv8-M one-byte DWT PC-sampling payload `0xff` reports `Trace prohibited`. It is a valid point observation,
not a PC address, decoder error, or data-loss boundary. Its timestamp and sample quality are preserved; the marker
does not increment the overflow count or reset exception state.

This additive event keeps the existing `PC_SAMPLE` binary layout and profile version unchanged. Adding another
`cmsis_pc_sample_state` value instead would change the PC array length and misalign consumers. Both event kinds remain
part of the `pcsample` output selection.

The event is visible in the CTF event table. If a `Processor State` view exists, it closes any open `Sleep` interval
for that route, leaving the state unknown. It neither establishes running state nor creates a trace-prohibited
duration or a graphical view by itself.

## Time-correlation events

Expand Down Expand Up @@ -325,11 +346,13 @@ events as generated XML tables. The companion XML contains only graphical views
- XY views for DWT values and data-address fragments.
- Time graphs for DWT matches, DWT and PMU counter pulses, decoded exception activity, and processor sleep.

ITM values, trace-status records, Global Timestamps, and ordinary PC samples stay in the standard event table because
they do not establish a duration. Each generalized route receives a separate graphical view. Its visible suffix is
the resolved processor name, if available; numeric Trace Bus IDs are omitted from visible labels but remain in the
ITM values, trace-status records, Global Timestamps, ordinary PC samples, and trace-prohibited markers stay in the
standard event table because they do not establish a duration. Each generalized route receives a separate graphical
view. Its visible suffix is the resolved processor name, if available; numeric Trace Bus IDs are omitted from visible
labels but remain in the
public CTF event context and internally in provider IDs and state queries. Routes and topics without corresponding
emitted data do not add graphical views.
emitted data do not add graphical views. When no graphical topic remains, ctrace omits the XML entirely and removes
any stale companion file; importing only the CTF bundle still provides the event table.

The production output planner conservatively assigns each formatted route a distinct clock domain and UUID, even
when configured frequencies match. After lazy stream projection, ctrace writes XML only if the completed bundle
Expand Down
27 changes: 22 additions & 5 deletions tools/ctrace/src/decode/DwtPacketDecoder.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,23 @@ static bool isSupportedAddressFragmentSize(std::uint8_t size)
return size == 1U || size == 2U || size == 4U;
}

/** @brief Classifies PC-sample payloads without confusing a four-byte PC with a one-byte marker. */
static std::optional<PcSampleKind> pcSampleKind(const DwtPayloadPacket& payload)
{
if (payload.size == 4U) {
return PcSampleKind::Pc;
}
if (payload.size == 1U) {
if (payload.value == 0U) {
return PcSampleKind::Sleep;
}
if (payload.value == 0xffU) {
return PcSampleKind::TraceProhibited;
}
}
return std::nullopt;
}

/** @brief Describes an invalid DWT event-counter payload. */
static std::string invalidEventCounterMessage(const DwtPayloadPacket& payload)
{
Expand Down Expand Up @@ -180,21 +197,21 @@ std::vector<TraceEvent> DwtPacketDecoder::decodeExceptionTrace(const DwtPayloadP
std::vector<TraceEvent> DwtPacketDecoder::decodePeriodicPcSample(const DwtPayloadPacket& payload)
{
auto output = flush(payload.quality, payload.tcyc);
const auto isPc = payload.size == 4U;
const auto isSleeping = payload.size == 1U && payload.value == 0U;
if (!isPc && !isSleeping) {
const auto kind = pcSampleKind(payload);
if (!kind.has_value()) {
output.push_back(makeDwtEvent(payload, TraceIssueEvent{
TraceIssueCode::UnsupportedDwtPcSamplePayload,
TraceIssueSeverity::Error,
"unsupported DWT PC-sample payload: size " + std::to_string(payload.size) +
", value " + std::to_string(payload.value) +
"; expected a 4-byte PC or a 1-byte zero sleep indication",
"; expected a 4-byte PC or a 1-byte marker (0x00: CPU Sleeping, 0xff: Trace prohibited)",
std::nullopt,
std::nullopt,
}));
return output;
}
output.push_back(makeDwtEvent(payload, PcSampleTraceEvent{payload.value, isSleeping}));
const auto pc = kind.value() == PcSampleKind::Pc ? payload.value : 0U;
Comment thread
jreineckearm marked this conversation as resolved.
output.push_back(makeDwtEvent(payload, PcSampleTraceEvent{pc, kind.value()}));
return output;
}

Expand Down
11 changes: 9 additions & 2 deletions tools/ctrace/src/model/TraceEvent.h
Original file line number Diff line number Diff line change
Expand Up @@ -217,10 +217,17 @@ struct PmuTraceEvent {
std::uint8_t overflowMask = 0;
};

/** @brief Contains a periodic DWT PC sample or its processor-sleep indication. */
/** @brief Distinguishes a sampled PC from the status markers carried by the same DWT packet. */
enum class PcSampleKind {
Pc,
Sleep,
TraceProhibited,
};

/** @brief Contains a periodic DWT PC sample or status marker; pc is meaningful only for kind Pc. */
struct PcSampleTraceEvent {
std::uint32_t pc = 0;
bool sleeping = false;
PcSampleKind kind = PcSampleKind::Pc;
};

/** @brief Marks a decoded local timestamp packet. */
Expand Down
10 changes: 9 additions & 1 deletion tools/ctrace/src/output/csv/CsvRowMapper.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -187,8 +187,16 @@ static void writePayloadColumns(CsvRow& row, const PmuTraceEvent& event)
/** @brief Writes one periodic PC sample to the CSV event columns. */
static void writePayloadColumns(CsvRow& row, const PcSampleTraceEvent& event)
{
if (!event.sleeping) {
switch (event.kind) {
case PcSampleKind::Pc:
row[column(CsvColumn::Pc)] = hexValue(event.pc, 4U);
break;
case PcSampleKind::Sleep:
row[column(CsvColumn::Note)] = "CPU Sleeping";
break;
case PcSampleKind::TraceProhibited:
row[column(CsvColumn::Note)] = "Trace prohibited";
break;
}
}

Expand Down
14 changes: 11 additions & 3 deletions tools/ctrace/src/output/ctf/CtfBundleOutput.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -291,12 +291,20 @@ void CtfBundleOutput::finalizeTraceCompassXml(const CtfMetadataModel& metadata)
return;
}

const auto viewRoutes = traceCompassViewRoutes(metadata);
const auto hasViews = std::any_of(viewRoutes.begin(), viewRoutes.end(), [](const auto& route) {
return route.views != 0U;
});
if (!hasViews) {
removeOutputFile(m_traceCompassXmlPath);
return;
}

if (metadata.isLegacySingleStreamLayout()) {
TraceCompassXmlWriter::writeLegacyFile(m_traceCompassXmlPath,
traceCompassViews(metadata, streams.front().streamClassId));
TraceCompassXmlWriter::writeLegacyFile(m_traceCompassXmlPath, viewRoutes.front().views);
return;
}
TraceCompassXmlWriter::writeRoutedFile(m_traceCompassXmlPath, traceCompassViewRoutes(metadata));
TraceCompassXmlWriter::writeRoutedFile(m_traceCompassXmlPath, viewRoutes);
}

void CtfBundleOutput::omitTraceCompassXml(std::size_t clockDomainCount)
Expand Down
4 changes: 2 additions & 2 deletions tools/ctrace/src/output/ctf/CtfBundleOutput.h
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@

class DiagnosticSink;

/** @brief Owns a CTF directory and its companion Trace Compass XML file. */
/** @brief Owns a CTF directory and its optional companion Trace Compass XML file. */
class CtfBundleOutput final : public TraceOutput {
public:
/**
Expand All @@ -40,7 +40,7 @@ class CtfBundleOutput final : public TraceOutput {
void prepareOutput() override;
/** @brief Starts the CTF encoder for the prepared target. */
void startOutput() override;
/** @brief Completes metadata and streams, then writes XML when their clocks permit it. */
/** @brief Completes the bundle and writes XML only for observed views sharing one clock. */
void stopOutput() override;
/** @brief Aborts the encoder and removes incomplete CTF and XML targets. */
void abortOutput() override;
Expand Down
22 changes: 14 additions & 8 deletions tools/ctrace/src/output/ctf/CtfEncoder.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -353,25 +353,31 @@ const CtfMetadataModel* CtfEncoder::completedMetadata() const noexcept

void CtfEncoder::writePcSampleEvent(const TraceEvent& event, const PcSampleTraceEvent& sample)
{
const auto pcSize = sample.sleeping ? 0U : 4U;
const auto payloadSize = 1U + pcSize + 1U + 4U;
const auto isPc = sample.kind == PcSampleKind::Pc;
const auto isSleeping = sample.kind == PcSampleKind::Sleep;
const auto isProhibited = sample.kind == PcSampleKind::TraceProhibited;
// A separate event preserves PC_SAMPLE's zero-or-one PC sequence length.
const auto pcSize = isPc ? 4U : 0U;
const auto payloadSize = (isProhibited ? 0U : 1U) + pcSize + 1U + 4U;
const auto eventTimestamp = allocateEventTimestamp(event.route);
const auto traceBusId = legacyCtfTraceBusId(event.route);
const auto quality = computeSampleQuality(event);
const auto state = CtfSchema::value(sample.sleeping ? CtfSchema::PcSampleState::Sleep
: CtfSchema::PcSampleState::Pc);
const auto eventId = isProhibited ? CtfSchema::EventId::PcSampleProhibited : CtfSchema::EventId::PcSample;
streamWriter(event.route)
.writeRecord(CtfSchema::value(CtfSchema::EventId::PcSample), eventTimestamp, traceBusId, payloadSize,
.writeRecord(CtfSchema::value(eventId), eventTimestamp, traceBusId, payloadSize,
[&](CtfStreamWriter::Record& record) {
record.writeU8(state);
if (!sample.sleeping) {
if (!isProhibited) {
record.writeU8(CtfSchema::value(isSleeping ? CtfSchema::PcSampleState::Sleep
: CtfSchema::PcSampleState::Pc));
}
if (isPc) {
record.writeU32(sample.pc);
}
record.writeU8(quality.first);
record.writeU32(quality.second);
});
const auto streamClassId = streamDescriptor(event.route).streamClassId;
if (sample.sleeping) {
if (isSleeping) {
m_metadata->observeGraphicalTopic(streamClassId, CtfGraphicalTopic::ProcessorState);
}
}
Expand Down
2 changes: 1 addition & 1 deletion tools/ctrace/src/output/ctf/CtfEncoder.h
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ class CtfEncoder final {
void writeDwtEvent(const TraceEvent& event, const DwtEventTraceEvent& counters);
/** @brief Expands one PMU trace-on-overflow mask into individual CTF records. */
void writePmuEvent(const TraceEvent& event, const PmuTraceEvent& counters);
/** @brief Encodes one periodic PC-sample or processor-sleep event. */
/** @brief Encodes one periodic PC sample, sleep indication, or trace-prohibited marker. */
void writePcSampleEvent(const TraceEvent& event, const PcSampleTraceEvent& sample);
/** @brief Encodes one reconstructed global timestamp event. */
void writeGlobalTimestampEvent(const TraceEvent& event, const GlobalTimestampTraceEvent& timestamp);
Expand Down
21 changes: 21 additions & 0 deletions tools/ctrace/src/output/ctf/CtfMetadataWriter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -563,6 +563,25 @@ event {
)";
}

/** @brief Writes the trace-prohibited PC-sampling marker declaration. */
static void writePcSampleProhibitedEvent(std::ostream& out, std::uint32_t streamClassId = CtfSchema::SwoStreamId)
{
out << R"(
event {
id = )"
<< CtfSchema::value(CtfSchema::EventId::PcSampleProhibited) << R"(;
name = ")"
<< CtfSchema::eventName(CtfSchema::EventId::PcSampleProhibited) << R"(";
stream_id = )"
<< streamClassId << R"(;
fields := struct {
uint8_t cmsis_sample_flags;
uint32_t cmsis_overflow_count;
};
};
)";
}

/** @brief Writes status, exception, and global timestamp declarations. */
static void writeStatusEvents(std::ostream& out, std::uint32_t streamClassId = CtfSchema::SwoStreamId,
std::string_view exceptionType = "cmsis_exception_number_t")
Expand Down Expand Up @@ -756,6 +775,7 @@ static void writeGeneralStreamSchemas(std::ostream& out, const CtfMetadataModel&
writePmuEvent(out, streamClassId);
writeStatusEvents(out, streamClassId, prefix + "_exception_number_t");
writePcSampleEvent(out, streamClassId);
writePcSampleProhibitedEvent(out, streamClassId);
}
}

Expand All @@ -781,6 +801,7 @@ void CtfMetadataWriter::write(const std::filesystem::path& outputDir, const CtfM
writePmuEvent(out);
writeStatusEvents(out);
writePcSampleEvent(out);
writePcSampleProhibitedEvent(out);
} else {
writeGeneralTraceEnvironment(out, model);
writeGeneralCommonTypes(out, model);
Expand Down
5 changes: 4 additions & 1 deletion tools/ctrace/src/output/ctf/CtfSchema.h
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ enum class EventId : std::uint32_t {
DwtEvent = 7U,
PmuEvent = 8U,
DwtMatch = 9U,
PcSampleProhibited = 10U,
};

/** @brief Classifies CTF trace-status records. */
Expand Down Expand Up @@ -62,7 +63,7 @@ enum class ExceptionOrigin : std::uint8_t {
Synthetic = 1U,
};

/** @brief Identifies whether a periodic PC sample carries a PC or reports processor sleep. */
/** @brief Encodes the zero-or-one PC sequence length in PC_SAMPLE records. */
enum class PcSampleState : std::uint8_t {
Sleep = 0U,
Pc = 1U,
Expand Down Expand Up @@ -297,6 +298,8 @@ constexpr std::string_view eventName(EventId id)
return "PMU_EVENT";
case EventId::DwtMatch:
return "DWT_MATCH";
case EventId::PcSampleProhibited:
return "PC_SAMPLE_PROHIBITED";
}
return "UNKNOWN";
}
Expand Down
Loading
Loading