From 1f6659a0a08ce4d792a5383e7d59256961468993 Mon Sep 17 00:00:00 2001 From: Jyotheeswar Ganne Date: Wed, 22 Jul 2026 03:36:34 -0600 Subject: [PATCH 1/6] Add compute_io_bound aie_dtrace core-tile metric set Adds a core-tile PC-range metric that measures Compute vs IO+Compute cycles via the two AIE core PC-range events (PC_Range_0-1 / PC_Range_2-3) on a single core tile (col 0, row 3). The wrapper PC is read from the col0/row0 elfs_metadata reloadable_elfs entry (all values must match). Configurable through profiling_runtime_config aie_tile and the new AIE_dtrace_settings.tile_based_aie_metrics option, and emitted alongside interface-tile bandwidth metrics in the same per-run CT file. Co-authored-by: Cursor --- .../filetypes/base_filetype_impl.h | 38 +++ .../plugin/aie_dtrace/aie_dtrace_metadata.cpp | 78 ++++++- .../plugin/aie_dtrace/aie_dtrace_metadata.h | 24 +- .../aie_dtrace/ve2/aie_dtrace_ct_writer.cpp | 217 +++++++++++++++--- .../aie_dtrace/ve2/aie_dtrace_ct_writer.h | 114 ++++++++- .../plugin/aie_dtrace/ve2/aie_dtrace_ve2.cpp | 60 ++++- 6 files changed, 473 insertions(+), 58 deletions(-) diff --git a/profile/database/static_info/filetypes/base_filetype_impl.h b/profile/database/static_info/filetypes/base_filetype_impl.h index 93108b05..aede571d 100644 --- a/profile/database/static_info/filetypes/base_filetype_impl.h +++ b/profile/database/static_info/filetypes/base_filetype_impl.h @@ -5,6 +5,8 @@ #define BASE_FILETYPE_DOT_H #include +#include +#include #include "xdp/profile/database/static_info/aie_constructs.h" namespace xdp::aie { @@ -17,6 +19,42 @@ class BaseFiletypeImpl { BaseFiletypeImpl() = delete; virtual ~BaseFiletypeImpl() {}; + // Returns the wrapper PC (reloadable ELF entry PC) for the core tile at + // column 0, row 0 from the top-level "elfs_metadata" section. All + // "reloadable_elfs" values in that entry must be identical; otherwise + // (or if the entry is missing/empty) std::nullopt is returned and the + // caller must skip configuration. + std::optional + getReloadableElfEntryPC() const + { + auto elfsMetadata = aie_meta.get_child_optional("elfs_metadata"); + if (!elfsMetadata) + return std::nullopt; + + for (const auto& entry : elfsMetadata.get()) { + auto column = entry.second.get_optional("column"); + auto row = entry.second.get_optional("row"); + if (!column || !row || *column != 0 || *row != 0) + continue; + + auto reloadableElfs = entry.second.get_child_optional("reloadable_elfs"); + if (!reloadableElfs) + return std::nullopt; + + std::optional commonPC; + for (const auto& elf : reloadableElfs.get()) { + auto pc = static_cast(elf.second.get_value()); + if (!commonPC) + commonPC = pc; + else if (*commonPC != pc) + return std::nullopt; + } + return commonPC; + } + + return std::nullopt; + } + // Top level interface used for both file type formats virtual driver_config diff --git a/profile/plugin/aie_dtrace/aie_dtrace_metadata.cpp b/profile/plugin/aie_dtrace/aie_dtrace_metadata.cpp index 0013e511..d9c5b843 100644 --- a/profile/plugin/aie_dtrace/aie_dtrace_metadata.cpp +++ b/profile/plugin/aie_dtrace/aie_dtrace_metadata.cpp @@ -29,6 +29,12 @@ namespace xdp { return metrics; } + static const std::set& coreMetricSets() + { + static const std::set metrics = {"compute_io_bound", "off"}; + return metrics; + } + AieDtraceMetadata::AieDtraceMetadata(uint64_t deviceID, void* handle) : deviceID(deviceID) , handle(handle) @@ -48,11 +54,6 @@ namespace xdp { const auto& ci = profiling_runtime_config::control_instrumentation(); if (usingBlob) { - if (ci.aie_tile.has_value() && !ci.aie_tile->empty()) { - xrt_core::message::send(severity_level::info, "XRT", - "AIE dtrace: core tile metric '" + *ci.aie_tile - + "' from profiling_runtime_config will be supported in a follow-up."); - } if (ci.mem_tile.has_value() && !ci.mem_tile->empty()) { xrt_core::message::send(severity_level::info, "XRT", "AIE dtrace: mem tile metric '" + *ci.mem_tile @@ -60,6 +61,23 @@ namespace xdp { } } + // Core (aie) tile metrics (e.g. compute_io_bound). Only used to enable the + // metric; the tile itself is fixed to the first column / first core row. + std::vector aieMetricsSettings; + if (usingBlob && ci.aie_tile.has_value() && !ci.aie_tile->empty()) { + xrt_core::message::send(severity_level::info, "XRT", + "AIE dtrace: using aie_tile metric '" + *ci.aie_tile + + "' from Debug.profiling_runtime_config."); + aieMetricsSettings = getSettingsVector("all:" + *ci.aie_tile); + } + else { + const std::string tileBasedAie = + xrt_core::config::get_aie_dtrace_settings_tile_based_aie_metrics(); + if (!tileBasedAie.empty()) + aieMetricsSettings = getSettingsVector(tileBasedAie); + } + getConfigMetricsForAIETiles(CORE_MODULE_IDX, aieMetricsSettings); + std::vector metricsSettings; if (usingBlob && ci.interface_tile.has_value() && !ci.interface_tile->empty()) { xrt_core::message::send(severity_level::info, "XRT", @@ -86,6 +104,7 @@ namespace xdp { using boost::property_tree::ptree; const std::set validSettings { "tile_based_interface_tile_metrics", + "tile_based_aie_metrics", "configure_aie_hardware", "config_one_partition", }; @@ -122,6 +141,55 @@ namespace xdp { return bandwidthMetricSets().count(metricSet) > 0; } + bool AieDtraceMetadata::isCoreMetricSet(const std::string& metricSet) const + { + return coreMetricSets().count(metricSet) > 0; + } + + void AieDtraceMetadata::getConfigMetricsForAIETiles(int moduleIdx, + const std::vector& metricsSettings) + { + if (metricsSettings.empty()) + return; + + // These settings only enable/disable the metric. The tile itself is fixed + // to the first column / first core row; only a single core tile is ever + // configured for compute_io_bound. + std::string metricSet; + for (const auto& setting : metricsSettings) { + std::vector parts; + boost::split(parts, setting, boost::is_any_of(":")); + // Accept "all:", ":", or bare "". + const std::string& candidate = parts.back(); + if (isCoreMetricSet(candidate)) { + metricSet = candidate; + break; + } + } + + if (metricSet.empty()) { + xrt_core::message::send(severity_level::warning, "XRT", + "AIE dtrace: no valid core (aie) tile metric set found in " + "tile_based_aie_metrics. Supported: compute_io_bound, off."); + return; + } + + if (metricSet == "off") + return; + + tile_type tile; + tile.col = COMPUTE_IO_CORE_COL; + tile.row = COMPUTE_IO_CORE_ROW; + tile.active_core = true; + configMetrics[moduleIdx][tile] = metricSet; + + xrt_core::message::send(severity_level::info, "XRT", + "AIE dtrace: configured core tile (col " + + std::to_string(static_cast(COMPUTE_IO_CORE_COL)) + ", row " + + std::to_string(static_cast(COMPUTE_IO_CORE_ROW)) + + ") with metric set '" + metricSet + "'."); + } + void AieDtraceMetadata::getConfigMetricsForInterfaceTiles(int moduleIdx, const std::vector& metricsSettings) { diff --git a/profile/plugin/aie_dtrace/aie_dtrace_metadata.h b/profile/plugin/aie_dtrace/aie_dtrace_metadata.h index f75f99c6..7762bf88 100644 --- a/profile/plugin/aie_dtrace/aie_dtrace_metadata.h +++ b/profile/plugin/aie_dtrace/aie_dtrace_metadata.h @@ -7,6 +7,7 @@ #include #include #include +#include #include #include @@ -18,8 +19,14 @@ namespace xdp { class AieDtraceMetadata { private: static constexpr int SHIM_MODULE_IDX = static_cast(module_type::shim); + static constexpr int CORE_MODULE_IDX = static_cast(module_type::core); static constexpr int NUM_MODULES = static_cast(module_type::num_types); + // The compute_io_bound metric configures a single fixed core tile at the + // first column / first core row (absolute location col 0, row 3). + static constexpr uint8_t COMPUTE_IO_CORE_COL = 0; + static constexpr uint8_t COMPUTE_IO_CORE_ROW = 3; + uint64_t deviceID = 0; double clockFreqMhz = 0.0; void* handle = nullptr; @@ -34,7 +41,10 @@ class AieDtraceMetadata { void checkDtraceSettings(); void getConfigMetricsForInterfaceTiles(int moduleIdx, const std::vector& metricsSettings); + void getConfigMetricsForAIETiles(int moduleIdx, + const std::vector& metricsSettings); bool isBandwidthMetricSet(const std::string& metricSet) const; + bool isCoreMetricSet(const std::string& metricSet) const; public: AieDtraceMetadata(uint64_t deviceID, void* handle); @@ -43,8 +53,12 @@ class AieDtraceMetadata { void* getHandle() { return handle; } bool isConfigured() const { - return SHIM_MODULE_IDX < static_cast(configMetrics.size()) + const int numModules = static_cast(configMetrics.size()); + const bool shimConfigured = SHIM_MODULE_IDX < numModules && !configMetrics[SHIM_MODULE_IDX].empty(); + const bool coreConfigured = CORE_MODULE_IDX < numModules + && !configMetrics[CORE_MODULE_IDX].empty(); + return shimConfigured || coreConfigured; } bool isConfigOnePartition() const { return configOnePartition; } @@ -71,6 +85,14 @@ class AieDtraceMetadata { aie::driver_config getAIEConfigMetadata(); + // Wrapper PC (reloadable ELF entry PC) for the compute_io_bound core tile. + // Returns std::nullopt when the col0/row0 elfs_metadata entry is missing or + // its reloadable_elfs values are not all identical. + std::optional getWrapperPC() const { + return metadataReader == nullptr ? std::nullopt + : metadataReader->getReloadableElfEntryPC(); + } + std::unique_ptr createAIEProfileConfig(); }; diff --git a/profile/plugin/aie_dtrace/ve2/aie_dtrace_ct_writer.cpp b/profile/plugin/aie_dtrace/ve2/aie_dtrace_ct_writer.cpp index a5f313fa..6192cef5 100644 --- a/profile/plugin/aie_dtrace/ve2/aie_dtrace_ct_writer.cpp +++ b/profile/plugin/aie_dtrace/ve2/aie_dtrace_ct_writer.cpp @@ -1039,7 +1039,7 @@ std::vector AieDtraceCTWriter::generateBandwidthCounters( return counters; } -bool AieDtraceCTWriter::writeBandwidthCTFile( +bool AieDtraceCTWriter::writeCounterCTFile( const std::vector& asmFileInfoList, const std::vector& allCounters, const std::vector& beginBlockWrites, @@ -1054,10 +1054,9 @@ bool AieDtraceCTWriter::writeBandwidthCTFile( return false; } - ctFile << "# Auto-generated CT file for AIE bandwidth monitoring\n"; - ctFile << "# Generated by XRT AIE Dtrace Plugin (simplified bandwidth mode)\n"; - ctFile << "# Fixed 4 counters per shim tile: S2MM ch0,ch1 + MM2S ch0,ch1\n"; - ctFile << "# Post-processing filters by metric: read_bandwidth, write_bandwidth, ddr_bandwidth\n\n"; + ctFile << "# Auto-generated CT file for AIE counter monitoring\n"; + ctFile << "# Generated by XRT AIE Dtrace Plugin\n"; + ctFile << "# Hardware configuration is embedded in the begin block (write_reg)\n\n"; ctFile << "begin\n"; ctFile << "{\n"; @@ -1172,33 +1171,16 @@ bool AieDtraceCTWriter::writeBandwidthCTFile( ctFile.close(); std::stringstream msg; - msg << "Generated bandwidth CT file: " << outputPath + msg << "Generated CT file: " << outputPath << " (" << allCounters.size() << " counters)"; xrt_core::message::send(severity_level::info, "XRT", msg.str()); return true; } -bool AieDtraceCTWriter::generateBandwidthCT( - const std::string& outputPath, - void* hwctx, - const std::vector& opLocations, - const std::string& metricSet, - uint8_t channel) +std::vector AieDtraceCTWriter::buildAsmFileInfoList( + const std::vector& opLocations) { - if (opLocations.empty()) { - xrt_core::message::send(severity_level::debug, "XRT", - "AIE dtrace: No op_locations provided for bandwidth CT generation"); - return false; - } - - auto shimColumns = getShimTileColumns(hwctx); - if (shimColumns.empty()) { - xrt_core::message::send(severity_level::warning, "XRT", - "AIE dtrace: No shim columns found in partition. Cannot generate bandwidth CT."); - return false; - } - std::vector asmFileInfoList; std::regex filenamePattern(R"(aie_runtime_control(\d+)?\.asm)"); @@ -1237,18 +1219,116 @@ bool AieDtraceCTWriter::generateBandwidthCT( } } + if (!asmFileInfoList.empty()) + applyUcSpansFromOpLoc(asmFileInfoList); + + return asmFileInfoList; +} + +bool AieDtraceCTWriter::appendBandwidthConfig( + void* hwctx, const std::string& metricSet, uint8_t channel, + std::vector& counters, std::vector& beginWrites) +{ + auto shimColumns = getShimTileColumns(hwctx); + if (shimColumns.empty()) { + xrt_core::message::send(severity_level::warning, "XRT", + "AIE dtrace: No shim columns found in partition. Skipping bandwidth counters."); + return false; + } + + auto bwCounters = generateBandwidthCounters(shimColumns, metricSet, channel); + if (bwCounters.empty()) { + xrt_core::message::send(severity_level::warning, "XRT", + "AIE dtrace: No bandwidth counters generated"); + return false; + } + counters.insert(counters.end(), bwCounters.begin(), bwCounters.end()); + + for (uint8_t column : shimColumns) { + // For detailed sets, counter 0 monitors PORT_RUNNING on the channel's + // stream-switch port, so the SS event port selection must be programmed. + auto streamSwitchWrites = generateStreamSwitchPortConfig(column, metricSet, channel); + beginWrites.insert(beginWrites.end(), streamSwitchWrites.begin(), streamSwitchWrites.end()); + + auto perfCounterWrites = generatePerfCounterConfig(column, metricSet, channel); + beginWrites.insert(beginWrites.end(), perfCounterWrites.begin(), perfCounterWrites.end()); + } + + return true; +} + +void AieDtraceCTWriter::appendComputeIoBoundConfig( + uint32_t wpc, + std::vector& counters, std::vector& beginWrites) +{ + // Single core tile: two counters (compute and io+compute). + CTCounterInfo compute; + compute.column = COMPUTE_IO_CORE_COL; + compute.row = COMPUTE_IO_CORE_ROW; + compute.counterNumber = 0; + compute.channel = 0; + compute.module = "aie"; + compute.address = calculateCounterAddress(COMPUTE_IO_CORE_COL, COMPUTE_IO_CORE_ROW, 0, "aie"); + compute.metricSet = "compute_io_bound"; + compute.portDirection = ""; + compute.eventType = "compute"; + counters.push_back(compute); + + CTCounterInfo ioCompute; + ioCompute.column = COMPUTE_IO_CORE_COL; + ioCompute.row = COMPUTE_IO_CORE_ROW; + ioCompute.counterNumber = 1; + ioCompute.channel = 0; + ioCompute.module = "aie"; + ioCompute.address = calculateCounterAddress(COMPUTE_IO_CORE_COL, COMPUTE_IO_CORE_ROW, 1, "aie"); + ioCompute.metricSet = "compute_io_bound"; + ioCompute.portDirection = ""; + ioCompute.eventType = "io_compute"; + counters.push_back(ioCompute); + + auto pcRangeWrites = generatePcRangeCoreConfig(COMPUTE_IO_CORE_COL, COMPUTE_IO_CORE_ROW, wpc); + beginWrites.insert(beginWrites.end(), pcRangeWrites.begin(), pcRangeWrites.end()); +} + +bool AieDtraceCTWriter::generateCT( + const std::string& outputPath, + void* hwctx, + const std::vector& opLocations, + bool includeBandwidth, + const std::string& bandwidthMetricSet, + uint8_t bandwidthChannel, + bool includeComputeIoBound, + uint32_t wpc) +{ + if (opLocations.empty()) { + xrt_core::message::send(severity_level::debug, "XRT", + "AIE dtrace: No op_locations provided for CT generation"); + return false; + } + + auto asmFileInfoList = buildAsmFileInfoList(opLocations); if (asmFileInfoList.empty()) { xrt_core::message::send(severity_level::debug, "XRT", - "AIE dtrace: No ASM files found in op_locations for bandwidth CT generation"); + "AIE dtrace: No ASM files found in op_locations for CT generation"); return false; } - applyUcSpansFromOpLoc(asmFileInfoList); + std::vector allCounters; + std::vector beginBlockWrites; + + // Both metric families can be emitted into the same CT file. Bandwidth + // counters live on shim tiles (row 0); the compute_io_bound counters live on + // the single core tile (col 0, row 3). filterCountersByColumn keys by column, + // so both simply land in the matching UC group and read distinct addresses. + if (includeBandwidth) + appendBandwidthConfig(hwctx, bandwidthMetricSet, bandwidthChannel, allCounters, beginBlockWrites); + + if (includeComputeIoBound) + appendComputeIoBoundConfig(wpc, allCounters, beginBlockWrites); - auto allCounters = generateBandwidthCounters(shimColumns, metricSet, channel); if (allCounters.empty()) { xrt_core::message::send(severity_level::warning, "XRT", - "AIE dtrace: No bandwidth counters generated"); + "AIE dtrace: No counters configured; CT file will not be generated."); return false; } @@ -1258,18 +1338,79 @@ bool AieDtraceCTWriter::generateBandwidthCT( asmFileInfo.counters = filterCountersByColumn(allCounters, asmFileInfo.colStart, asmFileInfo.colEnd); } - std::vector beginBlockWrites; - for (uint8_t column : shimColumns) { - // For detailed sets, counter 0 monitors PORT_RUNNING on the channel's - // stream-switch port, so the SS event port selection must be programmed. - auto ssWrites = generateStreamSwitchPortConfig(column, metricSet, channel); - beginBlockWrites.insert(beginBlockWrites.end(), ssWrites.begin(), ssWrites.end()); + return writeCounterCTFile(asmFileInfoList, allCounters, beginBlockWrites, outputPath); +} + +bool AieDtraceCTWriter::generateBandwidthCT( + const std::string& outputPath, + void* hwctx, + const std::vector& opLocations, + const std::string& metricSet, + uint8_t channel) +{ + return generateCT(outputPath, hwctx, opLocations, + /*includeBandwidth=*/true, metricSet, channel, + /*includeComputeIoBound=*/false, /*wpc=*/0); +} + +std::vector AieDtraceCTWriter::generatePcRangeCoreConfig( + uint8_t column, uint8_t row, uint32_t wpc) +{ + std::vector writes; + + uint64_t tileAddress = (static_cast(column) << columnShift) | + (static_cast(row) << rowShift); - auto pcWrites = generatePerfCounterConfig(column, metricSet, channel); - beginBlockWrites.insert(beginBlockWrites.end(), pcWrites.begin(), pcWrites.end()); + auto addWrite = [&](uint64_t offset, uint32_t value, const std::string& comment) { + CTRegisterWrite w; + w.address = tileAddress + offset; + w.value = value; + w.comment = comment; + writes.push_back(w); + }; + + std::string loc = "core (" + std::to_string(column) + "," + std::to_string(row) + ")"; + + // PC_Event0..3: Valid bit + 14-bit PC address. + // Compute range = [wpc, PROG_MEM_END] via PC_Event0/PC_Event1 (PC_Range_0-1) + // IO + Compute range = [0, PROG_MEM_END] via PC_Event2/PC_Event3 (PC_Range_2-3) + addWrite(CM_PC_EVENT0 + 0, PC_EVENT_VALID | (wpc & PC_ADDRESS_MASK), + "PC_Event0 @ " + loc + " (wpc = compute range start)"); + addWrite(CM_PC_EVENT0 + 4, PC_EVENT_VALID | (PROG_MEM_END & PC_ADDRESS_MASK), + "PC_Event1 @ " + loc + " (compute range end)"); + addWrite(CM_PC_EVENT0 + 8, PC_EVENT_VALID | 0, + "PC_Event2 @ " + loc + " (io+compute range start)"); + addWrite(CM_PC_EVENT0 + 12, PC_EVENT_VALID | (PROG_MEM_END & PC_ADDRESS_MASK), + "PC_Event3 @ " + loc + " (io+compute range end)"); + + // Reset performance counters 0 and 1. + addWrite(CM_PERF_COUNTER0 + 0, 0, "Reset PerfCounter0 @ " + loc); + addWrite(CM_PERF_COUNTER0 + 4, 0, "Reset PerfCounter1 @ " + loc); + + // Performance_Ctrl0: [7:0]=Cnt0_Start, [15:8]=Cnt0_Stop, [23:16]=Cnt1_Start, [31:24]=Cnt1_Stop + // Counter 0 counts PC_Range_0-1 (Compute); Counter 1 counts PC_Range_2-3 (IO + Compute). + { + uint32_t regValue = 0; + regValue |= (static_cast(PC_RANGE_0_1_EVENT) & 0xFF) << 0; + regValue |= (static_cast(PC_RANGE_0_1_EVENT) & 0xFF) << 8; + regValue |= (static_cast(PC_RANGE_2_3_EVENT) & 0xFF) << 16; + regValue |= (static_cast(PC_RANGE_2_3_EVENT) & 0xFF) << 24; + addWrite(CM_PERF_CTRL0, regValue, + "PerfCtrl0 @ " + loc + " (ctr0=PC_Range_0-1 compute, ctr1=PC_Range_2-3 io+compute)"); } - return writeBandwidthCTFile(asmFileInfoList, allCounters, beginBlockWrites, outputPath); + return writes; +} + +bool AieDtraceCTWriter::generateComputeIoBoundCT( + const std::string& outputPath, + void* hwctx, + const std::vector& opLocations, + uint32_t wpc) +{ + return generateCT(outputPath, hwctx, opLocations, + /*includeBandwidth=*/false, /*bandwidthMetricSet=*/"", /*bandwidthChannel=*/0, + /*includeComputeIoBound=*/true, wpc); } } // namespace xdp diff --git a/profile/plugin/aie_dtrace/ve2/aie_dtrace_ct_writer.h b/profile/plugin/aie_dtrace/ve2/aie_dtrace_ct_writer.h index d353b165..a61ae417 100644 --- a/profile/plugin/aie_dtrace/ve2/aie_dtrace_ct_writer.h +++ b/profile/plugin/aie_dtrace/ve2/aie_dtrace_ct_writer.h @@ -166,6 +166,52 @@ class AieDtraceCTWriter { const std::string& metricSet = "ddr_bandwidth", uint8_t channel = 0); + /** + * @brief Generate a self-contained CT file for the compute_io_bound metric + * + * Configures a single core (aie) tile at the first column / first core row + * with two PC-range events and two core performance counters: + * - Counter 0 (Compute): PC in [wpc, PROG_MEM_END] via PC_Range_0-1 + * - Counter 1 (IO + Compute): PC in [0, PROG_MEM_END] via PC_Range_2-3 + * + * @param outputPath Full path for the generated CT file + * @param hwctx Hardware context handle for partition info access + * @param opLocations Vector of op_loc from aiebu_assembler::get_op_locations + * @param wpc Wrapper PC (reloadable ELF entry PC) from AIE metadata + * @return true if CT file was generated successfully, false otherwise + */ + bool generateComputeIoBoundCT(const std::string& outputPath, + void* hwctx, + const std::vector& opLocations, + uint32_t wpc); + + /** + * @brief Generate a self-contained CT file combining bandwidth and/or + * compute_io_bound metrics into a single begin block + counter reads. + * + * Either family can be enabled independently; when both are enabled the shim + * bandwidth counters and the core compute_io_bound counters are emitted into + * the same CT file. + * + * @param outputPath Full path for the generated CT file + * @param hwctx Hardware context handle for partition info access + * @param opLocations Vector of op_loc from aiebu_assembler::get_op_locations + * @param includeBandwidth Emit interface-tile bandwidth counters + * @param bandwidthMetricSet Bandwidth metric set (used when includeBandwidth) + * @param bandwidthChannel DMA channel for detailed_ddr_*_bandwidth sets + * @param includeComputeIoBound Emit the single core-tile compute_io_bound counters + * @param wpc Wrapper PC (used when includeComputeIoBound) + * @return true if CT file was generated successfully, false otherwise + */ + bool generateCT(const std::string& outputPath, + void* hwctx, + const std::vector& opLocations, + bool includeBandwidth, + const std::string& bandwidthMetricSet, + uint8_t bandwidthChannel, + bool includeComputeIoBound, + uint32_t wpc); + private: /** * @brief Read ASM file information from CSV file @@ -290,17 +336,60 @@ class AieDtraceCTWriter { const std::string& metricSet = "ddr_bandwidth", uint8_t channel = 0); /** - * @brief Write the bandwidth CT file content with register configuration + * @brief Build the ASM file/timestamp info list from op_locations + * @param opLocations Vector of op_loc from aiebu_assembler::get_op_locations + * @return Vector of ASMFileInfo (UC spans applied); empty if none found + */ + std::vector buildAsmFileInfoList( + const std::vector& opLocations); + + /** + * @brief Append interface-tile bandwidth counters and begin-block writes + * @param hwctx Hardware context handle for shim column discovery + * @param metricSet Bandwidth metric set + * @param channel DMA channel for detailed_ddr_*_bandwidth sets + * @param counters [in,out] Accumulated counter list + * @param beginWrites [in,out] Accumulated begin-block register writes + * @return true if bandwidth config was appended + */ + bool appendBandwidthConfig(void* hwctx, const std::string& metricSet, uint8_t channel, + std::vector& counters, std::vector& beginWrites); + + /** + * @brief Append the single core-tile compute_io_bound counters and begin-block writes + * @param wpc Wrapper PC used as the lower bound of the Compute range + * @param counters [in,out] Accumulated counter list + * @param beginWrites [in,out] Accumulated begin-block register writes + */ + void appendComputeIoBoundConfig(uint32_t wpc, + std::vector& counters, std::vector& beginWrites); + + /** + * @brief Generate PC-range + performance counter config for a single core tile + * + * Programs PC_Event0-3 (with the Valid bit), resets performance counters 0/1, + * and configures Performance_Ctrl0 so counter 0 counts PC_Range_0-1 (Compute) + * and counter 1 counts PC_Range_2-3 (IO + Compute). + * + * @param column Partition-relative core tile column + * @param row Core tile row (absolute; first core row = 3) + * @param wpc Wrapper PC used as the lower bound of the Compute range + * @return Vector of register writes for the begin block + */ + std::vector generatePcRangeCoreConfig(uint8_t column, uint8_t row, uint32_t wpc); + + /** + * @brief Write a self-contained counter CT file with begin-block register writes * @param asmFileInfoList Vector of ASMFileInfo with timestamps * @param allCounters Vector of all CTCounterInfo for metadata * @param beginBlockWrites Vector of register writes for begin block * @param outputPath Full path for the output CT file * @return true if file was written successfully */ - bool writeBandwidthCTFile(const std::vector& asmFileInfoList, - const std::vector& allCounters, - const std::vector& beginBlockWrites, - const std::string& outputPath); + bool writeCounterCTFile(const std::vector& asmFileInfoList, + const std::vector& allCounters, + const std::vector& beginBlockWrites, + const std::string& outputPath); private: VPDatabase* db; @@ -322,6 +411,21 @@ class AieDtraceCTWriter { static constexpr uint64_t STREAM_SWITCH_EVENT_PORT_SEL_OFFSET = 0x0003FF00; static constexpr uint64_t PERF_CTRL_OFFSET = 0x00031000; + // Core (aie) module offsets for the compute_io_bound metric (aie2ps) + static constexpr uint64_t CM_PERF_CTRL0 = 0x00037500; // Counters 0,1 start/stop events + static constexpr uint64_t CM_PERF_COUNTER0 = 0x00037520; // Counter 0 (Counter 1 at +4) + static constexpr uint64_t CM_PC_EVENT0 = 0x00038020; // PC_Event0 (1..3 at +4 each) + static constexpr uint32_t PC_EVENT_VALID = 0x80000000; // PC_Event Valid bit (bit 31) + static constexpr uint32_t PC_ADDRESS_MASK = 0x00003FFF; // PC_Address field (bits 13:0) + static constexpr uint32_t PROG_MEM_END = 0x00003FFF; // End of 16KB program memory + static constexpr uint8_t PC_RANGE_0_1_EVENT = 20; // XAIE2PS_EVENTS_CORE_PC_RANGE_0_1 + static constexpr uint8_t PC_RANGE_2_3_EVENT = 21; // XAIE2PS_EVENTS_CORE_PC_RANGE_2_3 + + // compute_io_bound configures a single core tile at the first column / first + // core row (partition-relative col 0, row 3). + static constexpr uint8_t COMPUTE_IO_CORE_COL = 0; + static constexpr uint8_t COMPUTE_IO_CORE_ROW = 3; + // Bandwidth monitoring constants static constexpr uint8_t NUM_BANDWIDTH_COUNTERS = 4; static constexpr uint8_t SHIM_ROW = 0; diff --git a/profile/plugin/aie_dtrace/ve2/aie_dtrace_ve2.cpp b/profile/plugin/aie_dtrace/ve2/aie_dtrace_ve2.cpp index cfab9211..507f1195 100644 --- a/profile/plugin/aie_dtrace/ve2/aie_dtrace_ve2.cpp +++ b/profile/plugin/aie_dtrace/ve2/aie_dtrace_ve2.cpp @@ -23,6 +23,7 @@ namespace xdp { using severity_level = xrt_core::message::severity_level; static constexpr int SHIM_MODULE_IDX = static_cast(module_type::shim); + static constexpr int CORE_MODULE_IDX = static_cast(module_type::core); AieDtrace_VE2Impl::AieDtrace_VE2Impl(VPDatabase* database, std::shared_ptr metadata, @@ -108,10 +109,39 @@ namespace xdp { AieDtraceCTWriter ctWriter(db, metadata, deviceID, partitionStartCol); + // Determine which metric families are configured for this run. Both the + // interface-tile bandwidth metrics and the core-tile compute_io_bound metric + // can be emitted into the same per-run CT file. + bool includeComputeIoBound = false; + for (const auto& tc : metadata->getConfigMetricsVec(CORE_MODULE_IDX)) { + if (tc.second == "compute_io_bound") { + includeComputeIoBound = true; + break; + } + } + + uint32_t wpc = 0; + if (includeComputeIoBound) { + auto wpcOpt = metadata->getWrapperPC(); + if (!wpcOpt) { + xrt_core::message::send(severity_level::warning, "XRT", + "AIE dtrace: compute_io_bound requested but the wrapper PC " + "(elfs_metadata col0/row0 reloadable_elfs) is missing or its values " + "are not all identical; skipping core configuration."); + includeComputeIoBound = false; + } + else { + wpc = *wpcOpt; + } + } + + // Interface-tile bandwidth metrics are configured by default unless the user + // turned interface tiles off (which leaves the shim config map empty). + auto shimConfigMetrics = metadata->getConfigMetricsVec(SHIM_MODULE_IDX); + bool includeBandwidth = !shimConfigMetrics.empty(); std::string bandwidthMetricSet = "peak_read_bandwidth"; uint8_t bandwidthChannel = 0; - auto shimConfigMetrics = metadata->getConfigMetricsVec(SHIM_MODULE_IDX); - if (!shimConfigMetrics.empty()) { + if (includeBandwidth) { bandwidthMetricSet = shimConfigMetrics.front().second; // The detailed_ddr_*_bandwidth metric sets carry a DMA channel in their // ":" suffix (stored in configChannel0). Match by column/row. @@ -124,19 +154,31 @@ namespace xdp { } } xrt_core::message::send(severity_level::info, "XRT", - "AIE dtrace: Using metric set '" + bandwidthMetricSet + "' (channel " + "AIE dtrace: Using interface tile metric set '" + bandwidthMetricSet + "' (channel " + std::to_string(bandwidthChannel) + ") from configuration"); - } else { + } + + if (!includeBandwidth && !includeComputeIoBound) { xrt_core::message::send(severity_level::info, "XRT", - "AIE dtrace: No interface tile metrics configured, using default 'peak_read_bandwidth'"); + "AIE dtrace: No metrics configured; skipping CT generation."); + return; } - if (!ctWriter.generateBandwidthCT(outputPath, hwctx, it->second, bandwidthMetricSet, bandwidthChannel)) + if (!ctWriter.generateCT(outputPath, hwctx, it->second, + includeBandwidth, bandwidthMetricSet, bandwidthChannel, + includeComputeIoBound, wpc)) return; - xrt_core::message::send(severity_level::debug, "XRT", - "AIE dtrace: Bandwidth CT generated for kernel '" + kernel_name - + "' with metric set '" + bandwidthMetricSet + "'"); + std::stringstream genMsg; + genMsg << "AIE dtrace: CT generated for kernel '" << kernel_name << "' ("; + if (includeBandwidth) + genMsg << "interface_tile=" << bandwidthMetricSet; + if (includeBandwidth && includeComputeIoBound) + genMsg << ", "; + if (includeComputeIoBound) + genMsg << "aie_tile=compute_io_bound wpc=0x" << std::hex << wpc << std::dec; + genMsg << ")"; + xrt_core::message::send(severity_level::debug, "XRT", genMsg.str()); auto* run_impl = static_cast(run_impl_ptr); try { From 07215bc9a0c01072bd2005f3b6bf58e86d650feb Mon Sep 17 00:00:00 2001 From: Jyotheeswar Ganne Date: Thu, 23 Jul 2026 03:29:29 -0600 Subject: [PATCH 2/6] Use performance counters 2 and 3 for compute_io_bound Program Performance_Control1 (0x37504) and read Performance_Counter2/3 (0x37528/0x3752C) instead of counters 0/1, leaving counters 0/1 free. Counter 2 counts PC_Range_0-1 (Compute), counter 3 counts PC_Range_2-3 (IO + Compute). --- .../aie_dtrace/ve2/aie_dtrace_ct_writer.cpp | 33 ++++++++++--------- .../aie_dtrace/ve2/aie_dtrace_ct_writer.h | 4 +-- 2 files changed, 19 insertions(+), 18 deletions(-) diff --git a/profile/plugin/aie_dtrace/ve2/aie_dtrace_ct_writer.cpp b/profile/plugin/aie_dtrace/ve2/aie_dtrace_ct_writer.cpp index 6192cef5..5da75113 100644 --- a/profile/plugin/aie_dtrace/ve2/aie_dtrace_ct_writer.cpp +++ b/profile/plugin/aie_dtrace/ve2/aie_dtrace_ct_writer.cpp @@ -1261,14 +1261,15 @@ void AieDtraceCTWriter::appendComputeIoBoundConfig( uint32_t wpc, std::vector& counters, std::vector& beginWrites) { - // Single core tile: two counters (compute and io+compute). + // Single core tile: two counters (compute and io+compute) using + // performance counters 2 and 3. CTCounterInfo compute; compute.column = COMPUTE_IO_CORE_COL; compute.row = COMPUTE_IO_CORE_ROW; - compute.counterNumber = 0; + compute.counterNumber = 2; compute.channel = 0; compute.module = "aie"; - compute.address = calculateCounterAddress(COMPUTE_IO_CORE_COL, COMPUTE_IO_CORE_ROW, 0, "aie"); + compute.address = calculateCounterAddress(COMPUTE_IO_CORE_COL, COMPUTE_IO_CORE_ROW, 2, "aie"); compute.metricSet = "compute_io_bound"; compute.portDirection = ""; compute.eventType = "compute"; @@ -1277,10 +1278,10 @@ void AieDtraceCTWriter::appendComputeIoBoundConfig( CTCounterInfo ioCompute; ioCompute.column = COMPUTE_IO_CORE_COL; ioCompute.row = COMPUTE_IO_CORE_ROW; - ioCompute.counterNumber = 1; + ioCompute.counterNumber = 3; ioCompute.channel = 0; ioCompute.module = "aie"; - ioCompute.address = calculateCounterAddress(COMPUTE_IO_CORE_COL, COMPUTE_IO_CORE_ROW, 1, "aie"); + ioCompute.address = calculateCounterAddress(COMPUTE_IO_CORE_COL, COMPUTE_IO_CORE_ROW, 3, "aie"); ioCompute.metricSet = "compute_io_bound"; ioCompute.portDirection = ""; ioCompute.eventType = "io_compute"; @@ -1383,20 +1384,20 @@ std::vector AieDtraceCTWriter::generatePcRangeCoreConfig( addWrite(CM_PC_EVENT0 + 12, PC_EVENT_VALID | (PROG_MEM_END & PC_ADDRESS_MASK), "PC_Event3 @ " + loc + " (io+compute range end)"); - // Reset performance counters 0 and 1. - addWrite(CM_PERF_COUNTER0 + 0, 0, "Reset PerfCounter0 @ " + loc); - addWrite(CM_PERF_COUNTER0 + 4, 0, "Reset PerfCounter1 @ " + loc); + // Reset performance counters 2 and 3. + addWrite(CM_PERF_COUNTER0 + 8, 0, "Reset PerfCounter2 @ " + loc); + addWrite(CM_PERF_COUNTER0 + 12, 0, "Reset PerfCounter3 @ " + loc); - // Performance_Ctrl0: [7:0]=Cnt0_Start, [15:8]=Cnt0_Stop, [23:16]=Cnt1_Start, [31:24]=Cnt1_Stop - // Counter 0 counts PC_Range_0-1 (Compute); Counter 1 counts PC_Range_2-3 (IO + Compute). + // Performance_Ctrl1: [6:0]=Cnt2_Start, [14:8]=Cnt2_Stop, [22:16]=Cnt3_Start, [30:24]=Cnt3_Stop + // Counter 2 counts PC_Range_0-1 (Compute); Counter 3 counts PC_Range_2-3 (IO + Compute). { uint32_t regValue = 0; - regValue |= (static_cast(PC_RANGE_0_1_EVENT) & 0xFF) << 0; - regValue |= (static_cast(PC_RANGE_0_1_EVENT) & 0xFF) << 8; - regValue |= (static_cast(PC_RANGE_2_3_EVENT) & 0xFF) << 16; - regValue |= (static_cast(PC_RANGE_2_3_EVENT) & 0xFF) << 24; - addWrite(CM_PERF_CTRL0, regValue, - "PerfCtrl0 @ " + loc + " (ctr0=PC_Range_0-1 compute, ctr1=PC_Range_2-3 io+compute)"); + regValue |= (static_cast(PC_RANGE_0_1_EVENT) & 0x7F) << 0; + regValue |= (static_cast(PC_RANGE_0_1_EVENT) & 0x7F) << 8; + regValue |= (static_cast(PC_RANGE_2_3_EVENT) & 0x7F) << 16; + regValue |= (static_cast(PC_RANGE_2_3_EVENT) & 0x7F) << 24; + addWrite(CM_PERF_CTRL1, regValue, + "PerfCtrl1 @ " + loc + " (ctr2=PC_Range_0-1 compute, ctr3=PC_Range_2-3 io+compute)"); } return writes; diff --git a/profile/plugin/aie_dtrace/ve2/aie_dtrace_ct_writer.h b/profile/plugin/aie_dtrace/ve2/aie_dtrace_ct_writer.h index a61ae417..cf737ab4 100644 --- a/profile/plugin/aie_dtrace/ve2/aie_dtrace_ct_writer.h +++ b/profile/plugin/aie_dtrace/ve2/aie_dtrace_ct_writer.h @@ -412,8 +412,8 @@ class AieDtraceCTWriter { static constexpr uint64_t PERF_CTRL_OFFSET = 0x00031000; // Core (aie) module offsets for the compute_io_bound metric (aie2ps) - static constexpr uint64_t CM_PERF_CTRL0 = 0x00037500; // Counters 0,1 start/stop events - static constexpr uint64_t CM_PERF_COUNTER0 = 0x00037520; // Counter 0 (Counter 1 at +4) + static constexpr uint64_t CM_PERF_CTRL1 = 0x00037504; // Counters 2,3 start/stop events + static constexpr uint64_t CM_PERF_COUNTER0 = 0x00037520; // Counter 0 (Counter N at +4*N) static constexpr uint64_t CM_PC_EVENT0 = 0x00038020; // PC_Event0 (1..3 at +4 each) static constexpr uint32_t PC_EVENT_VALID = 0x80000000; // PC_Event Valid bit (bit 31) static constexpr uint32_t PC_ADDRESS_MASK = 0x00003FFF; // PC_Address field (bits 13:0) From 98ab81ec012578c6618dd3f0fc3e1ca1a473d7e6 Mon Sep 17 00:00:00 2001 From: Jyotheeswar Ganne Date: Mon, 27 Jul 2026 07:17:58 -0600 Subject: [PATCH 3/6] Support static-ELF designs for compute_io_bound When elfs_metadata.reloadable_elfs is empty (static/inlined designs), the kernelWrapper PC is unavailable so the metric was skipped. Add a robust fallback that derives the kernelWrapper loop start/stop PCs from the core tile's .lst listing (isolate main, find the single indirect 'jl pN' dispatch, then the first backward branch after it) and counts kernelWrapper cycles via performance-counter Start/Stop on PC breakpoint events (PC_0=start_pc, PC_1=stop_pc), so the dispatched kernel is included. The flow is gated on kernelWrapper being inline (checked from the tile source .cc); reloadable designs keep using the metadata wpc PC-range path. Listing/source lookup uses relative rows; register addressing uses the absolute core row from aie_tile_row_start (getAIETileRowOffset) instead of a hardcoded value. Co-authored-by: Cursor --- .../filetypes/base_filetype_impl.h | 28 ++ .../plugin/aie_dtrace/aie_dtrace_metadata.h | 17 +- .../aie_dtrace/ve2/aie_dtrace_ct_writer.cpp | 69 ++-- .../aie_dtrace/ve2/aie_dtrace_ct_writer.h | 59 +++- .../plugin/aie_dtrace/ve2/aie_dtrace_ve2.cpp | 61 +++- profile/plugin/aie_dtrace/ve2/lst_helper.cpp | 314 ++++++++++++++++++ profile/plugin/aie_dtrace/ve2/lst_helper.h | 36 ++ 7 files changed, 529 insertions(+), 55 deletions(-) create mode 100644 profile/plugin/aie_dtrace/ve2/lst_helper.cpp create mode 100644 profile/plugin/aie_dtrace/ve2/lst_helper.h diff --git a/profile/database/static_info/filetypes/base_filetype_impl.h b/profile/database/static_info/filetypes/base_filetype_impl.h index aede571d..1595cc0a 100644 --- a/profile/database/static_info/filetypes/base_filetype_impl.h +++ b/profile/database/static_info/filetypes/base_filetype_impl.h @@ -55,6 +55,34 @@ class BaseFiletypeImpl { return std::nullopt; } + // Returns the static ELF tile base name (e.g. "0_0") for the core tile at + // column 0, row 0 from "elfs_metadata" (the first key under "static_elfs"). + // This is the RELATIVE-row listing base used to locate 0_0.lst / 0_0.cc. + // std::nullopt if the entry or its static_elfs are missing. + std::optional + getStaticElfTileName() const + { + auto elfsMetadata = aie_meta.get_child_optional("elfs_metadata"); + if (!elfsMetadata) + return std::nullopt; + + for (const auto& entry : elfsMetadata.get()) { + auto column = entry.second.get_optional("column"); + auto row = entry.second.get_optional("row"); + if (!column || !row || *column != 0 || *row != 0) + continue; + + auto staticElfs = entry.second.get_child_optional("static_elfs"); + if (!staticElfs) + return std::nullopt; + for (const auto& e : staticElfs.get()) + return e.first; // first (and typically only) key, e.g. "0_0" + return std::nullopt; + } + + return std::nullopt; + } + // Top level interface used for both file type formats virtual driver_config diff --git a/profile/plugin/aie_dtrace/aie_dtrace_metadata.h b/profile/plugin/aie_dtrace/aie_dtrace_metadata.h index 7762bf88..d44268ba 100644 --- a/profile/plugin/aie_dtrace/aie_dtrace_metadata.h +++ b/profile/plugin/aie_dtrace/aie_dtrace_metadata.h @@ -87,12 +87,27 @@ class AieDtraceMetadata { // Wrapper PC (reloadable ELF entry PC) for the compute_io_bound core tile. // Returns std::nullopt when the col0/row0 elfs_metadata entry is missing or - // its reloadable_elfs values are not all identical. + // its reloadable_elfs values are not all identical (e.g. static/inlined designs). std::optional getWrapperPC() const { return metadataReader == nullptr ? std::nullopt : metadataReader->getReloadableElfEntryPC(); } + // Static ELF tile base name (e.g. "0_0") for the col0/row0 core tile, used to + // locate the relative-row listing/source (0_0.lst / 0_0.cc) for static designs. + std::optional getStaticElfTileName() const { + return metadataReader == nullptr ? std::nullopt + : metadataReader->getStaticElfTileName(); + } + + // AIE core tile row offset (aie_tile_row_start): absolute row of the first + // core row. The compute_io_bound tile is relative row 0, so its absolute row + // equals this offset. Falls back to COMPUTE_IO_CORE_ROW when unavailable. + uint8_t getCoreRowOffset() const { + return metadataReader == nullptr ? COMPUTE_IO_CORE_ROW + : metadataReader->getAIETileRowOffset(); + } + std::unique_ptr createAIEProfileConfig(); }; diff --git a/profile/plugin/aie_dtrace/ve2/aie_dtrace_ct_writer.cpp b/profile/plugin/aie_dtrace/ve2/aie_dtrace_ct_writer.cpp index 5da75113..8d8d9f7a 100644 --- a/profile/plugin/aie_dtrace/ve2/aie_dtrace_ct_writer.cpp +++ b/profile/plugin/aie_dtrace/ve2/aie_dtrace_ct_writer.cpp @@ -1258,18 +1258,21 @@ bool AieDtraceCTWriter::appendBandwidthConfig( } void AieDtraceCTWriter::appendComputeIoBoundConfig( - uint32_t wpc, + const ComputeIoCoreConfig& cfg, std::vector& counters, std::vector& beginWrites) { - // Single core tile: two counters (compute and io+compute) using - // performance counters 2 and 3. + // Single core tile at (COMPUTE_IO_CORE_COL, cfg.absRow): two counters (compute and + // io+compute) using performance counters 2 and 3. absRow is the absolute core row + // (relative row 0 + aie_tile_row_start) for register addressing. + const uint8_t row = cfg.absRow; + CTCounterInfo compute; compute.column = COMPUTE_IO_CORE_COL; - compute.row = COMPUTE_IO_CORE_ROW; + compute.row = row; compute.counterNumber = 2; compute.channel = 0; compute.module = "aie"; - compute.address = calculateCounterAddress(COMPUTE_IO_CORE_COL, COMPUTE_IO_CORE_ROW, 2, "aie"); + compute.address = calculateCounterAddress(COMPUTE_IO_CORE_COL, row, 2, "aie"); compute.metricSet = "compute_io_bound"; compute.portDirection = ""; compute.eventType = "compute"; @@ -1277,17 +1280,17 @@ void AieDtraceCTWriter::appendComputeIoBoundConfig( CTCounterInfo ioCompute; ioCompute.column = COMPUTE_IO_CORE_COL; - ioCompute.row = COMPUTE_IO_CORE_ROW; + ioCompute.row = row; ioCompute.counterNumber = 3; ioCompute.channel = 0; ioCompute.module = "aie"; - ioCompute.address = calculateCounterAddress(COMPUTE_IO_CORE_COL, COMPUTE_IO_CORE_ROW, 3, "aie"); + ioCompute.address = calculateCounterAddress(COMPUTE_IO_CORE_COL, row, 3, "aie"); ioCompute.metricSet = "compute_io_bound"; ioCompute.portDirection = ""; ioCompute.eventType = "io_compute"; counters.push_back(ioCompute); - auto pcRangeWrites = generatePcRangeCoreConfig(COMPUTE_IO_CORE_COL, COMPUTE_IO_CORE_ROW, wpc); + auto pcRangeWrites = generatePcRangeCoreConfig(COMPUTE_IO_CORE_COL, cfg); beginWrites.insert(beginWrites.end(), pcRangeWrites.begin(), pcRangeWrites.end()); } @@ -1299,7 +1302,7 @@ bool AieDtraceCTWriter::generateCT( const std::string& bandwidthMetricSet, uint8_t bandwidthChannel, bool includeComputeIoBound, - uint32_t wpc) + const ComputeIoCoreConfig& computeIoCfg) { if (opLocations.empty()) { xrt_core::message::send(severity_level::debug, "XRT", @@ -1325,7 +1328,7 @@ bool AieDtraceCTWriter::generateCT( appendBandwidthConfig(hwctx, bandwidthMetricSet, bandwidthChannel, allCounters, beginBlockWrites); if (includeComputeIoBound) - appendComputeIoBoundConfig(wpc, allCounters, beginBlockWrites); + appendComputeIoBoundConfig(computeIoCfg, allCounters, beginBlockWrites); if (allCounters.empty()) { xrt_core::message::send(severity_level::warning, "XRT", @@ -1351,14 +1354,15 @@ bool AieDtraceCTWriter::generateBandwidthCT( { return generateCT(outputPath, hwctx, opLocations, /*includeBandwidth=*/true, metricSet, channel, - /*includeComputeIoBound=*/false, /*wpc=*/0); + /*includeComputeIoBound=*/false, ComputeIoCoreConfig{}); } std::vector AieDtraceCTWriter::generatePcRangeCoreConfig( - uint8_t column, uint8_t row, uint32_t wpc) + uint8_t column, const ComputeIoCoreConfig& cfg) { std::vector writes; + const uint8_t row = cfg.absRow; uint64_t tileAddress = (static_cast(column) << columnShift) | (static_cast(row) << rowShift); @@ -1372,32 +1376,45 @@ std::vector AieDtraceCTWriter::generatePcRangeCoreConfig( std::string loc = "core (" + std::to_string(column) + "," + std::to_string(row) + ")"; + // Compute-counter (counter 2) uses PC_Event0/PC_Event1: + // reloadable : PC_Range_0-1 over [startPc(=wpc), PROG_MEM_END] + // static-inline: Start=PC_0@startPc, Stop=PC_1@stopPc (temporal, incl. kernel call) + // Total-counter (counter 3) always PC_Range_2-3 over [0, PROG_MEM_END] via PC_Event2/3. + const uint32_t computeEnd = cfg.useStartStop ? cfg.stopPc : PROG_MEM_END; + // PC_Event0..3: Valid bit + 14-bit PC address. - // Compute range = [wpc, PROG_MEM_END] via PC_Event0/PC_Event1 (PC_Range_0-1) - // IO + Compute range = [0, PROG_MEM_END] via PC_Event2/PC_Event3 (PC_Range_2-3) - addWrite(CM_PC_EVENT0 + 0, PC_EVENT_VALID | (wpc & PC_ADDRESS_MASK), - "PC_Event0 @ " + loc + " (wpc = compute range start)"); - addWrite(CM_PC_EVENT0 + 4, PC_EVENT_VALID | (PROG_MEM_END & PC_ADDRESS_MASK), - "PC_Event1 @ " + loc + " (compute range end)"); + addWrite(CM_PC_EVENT0 + 0, PC_EVENT_VALID | (cfg.startPc & PC_ADDRESS_MASK), + "PC_Event0 @ " + loc + (cfg.useStartStop ? " (kernelWrapper loop header / start_pc)" + : " (wpc = compute range start)")); + addWrite(CM_PC_EVENT0 + 4, PC_EVENT_VALID | (computeEnd & PC_ADDRESS_MASK), + "PC_Event1 @ " + loc + (cfg.useStartStop ? " (kernelWrapper loop back-edge / stop_pc)" + : " (compute range end)")); addWrite(CM_PC_EVENT0 + 8, PC_EVENT_VALID | 0, - "PC_Event2 @ " + loc + " (io+compute range start)"); + "PC_Event2 @ " + loc + " (total range start)"); addWrite(CM_PC_EVENT0 + 12, PC_EVENT_VALID | (PROG_MEM_END & PC_ADDRESS_MASK), - "PC_Event3 @ " + loc + " (io+compute range end)"); + "PC_Event3 @ " + loc + " (total range end)"); // Reset performance counters 2 and 3. addWrite(CM_PERF_COUNTER0 + 8, 0, "Reset PerfCounter2 @ " + loc); addWrite(CM_PERF_COUNTER0 + 12, 0, "Reset PerfCounter3 @ " + loc); // Performance_Ctrl1: [6:0]=Cnt2_Start, [14:8]=Cnt2_Stop, [22:16]=Cnt3_Start, [30:24]=Cnt3_Stop - // Counter 2 counts PC_Range_0-1 (Compute); Counter 3 counts PC_Range_2-3 (IO + Compute). + // Counter 2 = kernelWrapper (Compute); Counter 3 = Total (PC_Range_2-3). + // Static-inline counter 2 uses distinct Start(PC_0)/Stop(PC_1) breakpoint events so the + // counter accumulates the whole inner-loop iteration including the kernel call (which + // executes at low addresses outside [start_pc, stop_pc]); a PC-range would exclude it. { + const uint8_t cnt2Start = cfg.useStartStop ? PC_0_EVENT : PC_RANGE_0_1_EVENT; + const uint8_t cnt2Stop = cfg.useStartStop ? PC_1_EVENT : PC_RANGE_0_1_EVENT; uint32_t regValue = 0; - regValue |= (static_cast(PC_RANGE_0_1_EVENT) & 0x7F) << 0; - regValue |= (static_cast(PC_RANGE_0_1_EVENT) & 0x7F) << 8; + regValue |= (static_cast(cnt2Start) & 0x7F) << 0; + regValue |= (static_cast(cnt2Stop) & 0x7F) << 8; regValue |= (static_cast(PC_RANGE_2_3_EVENT) & 0x7F) << 16; regValue |= (static_cast(PC_RANGE_2_3_EVENT) & 0x7F) << 24; addWrite(CM_PERF_CTRL1, regValue, - "PerfCtrl1 @ " + loc + " (ctr2=PC_Range_0-1 compute, ctr3=PC_Range_2-3 io+compute)"); + "PerfCtrl1 @ " + loc + (cfg.useStartStop + ? " (ctr2 start=PC_0 stop=PC_1 kernelWrapper, ctr3=PC_Range_2-3 total)" + : " (ctr2=PC_Range_0-1 kernelWrapper, ctr3=PC_Range_2-3 total)")); } return writes; @@ -1407,11 +1424,11 @@ bool AieDtraceCTWriter::generateComputeIoBoundCT( const std::string& outputPath, void* hwctx, const std::vector& opLocations, - uint32_t wpc) + const ComputeIoCoreConfig& cfg) { return generateCT(outputPath, hwctx, opLocations, /*includeBandwidth=*/false, /*bandwidthMetricSet=*/"", /*bandwidthChannel=*/0, - /*includeComputeIoBound=*/true, wpc); + /*includeComputeIoBound=*/true, cfg); } } // namespace xdp diff --git a/profile/plugin/aie_dtrace/ve2/aie_dtrace_ct_writer.h b/profile/plugin/aie_dtrace/ve2/aie_dtrace_ct_writer.h index cf737ab4..2968c569 100644 --- a/profile/plugin/aie_dtrace/ve2/aie_dtrace_ct_writer.h +++ b/profile/plugin/aie_dtrace/ve2/aie_dtrace_ct_writer.h @@ -92,6 +92,28 @@ struct BandwidthCounterConfig { std::string eventType; // "running" or "stalled" }; +/** + * @brief Resolved configuration for the single core-tile compute_io_bound counter. + * + * Two modes, selected by design type: + * - Reloadable design (useStartStop=false): the compute counter is PC_Range_0-1 + * over [startPc(=wpc), PROG_MEM_END]. Works because wrapper+kernel are contiguous. + * - Static/inlined design (useStartStop=true): kernelWrapper is inlined into main's + * inner loop; the compute counter is driven by PC breakpoint events - Start=PC_0 + * at startPc (loop header), Stop=PC_1 at stopPc (loop back-edge) - so it accumulates + * the whole loop iteration including the dispatched kernel call. + * + * absRow is the ABSOLUTE core-tile row (relative row 0 + aie_tile_row_start) used for + * register addressing. + */ +struct ComputeIoCoreConfig { + bool valid = false; + bool useStartStop = false; // false = reloadable PC-range, true = static start/stop + uint32_t startPc = 0; // wpc (reloadable) or start_pc / loop header (static) + uint32_t stopPc = 0; // stop_pc / loop back-edge (static only) + uint8_t absRow = 0; // absolute core-tile row for register addressing +}; + /** * @class AieDtraceCTWriter * @brief Generates CT (CERT Tracing) files for VE2 AIE profiling @@ -169,21 +191,21 @@ class AieDtraceCTWriter { /** * @brief Generate a self-contained CT file for the compute_io_bound metric * - * Configures a single core (aie) tile at the first column / first core row - * with two PC-range events and two core performance counters: - * - Counter 0 (Compute): PC in [wpc, PROG_MEM_END] via PC_Range_0-1 - * - Counter 1 (IO + Compute): PC in [0, PROG_MEM_END] via PC_Range_2-3 + * Configures a single core (aie) tile with two core performance counters: + * - Counter 2 (kernelWrapper/Compute): reloadable -> PC_Range_0-1 over + * [startPc, PROG_MEM_END]; static-inline -> Start=PC_0@startPc, Stop=PC_1@stopPc + * - Counter 3 (Total): PC_Range_2-3 over [0, PROG_MEM_END] * * @param outputPath Full path for the generated CT file * @param hwctx Hardware context handle for partition info access * @param opLocations Vector of op_loc from aiebu_assembler::get_op_locations - * @param wpc Wrapper PC (reloadable ELF entry PC) from AIE metadata + * @param cfg Resolved compute_io_bound core configuration * @return true if CT file was generated successfully, false otherwise */ bool generateComputeIoBoundCT(const std::string& outputPath, void* hwctx, const std::vector& opLocations, - uint32_t wpc); + const ComputeIoCoreConfig& cfg); /** * @brief Generate a self-contained CT file combining bandwidth and/or @@ -200,7 +222,7 @@ class AieDtraceCTWriter { * @param bandwidthMetricSet Bandwidth metric set (used when includeBandwidth) * @param bandwidthChannel DMA channel for detailed_ddr_*_bandwidth sets * @param includeComputeIoBound Emit the single core-tile compute_io_bound counters - * @param wpc Wrapper PC (used when includeComputeIoBound) + * @param computeIoCfg Resolved compute_io_bound core configuration (used when includeComputeIoBound) * @return true if CT file was generated successfully, false otherwise */ bool generateCT(const std::string& outputPath, @@ -210,7 +232,7 @@ class AieDtraceCTWriter { const std::string& bandwidthMetricSet, uint8_t bandwidthChannel, bool includeComputeIoBound, - uint32_t wpc); + const ComputeIoCoreConfig& computeIoCfg); private: /** @@ -357,26 +379,27 @@ class AieDtraceCTWriter { /** * @brief Append the single core-tile compute_io_bound counters and begin-block writes - * @param wpc Wrapper PC used as the lower bound of the Compute range + * @param cfg Resolved compute_io_bound core configuration * @param counters [in,out] Accumulated counter list * @param beginWrites [in,out] Accumulated begin-block register writes */ - void appendComputeIoBoundConfig(uint32_t wpc, + void appendComputeIoBoundConfig(const ComputeIoCoreConfig& cfg, std::vector& counters, std::vector& beginWrites); /** - * @brief Generate PC-range + performance counter config for a single core tile + * @brief Generate PC-event + performance counter config for a single core tile * - * Programs PC_Event0-3 (with the Valid bit), resets performance counters 0/1, - * and configures Performance_Ctrl0 so counter 0 counts PC_Range_0-1 (Compute) - * and counter 1 counts PC_Range_2-3 (IO + Compute). + * Programs PC_Event0-3 (with the Valid bit), resets performance counters 2/3, + * and configures Performance_Ctrl1 so counter 2 measures kernelWrapper (Compute) + * and counter 3 measures total (PC_Range_2-3 over [0, PROG_MEM_END]). + * - Reloadable (cfg.useStartStop=false): counter 2 = PC_Range_0-1 over [startPc, end]. + * - Static-inline (cfg.useStartStop=true): counter 2 Start=PC_0@startPc, Stop=PC_1@stopPc. * * @param column Partition-relative core tile column - * @param row Core tile row (absolute; first core row = 3) - * @param wpc Wrapper PC used as the lower bound of the Compute range + * @param cfg Resolved compute_io_bound core configuration (provides absRow, mode, PCs) * @return Vector of register writes for the begin block */ - std::vector generatePcRangeCoreConfig(uint8_t column, uint8_t row, uint32_t wpc); + std::vector generatePcRangeCoreConfig(uint8_t column, const ComputeIoCoreConfig& cfg); /** * @brief Write a self-contained counter CT file with begin-block register writes @@ -418,6 +441,8 @@ class AieDtraceCTWriter { static constexpr uint32_t PC_EVENT_VALID = 0x80000000; // PC_Event Valid bit (bit 31) static constexpr uint32_t PC_ADDRESS_MASK = 0x00003FFF; // PC_Address field (bits 13:0) static constexpr uint32_t PROG_MEM_END = 0x00003FFF; // End of 16KB program memory + static constexpr uint8_t PC_0_EVENT = 16; // XAIE2PS_EVENTS_CORE_PC_0 (breakpoint @ PC_Event0) + static constexpr uint8_t PC_1_EVENT = 17; // XAIE2PS_EVENTS_CORE_PC_1 (breakpoint @ PC_Event1) static constexpr uint8_t PC_RANGE_0_1_EVENT = 20; // XAIE2PS_EVENTS_CORE_PC_RANGE_0_1 static constexpr uint8_t PC_RANGE_2_3_EVENT = 21; // XAIE2PS_EVENTS_CORE_PC_RANGE_2_3 diff --git a/profile/plugin/aie_dtrace/ve2/aie_dtrace_ve2.cpp b/profile/plugin/aie_dtrace/ve2/aie_dtrace_ve2.cpp index 507f1195..b0eb7422 100644 --- a/profile/plugin/aie_dtrace/ve2/aie_dtrace_ve2.cpp +++ b/profile/plugin/aie_dtrace/ve2/aie_dtrace_ve2.cpp @@ -6,6 +6,7 @@ #include "xdp/profile/plugin/aie_dtrace/ve2/aie_dtrace_ve2.h" #include "xdp/profile/plugin/aie_dtrace/ve2/aie_dtrace_ct_writer.h" #include "xdp/profile/plugin/aie_dtrace/ve2/elf_helper.h" +#include "xdp/profile/plugin/aie_dtrace/ve2/lst_helper.h" #include "core/common/api/hw_context_int.h" #include "core/common/api/kernel_int.h" @@ -120,18 +121,50 @@ namespace xdp { } } - uint32_t wpc = 0; + // Resolve the compute_io_bound core config. Two modes: + // - Reloadable design: wrapper PC (wpc) from metadata -> PC_Range_0-1 [wpc, end]. + // - Static/inlined design (no reloadable PC): if kernelWrapper is inline (checked + // from the tile source 0_0.cc), derive start/stop PCs from the tile listing + // (0_0.lst) -> counter Start=PC_0@start_pc, Stop=PC_1@stop_pc. + ComputeIoCoreConfig computeIoCfg; if (includeComputeIoBound) { + // Absolute core row = relative row 0 + aie_tile_row_start. + computeIoCfg.absRow = metadata->getCoreRowOffset(); + auto wpcOpt = metadata->getWrapperPC(); - if (!wpcOpt) { - xrt_core::message::send(severity_level::warning, "XRT", - "AIE dtrace: compute_io_bound requested but the wrapper PC " - "(elfs_metadata col0/row0 reloadable_elfs) is missing or its values " - "are not all identical; skipping core configuration."); - includeComputeIoBound = false; + if (wpcOpt) { + computeIoCfg.valid = true; + computeIoCfg.useStartStop = false; + computeIoCfg.startPc = *wpcOpt; // stopPc unused in range mode (writer uses PROG_MEM_END) + std::stringstream wpcMsg; + wpcMsg << "AIE dtrace: compute_io_bound reloadable design, wpc=0x" << std::hex << *wpcOpt; + xrt_core::message::send(severity_level::info, "XRT", wpcMsg.str()); } else { - wpc = *wpcOpt; + // Static/inlined design: gate on kernelWrapper being inline, then parse the listing. + const std::string tileBase = metadata->getStaticElfTileName().value_or("0_0"); + if (!isKernelWrapperInline(tileBase)) { + xrt_core::message::send(severity_level::warning, "XRT", + "AIE dtrace: compute_io_bound requested but no reloadable wrapper PC and " + "kernelWrapper is not inline for tile '" + tileBase + + "'; skipping core configuration."); + includeComputeIoBound = false; + } + else { + auto ss = getStaticStartStopPcFromLst(tileBase); + if (!ss) { + xrt_core::message::send(severity_level::warning, "XRT", + "AIE dtrace: compute_io_bound could not derive start/stop PCs from listing " + "for tile '" + tileBase + "'; skipping core configuration."); + includeComputeIoBound = false; + } + else { + computeIoCfg.valid = true; + computeIoCfg.useStartStop = true; + computeIoCfg.startPc = ss->first; + computeIoCfg.stopPc = ss->second; + } + } } } @@ -166,7 +199,7 @@ namespace xdp { if (!ctWriter.generateCT(outputPath, hwctx, it->second, includeBandwidth, bandwidthMetricSet, bandwidthChannel, - includeComputeIoBound, wpc)) + includeComputeIoBound, computeIoCfg)) return; std::stringstream genMsg; @@ -175,8 +208,14 @@ namespace xdp { genMsg << "interface_tile=" << bandwidthMetricSet; if (includeBandwidth && includeComputeIoBound) genMsg << ", "; - if (includeComputeIoBound) - genMsg << "aie_tile=compute_io_bound wpc=0x" << std::hex << wpc << std::dec; + if (includeComputeIoBound) { + genMsg << "aie_tile=compute_io_bound "; + if (computeIoCfg.useStartStop) + genMsg << "start_pc=0x" << std::hex << computeIoCfg.startPc + << " stop_pc=0x" << computeIoCfg.stopPc << std::dec; + else + genMsg << "wpc=0x" << std::hex << computeIoCfg.startPc << std::dec; + } genMsg << ")"; xrt_core::message::send(severity_level::debug, "XRT", genMsg.str()); diff --git a/profile/plugin/aie_dtrace/ve2/lst_helper.cpp b/profile/plugin/aie_dtrace/ve2/lst_helper.cpp new file mode 100644 index 00000000..5f3bf70e --- /dev/null +++ b/profile/plugin/aie_dtrace/ve2/lst_helper.cpp @@ -0,0 +1,314 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright (C) 2026 Advanced Micro Devices, Inc. All rights reserved + +#define XDP_PLUGIN_SOURCE + +#include "xdp/profile/plugin/aie_dtrace/ve2/lst_helper.h" + +#include "core/common/message.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace xdp { + +namespace { + + namespace fs = std::filesystem; + using severity_level = xrt_core::message::severity_level; + + // End of 16KB program memory; PC_Address is bits [13:0]. + constexpr uint32_t PROG_MEM_END = 0x3FFF; + + bool endsWith(const std::string& s, const std::string& suffix) + { + return s.size() >= suffix.size() + && s.compare(s.size() - suffix.size(), suffix.size(), suffix) == 0; + } + + std::string trimLeft(const std::string& s) + { + size_t i = s.find_first_not_of(" \t"); + return (i == std::string::npos) ? std::string() : s.substr(i); + } + + // Recursively search the current working directory for the first regular file + // whose path ends with 'suffix' (using forward-slash form). Empty on failure. + fs::path findBySuffix(const std::string& suffix) + { + std::error_code ec; + fs::path root = fs::current_path(ec); + if (ec) + return {}; + + fs::recursive_directory_iterator it(root, + fs::directory_options::skip_permission_denied, ec), end; + for (; !ec && it != end; it.increment(ec)) { + std::error_code fec; + if (!it->is_regular_file(fec)) + continue; + if (endsWith(it->path().generic_string(), suffix)) + return it->path(); + } + return {}; + } + + // One disassembly instruction (or VLIW bundle) line: leading address + the + // per-op mnemonic list (bytes stripped). + struct InstrLine { + uint32_t addr = 0; + std::vector ops; // trimmed op strings split on ';' + }; + + // Parse a disassembly line ": \t;\t..." into its + // leading address and the list of trimmed op strings (VLIW bundle split on ';'). + // The assembly portion starts at the first tab (after the byte columns). + bool parseInstrLine(const std::string& line, InstrLine& out) + { + size_t colon = line.find(':'); + if (colon == std::string::npos) + return false; + // Address is the leading whitespace-prefixed hex token before ':'. + std::string addrTok = trimLeft(line.substr(0, colon)); + if (addrTok.empty()) + return false; + for (char c : addrTok) { + if (!std::isxdigit(static_cast(c))) + return false; + } + size_t tab = line.find('\t', colon); + if (tab == std::string::npos) + return false; // no assembly (e.g. "...:" continuation lines) + + try { + out.addr = static_cast(std::stoul(addrTok, nullptr, 16)); + } + catch (...) { + return false; + } + + out.ops.clear(); + std::string asmText = line.substr(tab + 1); + std::stringstream ss(asmText); + std::string op; + while (std::getline(ss, op, ';')) { + std::string t = trimLeft(op); + if (!t.empty()) + out.ops.push_back(t); + } + return !out.ops.empty(); + } + + // Mnemonic = first whitespace/tab-delimited token of an op. + std::string mnemonic(const std::string& op) + { + size_t i = op.find_first_of(" \t"); + return (i == std::string::npos) ? op : op.substr(0, i); + } + + // Extract "#0x" immediate target from an op; std::nullopt if none. + std::optional immTarget(const std::string& op) + { + size_t h = op.find("#0x"); + if (h == std::string::npos) + return std::nullopt; + size_t start = h + 3; + size_t i = start; + while (i < op.size() && std::isxdigit(static_cast(op[i]))) + ++i; + if (i == start) + return std::nullopt; + try { + return static_cast(std::stoul(op.substr(start, i - start), nullptr, 16)); + } + catch (...) { + return std::nullopt; + } + } + + bool opIsIndirectJl(const std::string& op) + { + if (mnemonic(op) != "jl") + return false; + // Operand is a register (e.g. "p1"); direct calls use "#0x...". + std::string rest = trimLeft(op.substr(2)); + return !rest.empty() && rest[0] == 'p'; + } + + bool opIsBranchWithTarget(const std::string& op, uint32_t& target) + { + std::string m = mnemonic(op); + if (m != "j" && m != "jz" && m != "jnz") + return false; + auto t = immTarget(op); + if (!t) + return false; + target = *t; + return true; + } + +} // namespace + +bool +isKernelWrapperInline(const std::string& tileBase) +{ + const std::string suffix = "aie/" + tileBase + "/src/" + tileBase + ".cc"; + fs::path srcPath = findBySuffix(suffix); + if (srcPath.empty()) { + xrt_core::message::send(severity_level::debug, "XRT", + "AIE dtrace: source '" + suffix + "' not found under run dir; cannot confirm kernelWrapper inline."); + return false; + } + + std::ifstream f(srcPath); + if (!f.is_open()) + return false; + + // Scan for a kernelWrapper definition marked inline. We look at "void kernelWrapper(" + // lines only (ignores call sites like "kernelWrapper(args, ...)"); a bare/extern + // declaration without the inline attribute is skipped so it does not mask a later + // inline definition. + std::string line; + while (std::getline(f, line)) { + if (line.find("void kernelWrapper(") != std::string::npos + && (line.find("always_inline") != std::string::npos + || line.find("inline") != std::string::npos)) { + xrt_core::message::send(severity_level::debug, "XRT", + "AIE dtrace: kernelWrapper is inline in " + srcPath.generic_string()); + return true; + } + } + return false; +} + +std::optional> +getStaticStartStopPcFromLst(const std::string& tileBase) +{ + const std::string suffix = "aie/" + tileBase + "/Release/" + tileBase + ".lst"; + fs::path lstPath = findBySuffix(suffix); + if (lstPath.empty()) { + xrt_core::message::send(severity_level::warning, "XRT", + "AIE dtrace: listing '" + suffix + "' not found under run dir; skipping compute_io_bound."); + return std::nullopt; + } + + std::ifstream f(lstPath); + if (!f.is_open()) + return std::nullopt; + + // Collect function labels (addr, name) in file (address) order and all + // instruction lines (addr, ops). + std::vector> labels; + std::vector instrs; + + std::string line; + while (std::getline(f, line)) { + if (!line.empty() && line.back() == '\r') + line.pop_back(); + + // Label line: "<8-hex-digit addr> :" (a function symbol or a compiler-local ".L*" label). + if (line.size() > 10 && std::isxdigit(static_cast(line[0])) + && line.find(" <") != std::string::npos && endsWith(line, ">:")) { + size_t lt = line.find(" <"); + std::string addrTok = line.substr(0, lt); + std::string name = line.substr(lt + 2, line.size() - (lt + 2) - 2); // between "<" and ">:" + bool hexAddr = addrTok.size() == 8 + && std::all_of(addrTok.begin(), addrTok.end(), + [](char c){ return std::isxdigit(static_cast(c)); }); + if (hexAddr) { + try { + labels.emplace_back(static_cast(std::stoul(addrTok, nullptr, 16)), name); + } + catch (...) {} + continue; + } + } + + InstrLine il; + if (parseInstrLine(line, il)) + instrs.push_back(std::move(il)); + } + + // Find main and the next FUNCTION label after it (main region = [mainAddr, mainEnd)). + // Skip compiler-local basic-block labels (names starting with ".") which live inside a function. + uint32_t mainAddr = 0; + uint32_t mainEnd = PROG_MEM_END; + bool foundMain = false; + for (size_t i = 0; i < labels.size(); ++i) { + if (labels[i].second == "main") { + mainAddr = labels[i].first; + foundMain = true; + for (size_t j = i + 1; j < labels.size(); ++j) { + if (!labels[j].second.empty() && labels[j].second[0] != '.') { + mainEnd = labels[j].first; + break; + } + } + break; + } + } + if (!foundMain) { + xrt_core::message::send(severity_level::warning, "XRT", + "AIE dtrace: 'main' not found in " + lstPath.generic_string() + "; skipping compute_io_bound."); + return std::nullopt; + } + + // Within main: require exactly one indirect 'jl pN' dispatch. + int indirectJlCount = 0; + uint32_t jlAddr = 0; + for (const auto& il : instrs) { + if (il.addr < mainAddr || il.addr >= mainEnd) + continue; + for (const auto& op : il.ops) { + if (opIsIndirectJl(op)) { + ++indirectJlCount; + jlAddr = il.addr; + } + } + } + if (indirectJlCount != 1) { + xrt_core::message::send(severity_level::warning, "XRT", + "AIE dtrace: expected exactly one indirect 'jl pN' in main, found " + + std::to_string(indirectJlCount) + " in " + lstPath.generic_string() + + "; skipping compute_io_bound."); + return std::nullopt; + } + + // First backward branch after the indirect jl: target = start_pc, addr = stop_pc. + for (const auto& il : instrs) { + if (il.addr <= jlAddr || il.addr >= mainEnd) + continue; + for (const auto& op : il.ops) { + uint32_t target = 0; + if (opIsBranchWithTarget(op, target) && target < il.addr) { + uint32_t startPc = target; + uint32_t stopPc = il.addr; + if (startPc < stopPc && stopPc <= PROG_MEM_END) { + std::stringstream msg; + msg << "AIE dtrace: compute_io_bound start/stop PCs from " << lstPath.generic_string() + << ": start_pc=0x" << std::hex << startPc << " stop_pc=0x" << stopPc; + xrt_core::message::send(severity_level::info, "XRT", msg.str()); + return std::make_pair(startPc, stopPc); + } + xrt_core::message::send(severity_level::warning, "XRT", + "AIE dtrace: invalid start/stop PC range parsed; skipping compute_io_bound."); + return std::nullopt; + } + } + } + + xrt_core::message::send(severity_level::warning, "XRT", + "AIE dtrace: no backward branch found after kernel dispatch in " + + lstPath.generic_string() + "; skipping compute_io_bound."); + return std::nullopt; +} + +} // namespace xdp diff --git a/profile/plugin/aie_dtrace/ve2/lst_helper.h b/profile/plugin/aie_dtrace/ve2/lst_helper.h new file mode 100644 index 00000000..bb6c3e85 --- /dev/null +++ b/profile/plugin/aie_dtrace/ve2/lst_helper.h @@ -0,0 +1,36 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright (C) 2026 Advanced Micro Devices, Inc. All rights reserved +#ifndef XDP_AIE_DTRACE_VE2_LST_HELPER_H +#define XDP_AIE_DTRACE_VE2_LST_HELPER_H + +#include +#include +#include +#include + +namespace xdp { + +// Helpers for the compute_io_bound "static/inlined design" path. These parse the +// aiecompiler listing (.lst) and source (.cc) for a core tile, located by searching +// the current working directory (the run directory, where the design's aiecompiler +// Work tree is co-located in the Telluride flow). +// +// tileBase is the RELATIVE-row listing base name (e.g. "0_0"): first core tile in +// column 0, from elfs_metadata[col0/row0].static_elfs. + +// True if kernelWrapper is an inline function in aie//src/.cc +// (definition line carries __attribute__((always_inline)) or a plain inline). +// False when the source is not found or kernelWrapper is not inline. +bool +isKernelWrapperInline(const std::string& tileBase); + +// Parse aie//Release/.lst for the inlined-kernelWrapper inner +// loop: isolate main, find the single indirect 'jl pN' dispatch, then the first +// backward branch after it. Returns {start_pc, stop_pc} = {branch target (loop +// header), branch instruction address}, or std::nullopt if not found / invalid. +std::optional> +getStaticStartStopPcFromLst(const std::string& tileBase); + +} // namespace xdp + +#endif From 21e3bd8a7387b72b50d97f225c672a75c375ae33 Mon Sep 17 00:00:00 2001 From: Jyotheeswar Ganne Date: Tue, 28 Jul 2026 08:45:49 -0600 Subject: [PATCH 4/6] Use unified compute start/stop PCs for compute_io_bound Both static and reloadable designs now measure the compute window with the same rule instead of two different derivations: start_pc is the single indirect kernel dispatch ("jl pN") found in the core tile's .lst, and stop_pc is the 10th listed instruction from it (the dispatch counted as #1, "..." elision lines skipped), clamped at the end of the enclosing label. Performance counter 2 is always driven by PC breakpoint events (Start=PC_0, Stop=PC_1), so the called kernel is included in the count; counter 3 (total) is unchanged. Reloadable designs read every _reloadable*.lst and require them to agree on the PCs; static designs read .lst. Listing lookup searches only for those filenames, pruning the walk so it never descends into the other per-tile directories, and the result is cached so the search runs once per process rather than once per run. XRT_AIE_DTRACE_DESIGN_DIR can override the search root. Also shrinks the change relative to upstream: base_filetype_impl.h is back to master (the reloadable-ELF PC and static-ELF tile-name helpers are no longer needed), the unused metadata accessors are gone, and the absolute core row now comes from the existing driver_config.aie_tile_row_start. Co-authored-by: Cursor --- .../filetypes/base_filetype_impl.h | 66 --- .../plugin/aie_dtrace/aie_dtrace_metadata.h | 29 +- .../aie_dtrace/ve2/aie_dtrace_ct_writer.cpp | 65 +-- .../aie_dtrace/ve2/aie_dtrace_ct_writer.h | 63 +-- .../plugin/aie_dtrace/ve2/aie_dtrace_ve2.cpp | 68 +-- profile/plugin/aie_dtrace/ve2/lst_helper.cpp | 474 ++++++++++-------- profile/plugin/aie_dtrace/ve2/lst_helper.h | 39 +- 7 files changed, 342 insertions(+), 462 deletions(-) diff --git a/profile/database/static_info/filetypes/base_filetype_impl.h b/profile/database/static_info/filetypes/base_filetype_impl.h index 1595cc0a..93108b05 100644 --- a/profile/database/static_info/filetypes/base_filetype_impl.h +++ b/profile/database/static_info/filetypes/base_filetype_impl.h @@ -5,8 +5,6 @@ #define BASE_FILETYPE_DOT_H #include -#include -#include #include "xdp/profile/database/static_info/aie_constructs.h" namespace xdp::aie { @@ -19,70 +17,6 @@ class BaseFiletypeImpl { BaseFiletypeImpl() = delete; virtual ~BaseFiletypeImpl() {}; - // Returns the wrapper PC (reloadable ELF entry PC) for the core tile at - // column 0, row 0 from the top-level "elfs_metadata" section. All - // "reloadable_elfs" values in that entry must be identical; otherwise - // (or if the entry is missing/empty) std::nullopt is returned and the - // caller must skip configuration. - std::optional - getReloadableElfEntryPC() const - { - auto elfsMetadata = aie_meta.get_child_optional("elfs_metadata"); - if (!elfsMetadata) - return std::nullopt; - - for (const auto& entry : elfsMetadata.get()) { - auto column = entry.second.get_optional("column"); - auto row = entry.second.get_optional("row"); - if (!column || !row || *column != 0 || *row != 0) - continue; - - auto reloadableElfs = entry.second.get_child_optional("reloadable_elfs"); - if (!reloadableElfs) - return std::nullopt; - - std::optional commonPC; - for (const auto& elf : reloadableElfs.get()) { - auto pc = static_cast(elf.second.get_value()); - if (!commonPC) - commonPC = pc; - else if (*commonPC != pc) - return std::nullopt; - } - return commonPC; - } - - return std::nullopt; - } - - // Returns the static ELF tile base name (e.g. "0_0") for the core tile at - // column 0, row 0 from "elfs_metadata" (the first key under "static_elfs"). - // This is the RELATIVE-row listing base used to locate 0_0.lst / 0_0.cc. - // std::nullopt if the entry or its static_elfs are missing. - std::optional - getStaticElfTileName() const - { - auto elfsMetadata = aie_meta.get_child_optional("elfs_metadata"); - if (!elfsMetadata) - return std::nullopt; - - for (const auto& entry : elfsMetadata.get()) { - auto column = entry.second.get_optional("column"); - auto row = entry.second.get_optional("row"); - if (!column || !row || *column != 0 || *row != 0) - continue; - - auto staticElfs = entry.second.get_child_optional("static_elfs"); - if (!staticElfs) - return std::nullopt; - for (const auto& e : staticElfs.get()) - return e.first; // first (and typically only) key, e.g. "0_0" - return std::nullopt; - } - - return std::nullopt; - } - // Top level interface used for both file type formats virtual driver_config diff --git a/profile/plugin/aie_dtrace/aie_dtrace_metadata.h b/profile/plugin/aie_dtrace/aie_dtrace_metadata.h index d44268ba..5b3b7469 100644 --- a/profile/plugin/aie_dtrace/aie_dtrace_metadata.h +++ b/profile/plugin/aie_dtrace/aie_dtrace_metadata.h @@ -7,7 +7,6 @@ #include #include #include -#include #include #include @@ -22,8 +21,9 @@ class AieDtraceMetadata { static constexpr int CORE_MODULE_IDX = static_cast(module_type::core); static constexpr int NUM_MODULES = static_cast(module_type::num_types); - // The compute_io_bound metric configures a single fixed core tile at the - // first column / first core row (absolute location col 0, row 3). + // compute_io_bound configures a single core tile: the first column / first + // core row. Used as the config-map key here; the CT writer derives the + // actual absolute row from driver_config.aie_tile_row_start. static constexpr uint8_t COMPUTE_IO_CORE_COL = 0; static constexpr uint8_t COMPUTE_IO_CORE_ROW = 3; @@ -85,29 +85,6 @@ class AieDtraceMetadata { aie::driver_config getAIEConfigMetadata(); - // Wrapper PC (reloadable ELF entry PC) for the compute_io_bound core tile. - // Returns std::nullopt when the col0/row0 elfs_metadata entry is missing or - // its reloadable_elfs values are not all identical (e.g. static/inlined designs). - std::optional getWrapperPC() const { - return metadataReader == nullptr ? std::nullopt - : metadataReader->getReloadableElfEntryPC(); - } - - // Static ELF tile base name (e.g. "0_0") for the col0/row0 core tile, used to - // locate the relative-row listing/source (0_0.lst / 0_0.cc) for static designs. - std::optional getStaticElfTileName() const { - return metadataReader == nullptr ? std::nullopt - : metadataReader->getStaticElfTileName(); - } - - // AIE core tile row offset (aie_tile_row_start): absolute row of the first - // core row. The compute_io_bound tile is relative row 0, so its absolute row - // equals this offset. Falls back to COMPUTE_IO_CORE_ROW when unavailable. - uint8_t getCoreRowOffset() const { - return metadataReader == nullptr ? COMPUTE_IO_CORE_ROW - : metadataReader->getAIETileRowOffset(); - } - std::unique_ptr createAIEProfileConfig(); }; diff --git a/profile/plugin/aie_dtrace/ve2/aie_dtrace_ct_writer.cpp b/profile/plugin/aie_dtrace/ve2/aie_dtrace_ct_writer.cpp index 8d8d9f7a..160a19f5 100644 --- a/profile/plugin/aie_dtrace/ve2/aie_dtrace_ct_writer.cpp +++ b/profile/plugin/aie_dtrace/ve2/aie_dtrace_ct_writer.cpp @@ -101,11 +101,13 @@ AieDtraceCTWriter::AieDtraceCTWriter(VPDatabase* database, , deviceId(deviceId) , columnShift(0) , rowShift(0) + , coreRowStart(0) , partitionStartCol(startCol) { auto config = metadata->getAIEConfigMetadata(); columnShift = config.column_shift; rowShift = config.row_shift; + coreRowStart = config.aie_tile_row_start; } bool AieDtraceCTWriter::generate() @@ -1261,10 +1263,10 @@ void AieDtraceCTWriter::appendComputeIoBoundConfig( const ComputeIoCoreConfig& cfg, std::vector& counters, std::vector& beginWrites) { - // Single core tile at (COMPUTE_IO_CORE_COL, cfg.absRow): two counters (compute and - // io+compute) using performance counters 2 and 3. absRow is the absolute core row - // (relative row 0 + aie_tile_row_start) for register addressing. - const uint8_t row = cfg.absRow; + // Single core tile: two counters (kernelWrapper and total) using performance + // counters 2 and 3. The listing/metadata use relative core rows, so the first + // core row's absolute row for register addressing is aie_tile_row_start. + const uint8_t row = coreRowStart; CTCounterInfo compute; compute.column = COMPUTE_IO_CORE_COL; @@ -1290,8 +1292,8 @@ void AieDtraceCTWriter::appendComputeIoBoundConfig( ioCompute.eventType = "io_compute"; counters.push_back(ioCompute); - auto pcRangeWrites = generatePcRangeCoreConfig(COMPUTE_IO_CORE_COL, cfg); - beginWrites.insert(beginWrites.end(), pcRangeWrites.begin(), pcRangeWrites.end()); + auto pcWrites = generatePcStartStopCoreConfig(COMPUTE_IO_CORE_COL, cfg); + beginWrites.insert(beginWrites.end(), pcWrites.begin(), pcWrites.end()); } bool AieDtraceCTWriter::generateCT( @@ -1322,7 +1324,7 @@ bool AieDtraceCTWriter::generateCT( // Both metric families can be emitted into the same CT file. Bandwidth // counters live on shim tiles (row 0); the compute_io_bound counters live on - // the single core tile (col 0, row 3). filterCountersByColumn keys by column, + // the single core tile (col 0, first core row). filterCountersByColumn keys by column, // so both simply land in the matching UC group and read distinct addresses. if (includeBandwidth) appendBandwidthConfig(hwctx, bandwidthMetricSet, bandwidthChannel, allCounters, beginBlockWrites); @@ -1357,12 +1359,12 @@ bool AieDtraceCTWriter::generateBandwidthCT( /*includeComputeIoBound=*/false, ComputeIoCoreConfig{}); } -std::vector AieDtraceCTWriter::generatePcRangeCoreConfig( +std::vector AieDtraceCTWriter::generatePcStartStopCoreConfig( uint8_t column, const ComputeIoCoreConfig& cfg) { std::vector writes; - const uint8_t row = cfg.absRow; + const uint8_t row = coreRowStart; uint64_t tileAddress = (static_cast(column) << columnShift) | (static_cast(row) << rowShift); @@ -1376,19 +1378,13 @@ std::vector AieDtraceCTWriter::generatePcRangeCoreConfig( std::string loc = "core (" + std::to_string(column) + "," + std::to_string(row) + ")"; - // Compute-counter (counter 2) uses PC_Event0/PC_Event1: - // reloadable : PC_Range_0-1 over [startPc(=wpc), PROG_MEM_END] - // static-inline: Start=PC_0@startPc, Stop=PC_1@stopPc (temporal, incl. kernel call) - // Total-counter (counter 3) always PC_Range_2-3 over [0, PROG_MEM_END] via PC_Event2/3. - const uint32_t computeEnd = cfg.useStartStop ? cfg.stopPc : PROG_MEM_END; - + // PC_Event0/1 bracket the kernelWrapper dispatch window (counter 2); + // PC_Event2/3 give the total range [0, PROG_MEM_END] (counter 3). // PC_Event0..3: Valid bit + 14-bit PC address. addWrite(CM_PC_EVENT0 + 0, PC_EVENT_VALID | (cfg.startPc & PC_ADDRESS_MASK), - "PC_Event0 @ " + loc + (cfg.useStartStop ? " (kernelWrapper loop header / start_pc)" - : " (wpc = compute range start)")); - addWrite(CM_PC_EVENT0 + 4, PC_EVENT_VALID | (computeEnd & PC_ADDRESS_MASK), - "PC_Event1 @ " + loc + (cfg.useStartStop ? " (kernelWrapper loop back-edge / stop_pc)" - : " (compute range end)")); + "PC_Event0 @ " + loc + " (kernelWrapper start_pc = indirect kernel dispatch)"); + addWrite(CM_PC_EVENT0 + 4, PC_EVENT_VALID | (cfg.stopPc & PC_ADDRESS_MASK), + "PC_Event1 @ " + loc + " (kernelWrapper stop_pc)"); addWrite(CM_PC_EVENT0 + 8, PC_EVENT_VALID | 0, "PC_Event2 @ " + loc + " (total range start)"); addWrite(CM_PC_EVENT0 + 12, PC_EVENT_VALID | (PROG_MEM_END & PC_ADDRESS_MASK), @@ -1399,37 +1395,22 @@ std::vector AieDtraceCTWriter::generatePcRangeCoreConfig( addWrite(CM_PERF_COUNTER0 + 12, 0, "Reset PerfCounter3 @ " + loc); // Performance_Ctrl1: [6:0]=Cnt2_Start, [14:8]=Cnt2_Stop, [22:16]=Cnt3_Start, [30:24]=Cnt3_Stop - // Counter 2 = kernelWrapper (Compute); Counter 3 = Total (PC_Range_2-3). - // Static-inline counter 2 uses distinct Start(PC_0)/Stop(PC_1) breakpoint events so the - // counter accumulates the whole inner-loop iteration including the kernel call (which - // executes at low addresses outside [start_pc, stop_pc]); a PC-range would exclude it. + // Counter 2 = kernelWrapper, counter 3 = total. Counter 2 uses distinct Start(PC_0)/ + // Stop(PC_1) breakpoint events, i.e. a temporal window rather than a PC range, so the + // called kernel is counted even though it executes outside [start_pc, stop_pc]. { - const uint8_t cnt2Start = cfg.useStartStop ? PC_0_EVENT : PC_RANGE_0_1_EVENT; - const uint8_t cnt2Stop = cfg.useStartStop ? PC_1_EVENT : PC_RANGE_0_1_EVENT; uint32_t regValue = 0; - regValue |= (static_cast(cnt2Start) & 0x7F) << 0; - regValue |= (static_cast(cnt2Stop) & 0x7F) << 8; + regValue |= (static_cast(PC_0_EVENT) & 0x7F) << 0; + regValue |= (static_cast(PC_1_EVENT) & 0x7F) << 8; regValue |= (static_cast(PC_RANGE_2_3_EVENT) & 0x7F) << 16; regValue |= (static_cast(PC_RANGE_2_3_EVENT) & 0x7F) << 24; addWrite(CM_PERF_CTRL1, regValue, - "PerfCtrl1 @ " + loc + (cfg.useStartStop - ? " (ctr2 start=PC_0 stop=PC_1 kernelWrapper, ctr3=PC_Range_2-3 total)" - : " (ctr2=PC_Range_0-1 kernelWrapper, ctr3=PC_Range_2-3 total)")); + "PerfCtrl1 @ " + loc + + " (ctr2 start=PC_0 stop=PC_1 kernelWrapper, ctr3=PC_Range_2-3 total)"); } return writes; } -bool AieDtraceCTWriter::generateComputeIoBoundCT( - const std::string& outputPath, - void* hwctx, - const std::vector& opLocations, - const ComputeIoCoreConfig& cfg) -{ - return generateCT(outputPath, hwctx, opLocations, - /*includeBandwidth=*/false, /*bandwidthMetricSet=*/"", /*bandwidthChannel=*/0, - /*includeComputeIoBound=*/true, cfg); -} - } // namespace xdp diff --git a/profile/plugin/aie_dtrace/ve2/aie_dtrace_ct_writer.h b/profile/plugin/aie_dtrace/ve2/aie_dtrace_ct_writer.h index 2968c569..d31414e3 100644 --- a/profile/plugin/aie_dtrace/ve2/aie_dtrace_ct_writer.h +++ b/profile/plugin/aie_dtrace/ve2/aie_dtrace_ct_writer.h @@ -93,25 +93,16 @@ struct BandwidthCounterConfig { }; /** - * @brief Resolved configuration for the single core-tile compute_io_bound counter. + * @brief Resolved kernelWrapper PCs for the single core-tile compute_io_bound counter. * - * Two modes, selected by design type: - * - Reloadable design (useStartStop=false): the compute counter is PC_Range_0-1 - * over [startPc(=wpc), PROG_MEM_END]. Works because wrapper+kernel are contiguous. - * - Static/inlined design (useStartStop=true): kernelWrapper is inlined into main's - * inner loop; the compute counter is driven by PC breakpoint events - Start=PC_0 - * at startPc (loop header), Stop=PC_1 at stopPc (loop back-edge) - so it accumulates - * the whole loop iteration including the dispatched kernel call. - * - * absRow is the ABSOLUTE core-tile row (relative row 0 + aie_tile_row_start) used for - * register addressing. + * Used identically for static and reloadable designs: the compute counter is driven + * by PC breakpoint events - Start=PC_0 at startPc (the indirect kernel dispatch), + * Stop=PC_1 at stopPc (the 10th listed instruction from it) - so it accumulates the + * whole dispatch window including the called kernel. */ struct ComputeIoCoreConfig { - bool valid = false; - bool useStartStop = false; // false = reloadable PC-range, true = static start/stop - uint32_t startPc = 0; // wpc (reloadable) or start_pc / loop header (static) - uint32_t stopPc = 0; // stop_pc / loop back-edge (static only) - uint8_t absRow = 0; // absolute core-tile row for register addressing + uint32_t startPc = 0; // indirect kernel dispatch ("jl pN") + uint32_t stopPc = 0; // 10th listed instruction from the dispatch }; /** @@ -188,25 +179,6 @@ class AieDtraceCTWriter { const std::string& metricSet = "ddr_bandwidth", uint8_t channel = 0); - /** - * @brief Generate a self-contained CT file for the compute_io_bound metric - * - * Configures a single core (aie) tile with two core performance counters: - * - Counter 2 (kernelWrapper/Compute): reloadable -> PC_Range_0-1 over - * [startPc, PROG_MEM_END]; static-inline -> Start=PC_0@startPc, Stop=PC_1@stopPc - * - Counter 3 (Total): PC_Range_2-3 over [0, PROG_MEM_END] - * - * @param outputPath Full path for the generated CT file - * @param hwctx Hardware context handle for partition info access - * @param opLocations Vector of op_loc from aiebu_assembler::get_op_locations - * @param cfg Resolved compute_io_bound core configuration - * @return true if CT file was generated successfully, false otherwise - */ - bool generateComputeIoBoundCT(const std::string& outputPath, - void* hwctx, - const std::vector& opLocations, - const ComputeIoCoreConfig& cfg); - /** * @brief Generate a self-contained CT file combining bandwidth and/or * compute_io_bound metrics into a single begin block + counter reads. @@ -389,17 +361,17 @@ class AieDtraceCTWriter { /** * @brief Generate PC-event + performance counter config for a single core tile * - * Programs PC_Event0-3 (with the Valid bit), resets performance counters 2/3, - * and configures Performance_Ctrl1 so counter 2 measures kernelWrapper (Compute) - * and counter 3 measures total (PC_Range_2-3 over [0, PROG_MEM_END]). - * - Reloadable (cfg.useStartStop=false): counter 2 = PC_Range_0-1 over [startPc, end]. - * - Static-inline (cfg.useStartStop=true): counter 2 Start=PC_0@startPc, Stop=PC_1@stopPc. + * Programs PC_Event0-3 (with the Valid bit), resets performance counters 2/3, and + * configures Performance_Ctrl1 so counter 2 measures kernelWrapper (Start=PC_0 at + * startPc, Stop=PC_1 at stopPc) and counter 3 measures total (PC_Range_2-3 over + * [0, PROG_MEM_END]). * * @param column Partition-relative core tile column - * @param cfg Resolved compute_io_bound core configuration (provides absRow, mode, PCs) + * @param cfg Resolved kernelWrapper start/stop PCs * @return Vector of register writes for the begin block */ - std::vector generatePcRangeCoreConfig(uint8_t column, const ComputeIoCoreConfig& cfg); + std::vector generatePcStartStopCoreConfig(uint8_t column, + const ComputeIoCoreConfig& cfg); /** * @brief Write a self-contained counter CT file with begin-block register writes @@ -422,6 +394,7 @@ class AieDtraceCTWriter { // AIE configuration values uint8_t columnShift; uint8_t rowShift; + uint8_t coreRowStart; // Absolute row of the first AIE core row (aie_tile_row_start) uint8_t partitionStartCol; // Absolute start column of the hw_context partition // Base offsets by module type @@ -443,13 +416,11 @@ class AieDtraceCTWriter { static constexpr uint32_t PROG_MEM_END = 0x00003FFF; // End of 16KB program memory static constexpr uint8_t PC_0_EVENT = 16; // XAIE2PS_EVENTS_CORE_PC_0 (breakpoint @ PC_Event0) static constexpr uint8_t PC_1_EVENT = 17; // XAIE2PS_EVENTS_CORE_PC_1 (breakpoint @ PC_Event1) - static constexpr uint8_t PC_RANGE_0_1_EVENT = 20; // XAIE2PS_EVENTS_CORE_PC_RANGE_0_1 static constexpr uint8_t PC_RANGE_2_3_EVENT = 21; // XAIE2PS_EVENTS_CORE_PC_RANGE_2_3 - // compute_io_bound configures a single core tile at the first column / first - // core row (partition-relative col 0, row 3). + // compute_io_bound configures a single core tile: the first column / first core + // row. The absolute row comes from coreRowStart (driver_config.aie_tile_row_start). static constexpr uint8_t COMPUTE_IO_CORE_COL = 0; - static constexpr uint8_t COMPUTE_IO_CORE_ROW = 3; // Bandwidth monitoring constants static constexpr uint8_t NUM_BANDWIDTH_COUNTERS = 4; diff --git a/profile/plugin/aie_dtrace/ve2/aie_dtrace_ve2.cpp b/profile/plugin/aie_dtrace/ve2/aie_dtrace_ve2.cpp index b0eb7422..580fb2f5 100644 --- a/profile/plugin/aie_dtrace/ve2/aie_dtrace_ve2.cpp +++ b/profile/plugin/aie_dtrace/ve2/aie_dtrace_ve2.cpp @@ -26,6 +26,10 @@ namespace xdp { static constexpr int SHIM_MODULE_IDX = static_cast(module_type::shim); static constexpr int CORE_MODULE_IDX = static_cast(module_type::core); + // Listing base name of the compute_io_bound core tile: first column, first core + // row. Listings/elfs_metadata use RELATIVE core rows, hence "_0". + static constexpr const char* COMPUTE_IO_TILE_BASE = "0_0"; + AieDtrace_VE2Impl::AieDtrace_VE2Impl(VPDatabase* database, std::shared_ptr metadata, uint64_t deviceID) @@ -121,50 +125,23 @@ namespace xdp { } } - // Resolve the compute_io_bound core config. Two modes: - // - Reloadable design: wrapper PC (wpc) from metadata -> PC_Range_0-1 [wpc, end]. - // - Static/inlined design (no reloadable PC): if kernelWrapper is inline (checked - // from the tile source 0_0.cc), derive start/stop PCs from the tile listing - // (0_0.lst) -> counter Start=PC_0@start_pc, Stop=PC_1@stop_pc. + // Resolve the compute_io_bound compute start/stop PCs from the core tile's listing. + // Same rule for static and reloadable designs: start_pc is the indirect kernel + // dispatch, stop_pc the 10th listed instruction from it. tileBase is the + // relative-row listing base name of the first core tile. ComputeIoCoreConfig computeIoCfg; if (includeComputeIoBound) { - // Absolute core row = relative row 0 + aie_tile_row_start. - computeIoCfg.absRow = metadata->getCoreRowOffset(); - - auto wpcOpt = metadata->getWrapperPC(); - if (wpcOpt) { - computeIoCfg.valid = true; - computeIoCfg.useStartStop = false; - computeIoCfg.startPc = *wpcOpt; // stopPc unused in range mode (writer uses PROG_MEM_END) - std::stringstream wpcMsg; - wpcMsg << "AIE dtrace: compute_io_bound reloadable design, wpc=0x" << std::hex << *wpcOpt; - xrt_core::message::send(severity_level::info, "XRT", wpcMsg.str()); + const std::string tileBase = COMPUTE_IO_TILE_BASE; + auto pcs = getComputeStartStopPc(tileBase); + if (!pcs) { + xrt_core::message::send(severity_level::warning, "XRT", + "AIE dtrace: compute_io_bound could not derive compute start/stop PCs " + "for tile '" + tileBase + "'; skipping core configuration."); + includeComputeIoBound = false; } else { - // Static/inlined design: gate on kernelWrapper being inline, then parse the listing. - const std::string tileBase = metadata->getStaticElfTileName().value_or("0_0"); - if (!isKernelWrapperInline(tileBase)) { - xrt_core::message::send(severity_level::warning, "XRT", - "AIE dtrace: compute_io_bound requested but no reloadable wrapper PC and " - "kernelWrapper is not inline for tile '" + tileBase - + "'; skipping core configuration."); - includeComputeIoBound = false; - } - else { - auto ss = getStaticStartStopPcFromLst(tileBase); - if (!ss) { - xrt_core::message::send(severity_level::warning, "XRT", - "AIE dtrace: compute_io_bound could not derive start/stop PCs from listing " - "for tile '" + tileBase + "'; skipping core configuration."); - includeComputeIoBound = false; - } - else { - computeIoCfg.valid = true; - computeIoCfg.useStartStop = true; - computeIoCfg.startPc = ss->first; - computeIoCfg.stopPc = ss->second; - } - } + computeIoCfg.startPc = pcs->first; + computeIoCfg.stopPc = pcs->second; } } @@ -208,14 +185,9 @@ namespace xdp { genMsg << "interface_tile=" << bandwidthMetricSet; if (includeBandwidth && includeComputeIoBound) genMsg << ", "; - if (includeComputeIoBound) { - genMsg << "aie_tile=compute_io_bound "; - if (computeIoCfg.useStartStop) - genMsg << "start_pc=0x" << std::hex << computeIoCfg.startPc - << " stop_pc=0x" << computeIoCfg.stopPc << std::dec; - else - genMsg << "wpc=0x" << std::hex << computeIoCfg.startPc << std::dec; - } + if (includeComputeIoBound) + genMsg << "aie_tile=compute_io_bound start_pc=0x" << std::hex << computeIoCfg.startPc + << " stop_pc=0x" << computeIoCfg.stopPc << std::dec; genMsg << ")"; xrt_core::message::send(severity_level::debug, "XRT", genMsg.str()); diff --git a/profile/plugin/aie_dtrace/ve2/lst_helper.cpp b/profile/plugin/aie_dtrace/ve2/lst_helper.cpp index 5f3bf70e..ebd50f2c 100644 --- a/profile/plugin/aie_dtrace/ve2/lst_helper.cpp +++ b/profile/plugin/aie_dtrace/ve2/lst_helper.cpp @@ -10,12 +10,12 @@ #include #include #include +#include #include #include -#include +#include +#include #include -#include -#include #include namespace xdp { @@ -28,6 +28,10 @@ namespace { // End of 16KB program memory; PC_Address is bits [13:0]. constexpr uint32_t PROG_MEM_END = 0x3FFF; + // stop_pc is this many listed instructions from the dispatch, counting the + // dispatch itself as #1. + constexpr int STOP_PC_INSTR_COUNT = 10; + bool endsWith(const std::string& s, const std::string& suffix) { return s.size() >= suffix.size() @@ -40,73 +44,104 @@ namespace { return (i == std::string::npos) ? std::string() : s.substr(i); } - // Recursively search the current working directory for the first regular file - // whose path ends with 'suffix' (using forward-slash form). Empty on failure. - fs::path findBySuffix(const std::string& suffix) + // Depth limit for the design-directory search, relative to the search root. A + // design listing sits ~8 levels down (/vaiml_par_*//aiecompiler/Work/ + // aie//Release/.lst), so this is generous while bounding the walk. + constexpr int MAX_SEARCH_DEPTH = 12; + + // Root for the listing search: the XRT_AIE_DTRACE_DESIGN_DIR override when set, + // otherwise the current working directory (the run dir). + fs::path searchRoot() { + if (const char* env = std::getenv("XRT_AIE_DTRACE_DESIGN_DIR")) { + if (env[0] != '\0') { + std::error_code eec; + fs::path p(env); + if (fs::is_directory(p, eec)) + return p; + xrt_core::message::send(severity_level::warning, "XRT", + std::string("AIE dtrace: XRT_AIE_DTRACE_DESIGN_DIR='") + env + + "' is not a directory; falling back to the run directory."); + } + } std::error_code ec; - fs::path root = fs::current_path(ec); - if (ec) + fs::path cwd = fs::current_path(ec); + return ec ? fs::path{} : cwd; + } + + // Search only for the two listing filenames we need: + // reloadable design: "_reloadable*.lst" + // static design: ".lst" + // Reloadable listings win when both are present (in a reloadable design the tile's + // own .lst holds no kernel dispatch). + // + // The walk is kept cheap by pruning: it is depth-bounded, skips hidden directories, + // and inside a design "aie" directory it only descends into the tile directories of + // interest ( and _reloadable*), never the hundreds of other + // per-tile directories. Only the first file per unique filename is kept, so a run + // directory holding several copies of the same design Work tree yields no duplicates. + std::vector collectTileListings(const std::string& tileBase, bool& reloadable) + { + const std::string staticName = tileBase + ".lst"; + const std::string reloadablePrefix = tileBase + "_reloadable"; + + const fs::path root = searchRoot(); + if (root.empty()) return {}; + std::vector reloadableLst; + std::vector staticLst; + std::vector seen; + + std::error_code ec; fs::recursive_directory_iterator it(root, fs::directory_options::skip_permission_denied, ec), end; for (; !ec && it != end; it.increment(ec)) { - std::error_code fec; - if (!it->is_regular_file(fec)) - continue; - if (endsWith(it->path().generic_string(), suffix)) - return it->path(); - } - return {}; - } + const std::string name = it->path().filename().string(); - // One disassembly instruction (or VLIW bundle) line: leading address + the - // per-op mnemonic list (bytes stripped). - struct InstrLine { - uint32_t addr = 0; - std::vector ops; // trimmed op strings split on ';' - }; - - // Parse a disassembly line ": \t;\t..." into its - // leading address and the list of trimmed op strings (VLIW bundle split on ';'). - // The assembly portion starts at the first tab (after the byte columns). - bool parseInstrLine(const std::string& line, InstrLine& out) - { - size_t colon = line.find(':'); - if (colon == std::string::npos) - return false; - // Address is the leading whitespace-prefixed hex token before ':'. - std::string addrTok = trimLeft(line.substr(0, colon)); - if (addrTok.empty()) - return false; - for (char c : addrTok) { - if (!std::isxdigit(static_cast(c))) - return false; - } - size_t tab = line.find('\t', colon); - if (tab == std::string::npos) - return false; // no assembly (e.g. "...:" continuation lines) + std::error_code dec; + if (it->is_directory(dec)) { + if (it.depth() >= MAX_SEARCH_DEPTH || (!name.empty() && name[0] == '.')) { + it.disable_recursion_pending(); + continue; + } + // Within a design "aie" directory, only the tile(s) we care about are worth + // descending into. + if (it->path().parent_path().filename() == "aie" + && name != tileBase + && name.compare(0, reloadablePrefix.size(), reloadablePrefix) != 0) + it.disable_recursion_pending(); + continue; + } - try { - out.addr = static_cast(std::stoul(addrTok, nullptr, 16)); - } - catch (...) { - return false; + if (!endsWith(name, ".lst")) + continue; + const bool isReloadable = name.compare(0, reloadablePrefix.size(), reloadablePrefix) == 0; + if (!isReloadable && name != staticName) + continue; + if (std::find(seen.begin(), seen.end(), name) != seen.end()) + continue; + seen.push_back(name); + (isReloadable ? reloadableLst : staticLst).push_back(it->path()); } - out.ops.clear(); - std::string asmText = line.substr(tab + 1); - std::stringstream ss(asmText); - std::string op; - while (std::getline(ss, op, ';')) { - std::string t = trimLeft(op); - if (!t.empty()) - out.ops.push_back(t); + if (!reloadableLst.empty()) { + reloadable = true; + std::sort(reloadableLst.begin(), reloadableLst.end()); + return reloadableLst; } - return !out.ops.empty(); + reloadable = false; + std::sort(staticLst.begin(), staticLst.end()); + return staticLst; } + // One entry of the listing: either a label or an instruction line. + struct ListingEntry { + uint32_t addr = 0; + bool isLabel = false; + bool isIndirectDispatch = false; // instruction is "jl pN" + }; + // Mnemonic = first whitespace/tab-delimited token of an op. std::string mnemonic(const std::string& op) { @@ -114,201 +149,210 @@ namespace { return (i == std::string::npos) ? op : op.substr(0, i); } - // Extract "#0x" immediate target from an op; std::nullopt if none. - std::optional immTarget(const std::string& op) - { - size_t h = op.find("#0x"); - if (h == std::string::npos) - return std::nullopt; - size_t start = h + 3; - size_t i = start; - while (i < op.size() && std::isxdigit(static_cast(op[i]))) - ++i; - if (i == start) - return std::nullopt; - try { - return static_cast(std::stoul(op.substr(start, i - start), nullptr, 16)); - } - catch (...) { - return std::nullopt; - } - } - + // "jl pN" is the indirect kernel dispatch; "jl #0x..." is a direct call. bool opIsIndirectJl(const std::string& op) { if (mnemonic(op) != "jl") return false; - // Operand is a register (e.g. "p1"); direct calls use "#0x...". std::string rest = trimLeft(op.substr(2)); return !rest.empty() && rest[0] == 'p'; } - bool opIsBranchWithTarget(const std::string& op, uint32_t& target) + // Parse a label line "<8-hex-digit addr> :". + bool parseLabelLine(const std::string& line, uint32_t& addr) { - std::string m = mnemonic(op); - if (m != "j" && m != "jz" && m != "jnz") + if (line.size() <= 10 || !std::isxdigit(static_cast(line[0]))) + return false; + size_t lt = line.find(" <"); + if (lt == std::string::npos || !endsWith(line, ">:")) return false; - auto t = immTarget(op); - if (!t) + std::string addrTok = line.substr(0, lt); + if (addrTok.size() != 8 + || !std::all_of(addrTok.begin(), addrTok.end(), + [](char c){ return std::isxdigit(static_cast(c)); })) return false; - target = *t; + try { + addr = static_cast(std::stoul(addrTok, nullptr, 16)); + } + catch (...) { + return false; + } return true; } -} // namespace - -bool -isKernelWrapperInline(const std::string& tileBase) -{ - const std::string suffix = "aie/" + tileBase + "/src/" + tileBase + ".cc"; - fs::path srcPath = findBySuffix(suffix); - if (srcPath.empty()) { - xrt_core::message::send(severity_level::debug, "XRT", - "AIE dtrace: source '" + suffix + "' not found under run dir; cannot confirm kernelWrapper inline."); - return false; - } + // Parse a disassembly line ": \t;\t..." into its + // leading address, and report whether any op is the indirect dispatch. + // The assembly portion starts at the first tab (after the byte columns). + bool parseInstrLine(const std::string& line, uint32_t& addr, bool& indirectDispatch) + { + size_t colon = line.find(':'); + if (colon == std::string::npos) + return false; + std::string addrTok = trimLeft(line.substr(0, colon)); + if (addrTok.empty() + || !std::all_of(addrTok.begin(), addrTok.end(), + [](char c){ return std::isxdigit(static_cast(c)); })) + return false; + size_t tab = line.find('\t', colon); + if (tab == std::string::npos) + return false; // no assembly text (e.g. an elision "..." line) - std::ifstream f(srcPath); - if (!f.is_open()) - return false; - - // Scan for a kernelWrapper definition marked inline. We look at "void kernelWrapper(" - // lines only (ignores call sites like "kernelWrapper(args, ...)"); a bare/extern - // declaration without the inline attribute is skipped so it does not mask a later - // inline definition. - std::string line; - while (std::getline(f, line)) { - if (line.find("void kernelWrapper(") != std::string::npos - && (line.find("always_inline") != std::string::npos - || line.find("inline") != std::string::npos)) { - xrt_core::message::send(severity_level::debug, "XRT", - "AIE dtrace: kernelWrapper is inline in " + srcPath.generic_string()); - return true; + try { + addr = static_cast(std::stoul(addrTok, nullptr, 16)); + } + catch (...) { + return false; } - } - return false; -} -std::optional> -getStaticStartStopPcFromLst(const std::string& tileBase) -{ - const std::string suffix = "aie/" + tileBase + "/Release/" + tileBase + ".lst"; - fs::path lstPath = findBySuffix(suffix); - if (lstPath.empty()) { - xrt_core::message::send(severity_level::warning, "XRT", - "AIE dtrace: listing '" + suffix + "' not found under run dir; skipping compute_io_bound."); - return std::nullopt; + indirectDispatch = false; + std::stringstream ss(line.substr(tab + 1)); + std::string op; + while (std::getline(ss, op, ';')) { + std::string t = trimLeft(op); + if (!t.empty() && opIsIndirectJl(t)) + indirectDispatch = true; + } + return true; } - std::ifstream f(lstPath); - if (!f.is_open()) - return std::nullopt; - - // Collect function labels (addr, name) in file (address) order and all - // instruction lines (addr, ops). - std::vector> labels; - std::vector instrs; - - std::string line; - while (std::getline(f, line)) { - if (!line.empty() && line.back() == '\r') - line.pop_back(); - - // Label line: "<8-hex-digit addr> :" (a function symbol or a compiler-local ".L*" label). - if (line.size() > 10 && std::isxdigit(static_cast(line[0])) - && line.find(" <") != std::string::npos && endsWith(line, ">:")) { - size_t lt = line.find(" <"); - std::string addrTok = line.substr(0, lt); - std::string name = line.substr(lt + 2, line.size() - (lt + 2) - 2); // between "<" and ">:" - bool hexAddr = addrTok.size() == 8 - && std::all_of(addrTok.begin(), addrTok.end(), - [](char c){ return std::isxdigit(static_cast(c)); }); - if (hexAddr) { - try { - labels.emplace_back(static_cast(std::stoul(addrTok, nullptr, 16)), name); - } - catch (...) {} + // Read a listing into ordered labels/instructions. "..." elision lines carry no + // address and are skipped, so they are neither counted nor expanded. + std::vector readListing(const fs::path& path) + { + std::vector entries; + std::ifstream f(path); + if (!f.is_open()) + return entries; + + std::string line; + while (std::getline(f, line)) { + if (!line.empty() && line.back() == '\r') + line.pop_back(); + + ListingEntry e; + if (parseLabelLine(line, e.addr)) { + e.isLabel = true; + entries.push_back(e); continue; } + if (parseInstrLine(line, e.addr, e.isIndirectDispatch)) { + e.isLabel = false; + entries.push_back(e); + } } - - InstrLine il; - if (parseInstrLine(line, il)) - instrs.push_back(std::move(il)); + return entries; } - // Find main and the next FUNCTION label after it (main region = [mainAddr, mainEnd)). - // Skip compiler-local basic-block labels (names starting with ".") which live inside a function. - uint32_t mainAddr = 0; - uint32_t mainEnd = PROG_MEM_END; - bool foundMain = false; - for (size_t i = 0; i < labels.size(); ++i) { - if (labels[i].second == "main") { - mainAddr = labels[i].first; - foundMain = true; - for (size_t j = i + 1; j < labels.size(); ++j) { - if (!labels[j].second.empty() && labels[j].second[0] != '.') { - mainEnd = labels[j].first; - break; - } + // start_pc = the single indirect dispatch; stop_pc = the STOP_PC_INSTR_COUNT'th + // listed instruction from it (dispatch = #1), clamped to the last instruction + // before the next label. + std::optional> + startStopFromListing(const fs::path& path) + { + const std::vector entries = readListing(path); + + int dispatchCount = 0; + size_t dispatchIdx = 0; + for (size_t i = 0; i < entries.size(); ++i) { + if (!entries[i].isLabel && entries[i].isIndirectDispatch) { + ++dispatchCount; + dispatchIdx = i; } - break; } - } - if (!foundMain) { - xrt_core::message::send(severity_level::warning, "XRT", - "AIE dtrace: 'main' not found in " + lstPath.generic_string() + "; skipping compute_io_bound."); - return std::nullopt; - } + if (dispatchCount != 1) { + xrt_core::message::send(severity_level::warning, "XRT", + "AIE dtrace: expected exactly one indirect 'jl pN' dispatch, found " + + std::to_string(dispatchCount) + " in " + path.generic_string() + + "; skipping compute_io_bound."); + return std::nullopt; + } - // Within main: require exactly one indirect 'jl pN' dispatch. - int indirectJlCount = 0; - uint32_t jlAddr = 0; - for (const auto& il : instrs) { - if (il.addr < mainAddr || il.addr >= mainEnd) - continue; - for (const auto& op : il.ops) { - if (opIsIndirectJl(op)) { - ++indirectJlCount; - jlAddr = il.addr; + const uint32_t startPc = entries[dispatchIdx].addr; + uint32_t stopPc = startPc; + int counted = 1; // the dispatch itself is instruction #1 + for (size_t i = dispatchIdx + 1; i < entries.size() && counted < STOP_PC_INSTR_COUNT; ++i) { + if (entries[i].isLabel) { + // End of the enclosing label: stop here and use the last instruction seen. + xrt_core::message::send(severity_level::debug, "XRT", + "AIE dtrace: label boundary reached after " + std::to_string(counted) + + " instructions; clamping stop_pc in " + path.generic_string()); + break; } + stopPc = entries[i].addr; + ++counted; } - } - if (indirectJlCount != 1) { - xrt_core::message::send(severity_level::warning, "XRT", - "AIE dtrace: expected exactly one indirect 'jl pN' in main, found " - + std::to_string(indirectJlCount) + " in " + lstPath.generic_string() - + "; skipping compute_io_bound."); - return std::nullopt; + + if (startPc >= stopPc || stopPc > PROG_MEM_END) { + xrt_core::message::send(severity_level::warning, "XRT", + "AIE dtrace: invalid start/stop PCs from " + path.generic_string() + + "; skipping compute_io_bound."); + return std::nullopt; + } + return std::make_pair(startPc, stopPc); } - // First backward branch after the indirect jl: target = start_pc, addr = stop_pc. - for (const auto& il : instrs) { - if (il.addr <= jlAddr || il.addr >= mainEnd) - continue; - for (const auto& op : il.ops) { - uint32_t target = 0; - if (opIsBranchWithTarget(op, target) && target < il.addr) { - uint32_t startPc = target; - uint32_t stopPc = il.addr; - if (startPc < stopPc && stopPc <= PROG_MEM_END) { - std::stringstream msg; - msg << "AIE dtrace: compute_io_bound start/stop PCs from " << lstPath.generic_string() - << ": start_pc=0x" << std::hex << startPc << " stop_pc=0x" << stopPc; - xrt_core::message::send(severity_level::info, "XRT", msg.str()); - return std::make_pair(startPc, stopPc); - } - xrt_core::message::send(severity_level::warning, "XRT", - "AIE dtrace: invalid start/stop PC range parsed; skipping compute_io_bound."); + // Locate the tile listings and derive the compute start/stop PCs from them. + std::optional> + resolveComputeStartStopPc(const std::string& tileBase) + { + bool reloadable = false; + const std::vector listings = collectTileListings(tileBase, reloadable); + if (listings.empty()) { + xrt_core::message::send(severity_level::warning, "XRT", + "AIE dtrace: no listing found for tile '" + tileBase + + "' under the design/run directory (set XRT_AIE_DTRACE_DESIGN_DIR to point at" + " the design); skipping compute_io_bound."); + return std::nullopt; + } + + // All listings must agree on the PCs (trivially true for the single static one). + std::optional> common; + for (const auto& path : listings) { + auto pcs = startStopFromListing(path); + if (!pcs) + return std::nullopt; + if (!common) { + common = pcs; + } + else if (*common != *pcs) { + std::stringstream msg; + msg << "AIE dtrace: reloadable listings disagree on kernelWrapper PCs (" + << std::hex << "0x" << common->first << "/0x" << common->second + << " vs 0x" << pcs->first << "/0x" << pcs->second << std::dec + << ") in " << path.generic_string() << "; skipping compute_io_bound."; + xrt_core::message::send(severity_level::warning, "XRT", msg.str()); return std::nullopt; } } + + std::stringstream msg; + msg << "AIE dtrace: compute_io_bound " << (reloadable ? "reloadable" : "static") + << " design, start_pc=0x" << std::hex << common->first + << " stop_pc=0x" << common->second << std::dec + << " (from " << listings.size() << " listing(s))"; + xrt_core::message::send(severity_level::info, "XRT", msg.str()); + + return common; } - xrt_core::message::send(severity_level::warning, "XRT", - "AIE dtrace: no backward branch found after kernel dispatch in " - + lstPath.generic_string() + "; skipping compute_io_bound."); - return std::nullopt; +} // namespace + +std::optional> +getComputeStartStopPc(const std::string& tileBase) +{ + // generateCTForRun runs per kernel run, but the listings are fixed for the loaded + // design: resolve once (the directory search is the expensive part) and reuse. + static std::mutex mtx; + static std::map>> cache; + + std::lock_guard lock(mtx); + auto hit = cache.find(tileBase); + if (hit != cache.end()) + return hit->second; + + auto result = resolveComputeStartStopPc(tileBase); + cache.emplace(tileBase, result); + return result; } } // namespace xdp diff --git a/profile/plugin/aie_dtrace/ve2/lst_helper.h b/profile/plugin/aie_dtrace/ve2/lst_helper.h index bb6c3e85..2e738446 100644 --- a/profile/plugin/aie_dtrace/ve2/lst_helper.h +++ b/profile/plugin/aie_dtrace/ve2/lst_helper.h @@ -5,31 +5,32 @@ #include #include -#include #include +#include namespace xdp { -// Helpers for the compute_io_bound "static/inlined design" path. These parse the -// aiecompiler listing (.lst) and source (.cc) for a core tile, located by searching -// the current working directory (the run directory, where the design's aiecompiler -// Work tree is co-located in the Telluride flow). +// Derive the compute_io_bound compute-window start/stop PCs from the aiecompiler +// listing (.lst) of a core tile. Used for BOTH design types: +// - reloadable: listings _reloadable*.lst (kernelWrapper is a real +// function); every listing must agree on the PCs. +// - static: listing .lst (kernelWrapper is inlined into main). // -// tileBase is the RELATIVE-row listing base name (e.g. "0_0"): first core tile in -// column 0, from elfs_metadata[col0/row0].static_elfs. - -// True if kernelWrapper is an inline function in aie//src/.cc -// (definition line carries __attribute__((always_inline)) or a plain inline). -// False when the source is not found or kernelWrapper is not inline. -bool -isKernelWrapperInline(const std::string& tileBase); - -// Parse aie//Release/.lst for the inlined-kernelWrapper inner -// loop: isolate main, find the single indirect 'jl pN' dispatch, then the first -// backward branch after it. Returns {start_pc, stop_pc} = {branch target (loop -// header), branch instruction address}, or std::nullopt if not found / invalid. +// start_pc = PC of the single indirect kernel dispatch ("jl pN"; direct calls +// use "jl #0x..."). +// stop_pc = the 10th instruction listed from start_pc (the jl counts as #1), +// clamped to the last instruction of the enclosing label. +// +// tileBase is the RELATIVE-row listing base name of the first core tile ("0_0"). +// Listings are located by a depth-bounded search for the design's "aie" directory, +// starting at XRT_AIE_DTRACE_DESIGN_DIR when set, otherwise the current working +// directory (the run dir, where the design's aiecompiler Work tree is co-located). +// The result is cached, so the search runs at most once per tile per process. +// +// Returns {start_pc, stop_pc}, or std::nullopt if the listing is missing or the +// expected dispatch/instruction pattern is not found. std::optional> -getStaticStartStopPcFromLst(const std::string& tileBase); +getComputeStartStopPc(const std::string& tileBase); } // namespace xdp From 3700306104030595f34e639c3fbc30118ffb8bef Mon Sep 17 00:00:00 2001 From: Jyotheeswar Ganne Date: Wed, 29 Jul 2026 00:27:10 -0600 Subject: [PATCH 5/6] Read compute_io_bound PCs from generated PC metadata JSON Stop deriving the compute start/stop PCs at run time. The listing parsing and directory search are replaced by a single read of aie_pc_metadata.json from the run directory, generated on the host by "vaiprofile --gen-pc-metadata ". This removes the per-run filesystem walk that stalled generateCTForRun (it descended into the design's per-tile directories over NFS), deletes lst_helper.{h,cpp} entirely, and keeps the derivation rule in exactly one place so the C++ side cannot drift from it. The PCs are cached after the first read; when the JSON is missing or malformed a warning names the file and the generating command, and compute_io_bound is skipped. The counter configuration is unchanged: PC_Event0/1 hold start/stop and counter 2 uses Start=PC_0 / Stop=PC_1, counter 3 remains the total. Co-authored-by: Cursor --- .../plugin/aie_dtrace/ve2/aie_dtrace_ve2.cpp | 85 ++++- .../plugin/aie_dtrace/ve2/aie_dtrace_ve2.h | 6 + profile/plugin/aie_dtrace/ve2/lst_helper.cpp | 358 ------------------ profile/plugin/aie_dtrace/ve2/lst_helper.h | 37 -- 4 files changed, 78 insertions(+), 408 deletions(-) delete mode 100644 profile/plugin/aie_dtrace/ve2/lst_helper.cpp delete mode 100644 profile/plugin/aie_dtrace/ve2/lst_helper.h diff --git a/profile/plugin/aie_dtrace/ve2/aie_dtrace_ve2.cpp b/profile/plugin/aie_dtrace/ve2/aie_dtrace_ve2.cpp index 580fb2f5..f3c933b8 100644 --- a/profile/plugin/aie_dtrace/ve2/aie_dtrace_ve2.cpp +++ b/profile/plugin/aie_dtrace/ve2/aie_dtrace_ve2.cpp @@ -6,7 +6,6 @@ #include "xdp/profile/plugin/aie_dtrace/ve2/aie_dtrace_ve2.h" #include "xdp/profile/plugin/aie_dtrace/ve2/aie_dtrace_ct_writer.h" #include "xdp/profile/plugin/aie_dtrace/ve2/elf_helper.h" -#include "xdp/profile/plugin/aie_dtrace/ve2/lst_helper.h" #include "core/common/api/hw_context_int.h" #include "core/common/api/kernel_int.h" @@ -16,9 +15,12 @@ #include "xdp/profile/database/static_info/aie_util.h" +#include #include #include +#include #include +#include namespace xdp { using severity_level = xrt_core::message::severity_level; @@ -30,6 +32,66 @@ namespace xdp { // row. Listings/elfs_metadata use RELATIVE core rows, hence "_0". static constexpr const char* COMPUTE_IO_TILE_BASE = "0_0"; + // PC metadata produced on the host by "vaiprofile --gen-pc-metadata" and read from + // the run directory. Holds the compute_io_bound start/stop PCs so this plugin does + // not have to parse aiecompiler listings at run time. + static constexpr const char* PC_METADATA_FILENAME = "aie_pc_metadata.json"; + + // Read compute_io_bound start/stop PCs for 'tileBase' from the PC metadata JSON in + // the run directory. std::nullopt when the file is missing or lacks the tile entry. + static std::optional> + readComputeStartStopPc(const std::string& tileBase) + { + const std::string path = + (std::filesystem::current_path() / PC_METADATA_FILENAME).string(); + + std::error_code ec; + if (!std::filesystem::is_regular_file(path, ec)) { + xrt_core::message::send(severity_level::warning, "XRT", + "AIE dtrace: '" + std::string(PC_METADATA_FILENAME) + "' not found in the run " + "directory; generate it with 'vaiprofile --gen-pc-metadata ' and copy it " + "here. Skipping compute_io_bound."); + return std::nullopt; + } + + try { + boost::property_tree::ptree pt; + boost::property_tree::read_json(path, pt); + + auto tile = pt.get_child_optional("compute_io_bound." + tileBase); + if (!tile) { + xrt_core::message::send(severity_level::warning, "XRT", + "AIE dtrace: no 'compute_io_bound." + tileBase + "' entry in " + path + + "; skipping compute_io_bound."); + return std::nullopt; + } + + auto startPc = tile->get_optional("start_pc"); + auto stopPc = tile->get_optional("stop_pc"); + if (!startPc || !stopPc) { + xrt_core::message::send(severity_level::warning, "XRT", + "AIE dtrace: 'compute_io_bound." + tileBase + "' in " + path + + " is missing start_pc/stop_pc; skipping compute_io_bound."); + return std::nullopt; + } + + std::stringstream msg; + msg << "AIE dtrace: compute_io_bound PCs from " << path << " (design '" + << pt.get("design", "unknown") << "', " + << pt.get("design_type", "unknown") << "): start_pc=0x" + << std::hex << *startPc << " stop_pc=0x" << *stopPc << std::dec; + xrt_core::message::send(severity_level::info, "XRT", msg.str()); + + return std::make_pair(*startPc, *stopPc); + } + catch (const std::exception& e) { + xrt_core::message::send(severity_level::warning, "XRT", + "AIE dtrace: could not parse " + path + " (" + e.what() + + "); skipping compute_io_bound."); + return std::nullopt; + } + } + AieDtrace_VE2Impl::AieDtrace_VE2Impl(VPDatabase* database, std::shared_ptr metadata, uint64_t deviceID) @@ -125,23 +187,20 @@ namespace xdp { } } - // Resolve the compute_io_bound compute start/stop PCs from the core tile's listing. - // Same rule for static and reloadable designs: start_pc is the indirect kernel - // dispatch, stop_pc the 10th listed instruction from it. tileBase is the - // relative-row listing base name of the first core tile. + // Compute start/stop PCs come from the host-generated PC metadata JSON; resolve + // once per process since they are fixed for the loaded design. ComputeIoCoreConfig computeIoCfg; if (includeComputeIoBound) { - const std::string tileBase = COMPUTE_IO_TILE_BASE; - auto pcs = getComputeStartStopPc(tileBase); - if (!pcs) { - xrt_core::message::send(severity_level::warning, "XRT", - "AIE dtrace: compute_io_bound could not derive compute start/stop PCs " - "for tile '" + tileBase + "'; skipping core configuration."); + if (!m_computeIoPcsResolved) { + m_computeIoPcs = readComputeStartStopPc(COMPUTE_IO_TILE_BASE); + m_computeIoPcsResolved = true; + } + if (!m_computeIoPcs) { includeComputeIoBound = false; } else { - computeIoCfg.startPc = pcs->first; - computeIoCfg.stopPc = pcs->second; + computeIoCfg.startPc = m_computeIoPcs->first; + computeIoCfg.stopPc = m_computeIoPcs->second; } } diff --git a/profile/plugin/aie_dtrace/ve2/aie_dtrace_ve2.h b/profile/plugin/aie_dtrace/ve2/aie_dtrace_ve2.h index 3fa6926a..7434b84a 100644 --- a/profile/plugin/aie_dtrace/ve2/aie_dtrace_ve2.h +++ b/profile/plugin/aie_dtrace/ve2/aie_dtrace_ve2.h @@ -6,7 +6,9 @@ #include #include +#include #include +#include #include #include "aiebu/aiebu_assembler.h" @@ -37,6 +39,10 @@ namespace xdp { void computeOpLocations(void* elf_handle, const std::string& kernel_name); std::map> m_op_locations_cache; + + // compute_io_bound start/stop PCs read once from the PC metadata JSON. + std::optional> m_computeIoPcs; + bool m_computeIoPcsResolved = false; }; } diff --git a/profile/plugin/aie_dtrace/ve2/lst_helper.cpp b/profile/plugin/aie_dtrace/ve2/lst_helper.cpp deleted file mode 100644 index ebd50f2c..00000000 --- a/profile/plugin/aie_dtrace/ve2/lst_helper.cpp +++ /dev/null @@ -1,358 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright (C) 2026 Advanced Micro Devices, Inc. All rights reserved - -#define XDP_PLUGIN_SOURCE - -#include "xdp/profile/plugin/aie_dtrace/ve2/lst_helper.h" - -#include "core/common/message.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace xdp { - -namespace { - - namespace fs = std::filesystem; - using severity_level = xrt_core::message::severity_level; - - // End of 16KB program memory; PC_Address is bits [13:0]. - constexpr uint32_t PROG_MEM_END = 0x3FFF; - - // stop_pc is this many listed instructions from the dispatch, counting the - // dispatch itself as #1. - constexpr int STOP_PC_INSTR_COUNT = 10; - - bool endsWith(const std::string& s, const std::string& suffix) - { - return s.size() >= suffix.size() - && s.compare(s.size() - suffix.size(), suffix.size(), suffix) == 0; - } - - std::string trimLeft(const std::string& s) - { - size_t i = s.find_first_not_of(" \t"); - return (i == std::string::npos) ? std::string() : s.substr(i); - } - - // Depth limit for the design-directory search, relative to the search root. A - // design listing sits ~8 levels down (/vaiml_par_*//aiecompiler/Work/ - // aie//Release/.lst), so this is generous while bounding the walk. - constexpr int MAX_SEARCH_DEPTH = 12; - - // Root for the listing search: the XRT_AIE_DTRACE_DESIGN_DIR override when set, - // otherwise the current working directory (the run dir). - fs::path searchRoot() - { - if (const char* env = std::getenv("XRT_AIE_DTRACE_DESIGN_DIR")) { - if (env[0] != '\0') { - std::error_code eec; - fs::path p(env); - if (fs::is_directory(p, eec)) - return p; - xrt_core::message::send(severity_level::warning, "XRT", - std::string("AIE dtrace: XRT_AIE_DTRACE_DESIGN_DIR='") + env - + "' is not a directory; falling back to the run directory."); - } - } - std::error_code ec; - fs::path cwd = fs::current_path(ec); - return ec ? fs::path{} : cwd; - } - - // Search only for the two listing filenames we need: - // reloadable design: "_reloadable*.lst" - // static design: ".lst" - // Reloadable listings win when both are present (in a reloadable design the tile's - // own .lst holds no kernel dispatch). - // - // The walk is kept cheap by pruning: it is depth-bounded, skips hidden directories, - // and inside a design "aie" directory it only descends into the tile directories of - // interest ( and _reloadable*), never the hundreds of other - // per-tile directories. Only the first file per unique filename is kept, so a run - // directory holding several copies of the same design Work tree yields no duplicates. - std::vector collectTileListings(const std::string& tileBase, bool& reloadable) - { - const std::string staticName = tileBase + ".lst"; - const std::string reloadablePrefix = tileBase + "_reloadable"; - - const fs::path root = searchRoot(); - if (root.empty()) - return {}; - - std::vector reloadableLst; - std::vector staticLst; - std::vector seen; - - std::error_code ec; - fs::recursive_directory_iterator it(root, - fs::directory_options::skip_permission_denied, ec), end; - for (; !ec && it != end; it.increment(ec)) { - const std::string name = it->path().filename().string(); - - std::error_code dec; - if (it->is_directory(dec)) { - if (it.depth() >= MAX_SEARCH_DEPTH || (!name.empty() && name[0] == '.')) { - it.disable_recursion_pending(); - continue; - } - // Within a design "aie" directory, only the tile(s) we care about are worth - // descending into. - if (it->path().parent_path().filename() == "aie" - && name != tileBase - && name.compare(0, reloadablePrefix.size(), reloadablePrefix) != 0) - it.disable_recursion_pending(); - continue; - } - - if (!endsWith(name, ".lst")) - continue; - const bool isReloadable = name.compare(0, reloadablePrefix.size(), reloadablePrefix) == 0; - if (!isReloadable && name != staticName) - continue; - if (std::find(seen.begin(), seen.end(), name) != seen.end()) - continue; - seen.push_back(name); - (isReloadable ? reloadableLst : staticLst).push_back(it->path()); - } - - if (!reloadableLst.empty()) { - reloadable = true; - std::sort(reloadableLst.begin(), reloadableLst.end()); - return reloadableLst; - } - reloadable = false; - std::sort(staticLst.begin(), staticLst.end()); - return staticLst; - } - - // One entry of the listing: either a label or an instruction line. - struct ListingEntry { - uint32_t addr = 0; - bool isLabel = false; - bool isIndirectDispatch = false; // instruction is "jl pN" - }; - - // Mnemonic = first whitespace/tab-delimited token of an op. - std::string mnemonic(const std::string& op) - { - size_t i = op.find_first_of(" \t"); - return (i == std::string::npos) ? op : op.substr(0, i); - } - - // "jl pN" is the indirect kernel dispatch; "jl #0x..." is a direct call. - bool opIsIndirectJl(const std::string& op) - { - if (mnemonic(op) != "jl") - return false; - std::string rest = trimLeft(op.substr(2)); - return !rest.empty() && rest[0] == 'p'; - } - - // Parse a label line "<8-hex-digit addr> :". - bool parseLabelLine(const std::string& line, uint32_t& addr) - { - if (line.size() <= 10 || !std::isxdigit(static_cast(line[0]))) - return false; - size_t lt = line.find(" <"); - if (lt == std::string::npos || !endsWith(line, ">:")) - return false; - std::string addrTok = line.substr(0, lt); - if (addrTok.size() != 8 - || !std::all_of(addrTok.begin(), addrTok.end(), - [](char c){ return std::isxdigit(static_cast(c)); })) - return false; - try { - addr = static_cast(std::stoul(addrTok, nullptr, 16)); - } - catch (...) { - return false; - } - return true; - } - - // Parse a disassembly line ": \t;\t..." into its - // leading address, and report whether any op is the indirect dispatch. - // The assembly portion starts at the first tab (after the byte columns). - bool parseInstrLine(const std::string& line, uint32_t& addr, bool& indirectDispatch) - { - size_t colon = line.find(':'); - if (colon == std::string::npos) - return false; - std::string addrTok = trimLeft(line.substr(0, colon)); - if (addrTok.empty() - || !std::all_of(addrTok.begin(), addrTok.end(), - [](char c){ return std::isxdigit(static_cast(c)); })) - return false; - size_t tab = line.find('\t', colon); - if (tab == std::string::npos) - return false; // no assembly text (e.g. an elision "..." line) - - try { - addr = static_cast(std::stoul(addrTok, nullptr, 16)); - } - catch (...) { - return false; - } - - indirectDispatch = false; - std::stringstream ss(line.substr(tab + 1)); - std::string op; - while (std::getline(ss, op, ';')) { - std::string t = trimLeft(op); - if (!t.empty() && opIsIndirectJl(t)) - indirectDispatch = true; - } - return true; - } - - // Read a listing into ordered labels/instructions. "..." elision lines carry no - // address and are skipped, so they are neither counted nor expanded. - std::vector readListing(const fs::path& path) - { - std::vector entries; - std::ifstream f(path); - if (!f.is_open()) - return entries; - - std::string line; - while (std::getline(f, line)) { - if (!line.empty() && line.back() == '\r') - line.pop_back(); - - ListingEntry e; - if (parseLabelLine(line, e.addr)) { - e.isLabel = true; - entries.push_back(e); - continue; - } - if (parseInstrLine(line, e.addr, e.isIndirectDispatch)) { - e.isLabel = false; - entries.push_back(e); - } - } - return entries; - } - - // start_pc = the single indirect dispatch; stop_pc = the STOP_PC_INSTR_COUNT'th - // listed instruction from it (dispatch = #1), clamped to the last instruction - // before the next label. - std::optional> - startStopFromListing(const fs::path& path) - { - const std::vector entries = readListing(path); - - int dispatchCount = 0; - size_t dispatchIdx = 0; - for (size_t i = 0; i < entries.size(); ++i) { - if (!entries[i].isLabel && entries[i].isIndirectDispatch) { - ++dispatchCount; - dispatchIdx = i; - } - } - if (dispatchCount != 1) { - xrt_core::message::send(severity_level::warning, "XRT", - "AIE dtrace: expected exactly one indirect 'jl pN' dispatch, found " - + std::to_string(dispatchCount) + " in " + path.generic_string() - + "; skipping compute_io_bound."); - return std::nullopt; - } - - const uint32_t startPc = entries[dispatchIdx].addr; - uint32_t stopPc = startPc; - int counted = 1; // the dispatch itself is instruction #1 - for (size_t i = dispatchIdx + 1; i < entries.size() && counted < STOP_PC_INSTR_COUNT; ++i) { - if (entries[i].isLabel) { - // End of the enclosing label: stop here and use the last instruction seen. - xrt_core::message::send(severity_level::debug, "XRT", - "AIE dtrace: label boundary reached after " + std::to_string(counted) - + " instructions; clamping stop_pc in " + path.generic_string()); - break; - } - stopPc = entries[i].addr; - ++counted; - } - - if (startPc >= stopPc || stopPc > PROG_MEM_END) { - xrt_core::message::send(severity_level::warning, "XRT", - "AIE dtrace: invalid start/stop PCs from " + path.generic_string() - + "; skipping compute_io_bound."); - return std::nullopt; - } - return std::make_pair(startPc, stopPc); - } - - // Locate the tile listings and derive the compute start/stop PCs from them. - std::optional> - resolveComputeStartStopPc(const std::string& tileBase) - { - bool reloadable = false; - const std::vector listings = collectTileListings(tileBase, reloadable); - if (listings.empty()) { - xrt_core::message::send(severity_level::warning, "XRT", - "AIE dtrace: no listing found for tile '" + tileBase - + "' under the design/run directory (set XRT_AIE_DTRACE_DESIGN_DIR to point at" - " the design); skipping compute_io_bound."); - return std::nullopt; - } - - // All listings must agree on the PCs (trivially true for the single static one). - std::optional> common; - for (const auto& path : listings) { - auto pcs = startStopFromListing(path); - if (!pcs) - return std::nullopt; - if (!common) { - common = pcs; - } - else if (*common != *pcs) { - std::stringstream msg; - msg << "AIE dtrace: reloadable listings disagree on kernelWrapper PCs (" - << std::hex << "0x" << common->first << "/0x" << common->second - << " vs 0x" << pcs->first << "/0x" << pcs->second << std::dec - << ") in " << path.generic_string() << "; skipping compute_io_bound."; - xrt_core::message::send(severity_level::warning, "XRT", msg.str()); - return std::nullopt; - } - } - - std::stringstream msg; - msg << "AIE dtrace: compute_io_bound " << (reloadable ? "reloadable" : "static") - << " design, start_pc=0x" << std::hex << common->first - << " stop_pc=0x" << common->second << std::dec - << " (from " << listings.size() << " listing(s))"; - xrt_core::message::send(severity_level::info, "XRT", msg.str()); - - return common; - } - -} // namespace - -std::optional> -getComputeStartStopPc(const std::string& tileBase) -{ - // generateCTForRun runs per kernel run, but the listings are fixed for the loaded - // design: resolve once (the directory search is the expensive part) and reuse. - static std::mutex mtx; - static std::map>> cache; - - std::lock_guard lock(mtx); - auto hit = cache.find(tileBase); - if (hit != cache.end()) - return hit->second; - - auto result = resolveComputeStartStopPc(tileBase); - cache.emplace(tileBase, result); - return result; -} - -} // namespace xdp diff --git a/profile/plugin/aie_dtrace/ve2/lst_helper.h b/profile/plugin/aie_dtrace/ve2/lst_helper.h deleted file mode 100644 index 2e738446..00000000 --- a/profile/plugin/aie_dtrace/ve2/lst_helper.h +++ /dev/null @@ -1,37 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright (C) 2026 Advanced Micro Devices, Inc. All rights reserved -#ifndef XDP_AIE_DTRACE_VE2_LST_HELPER_H -#define XDP_AIE_DTRACE_VE2_LST_HELPER_H - -#include -#include -#include -#include - -namespace xdp { - -// Derive the compute_io_bound compute-window start/stop PCs from the aiecompiler -// listing (.lst) of a core tile. Used for BOTH design types: -// - reloadable: listings _reloadable*.lst (kernelWrapper is a real -// function); every listing must agree on the PCs. -// - static: listing .lst (kernelWrapper is inlined into main). -// -// start_pc = PC of the single indirect kernel dispatch ("jl pN"; direct calls -// use "jl #0x..."). -// stop_pc = the 10th instruction listed from start_pc (the jl counts as #1), -// clamped to the last instruction of the enclosing label. -// -// tileBase is the RELATIVE-row listing base name of the first core tile ("0_0"). -// Listings are located by a depth-bounded search for the design's "aie" directory, -// starting at XRT_AIE_DTRACE_DESIGN_DIR when set, otherwise the current working -// directory (the run dir, where the design's aiecompiler Work tree is co-located). -// The result is cached, so the search runs at most once per tile per process. -// -// Returns {start_pc, stop_pc}, or std::nullopt if the listing is missing or the -// expected dispatch/instruction pattern is not found. -std::optional> -getComputeStartStopPc(const std::string& tileBase); - -} // namespace xdp - -#endif From 34a77127a3c8d1640779d8aa4ce6d06780c590d7 Mon Sep 17 00:00:00 2001 From: Jyotheeswar Ganne Date: Wed, 29 Jul 2026 00:48:13 -0600 Subject: [PATCH 6/6] Parse the compute_io_bound PCs as hex strings The generator now writes start_pc/stop_pc as "0x"-prefixed hex strings, so read them as strings and convert with base 16 instead of asking ptree for a uint32_t. Reject a value with trailing junk rather than silently using the truncated prefix, and name the expected spelling in the warning. Co-authored-by: Cursor --- .../plugin/aie_dtrace/ve2/aie_dtrace_ve2.cpp | 23 ++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/profile/plugin/aie_dtrace/ve2/aie_dtrace_ve2.cpp b/profile/plugin/aie_dtrace/ve2/aie_dtrace_ve2.cpp index f3c933b8..0a36254e 100644 --- a/profile/plugin/aie_dtrace/ve2/aie_dtrace_ve2.cpp +++ b/profile/plugin/aie_dtrace/ve2/aie_dtrace_ve2.cpp @@ -66,12 +66,29 @@ namespace xdp { return std::nullopt; } - auto startPc = tile->get_optional("start_pc"); - auto stopPc = tile->get_optional("stop_pc"); + // The PCs are "0x"-prefixed hex strings, JSON having no hex number literal. + auto parsePc = [](const boost::optional& text) -> std::optional { + if (!text) + return std::nullopt; + try { + size_t end = 0; + const unsigned long value = std::stoul(*text, &end, 16); + if (end != text->size()) // trailing junk + return std::nullopt; + return static_cast(value); + } + catch (const std::exception&) { + return std::nullopt; + } + }; + + auto startPc = parsePc(tile->get_optional("start_pc")); + auto stopPc = parsePc(tile->get_optional("stop_pc")); if (!startPc || !stopPc) { xrt_core::message::send(severity_level::warning, "XRT", "AIE dtrace: 'compute_io_bound." + tileBase + "' in " + path - + " is missing start_pc/stop_pc; skipping compute_io_bound."); + + " has missing or malformed start_pc/stop_pc (expected hex strings such as " + "\"0x337e\"); skipping compute_io_bound."); return std::nullopt; }