Beyond Polling
While simple polling is effective for validation, it wastes valuable CPU cycles. By integrating interrupts into the Zynq-7000 architecture, we transform the system into an event-driven powerhouse. This allows the ARM Cortex-A9 to execute complex algorithms while the AXI DMA handles the heavy lifting of moving data between the PL and DDR memory.
Required Materials
- Hardware: Cora Z7-07S (Zynq-7000)
- Toolchain: Xilinx Vivado + SDK 2018.3
- Debug: Integrated Logic Analyzer (ILA) & Tera Term
DMA Interrupt & Debug Architecture
Hardware Implementation
The hardware transition requires exposing the IRQ_F2P (Fabric to Processor) port on the Zynq IP. Since the AXI DMA provides independent signals for MM2S (Memory to Stream) and S2MM (Stream to Memory), we utilize a Concat IP block to aggregate these signals into a single vector for the Generic Interrupt Controller (GIC).
1. Connect Handlers & Start GIC
static int SetupIntrSystem(INTC * IntcInstancePtr, XAxiDma * AxiDmaPtr, u16 TxIntrId, u16 RxIntrId)
{
// 1. Initialize Interrupt Controller
XIntc_Initialize(IntcInstancePtr, INTC_DEVICE_ID);
// 2. Connect Handlers (Callback functions)
XIntc_Connect(IntcInstancePtr, TxIntrId, (XInterruptHandler) TxIntrHandler, AxiDmaPtr);
XIntc_Connect(IntcInstancePtr, RxIntrId, (XInterruptHandler) RxIntrHandler, AxiDmaPtr);
// 3. Start GIC and enable specific hardware lines
XIntc_Start(IntcInstancePtr, XIN_REAL_MODE);
XIntc_Enable(IntcInstancePtr, TxIntrId);
XIntc_Enable(IntcInstancePtr, RxIntrId);
// 4. Initialize Hardware Exceptions
Xil_ExceptionInit();
Xil_ExceptionRegisterHandler(XIL_EXCEPTION_ID_INT, (Xil_ExceptionHandler)INTC_HANDLER, IntcInstancePtr);
Xil_ExceptionEnable();
return XST_SUCCESS;
}2. The Interrupt Service Routine (ISR)
static void TxIntrHandler(void *Callback)
{
u32 IrqStatus;
int TimeOut;
XAxiDma *AxiDmaInst = (XAxiDma *)Callback;
/* 1. Identify the Interrupt Source */
IrqStatus = XAxiDma_IntrGetIrq(AxiDmaInst, XAXIDMA_DMA_TO_DEVICE);
/* 2. Acknowledge and Clear */
// It is vital to acknowledge the interrupt immediately to clear
// the hardware latch and prepare for the next event.
XAxiDma_IntrAckIrq(AxiDmaInst, IrqStatus, XAXIDMA_DMA_TO_DEVICE);
/* 3. Filter Spurious Interrupts */
if (!(IrqStatus & XAXIDMA_IRQ_ALL_MASK)) {
return;
}
/* 4. Error Recovery Logic */
// If the Error mask is set, the DMA engine has halted.
// We set a global error flag and initiate a hardware reset.
if ((IrqStatus & XAXIDMA_IRQ_ERROR_MASK)) {
Error = 1;
XAxiDma_Reset(AxiDmaInst);
TimeOut = RESET_TIMEOUT_COUNTER;
while (TimeOut) {
if (XAxiDma_ResetIsDone(AxiDmaInst)) {
break;
}
TimeOut -= 1;
}
return;
}
/* 5. Completion Assertion */
// XAXIDMA_IRQ_IOC_MASK indicates "Interrupt on Complete."
// We set the volatile TxDone flag to notify the main-line code.
if ((IrqStatus & XAXIDMA_IRQ_IOC_MASK)) {
xil_printf("---- MM2S Interrupt is Active ----
");
TxDone = 1;
}
}Pro-Tip: Always use the volatile keyword for completion flags. Without it, the compiler optimizer may assume the variable never changes, causing your main loop to hang indefinitely.
Logic Inspection: The "Why" behind Latency
Using an Integrated Logic Analyzer (ILA), we can observe the micro-timing of these interrupts. A key observation in this project was the duration of the IRQ assertion.

ILA Capture: Note the gap between IRQ rise and de-assertion.
Software Latency Explained
The hardware clears the interrupt almost instantly, but the CPU takes time to finish the first ISR before it can jump to the next. In a baremetal environment, this latency is the cost of context switching and serial logging (xil_printf).
Results & Validation
---- Entering main() --- ---- MM2S Interrupt is Active ---- ---- S2MM Interrupt is Active ---- Data valid 0: sent C = received C PASS ... Successfully ran AXI DMA interrupt Example ---- Exiting main() ---
