PYNQ-Z2 Zynq-7000 GEM RX Interrupt Triggered Twice – Duplicate ARP/ICMP Replies (Bare-Metal)

Hello,

I am working on the PYNQ-Z2 (Zynq-7000) with part number XC7Z020-1CLG400C and implementing a bare-metal Ethernet driver using the PS Gigabit Ethernet Controller (GEM0). The interface is configured for 1000 Mbps, full duplex. I am not using lwIP; instead, I implemented a custom stack supporting ARP, IPv4, ICMP, TCP, and Modbus TCP.

The board is connected directly to a PC (board = server, PC = client). I am testing using the Windows ping command and monitoring traffic in Wireshark.

The issue is that for every ARP request or ICMP Echo Request sent from the PC, the board sends two replies. In Wireshark, I can clearly see duplicate ARP replies and duplicate ICMP Echo Replies. This happens consistently.

From debugging, it appears that the RX interrupt is being triggered twice for a single received frame, or the same RX descriptor is being processed twice. The Ethernet frame handler is called inside the RX ISR, which results in duplicate transmission.

I am currently using a single RX descriptor and a single TX descriptor. The RX interrupt is cleared using write-one-to-clear on ISR and RXSR. After processing, I return the descriptor to GEM by clearing word1 and restoring word0 with WRAP.

I have attached:

Β· The gem_rx_isr() implementation

Β· Screenshot of the command prompt ping output

Β· Wireshark capture showing duplicate replies

CODE:

static void gem_rx_isr(void *callback)

{

   XGpio_DiscreteWrite(&AXI_GPIO_2, 2, 0x0001);



(**void**)callback;



   /\* Clear interrupt first (W1C) \*/

   gem_write(XEMACPS_ISR_OFFSET, gem_read(XEMACPS_ISR_OFFSET));

   gem_write(XEMACPS_RXSR_OFFSET, gem_read(XEMACPS_RXSR_OFFSET));



   GemDesc \*d = rx_desc(0);



   Xil_DCacheInvalidateRange((uintptr_t)d, **sizeof**(GemDesc));



   uint32_t status = d->word1;

   uint32_t len    = status & RX_DESC_LEN_MASK1;

   uint32_t buf    = d->word0 & RX_DESC_ADDR_MASK0;

   uint32_t wrap   = d->word0 & RX_DESC_WRAP_MASK0;



   Xil_DCacheInvalidateRange(buf, len);



   handle_ethernet_frame((uint8_t \*)buf, len);

   rx_packets++;



   /\* Return descriptor to GEM (OWN=0) \*/

   d->word1 = 0;

   d->word0 = buf | wrap;



   Xil_DCacheFlushRange((uintptr_t)d, **sizeof**(GemDesc));

   XGpio_DiscreteWrite(&AXI_GPIO_2, 2, 0x0000);

}

SCREENSHOT OF THE COMMAND PROMPT AND WIRESHARK:

My questions are:

1. Can FRAMERX interrupt be triggered twice for a single frame?

2. Should I explicitly check the RX descriptor OWN bit before processing?

3. Should the RX ISR loop through descriptors until ownership changes?

4. Could using only one RX descriptor cause repeated interrupts?

5. Is my interrupt clearing sequence (ISR + RXSR) correct?

My goal is to ensure one interrupt and one reply per received frame.

Any guidance on proper RX interrupt and descriptor handling for bare-metal GEM would be appreciated.

Thank you.

Hi @Kesar_Bavda

What PYNQ image are you using?

If you’re using v3.0 then can you try V3.1 or vice versa and see if you observe the same behaviour?

I am not using the PYNQ Linux image.

I create the hardware design (PS + PL) in Vivado and export it to SDK. In SDK, I create a Hello World (C) application project, and inside that template I replace the code with my own C implementation for the PS Gigabit Ethernet (GEM) controller.

I run the application directly using JTAG (Run As β†’ Launch on Hardware). I am not manually creating or using an FSBL, and no Linux image is involved in my setup.

Since this is a pure bare-metal C application running on the PS and downloaded via JTAG, the PYNQ image version (v3.0 or v3.1) should not affect the GEM behavior in my case.

Please let me know if there is any specific PS clock or GEM initialization detail that I should verify.

This forum is mainly for questions around the PYNQ runtime or PYNQ image SD build flow. If you are using the Pynq-Z2 board as a standard FPGA-based SoC, then I would recommend you reach out on AMD’s adaptive forum: https://adaptivesupport.amd.com/

1 Like