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
16 changes: 16 additions & 0 deletions tools/ctrace/src/decode/DwtPacketDecoder.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,8 @@ constexpr std::uint32_t kPmuOverflowMask = 0xffU;

constexpr std::uint8_t kArmv7MFullPcBytes = 4U;
constexpr std::uint8_t kArmv7MAddressOffsetBytes = 2U;
constexpr std::uint8_t kArmv8MMatchBytes = 1U;
constexpr std::uint32_t kArmv8MMatchValue = 1U;

/** @brief Describes an invalid DWT event-counter payload. */
static std::string invalidEventCounterMessage(const DwtPayloadPacket& payload)
Expand Down Expand Up @@ -244,6 +246,20 @@ void DwtPacketDecoder::decodeDataTrace(const DwtPayloadPacket& payload, std::vec
event.quality = payload.quality;

if (packetType == DwtDataPacketType::Address) {
const auto isMatch = !secondarySubtype && payload.size == kArmv8MMatchBytes && payload.value == kArmv8MMatchValue;
if (isMatch) {
auto& pending = m_pendingDataTrace[comparator];
if (pending.has_value()) {
flushPending(comparator, qualityForPendingFlush(*pending, payload.quality), payload.tcyc, output);
}
TraceEvent match{DwtMatchTraceEvent{comparator}};
match.index = payload.index;
match.traceBusId = payload.traceBusId;
match.tcyc = payload.tcyc;
match.quality = payload.quality;
output.push_back(std::move(match));
return;
}
const auto expectedSize = secondarySubtype ? kArmv7MAddressOffsetBytes : kArmv7MFullPcBytes;
if (payload.size != expectedSize) {
auto flushed = flush(payload.quality, payload.tcyc);
Expand Down
12 changes: 9 additions & 3 deletions tools/ctrace/src/model/TraceEvent.h
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,11 @@ struct DwtAddressTraceEvent {
DwtAddressTraceLocation location;
};

/** @brief Reports that one DWT comparator generated a match without additional trace data. */
struct DwtMatchTraceEvent {
std::uint32_t comparator = 0;
};

/** @brief Returns the program counter carried by a DWT address event, if present. */
inline std::optional<std::uint32_t> dwtAddressPc(const DwtAddressTraceEvent& event)
{
Expand Down Expand Up @@ -230,9 +235,10 @@ struct TraceIssueEvent {
};

/** @brief Stores the semantic payload of a decoded trace event. */
using TraceEventPayload = std::variant<SoftwareTraceEvent, DwtDataTraceEvent, DwtAddressTraceEvent, ExceptionTraceEvent,
DwtEventTraceEvent, PmuTraceEvent, PcSampleTraceEvent, LocalTimestampTraceEvent,
GlobalTimestampTraceEvent, OverflowTraceEvent, SyncTraceEvent, TraceIssueEvent>;
using TraceEventPayload =
std::variant<SoftwareTraceEvent, DwtDataTraceEvent, DwtAddressTraceEvent, DwtMatchTraceEvent, ExceptionTraceEvent,
DwtEventTraceEvent, PmuTraceEvent, PcSampleTraceEvent, LocalTimestampTraceEvent,
GlobalTimestampTraceEvent, OverflowTraceEvent, SyncTraceEvent, TraceIssueEvent>;

/** @brief Describes timestamp and data-loss quality at an event. */
struct TraceQuality {
Expand Down
6 changes: 6 additions & 0 deletions tools/ctrace/src/model/TraceSelection.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,12 @@ static std::optional<TraceEventType> typeFor(const DwtAddressTraceEvent&)
return TraceEventType::Dwt;
}

/** @brief Maps a comparator-only DWT match payload to data trace output. */
static std::optional<TraceEventType> typeFor(const DwtMatchTraceEvent&)
{
return TraceEventType::Dwt;
}

/** @brief Maps an exception payload to its selectable event type. */
static std::optional<TraceEventType> typeFor(const ExceptionTraceEvent&)
{
Expand Down
2 changes: 2 additions & 0 deletions tools/ctrace/src/output/csv/CsvRowMapper.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,8 @@ static CsvRow eventToCsvRow(const TraceEvent& event)
if (const auto offset = dwtAddressOffset(*address)) {
row[column(CsvColumn::Offset)] = hexValue(*offset, 2);
}
} else if (const auto* match = traceEventPayload<DwtMatchTraceEvent>(event)) {
row[column(CsvColumn::Source)] = std::to_string(match->comparator);
} else if (const auto* exception = traceEventPayload<ExceptionTraceEvent>(event)) {
row[column(CsvColumn::Source)] = std::to_string(exception->number);
row[column(CsvColumn::Value)] = exceptionActionCsvValue(exception->action);
Expand Down
17 changes: 17 additions & 0 deletions tools/ctrace/src/output/ctf/CtfEncoder.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,10 @@ void CtfEncoder::writeEvent(const TraceEvent& event)
if (selected) {
writeDwtAddrEvent(event, *address);
}
} else if (const auto* match = traceEventPayload<DwtMatchTraceEvent>(event)) {
if (selected) {
writeDwtMatchEvent(event, *match);
}
} else if (const auto* counters = traceEventPayload<DwtEventTraceEvent>(event)) {
if (selected) {
writeDwtEvent(event, *counters);
Expand Down Expand Up @@ -363,6 +367,19 @@ void CtfEncoder::writeDwtAddrEvent(const TraceEvent& event, const DwtAddressTrac
});
}

void CtfEncoder::writeDwtMatchEvent(const TraceEvent& event, const DwtMatchTraceEvent& match)
{
constexpr auto payloadSize = 1U + 1U + 4U;
const auto eventTimestamp = allocateEventTimestamp(event.traceBusId);
const auto quality = computeSampleQuality(event);
m_stream.writeRecord(CtfSchema::value(CtfSchema::EventId::DwtMatch), eventTimestamp, event.traceBusId, payloadSize,
[&](CtfStreamWriter::Record& record) {
record.writeU8(static_cast<std::uint8_t>(match.comparator & 0xffU));
record.writeU8(quality.first);
record.writeU32(quality.second);
});
}

void CtfEncoder::writeDwtEvent(const TraceEvent& event, const DwtEventTraceEvent& counters)
{
constexpr auto payloadSize = 1U + 1U + 4U;
Expand Down
2 changes: 2 additions & 0 deletions tools/ctrace/src/output/ctf/CtfEncoder.h
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,8 @@ class CtfEncoder final {
void reportDwtSizeMismatch(const TraceEvent& event, const DwtDataTraceEvent& data, const ResolvedTraceSource* source);
/** @brief Encodes one DWT address event. */
void writeDwtAddrEvent(const TraceEvent& event, const DwtAddressTraceEvent& address);
/** @brief Encodes one comparator-only DWT match event. */
void writeDwtMatchEvent(const TraceEvent& event, const DwtMatchTraceEvent& match);
/** @brief Expands one DWT event-counter mask into individual CTF records. */
void writeDwtEvent(const TraceEvent& event, const DwtEventTraceEvent& counters);
/** @brief Expands one PMU trace-on-overflow mask into individual CTF records. */
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 @@ -440,6 +440,26 @@ event {
)";
}

/** @brief Writes the comparator-only DWT match event declaration. */
static void writeDwtMatchEvent(std::ostream& out)
{
out << R"(
event {
id = )"
<< CtfSchema::value(CtfSchema::EventId::DwtMatch) << R"(;
name = ")"
<< CtfSchema::eventName(CtfSchema::EventId::DwtMatch) << R"(";
stream_id = )"
<< CtfSchema::SwoStreamId << R"(;
fields := struct {
cmsis_dwt_comparator_t cmsis_dwt_comparator;
uint8_t cmsis_sample_flags;
uint32_t cmsis_overflow_count;
};
};
)";
}

/** @brief Writes the DWT event-counter declaration. */
static void writeDwtEvent(std::ostream& out)
{
Expand Down Expand Up @@ -565,6 +585,7 @@ void CtfMetadataWriter::write(const std::filesystem::path& outputDir, const std:
writeItmEvent(out);
writeDwtValueEvent(out);
writeDwtAddressEvent(out);
writeDwtMatchEvent(out);
writeDwtEvent(out);
writePmuEvent(out);
writeStatusEvents(out);
Expand Down
3 changes: 3 additions & 0 deletions tools/ctrace/src/output/ctf/CtfSchema.h
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ enum class EventId : std::uint32_t {
PcSample = 6U,
DwtEvent = 7U,
PmuEvent = 8U,
DwtMatch = 9U,
};

/** @brief Classifies CTF trace-status records. */
Expand Down Expand Up @@ -255,6 +256,8 @@ constexpr std::string_view eventName(EventId id)
return "DWT_EVENT";
case EventId::PmuEvent:
return "PMU_EVENT";
case EventId::DwtMatch:
return "DWT_MATCH";
}
return "UNKNOWN";
}
Expand Down
35 changes: 35 additions & 0 deletions tools/ctrace/src/output/ctf/TraceCompassXmlWriter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,30 @@ static std::string eventCounterHandlers(CtfSchema::EventId eventId, std::string_
return handlers.str();
}

/** @brief Generates one visible pulse for every comparator-only DWT match. */
static std::string dwtMatchHandler()
{
std::ostringstream handler;
handler << R"( <stateChange>
<stateAttribute type="constant" value=")"
<< CtfSchema::eventName(CtfSchema::EventId::DwtMatch) << R"(" />
<stateAttribute type="eventField" value="cmsis_dwt_comparator" />
<stateValue type="int" value="1" stack="push" />
</stateChange>
<stateChange>
<stateAttribute type="constant" value=")"
<< CtfSchema::eventName(CtfSchema::EventId::DwtMatch) << R"(" />
<stateAttribute type="eventField" value="cmsis_dwt_comparator" />
<stateValue type="null" stack="pop" />
<futureTime type="script" value="timestamp + )"
<< kEventPulseNanoseconds << R"(" scriptEngine="rhino">
<stateValue id="timestamp" type="eventField" value="timestamp" />
</futureTime>
</stateChange>
)";
return handler.str();
}

/** @brief Generates the Trace Compass state-provider definition. */
static std::string stateProviderXml()
{
Expand All @@ -159,6 +183,10 @@ static std::string stateProviderXml()
<eventHandler eventName=")"
<< CtfSchema::eventName(CtfSchema::EventId::Itm) << R"(">
)" << valueHandlers(CtfSchema::EventId::Itm, "itm", "cmsis_itm_channel", "value")
<< R"( </eventHandler>
<eventHandler eventName=")"
<< CtfSchema::eventName(CtfSchema::EventId::DwtMatch) << R"(">
)" << dwtMatchHandler()
<< R"( </eventHandler>
<eventHandler eventName=")"
<< CtfSchema::eventName(CtfSchema::EventId::DwtEvent) << R"(">
Expand Down Expand Up @@ -380,6 +408,13 @@ static std::string viewsXml()
<< CtfSchema::eventName(CtfSchema::EventId::DwtAddress)
<< '/' << R"(*"><display type="constant" value="address" /><name type="self" /></entry>
</xyView>
<timeGraphView id="arm.cmsis.swo.tg.dwt_match.v1">
<head><analysis id="arm.cmsis.swo.analysis.v1" /><label value="DWT Match" /></head>
<definedValue name="Something happened" value="1" color="#F6BD16" />
<entry path=")"
<< CtfSchema::eventName(CtfSchema::EventId::DwtMatch)
<< '/' << R"(*" displayText="true"><display type="constant" value="1" /><name type="self" /></entry>
</timeGraphView>
<timeGraphView id="arm.cmsis.swo.tg.dwt_event.v1">
<head><analysis id="arm.cmsis.swo.analysis.v1" /><label value="DWT Event Counters" /></head>
)";
Expand Down
10 changes: 10 additions & 0 deletions tools/ctrace/test/data/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,16 @@ packet preservation and bitwise CTF expansion across the boundary.
- Raw capture excerpt: `97807dad2f69b1274df8960d3459426d1da4a6892d05e7623f3e16f06c5d85c8`
- Trace-run YAML: `a7b924d89854ac85e2751d1297ec78783fa12cb3fa54f5638691dd48d546a34e`

The `trace-match` fixture is completely synthetic. It was generated from the
Armv8-M ITM and DWT packet definitions and was not captured from real hardware.
It contains a hardware synchronization packet followed by one Data Trace Match
packet for each comparator 0 through 3 and local timestamps. The integration
test verifies the generated CSV rows, CTF records, labels, and Trace Compass
timeline configuration.

- Generated raw trace: `5cffb5803675dc02ecd5ed4939a42c660ad7cabd3542b8ca1506230e20d14a50`
- Generated trace-run YAML: `b40c10634b8ba335b14b75f0026758ad84dd68aaf68f0a1bbfd2a5745756c5e8`

`trace-run` contains only the small current-schema inputs needed by executable
tests. Reader unit tests cover only the fields consumed by ctrace. A C++
entry-point test creates a reviewable eight-byte ITM stream below the build tree
Expand Down
47 changes: 47 additions & 0 deletions tools/ctrace/test/data/trace-match/trace-match.ctrace-run.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
ctrace-run:
generated-by: synthetic Armv8-M DWT match fixture
ctrace-setup:
- timestamps:
clock: 1000000
data:
- location: 0x20000000
label: Match Comparator 0
output: match
- location: 0x20000004
label: Match Comparator 1
output: match
- location: 0x20000008
label: Match Comparator 2
output: match
- location: 0x2000000C
label: Match Comparator 3
output: match
ctrace-refs:
- ctrace-ref: data#0
type: dwt
label: Match Comparator 0
address: 0x20000000
size: 4
data-type: unsigned
source: 0
- ctrace-ref: data#1
type: dwt
label: Match Comparator 1
address: 0x20000004
size: 4
data-type: unsigned
source: 1
- ctrace-ref: data#2
type: dwt
label: Match Comparator 2
address: 0x20000008
size: 4
data-type: unsigned
source: 2
- ctrace-ref: data#3
type: dwt
label: Match Comparator 3
address: 0x2000000C
size: 4
data-type: unsigned
source: 3
Binary file added tools/ctrace/test/data/trace-match/trace-match.raw
Binary file not shown.
46 changes: 46 additions & 0 deletions tools/ctrace/test/integration/src/CtraceIntegTests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,52 @@ TEST_F(CtraceIntegTests, ExpandsDwtEventCountersAcrossCsvAndCtf)
"<label value=\"DWT Event Counters\" />");
}

TEST_F(CtraceIntegTests, ConvertsDwtMatchAcrossCsvAndCtf)
{
const auto fixtureDirectory = testDataDirectory() / "trace-match";
copyFixtureFile(fixtureDirectory, "trace-match.raw", "trace-match.SWO.raw");
copyFixtureFile(fixtureDirectory, "trace-match.ctrace-run.yml");

// This Armv8-M packet stream is completely synthetic and was generated from
// the architecture specification without a capture from real hardware.
constexpr std::array<unsigned char, 18U> expectedRaw{{
0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x80U, 0x45U, 0x01U, 0x10U,
0x55U, 0x01U, 0x20U, 0x65U, 0x01U, 0x30U, 0x75U, 0x01U, 0x40U,
}};
EXPECT_EQ(readBinaryFile(workDirectory() / "trace-match.SWO.raw"),
std::vector<unsigned char>(expectedRaw.begin(), expectedRaw.end()));

const auto result = run({"ctrace", workDirectory().string(), "--target", "trace-match", "--all"});
EXPECT_EQ(0, result.exitCode) << result.stderrText;
EXPECT_EQ("cycles,stream,type,source,value,pc,offset,note\n"
"1,,dwt,0,,,,\n"
"3,,dwt,1,,,,\n"
"6,,dwt,2,,,,\n"
"10,,dwt,3,,,,\n",
readTextFile(workDirectory() / "trace-match.SWO.csv"));

const auto records = CtfTestSupport::readCtfRecords(workDirectory() / "trace-match.ctf" / "stream_0");
std::vector<std::uint64_t> matchTimestamps;
std::vector<std::uint8_t> matchComparators;
for (const auto& record : records) {
if (record.id != CtfSchema::value(CtfSchema::EventId::DwtMatch)) {
continue;
}
ASSERT_EQ(record.payload.size(), 6U);
matchTimestamps.push_back(record.timestamp);
matchComparators.push_back(record.payload[0U]);
}
EXPECT_EQ(matchTimestamps, (std::vector<std::uint64_t>{1U, 3U, 6U, 10U}));
EXPECT_EQ(matchComparators, (std::vector<std::uint8_t>{0U, 1U, 2U, 3U}));

const auto metadata = readTextFile(workDirectory() / "trace-match.ctf" / "metadata");
expectContains(metadata, "name = \"DWT_MATCH\"");
expectContains(metadata, "\"Match Comparator 3\" = 3");
const auto xml = readTextFile(workDirectory() / "trace-match.SWO.traceanalysis.xml");
expectContains(xml, "<label value=\"DWT Match\" />");
expectContains(xml, "<definedValue name=\"Something happened\" value=\"1\"");
}

TEST_F(CtraceIntegTests, ConvertsCapturedDwtEventCountersAcrossOverflow)
{
const auto fixtureDirectory = testDataDirectory() / "trace-event";
Expand Down
42 changes: 42 additions & 0 deletions tools/ctrace/test/unit/src/decode/DwtPacketDecoderTests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -277,6 +277,48 @@ TEST(CtraceUnitTests, testDwtPacketDecoderPreservesRepeatedAddressFragments)
verify(9U, 0x1000U, 0x2000U);
}

TEST(CtraceUnitTests, testDwtPacketDecoderEmitsComparatorOnlyMatch)
{
DwtPacketDecoder decoder;
auto payload = dwtPayload(12U, 1U, 1U, 17U, 3U, 99U);
payload.quality = TraceQuality{true, false, 7U};

const auto packets = decoder.decode(payload);
ASSERT_EQ(packets.size(), 1U);
const auto* match = traceEventPayload<DwtMatchTraceEvent>(packets.front());
ASSERT_NE(match, nullptr);
EXPECT_EQ(match->comparator, 2U);
EXPECT_EQ(packets.front().index, 17U);
EXPECT_EQ(packets.front().traceBusId, 3U);
EXPECT_EQ(packets.front().tcyc, std::optional<std::uint64_t>(99U));
ASSERT_TRUE(packets.front().quality.has_value());
EXPECT_TRUE(packets.front().quality->overflow);
EXPECT_FALSE(packets.front().quality->timestampReliable);
EXPECT_EQ(packets.front().quality->overflowCount, 7U);
EXPECT_EQ(traceEventType(packets.front()), TraceEventType::Dwt);
EXPECT_EQ(CsvRowMapper::row(packets.front()), "99,3,dwt,2,,,,");
}

TEST(CtraceUnitTests, testDwtPacketDecoderFlushesPendingComparatorBeforeMatch)
{
DwtPacketDecoder decoder;
auto pc = dwtPayload(8U, 4U, 0x08001234U, 10U, 3U, 90U);
pc.quality.timestampReliable = true;
EXPECT_TRUE(decoder.decode(pc).empty());

auto match = dwtPayload(8U, 1U, 1U, 11U, 3U, 100U);
match.quality.timestampReliable = true;
const auto packets = decoder.decode(match);
ASSERT_EQ(packets.size(), 2U);
ASSERT_NE(traceEventPayload<DwtAddressTraceEvent>(packets[0]), nullptr);
EXPECT_EQ(dwtAddressPc(*traceEventPayload<DwtAddressTraceEvent>(packets[0])),
std::optional<std::uint32_t>(0x08001234U));
EXPECT_NE(traceEventPayload<DwtMatchTraceEvent>(packets[1]), nullptr);
EXPECT_EQ(packets[0].tcyc, std::optional<std::uint64_t>(100U));
EXPECT_EQ(packets[1].tcyc, std::optional<std::uint64_t>(100U));
EXPECT_TRUE(decoder.flush({}, 101U).empty());
}

TEST(CtraceUnitTests, testDwtPacketDecoderRejectsUnsupportedAddressWidths)
{
const auto verify = [](std::uint8_t discriminator, std::uint8_t size) {
Expand Down
Loading
Loading