diff --git a/include/aie/Dialect/AIE/IR/AIEAttrs.td b/include/aie/Dialect/AIE/IR/AIEAttrs.td index b41f272a46f..abe0185da32 100644 --- a/include/aie/Dialect/AIE/IR/AIEAttrs.td +++ b/include/aie/Dialect/AIE/IR/AIEAttrs.td @@ -12,6 +12,7 @@ #define AIE_ATTRS include "aie/Dialect/AIE/IR/AIE.td" +include "aie/Dialect/AIE/IR/AIETraceAttrs.td" include "mlir/IR/AttrTypeBase.td" include "mlir/IR/EnumAttr.td" @@ -221,7 +222,7 @@ def InitValuesArrayAttr // Generated AIE event enumerations //===----------------------------------------------------------------------===// -include "AIEEvents.td.inc" +include "AIEEventsAIE.td.inc" include "AIEEventsAIE2.td.inc" include "AIEEventsAIE2P.td.inc" diff --git a/include/aie/Dialect/AIE/IR/AIEDialect.h b/include/aie/Dialect/AIE/IR/AIEDialect.h index 0a980527cde..348b7b61f2f 100644 --- a/include/aie/Dialect/AIE/IR/AIEDialect.h +++ b/include/aie/Dialect/AIE/IR/AIEDialect.h @@ -201,6 +201,11 @@ void printObjectFifoConsumerTiles(mlir::OpAsmPrinter &printer, int32_t getBufferBaseAddress(mlir::Operation *bufOp); +// Trace Event Value Parsing/Printing (handles both string and typed enums) +mlir::ParseResult parseTraceEvent(mlir::AsmParser &parser, + mlir::Attribute &result); +void printTraceEventEnum(mlir::AsmPrinter &printer, mlir::Attribute attr); + } // namespace xilinx::AIE // include TableGen generated Op definitions diff --git a/include/aie/Dialect/AIE/IR/AIEOps.td b/include/aie/Dialect/AIE/IR/AIEOps.td index 2424fcfad82..c1240d9b0e1 100644 --- a/include/aie/Dialect/AIE/IR/AIEOps.td +++ b/include/aie/Dialect/AIE/IR/AIEOps.td @@ -27,6 +27,8 @@ include "mlir/Interfaces/SideEffectInterfaces.td" class AIE_Op traits = []> : Op; +include "aie/Dialect/AIE/IR/AIETraceOps.td" + def AIE_DeviceOp: AIE_Op<"device", [ Symbol, diff --git a/include/aie/Dialect/AIE/IR/AIETargetModel.h b/include/aie/Dialect/AIE/IR/AIETargetModel.h index 0e4a8bf2c21..c334cb2f643 100644 --- a/include/aie/Dialect/AIE/IR/AIETargetModel.h +++ b/include/aie/Dialect/AIE/IR/AIETargetModel.h @@ -279,6 +279,13 @@ class AIETargetModel { /// Return the number of buffer descriptors for a given tile type. virtual uint32_t getNumBDs(AIETileType tileType) const = 0; + /// Get stream switch port index for a given port specification + /// Return port index for Stream_Switch_Event_Port_Selection register, or + /// nullopt if invalid + virtual std::optional + getStreamSwitchPortIndex(int col, int row, WireBundle bundle, + uint32_t channel, bool master) const = 0; + /// Return the number of buffer descriptors supported by the DMA in the given /// tile. uint32_t getNumBDs(int col, int row) const { @@ -383,6 +390,15 @@ class AIETargetModel { /// Encode a field value with proper bit shifting. /// Return Value shifted to correct bit position uint32_t encodeFieldValue(const BitFieldInfo &field, uint32_t value) const; + + /// Compute a 32-bit mask for a register field. + /// Return nullopt if the field does not fit in a 32-bit register. + std::optional getFieldMask(const BitFieldInfo &field) const; + + /// Resolve stream switch port specification to port index. + /// Return Port index for stream switch register, or nullopt if invalid + std::optional resolvePortValue(llvm::StringRef value, TileID tile, + bool master) const; }; class AIE1TargetModel : public AIETargetModel { @@ -463,6 +479,11 @@ class AIE1TargetModel : public AIETargetModel { int srcChan, WireBundle dstBundle, int dstChan) const override; + std::optional getStreamSwitchPortIndex(int col, int row, + WireBundle bundle, + uint32_t channel, + bool master) const override; + uint32_t getColumnShift() const override { return 23; } uint32_t getRowShift() const override { return 18; } @@ -573,6 +594,11 @@ class AIE2TargetModel : public AIETargetModel { int srcChan, WireBundle dstBundle, int dstChan) const override; + std::optional getStreamSwitchPortIndex(int col, int row, + WireBundle bundle, + uint32_t channel, + bool master) const override; + uint32_t getColumnShift() const override { return 25; } uint32_t getRowShift() const override { return 20; } diff --git a/include/aie/Dialect/AIE/IR/AIETraceAttrs.td b/include/aie/Dialect/AIE/IR/AIETraceAttrs.td new file mode 100644 index 00000000000..5eade9e7f28 --- /dev/null +++ b/include/aie/Dialect/AIE/IR/AIETraceAttrs.td @@ -0,0 +1,162 @@ +//===- AIETraceAttrs.td ------------------------------------*- tablegen -*-===// +// +// This file is licensed under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +// Copyright (C) 2025, Advanced Micro Devices, Inc. +// +//===----------------------------------------------------------------------===// +// Defines attributes for AIE trace operations +//===----------------------------------------------------------------------===// + +#ifndef AIE_TRACE_ATTRS +#define AIE_TRACE_ATTRS + +include "aie/Dialect/AIE/IR/AIE.td" +include "mlir/IR/AttrTypeBase.td" +include "mlir/IR/EnumAttr.td" + +//===----------------------------------------------------------------------===// +// Trace Mode Enumeration +//===----------------------------------------------------------------------===// + +def TraceModeEventTime : I32EnumAttrCase<"EventTime", 0, "Event-Time">; +def TraceModeEventPC : I32EnumAttrCase<"EventPC", 1, "Event-PC">; +def TraceModeExecution : I32EnumAttrCase<"Execution", 2, "Execution">; + +def TraceModeAttr : I32EnumAttr<"TraceMode", + "Trace capture mode", + [ + TraceModeEventTime, + TraceModeEventPC, + TraceModeExecution + ]> { + let cppNamespace = "::xilinx::AIE"; + let description = [{ + Specifies the trace mode: + - Event-Time (00): Captures event occurrence with timestamp + - Event-PC (01): Captures program counter when event occurs + - Execution (10): Instruction-level execution trace + }]; +} + +//===----------------------------------------------------------------------===// +// Packet Type Enumeration +//===----------------------------------------------------------------------===// + +def TracePacketTypeCore : I32EnumAttrCase<"Core", 0, "core">; +def TracePacketTypeMem : I32EnumAttrCase<"Mem", 1, "mem">; +def TracePacketTypeShimTile : I32EnumAttrCase<"ShimTile", 2, "shimtile">; +def TracePacketTypeMemTile : I32EnumAttrCase<"MemTile", 3, "memtile">; + +def TracePacketTypeAttr : I32EnumAttr<"TracePacketType", + "Packet type identifier for parsing", + [ + TracePacketTypeCore, + TracePacketTypeMem, + TracePacketTypeShimTile, + TracePacketTypeMemTile + ]> { + let cppNamespace = "::xilinx::AIE"; + let description = [{ + Packet Type Convention: + - 0: Core tile (CORE_MODULE) + - 1: Core tile (MEMORY_MODULE) + - 2: Shim tile + - 3: Mem tile + }]; +} + +//===----------------------------------------------------------------------===// +// Trace Event Attribute +//===----------------------------------------------------------------------===// + +def TraceEventAttr : AttrDef { + let mnemonic = "trace_event"; + let summary = "Reference to a trace event by name or enum"; + + let description = [{ + References an event by name string or typed enum. Strings are used for + generic references that are resolved during lowering. Enums provide + compile-time type checking. + + Validated against: + - Tile type (core events only valid for core tiles, etc.) + - Architecture (AIE/AIE2/AIE2P/AIE4) + + Examples: + "INSTR_EVENT_0" // String (resolved during lowering) + CoreEventAIE2::INSTR_EVENT_0 // Enum + }]; + + let parameters = (ins + "Attribute":$value + ); + + let assemblyFormat = "`<` custom($value) `>`"; + + let extraClassDeclaration = [{ + // Helper to get event name as string for lookups + std::string getEventName() const; + + // Check if this is a string attribute (needs promotion) + bool isStringAttr() const; + + // Get the underlying enum value if this is an enum + std::optional getEnumValue() const; + }]; +} + +//===----------------------------------------------------------------------===// +// Combo Event Logic Enumeration +//===----------------------------------------------------------------------===// + +def ComboLogicAND : I32EnumAttrCase<"AND", 0, "AND">; +def ComboLogicAND_NOT : I32EnumAttrCase<"AND_NOT", 1, "AND_NOT">; +def ComboLogicOR : I32EnumAttrCase<"OR", 2, "OR">; +def ComboLogicOR_NOT : I32EnumAttrCase<"OR_NOT", 3, "OR_NOT">; + +def ComboLogicAttr : I32EnumAttr<"ComboLogic", + "Combo event logic function", + [ + ComboLogicAND, + ComboLogicAND_NOT, + ComboLogicOR, + ComboLogicOR_NOT + ]> { + let cppNamespace = "::xilinx::AIE"; + let description = [{ + Logical operation for combining two events: + - AND (00): eventA AND eventB + - AND_NOT (01): eventA AND NOT eventB + - OR (10): eventA OR eventB + - OR_NOT (11): eventA OR NOT eventB + }]; +} + +//===----------------------------------------------------------------------===// +// Edge Detection Trigger Mode Enumeration +//===----------------------------------------------------------------------===// + +def EdgeTriggerRISING : I32EnumAttrCase<"RISING", 1, "RISING">; +def EdgeTriggerFALLING : I32EnumAttrCase<"FALLING", 2, "FALLING">; +def EdgeTriggerBOTH : I32EnumAttrCase<"BOTH", 3, "BOTH">; + +def EdgeTriggerAttr : I32EnumAttr<"EdgeTrigger", + "Edge detection trigger mode", + [ + EdgeTriggerRISING, + EdgeTriggerFALLING, + EdgeTriggerBOTH + ]> { + let cppNamespace = "::xilinx::AIE"; + let description = [{ + Edge detection trigger mode: + - RISING (01): Trigger on rising edge (0→1 transition) + - FALLING (10): Trigger on falling edge (1→0 transition) + - BOTH (11): Trigger on both edges + }]; +} + +#endif // AIE_TRACE_ATTRS diff --git a/include/aie/Dialect/AIE/IR/AIETraceOps.td b/include/aie/Dialect/AIE/IR/AIETraceOps.td new file mode 100644 index 00000000000..6ca2ecce33e --- /dev/null +++ b/include/aie/Dialect/AIE/IR/AIETraceOps.td @@ -0,0 +1,498 @@ +//===- AIETraceOps.td --------------------------------------*- tablegen -*-===// +// +// This file is licensed under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +// Copyright (C) 2025, Advanced Micro Devices, Inc. +// +//===----------------------------------------------------------------------===// +// Defines operations for AIE trace configuration +//===----------------------------------------------------------------------===// + +#ifndef AIE_TRACE_OPS +#define AIE_TRACE_OPS + +include "aie/Dialect/AIE/IR/AIE.td" +include "aie/Dialect/AIE/IR/AIETraceAttrs.td" +include "mlir/IR/SymbolInterfaces.td" +include "mlir/Interfaces/SideEffectInterfaces.td" + +//===----------------------------------------------------------------------===// +// AIE_TraceOp - Top-Level Trace Configuration (Symbol) +//===----------------------------------------------------------------------===// + +def AIE_TraceOp : AIE_Op<"trace", [ + Symbol, + HasParent<"DeviceOp">, + SingleBlockImplicitTerminator<"EndOp">, + DeclareOpInterfaceMethods + ]> { + let summary = "Declare a named trace configuration for a tile"; + + let description = [{ + Declares a named trace configuration that can be invoked from a runtime + sequence. The trace operation is a symbol that defines what events to + monitor and how to configure the trace hardware, but does not actually + apply the configuration. Configuration is applied via `aie.trace.start_config` + within a `aie.runtime_sequence` block. + + Example: + ```mlir + %tile02 = aie.tile(0, 2) + + aie.trace @trace_tile02(%tile02) { + aie.trace.mode "Event-Time" + aie.trace.packet id=1 type=core + aie.trace.event<"INSTR_EVENT_0"> + aie.trace.event<"INSTR_VECTOR"> + aie.trace.start broadcast=15 + aie.trace.stop broadcast=14 + } + ``` + }]; + + let arguments = (ins + Index:$tile, + SymbolNameAttr:$sym_name, + OptionalAttr:$buffer_size + ); + + let regions = (region SizedRegion<1>:$body); + + let assemblyFormat = [{ + $sym_name `(` $tile `)` (`buffer_size` `=` $buffer_size^)? + $body attr-dict + }]; + + let hasVerifier = 1; +} + +//===----------------------------------------------------------------------===// +// AIE_TraceModeOp - Trace Mode Selection +//===----------------------------------------------------------------------===// + +def AIE_TraceModeOp : AIE_Op<"trace.mode", [ + HasParent<"TraceOp"> + ]> { + let summary = "Set trace mode (Event-Time, Event-PC, or Execution)"; + + let description = [{ + Specifies the trace mode. Default: "Event-Time" + + Example: + ```mlir + aie.trace.mode "Event-Time" + ``` + }]; + + let arguments = (ins TraceModeAttr:$mode); + + let assemblyFormat = [{ + $mode attr-dict + }]; +} + +//===----------------------------------------------------------------------===// +// AIE_TraceEventOp - Event Slot Configuration +//===----------------------------------------------------------------------===// + +def AIE_TraceEventOp : AIE_Op<"trace.event", [ + HasParent<"TraceOp"> + ]> { + let summary = "Monitor a specific event in this trace unit"; + + let description = [{ + Adds an event to be monitored by the trace unit. Each tile type + (core/mem/memtile/shimtile) has architecture-specific events. + + Events can be specified as strings or enum values. + + Maximum 8 events per trace unit (verified at compile time). + + Example: + ```mlir + aie.trace.event<"INSTR_EVENT_0"> // String + aie.trace.event // Enum value + ``` + }]; + + let arguments = (ins + TraceEventAttr:$event, + OptionalAttr:$label + ); + + let assemblyFormat = [{ + $event (`label` `=` $label^)? attr-dict + }]; + + let hasVerifier = 1; +} + +//===----------------------------------------------------------------------===// +// AIE_TracePacketOp - Packet Routing Configuration +//===----------------------------------------------------------------------===// + +def AIE_TracePacketOp : AIE_Op<"trace.packet", [ + HasParent<"TraceOp"> + ]> { + let summary = "Enable packet-switched trace routing"; + + let description = [{ + Enables packet routing for trace data. Assigns packet ID (1-31) and + packet type to differentiate tile sources during parsing. + + Example: + ```mlir + aie.trace.packet id=1 type=core + ``` + }]; + + let arguments = (ins + ConfinedAttr, IntMaxValue<31>]>:$id, + TracePacketTypeAttr:$type + ); + + let assemblyFormat = [{ + `id` `=` $id `type` `=` $type attr-dict + }]; + + let hasVerifier = 1; +} + +//===----------------------------------------------------------------------===// +// AIE_TracePortOp - Stream Switch Port Event Configuration +//===----------------------------------------------------------------------===// + +def AIE_TracePortOp : AIE_Op<"trace.port", [ + HasParent<"TraceOp"> + ]> { + let summary = "Configure stream switch port for event monitoring"; + + let description = [{ + Configures a stream switch port monitoring slot (0-7) to track events + from a specific port. The port is specified by type (NORTH, DMA, etc.), + channel number, and direction (S2MM=master, MM2S=slave). + + Direction semantics: + - S2MM (Stream-to-Memory-Mapped) = master port (receiving from stream) + - MM2S (Memory-Mapped-to-Stream) = slave port (sending to stream) + + After configuration, the port can be monitored using: + - PORT_RUNNING_ + - PORT_IDLE_ + - PORT_STALLED_ + - PORT_TLAST_ + + Example: + ```mlir + aie.trace.port<0> port = North channel = 1 direction = S2MM + aie.trace.port<1> port = DMA channel = 0 direction = MM2S + aie.trace.event<"PORT_RUNNING_0"> // Monitor NORTH:1 (S2MM) + aie.trace.event<"PORT_IDLE_1"> // Monitor DMA:0 (MM2S) + ``` + }]; + + let arguments = (ins + ConfinedAttr, IntMaxValue<7>]>:$slot, + WireBundle:$port, + AIEI32Attr:$channel, + DMAChannelDir:$direction + ); + + let assemblyFormat = [{ + `<` $slot `>` `port` `=` $port `channel` `=` $channel `direction` `=` $direction + attr-dict + }]; + + let hasVerifier = 1; +} + +//===----------------------------------------------------------------------===// +// AIE_TraceStartEventOp - Trace Start Event Configuration +//===----------------------------------------------------------------------===// + +def AIE_TraceStartEventOp : AIE_Op<"trace.start", [ + HasParent<"TraceOp"> + ]> { + let summary = "Configure trace start event"; + + let description = [{ + Specifies the event that triggers trace capture to begin. + For multi-tile tracing, typically uses a broadcast event (default: 15). + + Example: + ```mlir + aie.trace.start broadcast=15 + aie.trace.start event=<"TRUE"> + ``` + }]; + + let arguments = (ins + OptionalAttr:$broadcast, + OptionalAttr:$event + ); + + let hasCustomAssemblyFormat = 1; + let hasVerifier = 1; +} + +//===----------------------------------------------------------------------===// +// AIE_TraceStopEventOp - Trace Stop Event Configuration +//===----------------------------------------------------------------------===// + +def AIE_TraceStopEventOp : AIE_Op<"trace.stop", [ + HasParent<"TraceOp"> + ]> { + let summary = "Configure trace stop event"; + + let description = [{ + Specifies the event that triggers trace capture to end. + For multi-tile tracing, typically uses a broadcast event (default: 14). + + Example: + ```mlir + aie.trace.stop broadcast=14 + aie.trace.stop event=<"USER_EVENT_0"> + ``` + }]; + + let arguments = (ins + OptionalAttr:$broadcast, + OptionalAttr:$event + ); + + let hasCustomAssemblyFormat = 1; + let hasVerifier = 1; +} + +//===----------------------------------------------------------------------===// +// AIE_TraceConfigOp - Configuration Container (Intermediate, Symbol) +//===----------------------------------------------------------------------===// + +def AIE_TraceConfigOp : AIE_Op<"trace.config", [ + Symbol, + HasParent<"DeviceOp">, + SingleBlockImplicitTerminator<"EndOp"> + ]> { + let summary = "Intermediate representation of lowered trace configuration"; + + let description = [{ + Generated by AIETraceToConfigPass. Contains calculated register + addresses and values needed to configure trace hardware. + + This is an intermediate representation that makes it easier to perform + optimizations before final lowering to NPU register writes. + + Example (generated, not user-written): + ```mlir + aie.trace.config @trace_tile02_config(%tile02) { + aie.trace.reg register="Trace_Control0" field="Trace_Start_Event" value=15 + aie.trace.reg register="Trace_Control0" field="Mode" value="Event-Time" + } + ``` + }]; + + let arguments = (ins + Index:$tile, + SymbolNameAttr:$sym_name, + OptionalAttr:$packet_type + ); + + let regions = (region SizedRegion<1>:$body); + + let assemblyFormat = [{ + $sym_name `(` $tile `)` (`packet_type` `=` $packet_type^)? $body attr-dict + }]; +} + +//===----------------------------------------------------------------------===// +// AIE_TraceRegOp - Logical Register/Bitfield Specification +//===----------------------------------------------------------------------===// + +def AIE_TraceRegOp : AIE_Op<"trace.reg", [ + HasParent<"TraceConfigOp"> + ]> { + let summary = "Specify a trace register field write by logical name"; + + let description = [{ + Specifies a single register field write needed to configure trace hardware. + Uses logical register names (e.g., "Trace_Control0"), field names + (e.g., "Trace_Start_Event"), and values (integers or event names). If field + is omitted, entire register is written. + + The mask field (optional) allows partial writes. When present, only the bits + set in the mask are written, and the value is assumed to already be shifted. + This enables combining multiple field writes into a single register write. + + Example: + ```mlir + aie.trace.reg register="Trace_Control0" field="Trace_Start_Event" value=15 + aie.trace.reg register="Trace_Control0" field="Mode" value=0 + aie.trace.reg register="Trace_Control0" value=0x12345678 mask=0xFF00 + ``` + }]; + + let arguments = (ins + StrAttr:$reg_name, + OptionalAttr:$field, + AnyAttr:$value, + OptionalAttr:$mask, + OptionalAttr:$comment + ); + + let assemblyFormat = [{ + `register` `=` $reg_name + (`field` `=` $field^)? + `value` `=` custom($value) + (`mask` `=` $mask^)? + (`comment` `=` $comment^)? + attr-dict + }]; +} + +//===----------------------------------------------------------------------===// +// AIE_TraceComboEventOp - Combo Event Configuration +//===----------------------------------------------------------------------===// + +def AIE_TraceComboEventOp : AIE_Op<"trace.combo_event", [ + HasParent<"TraceOp"> + ]> { + let summary = "Configure a combo event for logical event combinations"; + + let description = [{ + Configures a combo event that creates a derived event from logical + combinations of other events. Combo events are evaluated by hardware + Event Logic and produce new events (COMBO_EVENT_0/1/2/3) that can be + traced using aie.trace.event operations. + + The combo event is configured with two input events and a logic function + (AND, AND_NOT, OR, OR_NOT). AIE2 supports hierarchical combinations + through slot 2 (combo of combo0 and combo1 results). + + Events can be specified as strings or typed enums. + + Slots: + - Slot 0 (combo0): uses eventA, eventB + - Slot 1 (combo1): uses eventC, eventD + - Slot 2 (combo2): hierarchical, uses COMBO_EVENT_0 and COMBO_EVENT_1 + - Slot 3 (combo3): architecture-specific state machine (no configuration) + + Example: + ```mlir + aie.trace @my_trace(%tile02) { + // Configure Combo 0: lock stalled AND NOT DMA active + aie.trace.combo_event<0> <"LOCK_STALL"> AND_NOT <"DMA_S2MM_0_STALLED"> + + // Configure Combo 1: instruction event OR vector operation + aie.trace.combo_event<1> OR + + // Configure Combo 2: (combo0) AND (combo1) + aie.trace.combo_event<2> <"COMBO_EVENT_0"> AND <"COMBO_EVENT_1"> + + // Trace the combo results + aie.trace.event<"COMBO_EVENT_0"> + aie.trace.event<"COMBO_EVENT_1"> + aie.trace.event<"COMBO_EVENT_2"> + ... + } + ``` + }]; + + let arguments = (ins + AIEI32Attr:$slot, // 0, 1, or 2 + TraceEventAttr:$eventA, // First input event + ComboLogicAttr:$logic, // Logic function + TraceEventAttr:$eventB // Second input event + ); + + let assemblyFormat = [{ + `<` $slot `>` $eventA $logic $eventB attr-dict + }]; + // let hasCustomAssemblyFormat = 1; + + let hasVerifier = 1; +} + +//===----------------------------------------------------------------------===// +// AIE_TraceEdgeEventOp - Edge Detection Event Configuration +//===----------------------------------------------------------------------===// + +def AIE_TraceEdgeEventOp : AIE_Op<"trace.edge_event", [ + HasParent<"TraceOp"> + ]> { + let summary = "Configure an edge detection event"; + + let description = [{ + Configures an edge detection event that triggers on signal transitions + (rising edge, falling edge, or both) rather than signal levels. + + Edge detection is useful for counting event occurrences rather than + measuring duration. For example, counting the number of lock stalls + vs. the total cycles spent stalled. + + Events can be specified as strings or typed enums. + + Each module has 2 edge detectors producing EDGE_DETECTION_EVENT_0/1 + that can be traced using aie.trace.event operations. + + Example: + ```mlir + aie.trace @my_trace(%tile02) { + // Configure Edge detector 0: count lock stalls (rising edges) + aie.trace.edge_event<0> event=<"LOCK_STALL"> trigger=RISING + + // Configure Edge detector 1: count DMA transitions (both edges) + aie.trace.edge_event<1> event= trigger=BOTH + + // Trace the edge-detected events + aie.trace.event<"EDGE_DETECTION_EVENT_0"> + aie.trace.event<"EDGE_DETECTION_EVENT_1"> + ... + } + ``` + }]; + + let arguments = (ins + AIEI32Attr:$slot, // 0 or 1 + TraceEventAttr:$event, // Event to monitor + EdgeTriggerAttr:$trigger // RISING, FALLING, or BOTH + ); + + let hasCustomAssemblyFormat = 1; + + let hasVerifier = 1; +} + +//===----------------------------------------------------------------------===// +// AIE_TraceStartConfigOp - Runtime Configuration Invocation +//===----------------------------------------------------------------------===// + +def AIE_TraceStartConfigOp : AIE_Op<"trace.start_config", []> { + let summary = "Apply a trace configuration in runtime sequence"; + + let description = [{ + Invokes a named trace configuration within a runtime sequence. + During lowering, this operation is replaced by the actual register + writes (npu_write32) that implement the trace configuration. + + Example: + ```mlir + aie.runtime_sequence @seq { + aie.trace.start_config @my_trace + } + ``` + }]; + + let arguments = (ins + FlatSymbolRefAttr:$trace_config + ); + + let assemblyFormat = [{ + $trace_config attr-dict + }]; + + let hasVerifier = 1; +} + +#endif // AIE_TRACE_OPS diff --git a/include/aie/Dialect/AIE/Transforms/AIEPasses.h b/include/aie/Dialect/AIE/Transforms/AIEPasses.h index aff91edd37b..c936bb96f56 100644 --- a/include/aie/Dialect/AIE/Transforms/AIEPasses.h +++ b/include/aie/Dialect/AIE/Transforms/AIEPasses.h @@ -61,6 +61,9 @@ createAIEAssignBufferDescriptorIDsPass(); std::unique_ptr> createAIEGenerateColumnControlOverlayPass(); std::unique_ptr> createAIEAssignTileCtrlIDsPass(); +std::unique_ptr> createAIETraceToConfigPass(); +std::unique_ptr> +createAIETraceRegPackWritesPass(); /// Generate the code for registering passes. #define GEN_PASS_REGISTRATION diff --git a/include/aie/Dialect/AIE/Transforms/AIEPasses.td b/include/aie/Dialect/AIE/Transforms/AIEPasses.td index 70299a0b30c..1ee8bc3f0dd 100644 --- a/include/aie/Dialect/AIE/Transforms/AIEPasses.td +++ b/include/aie/Dialect/AIE/Transforms/AIEPasses.td @@ -346,4 +346,53 @@ def AIEGenerateColumnControlOverlay : Pass<"aie-generate-column-control-overlay" ]; } +def AIETraceToConfig : Pass<"aie-trace-to-config", "DeviceOp"> { + let summary = "Lower high-level trace ops to register configuration ops"; + let description = [{ + Converts aie.trace operations to aie.trace.config operations containing + aie.trace.reg operations that specify register field writes. + + This pass transforms declarative trace configurations into register-level + specifications that can be further lowered to NPU register writes. + + Note: This pass only does semantic lowering. NPU write generation + happens in aie-inline-trace-config (AIEX dialect). + }]; + + let constructor = "xilinx::AIE::createAIETraceToConfigPass()"; + let dependentDialects = [ + "xilinx::AIE::AIEDialect", + ]; +} + +def AIETraceRegPackWrites : Pass<"aie-trace-pack-reg-writes", "DeviceOp"> { + let summary = "Pack multiple register field writes into single register writes"; + let description = [{ + Packs multiple aie.trace.reg operations that write to the same register + into a single aie.trace.reg operation with a combined mask and value. + + This pass operates in two phases: + 1. Convert field+value pairs to mask+shifted_value using register database + 2. Merge multiple writes to the same register with non-overlapping masks + + Example transformation: + ```mlir + // Before: + aie.trace.reg register="Trace_Event0" field="Trace_Event0" value=33 + aie.trace.reg register="Trace_Event0" field="Trace_Event1" value=34 + + // After: + aie.trace.reg register="Trace_Event0" value=0x22210000 mask=0xFFFF0000 + ``` + + It is an error for two aie.trace.reg in the same aie.trace block to have + overlapping masks for the same register. + }]; + + let constructor = "xilinx::AIE::createAIETraceRegPackWritesPass()"; + let dependentDialects = [ + "xilinx::AIE::AIEDialect", + ]; +} + #endif diff --git a/include/aie/Dialect/AIEX/Transforms/AIEXPasses.h b/include/aie/Dialect/AIEX/Transforms/AIEXPasses.h index a9b6762fc08..0c50363ac8b 100644 --- a/include/aie/Dialect/AIEX/Transforms/AIEXPasses.h +++ b/include/aie/Dialect/AIEX/Transforms/AIEXPasses.h @@ -56,6 +56,8 @@ std::unique_ptr> createAIELegalizeControlPacketPass(); std::unique_ptr> createAIEExpandLoadPdiPass(); +std::unique_ptr> +createAIEXInlineTraceConfigPass(); /// Generate the code for registering passes. #define GEN_PASS_REGISTRATION diff --git a/include/aie/Dialect/AIEX/Transforms/AIEXPasses.td b/include/aie/Dialect/AIEX/Transforms/AIEXPasses.td index d4d295a6a03..30de3c62e6e 100644 --- a/include/aie/Dialect/AIEX/Transforms/AIEXPasses.td +++ b/include/aie/Dialect/AIEX/Transforms/AIEXPasses.td @@ -319,6 +319,24 @@ def AIEExpandLoadPdi : Pass<"aie-expand-load-pdi", "mlir::ModuleOp"> { let constructor = "xilinx::AIEX::createAIEExpandLoadPdiPass()"; let dependentDialects = [ "mlir::memref::MemRefDialect", + "xilinx::AIE::AIEDialect", + "xilinx::AIEX::AIEXDialect", + ]; +} + +def AIEXInlineTraceConfig : Pass<"aie-inline-trace-config", "AIE::DeviceOp"> { + let summary = "Inline trace configuration and generate NPU writes"; + let description = [{ + Replaces aie.trace.start_config operations by: + 1. Looking up the referenced trace.config symbol + 2. Resolving register/field names using RegisterDatabase + 3. Encoding bitfield values + 4. Merging multiple field writes to same register + 5. Generating aiex.npu.write32 operations with col/row preserved + }]; + + let constructor = "xilinx::AIEX::createAIEXInlineTraceConfigPass()"; + let dependentDialects = [ "xilinx::AIE::AIEDialect", "xilinx::AIEX::AIEXDialect", ]; diff --git a/lib/Dialect/AIE/IR/AIEDialect.cpp b/lib/Dialect/AIE/IR/AIEDialect.cpp index 13ad039bd28..620d68c28c8 100644 --- a/lib/Dialect/AIE/IR/AIEDialect.cpp +++ b/lib/Dialect/AIE/IR/AIEDialect.cpp @@ -2338,6 +2338,45 @@ LogicalResult UseLockOp::verify() { #include "aie/Dialect/AIE/IR/AIEEnums.cpp.inc" #include "aie/Dialect/AIE/IR/AIEInterfaces.cpp.inc" +//===----------------------------------------------------------------------===// +// TraceEventAttr +//===----------------------------------------------------------------------===// + +// Custom parser for TraceEventAttr value (uses shared helper) +static ParseResult parseTraceEventValue(AsmParser &parser, Attribute &value) { + return xilinx::AIE::parseTraceEvent(parser, value); +} + +// Custom printer for TraceEventAttr value (uses shared helper) +static void printTraceEventValue(AsmPrinter &printer, Attribute value) { + xilinx::AIE::printTraceEventEnum(printer, value); +} + +// Custom parser for TraceEventAttr value (uses shared helper) +static ParseResult parseTraceRegValue(OpAsmParser &parser, Attribute &value) { + + // Try to parse as number + int64_t intValue; + OptionalParseResult parseResult = parser.parseOptionalInteger(intValue); + if (parseResult.has_value() && succeeded(parseResult.value())) { + MLIRContext *ctx = parser.getContext(); + value = IntegerAttr::get(IntegerType::get(ctx, 32), intValue); + return success(); + } + return xilinx::AIE::parseTraceEvent(parser, value); +} + +// Custom printer for TraceEventAttr value (uses shared helper) +static void printTraceRegValue(OpAsmPrinter &printer, Operation *op, + Attribute value) { + // if it's an intattr + if (auto intAttr = llvm::dyn_cast(value)) { + printer << intAttr.getInt(); + return; + } + xilinx::AIE::printTraceEventEnum(printer, value); +} + #define GET_OP_CLASSES #include "aie/Dialect/AIE/IR/AIEOps.cpp.inc" diff --git a/lib/Dialect/AIE/IR/AIETargetModel.cpp b/lib/Dialect/AIE/IR/AIETargetModel.cpp index c7ad1406101..28d2d3c263f 100644 --- a/lib/Dialect/AIE/IR/AIETargetModel.cpp +++ b/lib/Dialect/AIE/IR/AIETargetModel.cpp @@ -31,6 +31,17 @@ std::string getModuleForTile(const AIETargetModel &model, TileID tile, return isMem ? std::string("memory") : std::string("core"); } +// Get module name for event lookups (events database uses different names) +std::string getModuleForTileEvents(const AIETargetModel &model, TileID tile, + bool isMem) { + if (model.isShimNOCorPLTile(tile.col, tile.row)) + return "pl"; // Events database uses "pl" for shim tiles + if (model.isMemTile(tile.col, tile.row)) + return "mem_tile"; // Events database uses "mem_tile" instead of + // "memory_tile" + return isMem ? std::string("memory") : std::string("core"); +} + } // namespace AIETargetModel::~AIETargetModel() = default; @@ -62,7 +73,7 @@ std::optional AIETargetModel::lookupEvent(llvm::StringRef name, const auto *db = getRegisterDatabase(); if (!db) return std::nullopt; - return db->lookupEvent(name, getModuleForTile(*this, tile, isMem)); + return db->lookupEvent(name, getModuleForTileEvents(*this, tile, isMem)); } uint32_t AIETargetModel::encodeFieldValue(const BitFieldInfo &field, @@ -73,6 +84,59 @@ uint32_t AIETargetModel::encodeFieldValue(const BitFieldInfo &field, return db->encodeFieldValue(field, value); } +std::optional +AIETargetModel::getFieldMask(const BitFieldInfo &field) const { + uint32_t width = field.getWidth(); + if (width == 0 || width > 32 || field.bit_end >= 32) + return std::nullopt; + + uint64_t mask = (width == 32) ? 0xFFFFFFFFULL : ((1ULL << width) - 1ULL); + mask <<= field.bit_start; + if (mask > UINT32_MAX) + return std::nullopt; + + return static_cast(mask); +} + +std::optional AIETargetModel::resolvePortValue(llvm::StringRef value, + TileID tile, + bool master) const { + auto colonPos = value.find(':'); + if (colonPos == StringRef::npos) + return std::nullopt; + + StringRef portName = value.substr(0, colonPos); + StringRef channelStr = value.substr(colonPos + 1); + + int channel; + if (channelStr.getAsInteger(10, channel) || channel < 0) + return std::nullopt; + + WireBundle bundle; + if (portName.equals_insensitive("north")) { + bundle = WireBundle::North; + } else if (portName.equals_insensitive("south")) { + bundle = WireBundle::South; + } else if (portName.equals_insensitive("east")) { + bundle = WireBundle::East; + } else if (portName.equals_insensitive("west")) { + bundle = WireBundle::West; + } else if (portName.equals_insensitive("dma")) { + bundle = WireBundle::DMA; + } else if (portName.equals_insensitive("fifo")) { + bundle = WireBundle::FIFO; + } else if (portName.equals_insensitive("core")) { + bundle = WireBundle::Core; + } else if (portName.equals_insensitive("ctrl")) { + bundle = WireBundle::TileControl; + } else { + return std::nullopt; + } + + return getStreamSwitchPortIndex(tile.col, tile.row, bundle, + static_cast(channel), master); +} + /// /// AIE1 TargetModel /// @@ -400,6 +464,239 @@ AIE1TargetModel::getLocalLockAddress(uint32_t lockId, TileID tile) const { return std::nullopt; } +namespace { +namespace aie1_port_id { +namespace core { + +// Slave port offset/size constants +static constexpr uint32_t S_CORE_OFFSET = 0; +static constexpr uint32_t S_CORE_SIZE = 2; +static constexpr uint32_t S_CTRL_OFFSET = 4; +static constexpr uint32_t S_CTRL_SIZE = 1; +static constexpr uint32_t S_DMA_OFFSET = 2; +static constexpr uint32_t S_DMA_SIZE = 2; +static constexpr uint32_t S_EAST_OFFSET = 21; +static constexpr uint32_t S_EAST_SIZE = 4; +static constexpr uint32_t S_FIFO_OFFSET = 5; +static constexpr uint32_t S_FIFO_SIZE = 2; +static constexpr uint32_t S_NORTH_OFFSET = 17; +static constexpr uint32_t S_NORTH_SIZE = 4; +static constexpr uint32_t S_SOUTH_OFFSET = 7; +static constexpr uint32_t S_SOUTH_SIZE = 6; +static constexpr uint32_t S_TRACE_OFFSET = 25; +static constexpr uint32_t S_TRACE_SIZE = 2; +static constexpr uint32_t S_WEST_OFFSET = 13; +static constexpr uint32_t S_WEST_SIZE = 4; + +// Master port offset/size constants +static constexpr uint32_t M_CORE_OFFSET = 0; +static constexpr uint32_t M_CORE_SIZE = 2; +static constexpr uint32_t M_CTRL_OFFSET = 4; +static constexpr uint32_t M_CTRL_SIZE = 1; +static constexpr uint32_t M_DMA_OFFSET = 2; +static constexpr uint32_t M_DMA_SIZE = 2; +static constexpr uint32_t M_EAST_OFFSET = 21; +static constexpr uint32_t M_EAST_SIZE = 4; +static constexpr uint32_t M_FIFO_OFFSET = 5; +static constexpr uint32_t M_FIFO_SIZE = 2; +static constexpr uint32_t M_NORTH_OFFSET = 15; +static constexpr uint32_t M_NORTH_SIZE = 6; +static constexpr uint32_t M_SOUTH_OFFSET = 7; +static constexpr uint32_t M_SOUTH_SIZE = 4; +static constexpr uint32_t M_WEST_OFFSET = 11; +static constexpr uint32_t M_WEST_SIZE = 4; + +} // namespace core + +namespace shim { + +// Slave port offset/size constants +static constexpr uint32_t S_CTRL_OFFSET = 0; +static constexpr uint32_t S_CTRL_SIZE = 1; +static constexpr uint32_t S_EAST_OFFSET = 19; +static constexpr uint32_t S_EAST_SIZE = 4; +static constexpr uint32_t S_FIFO_OFFSET = 1; +static constexpr uint32_t S_FIFO_SIZE = 2; +static constexpr uint32_t S_NORTH_OFFSET = 15; +static constexpr uint32_t S_NORTH_SIZE = 4; +static constexpr uint32_t S_SOUTH_OFFSET = 3; +static constexpr uint32_t S_SOUTH_SIZE = 8; +static constexpr uint32_t S_TRACE_OFFSET = 23; +static constexpr uint32_t S_TRACE_SIZE = 1; +static constexpr uint32_t S_WEST_OFFSET = 11; +static constexpr uint32_t S_WEST_SIZE = 4; + +// Master port offset/size constants +static constexpr uint32_t M_CTRL_OFFSET = 0; +static constexpr uint32_t M_CTRL_SIZE = 1; +static constexpr uint32_t M_EAST_OFFSET = 19; +static constexpr uint32_t M_EAST_SIZE = 4; +static constexpr uint32_t M_FIFO_OFFSET = 1; +static constexpr uint32_t M_FIFO_SIZE = 2; +static constexpr uint32_t M_NORTH_OFFSET = 13; +static constexpr uint32_t M_NORTH_SIZE = 6; +static constexpr uint32_t M_SOUTH_OFFSET = 3; +static constexpr uint32_t M_SOUTH_SIZE = 6; +static constexpr uint32_t M_WEST_OFFSET = 9; +static constexpr uint32_t M_WEST_SIZE = 4; + +} // namespace shim +} // namespace aie1_port_id +} // anonymous namespace + +std::optional AIE1TargetModel::getStreamSwitchPortIndex( + int col, int row, WireBundle bundle, uint32_t port_num, bool master) const { + if (master) { + if (isCoreTile(col, row)) { + switch (bundle) { + case WireBundle::Core: + if (port_num >= aie1_port_id::core::M_CORE_SIZE) + return std::nullopt; + return aie1_port_id::core::M_CORE_OFFSET + port_num; + case WireBundle::TileControl: + if (port_num >= aie1_port_id::core::M_CTRL_SIZE) + return std::nullopt; + return aie1_port_id::core::M_CTRL_OFFSET + port_num; + case WireBundle::DMA: + if (port_num >= aie1_port_id::core::M_DMA_SIZE) + return std::nullopt; + return aie1_port_id::core::M_DMA_OFFSET + port_num; + case WireBundle::East: + if (port_num >= aie1_port_id::core::M_EAST_SIZE) + return std::nullopt; + return aie1_port_id::core::M_EAST_OFFSET + port_num; + case WireBundle::FIFO: + if (port_num >= aie1_port_id::core::M_FIFO_SIZE) + return std::nullopt; + return aie1_port_id::core::M_FIFO_OFFSET + port_num; + case WireBundle::North: + if (port_num >= aie1_port_id::core::M_NORTH_SIZE) + return std::nullopt; + return aie1_port_id::core::M_NORTH_OFFSET + port_num; + case WireBundle::South: + if (port_num >= aie1_port_id::core::M_SOUTH_SIZE) + return std::nullopt; + return aie1_port_id::core::M_SOUTH_OFFSET + port_num; + case WireBundle::West: + if (port_num >= aie1_port_id::core::M_WEST_SIZE) + return std::nullopt; + return aie1_port_id::core::M_WEST_OFFSET + port_num; + default: + return std::nullopt; + } + } else if (isShimNOCorPLTile(col, row)) { + switch (bundle) { + case WireBundle::TileControl: + if (port_num >= aie1_port_id::shim::M_CTRL_SIZE) + return std::nullopt; + return aie1_port_id::shim::M_CTRL_OFFSET + port_num; + case WireBundle::East: + if (port_num >= aie1_port_id::shim::M_EAST_SIZE) + return std::nullopt; + return aie1_port_id::shim::M_EAST_OFFSET + port_num; + case WireBundle::FIFO: + if (port_num >= aie1_port_id::shim::M_FIFO_SIZE) + return std::nullopt; + return aie1_port_id::shim::M_FIFO_OFFSET + port_num; + case WireBundle::North: + if (port_num >= aie1_port_id::shim::M_NORTH_SIZE) + return std::nullopt; + return aie1_port_id::shim::M_NORTH_OFFSET + port_num; + case WireBundle::South: + if (port_num >= aie1_port_id::shim::M_SOUTH_SIZE) + return std::nullopt; + return aie1_port_id::shim::M_SOUTH_OFFSET + port_num; + case WireBundle::West: + if (port_num >= aie1_port_id::shim::M_WEST_SIZE) + return std::nullopt; + return aie1_port_id::shim::M_WEST_OFFSET + port_num; + default: + return std::nullopt; + } + } else { + return std::nullopt; + } + } else { + if (isCoreTile(col, row)) { + switch (bundle) { + case WireBundle::Core: + if (port_num >= aie1_port_id::core::S_CORE_SIZE) + return std::nullopt; + return aie1_port_id::core::S_CORE_OFFSET + port_num; + case WireBundle::TileControl: + if (port_num >= aie1_port_id::core::S_CTRL_SIZE) + return std::nullopt; + return aie1_port_id::core::S_CTRL_OFFSET + port_num; + case WireBundle::DMA: + if (port_num >= aie1_port_id::core::S_DMA_SIZE) + return std::nullopt; + return aie1_port_id::core::S_DMA_OFFSET + port_num; + case WireBundle::East: + if (port_num >= aie1_port_id::core::S_EAST_SIZE) + return std::nullopt; + return aie1_port_id::core::S_EAST_OFFSET + port_num; + case WireBundle::FIFO: + if (port_num >= aie1_port_id::core::S_FIFO_SIZE) + return std::nullopt; + return aie1_port_id::core::S_FIFO_OFFSET + port_num; + case WireBundle::North: + if (port_num >= aie1_port_id::core::S_NORTH_SIZE) + return std::nullopt; + return aie1_port_id::core::S_NORTH_OFFSET + port_num; + case WireBundle::South: + if (port_num >= aie1_port_id::core::S_SOUTH_SIZE) + return std::nullopt; + return aie1_port_id::core::S_SOUTH_OFFSET + port_num; + case WireBundle::Trace: + if (port_num >= aie1_port_id::core::S_TRACE_SIZE) + return std::nullopt; + return aie1_port_id::core::S_TRACE_OFFSET + port_num; + case WireBundle::West: + if (port_num >= aie1_port_id::core::S_WEST_SIZE) + return std::nullopt; + return aie1_port_id::core::S_WEST_OFFSET + port_num; + default: + return std::nullopt; + } + } else if (isShimNOCorPLTile(col, row)) { + switch (bundle) { + case WireBundle::TileControl: + if (port_num >= aie1_port_id::shim::S_CTRL_SIZE) + return std::nullopt; + return aie1_port_id::shim::S_CTRL_OFFSET + port_num; + case WireBundle::East: + if (port_num >= aie1_port_id::shim::S_EAST_SIZE) + return std::nullopt; + return aie1_port_id::shim::S_EAST_OFFSET + port_num; + case WireBundle::FIFO: + if (port_num >= aie1_port_id::shim::S_FIFO_SIZE) + return std::nullopt; + return aie1_port_id::shim::S_FIFO_OFFSET + port_num; + case WireBundle::North: + if (port_num >= aie1_port_id::shim::S_NORTH_SIZE) + return std::nullopt; + return aie1_port_id::shim::S_NORTH_OFFSET + port_num; + case WireBundle::South: + if (port_num >= aie1_port_id::shim::S_SOUTH_SIZE) + return std::nullopt; + return aie1_port_id::shim::S_SOUTH_OFFSET + port_num; + case WireBundle::Trace: + if (port_num >= aie1_port_id::shim::S_TRACE_SIZE) + return std::nullopt; + return aie1_port_id::shim::S_TRACE_OFFSET + port_num; + case WireBundle::West: + if (port_num >= aie1_port_id::shim::S_WEST_SIZE) + return std::nullopt; + return aie1_port_id::shim::S_WEST_OFFSET + port_num; + default: + return std::nullopt; + } + } else { + return std::nullopt; + } + } +} + /// /// AIE2 TargetModel /// @@ -840,6 +1137,311 @@ AIE2TargetModel::getLocalLockAddress(uint32_t lockId, TileID tile) const { return std::nullopt; } +namespace { +namespace aie2_port_id { +namespace core { + +// Slave port offset/size constants +static constexpr uint32_t S_CORE_OFFSET = 0; +static constexpr uint32_t S_CORE_SIZE = 1; +static constexpr uint32_t S_CTRL_OFFSET = 3; +static constexpr uint32_t S_CTRL_SIZE = 1; +static constexpr uint32_t S_DMA_OFFSET = 1; +static constexpr uint32_t S_DMA_SIZE = 2; +static constexpr uint32_t S_EAST_OFFSET = 19; +static constexpr uint32_t S_EAST_SIZE = 4; +static constexpr uint32_t S_FIFO_OFFSET = 4; +static constexpr uint32_t S_FIFO_SIZE = 1; +static constexpr uint32_t S_NORTH_OFFSET = 15; +static constexpr uint32_t S_NORTH_SIZE = 4; +static constexpr uint32_t S_SOUTH_OFFSET = 5; +static constexpr uint32_t S_SOUTH_SIZE = 6; +static constexpr uint32_t S_TRACE_OFFSET = 23; +static constexpr uint32_t S_TRACE_SIZE = 2; +static constexpr uint32_t S_WEST_OFFSET = 11; +static constexpr uint32_t S_WEST_SIZE = 4; + +// Master port offset/size constants +static constexpr uint32_t M_CORE_OFFSET = 0; +static constexpr uint32_t M_CORE_SIZE = 1; +static constexpr uint32_t M_CTRL_OFFSET = 3; +static constexpr uint32_t M_CTRL_SIZE = 1; +static constexpr uint32_t M_DMA_OFFSET = 1; +static constexpr uint32_t M_DMA_SIZE = 2; +static constexpr uint32_t M_EAST_OFFSET = 19; +static constexpr uint32_t M_EAST_SIZE = 4; +static constexpr uint32_t M_FIFO_OFFSET = 4; +static constexpr uint32_t M_FIFO_SIZE = 1; +static constexpr uint32_t M_NORTH_OFFSET = 13; +static constexpr uint32_t M_NORTH_SIZE = 6; +static constexpr uint32_t M_SOUTH_OFFSET = 5; +static constexpr uint32_t M_SOUTH_SIZE = 4; +static constexpr uint32_t M_WEST_OFFSET = 9; +static constexpr uint32_t M_WEST_SIZE = 4; + +} // namespace core + +namespace mem { + +// Slave port offset/size constants +static constexpr uint32_t S_CTRL_OFFSET = 6; +static constexpr uint32_t S_CTRL_SIZE = 1; +static constexpr uint32_t S_DMA_OFFSET = 0; +static constexpr uint32_t S_DMA_SIZE = 6; +static constexpr uint32_t S_NORTH_OFFSET = 13; +static constexpr uint32_t S_NORTH_SIZE = 4; +static constexpr uint32_t S_SOUTH_OFFSET = 7; +static constexpr uint32_t S_SOUTH_SIZE = 6; +static constexpr uint32_t S_TRACE_OFFSET = 17; +static constexpr uint32_t S_TRACE_SIZE = 1; + +// Master port offset/size constants +static constexpr uint32_t M_CTRL_OFFSET = 6; +static constexpr uint32_t M_CTRL_SIZE = 1; +static constexpr uint32_t M_DMA_OFFSET = 0; +static constexpr uint32_t M_DMA_SIZE = 6; +static constexpr uint32_t M_NORTH_OFFSET = 11; +static constexpr uint32_t M_NORTH_SIZE = 6; +static constexpr uint32_t M_SOUTH_OFFSET = 7; +static constexpr uint32_t M_SOUTH_SIZE = 4; + +} // namespace mem + +namespace shim { + +// Slave port offset/size constants +static constexpr uint32_t S_CTRL_OFFSET = 0; +static constexpr uint32_t S_CTRL_SIZE = 1; +static constexpr uint32_t S_EAST_OFFSET = 18; +static constexpr uint32_t S_EAST_SIZE = 4; +static constexpr uint32_t S_FIFO_OFFSET = 1; +static constexpr uint32_t S_FIFO_SIZE = 1; +static constexpr uint32_t S_NORTH_OFFSET = 14; +static constexpr uint32_t S_NORTH_SIZE = 4; +static constexpr uint32_t S_SOUTH_OFFSET = 2; +static constexpr uint32_t S_SOUTH_SIZE = 8; +static constexpr uint32_t S_TRACE_OFFSET = 22; +static constexpr uint32_t S_TRACE_SIZE = 2; +static constexpr uint32_t S_WEST_OFFSET = 10; +static constexpr uint32_t S_WEST_SIZE = 4; + +// Master port offset/size constants +static constexpr uint32_t M_CTRL_OFFSET = 0; +static constexpr uint32_t M_CTRL_SIZE = 1; +static constexpr uint32_t M_EAST_OFFSET = 18; +static constexpr uint32_t M_EAST_SIZE = 4; +static constexpr uint32_t M_FIFO_OFFSET = 1; +static constexpr uint32_t M_FIFO_SIZE = 1; +static constexpr uint32_t M_NORTH_OFFSET = 12; +static constexpr uint32_t M_NORTH_SIZE = 6; +static constexpr uint32_t M_SOUTH_OFFSET = 2; +static constexpr uint32_t M_SOUTH_SIZE = 6; +static constexpr uint32_t M_WEST_OFFSET = 8; +static constexpr uint32_t M_WEST_SIZE = 4; + +} // namespace shim +} // namespace aie2_port_id +} // namespace + +std::optional AIE2TargetModel::getStreamSwitchPortIndex( + int col, int row, WireBundle bundle, uint32_t port_num, bool master) const { + + if (master) { + if (isCoreTile(col, row)) { + switch (bundle) { + case WireBundle::Core: + if (port_num >= aie2_port_id::core::M_CORE_SIZE) + return std::nullopt; + return aie2_port_id::core::M_CORE_OFFSET + port_num; + case WireBundle::TileControl: + if (port_num >= aie2_port_id::core::M_CTRL_SIZE) + return std::nullopt; + return aie2_port_id::core::M_CTRL_OFFSET + port_num; + case WireBundle::DMA: + if (port_num >= aie2_port_id::core::M_DMA_SIZE) + return std::nullopt; + return aie2_port_id::core::M_DMA_OFFSET + port_num; + case WireBundle::East: + if (port_num >= aie2_port_id::core::M_EAST_SIZE) + return std::nullopt; + return aie2_port_id::core::M_EAST_OFFSET + port_num; + case WireBundle::FIFO: + if (port_num >= aie2_port_id::core::M_FIFO_SIZE) + return std::nullopt; + return aie2_port_id::core::M_FIFO_OFFSET + port_num; + case WireBundle::North: + if (port_num >= aie2_port_id::core::M_NORTH_SIZE) + return std::nullopt; + return aie2_port_id::core::M_NORTH_OFFSET + port_num; + case WireBundle::South: + if (port_num >= aie2_port_id::core::M_SOUTH_SIZE) + return std::nullopt; + return aie2_port_id::core::M_SOUTH_OFFSET + port_num; + case WireBundle::West: + if (port_num >= aie2_port_id::core::M_WEST_SIZE) + return std::nullopt; + return aie2_port_id::core::M_WEST_OFFSET + port_num; + default: + return std::nullopt; + } + } else if (isMemTile(col, row)) { + switch (bundle) { + case WireBundle::TileControl: + if (port_num >= aie2_port_id::mem::M_CTRL_SIZE) + return std::nullopt; + return aie2_port_id::mem::M_CTRL_OFFSET + port_num; + case WireBundle::DMA: + if (port_num >= aie2_port_id::mem::M_DMA_SIZE) + return std::nullopt; + return aie2_port_id::mem::M_DMA_OFFSET + port_num; + case WireBundle::North: + if (port_num >= aie2_port_id::mem::M_NORTH_SIZE) + return std::nullopt; + return aie2_port_id::mem::M_NORTH_OFFSET + port_num; + case WireBundle::South: + if (port_num >= aie2_port_id::mem::M_SOUTH_SIZE) + return std::nullopt; + return aie2_port_id::mem::M_SOUTH_OFFSET + port_num; + default: + return std::nullopt; + } + } else if (isShimNOCTile(col, row)) { + switch (bundle) { + case WireBundle::TileControl: + if (port_num >= aie2_port_id::shim::M_CTRL_SIZE) + return std::nullopt; + return aie2_port_id::shim::M_CTRL_OFFSET + port_num; + case WireBundle::East: + if (port_num >= aie2_port_id::shim::M_EAST_SIZE) + return std::nullopt; + return aie2_port_id::shim::M_EAST_OFFSET + port_num; + case WireBundle::FIFO: + if (port_num >= aie2_port_id::shim::M_FIFO_SIZE) + return std::nullopt; + return aie2_port_id::shim::M_FIFO_OFFSET + port_num; + case WireBundle::North: + if (port_num >= aie2_port_id::shim::M_NORTH_SIZE) + return std::nullopt; + return aie2_port_id::shim::M_NORTH_OFFSET + port_num; + case WireBundle::South: + if (port_num >= aie2_port_id::shim::M_SOUTH_SIZE) + return std::nullopt; + return aie2_port_id::shim::M_SOUTH_OFFSET + port_num; + case WireBundle::West: + if (port_num >= aie2_port_id::shim::M_WEST_SIZE) + return std::nullopt; + return aie2_port_id::shim::M_WEST_OFFSET + port_num; + default: + return std::nullopt; + } + } + // Slave ports + } else { + + if (isCoreTile(col, row)) { + switch (bundle) { + case WireBundle::Core: + if (port_num >= aie2_port_id::core::S_CORE_SIZE) + return std::nullopt; + return aie2_port_id::core::S_CORE_OFFSET + port_num; + case WireBundle::TileControl: + if (port_num >= aie2_port_id::core::S_CTRL_SIZE) + return std::nullopt; + return aie2_port_id::core::S_CTRL_OFFSET + port_num; + case WireBundle::DMA: + if (port_num >= aie2_port_id::core::S_DMA_SIZE) + return std::nullopt; + return aie2_port_id::core::S_DMA_OFFSET + port_num; + case WireBundle::East: + if (port_num >= aie2_port_id::core::S_EAST_SIZE) + return std::nullopt; + return aie2_port_id::core::S_EAST_OFFSET + port_num; + case WireBundle::FIFO: + if (port_num >= aie2_port_id::core::S_FIFO_SIZE) + return std::nullopt; + return aie2_port_id::core::S_FIFO_OFFSET + port_num; + case WireBundle::North: + if (port_num >= aie2_port_id::core::S_NORTH_SIZE) + return std::nullopt; + return aie2_port_id::core::S_NORTH_OFFSET + port_num; + case WireBundle::South: + if (port_num >= aie2_port_id::core::S_SOUTH_SIZE) + return std::nullopt; + return aie2_port_id::core::S_SOUTH_OFFSET + port_num; + case WireBundle::Trace: + if (port_num >= aie2_port_id::core::S_TRACE_SIZE) + return std::nullopt; + return aie2_port_id::core::S_TRACE_OFFSET + port_num; + case WireBundle::West: + if (port_num >= aie2_port_id::core::S_WEST_SIZE) + return std::nullopt; + return aie2_port_id::core::S_WEST_OFFSET + port_num; + default: + return std::nullopt; + } + } else if (isMemTile(col, row)) { + switch (bundle) { + case WireBundle::TileControl: + if (port_num >= aie2_port_id::mem::S_CTRL_SIZE) + return std::nullopt; + return aie2_port_id::mem::S_CTRL_OFFSET + port_num; + case WireBundle::DMA: + if (port_num >= aie2_port_id::mem::S_DMA_SIZE) + return std::nullopt; + return aie2_port_id::mem::S_DMA_OFFSET + port_num; + case WireBundle::North: + if (port_num >= aie2_port_id::mem::S_NORTH_SIZE) + return std::nullopt; + return aie2_port_id::mem::S_NORTH_OFFSET + port_num; + case WireBundle::South: + if (port_num >= aie2_port_id::mem::S_SOUTH_SIZE) + return std::nullopt; + return aie2_port_id::mem::S_SOUTH_OFFSET + port_num; + case WireBundle::Trace: + if (port_num >= aie2_port_id::mem::S_TRACE_SIZE) + return std::nullopt; + return aie2_port_id::mem::S_TRACE_OFFSET + port_num; + default: + return std::nullopt; + } + } else if (isShimNOCTile(col, row)) { + switch (bundle) { + case WireBundle::TileControl: + if (port_num >= aie2_port_id::shim::S_CTRL_SIZE) + return std::nullopt; + return aie2_port_id::shim::S_CTRL_OFFSET + port_num; + case WireBundle::East: + if (port_num >= aie2_port_id::shim::S_EAST_SIZE) + return std::nullopt; + return aie2_port_id::shim::S_EAST_OFFSET + port_num; + case WireBundle::FIFO: + if (port_num >= aie2_port_id::shim::S_FIFO_SIZE) + return std::nullopt; + return aie2_port_id::shim::S_FIFO_OFFSET + port_num; + case WireBundle::North: + if (port_num >= aie2_port_id::shim::S_NORTH_SIZE) + return std::nullopt; + return aie2_port_id::shim::S_NORTH_OFFSET + port_num; + case WireBundle::South: + if (port_num >= aie2_port_id::shim::S_SOUTH_SIZE) + return std::nullopt; + return aie2_port_id::shim::S_SOUTH_OFFSET + port_num; + case WireBundle::Trace: + if (port_num >= aie2_port_id::shim::S_TRACE_SIZE) + return std::nullopt; + return aie2_port_id::shim::S_TRACE_OFFSET + port_num; + case WireBundle::West: + if (port_num >= aie2_port_id::shim::S_WEST_SIZE) + return std::nullopt; + return aie2_port_id::shim::S_WEST_OFFSET + port_num; + default: + return std::nullopt; + } + } + } + return std::nullopt; +} + void AIETargetModel::validate() const { // Every tile in a shimtile row must be a shimtile, and can only be one type // of shim tile. diff --git a/lib/Dialect/AIE/IR/AIETraceOps.cpp b/lib/Dialect/AIE/IR/AIETraceOps.cpp new file mode 100644 index 00000000000..05ece433144 --- /dev/null +++ b/lib/Dialect/AIE/IR/AIETraceOps.cpp @@ -0,0 +1,743 @@ +//===- AIETraceOps.cpp ------------------------------------------*- C++ -*-===// +// +// This file is licensed under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +// Copyright (C) 2025, Advanced Micro Devices, Inc. +// +//===----------------------------------------------------------------------===// +// Implementation of AIE trace operations +//===----------------------------------------------------------------------===// + +#include "aie/Dialect/AIE/IR/AIEDialect.h" + +#include "mlir/IR/OpImplementation.h" +#include "mlir/IR/SymbolTable.h" +#include "llvm/ADT/SmallVector.h" +#include "llvm/ADT/StringRef.h" + +using namespace mlir; +using namespace xilinx; +using namespace xilinx::AIE; + +//===----------------------------------------------------------------------===// +// TraceEventAttr Helper Methods +//===----------------------------------------------------------------------===// + +std::string TraceEventAttr::getEventName() const { + if (auto strAttr = llvm::dyn_cast(getValue())) { + // If the string is fully qualified (contains '::'), extract just the name + StringRef strValue = strAttr.getValue(); + size_t pos = strValue.find("::"); + if (pos != StringRef::npos) { + return strValue.substr(pos + 2).str(); + } + return strAttr.getValue().str(); + } + + // Fallback: unexpected attribute kind. + return ""; +} + +bool TraceEventAttr::isStringAttr() const { + return llvm::isa(getValue()); +} + +std::optional TraceEventAttr::getEnumValue() const { + if (auto intAttr = llvm::dyn_cast(getValue())) { + return intAttr.getInt(); + } + // String values don't have enum values - they're event names + return std::nullopt; +} + +//===----------------------------------------------------------------------===// +// Tile Type Validation Helper +//===----------------------------------------------------------------------===// + +static bool isValidEventForTile(TileOp tile, Attribute eventAttr) { + int col = tile.getCol(); + int row = tile.getRow(); + const auto &targetModel = getTargetModel(tile); + + // Determine tile type + bool isShimTile = targetModel.isShimNOCorPLTile(col, row); + bool isMemTile = targetModel.isMemTile(col, row); + bool isCoreTile = targetModel.isCoreTile(col, row); + AIEArch arch = targetModel.getTargetArch(); + + // If eventAttr is a TraceEventAttr, extract the inner attribute + Attribute innerAttr = eventAttr; + if (auto traceEvent = llvm::dyn_cast(eventAttr)) { + innerAttr = traceEvent.getValue(); + } + + // Enum-qualified strings (e.g. "CoreEventAIE2P::INSTR_EVENT_0") encode both + // architecture and tile type in the prefix. We validate the prefix against + // the device arch and tile kind here. Plain strings (no "::") are deferred + // to lowering where the event database is available. + // + // NOTE: The parser stores enum events as strings rather than typed + // I32EnumAttrs because AIE2 and AIE2P enums share identical integer values, + // making isa<> checks ambiguous across architectures. + if (auto strAttr = llvm::dyn_cast(innerAttr)) { + StringRef val = strAttr.getValue(); + size_t sep = val.find("::"); + if (sep == StringRef::npos) + return true; // plain string — validated during lowering + + StringRef prefix = val.substr(0, sep); + + // Map (arch, tileKind) → set of allowed prefixes. + if (isCoreTile) { + switch (arch) { + case AIEArch::AIE1: + return prefix == "CoreEventAIE" || prefix == "MemEventAIE"; + case AIEArch::AIE2: + return prefix == "CoreEventAIE2" || prefix == "MemEventAIE2"; + case AIEArch::AIE2p: + return prefix == "CoreEventAIE2P" || prefix == "MemEventAIE2P"; + default: + return false; + } + } + if (isMemTile) { + switch (arch) { + case AIEArch::AIE2: + return prefix == "MemTileEventAIE2"; + case AIEArch::AIE2p: + return prefix == "MemTileEventAIE2P"; + default: + return false; + } + } + if (isShimTile) { + switch (arch) { + case AIEArch::AIE1: + return prefix == "ShimTileEventAIE"; + case AIEArch::AIE2: + return prefix == "ShimTileEventAIE2"; + case AIEArch::AIE2p: + return prefix == "ShimTileEventAIE2P"; + default: + return false; + } + } + return false; + } + + return false; +} + +//===----------------------------------------------------------------------===// +// TraceOp +//===----------------------------------------------------------------------===// + +void TraceOp::getAsmResultNames( + function_ref setNameFn) { + // No results for this operation +} + +LogicalResult TraceOp::verify() { + // Count trace events + int eventCount = 0; + for (auto &op : getBody().getOps()) { + if (isa(op)) { + eventCount++; + } + } + + // Check max 8 events + if (eventCount > 8) { + return emitOpError("trace unit supports maximum 8 events, got ") + << eventCount; + } + + // Verify tile operand is a TileOp + if (!getTile().getDefiningOp()) { + return emitOpError("tile operand must be a TileOp"); + } + + // Track combo/edge slot usage within this trace + llvm::DenseSet comboSlots; + llvm::DenseSet edgeSlots; + + for (auto &op : getBody().getOps()) { + if (auto comboOp = dyn_cast(op)) { + uint32_t slot = comboOp.getSlot(); + if (!comboSlots.insert(slot).second) { + return comboOp.emitOpError("combo event slot ") + << slot << " already in use in this trace"; + } + } else if (auto edgeOp = dyn_cast(op)) { + uint32_t slot = edgeOp.getSlot(); + if (!edgeSlots.insert(slot).second) { + return edgeOp.emitOpError("edge detection slot ") + << slot << " already in use in this trace"; + } + } + } + + return success(); +} + +//===----------------------------------------------------------------------===// +// Helper function for parsing event values (string or typed enum) +// Made available in xilinx::AIE namespace for reuse in AIEDialect.cpp +//===----------------------------------------------------------------------===// + +ParseResult xilinx::AIE::parseTraceEvent(AsmParser &parser, Attribute &result) { + MLIRContext *ctx = parser.getContext(); + + // Try to parse as string first + std::string strValue; + if (succeeded(parser.parseOptionalString(&strValue))) { + result = StringAttr::get(ctx, strValue); + return success(); + } + + // Try to parse as enum (EnumType::VALUE) + StringRef enumTypeName; + llvm::SMLoc loc = parser.getCurrentLocation(); + + if (failed(parser.parseKeyword(&enumTypeName))) { + return parser.emitError(loc, "expected string or enum event"); + } + + if (failed(parser.parseColon()) || failed(parser.parseColon())) { + return parser.emitError(loc, "expected '::' after enum type name"); + } + + StringRef caseName; + if (failed(parser.parseKeyword(&caseName))) { + return parser.emitError(parser.getCurrentLocation(), + "expected enum case name"); + } + // Store as a qualified string ("EnumType::CaseName") rather than a typed + // I32EnumAttr because different architecture enums share identical integer + // values, making isa<> checks ambiguous. The enum prefix is validated + // against the device architecture in isValidEventForTile(). + + // Define a helper struct for enum validation + struct EnumValidator { + StringRef name; + std::function symbolizer; + }; + + // Table of supported enum types and their symbolizer functions + static const EnumValidator validators[] = { + // AIE2 event enums + {"CoreEventAIE2", + [](StringRef s) { return symbolizeCoreEventAIE2(s).has_value(); }}, + {"MemEventAIE2", + [](StringRef s) { + auto result = symbolizeMemEventAIE2(s); + return result.has_value(); + }}, + {"MemTileEventAIE2", + [](StringRef s) { + auto result = symbolizeMemTileEventAIE2(s); + return result.has_value(); + }}, + {"ShimTileEventAIE2", + [](StringRef s) { + auto result = symbolizeShimTileEventAIE2(s); + return result.has_value(); + }}, + // AIE event enums + {"CoreEventAIE", + [](StringRef s) { + auto result = symbolizeCoreEventAIE(s); + return result.has_value(); + }}, + {"MemEventAIE", + [](StringRef s) { + auto result = symbolizeMemEventAIE(s); + return result.has_value(); + }}, + {"ShimTileEventAIE", + [](StringRef s) { + auto result = symbolizeShimTileEventAIE(s); + return result.has_value(); + }}, + // AIE2P event enums + {"CoreEventAIE2P", + [](StringRef s) { + auto result = symbolizeCoreEventAIE2P(s); + return result.has_value(); + }}, + {"MemEventAIE2P", + [](StringRef s) { + auto result = symbolizeMemEventAIE2P(s); + return result.has_value(); + }}, + {"MemTileEventAIE2P", + [](StringRef s) { + auto result = symbolizeMemTileEventAIE2P(s); + return result.has_value(); + }}, + {"ShimTileEventAIE2P", + [](StringRef s) { + auto result = symbolizeShimTileEventAIE2P(s); + return result.has_value(); + }}, + }; + + // Look up and validate the enum type + for (const auto &validator : validators) { + if (enumTypeName != validator.name) + continue; + if (!validator.symbolizer(caseName)) + return parser.emitError(loc, "unknown ") + << enumTypeName << " value: " << caseName; + result = StringAttr::get(ctx, enumTypeName + "::" + caseName); + return success(); + } + + return parser.emitError(loc, "unknown event enum type: ") << enumTypeName; +} + +void xilinx::AIE::printTraceEventEnum(AsmPrinter &printer, Attribute attr) { + if (auto traceAttr = llvm::dyn_cast(attr)) { + printTraceEventEnum(printer, traceAttr.getValue()); + return; + } + if (auto strAttr = llvm::dyn_cast(attr)) { + // If string contains "::" (enum format), print without quotes + if (strAttr.getValue().contains("::")) { + printer << strAttr.getValue(); + return; + } + printer << "\"" << strAttr.getValue() << "\""; + } + if (auto intAttr = llvm::dyn_cast(attr)) { + printer << intAttr.getInt(); + return; + } +} + +//===----------------------------------------------------------------------===// +// TraceEventOp +//===----------------------------------------------------------------------===// + +LogicalResult TraceEventOp::verify() { + // Get event name/value + auto eventAttr = getEvent(); + + // Basic validation - event should not be empty + std::string eventName = eventAttr.getEventName(); + if (eventName.empty()) { + return emitOpError("event name cannot be empty"); + } + + // Validate event type matches tile type + auto trace = (*this)->getParentOfType(); + if (!trace) { + return emitOpError("must be nested in aie.trace"); + } + + auto tileOp = dyn_cast(trace.getTile().getDefiningOp()); + if (!tileOp) { + return emitOpError("trace tile must be a TileOp"); + } + + if (!isValidEventForTile(tileOp, eventAttr.getValue())) { + int col = tileOp.getCol(); + int row = tileOp.getRow(); + const auto &targetModel = getTargetModel(tileOp); + std::string tileTypeStr; + if (targetModel.isCoreTile(col, row)) + tileTypeStr = "core tile"; + else if (targetModel.isMemTile(col, row)) + tileTypeStr = "mem tile"; + else + tileTypeStr = "shim tile"; + + // Use the full string value (including enum prefix) for clarity. + std::string fullEventName; + if (auto strAttr = llvm::dyn_cast(eventAttr.getValue())) + fullEventName = strAttr.getValue().str(); + else + fullEventName = eventName; + + return emitOpError("event '") + << fullEventName << "' is not valid for " << tileTypeStr << " (" + << stringifyAIEArch(targetModel.getTargetArch()) << ") at (" << col + << ", " << row << ")"; + } + + return success(); +} + +//===----------------------------------------------------------------------===// +// TracePacketOp +//===----------------------------------------------------------------------===// + +LogicalResult TracePacketOp::verify() { + // Packet ID range is already enforced by Confined constraint in TableGen + // Just verify it's within valid range + int32_t id = getId(); + if (id < 1 || id > 31) { + return emitOpError("packet ID must be in range [1, 31], got ") << id; + } + + return success(); +} + +//===----------------------------------------------------------------------===// +// TracePortOp +//===----------------------------------------------------------------------===// + +LogicalResult TracePortOp::verify() { + // Get parent trace and tile + auto trace = (*this)->getParentOfType(); + if (!trace) { + return emitOpError("must be nested in aie.trace"); + } + + auto tileOp = dyn_cast(trace.getTile().getDefiningOp()); + if (!tileOp) { + return emitOpError("trace tile must be a TileOp"); + } + + // Get target model + auto device = trace->getParentOfType(); + const auto &targetModel = device.getTargetModel(); + + // Convert DMAChannelDir to master flag: S2MM=master, MM2S=slave + bool isMaster = (getDirection() == DMAChannelDir::S2MM); + + // Verify port is valid for this tile + if (!targetModel + .getStreamSwitchPortIndex(tileOp.getCol(), tileOp.getRow(), + getPort(), getChannel(), isMaster) + .has_value()) { + return emitOpError("invalid stream switch port configuration for tile (") + << tileOp.getCol() << ", " << tileOp.getRow() << ")"; + } + + // Check for duplicate slots within same trace + for (auto &op : trace.getBody().getOps()) { + if (auto otherPort = dyn_cast(op)) { + if (otherPort != *this && otherPort.getSlot() == getSlot()) { + return emitOpError("duplicate port slot ") + << getSlot() << " in trace " << trace.getSymName(); + } + } + } + + return success(); +} + +//===----------------------------------------------------------------------===// +// TraceStartEventOp +//===----------------------------------------------------------------------===// + +LogicalResult TraceStartEventOp::verify() { + // Must have either broadcast or event, but not both + bool hasBroadcast = getBroadcast().has_value(); + bool hasEvent = getEvent().has_value(); + + if (!hasBroadcast && !hasEvent) { + return emitOpError("must specify either broadcast or event"); + } + + if (hasBroadcast && hasEvent) { + return emitOpError("cannot specify both broadcast and event"); + } + + return success(); +} + +//===----------------------------------------------------------------------===// +// TraceStopEventOp +//===----------------------------------------------------------------------===// + +LogicalResult TraceStopEventOp::verify() { + // Must have either broadcast or event, but not both + bool hasBroadcast = getBroadcast().has_value(); + bool hasEvent = getEvent().has_value(); + + if (!hasBroadcast && !hasEvent) { + return emitOpError("must specify either broadcast or event"); + } + + if (hasBroadcast && hasEvent) { + return emitOpError("cannot specify both broadcast and event"); + } + + return success(); +} + +//===----------------------------------------------------------------------===// +// TraceStartEventOp and TraceStopEventOp +//===----------------------------------------------------------------------===// + +void TraceStartEventOp::print(OpAsmPrinter &p) { + if (auto broadcast = getBroadcast()) { + p << " broadcast = " << broadcast.value(); + } + if (auto event = getEvent()) { + p << " event = <"; + printTraceEventEnum(p, event->getValue()); + p << ">"; + } + p.printOptionalAttrDict((*this)->getAttrs(), + /*elidedAttrs=*/{"broadcast", "event"}); +} + +ParseResult TraceStartEventOp::parse(OpAsmParser &parser, + OperationState &result) { + // Parse optional broadcast + if (succeeded(parser.parseOptionalKeyword("broadcast"))) { + IntegerAttr broadcast; + if (parser.parseEqual() || + parser.parseAttribute(broadcast, parser.getBuilder().getI32Type(), + "broadcast", result.attributes)) + return failure(); + } + + // Parse optional event + if (succeeded(parser.parseOptionalKeyword("event"))) { + if (parser.parseEqual() || parser.parseLess()) + return failure(); + + Attribute innerValue; + if (failed(parseTraceEvent(parser, innerValue))) + return failure(); + + auto traceEvent = TraceEventAttr::get(parser.getContext(), innerValue); + result.attributes.set("event", traceEvent); + + if (parser.parseGreater()) + return failure(); + } + + if (parser.parseOptionalAttrDict(result.attributes)) + return failure(); + + return success(); +} + +void TraceStopEventOp::print(OpAsmPrinter &p) { + if (auto broadcast = getBroadcast()) { + p << " broadcast = " << broadcast.value(); + } + if (auto event = getEvent()) { + p << " event = <"; + printTraceEventEnum(p, event->getValue()); + p << ">"; + } + p.printOptionalAttrDict((*this)->getAttrs(), + /*elidedAttrs=*/{"broadcast", "event"}); +} + +ParseResult TraceStopEventOp::parse(OpAsmParser &parser, + OperationState &result) { + // Parse optional broadcast + if (succeeded(parser.parseOptionalKeyword("broadcast"))) { + IntegerAttr broadcast; + if (parser.parseEqual() || + parser.parseAttribute(broadcast, parser.getBuilder().getI32Type(), + "broadcast", result.attributes)) + return failure(); + } + + // Parse optional event + if (succeeded(parser.parseOptionalKeyword("event"))) { + if (parser.parseEqual() || parser.parseLess()) + return failure(); + + Attribute innerValue; + if (failed(parseTraceEvent(parser, innerValue))) + return failure(); + + auto traceEvent = TraceEventAttr::get(parser.getContext(), innerValue); + result.attributes.set("event", traceEvent); + + if (parser.parseGreater()) + return failure(); + } + + if (parser.parseOptionalAttrDict(result.attributes)) + return failure(); + + return success(); +} + +//===----------------------------------------------------------------------===// +// TraceComboEventOp +//===----------------------------------------------------------------------===// + +LogicalResult TraceComboEventOp::verify() { + uint32_t slot = getSlot(); + + // Check slot is valid (0, 1, or 2) + if (slot > 2) { + return emitOpError("combo event slot must be 0, 1, or 2, got ") << slot; + } + + // Get parent trace and tile for validation + auto trace = (*this)->getParentOfType(); + if (!trace) { + return emitOpError("must be nested in aie.trace"); + } + + auto tileOp = dyn_cast(trace.getTile().getDefiningOp()); + if (!tileOp) { + return emitOpError("trace tile must be a TileOp"); + } + + // Validate event types match tile + if (!isValidEventForTile(tileOp, getEventA().getValue())) { + return emitOpError("eventA is not valid for this tile type"); + } + if (!isValidEventForTile(tileOp, getEventB().getValue())) { + return emitOpError("eventB is not valid for this tile type"); + } + + // Validate event selection based on slot + std::string eventAName = getEventA().getEventName(); + std::string eventBName = getEventB().getEventName(); + + if (slot == 0) { + // Combo 0: should not use eventC/D or combo results + if (eventAName.find("COMBO_EVENT") != std::string::npos || + eventBName.find("COMBO_EVENT") != std::string::npos) { + return emitOpError("combo slot 0 should use regular events, not " + "COMBO_EVENT_* (uses eventA/B)"); + } + } else if (slot == 1) { + // Combo 1: should not use combo results + if (eventAName.find("COMBO_EVENT") != std::string::npos || + eventBName.find("COMBO_EVENT") != std::string::npos) { + return emitOpError("combo slot 1 should use regular events, not " + "COMBO_EVENT_* (uses eventC/D)"); + } + } else if (slot == 2) { + // Combo 2 is hierarchical - must use COMBO_EVENT_0 and COMBO_EVENT_1 + if (eventAName != "COMBO_EVENT_0") { + return emitOpError("combo slot 2 first event must be COMBO_EVENT_0 " + "(hierarchical), got ") + << eventAName; + } + if (eventBName != "COMBO_EVENT_1") { + return emitOpError("combo slot 2 second event must be COMBO_EVENT_1 " + "(hierarchical), got ") + << eventBName; + } + } + + return success(); +} + +//===----------------------------------------------------------------------===// +// TraceEdgeEventOp +//===----------------------------------------------------------------------===// + +LogicalResult TraceEdgeEventOp::verify() { + uint32_t slot = getSlot(); + + // Check slot is valid (0 or 1) + if (slot > 1) { + return emitOpError("edge detection slot must be 0 or 1, got ") << slot; + } + + // Get parent trace and tile for validation + auto trace = (*this)->getParentOfType(); + if (!trace) { + return emitOpError("must be nested in aie.trace"); + } + + auto tileOp = dyn_cast(trace.getTile().getDefiningOp()); + if (!tileOp) { + return emitOpError("trace tile must be a TileOp"); + } + + // Validate event type matches tile + if (!isValidEventForTile(tileOp, getEvent().getValue())) { + return emitOpError("event is not valid for this tile type"); + } + + // Edge events should not be other edge/combo events + std::string eventName = getEvent().getEventName(); + if (eventName.find("EDGE_DETECTION_EVENT") != std::string::npos) { + return emitOpError("edge detection source should be a regular event, not " + "another EDGE_DETECTION_EVENT"); + } + if (eventName.find("COMBO_EVENT") != std::string::npos) { + return emitOpError("edge detection source should be a regular event, not " + "a COMBO_EVENT (combo events can be used but may have " + "unexpected behavior)"); + } + + return success(); +} + +void TraceEdgeEventOp::print(OpAsmPrinter &p) { + p << "<" << getSlot() << "> event = <"; + printTraceEventEnum(p, getEvent().getValue()); + p << "> trigger = " << getTrigger(); + p.printOptionalAttrDict((*this)->getAttrs(), + /*elidedAttrs=*/{"slot", "event", "trigger"}); +} + +ParseResult TraceEdgeEventOp::parse(OpAsmParser &parser, + OperationState &result) { + IntegerAttr slot; + Attribute eventValue; + EdgeTriggerAttr trigger; + + if (parser.parseLess() || + parser.parseAttribute(slot, parser.getBuilder().getI32Type(), "slot", + result.attributes) || + parser.parseGreater() || parser.parseKeyword("event") || + parser.parseEqual() || parser.parseLess()) + return failure(); + + // Parse event value (string or enum) + if (failed(parseTraceEvent(parser, eventValue))) + return failure(); + + auto event = TraceEventAttr::get(parser.getContext(), eventValue); + result.attributes.set("event", event); + + if (parser.parseGreater() || parser.parseKeyword("trigger") || + parser.parseEqual()) + return failure(); + + // Parse trigger as keyword (RISING, FALLING, BOTH) + StringRef triggerStr; + if (failed(parser.parseKeyword(&triggerStr))) + return failure(); + + auto triggerEnum = symbolizeEdgeTrigger(triggerStr); + if (!triggerEnum) { + return parser.emitError(parser.getCurrentLocation(), + "unknown edge trigger: ") + << triggerStr; + } + trigger = EdgeTriggerAttr::get(parser.getContext(), *triggerEnum); + result.attributes.set("trigger", trigger); + + if (parser.parseOptionalAttrDict(result.attributes)) + return failure(); + + return success(); +} + +//===----------------------------------------------------------------------===// +// TraceStartConfigOp +//===----------------------------------------------------------------------===// + +LogicalResult TraceStartConfigOp::verify() { + // Verify that the referenced symbol exists + // This will be checked more thoroughly during lowering + auto symbolName = getTraceConfig(); + if (symbolName.empty()) { + return emitOpError("trace config symbol name cannot be empty"); + } + + return success(); +} diff --git a/lib/Dialect/AIE/IR/CMakeLists.txt b/lib/Dialect/AIE/IR/CMakeLists.txt index 65040f3a868..45cff74cc72 100644 --- a/lib/Dialect/AIE/IR/CMakeLists.txt +++ b/lib/Dialect/AIE/IR/CMakeLists.txt @@ -8,6 +8,7 @@ add_mlir_dialect_library(AIE AIETargetModel.cpp AIEDialect.cpp + AIETraceOps.cpp ADDITIONAL_HEADER_DIRS ${AIE_BINARY_DIR}/include diff --git a/lib/Dialect/AIE/Transforms/AIETraceToConfig.cpp b/lib/Dialect/AIE/Transforms/AIETraceToConfig.cpp new file mode 100644 index 00000000000..2a3c1c18a42 --- /dev/null +++ b/lib/Dialect/AIE/Transforms/AIETraceToConfig.cpp @@ -0,0 +1,656 @@ +//===- AIETraceToConfig.cpp -------------------------------------*- C++ -*-===// +// +// This file is licensed under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +// Copyright (C) 2025, Advanced Micro Devices, Inc. +// +//===----------------------------------------------------------------------===// +// Pass to lower aie.trace to aie.trace.config +//===----------------------------------------------------------------------===// + +#include "aie/Dialect/AIE/IR/AIEDialect.h" +#include "aie/Dialect/AIE/Transforms/AIEPasses.h" + +#include "mlir/IR/Attributes.h" +#include "mlir/Pass/Pass.h" + +using namespace mlir; +using namespace xilinx; +using namespace xilinx::AIE; + +namespace { + +struct AIETraceToConfigPass : AIETraceToConfigBase { + void runOnOperation() override { + DeviceOp device = getOperation(); + OpBuilder builder(device); + const auto &targetModel = device.getTargetModel(); + + // Collect all trace operations + SmallVector traces; + device.walk([&](TraceOp trace) { traces.push_back(trace); }); + + for (auto trace : traces) { + // Create config symbol name + std::string configName = (trace.getSymName().str() + "_config"); + auto tile = cast(trace.getTile().getDefiningOp()); + TileID tileID = {tile.getCol(), tile.getRow()}; + + // Find packet type (if any) + TracePacketType packetType = TracePacketType::Core; // default + for (auto &op : trace.getBody().getOps()) { + if (auto packetOp = dyn_cast(op)) { + packetType = packetOp.getType(); + break; + } + } + + // Insert trace.config after trace declaration + builder.setInsertionPointAfter(trace); + auto configOp = builder.create( + trace.getLoc(), trace.getTile(), builder.getStringAttr(configName), + TracePacketTypeAttr::get(builder.getContext(), packetType)); + + // Build register writes inside config body + Block *configBody = new Block(); + configOp.getBody().push_back(configBody); + OpBuilder configBuilder = OpBuilder::atBlockEnd(configBody); + + bool isMem = (packetType == TracePacketType::Mem); + + // Process combo/edge events FIRST (before other trace config) + // This ensures COMBO_EVENT_*/EDGE_DETECTION_EVENT_* are configured + // before they can be referenced in trace.event operations + + // 0a. Emit combo event configurations + for (auto &op : trace.getBody().getOps()) { + if (auto comboOp = dyn_cast(op)) { + uint32_t slot = comboOp.getSlot(); + + // Get input events - use getEventName() helper + std::string eventAName = comboOp.getEventA().getEventName(); + std::string eventBName = comboOp.getEventB().getEventName(); + ComboLogic logic = comboOp.getLogic(); + + // If enum, use enum value directly; otherwise lookup by name + std::optional eventANum, eventBNum; + + if (auto enumValA = comboOp.getEventA().getEnumValue()) { + eventANum = static_cast(*enumValA); + } else { + eventANum = targetModel.lookupEvent(eventAName, tileID, isMem); + } + + if (auto enumValB = comboOp.getEventB().getEnumValue()) { + eventBNum = static_cast(*enumValB); + } else { + eventBNum = targetModel.lookupEvent(eventBName, tileID, isMem); + } + + if (!eventANum) { + comboOp.emitError("unknown trace event '") << eventAName << "'"; + return signalPassFailure(); + } + if (!eventBNum) { + comboOp.emitError("unknown trace event '") << eventBName << "'"; + return signalPassFailure(); + } + + // Map slot to input event fields + StringRef eventAField, eventBField, controlField; + if (slot == 0) { + eventAField = "eventA"; + eventBField = "eventB"; + controlField = "combo0"; + } else if (slot == 1) { + eventAField = "eventC"; + eventBField = "eventD"; + controlField = "combo1"; + } else if (slot == 2) { + // Combo2 is hierarchical - reuses eventA/B fields but represents + // combo0/combo1 + eventAField = "eventA"; + eventBField = "eventB"; + controlField = "combo2"; + } + + // Emit Combo_event_inputs register fields + configBuilder.create( + comboOp.getLoc(), builder.getStringAttr("Combo_event_inputs"), + builder.getStringAttr(eventAField), comboOp.getEventA(), + /*mask=*/nullptr, + builder.getStringAttr("combo" + std::to_string(slot) + + " eventA")); + + configBuilder.create( + comboOp.getLoc(), builder.getStringAttr("Combo_event_inputs"), + builder.getStringAttr(eventBField), comboOp.getEventB(), + /*mask=*/nullptr, + builder.getStringAttr("combo" + std::to_string(slot) + + " eventB")); + + // Emit Combo_event_control register field + configBuilder.create( + comboOp.getLoc(), builder.getStringAttr("Combo_event_control"), + builder.getStringAttr(controlField), + builder.getI32IntegerAttr(static_cast(logic)), + /*mask=*/nullptr, + builder.getStringAttr("combo" + std::to_string(slot) + " logic")); + } + } + + // 0b. Emit edge detection configurations + for (auto &op : trace.getBody().getOps()) { + if (auto edgeOp = dyn_cast(op)) { + uint32_t slot = edgeOp.getSlot(); + std::string eventName = edgeOp.getEvent().getEventName(); + EdgeTrigger trigger = edgeOp.getTrigger(); + + // If enum, use enum value directly; otherwise lookup by name + std::optional eventNum; + + if (auto enumVal = edgeOp.getEvent().getEnumValue()) { + eventNum = static_cast(*enumVal); + } else { + eventNum = targetModel.lookupEvent(eventName, tileID, isMem); + } + + if (!eventNum) { + edgeOp.emitError("unknown trace event '") << eventName << "'"; + return signalPassFailure(); + } + + // Map slot to field names + StringRef eventField = + (slot == 0) ? "Edge_Detection_Event_0" : "Edge_Detection_Event_1"; + StringRef risingField = (slot == 0) + ? "Edge_Detection_0_Trigger_Rising" + : "Edge_Detection_1_Trigger_Rising"; + StringRef fallingField = (slot == 0) + ? "Edge_Detection_0_Trigger_Falling" + : "Edge_Detection_1_Trigger_Falling"; + + // Source event + configBuilder.create( + edgeOp.getLoc(), + builder.getStringAttr("Edge_Detection_event_control"), + builder.getStringAttr(eventField), edgeOp.getEvent(), + /*mask=*/nullptr, + builder.getStringAttr("edge" + std::to_string(slot) + " source")); + + // Trigger mode + bool rising = + (trigger == EdgeTrigger::RISING || trigger == EdgeTrigger::BOTH); + bool falling = + (trigger == EdgeTrigger::FALLING || trigger == EdgeTrigger::BOTH); + + configBuilder.create( + edgeOp.getLoc(), + builder.getStringAttr("Edge_Detection_event_control"), + builder.getStringAttr(risingField), + builder.getI32IntegerAttr(rising ? 1 : 0), + /*mask=*/nullptr, + builder.getStringAttr("edge" + std::to_string(slot) + " rising")); + + configBuilder.create( + edgeOp.getLoc(), + builder.getStringAttr("Edge_Detection_event_control"), + builder.getStringAttr(fallingField), + builder.getI32IntegerAttr(falling ? 1 : 0), + /*mask=*/nullptr, + builder.getStringAttr("edge" + std::to_string(slot) + + " falling")); + } + } + + // 1. Emit Trace_Control0 fields + // Check for start/stop events + for (auto &op : trace.getBody().getOps()) { + if (auto startOp = dyn_cast(op)) { + uint32_t startEvent = 0; + if (startOp.getBroadcast()) { + startEvent = *startOp.getBroadcast(); + } else if (auto eventAttr = startOp.getEvent()) { + // Use getEventName() helper and check for enum + std::string eventName = eventAttr->getEventName(); + + std::optional eventNum; + if (auto enumVal = eventAttr->getEnumValue()) { + eventNum = static_cast(*enumVal); + } else { + eventNum = targetModel.lookupEvent(eventName, tileID, isMem); + } + + if (eventNum) { + startEvent = *eventNum; + } else { + startOp.emitError("unknown trace event '") << eventName << "'"; + return signalPassFailure(); + } + } + + configBuilder.create( + trace.getLoc(), builder.getStringAttr("Trace_Control0"), + builder.getStringAttr("Trace_Start_Event"), + builder.getI32IntegerAttr(startEvent), + /*mask=*/nullptr, builder.getStringAttr("start event")); + } + + if (auto stopOp = dyn_cast(op)) { + uint32_t stopEvent = 0; + if (stopOp.getBroadcast()) { + stopEvent = *stopOp.getBroadcast(); + } else if (auto eventAttr = stopOp.getEvent()) { + // Use getEventName() helper and check for enum + std::string eventName = eventAttr->getEventName(); + + std::optional eventNum; + if (auto enumVal = eventAttr->getEnumValue()) { + eventNum = static_cast(*enumVal); + } else { + eventNum = targetModel.lookupEvent(eventName, tileID, isMem); + } + + if (eventNum) { + stopEvent = *eventNum; + } else { + stopOp.emitError("unknown trace event '") << eventName << "'"; + return signalPassFailure(); + } + } + + configBuilder.create( + trace.getLoc(), builder.getStringAttr("Trace_Control0"), + builder.getStringAttr("Trace_Stop_Event"), + builder.getI32IntegerAttr(stopEvent), + /*mask=*/nullptr, builder.getStringAttr("stop event")); + } + + // Emit mode if present. + // Memory trace does not expose Trace_Control0.Mode in the register DB. + if (auto modeOp = dyn_cast(op); modeOp && !isMem) { + configBuilder.create( + trace.getLoc(), builder.getStringAttr("Trace_Control0"), + builder.getStringAttr("Mode"), + builder.getI32IntegerAttr( + static_cast(modeOp.getMode())), + /*mask=*/nullptr, builder.getStringAttr("trace mode")); + } + + // Emit packet config if present + if (auto packetOp = dyn_cast(op)) { + configBuilder.create( + trace.getLoc(), builder.getStringAttr("Trace_Control1"), + builder.getStringAttr("ID"), + builder.getI32IntegerAttr(packetOp.getId()), + /*mask=*/nullptr, builder.getStringAttr("packet ID")); + + configBuilder.create( + trace.getLoc(), builder.getStringAttr("Trace_Control1"), + builder.getStringAttr("Packet_Type"), + builder.getI32IntegerAttr( + static_cast(packetOp.getType())), + /*mask=*/nullptr, builder.getStringAttr("packet type")); + } + } + + // 2. Emit port configurations (Stream_Switch_Event_Port_Selection_0/1) + for (auto &op : trace.getBody().getOps()) { + if (auto portOp = dyn_cast(op)) { + uint32_t slot = portOp.getSlot(); + + // Determine which register based on slot + StringRef registerName = (slot < 4) + ? "Stream_Switch_Event_Port_Selection_0" + : "Stream_Switch_Event_Port_Selection_1"; + + // Generate field names + std::string idFieldName = "Port_" + std::to_string(slot) + "_ID"; + std::string masterSlaveFieldName = + "Port_" + std::to_string(slot) + "_Master_Slave"; + + // Generate port value string "PORT:CHANNEL" + std::string portValue = stringifyWireBundle(portOp.getPort()).str() + + ":" + std::to_string(portOp.getChannel()); + + // Convert DMAChannelDir to master flag: S2MM=master(1), MM2S=slave(0) + int masterSlaveValue = + (portOp.getDirection() == DMAChannelDir::S2MM) ? 1 : 0; + + // Emit Port_N_ID field + configBuilder.create( + portOp.getLoc(), builder.getStringAttr(registerName), + builder.getStringAttr(idFieldName), + builder.getStringAttr(portValue), // "NORTH:1" format + /*mask=*/nullptr, + builder.getStringAttr("port " + std::to_string(slot) + " ID")); + + // Emit Port_N_Master_Slave field + configBuilder.create( + portOp.getLoc(), builder.getStringAttr(registerName), + builder.getStringAttr(masterSlaveFieldName), + builder.getI32IntegerAttr(masterSlaveValue), + /*mask=*/nullptr, + builder.getStringAttr("port " + std::to_string(slot) + + " master/slave")); + } + } + + // 3. Emit event slots (Trace_Event0 / Trace_Event1) + SmallVector events; + for (auto &op : trace.getBody().getOps()) { + if (auto eventOp = dyn_cast(op)) { + events.push_back(eventOp); + } + } + + for (size_t i = 0; i < events.size() && i < 8; ++i) { + std::string eventName = events[i].getEvent().getEventName(); + + // If enum, use enum value directly; otherwise lookup by name + std::optional eventNum; + + if (auto enumVal = events[i].getEvent().getEnumValue()) { + eventNum = static_cast(*enumVal); + } else { + eventNum = targetModel.lookupEvent(eventName, tileID, isMem); + } + + if (!eventNum) { + events[i].emitError("unknown trace event '") << eventName << "'"; + return signalPassFailure(); + } + + // Determine which register and field + StringRef registerName = (i < 4) ? "Trace_Event0" : "Trace_Event1"; + std::string fieldName = "Trace_Event" + std::to_string(i); + + // Emit register write with event number as integer + configBuilder.create( + trace.getLoc(), builder.getStringAttr(registerName), + builder.getStringAttr(fieldName), + events[i].getEvent(), // builder.getI32IntegerAttr(*eventNum), + /*mask=*/nullptr, builder.getStringAttr(eventName)); + } + + // Add terminator + configBuilder.create(trace.getLoc()); + + // Update all trace.start_config references + device.walk([&](TraceStartConfigOp startConfig) { + if (startConfig.getTraceConfig() == trace.getSymName()) { + startConfig.setTraceConfigAttr( + SymbolRefAttr::get(builder.getContext(), configName)); + } + }); + + // Remove original trace op + trace.erase(); + } + } +}; + +} // namespace + +std::unique_ptr> +xilinx::AIE::createAIETraceToConfigPass() { + return std::make_unique(); +} + +//===----------------------------------------------------------------------===// +// AIETraceRegPackWritesPass - Pack multiple register field writes +//===----------------------------------------------------------------------===// + +namespace { + +struct AIETraceRegPackWritesPass + : AIETraceRegPackWritesBase { + void runOnOperation() override { + DeviceOp device = getOperation(); + const auto &targetModel = device.getTargetModel(); + + // Process each trace config + device.walk([&](TraceConfigOp configOp) { + // Determine module based on tile type and packet type + auto tile = cast(configOp.getTile().getDefiningOp()); + + // Get packet type to determine if this is memory or core trace + bool isMem = false; + if (auto packetType = configOp.getPacketType()) { + isMem = (*packetType == TracePacketType::Mem); + } + + // Phase 1: Convert field+value to mask+shifted_value + SmallVector regsToConvert; + for (auto &op : configOp.getBody().front()) { + if (auto regOp = dyn_cast(op)) { + if (regOp.getField() && !regOp.getMask()) { + regsToConvert.push_back(regOp); + } + } + } + + OpBuilder builder(&configOp.getBody().front(), + configOp.getBody().front().begin()); + + for (auto regOp : regsToConvert) { + // Look up register and field information + TileID tileID = {tile.getCol(), tile.getRow()}; + const RegisterInfo *regInfo = + targetModel.lookupRegister(regOp.getRegName(), tileID, isMem); + + if (!regInfo) { + regOp.emitError("Register not found in database: ") + << regOp.getRegName(); + return signalPassFailure(); + } + + const BitFieldInfo *fieldInfo = regInfo->getField(*regOp.getField()); + if (!fieldInfo) { + regOp.emitError("Field not found in register: ") + << *regOp.getField() << " in " << regOp.getRegName(); + return signalPassFailure(); + } + + // Get the value - handle both integers and port strings + uint32_t value = 0; + Attribute valAttr = regOp.getValue(); + if (auto traceEventAttr = dyn_cast(valAttr)) { + if (auto enumVal = traceEventAttr.getEnumValue()) { + value = static_cast(*enumVal); + valAttr = builder.getI32IntegerAttr(value); + } else { + std::string eventName = traceEventAttr.getEventName(); + std::optional eventNum = + targetModel.lookupEvent(eventName, tileID, isMem); + if (!eventNum) { + regOp.emitError("unknown trace event '") << eventName << "'"; + return signalPassFailure(); + } + value = *eventNum; + valAttr = builder.getI32IntegerAttr(value); + } + } + if (auto intAttr = dyn_cast(valAttr)) { + // Integer value + value = intAttr.getInt(); + } else if (auto strAttr = dyn_cast(valAttr)) { + // String value - check if it's a port specification + StringRef valueStr = strAttr.getValue(); + + // Determine master/slave from field name + // If field name contains "Master_Slave", this is not a port ID field + // Port ID fields are named "Port_N_ID" + bool isMasterSlaveField = + fieldInfo->name.find("Master_Slave") != std::string::npos; + + if (!isMasterSlaveField && valueStr.contains(':')) { + // This looks like "PORT:CHANNEL" format + // We need master/slave info - look for corresponding Master_Slave + // field + bool master = false; // Default to slave + + // Derive the port slot prefix from the current field name, e.g.: + // "Port_1_ID" -> "Port_1" + StringRef fieldName(fieldInfo->name); + StringRef portSlotPrefix; + if (fieldName.starts_with("Port_")) { + size_t idSuffixPos = fieldName.find("_ID"); + if (idSuffixPos != std::string::npos) + portSlotPrefix = fieldName.take_front(idSuffixPos); + } + // Search for companion Master_Slave field in same register and, + // when possible, for the same port slot. + for (auto &siblingOp : configOp.getBody().front()) { + if (auto siblingReg = dyn_cast(siblingOp)) { + if (siblingReg.getRegName() != regOp.getRegName() || + !siblingReg.getField()) + continue; + StringRef siblingFieldName = *siblingReg.getField(); + // If we could determine a slot prefix (e.g. "Port_1"), require + // the sibling to match that slot and contain "Master_Slave". + if (!portSlotPrefix.empty()) { + if (!siblingFieldName.starts_with(portSlotPrefix) || + !siblingFieldName.contains("Master_Slave")) + continue; + } else { + // Fallback: match any Master_Slave field in this register. + if (!siblingFieldName.contains("Master_Slave")) + continue; + } + // Found companion field - extract master flag + if (auto siblingInt = + dyn_cast(siblingReg.getValue())) { + master = (siblingInt.getInt() != 0); + } + break; + } + } + + // Resolve port value + auto portIndex = + targetModel.resolvePortValue(valueStr, tileID, master); + if (!portIndex) { + regOp.emitError("Failed to resolve port value: ") << valueStr; + return signalPassFailure(); + } + value = *portIndex; + } else { + regOp.emitError("Unsupported string value: ") << valueStr; + return signalPassFailure(); + } + } else { + regOp.emitError("Unsupported value type in pack pass"); + return signalPassFailure(); + } + + // Compute mask and shifted value + auto mask = targetModel.getFieldMask(*fieldInfo); + if (!mask) { + regOp.emitError("Invalid field bit range for register write: ") + << regOp.getRegName() << "." << fieldInfo->name << " [" + << fieldInfo->bit_start << ":" << fieldInfo->bit_end + << "], width=" << fieldInfo->getWidth(); + return signalPassFailure(); + } + uint32_t shiftedValue = targetModel.encodeFieldValue(*fieldInfo, value); + // Create new operation with mask + builder.setInsertionPoint(regOp); + builder.create(regOp.getLoc(), regOp.getRegNameAttr(), + nullptr, // no field + builder.getI32IntegerAttr(shiftedValue), + builder.getI32IntegerAttr(*mask), + regOp.getCommentAttr()); + + // Remove old operation + regOp.erase(); + } + + // Phase 2: Merge writes to same register with non-overlapping masks + bool changed = true; + while (changed) { + changed = false; + + // Collect all register writes + SmallVector regWrites; + for (TraceRegOp op : configOp.getBody().front().getOps()) { + if (op.getMask()) { + regWrites.push_back(op); + } + } + + // Try to merge pairs + for (size_t i = 0; i < regWrites.size() && !changed; ++i) { + for (size_t j = i + 1; j < regWrites.size() && !changed; ++j) { + TraceRegOp reg1 = regWrites[i]; + TraceRegOp reg2 = regWrites[j]; + + // Must be same register + if (reg1.getRegName() != reg2.getRegName()) + continue; + + auto mask1Attr = reg1.getMask(); + auto mask2Attr = reg2.getMask(); + if (!mask1Attr || !mask2Attr) + continue; + + uint32_t mask1 = *mask1Attr; + uint32_t mask2 = *mask2Attr; + + // Check for overlap + if (mask1 & mask2) { + reg1.emitError("Overlapping masks for register ") + << reg1.getRegName() << ": mask1=" << mask1 + << " mask2=" << mask2; + return signalPassFailure(); + } + + // Merge the two writes + auto value1Attr = dyn_cast(reg1.getValue()); + auto value2Attr = dyn_cast(reg2.getValue()); + if (!value1Attr || !value2Attr) { + reg1.emitError( + "Expected integer values for packed register writes"); + return signalPassFailure(); + } + uint32_t value1 = value1Attr.getInt(); + uint32_t value2 = value2Attr.getInt(); + uint32_t mergedValue = value1 | value2; + uint32_t mergedMask = mask1 | mask2; + + // Create merged operation + builder.setInsertionPoint(reg1); + std::string comment; + if (reg1.getComment()) + comment += reg1.getComment()->str(); + if (reg2.getComment()) { + if (!comment.empty()) + comment += " + "; + comment += reg2.getComment()->str(); + } + + builder.create( + reg1.getLoc(), reg1.getRegNameAttr(), nullptr, + builder.getI32IntegerAttr(mergedValue), + builder.getI32IntegerAttr(mergedMask), + comment.empty() ? nullptr : builder.getStringAttr(comment)); + + // Remove both old operations + reg1.erase(); + reg2.erase(); + changed = true; + } + } + } + }); + } +}; + +} // namespace + +std::unique_ptr> +xilinx::AIE::createAIETraceRegPackWritesPass() { + return std::make_unique(); +} diff --git a/lib/Dialect/AIE/Transforms/CMakeLists.txt b/lib/Dialect/AIE/Transforms/CMakeLists.txt index a8021411e40..89ed2ae12df 100644 --- a/lib/Dialect/AIE/Transforms/CMakeLists.txt +++ b/lib/Dialect/AIE/Transforms/CMakeLists.txt @@ -25,6 +25,7 @@ add_mlir_dialect_library( AIEObjectFifoRegisterProcess.cpp AIELowerCascadeFlows.cpp AIEGenerateColumnControlOverlay.cpp + AIETraceToConfig.cpp ADDITIONAL_HEADER_DIRS ${AIE_BINARY_DIR}/include diff --git a/lib/Dialect/AIEX/Transforms/AIEInlineTraceConfig.cpp b/lib/Dialect/AIEX/Transforms/AIEInlineTraceConfig.cpp new file mode 100644 index 00000000000..30912becc05 --- /dev/null +++ b/lib/Dialect/AIEX/Transforms/AIEInlineTraceConfig.cpp @@ -0,0 +1,125 @@ +//===- AIEInlineTraceConfig.cpp ---------------------------------*- C++ -*-===// +// +// This file is licensed under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +// Copyright (C) 2025, Advanced Micro Devices, Inc. +// +//===----------------------------------------------------------------------===// +// Pass to inline trace.start_config and generate npu.write32 +//===----------------------------------------------------------------------===// + +#include "aie/Dialect/AIE/IR/AIEDialect.h" +#include "aie/Dialect/AIEX/IR/AIEXDialect.h" +#include "aie/Dialect/AIEX/Transforms/AIEXPasses.h" + +#include "mlir/IR/Attributes.h" +#include "mlir/Pass/Pass.h" + +using namespace mlir; +using namespace xilinx; +using namespace xilinx::AIE; +using namespace xilinx::AIEX; + +namespace { + +struct AIEInlineTraceConfigPass + : AIEXInlineTraceConfigBase { + void runOnOperation() override { + AIE::DeviceOp device = getOperation(); + const auto &targetModel = device.getTargetModel(); + + // Collect all trace.start_config operations + SmallVector startConfigs; + device.walk([&](TraceStartConfigOp startConfig) { + startConfigs.push_back(startConfig); + }); + + for (auto startConfig : startConfigs) { + OpBuilder builder(startConfig); + + // Lookup the trace config symbol + auto configSymbolName = startConfig.getTraceConfig(); + auto configOp = + dyn_cast_or_null(SymbolTable::lookupNearestSymbolFrom( + device, builder.getStringAttr(configSymbolName))); + + if (!configOp) { + startConfig.emitError("trace config symbol '") + << configSymbolName << "' not found"; + return signalPassFailure(); + } + + // Get tile and extract col/row + auto tile = configOp.getTile(); + auto tileOp = dyn_cast(tile.getDefiningOp()); + if (!tileOp) { + startConfig.emitError("tile operand must be a TileOp"); + return signalPassFailure(); + } + + int col = tileOp.getCol(); + int row = tileOp.getRow(); + TileID tileID = {col, row}; + + // Determine if we're accessing memory module from packet type + bool isMem = false; + if (configOp.getPacketType()) { + isMem = (*configOp.getPacketType() == TracePacketType::Mem); + } + + // Process all trace.reg operations in the config + for (auto &op : configOp.getBody().getOps()) { + auto regOp = dyn_cast(op); + if (!regOp) + continue; + + // After packing, field should not be present + if (regOp.getField()) { + regOp.emitError("aie.trace.reg still has field attribute - run " + "-aie-trace-pack-reg-writes pass first"); + return signalPassFailure(); + } + + // Look up register to get offset + auto regName = regOp.getRegName().str(); + const RegisterInfo *regInfo = + targetModel.lookupRegister(regName, tileID, isMem); + if (!regInfo) { + regOp.emitError("Register '") << regName << "' not found for tile (" + << col << ", " << row << ")"; + return signalPassFailure(); + } + + // Extract value (mask is discarded) + uint32_t value = 0; + if (auto intAttr = llvm::dyn_cast(regOp.getValue())) { + value = intAttr.getInt(); + } else { + regOp.emitError("value must be an integer after packing"); + return signalPassFailure(); + } + + // Generate aiex.npu.write32 operation with col/row + builder.create( + regOp.getLoc(), builder.getUI32IntegerAttr(regInfo->offset), + builder.getUI32IntegerAttr(value), + nullptr, // buffer + builder.getI32IntegerAttr(col), // column + builder.getI32IntegerAttr(row) // row + ); + } + + // Remove the start_config invocation + startConfig.erase(); + } + } +}; + +} // namespace + +std::unique_ptr> +xilinx::AIEX::createAIEXInlineTraceConfigPass() { + return std::make_unique(); +} diff --git a/lib/Dialect/AIEX/Transforms/CMakeLists.txt b/lib/Dialect/AIEX/Transforms/CMakeLists.txt index 3b4cbf00176..fb205cb4048 100644 --- a/lib/Dialect/AIEX/Transforms/CMakeLists.txt +++ b/lib/Dialect/AIEX/Transforms/CMakeLists.txt @@ -25,6 +25,7 @@ add_mlir_dialect_library(AIEXTransforms AIETransformBfpTypes.cpp AIETxnToControlPacket.cpp AIEExpandLoadPdi.cpp + AIEInlineTraceConfig.cpp ADDITIONAL_HEADER_DIRS ${AIE_BINARY_DIR}/include diff --git a/programming_examples/basic/event_trace/CMakeLists.txt b/programming_examples/basic/event_trace/CMakeLists.txt new file mode 100644 index 00000000000..a1409034ae8 --- /dev/null +++ b/programming_examples/basic/event_trace/CMakeLists.txt @@ -0,0 +1,68 @@ +# This file is licensed under the Apache License v2.0 with LLVM Exceptions. +# See https://llvm.org/LICENSE.txt for license information. +# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +# +# Copyright (C) 2026, Advanced Micro Devices, Inc. + +# parameters +# -DXRT_INC_DIR: Full path to src/runtime_src/core/include in XRT cloned repo +# -DXRT_LIB_DIR: Path to xrt_coreutil.lib +# -DTARGET_NAME: Target name to be built + +# cmake needs this line +cmake_minimum_required(VERSION 3.30) +set(CMAKE_CXX_STANDARD 23) +set(CMAKE_CXX_STANDARD_REQUIRED YES) + +include(../../common.cmake) + +find_program(WSL NAMES powershell.exe) + +if (NOT WSL) + set(CMAKE_C_COMPILER gcc-13) + set(CMAKE_CXX_COMPILER g++-13) + set(XRT_INC_DIR /opt/xilinx/xrt/include CACHE STRING "Path to XRT cloned repo") + set(XRT_LIB_DIR /opt/xilinx/xrt/lib CACHE STRING "Path to xrt_coreutil.lib") +else() + set(XRT_INC_DIR C:/Technical/XRT/src/runtime_src/core/include CACHE STRING "Path to XRT cloned repo") + set(XRT_LIB_DIR C:/Technical/xrtNPUfromDLL CACHE STRING "Path to xrt_coreutil.lib") +endif() + +set(IN1_SIZE 16384 CACHE STRING "in1 buffer size") +set(IN2_SIZE 4 CACHE STRING "in2 buffer size") +set(OUT_SIZE 16384 CACHE STRING "out buffer size") +set(TARGET_NAME mlir_trace_example CACHE STRING "Target to be built") + +SET (ProjectName ${TARGET_NAME}) +SET (currentTarget ${TARGET_NAME}) + +if ( WSL ) + set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_RELEASE ${CMAKE_BINARY_DIR}) + add_compile_options(/Zc:__cplusplus) +endif () + +project(${ProjectName}) + +add_executable(${currentTarget} + test.cpp +) + +target_compile_definitions(${currentTarget} PUBLIC + IN1_SIZE=${IN1_SIZE} + IN2_SIZE=${IN2_SIZE} + OUT_SIZE=${OUT_SIZE} +) + +target_include_directories (${currentTarget} PUBLIC + ${XRT_INC_DIR} +) + +target_link_directories(${currentTarget} PUBLIC + ${XRT_LIB_DIR} +) + +target_link_libraries(${currentTarget} PUBLIC + xrt_coreutil +) + +target_link_test_utils(${currentTarget}) diff --git a/programming_examples/basic/event_trace/Makefile b/programming_examples/basic/event_trace/Makefile new file mode 100644 index 00000000000..96a1d7b73f6 --- /dev/null +++ b/programming_examples/basic/event_trace/Makefile @@ -0,0 +1,118 @@ +##===- Makefile -----------------------------------------------------------===## +# +# This file licensed under the Apache License v2.0 with LLVM Exceptions. +# See https://llvm.org/LICENSE.txt for license information. +# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +# +# Copyright (C) 2026, Advanced Micro Devices, Inc. +# +##===----------------------------------------------------------------------===## +# +# Makefile for MLIR trace example +# +##===----------------------------------------------------------------------===## + +srcdir := $(shell dirname $(realpath $(firstword $(MAKEFILE_LIST)))) + +include ${srcdir}/../../makefile-common + +devicename ?= npu +targetname = mlir_trace_example + +in1_size = 16384 # in bytes +in2_size = 4 # in bytes, should always be 4 (1x int32) +out_size = 16384 # in bytes, should always be equal to in1_size +trace_size = 8192 + +all: run_trace_py + +build/insts.bin: build/final.xclbin + +# Compile the AIE kernel +build/scale.o: ${srcdir}/vector_scalar_mul.cc + mkdir -p ${@D} +ifeq ($(devicename),npu) + cd ${@D} && ${PEANO_INSTALL_DIR}/bin/clang++ ${PEANOWRAP2_FLAGS} -c $< -o ${@F} +else ifeq ($(devicename),npu2) + cd ${@D} && ${PEANO_INSTALL_DIR}/bin/clang++ ${PEANOWRAP2P_FLAGS} -c $< -o ${@F} +else + echo "Device type not supported" +endif + +# Preprocess the MLIR: substitute NPUDEVICE with the target device string +build/aie_trace.mlir: ${srcdir}/aie_trace.mlir + mkdir -p ${@D} + sed 's/NPUDEVICE/${devicename}_1col/g' $< > $@ + +# Generate MLIR from Python +build/aie_trace_from_py.mlir: ${srcdir}/aie_trace.py + mkdir -p ${@D} + python3 $< > $@ + +# Build xclbin from the mlir +build/final.xclbin: build/aie_trace.mlir build/scale.o + mkdir -p ${@D} + cd ${@D} && aiecc.py --aie-generate-xclbin --no-compile-host --xclbin-name=${@F} \ + --no-xchesscc --no-xbridge \ + --aie-generate-npu-insts --npu-insts-name=insts.bin aie_trace.mlir + +# Build xclbin from Python-generated mlir +build/final_py.xclbin: build/aie_trace_from_py.mlir build/scale.o + mkdir -p ${@D} + cd ${@D} && aiecc.py --aie-generate-xclbin --no-compile-host --xclbin-name=${@F} \ + --no-xchesscc --no-xbridge \ + --aie-generate-npu-insts --npu-insts-name=insts_py.bin aie_trace_from_py.mlir + +build/insts.bin: build/final.xclbin + +# Build the host executable +${targetname}.exe: ${srcdir}/test.cpp + rm -rf _build + mkdir -p _build + cd _build && ${powershell} cmake ${srcdir} -DTARGET_NAME=${targetname} -DIN1_SIZE=${in1_size} -DIN2_SIZE=${in2_size} -DOUT_SIZE=${out_size} + cd _build && ${powershell} cmake --build . --config Release +ifeq "${powershell}" "powershell.exe" + cp _build/${targetname}.exe $@ +else + cp _build/${targetname} $@ +endif + +# Build and run with the new trace syntax +run_trace: ${targetname}.exe build/final.xclbin build/insts.bin + @echo "Running with declarative trace syntax..." + ${powershell} ./$< -x build/final.xclbin -i build/insts.bin -k MLIR_AIE -t ${trace_size} + ${srcdir}/../../../python/utils/trace/parse.py --input trace.txt --mlir build/aie_trace.mlir.prj/input_physical_with_elfs.mlir --output trace.json + ${srcdir}/../../../python/utils/trace/get_trace_summary.py --input trace.json + + @echo "" + @echo "Generating trace visualization..." + python3 ${srcdir}/visualize_trace.py -i trace.json -o trace_timeline.png -t "Declarative Trace" + @echo "Trace visualization saved to trace_timeline.png" + +# Run Python version with new trace syntax +run_trace_py: build/final.xclbin build/insts.bin + @echo "Running Python test with declarative trace syntax..." + python3 ${srcdir}/test.py --xclbin build/final.xclbin --instr build/insts.bin --kernel MLIR_AIE --verbosity 1 --trace-sz ${trace_size} --trace-file trace.txt + ${srcdir}/../../../python/utils/trace/parse.py --input trace.txt --mlir build/aie_trace.mlir.prj/input_physical_with_elfs.mlir --output trace.json + ${srcdir}/../../../python/utils/trace/get_trace_summary.py --input trace.json + @echo "" + @echo "Generating trace visualization..." + python3 ${srcdir}/visualize_trace.py -i trace.json -o trace_timeline.png -t "Declarative Trace (Python)" + @echo "Trace visualization saved to trace_timeline.png" + +# Generate MLIR from Python and build/run +run_trace_from_py: ${targetname}.exe build/final_py.xclbin + @echo "Running with Python-generated declarative trace MLIR..." + ${powershell} ./$< -x build/final_py.xclbin -i build/insts_py.bin -k MLIR_AIE -t ${trace_size} + +# Just generate and print the MLIR from the Python bindings +generate_mlir_from_py: build/aie_trace_from_py.mlir + @cat $< + +clean_trace: + rm -rf tmpTrace trace.txt trace_timeline.png trace.json + +clean: clean_trace + rm -rf build _build ${targetname}*.exe + +.PHONY: all run_trace run_trace_py run_trace_from_py generate_mlir_from_py clean clean_trace diff --git a/programming_examples/basic/event_trace/README.md b/programming_examples/basic/event_trace/README.md new file mode 100644 index 00000000000..e6fde07f332 --- /dev/null +++ b/programming_examples/basic/event_trace/README.md @@ -0,0 +1,73 @@ +# MLIR Trace Example + +Standalone MLIR example for tracing on AMD NPU devices using a vector-scalar multiply kernel. + +## Contents + +- `aie_trace.mlir` - declarative trace configuration (`aie.trace`, `aie.trace.event`, `aie.trace.start_config`) +- `vector_scalar_mul.cc` - AIE kernel +- `test.cpp` / `test.py` - host runners +- `visualize_trace.py` - renders a PNG timeline from parsed trace JSON +- `run_makefile.lit` / `run_strix_makefile.lit` - lit test definitions for NPU1 and NPU2 + +### Run with trace + +```bash +make run_trace +``` + +### Run with trace (Python) + +```bash +make run_trace_py +``` + +## Outputs + +After `make run_trace` or `make run_trace_py`: +- `trace.txt` - raw trace dump +- `trace.json` - parsed trace events +- `trace_timeline.png` - timeline visualization + +## Declarative trace syntax + +From `aie_trace.mlir`: + +```mlir +aie.trace @core_trace(%tile_0_2) { + aie.trace.mode "Event-Time" + aie.trace.packet id=1 type=core + aie.trace.event<"INSTR_EVENT_0"> + aie.trace.event<"INSTR_EVENT_1"> + aie.trace.port<0> port=DMA channel=0 direction=S2MM + aie.trace.start event=<"BROADCAST_15"> + aie.trace.stop event=<"BROADCAST_14"> +} + +aie.runtime_sequence(...) { + aie.trace.start_config @core_trace +} +``` + +Compiler lowering pipeline for declarative trace: +1. `-aie-trace-to-config` +2. `-aie-trace-pack-reg-writes` +3. `-aie-inline-trace-config` + +Inspect intermediate IR: + +```bash +aie-opt -aie-trace-to-config aie_trace.mlir +aie-opt -aie-trace-to-config -aie-trace-pack-reg-writes aie_trace.mlir +aie-opt -aie-trace-to-config -aie-trace-pack-reg-writes -aie-inline-trace-config aie_trace.mlir +``` + +## Example Visualization + +Generate visualization from existing parsed output: + +```bash +python3 visualize_trace.py -i trace.json -o trace_timeline.png -t "Trace Timeline" +``` + +Copyright 2026 Advanced Micro Devices, Inc. \ No newline at end of file diff --git a/programming_examples/basic/event_trace/aie_trace.mlir b/programming_examples/basic/event_trace/aie_trace.mlir new file mode 100644 index 00000000000..a69d7b567dc --- /dev/null +++ b/programming_examples/basic/event_trace/aie_trace.mlir @@ -0,0 +1,252 @@ +//===- aie_trace.mlir ------------------------------------------*- MLIR -*-===// +// +// This file is licensed under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +// Copyright (C) 2026, Advanced Micro Devices, Inc. +// +//===----------------------------------------------------------------------===// +// +// This example uses: +// - aie.trace operation for declarative trace configuration +// - aie.trace.event for specifying events to capture +// - aie.trace.start_config in runtime sequence +// +// The passes aie-trace-to-config and aie-inline-trace-config will lower this. +// +//===----------------------------------------------------------------------===// + +module { + aie.device(npu1_1col) { + // External kernel function declaration + func.func private @vector_scalar_mul_aie_scalar(memref<1024xi32>, memref<1024xi32>, memref<1xi32>, i32) + + // Tile declarations + %shim_noc_tile_0_0 = aie.tile(0, 0) + %tile_0_2 = aie.tile(0, 2) + + // ObjectFIFOs for data movement + aie.objectfifo @in(%shim_noc_tile_0_0, {%tile_0_2}, 2 : i32) : !aie.objectfifo> + aie.objectfifo @infactor(%shim_noc_tile_0_0, {%tile_0_2}, 2 : i32) : !aie.objectfifo> + aie.objectfifo @out(%tile_0_2, {%shim_noc_tile_0_0}, 2 : i32) : !aie.objectfifo> + + // Core computation + %core_0_2 = aie.core(%tile_0_2) { + %c0 = arith.constant 0 : index + %c9223372036854775807 = arith.constant 9223372036854775807 : index + %c1 = arith.constant 1 : index + scf.for %arg0 = %c0 to %c9223372036854775807 step %c1 { + %0 = aie.objectfifo.acquire @infactor(Consume, 1) : !aie.objectfifosubview> + %1 = aie.objectfifo.subview.access %0[0] : !aie.objectfifosubview> -> memref<1xi32> + %c0_0 = arith.constant 0 : index + %c4 = arith.constant 4 : index + %c1_1 = arith.constant 1 : index + scf.for %arg1 = %c0_0 to %c4 step %c1_1 { + %2 = aie.objectfifo.acquire @out(Produce, 1) : !aie.objectfifosubview> + %3 = aie.objectfifo.subview.access %2[0] : !aie.objectfifosubview> -> memref<1024xi32> + %4 = aie.objectfifo.acquire @in(Consume, 1) : !aie.objectfifosubview> + %5 = aie.objectfifo.subview.access %4[0] : !aie.objectfifosubview> -> memref<1024xi32> + %c1024_i32 = arith.constant 1024 : i32 + func.call @vector_scalar_mul_aie_scalar(%5, %3, %1, %c1024_i32) : (memref<1024xi32>, memref<1024xi32>, memref<1xi32>, i32) -> () + aie.objectfifo.release @in(Consume, 1) + aie.objectfifo.release @out(Produce, 1) + } + aie.objectfifo.release @infactor(Consume, 1) + } + aie.end + } {link_with = "scale.o"} + + // ======================================================================== + // TRACE CONFIGURATION + // ======================================================================== + + // Trace configuration for compute tile (0,2) - core events + aie.trace @core_trace(%tile_0_2) { + // Set trace mode (Event-Time captures timestamps) + aie.trace.mode "Event-Time" + + // Configure packet routing (ID and type for packet-switched routing) + aie.trace.packet id=1 type=core + + // Specify which events to capture (up to 8 events) + aie.trace.event<"INSTR_EVENT_0"> // User event 0 (start marker) + aie.trace.event<"INSTR_EVENT_1"> // User event 1 (end marker) + aie.trace.event<"INSTR_VECTOR"> // Vector instructions + aie.trace.event<"MEMORY_STALL"> // Memory access stalls + aie.trace.event<"STREAM_STALL"> // Stream buffer stalls + aie.trace.event<"LOCK_STALL"> // Lock acquisition stalls + aie.trace.event<"PORT_RUNNING_1"> // DMA:0 slave port running + aie.trace.event<"PORT_IDLE_1"> // DMA:1 master port running + aie.trace.port<0> port=DMA channel=0 direction=S2MM + aie.trace.port<1> port=DMA channel=0 direction=MM2S + + // Specify start/stop control (broadcast events) + aie.trace.start event=<"BROADCAST_15"> + aie.trace.stop event=<"BROADCAST_14"> + } + + // Trace configuration for compute tile (0,2) - memory events + aie.trace @mem_trace(%tile_0_2) { + // Set trace mode (Event-Time captures timestamps) + aie.trace.mode "Event-Time" + + // Configure packet routing (ID and type for packet-switched routing) + aie.trace.packet id=3 type=mem + + // Specify which events to capture (up to 8 events) + aie.trace.event<"DMA_S2MM_0_START_TASK"> + aie.trace.event<"DMA_S2MM_1_START_TASK"> + aie.trace.event<"DMA_MM2S_0_START_TASK"> + aie.trace.event<"DMA_S2MM_0_FINISHED_TASK"> + aie.trace.event<"DMA_S2MM_1_FINISHED_TASK"> + aie.trace.event<"DMA_MM2S_0_FINISHED_TASK"> + aie.trace.event<"DMA_S2MM_0_STREAM_STARVATION"> + aie.trace.event<"DMA_S2MM_1_STREAM_STARVATION"> + + // Specify start/stop control (broadcast events) + aie.trace.start event=<"BROADCAST_15"> + aie.trace.stop event=<"BROADCAST_14"> + } + + // Trace configuration for shim tile (0,0) + // Captures DMA activity at the interface to DDR + aie.trace @shim_trace(%shim_noc_tile_0_0) { + aie.trace.packet id=2 type=shimtile + + // Shim DMA events + aie.trace.event<"DMA_S2MM_0_START_TASK"> + aie.trace.event<"DMA_S2MM_1_START_TASK"> + aie.trace.event<"DMA_MM2S_0_START_TASK"> + aie.trace.event<"DMA_S2MM_0_FINISHED_TASK"> + aie.trace.event<"DMA_S2MM_1_FINISHED_TASK"> + aie.trace.event<"DMA_MM2S_0_FINISHED_TASK"> + aie.trace.event<"DMA_S2MM_0_STREAM_STARVATION"> + aie.trace.event<"DMA_S2MM_1_STREAM_STARVATION"> + + aie.trace.start event=<"TRUE"> + aie.trace.stop event=<"NONE"> + } + + // Packet flows to route trace data (same as before) + // These define the routing but the trace config is separate + aie.packet_flow(1) { + aie.packet_source<%tile_0_2, Trace : 0> + aie.packet_dest<%shim_noc_tile_0_0, DMA : 1> + } {keep_pkt_header = true} + aie.packet_flow(3) { + aie.packet_source<%tile_0_2, Trace : 1> + aie.packet_dest<%shim_noc_tile_0_0, DMA : 1> + } {keep_pkt_header = true} + + aie.packet_flow(2) { + aie.packet_source<%shim_noc_tile_0_0, Trace : 0> + aie.packet_dest<%shim_noc_tile_0_0, DMA : 1> + } {keep_pkt_header = true} + + // ======================================================================== + // RUNTIME SEQUENCE WITH TRACE ACTIVATION + // ======================================================================== + + // Runtime sequence with trace configuration + aie.runtime_sequence(%arg0: memref<4096xi32>, %arg1: memref<1xi32>, %arg2: memref<4096xi32>) { + + // ======================================================================== + // TRACE INITIALIZATION + // ======================================================================== + + // Start trace configuration for core tile + // This will be lowered to the aiex.npu.write32 operations automatically + aie.trace.start_config @core_trace + aie.trace.start_config @mem_trace + + // Start trace configuration for shim tile + aie.trace.start_config @shim_trace + + // Address 212992 (0x34000): Timer_Control + aiex.npu.write32 {address = 212992 : ui32, column = 0 : i32, row = 2 : i32, value = 31232 : ui32} + + // Configure trace buffer descriptor (still manual for now) + aiex.npu.writebd { + bd_id = 15 : i32, + buffer_length = 8192 : i32, + buffer_offset = 0 : i32, + burst_length = 64 : i32, + column = 0 : i32, + d0_size = 0 : i32, + d0_stride = 0 : i32, + d0_zero_after = 0 : i32, + d0_zero_before = 0 : i32, + d1_size = 0 : i32, + d1_stride = 0 : i32, + d1_zero_after = 0 : i32, + d1_zero_before = 0 : i32, + d2_size = 0 : i32, + d2_stride = 0 : i32, + d2_zero_after = 0 : i32, + d2_zero_before = 0 : i32, + enable_packet = 1 : i32, + iteration_current = 0 : i32, + iteration_size = 0 : i32, + iteration_stride = 0 : i32, + lock_acq_enable = 0 : i32, + lock_acq_id = 0 : i32, + lock_acq_val = 0 : i32, + lock_rel_id = 0 : i32, + lock_rel_val = 0 : i32, + next_bd = 0 : i32, + out_of_order_id = 0 : i32, + packet_id = 0 : i32, + packet_type = 0 : i32, + row = 0 : i32, + use_next_bd = 0 : i32, + valid_bd = 1 : i32 + } + + // Patch trace buffer address + aiex.npu.address_patch {addr = 119268 : ui32, arg_idx = 4 : i32, arg_plus = 0 : i32} + + // Configure DMA channel for trace + aiex.npu.maskwrite32 {address = 119304 : ui32, column = 0 : i32, mask = 7936 : ui32, row = 0 : i32, value = 3840 : ui32} + aiex.npu.write32 {address = 119308 : ui32, column = 0 : i32, row = 0 : i32, value = 2147483663 : ui32} + + // Start trace control + aiex.npu.write32 {address = 212992 : ui32, column = 0 : i32, row = 0 : i32, value = 32512 : ui32} + aiex.npu.write32 {address = 213068 : ui32, column = 0 : i32, row = 0 : i32, value = 127 : ui32} + aiex.npu.write32 {address = 213000 : ui32, column = 0 : i32, row = 0 : i32, value = 127 : ui32} + + // ======================================================================== + // DATA TRANSFER CONFIGURATION + // ======================================================================== + + %0 = aiex.dma_configure_task_for @in { + aie.dma_bd(%arg0 : memref<4096xi32>, 0, 4096, [, , , ]) {burst_length = 0 : i32} + aie.end + } {issue_token = true} + + %1 = aiex.dma_configure_task_for @infactor { + aie.dma_bd(%arg1 : memref<1xi32>, 0, 1, [, , , ]) {burst_length = 0 : i32} + aie.end + } {issue_token = true} + + %2 = aiex.dma_configure_task_for @out { + aie.dma_bd(%arg2 : memref<4096xi32>, 0, 4096, [, , , ]) {burst_length = 0 : i32} + aie.end + } {issue_token = true} + + aiex.dma_start_task(%0) + aiex.dma_start_task(%1) + aiex.dma_start_task(%2) + aiex.dma_await_task(%0) + aiex.dma_await_task(%1) + aiex.dma_await_task(%2) + + // ======================================================================== + // TRACE COMPLETION + // ======================================================================== + + aiex.npu.write32 {address = 213064 : ui32, column = 0 : i32, row = 0 : i32, value = 126 : ui32} + aiex.npu.write32 {address = 213000 : ui32, column = 0 : i32, row = 0 : i32, value = 126 : ui32} + } + } +} diff --git a/programming_examples/basic/event_trace/aie_trace.py b/programming_examples/basic/event_trace/aie_trace.py new file mode 100644 index 00000000000..652412fa98c --- /dev/null +++ b/programming_examples/basic/event_trace/aie_trace.py @@ -0,0 +1,244 @@ +# aie_trace.py -*- Python -*- +# +# This file is licensed under the Apache License v2.0 with LLVM Exceptions. +# See https://llvm.org/LICENSE.txt for license information. +# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +# +# Copyright (C) 2026, Advanced Micro Devices, Inc. +# +# ===-----------------------------------------------------------------------===# +# +# Python equivalent of aie_trace.mlir using declarative trace ops: +# - aie.trace for trace configuration +# - aie.trace.event for specifying events to capture +# - aie.trace.start_config in runtime sequence +# +# The passes aie-trace-to-config and aie-inline-trace-config will lower this. +# +# Usage: +# python3 aie_trace.py > aie_trace_from_py.mlir +# +# ===-----------------------------------------------------------------------===# + +import sys +import numpy as np + +from aie.dialects.aie import * +from aie.dialects.aiex import * +from aie.extras.context import mlir_mod_ctx +from aie.iron.controlflow import range_ + + +def build_aie_trace(): + tensor_size = 4096 + tile_size = 1024 + num_sub_vectors = 4 + + @device(AIEDevice.npu1_1col) + def device_body(): + tile_ty = np.ndarray[(tile_size,), np.dtype[np.int32]] + scalar_ty = np.ndarray[(1,), np.dtype[np.int32]] + tensor_ty = np.ndarray[(tensor_size,), np.dtype[np.int32]] + + # External kernel function declaration + scale = external_func( + "vector_scalar_mul_aie_scalar", + inputs=[tile_ty, tile_ty, scalar_ty, np.int32], + ) + + # Tile declarations + shim_noc_tile_0_0 = tile(0, 0) + tile_0_2 = tile(0, 2) + + # ObjectFIFOs for data movement + of_in = object_fifo("in", shim_noc_tile_0_0, tile_0_2, 2, tile_ty) + of_factor = object_fifo("infactor", shim_noc_tile_0_0, tile_0_2, 2, scalar_ty) + of_out = object_fifo("out", tile_0_2, shim_noc_tile_0_0, 2, tile_ty) + + # Core computation + @core(tile_0_2, "scale.o") + def core_body(): + for _ in range_(sys.maxsize): + elem_factor = of_factor.acquire(ObjectFifoPort.Consume, 1) + for _ in range_(num_sub_vectors): + elem_out = of_out.acquire(ObjectFifoPort.Produce, 1) + elem_in = of_in.acquire(ObjectFifoPort.Consume, 1) + scale(elem_in, elem_out, elem_factor, tile_size) + of_in.release(ObjectFifoPort.Consume, 1) + of_out.release(ObjectFifoPort.Produce, 1) + of_factor.release(ObjectFifoPort.Consume, 1) + + # ================================================================== + # TRACE CONFIGURATION + # ================================================================== + + # Trace configuration for compute tile (0,2) - core events + @trace(tile_0_2, "core_trace") + def core_trace_body(): + trace_mode(TraceMode.EventTime) + trace_packet(1, TracePacketType.Core) + trace_event("INSTR_EVENT_0") + trace_event("INSTR_EVENT_1") + trace_event("INSTR_VECTOR") + trace_event("MEMORY_STALL") + trace_event("STREAM_STALL") + trace_event("LOCK_STALL") + trace_event("PORT_RUNNING_1") + trace_event("PORT_IDLE_1") + trace_port(0, WireBundle.DMA, 0, DMAChannelDir.S2MM) + trace_port(1, WireBundle.DMA, 0, DMAChannelDir.MM2S) + trace_start(event="BROADCAST_15") + trace_stop(event="BROADCAST_14") + + # Trace configuration for compute tile (0,2) - memory events + @trace(tile_0_2, "mem_trace") + def mem_trace_body(): + trace_mode(TraceMode.EventTime) + trace_packet(3, TracePacketType.Mem) + trace_event("DMA_S2MM_0_START_TASK") + trace_event("DMA_S2MM_1_START_TASK") + trace_event("DMA_MM2S_0_START_TASK") + trace_event("DMA_S2MM_0_FINISHED_TASK") + trace_event("DMA_S2MM_1_FINISHED_TASK") + trace_event("DMA_MM2S_0_FINISHED_TASK") + trace_event("DMA_S2MM_0_STREAM_STARVATION") + trace_event("DMA_S2MM_1_STREAM_STARVATION") + trace_start(event="BROADCAST_15") + trace_stop(event="BROADCAST_14") + + # Trace configuration for shim tile (0,0) + @trace(shim_noc_tile_0_0, "shim_trace") + def shim_trace_body(): + trace_packet(2, TracePacketType.ShimTile) + trace_event("DMA_S2MM_0_START_TASK") + trace_event("DMA_S2MM_1_START_TASK") + trace_event("DMA_MM2S_0_START_TASK") + trace_event("DMA_S2MM_0_FINISHED_TASK") + trace_event("DMA_S2MM_1_FINISHED_TASK") + trace_event("DMA_MM2S_0_FINISHED_TASK") + trace_event("DMA_S2MM_0_STREAM_STARVATION") + trace_event("DMA_S2MM_1_STREAM_STARVATION") + trace_start(event="TRUE") + trace_stop(event="NONE") + + # Packet flows to route trace data + packetflow( + 1, + tile_0_2, + WireBundle.Trace, + 0, + {"dest": shim_noc_tile_0_0, "port": WireBundle.DMA, "channel": 1}, + keep_pkt_header=True, + ) + packetflow( + 3, + tile_0_2, + WireBundle.Trace, + 1, + {"dest": shim_noc_tile_0_0, "port": WireBundle.DMA, "channel": 1}, + keep_pkt_header=True, + ) + packetflow( + 2, + shim_noc_tile_0_0, + WireBundle.Trace, + 0, + {"dest": shim_noc_tile_0_0, "port": WireBundle.DMA, "channel": 1}, + keep_pkt_header=True, + ) + + # ================================================================== + # RUNTIME SEQUENCE WITH TRACE ACTIVATION + # ================================================================== + + @runtime_sequence(tensor_ty, scalar_ty, tensor_ty) + def sequence(A, F, C): + # Trace initialization - applied by lowering passes + trace_start_config("core_trace") + trace_start_config("mem_trace") + trace_start_config("shim_trace") + + # Timer_Control (address 0x34000 = 212992) + npu_write32(column=0, row=2, address=212992, value=31232) + + # Configure trace buffer descriptor + npu_writebd( + bd_id=15, + buffer_length=8192, + buffer_offset=0, + burst_length=64, + column=0, + d0_size=0, + d0_stride=0, + d0_zero_after=0, + d0_zero_before=0, + d1_size=0, + d1_stride=0, + d1_zero_after=0, + d1_zero_before=0, + d2_size=0, + d2_stride=0, + d2_zero_after=0, + d2_zero_before=0, + enable_packet=1, + iteration_current=0, + iteration_size=0, + iteration_stride=0, + lock_acq_enable=0, + lock_acq_id=0, + lock_acq_val=0, + lock_rel_id=0, + lock_rel_val=0, + next_bd=0, + out_of_order_id=0, + packet_id=0, + packet_type=0, + row=0, + use_next_bd=0, + valid_bd=1, + ) + + # Patch trace buffer address + npu_address_patch(addr=119268, arg_idx=4, arg_plus=0) + + # Configure DMA channel for trace + npu_maskwrite32(address=119304, column=0, mask=7936, row=0, value=3840) + npu_write32(address=119308, column=0, row=0, value=2147483663) + + # Start trace control + npu_write32(address=212992, column=0, row=0, value=32512) + npu_write32(address=213068, column=0, row=0, value=127) + npu_write32(address=213000, column=0, row=0, value=127) + + # ============================================================== + # DATA TRANSFER CONFIGURATION + # ============================================================== + + in_task = shim_dma_single_bd_task( + of_in, A, sizes=[1, 1, 1, tensor_size], issue_token=True + ) + factor_task = shim_dma_single_bd_task( + of_factor, F, sizes=[1, 1, 1, 1], issue_token=True + ) + out_task = shim_dma_single_bd_task( + of_out, C, sizes=[1, 1, 1, tensor_size], issue_token=True + ) + + dma_start_task(in_task, factor_task, out_task) + dma_await_task(in_task, factor_task, out_task) + + # ============================================================== + # TRACE COMPLETION + # ============================================================== + + npu_write32(address=213064, column=0, row=0, value=126) + npu_write32(address=213000, column=0, row=0, value=126) + + +with mlir_mod_ctx() as ctx: + build_aie_trace() + res = ctx.module.operation.verify() + if res == True: + print(ctx.module) + else: + print(res) diff --git a/programming_examples/basic/event_trace/run_makefile.lit b/programming_examples/basic/event_trace/run_makefile.lit new file mode 100644 index 00000000000..3d292cff0fc --- /dev/null +++ b/programming_examples/basic/event_trace/run_makefile.lit @@ -0,0 +1,10 @@ +// (c) Copyright 2026 Advanced Micro Devices, Inc. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +// REQUIRES: ryzen_ai_npu1 +// +// RUN: mkdir -p test_npu1 +// RUN: cd test_npu1 +// RUN: %run_on_npu1% make -f %S/Makefile clean +// RUN: %run_on_npu1% make -f %S/Makefile run_trace +// RUN: %run_on_npu1% make -f %S/Makefile run_trace_py diff --git a/programming_examples/basic/event_trace/run_strix_makefile.lit b/programming_examples/basic/event_trace/run_strix_makefile.lit new file mode 100644 index 00000000000..167e70ae067 --- /dev/null +++ b/programming_examples/basic/event_trace/run_strix_makefile.lit @@ -0,0 +1,10 @@ +// (c) Copyright 2026 Advanced Micro Devices, Inc. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +// REQUIRES: ryzen_ai_npu2 +// +// RUN: mkdir -p test_npu2 +// RUN: cd test_npu2 +// RUN: %run_on_npu2% make -f %S/Makefile clean devicename=npu2 +// RUN: %run_on_npu2% make -f %S/Makefile run_trace devicename=npu2 +// RUN: %run_on_npu2% make -f %S/Makefile run_trace_py devicename=npu2 diff --git a/programming_examples/basic/event_trace/test.cpp b/programming_examples/basic/event_trace/test.cpp new file mode 100644 index 00000000000..0b87ba86e77 --- /dev/null +++ b/programming_examples/basic/event_trace/test.cpp @@ -0,0 +1,69 @@ +//===- test.cpp -------------------------------------------------*- C++ -*-===// +// +// This file is licensed under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +// Copyright (C) 2026, Advanced Micro Devices, Inc. +// +//===----------------------------------------------------------------------===// + +#include "xrt_test_wrapper.h" +#include + +#ifndef DATATYPES_USING_DEFINED +#define DATATYPES_USING_DEFINED +using DATATYPE_IN1 = std::int32_t; +using DATATYPE_IN2 = std::int32_t; +using DATATYPE_OUT = std::int32_t; +#endif + +// Initialize Input buffer 1 +void initialize_bufIn1(DATATYPE_IN1 *bufIn1, int SIZE) { + for (int i = 0; i < SIZE; i++) + bufIn1[i] = i + 1; +} + +// Initialize Input buffer 2 +void initialize_bufIn2(DATATYPE_IN2 *bufIn2, int SIZE) { + bufIn2[0] = 3; // scaleFactor +} + +// Initialize Output buffer +void initialize_bufOut(DATATYPE_OUT *bufOut, int SIZE) { + memset(bufOut, 0, SIZE); +} + +// Functional correctness verifier +int verify_vector_scalar_mul(DATATYPE_IN1 *bufIn1, DATATYPE_IN2 *bufIn2, + DATATYPE_OUT *bufOut, int SIZE, int verbosity) { + int errors = 0; + + for (int i = 0; i < SIZE; i++) { + int32_t ref = bufIn1[i] * bufIn2[0]; + int32_t test = bufOut[i]; + if (test != ref) { + if (verbosity >= 1) + std::cout << "Error in output " << test << " != " << ref << std::endl; + errors++; + } else { + if (verbosity >= 1) + std::cout << "Correct output " << test << " == " << ref << std::endl; + } + } + return errors; +} + +int main(int argc, const char *argv[]) { + constexpr int IN1_VOLUME = IN1_SIZE / sizeof(DATATYPE_IN1); + constexpr int IN2_VOLUME = IN2_SIZE / sizeof(DATATYPE_IN2); + constexpr int OUT_VOLUME = OUT_SIZE / sizeof(DATATYPE_OUT); + + args myargs = parse_args(argc, argv); + + int res = setup_and_run_aie( + IN1_VOLUME, IN2_VOLUME, OUT_VOLUME, myargs); + return res; +} diff --git a/programming_examples/basic/event_trace/test.py b/programming_examples/basic/event_trace/test.py new file mode 100755 index 00000000000..00a2de6a358 --- /dev/null +++ b/programming_examples/basic/event_trace/test.py @@ -0,0 +1,331 @@ +#!/usr/bin/env python3 +# ===- test.py ------------------------------------------------*- Python -*-===# +# +# This file is licensed under the Apache License v2.0 with LLVM Exceptions. +# See https://llvm.org/LICENSE.txt for license information. +# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +# +# Copyright (C) 2025, Advanced Micro Devices, Inc. +# +# ===-----------------------------------------------------------------------===# + +import argparse +import numpy as np +import time +import sys +from pathlib import Path + +try: + import pyxrt as xrt +except ImportError: + print("ERROR: Could not import pyxrt. Please install XRT Python bindings.") + sys.exit(1) + +# Buffer sizes +IN1_SIZE = 16384 # bytes +IN2_SIZE = 4 # bytes +OUT_SIZE = 16384 # bytes + +DATATYPE_IN1 = np.int32 +DATATYPE_IN2 = np.int32 +DATATYPE_OUT = np.int32 + +IN1_VOLUME = IN1_SIZE // np.dtype(DATATYPE_IN1).itemsize +IN2_VOLUME = IN2_SIZE // np.dtype(DATATYPE_IN2).itemsize +OUT_VOLUME = OUT_SIZE // np.dtype(DATATYPE_OUT).itemsize + + +def initialize_bufIn1(size): + """Initialize Input buffer 1""" + rng = np.random.default_rng(seed=42) + return rng.integers(1, 100, size=size, dtype=DATATYPE_IN1) + + +def initialize_bufIn2(size): + """Initialize Input buffer 2""" + buf = np.zeros(size, dtype=DATATYPE_IN2) + buf[0] = 3 # scaleFactor + return buf + + +def initialize_bufOut(size): + """Initialize Output buffer""" + return np.zeros(size, dtype=DATATYPE_OUT) + + +def verify_vector_scalar_mul(bufIn1, bufIn2, bufOut, size, verbosity=0): + """Functional correctness verifier""" + errors = 0 + + for i in range(size): + ref = bufIn1[i] * bufIn2[0] + test = bufOut[i] + if test != ref: + if verbosity >= 1: + print(f"Error in output {test} != {ref}") + errors += 1 + else: + if verbosity >= 1: + print(f"Correct output {test} == {ref}") + + return errors + + +def load_instr_binary(instr_file): + """Load instruction binary file""" + try: + with open(instr_file, "rb") as f: + instr_data = f.read() + # Convert to uint32 array + instr_v = np.frombuffer(instr_data, dtype=np.uint32) + return instr_v + except FileNotFoundError: + print(f"ERROR: Instruction file '{instr_file}' not found.") + sys.exit(1) + + +def write_out_trace(trace_buffer, trace_file): + """Write trace buffer to file""" + try: + # Filter out zero values and write as hex strings + out_str = "\n".join(f"{i:08x}" for i in trace_buffer if i != 0) + with open(trace_file, "w") as f: + f.write(out_str) + print(f"Trace written to {trace_file}") + except Exception as e: + print(f"ERROR: Could not write trace file: {e}") + + +def parse_args(): + """Parse command line arguments""" + parser = argparse.ArgumentParser(description="XRT Test Wrapper (Python)") + + parser.add_argument("--xclbin", required=True, help="Path to XCLBIN file") + parser.add_argument( + "--instr", required=True, help="Path to instruction binary file" + ) + parser.add_argument("--kernel", default="MLIR_AIE", help="Kernel name") + parser.add_argument( + "--verbosity", "-v", type=int, default=0, help="Verbosity level" + ) + parser.add_argument( + "--verify", action="store_true", default=True, help="Verify results" + ) + parser.add_argument( + "--no-verify", dest="verify", action="store_false", help="Skip verification" + ) + parser.add_argument("--iters", type=int, default=1, help="Number of iterations") + parser.add_argument( + "--warmup", type=int, default=0, help="Number of warmup iterations" + ) + parser.add_argument( + "--trace-sz", type=int, default=0, help="Trace buffer size in bytes" + ) + parser.add_argument("--trace-file", default="trace.txt", help="Trace output file") + + return parser.parse_args() + + +def main(): + args = parse_args() + + if args.verbosity >= 1: + print("=" * 80) + print(f"XCLBIN: {args.xclbin}") + print(f"Instruction file: {args.instr}") + print(f"Kernel: {args.kernel}") + print( + f"IN1_VOLUME: {IN1_VOLUME}, IN2_VOLUME: {IN2_VOLUME}, OUT_VOLUME: {OUT_VOLUME}" + ) + print("=" * 80) + + # Load instruction sequence + instr_v = load_instr_binary(args.instr) + if args.verbosity >= 1: + print(f"Sequence instr count: {len(instr_v)}") + + # Start the XRT context and load the kernel + if args.verbosity >= 1: + print("Loading device and kernel...") + + device = xrt.device(0) + xclbin = xrt.xclbin(args.xclbin) + device.register_xclbin(xclbin) + + # Get kernel name from xclbin + xkernels = xclbin.get_kernels() + xkernel = None + for k in xkernels: + if k.get_name() == args.kernel: + xkernel = k + break + + if xkernel is None: + print(f"ERROR: Kernel '{args.kernel}' not found in xclbin") + sys.exit(1) + + kernel = xrt.kernel( + device, xclbin.get_uuid(), args.kernel, xrt.kernel.cu_access_mode.exclusive + ) + + if args.verbosity >= 1: + print(f"Kernel loaded: {args.kernel}") + + # Set up buffer objects + bo_instr = xrt.bo(device, len(instr_v) * 4, xrt.bo.cacheable, kernel.group_id(1)) + bo_in1 = xrt.bo(device, IN1_SIZE, xrt.bo.host_only, kernel.group_id(3)) + bo_in2 = xrt.bo(device, IN2_SIZE, xrt.bo.host_only, kernel.group_id(4)) + bo_out = xrt.bo(device, OUT_SIZE, xrt.bo.host_only, kernel.group_id(5)) + bo_ctrlpkts = xrt.bo(device, 8, xrt.bo.host_only, kernel.group_id(6)) + + # Workaround: allocate trace buffer (small if not used, 4x size if used) + tmp_trace_size = args.trace_sz * 4 if args.trace_sz > 0 else 1 + bo_trace = xrt.bo(device, tmp_trace_size, xrt.bo.host_only, kernel.group_id(7)) + + if args.verbosity >= 1: + print("Writing data into buffer objects...") + + # Map buffers and initialize + # Note: pyxrt write() expects bytes + bo_instr.write(instr_v.tobytes(), 0) + + bufIn1 = initialize_bufIn1(IN1_VOLUME) + bufIn2 = initialize_bufIn2(IN2_VOLUME) + bufOut = initialize_bufOut(OUT_VOLUME) + + bo_in1.write(bufIn1.tobytes(), 0) + bo_in2.write(bufIn2.tobytes(), 0) + bo_out.write(bufOut.tobytes(), 0) + + if args.trace_sz > 0: + # Initialize trace buffer with zeros + trace_init = np.zeros(tmp_trace_size, dtype=np.uint8) + bo_trace.write(trace_init.tobytes(), 0) + + # Sync host to device + bo_instr.sync(xrt.xclBOSyncDirection.XCL_BO_SYNC_BO_TO_DEVICE) + bo_in1.sync(xrt.xclBOSyncDirection.XCL_BO_SYNC_BO_TO_DEVICE) + bo_in2.sync(xrt.xclBOSyncDirection.XCL_BO_SYNC_BO_TO_DEVICE) + bo_out.sync(xrt.xclBOSyncDirection.XCL_BO_SYNC_BO_TO_DEVICE) + + if args.trace_sz > 0: + bo_trace.sync(xrt.xclBOSyncDirection.XCL_BO_SYNC_BO_TO_DEVICE) + + # Initialize run configs + num_iter = args.iters + args.warmup + npu_time_total = 0.0 + npu_time_min = float("inf") + npu_time_max = 0.0 + errors = 0 + + # Main run loop + for iter in range(num_iter): + if args.verbosity >= 1: + print(f"\n{'='*60}") + print(f"Iteration {iter + 1}/{num_iter}") + print(f"{'='*60}") + + # Run kernel + if args.verbosity >= 1: + print("Running Kernel...") + + opcode = 3 + start = time.perf_counter() + run = kernel( + opcode, + bo_instr, + len(instr_v), + bo_in1, + bo_in2, + bo_out, + bo_ctrlpkts, + bo_trace, + ) + run.wait() + stop = time.perf_counter() + + # Sync device to host + bo_out.sync(xrt.xclBOSyncDirection.XCL_BO_SYNC_BO_FROM_DEVICE) + if args.trace_sz > 0: + bo_trace.sync(xrt.xclBOSyncDirection.XCL_BO_SYNC_BO_FROM_DEVICE) + + # Skip warmup iterations for timing + if iter < args.warmup: + if args.verbosity >= 1: + print("(Warmup iteration - not counted)") + continue + + # Read output buffer + # pyxrt read() returns numpy array directly + bufOut_bytes = bo_out.read(OUT_SIZE, 0) + bufOut = np.frombuffer(bufOut_bytes, dtype=DATATYPE_OUT) + + # Verify results + if args.verify: + if args.verbosity >= 1: + print("Verifying results...") + + vstart = time.time() + errors += verify_vector_scalar_mul( + bufIn1, bufIn2, bufOut, IN1_VOLUME, args.verbosity + ) + vstop = time.time() + + if args.verbosity >= 1: + print(f"Verify time: {vstop - vstart:.3f} secs") + else: + if args.verbosity >= 1: + print("WARNING: Results not verified.") + + # Write trace on first non-warmup iteration + if args.trace_sz > 0 and iter == args.warmup: + # Read trace buffer (buffer 7) + trace_data_bytes = bo_trace.read(args.trace_sz, 0) + # Convert to uint32 array for proper formatting + trace_buffer = np.frombuffer(trace_data_bytes, dtype=np.uint32) + + if args.verbosity >= 1: + print(f"Trace buffer shape: {trace_buffer.shape}") + print(f"Trace buffer dtype: {trace_buffer.dtype}") + + write_out_trace(trace_buffer, args.trace_file) + + # Accumulate timing + npu_time = (stop - start) * 1e6 # Convert to microseconds + npu_time_total += npu_time + npu_time_min = min(npu_time, npu_time_min) + npu_time_max = max(npu_time, npu_time_max) + + if args.verbosity >= 1: + print(f"NPU time: {npu_time:.2f} μs") + + # Print summary + n_iterations = args.iters + if n_iterations > 0: + npu_time_avg = npu_time_total / n_iterations + else: + npu_time_avg = 0 + + print("\n" + "=" * 80) + print("PERFORMANCE SUMMARY") + print("=" * 80) + print(f"Avg NPU time: {npu_time_avg:.2f} μs") + print(f"Min NPU time: {npu_time_min:.2f} μs") + print(f"Max NPU time: {npu_time_max:.2f} μs") + print(f"Iterations: {n_iterations}") + print("=" * 80) + + if args.verify: + if errors == 0: + print("\n✓ TEST PASSED - All results verified correctly!") + return 0 + else: + print(f"\n✗ TEST FAILED - {errors} errors detected!") + return 1 + else: + print("\n? TEST SKIPPED - Verification disabled") + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/programming_examples/basic/event_trace/vector_scalar_mul.cc b/programming_examples/basic/event_trace/vector_scalar_mul.cc new file mode 100644 index 00000000000..05bbbfcb948 --- /dev/null +++ b/programming_examples/basic/event_trace/vector_scalar_mul.cc @@ -0,0 +1,27 @@ +//===- vector_scalar_mul.cc -------------------------------------*- C++ -*-===// +// +// This file is licensed under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +// Copyright (C) 2026, Advanced Micro Devices, Inc. +// +//===----------------------------------------------------------------------===// + +#include +#include +#include +#include + +extern "C" { + +void vector_scalar_mul_aie_scalar(int32_t *a, int32_t *c, int32_t *factor, + int32_t N) { + event0(); // event to mark start of function + for (int i = 0; i < N; i++) { + c[i] = *factor * a[i]; + } + event1(); // event to mark end of function +} + +} // extern "C" diff --git a/programming_examples/basic/event_trace/visualize_trace.py b/programming_examples/basic/event_trace/visualize_trace.py new file mode 100755 index 00000000000..b8d3eb19288 --- /dev/null +++ b/programming_examples/basic/event_trace/visualize_trace.py @@ -0,0 +1,240 @@ +#!/usr/bin/env python3 +##===- visualize_trace.py -----------------------------------------------===## +# +# This file licensed under the Apache License v2.0 with LLVM Exceptions. +# See https://llvm.org/LICENSE.txt for license information. +# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +# +# Copyright (C) 2025, Advanced Micro Devices, Inc. +# +##===----------------------------------------------------------------------===## +# +# Script to visualize trace JSON data as a PNG timeline +# +##===----------------------------------------------------------------------===## + +import json +import argparse +import matplotlib.pyplot as plt +import matplotlib.patches as mpatches +from collections import defaultdict + + +def parse_trace_json(trace_file): + """Parse the trace JSON file and extract events.""" + with open(trace_file, "r") as f: + data = json.load(f) + + # Organize data by process and thread + processes = {} + threads = {} + events = [] + + for entry in data: + if entry["ph"] == "M": # Metadata + if entry["name"] == "process_name": + processes[entry["pid"]] = entry["args"]["name"] + elif entry["name"] == "thread_name": + threads[(entry["pid"], entry["tid"])] = entry["args"]["name"] + elif entry["ph"] in ["B", "E"]: # Begin or End events + events.append(entry) + + return processes, threads, events + + +def create_timeline(processes, threads, events, output_file, title="Trace Timeline"): + """Create a timeline visualization of trace events.""" + + # Group events into intervals (B -> E pairs) + active_events = {} + intervals = [] + + for event in events: + key = (event["pid"], event["tid"], event["name"]) + + if event["ph"] == "B": # Begin + active_events[key] = event["ts"] + elif event["ph"] == "E": # End + if key in active_events: + start_ts = active_events[key] + end_ts = event["ts"] + intervals.append( + { + "pid": event["pid"], + "tid": event["tid"], + "name": event["name"], + "start": start_ts, + "end": end_ts, + "duration": end_ts - start_ts, + } + ) + del active_events[key] + + if not intervals: + print("No trace intervals found!") + return + + # Create unique lanes for each (pid, tid) combination + lanes = {} + lane_idx = 0 + for pid in sorted(set(i["pid"] for i in intervals)): + for tid in sorted(set(i["tid"] for i in intervals if i["pid"] == pid)): + lanes[(pid, tid)] = lane_idx + lane_idx += 1 + + # Create figure + fig, ax = plt.subplots(figsize=(20, max(8, min(lane_idx * 0.5, 20)))) + + # Calculate time range + if intervals: + min_time = min(i["start"] for i in intervals) + max_time = max(i["end"] for i in intervals) + time_range = max_time - min_time + else: + min_time = 0 + max_time = 1 + time_range = 1 + + ax.set_xlim(min_time - time_range * 0.02, max_time + time_range * 0.02) + + # Color map for different event types + event_colors = {} + color_palette = plt.cm.tab20.colors + color_idx = 0 + + # Plot intervals + for interval in intervals: + lane = lanes[(interval["pid"], interval["tid"])] + event_name = interval["name"] + + # Assign color to event type + if event_name not in event_colors: + event_colors[event_name] = color_palette[color_idx % len(color_palette)] + color_idx += 1 + + color = event_colors[event_name] + + # Draw rectangle for the interval + rect = mpatches.Rectangle( + (interval["start"], lane - 0.4), + interval["duration"], + 0.8, + facecolor=color, + edgecolor="black", + linewidth=0.5, + alpha=0.7, + ) + ax.add_patch(rect) + + # Add text label if duration is large enough (relative to visible range) + label_threshold = time_range / 100 # Show label if event is > 1% of total time + if interval["duration"] > label_threshold: + ax.text( + interval["start"] + interval["duration"] / 2, + lane, + event_name.replace("INSTR_", "").replace("DMA_", "").replace("_", " "), + ha="center", + va="center", + fontsize=7, + weight="bold", + clip_on=True, + ) + + # Set up axes + ax.set_ylim(-0.5, lane_idx - 0.5) + ax.set_yticks(range(lane_idx)) + + # Create lane labels + lane_labels = [] + for (pid, tid), lane in sorted(lanes.items(), key=lambda x: x[1]): + proc_name = processes.get(pid, f"Process {pid}") + thread_name = threads.get((pid, tid), f"Thread {tid}") + lane_labels.append(f"{proc_name}\n{thread_name}") + + ax.set_yticklabels(lane_labels, fontsize=8) + ax.set_xlabel("Time (cycles)", fontsize=10) + ax.set_title(title, fontsize=12, weight="bold") + ax.grid(True, axis="x", alpha=0.3) + + # Add legend for event types (limit to most common) + event_counts = defaultdict(int) + for interval in intervals: + event_counts[interval["name"]] += 1 + + # Show legend for top events + top_events = sorted(event_counts.items(), key=lambda x: x[1], reverse=True)[:15] + legend_patches = [ + mpatches.Patch(color=event_colors[name], label=f"{name} ({count})") + for name, count in top_events + if name in event_colors + ] + + if legend_patches: + ax.legend( + handles=legend_patches, + loc="upper left", + bbox_to_anchor=(1.02, 1), + fontsize=8, + framealpha=0.9, + ) + + # Adjust layout + plt.tight_layout() + + # Save figure + plt.savefig(output_file, dpi=150, bbox_inches="tight") + print(f"Trace visualization saved to: {output_file}") + + # Print statistics + print("\nTrace Statistics:") + print(f" Total intervals: {len(intervals)}") + print(f" Total lanes: {lane_idx}") + print( + f" Time range: {min(i['start'] for i in intervals)} - {max(i['end'] for i in intervals)} cycles" + ) + print( + f" Duration: {max(i['end'] for i in intervals) - min(i['start'] for i in intervals)} cycles" + ) + + print("\nTop Events:") + for name, count in top_events[:10]: + print(f" {name}: {count}") + + +def main(): + parser = argparse.ArgumentParser( + description="Visualize trace JSON data as a PNG timeline" + ) + parser.add_argument( + "-i", + "--input", + default="trace_mlir.json", + help="Input trace JSON file (default: trace_mlir.json)", + ) + parser.add_argument( + "-o", + "--output", + default="trace_timeline.png", + help="Output PNG file (default: trace_timeline.png)", + ) + parser.add_argument( + "-t", + "--title", + default="MLIR Trace Timeline", + help="Chart title (default: MLIR Trace Timeline)", + ) + + args = parser.parse_args() + + print(f"Reading trace data from: {args.input}") + processes, threads, events = parse_trace_json(args.input) + + print( + f"Found {len(processes)} processes, {len(threads)} threads, {len(events)} events" + ) + + create_timeline(processes, threads, events, args.output, args.title) + + +if __name__ == "__main__": + main() diff --git a/python/compiler/aiecc/main.py b/python/compiler/aiecc/main.py index 81a69b748fa..5379588d3e4 100644 --- a/python/compiler/aiecc/main.py +++ b/python/compiler/aiecc/main.py @@ -70,6 +70,9 @@ def _create_input_with_addresses_pipeline( # Build nested device pipeline with conditional passes device_pipeline = ( Pipeline() + .add_pass("aie-trace-to-config") + .add_pass("aie-trace-pack-reg-writes") + .add_pass("aie-inline-trace-config") .add_pass("aie-assign-lock-ids") .add_pass("aie-register-objectFifos") .add_pass( diff --git a/python/dialects/aie.py b/python/dialects/aie.py index cf854771ca2..ddff535e37f 100644 --- a/python/dialects/aie.py +++ b/python/dialects/aie.py @@ -4,6 +4,7 @@ import inspect from typing import List, Tuple, Dict, Any, Union import contextlib +from enum import IntEnum import numpy as np @@ -62,6 +63,7 @@ IntegerAttr, IntegerType, MemRefType, + StringAttr, TypeAttr, UnitAttr, Value, @@ -186,6 +188,20 @@ def _objectFifo_depth_attr(x, context): return IntegerAttr.get(IntegerType.get_signless(32, context=context), x) +@register_attribute_builder("TraceEventAttr") +def _trace_event_attr(x, context): + """Build TraceEventAttr from string or StringAttr.""" + if isinstance(x, str): + return Attribute.parse(f'#aie.trace_event<"{x}">', context=context) + elif isinstance(x, StringAttr): + return Attribute.parse(f'#aie.trace_event<"{x.value}">', context=context) + elif isinstance(x, IntEnum): + return Attribute.parse(f'#aie.trace_event<"{str(x)}">', context=context) + else: + # Assume it's already an Attribute (could be an enum) + return x + + #### MLIR Helpers #### """ @@ -537,6 +553,54 @@ def __init__( core = region_op(Core, terminator=lambda *_: EndOp()) device = region_op(Device, terminator=lambda *_: EndOp()) +trace = region_op( + lambda tile, sym_name, *, buffer_size=None, loc=None, ip=None: TraceOp( + tile, sym_name, buffer_size=buffer_size, loc=loc, ip=ip + ), + terminator=lambda *_: EndOp(), +) + + +def trace_mode(mode, *, loc=None, ip=None): + return TraceModeOp(mode=mode, loc=loc, ip=ip) + + +def trace_event(event, *, label=None, loc=None, ip=None): + return TraceEventOp(event=event, label=label, loc=loc, ip=ip) + + +def trace_packet(id, type, *, loc=None, ip=None): + return TracePacketOp(id=id, type_=type, loc=loc, ip=ip) + + +def trace_port(slot, port, channel, direction, *, loc=None, ip=None): + return TracePortOp( + slot=slot, port=port, channel=channel, direction=direction, loc=loc, ip=ip + ) + + +def trace_start(*, broadcast=None, event=None, loc=None, ip=None): + return TraceStartEventOp(broadcast=broadcast, event=event, loc=loc, ip=ip) + + +def trace_stop(*, broadcast=None, event=None, loc=None, ip=None): + return TraceStopEventOp(broadcast=broadcast, event=event, loc=loc, ip=ip) + + +def trace_combo_event(slot, eventA, logic, eventB, *, loc=None, ip=None): + return TraceComboEventOp( + slot=slot, eventA=eventA, logic=logic, eventB=eventB, loc=loc, ip=ip + ) + + +def trace_edge_event(slot, event, trigger, *, loc=None, ip=None): + return TraceEdgeEventOp(slot=slot, event=event, trigger=trigger, loc=loc, ip=ip) + + +def trace_start_config(name, *, loc=None, ip=None): + return TraceStartConfigOp(trace_config=name, loc=loc, ip=ip) + + switchbox = region_op( lambda tile, *, loc=None, ip=None: SwitchboxOp(T.index(), tile, loc=loc, ip=ip) ) diff --git a/test/CppTests/register_database.cpp b/test/CppTests/register_database.cpp index 2ff04f937a4..0eb3b0f3ed7 100644 --- a/test/CppTests/register_database.cpp +++ b/test/CppTests/register_database.cpp @@ -22,23 +22,19 @@ using namespace mlir; void test_load_database() { std::cout << "Test: Load AIE2 Register Database\n"; - auto db = RegisterDatabase::loadAIE2(); if (!db) { throw std::runtime_error("Failed to load AIE2 register database"); } - std::cout << " ✓ Database loaded successfully\n"; } void test_lookup_core_events() { std::cout << "Test: Lookup Core Events\n"; - auto db = RegisterDatabase::loadAIE2(); if (!db) { throw std::runtime_error("Failed to load database"); } - // Test INSTR_EVENT_0 in core module auto instr0 = db->lookupEvent("INSTR_EVENT_0", "core"); if (!instr0) { @@ -49,7 +45,6 @@ void test_lookup_core_events() { void test_lookup_pl_events() { std::cout << "Test: Lookup PL (Shim) Events\n"; - auto db = RegisterDatabase::loadAIE2(); if (!db) { throw std::runtime_error("Failed to load database"); @@ -66,12 +61,10 @@ void test_lookup_pl_events() { void test_nonexistent_event() { std::cout << "Test: Lookup Non-existent Event\n"; - auto db = RegisterDatabase::loadAIE2(); if (!db) { throw std::runtime_error("Failed to load database"); } - // Should return nullopt for non-existent event auto result = db->lookupEvent("NONEXISTENT_EVENT", "core"); if (result.has_value()) { @@ -82,7 +75,6 @@ void test_nonexistent_event() { void test_wrong_module() { std::cout << "Test: Lookup Event in Wrong Module\n"; - auto db = RegisterDatabase::loadAIE2(); if (!db) { throw std::runtime_error("Failed to load database"); @@ -289,7 +281,6 @@ int main() { std::cout << "==============================================\n"; std::cout << "Register Database Unit Tests\n"; std::cout << "==============================================\n\n"; - test_load_database(); test_lookup_core_events(); test_lookup_pl_events(); diff --git a/test/Dialect/AIE/combo_edge/test_combo_edge_full.mlir b/test/Dialect/AIE/combo_edge/test_combo_edge_full.mlir new file mode 100644 index 00000000000..5ad4f4c6833 --- /dev/null +++ b/test/Dialect/AIE/combo_edge/test_combo_edge_full.mlir @@ -0,0 +1,43 @@ +// RUN: aie-opt %s -aie-trace-to-config | FileCheck %s + +aie.device(npu1_1col) { + %tile02 = aie.tile(0, 2) + + // CHECK-LABEL: @test_combo_and_edge + aie.trace @test_combo_and_edge(%tile02) { + aie.trace.mode "Event-Time" + aie.trace.packet id=1 type=core + + // Combo event (using events we know exist from previous tests) + aie.trace.combo_event<0> <"INSTR_EVENT_0"> AND_NOT <"INSTR_VECTOR"> + + // Edge detection + aie.trace.edge_event<0> event=<"LOCK_STALL"> trigger=BOTH + + // Trace the derived events + aie.trace.event<"COMBO_EVENT_0"> + aie.trace.event<"EDGE_DETECTION_EVENT_0"> + aie.trace.event<"INSTR_EVENT_0"> + + aie.trace.start broadcast=15 + aie.trace.stop broadcast=14 + } + + // Check combo event configuration + // CHECK: aie.trace.reg register = "Combo_event_inputs" field = "eventA" + // CHECK: aie.trace.reg register = "Combo_event_inputs" field = "eventB" + // CHECK: aie.trace.reg register = "Combo_event_control" field = "combo0" value = 1 + // Check edge detection configuration + // CHECK: aie.trace.reg register = "Edge_Detection_event_control" field = "Edge_Detection_Event_0" + // CHECK: aie.trace.reg register = "Edge_Detection_event_control" field = "Edge_Detection_0_Trigger_Rising" value = 1 + // CHECK: aie.trace.reg register = "Edge_Detection_event_control" field = "Edge_Detection_0_Trigger_Falling" value = 1 + // Check trace control + // CHECK: aie.trace.reg register = "Trace_Control0" field = "Mode" value = 0 + // CHECK: aie.trace.reg register = "Trace_Control1" field = "ID" value = 1 + // CHECK: aie.trace.reg register = "Trace_Control0" field = "Trace_Start_Event" value = 15 + // CHECK: aie.trace.reg register = "Trace_Control0" field = "Trace_Stop_Event" value = 14 + // Check event slots + // CHECK: aie.trace.reg register = "Trace_Event0" field = "Trace_Event0" + // CHECK: aie.trace.reg register = "Trace_Event0" field = "Trace_Event1" + // CHECK: aie.trace.reg register = "Trace_Event0" field = "Trace_Event2" +} diff --git a/test/Dialect/AIE/combo_edge/test_combo_event_parse.mlir b/test/Dialect/AIE/combo_edge/test_combo_event_parse.mlir new file mode 100644 index 00000000000..7ea87c8ad59 --- /dev/null +++ b/test/Dialect/AIE/combo_edge/test_combo_event_parse.mlir @@ -0,0 +1,94 @@ +//===- test_combo_event_parse.mlir -----------------------------*- MLIR -*-===// +// +// Copyright (C) 2025, Advanced Micro Devices, Inc. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +// RUN: aie-opt %s | FileCheck %s + +aie.device(npu1_1col) { + %tile02 = aie.tile(0, 2) + + // CHECK-LABEL: @test_combo_basic + aie.trace @test_combo_basic(%tile02) { + // CHECK: aie.trace.combo_event<0> <"LOCK_STALL"> AND <"DMA_S2MM_0_STALLED"> + aie.trace.combo_event<0> <"LOCK_STALL"> AND <"DMA_S2MM_0_STALLED"> + + // CHECK: aie.trace.combo_event<1> <"INSTR_EVENT_0"> OR <"INSTR_VECTOR"> + aie.trace.combo_event<1> <"INSTR_EVENT_0"> OR <"INSTR_VECTOR"> + + // CHECK: aie.trace.event <"COMBO_EVENT_0"> + aie.trace.event <"COMBO_EVENT_0"> + aie.trace.event + } + + // CHECK-LABEL: @test_edge_basic + aie.trace @test_edge_basic(%tile02) { + // CHECK: aie.trace.edge_event<0> event = <"LOCK_STALL"> trigger = RISING + aie.trace.edge_event<0> event=<"LOCK_STALL"> trigger=RISING + + // CHECK: aie.trace.edge_event<1> event = <"DMA_MM2S_0_FINISHED_BD"> trigger = BOTH + aie.trace.edge_event<1> event=<"DMA_MM2S_0_FINISHED_BD"> trigger=BOTH + + // CHECK: aie.trace.event <"EDGE_DETECTION_EVENT_0"> + aie.trace.event<"EDGE_DETECTION_EVENT_0"> + } + + // CHECK-LABEL: @test_hierarchical + aie.trace @test_hierarchical(%tile02) { + // CHECK: aie.trace.combo_event<0> <"LOCK_STALL"> AND <"DMA_S2MM_0_STALLED"> + aie.trace.combo_event<0> <"LOCK_STALL"> AND <"DMA_S2MM_0_STALLED"> + + // CHECK: aie.trace.combo_event<1> <"INSTR_EVENT_0"> OR <"INSTR_VECTOR"> + aie.trace.combo_event<1> <"INSTR_EVENT_0"> OR <"INSTR_VECTOR"> + + // CHECK: aie.trace.combo_event<2> <"COMBO_EVENT_0"> AND <"COMBO_EVENT_1"> + aie.trace.combo_event<2> <"COMBO_EVENT_0"> AND <"COMBO_EVENT_1"> + + // CHECK: aie.trace.event <"COMBO_EVENT_2"> + aie.trace.event<"COMBO_EVENT_2"> + } + + // CHECK-LABEL: @test_all_logic + aie.trace @test_all_logic(%tile02) { + // CHECK: aie.trace.combo_event<0> <"LOCK_STALL"> AND <"DMA_S2MM_0_STALLED"> + aie.trace.combo_event<0> <"LOCK_STALL"> AND <"DMA_S2MM_0_STALLED"> + } + + // CHECK-LABEL: @test_and_not + aie.trace @test_and_not(%tile02) { + // CHECK: aie.trace.combo_event<0> <"LOCK_STALL"> AND_NOT <"DMA_S2MM_0_STALLED"> + aie.trace.combo_event<0> <"LOCK_STALL"> AND_NOT <"DMA_S2MM_0_STALLED"> + } + + // CHECK-LABEL: @test_or + aie.trace @test_or(%tile02) { + // CHECK: aie.trace.combo_event<0> <"LOCK_STALL"> OR <"DMA_S2MM_0_STALLED"> + aie.trace.combo_event<0> <"LOCK_STALL"> OR <"DMA_S2MM_0_STALLED"> + } + + // CHECK-LABEL: @test_or_not + aie.trace @test_or_not(%tile02) { + // CHECK: aie.trace.combo_event<0> <"LOCK_STALL"> OR_NOT <"DMA_S2MM_0_STALLED"> + aie.trace.combo_event<0> <"LOCK_STALL"> OR_NOT <"DMA_S2MM_0_STALLED"> + } + + // CHECK-LABEL: @test_rising + aie.trace @test_rising(%tile02) { + // CHECK: aie.trace.edge_event<0> event = <"LOCK_STALL"> trigger = RISING + aie.trace.edge_event<0> event=<"LOCK_STALL"> trigger=RISING + } + + // CHECK-LABEL: @test_falling + aie.trace @test_falling(%tile02) { + // CHECK: aie.trace.edge_event<0> event = <"LOCK_STALL"> trigger = FALLING + aie.trace.edge_event<0> event=<"LOCK_STALL"> trigger=FALLING + } + + // CHECK-LABEL: @test_both + aie.trace @test_both(%tile02) { + // CHECK: aie.trace.edge_event<0> event = <"LOCK_STALL"> trigger = BOTH + aie.trace.edge_event<0> event=<"LOCK_STALL"> trigger=BOTH + } +} diff --git a/test/Dialect/AIE/combo_edge/test_combo_event_verify.mlir b/test/Dialect/AIE/combo_edge/test_combo_event_verify.mlir new file mode 100644 index 00000000000..f8bdc67c104 --- /dev/null +++ b/test/Dialect/AIE/combo_edge/test_combo_event_verify.mlir @@ -0,0 +1,74 @@ +//===- test_combo_event_verify.mlir ----------------------------*- MLIR -*-===// +// +// Copyright (C) 2025, Advanced Micro Devices, Inc. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +// RUN: aie-opt %s -split-input-file -verify-diagnostics + +aie.device(npu1_1col) { + %tile02 = aie.tile(0, 2) + + aie.trace @bad_combo_slot(%tile02) { + // expected-error@+1 {{combo event slot must be 0, 1, or 2, got 5}} + aie.trace.combo_event<5> <"LOCK_STALL"> AND <"DMA_S2MM_0_STALLED"> + } +} + +// ----- + +aie.device(npu1_1col) { + %tile02 = aie.tile(0, 2) + + aie.trace @duplicate_combo_slot(%tile02) { + aie.trace.combo_event<0> <"LOCK_STALL"> AND <"DMA_S2MM_0_STALLED"> + // expected-error@+1 {{combo event slot 0 already in use in this trace}} + aie.trace.combo_event<0> <"INSTR_EVENT_0"> OR <"INSTR_VECTOR"> + } +} + +// ----- + +aie.device(npu1_1col) { + %tile02 = aie.tile(0, 2) + + aie.trace @bad_hierarchical(%tile02) { + // expected-error@+1 {{combo slot 2 first event must be COMBO_EVENT_0 (hierarchical), got LOCK_STALL}} + aie.trace.combo_event<2> <"LOCK_STALL"> AND <"DMA_S2MM_0_STALLED"> + } +} + +// ----- + +aie.device(npu1_1col) { + %tile02 = aie.tile(0, 2) + + aie.trace @bad_edge_slot(%tile02) { + // expected-error@+1 {{edge detection slot must be 0 or 1, got 5}} + aie.trace.edge_event<5> event=<"LOCK_STALL"> trigger=RISING + } +} + +// ----- + +aie.device(npu1_1col) { + %tile02 = aie.tile(0, 2) + + aie.trace @duplicate_edge_slot(%tile02) { + aie.trace.edge_event<0> event=<"LOCK_STALL"> trigger=RISING + // expected-error@+1 {{edge detection slot 0 already in use in this trace}} + aie.trace.edge_event<0> event=<"DMA_MM2S_0_FINISHED_BD"> trigger=FALLING + } +} + +// ----- + +aie.device(npu1_1col) { + %tile02 = aie.tile(0, 2) + + aie.trace @edge_of_edge(%tile02) { + // expected-error@+1 {{edge detection source should be a regular event, not another EDGE_DETECTION_EVENT}} + aie.trace.edge_event<0> event=<"EDGE_DETECTION_EVENT_0"> trigger=RISING + } +} diff --git a/test/Dialect/AIE/combo_edge/test_combo_to_config.mlir b/test/Dialect/AIE/combo_edge/test_combo_to_config.mlir new file mode 100644 index 00000000000..920d9c6f876 --- /dev/null +++ b/test/Dialect/AIE/combo_edge/test_combo_to_config.mlir @@ -0,0 +1,23 @@ +//===- test_combo_to_config.mlir -------------------------------*- MLIR -*-===// +// +// Copyright (C) 2025, Advanced Micro Devices, Inc. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +// RUN: aie-opt %s -aie-trace-to-config | FileCheck %s + +aie.device(npu1_1col) { + %tile_0_2 = aie.tile(0, 2) + + // CHECK-LABEL: @test_combo_lowering + aie.trace @test_combo_lowering(%tile_0_2) { + aie.trace.combo_event<0> AND + aie.trace.event<"COMBO_EVENT_0"> + } + + // CHECK: aie.trace.reg register = "Combo_event_inputs" field = "eventA" value = CoreEventAIE2::INSTR_EVENT_0 + // CHECK: aie.trace.reg register = "Combo_event_inputs" field = "eventB" value = CoreEventAIE2::INSTR_VECTOR + // CHECK: aie.trace.reg register = "Combo_event_control" field = "combo0" value = 0 + // CHECK: aie.trace.reg register = "Trace_Event0" field = "Trace_Event0" value = "COMBO_EVENT_0" comment = "COMBO_EVENT_0" +} diff --git a/test/Dialect/AIE/combo_edge/test_edge_to_config.mlir b/test/Dialect/AIE/combo_edge/test_edge_to_config.mlir new file mode 100644 index 00000000000..3a289733afe --- /dev/null +++ b/test/Dialect/AIE/combo_edge/test_edge_to_config.mlir @@ -0,0 +1,23 @@ +//===- test_edge_to_config.mlir --------------------------------*- MLIR -*-===// +// +// Copyright (C) 2025, Advanced Micro Devices, Inc. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +// RUN: aie-opt %s -aie-trace-to-config | FileCheck %s + +aie.device(npu1_1col) { + %tile_0_2 = aie.tile(0, 2) + + // CHECK-LABEL: @test_edge_lowering + aie.trace @test_edge_lowering(%tile_0_2) { + aie.trace.edge_event<0> event= trigger=RISING + aie.trace.event<"EDGE_DETECTION_EVENT_0"> + } + + // CHECK: aie.trace.reg register = "Edge_Detection_event_control" field = "Edge_Detection_Event_0" value = CoreEventAIE2::LOCK_STALL + // CHECK: aie.trace.reg register = "Edge_Detection_event_control" field = "Edge_Detection_0_Trigger_Rising" value = 1 + // CHECK: aie.trace.reg register = "Edge_Detection_event_control" field = "Edge_Detection_0_Trigger_Falling" value = 0 + // CHECK: aie.trace.reg register = "Trace_Event0" field = "Trace_Event0" +} diff --git a/test/Dialect/AIE/trace/test_trace_port_parse.mlir b/test/Dialect/AIE/trace/test_trace_port_parse.mlir new file mode 100644 index 00000000000..bfc8c27f318 --- /dev/null +++ b/test/Dialect/AIE/trace/test_trace_port_parse.mlir @@ -0,0 +1,38 @@ +//===- test_trace_port_parse.mlir ------------------------------*- MLIR -*-===// +// +// This file is licensed under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +// (c) Copyright 2025 Advanced Micro Devices, Inc. +// +//===----------------------------------------------------------------------===// + +// RUN: aie-opt %s | FileCheck %s + +// CHECK-LABEL: module { +module { + // CHECK: aie.device(npu1_1col) + aie.device(npu1_1col) { + // CHECK: %[[TILE:.*]] = aie.tile(0, 2) + %tile_0_2 = aie.tile(0, 2) + + // CHECK: aie.trace @port_trace(%[[TILE]]) { + aie.trace @port_trace(%tile_0_2) { + // CHECK: aie.trace.port<0> port = North channel = 1 direction = S2MM + aie.trace.port<0> port=North channel=1 direction=S2MM + + // CHECK: aie.trace.port<1> port = DMA channel = 0 direction = MM2S + aie.trace.port<1> port=DMA channel=0 direction=MM2S + + // CHECK: aie.trace.port<2> port = South channel = 2 direction = S2MM + aie.trace.port<2> port=South channel=2 direction=S2MM + + // CHECK: aie.trace.event <"PORT_RUNNING_0"> + aie.trace.event<"PORT_RUNNING_0"> + + // CHECK: aie.trace.event <"PORT_IDLE_1"> + aie.trace.event<"PORT_IDLE_1"> + } + } +} diff --git a/test/Dialect/AIE/trace/test_trace_port_to_config.mlir b/test/Dialect/AIE/trace/test_trace_port_to_config.mlir new file mode 100644 index 00000000000..5ee50f21564 --- /dev/null +++ b/test/Dialect/AIE/trace/test_trace_port_to_config.mlir @@ -0,0 +1,44 @@ +//===- test_trace_port_to_config.mlir --------------------------*- MLIR -*-===// +// +// This file is licensed under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +// (c) Copyright 2026 Advanced Micro Devices, Inc. +// +//===----------------------------------------------------------------------===// + +// RUN: aie-opt %s -aie-trace-to-config | FileCheck %s + +module { + aie.device(npu1_1col) { + %tile_0_2 = aie.tile(0, 2) + + aie.trace @port_trace(%tile_0_2) { + aie.trace.mode "Event-Time" + aie.trace.packet id=1 type="core" + aie.trace.port<0> port=North channel=1 direction=S2MM + aie.trace.port<1> port=DMA channel=0 direction=MM2S + aie.trace.event<"PORT_RUNNING_0"> + aie.trace.start broadcast=15 + aie.trace.stop broadcast=14 + } + + aie.runtime_sequence @seq(%arg0: memref<32xi32>) { + aie.trace.start_config @port_trace + } + } +} + +// CHECK: aie.device(npu1_1col) +// CHECK: %[[TILE:.*]] = aie.tile(0, 2) +// CHECK: aie.trace.config @port_trace_config(%[[TILE]]) +// CHECK-DAG: aie.trace.reg register = "Trace_Control0" field = "Trace_Start_Event" value = 15 +// CHECK-DAG: aie.trace.reg register = "Trace_Control0" field = "Trace_Stop_Event" value = 14 +// CHECK-DAG: aie.trace.reg register = "Trace_Control0" field = "Mode" value = 0 +// CHECK-DAG: aie.trace.reg register = "Trace_Control1" field = "ID" value = 1 +// CHECK-DAG: aie.trace.reg register = "Trace_Control1" field = "Packet_Type" value = 0 +// CHECK-DAG: aie.trace.reg register = "Stream_Switch_Event_Port_Selection_0" field = "Port_0_ID" value = "North:1" +// CHECK-DAG: aie.trace.reg register = "Stream_Switch_Event_Port_Selection_0" field = "Port_0_Master_Slave" value = 1 +// CHECK-DAG: aie.trace.reg register = "Stream_Switch_Event_Port_Selection_0" field = "Port_1_ID" value = "DMA:0" +// CHECK-DAG: aie.trace.reg register = "Stream_Switch_Event_Port_Selection_0" field = "Port_1_Master_Slave" value = 0 diff --git a/test/Dialect/AIE/trace/test_trace_port_verify.mlir b/test/Dialect/AIE/trace/test_trace_port_verify.mlir new file mode 100644 index 00000000000..db97d2cc2c7 --- /dev/null +++ b/test/Dialect/AIE/trace/test_trace_port_verify.mlir @@ -0,0 +1,62 @@ +//===- test_trace_port_verify.mlir -----------------------------*- MLIR -*-===// +// +// This file is licensed under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +// (c) Copyright 2025 Advanced Micro Devices, Inc. +// +//===----------------------------------------------------------------------===// + +// RUN: aie-opt %s -split-input-file -verify-diagnostics + +module { + aie.device(npu1_1col) { + %tile_0_2 = aie.tile(0, 2) + + aie.trace @bad_slot(%tile_0_2) { + // expected-error @+1 {{attribute 'slot' failed to satisfy constraint: 32-bit signless integer attribute whose minimum value is 0 whose maximum value is 7}} + aie.trace.port<8> port=North channel=1 direction=S2MM + } + } +} + +// ----- + +module { + aie.device(npu1_1col) { + %tile_0_2 = aie.tile(0, 2) + + aie.trace @dma_channel_oob(%tile_0_2) { + // expected-error @+1 {{invalid stream switch port configuration for tile (0, 2)}} + aie.trace.port<0> port=DMA channel=7 direction=MM2S + } + } +} + +// ----- + +module { + aie.device(npu1_1col) { + %tile_0_2 = aie.tile(0, 2) + + aie.trace @duplicate_slot(%tile_0_2) { + // expected-error @+1 {{duplicate port slot 0 in trace duplicate_slot}} + aie.trace.port<0> port=North channel=1 direction=S2MM + aie.trace.port<0> port=DMA channel=0 direction=MM2S + } + } +} + +// ----- + +module { + aie.device(npu1_1col) { + %tile_0_2 = aie.tile(0, 2) + + aie.trace @negative_channel(%tile_0_2) { + // expected-error @+1 {{invalid stream switch port configuration for tile (0, 2)}} + aie.trace.port<0> port=North channel=-1 direction=S2MM + } + } +} diff --git a/test/Dialect/AIEX/trace/test_trace_port_end_to_end.mlir b/test/Dialect/AIEX/trace/test_trace_port_end_to_end.mlir new file mode 100644 index 00000000000..d2c5b3f8a29 --- /dev/null +++ b/test/Dialect/AIEX/trace/test_trace_port_end_to_end.mlir @@ -0,0 +1,26 @@ +// RUN: aie-opt %s -aie-trace-to-config -aie-trace-pack-reg-writes -aie-inline-trace-config | FileCheck %s + +module { + aie.device(npu1_1col) { + %tile_0_2 = aie.tile(0, 2) + + aie.trace @port_trace(%tile_0_2) { + aie.trace.mode "Event-Time" + aie.trace.packet id=1 type="core" + aie.trace.port<0> port=North channel=1 direction=S2MM + aie.trace.event<"PORT_RUNNING_0"> + aie.trace.start broadcast=15 + aie.trace.stop broadcast=14 + } + + aie.runtime_sequence @seq(%arg0: memref<32xi32>) { + aie.trace.start_config @port_trace + } + } +} + +// CHECK: aie.runtime_sequence @seq +// CHECK: aiex.npu.write32 {address = 213200 +// CHECK: aiex.npu.write32 {address = 213204 +// CHECK: aiex.npu.write32 {address = 261888 +// CHECK: aiex.npu.write32 {address = 213216 diff --git a/test/dialect/AIE/trace/test_trace_parse.mlir b/test/dialect/AIE/trace/test_trace_parse.mlir new file mode 100644 index 00000000000..dbb371d1ce4 --- /dev/null +++ b/test/dialect/AIE/trace/test_trace_parse.mlir @@ -0,0 +1,44 @@ +//===- test_trace_parse.mlir -----------------------------------*- MLIR -*-===// +// +// This file is licensed under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +// (c) Copyright 2025 Advanced Micro Devices, Inc. +// +//===----------------------------------------------------------------------===// + +// RUN: aie-opt %s | FileCheck %s + +// CHECK-LABEL: module { +module { + // CHECK: aie.device(npu1_1col) + aie.device(npu1_1col) { + // CHECK: %[[TILE:.*]] = aie.tile(0, 2) + %tile02 = aie.tile(0, 2) + + // CHECK: aie.trace @test_trace(%[[TILE]]) { + aie.trace @test_trace(%tile02) { + // CHECK: aie.trace.mode "Event-Time" + aie.trace.mode "Event-Time" + + // CHECK: aie.trace.packet id = 1 type = core + aie.trace.packet id=1 type="core" + + // CHECK: aie.trace.event <"INSTR_EVENT_0"> + aie.trace.event<"INSTR_EVENT_0"> + + // CHECK: aie.trace.event <"INSTR_VECTOR"> label = "vector_op" + aie.trace.event<"INSTR_VECTOR"> label="vector_op" + + // CHECK: aie.trace.event <"LOCK_STALL"> + aie.trace.event<"LOCK_STALL"> + + // CHECK: aie.trace.start broadcast = 15 + aie.trace.start broadcast=15 + + // CHECK: aie.trace.stop broadcast = 14 + aie.trace.stop broadcast=14 + } + } +} diff --git a/test/dialect/AIE/trace/test_trace_to_config.mlir b/test/dialect/AIE/trace/test_trace_to_config.mlir new file mode 100644 index 00000000000..832e16d7833 --- /dev/null +++ b/test/dialect/AIE/trace/test_trace_to_config.mlir @@ -0,0 +1,53 @@ +//===- test_trace_to_config.mlir -------------------------------*- MLIR -*-===// +// +// This file is licensed under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +// (c) Copyright 2025 Advanced Micro Devices, Inc. +// +//===----------------------------------------------------------------------===// + +// RUN: aie-opt %s -aie-trace-to-config | FileCheck %s + +// CHECK-LABEL: module { +module { + aie.device(npu1_1col) { + %tile02 = aie.tile(0, 2) + + aie.trace @test_trace(%tile02) { + aie.trace.mode "Event-Time" + aie.trace.packet id=1 type="core" + aie.trace.event<"INSTR_EVENT_0"> + aie.trace.event<"INSTR_VECTOR"> + aie.trace.event<"LOCK_STALL"> + aie.trace.start broadcast=15 + aie.trace.stop broadcast=14 + } + + // CHECK-DAG: aie.trace.reg register = "Trace_Control0" field = "Mode" value = 0 + // CHECK-DAG: aie.trace.reg register = "Trace_Control1" field = "ID" value = 1 + // CHECK-DAG: aie.trace.reg register = "Trace_Control1" field = "Packet_Type" value = 0 + // CHECK-DAG: aie.trace.reg register = "Trace_Control0" field = "Trace_Start_Event" value = 15 + // CHECK-DAG: aie.trace.reg register = "Trace_Control0" field = "Trace_Stop_Event" value = 14 + // CHECK-DAG: aie.trace.reg register = "Trace_Event0" field = "Trace_Event0" value = "INSTR_EVENT_0" + // CHECK-DAG: aie.trace.reg register = "Trace_Event0" field = "Trace_Event1" value = "INSTR_VECTOR" + // CHECK-DAG: aie.trace.reg register = "Trace_Event0" field = "Trace_Event2" value = "LOCK_STALL" + + aie.trace @mem_trace(%tile02) { + aie.trace.mode "Event-Time" + aie.trace.packet id=3 type="mem" + aie.trace.event<"DMA_S2MM_0_START_TASK"> + aie.trace.start broadcast=15 + aie.trace.stop broadcast=14 + } + + // CHECK-LABEL: aie.trace.config @mem_trace_config + // CHECK-NOT: aie.trace.reg register = "Trace_Control0" field = "Mode" + // CHECK-DAG: aie.trace.reg register = "Trace_Control1" field = "ID" value = 3 + // CHECK-DAG: aie.trace.reg register = "Trace_Control1" field = "Packet_Type" value = 1 + // CHECK-DAG: aie.trace.reg register = "Trace_Control0" field = "Trace_Start_Event" value = 15 + // CHECK-DAG: aie.trace.reg register = "Trace_Control0" field = "Trace_Stop_Event" value = 14 + // CHECK-DAG: aie.trace.reg register = "Trace_Event0" field = "Trace_Event0" value = "DMA_S2MM_0_START_TASK" + } +} diff --git a/test/dialect/AIE/trace/test_trace_to_config_verify.mlir b/test/dialect/AIE/trace/test_trace_to_config_verify.mlir new file mode 100644 index 00000000000..6316dc620da --- /dev/null +++ b/test/dialect/AIE/trace/test_trace_to_config_verify.mlir @@ -0,0 +1,54 @@ +//===- test_trace_to_config_verify.mlir ------------------------*- MLIR -*-===// +// +// This file is licensed under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +// (c) Copyright 2025 Advanced Micro Devices, Inc. +// +//===----------------------------------------------------------------------===// + +// RUN: aie-opt %s -aie-trace-to-config -split-input-file -verify-diagnostics + +module { + aie.device(npu1_1col) { + %tile02 = aie.tile(0, 2) + + aie.trace @unknown_trace_event(%tile02) { + // expected-error@+1 {{unknown trace event 'NOT_A_REAL_EVENT'}} + aie.trace.event<"NOT_A_REAL_EVENT"> + aie.trace.start broadcast=15 + aie.trace.stop broadcast=14 + } + } +} + +// ----- + +module { + aie.device(npu1_1col) { + %tile02 = aie.tile(0, 2) + + aie.trace @unknown_start_event(%tile02) { + aie.trace.event<"INSTR_EVENT_0"> + // expected-error@+1 {{unknown trace event 'ALSO_NOT_REAL'}} + aie.trace.start event=<"ALSO_NOT_REAL"> + aie.trace.stop broadcast=14 + } + } +} + +// ----- + +module { + aie.device(npu1_1col) { + %tile02 = aie.tile(0, 2) + + aie.trace @unknown_stop_event(%tile02) { + aie.trace.event<"INSTR_EVENT_0"> + aie.trace.start broadcast=15 + // expected-error@+1 {{unknown trace event 'STILL_NOT_REAL'}} + aie.trace.stop event=<"STILL_NOT_REAL"> + } + } +} diff --git a/test/dialect/AIE/trace/test_trace_verify.mlir b/test/dialect/AIE/trace/test_trace_verify.mlir new file mode 100644 index 00000000000..dcf11132304 --- /dev/null +++ b/test/dialect/AIE/trace/test_trace_verify.mlir @@ -0,0 +1,134 @@ +//===- test_trace_verify.mlir ----------------------------------*- MLIR -*-===// +// +// This file is licensed under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +// (c) Copyright 2025 Advanced Micro Devices, Inc. +// +//===----------------------------------------------------------------------===// + +// RUN: aie-opt %s -split-input-file -verify-diagnostics + +// Test: Too many events (max 8) +module { + aie.device(npu1_1col) { + %tile = aie.tile(0, 2) + + // expected-error@+1 {{trace unit supports maximum 8 events}} + aie.trace @test_too_many_events(%tile) { + aie.trace.event<"INSTR_EVENT_0"> + aie.trace.event<"INSTR_EVENT_1"> + aie.trace.event<"INSTR_VECTOR"> + aie.trace.event<"LOCK_STALL"> + aie.trace.event<"MEMORY_STALL"> + aie.trace.event<"STREAM_STALL"> + aie.trace.event<"CASCADE_STALL"> + aie.trace.event<"ACTIVE"> + aie.trace.event<"DISABLED"> + } + } +} + +// ----- + +// Test: Invalid typed enum case must fail parsing +module { + aie.device(npu1_1col) { + %tile = aie.tile(0, 2) + + aie.trace @test_invalid_enum_case(%tile) { + // expected-error@+1 {{unknown CoreEventAIE2 value}} + aie.trace.event + } + } +} + +// ----- + +// Test: Packet ID out of range (too low) +module { + aie.device(npu1_1col) { + %tile = aie.tile(0, 2) + + aie.trace @test_packet_id_low(%tile) { + // expected-error@+1 {{attribute 'id' failed to satisfy constraint: 32-bit signless integer attribute whose minimum value is 1 whose maximum value is 31}} + aie.trace.packet id=0 type="core" + } + } +} + +// ----- + +// Test: Packet ID out of range (too high) +module { + aie.device(npu1_1col) { + %tile = aie.tile(0, 2) + + aie.trace @test_packet_id_high(%tile) { + // expected-error@+1 {{attribute 'id' failed to satisfy constraint: 32-bit signless integer attribute whose minimum value is 1 whose maximum value is 31}} + aie.trace.packet id=32 type="core" + } + } +} + +// ----- + +// Test: Start event - must specify broadcast or event +module { + aie.device(npu1_1col) { + %tile = aie.tile(0, 2) + + aie.trace @test_start_missing(%tile) { + // expected-error@+1 {{must specify either broadcast or event}} + aie.trace.start + } + } +} + +// ----- + +// Test: Start event - cannot specify both +module { + aie.device(npu1_1col) { + %tile = aie.tile(0, 2) + + aie.trace @test_start_both(%tile) { + // expected-error@+1 {{cannot specify both broadcast and event}} + aie.trace.start broadcast=15 event=<"TRUE"> + } + } +} + +// ----- + +// Test: Valid trace configuration (should pass) +module { + aie.device(npu1_1col) { + %tile = aie.tile(0, 2) + + aie.trace @test_valid(%tile) { + aie.trace.mode "Event-Time" + aie.trace.packet id=1 type="core" + aie.trace.event<"INSTR_EVENT_0"> + aie.trace.event<"INSTR_VECTOR"> + aie.trace.event<"LOCK_STALL"> + aie.trace.start broadcast=15 + aie.trace.stop broadcast=14 + } + } +} + +// ----- + +// Test: Typed event enum must match target architecture +module { + aie.device(npu1_1col) { + %tile = aie.tile(0, 2) + + aie.trace @test_enum_arch_mismatch(%tile) { + // expected-error@+1 {{event 'CoreEventAIE2P::INSTR_EVENT_0' is not valid for core tile (AIE2) at (0, 2)}} + aie.trace.event + } + } +} diff --git a/test/dialect/AIEX/trace/test_inline_trace_config.mlir b/test/dialect/AIEX/trace/test_inline_trace_config.mlir new file mode 100644 index 00000000000..0a507e03f6f --- /dev/null +++ b/test/dialect/AIEX/trace/test_inline_trace_config.mlir @@ -0,0 +1,40 @@ +//===- test_inline_trace_config.mlir ---------------------------*- MLIR -*-===// +// +// This file is licensed under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +// (c) Copyright 2025 Advanced Micro Devices, Inc. +// +//===----------------------------------------------------------------------===// + +// RUN: aie-opt %s -aie-trace-to-config -aie-trace-pack-reg-writes -aie-inline-trace-config | FileCheck %s + +// CHECK-LABEL: module { +module { + aie.device(npu1_1col) { + %tile02 = aie.tile(0, 2) + + aie.trace @test_trace(%tile02) { + aie.trace.mode "Event-Time" + aie.trace.packet id=1 type="core" + aie.trace.event<"INSTR_EVENT_0"> + aie.trace.start broadcast=15 + aie.trace.stop broadcast=14 + } + + // CHECK: aie.trace.config @test_trace_config + + // Runtime sequence with trace configuration + aie.runtime_sequence @seq(%arg0: memref<32xi32>) { + // CHECK: aie.runtime_sequence + aie.trace.start_config @test_trace + + // After inlining with aie-inline-trace-config, npu.write32 is generated + // CHECK-NOT: aie.trace.start_config + // CHECK: aiex.npu.write32 + // CHECK-SAME: column = 0 : i32 + // CHECK-SAME: row = 2 : i32 + } + } +} diff --git a/test/dialect/AIEX/trace/test_inline_trace_config_verify.mlir b/test/dialect/AIEX/trace/test_inline_trace_config_verify.mlir new file mode 100644 index 00000000000..6bcadd3d142 --- /dev/null +++ b/test/dialect/AIEX/trace/test_inline_trace_config_verify.mlir @@ -0,0 +1,30 @@ +//===- test_inline_trace_config_verify.mlir --------------------*- MLIR -*-===// +// +// This file is licensed under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +// (c) Copyright 2025 Advanced Micro Devices, Inc. +// +//===----------------------------------------------------------------------===// + +// RUN: aie-opt %s -aie-trace-to-config -aie-inline-trace-config -verify-diagnostics + +module { + aie.device(npu1_1col) { + %tile02 = aie.tile(0, 2) + + // expected-error@+1 {{aie.trace.reg still has field attribute - run -aie-trace-pack-reg-writes pass first}} + aie.trace @test_trace(%tile02) { + aie.trace.mode "Event-Time" + aie.trace.packet id=1 type="core" + aie.trace.event<"INSTR_EVENT_0"> + aie.trace.start broadcast=15 + aie.trace.stop broadcast=14 + } + + aie.runtime_sequence @seq(%arg0: memref<32xi32>) { + aie.trace.start_config @test_trace + } + } +} diff --git a/test/dialect/AIEX/trace/test_trace_end_to_end.mlir b/test/dialect/AIEX/trace/test_trace_end_to_end.mlir new file mode 100644 index 00000000000..924515bcf00 --- /dev/null +++ b/test/dialect/AIEX/trace/test_trace_end_to_end.mlir @@ -0,0 +1,49 @@ +//===- test_trace_end_to_end.mlir ------------------------------*- MLIR -*-===// +// +// This file is licensed under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +// (c) Copyright 2025 Advanced Micro Devices, Inc. +// +//===----------------------------------------------------------------------===// + +// RUN: aie-opt %s -aie-trace-to-config -aie-trace-pack-reg-writes -aie-inline-trace-config | FileCheck %s + +// This test demonstrates the complete trace configuration pipeline +// from high-level aie.trace operations through to inlined register specifications + +// CHECK-LABEL: module { +module { + aie.device(npu1_1col) { + // CHECK: %[[TILE:.*]] = aie.tile(0, 2) + %tile02 = aie.tile(0, 2) + + // High-level trace configuration + aie.trace @my_trace(%tile02) { + aie.trace.mode "Event-Time" + aie.trace.packet id=1 type="core" + aie.trace.event label="kernel_start" + aie.trace.event<"INSTR_EVENT_1"> label="kernel_end" + aie.trace.event<"INSTR_VECTOR"> label="vector_op" + aie.trace.event<"LOCK_STALL"> label="lock_stall" + aie.trace.start broadcast=15 + aie.trace.stop broadcast=14 + } + + // After Pass 1 (TraceToConfig): aie.trace → aie.trace.config + // CHECK: aie.trace.config @my_trace_config(%[[TILE]]) packet_type = core { + + // Runtime sequence with trace invocation + aie.runtime_sequence @seq(%arg0: memref<32xi32>) { + // CHECK: aie.runtime_sequence + aie.trace.start_config @my_trace + + // After Pass 2 (AIEXInlineTraceConfig): generates npu.write32 with col/row + // CHECK-NOT: aie.trace.start_config + // CHECK: aiex.npu.write32 + // CHECK-SAME: column = 0 : i32 + // CHECK-SAME: row = 2 : i32 + } + } +} diff --git a/test/python/trace_ops.py b/test/python/trace_ops.py new file mode 100644 index 00000000000..e0808c49de7 --- /dev/null +++ b/test/python/trace_ops.py @@ -0,0 +1,318 @@ +# +# This file is licensed under the Apache License v2.0 with LLVM Exceptions. +# See https://llvm.org/LICENSE.txt for license information. +# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +# +# (c) Copyright 2026 Advanced Micro Devices, Inc. + +# RUN: %python %s | FileCheck %s + +from aie.dialects.aie import ( + AIEDevice, + ComboLogic, + DMAChannelDir, + EdgeTrigger, + TraceMode, + TracePacketType, + WireBundle, + device, + tile, + trace, + trace_combo_event, + trace_edge_event, + trace_event, + trace_mode, + trace_packet, + trace_port, + trace_start, + trace_start_config, + trace_stop, +) +from aie.dialects.aiex import runtime_sequence +from util import construct_and_print_module + + +# CHECK-LABEL: traceBasic +# CHECK: %[[TILE:.*]] = aie.tile(0, 2) +# CHECK: aie.trace @my_trace(%[[TILE]]) { +# CHECK: aie.trace.mode "Event-Time" +# CHECK: aie.trace.packet id = 1 type = core +# CHECK: aie.trace.event <"INSTR_EVENT_0"> +# CHECK: aie.trace.event <"INSTR_VECTOR"> +# CHECK: aie.trace.start event = <"TRUE"> +# CHECK: aie.trace.stop event = <"NONE"> +# CHECK: } +@construct_and_print_module +def traceBasic(): + @device(AIEDevice.npu1_1col) + def device_body(): + t = tile(0, 2) + + @trace(t, "my_trace") + def body(): + trace_mode(TraceMode.EventTime) + trace_packet(1, TracePacketType.Core) + trace_event("INSTR_EVENT_0") + trace_event("INSTR_VECTOR") + trace_start(event="TRUE") + trace_stop(event="NONE") + + +# CHECK-LABEL: traceAllModes +# CHECK: aie.trace @trace_et +# CHECK: aie.trace.mode "Event-Time" +# CHECK: aie.trace @trace_ep +# CHECK: aie.trace.mode "Event-PC" +# CHECK: aie.trace @trace_ex +# CHECK: aie.trace.mode Execution +@construct_and_print_module +def traceAllModes(): + @device(AIEDevice.npu1_1col) + def device_body(): + t = tile(0, 2) + + @trace(t, "trace_et") + def body1(): + trace_mode(TraceMode.EventTime) + trace_start(event="TRUE") + trace_stop(event="NONE") + + @trace(t, "trace_ep") + def body2(): + trace_mode(TraceMode.EventPC) + trace_start(event="TRUE") + trace_stop(event="NONE") + + @trace(t, "trace_ex") + def body3(): + trace_mode(TraceMode.Execution) + trace_start(event="TRUE") + trace_stop(event="NONE") + + +# CHECK-LABEL: tracePacketTypes +# CHECK: aie.trace.packet id = 1 type = core +# CHECK: aie.trace.packet id = 2 type = mem +# CHECK: aie.trace.packet id = 3 type = shimtile +# CHECK: aie.trace.packet id = 4 type = memtile +@construct_and_print_module +def tracePacketTypes(): + @device(AIEDevice.npu1_1col) + def device_body(): + t = tile(0, 2) + + @trace(t, "t1") + def body1(): + trace_packet(1, TracePacketType.Core) + trace_start(event="TRUE") + trace_stop(event="NONE") + + @trace(t, "t2") + def body2(): + trace_packet(2, TracePacketType.Mem) + trace_start(event="TRUE") + trace_stop(event="NONE") + + @trace(t, "t3") + def body3(): + trace_packet(3, TracePacketType.ShimTile) + trace_start(event="TRUE") + trace_stop(event="NONE") + + @trace(t, "t4") + def body4(): + trace_packet(4, TracePacketType.MemTile) + trace_start(event="TRUE") + trace_stop(event="NONE") + + +# CHECK-LABEL: traceEventWithLabel +# CHECK: aie.trace.event <"INSTR_EVENT_0"> label = "start_marker" +# CHECK: aie.trace.event <"INSTR_EVENT_1"> +@construct_and_print_module +def traceEventWithLabel(): + @device(AIEDevice.npu1_1col) + def device_body(): + t = tile(0, 2) + + @trace(t, "my_trace") + def body(): + trace_event("INSTR_EVENT_0", label="start_marker") + trace_event("INSTR_EVENT_1") + trace_start(event="TRUE") + trace_stop(event="NONE") + + +# CHECK-LABEL: tracePort +# CHECK: aie.trace.port<0> port = DMA channel = 0 direction = S2MM +# CHECK: aie.trace.port<1> port = DMA channel = 1 direction = MM2S +# CHECK: aie.trace.port<2> port = North channel = 0 direction = S2MM +@construct_and_print_module +def tracePort(): + @device(AIEDevice.npu1_1col) + def device_body(): + t = tile(0, 2) + + @trace(t, "my_trace") + def body(): + trace_port(0, WireBundle.DMA, 0, DMAChannelDir.S2MM) + trace_port(1, WireBundle.DMA, 1, DMAChannelDir.MM2S) + trace_port(2, WireBundle.North, 0, DMAChannelDir.S2MM) + trace_start(event="TRUE") + trace_stop(event="NONE") + + +# CHECK-LABEL: traceStartStopBroadcast +# CHECK: aie.trace.start broadcast = 15 +# CHECK: aie.trace.stop broadcast = 14 +@construct_and_print_module +def traceStartStopBroadcast(): + @device(AIEDevice.npu1_1col) + def device_body(): + t = tile(0, 2) + + @trace(t, "my_trace") + def body(): + trace_start(broadcast=15) + trace_stop(broadcast=14) + + +# CHECK-LABEL: traceStartStopEvent +# CHECK: aie.trace.start event = <"BROADCAST_15"> +# CHECK: aie.trace.stop event = <"BROADCAST_14"> +@construct_and_print_module +def traceStartStopEvent(): + @device(AIEDevice.npu1_1col) + def device_body(): + t = tile(0, 2) + + @trace(t, "my_trace") + def body(): + trace_start(event="BROADCAST_15") + trace_stop(event="BROADCAST_14") + + +# CHECK-LABEL: traceComboEvent +# CHECK: aie.trace.combo_event<0> <"LOCK_STALL"> AND <"STREAM_STALL"> +# CHECK: aie.trace.combo_event<1> <"INSTR_EVENT_0"> OR <"INSTR_VECTOR"> +# CHECK: aie.trace.combo_event<2> <"COMBO_EVENT_0"> AND_NOT <"COMBO_EVENT_1"> +@construct_and_print_module +def traceComboEvent(): + @device(AIEDevice.npu1_1col) + def device_body(): + t = tile(0, 2) + + @trace(t, "my_trace") + def body(): + trace_combo_event(0, "LOCK_STALL", ComboLogic.AND, "STREAM_STALL") + trace_combo_event(1, "INSTR_EVENT_0", ComboLogic.OR, "INSTR_VECTOR") + trace_combo_event(2, "COMBO_EVENT_0", ComboLogic.AND_NOT, "COMBO_EVENT_1") + trace_start(event="TRUE") + trace_stop(event="NONE") + + +# CHECK-LABEL: traceEdgeEvent +# CHECK: aie.trace.edge_event<0> event = <"LOCK_STALL"> trigger = RISING +# CHECK: aie.trace.edge_event<1> event = <"DMA_S2MM_0_START_TASK"> trigger = BOTH +@construct_and_print_module +def traceEdgeEvent(): + @device(AIEDevice.npu1_1col) + def device_body(): + t = tile(0, 2) + + @trace(t, "my_trace") + def body(): + trace_edge_event(0, "LOCK_STALL", EdgeTrigger.RISING) + trace_edge_event(1, "DMA_S2MM_0_START_TASK", EdgeTrigger.BOTH) + trace_start(event="TRUE") + trace_stop(event="NONE") + + +# CHECK-LABEL: traceStartConfig +# CHECK: aie.trace @cfg_trace +# CHECK: aie.runtime_sequence @seq() +# CHECK: aie.trace.start_config @cfg_trace +@construct_and_print_module +def traceStartConfig(): + @device(AIEDevice.npu1_1col) + def device_body(): + t = tile(0, 2) + + @trace(t, "cfg_trace") + def body(): + trace_mode(TraceMode.EventTime) + trace_packet(1, TracePacketType.Core) + trace_event("INSTR_EVENT_0") + trace_start(event="TRUE") + trace_stop(event="NONE") + + @runtime_sequence() + def seq(): + trace_start_config("cfg_trace") + + +# CHECK-LABEL: traceBufferSize +# CHECK: aie.trace @sized_trace(%{{.*}}) buffer_size = 8192 +@construct_and_print_module +def traceBufferSize(): + @device(AIEDevice.npu1_1col) + def device_body(): + t = tile(0, 2) + + @trace(t, "sized_trace", buffer_size=8192) + def body(): + trace_event("INSTR_EVENT_0") + trace_start(event="TRUE") + trace_stop(event="NONE") + + +# CHECK-LABEL: traceFullExample +# CHECK: %[[T02:.*]] = aie.tile(0, 2) +# CHECK: %[[T00:.*]] = aie.tile(0, 0) +# CHECK: aie.trace @core_trace(%[[T02]]) { +# CHECK: aie.trace.mode "Event-Time" +# CHECK: aie.trace.packet id = 1 type = core +# CHECK: aie.trace.event <"INSTR_EVENT_0"> +# CHECK: aie.trace.event <"MEMORY_STALL"> +# CHECK: aie.trace.port<0> port = DMA channel = 0 direction = S2MM +# CHECK: aie.trace.start event = <"BROADCAST_15"> +# CHECK: aie.trace.stop event = <"BROADCAST_14"> +# CHECK: } +# CHECK: aie.trace @shim_trace(%[[T00]]) { +# CHECK: aie.trace.packet id = 2 type = shimtile +# CHECK: aie.trace.event <"DMA_S2MM_0_START_TASK"> +# CHECK: aie.trace.start event = <"TRUE"> +# CHECK: aie.trace.stop event = <"NONE"> +# CHECK: } +# CHECK: aie.runtime_sequence @seq() { +# CHECK: aie.trace.start_config @core_trace +# CHECK: aie.trace.start_config @shim_trace +# CHECK: } +@construct_and_print_module +def traceFullExample(): + @device(AIEDevice.npu1_1col) + def device_body(): + t02 = tile(0, 2) + t00 = tile(0, 0) + + @trace(t02, "core_trace") + def core_body(): + trace_mode(TraceMode.EventTime) + trace_packet(1, TracePacketType.Core) + trace_event("INSTR_EVENT_0") + trace_event("MEMORY_STALL") + trace_port(0, WireBundle.DMA, 0, DMAChannelDir.S2MM) + trace_start(event="BROADCAST_15") + trace_stop(event="BROADCAST_14") + + @trace(t00, "shim_trace") + def shim_body(): + trace_packet(2, TracePacketType.ShimTile) + trace_event("DMA_S2MM_0_START_TASK") + trace_start(event="TRUE") + trace_stop(event="NONE") + + @runtime_sequence() + def seq(): + trace_start_config("core_trace") + trace_start_config("shim_trace") diff --git a/utils/generate_events_tablegen.py b/utils/generate_events_tablegen.py index 8ae4164dcf5..38d89c638fe 100755 --- a/utils/generate_events_tablegen.py +++ b/utils/generate_events_tablegen.py @@ -60,7 +60,7 @@ def MemTileEvent{suffix}: I32EnumAttr<"MemTileEvent{suffix}", "Memory tile event "name": "aie", "display_name": "AIE", "prefix": "XAIE_EVENTS_", - "suffix": "", # No suffix for AIE1 + "suffix": "AIE", }, "xaie_events_aieml.h": { "name": "aieml",