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.

