Previously on “Challenge Accepted: FPGA Ethernet Filters” I started this ambitious (to say the least 😆) journey, of building an FPGA-based network filter. I spent time understanding, debugging and wrapping my head around the whole setup and implementation, without realizing one simple and painful fact: the Ethernet physical connection was not working.
My goal was to have the FPGA filter the incoming packets at the hardware level. Every step of the process worked, and then I hit a wall of “UUU”. The “U” stands for uninitialized, meaning that the board had no idea what to do with data frames within the simulation. That meant I needed to go back to the drawing board- which this blog is all about.

I assumed programming the FPGA to filter traffic would be as simple as making buttons blink or toggling LEDs. I was very wrong. Mad respect to every FPGA engineer and hobbyist out there—this stuff is no joke! I digress…

In my mind, I’d write a little VHDL or drag some blocks around, and boom: functional board. But I missed the fact that the board wasn’t doing anything at all. It wasn’t programmed, nothing was connected—it was just sitting there (hence the Field Programmable part 🙄). Thinking of the FPGA as a plug-and-play “router” was my downfall.
So I had to step back and ask: can I even program this board properly in Vivado? Can I get Ethernet working? Let’s find out.

Fixing the Constraint File
The first version of the constraint file mapped only the essential RX and TX signals I was working with in my VHDL module—signals like rx_clk
, rx_data
, tx_en
, and tx_data
. However, while debugging why Ethernet communication wasn’t working, I realized two important things:
- Clock Conflicts: I was using the same pin (
H16
) for both the system clock and the Ethernet transmit clock (tx_clk
). That’s… not great, since it might’ve been the caused of my timing issues during synthesis and implementation. - Missing PHY Signals: The first constraint file didn’t account for important signals used by the Ethernet PHY. Without properly configuring these pins, the PHY wasn’t being managed correctly—and as I found out, that can completely prevent Ethernet from functioning.
So, I redid the constraint file. This version includes a complete map of all Ethernet-related pins, fixed the clock configuration (using CLK100MHZ
from the board’s oscillator), and ensured that all PHY control lines were accounted for.
## Clock signal
set_property -dict { PACKAGE_PIN E3 IOSTANDARD LVCMOS33 } [get_ports { CLK100MHZ }];
create_clock -add -name sys_clk_pin -period 10.00 -waveform {0 5} [get_ports { CLK100MHZ }];
## Switches
set_property -dict { PACKAGE_PIN A8 IOSTANDARD LVCMOS33 } [get_ports { sw[0] }];
set_property -dict { PACKAGE_PIN C11 IOSTANDARD LVCMOS33 } [get_ports { sw[1] }];
set_property -dict { PACKAGE_PIN C10 IOSTANDARD LVCMOS33 } [get_ports { sw[2] }];
set_property -dict { PACKAGE_PIN A10 IOSTANDARD LVCMOS33 } [get_ports { sw[3] }];
## SMSC Ethernet PHY
set_property -dict { PACKAGE_PIN D17 IOSTANDARD LVCMOS33 } [get_ports { eth_col }];
set_property -dict { PACKAGE_PIN G14 IOSTANDARD LVCMOS33 } [get_ports { eth_crs }];
set_property -dict { PACKAGE_PIN F16 IOSTANDARD LVCMOS33 } [get_ports { eth_mdc }];
set_property -dict { PACKAGE_PIN K13 IOSTANDARD LVCMOS33 } [get_ports { eth_mdio }];
set_property -dict { PACKAGE_PIN G18 IOSTANDARD LVCMOS33 } [get_ports { eth_ref_clk }];
set_property -dict { PACKAGE_PIN C16 IOSTANDARD LVCMOS33 } [get_ports { eth_rstn }];
set_property -dict { PACKAGE_PIN F15 IOSTANDARD LVCMOS33 } [get_ports { eth_rx_clk }];
set_property -dict { PACKAGE_PIN G16 IOSTANDARD LVCMOS33 } [get_ports { eth_rx_dv }];
set_property -dict { PACKAGE_PIN D18 IOSTANDARD LVCMOS33 } [get_ports { eth_rxd[0] }];
set_property -dict { PACKAGE_PIN E17 IOSTANDARD LVCMOS33 } [get_ports { eth_rxd[1] }];
set_property -dict { PACKAGE_PIN E18 IOSTANDARD LVCMOS33 } [get_ports { eth_rxd[2] }];
set_property -dict { PACKAGE_PIN G17 IOSTANDARD LVCMOS33 } [get_ports { eth_rxd[3] }];
set_property -dict { PACKAGE_PIN C17 IOSTANDARD LVCMOS33 } [get_ports { eth_rxerr }];
set_property -dict { PACKAGE_PIN H16 IOSTANDARD LVCMOS33 } [get_ports { eth_tx_clk }];
set_property -dict { PACKAGE_PIN H15 IOSTANDARD LVCMOS33 } [get_ports { eth_tx_en }];
set_property -dict { PACKAGE_PIN H14 IOSTANDARD LVCMOS33 } [get_ports { eth_txd[0] }];
set_property -dict { PACKAGE_PIN J14 IOSTANDARD LVCMOS33 } [get_ports { eth_txd[1] }];
set_property -dict { PACKAGE_PIN J13 IOSTANDARD LVCMOS33 } [get_ports { eth_txd[2] }];
set_property -dict { PACKAGE_PIN H17 IOSTANDARD LVCMOS33 } [get_ports { eth_txd[3] }];

So then… I hit a roadblock.
I’d never done this before, and I was stuck asking myself: What else does this thing need to actually work?!
That’s when my partner in crime stepped in and said:
“I’m sure someone’s done this before. Let’s look!” Aromak
And lo and behold… he found a Github repo that had exactly what I needed!

The project shows how to build and send raw UDP packets directly from an FPGA using the Ethernet PHY on a Arty A7 board. Now, noticed that the board constraints and pin were different, but I have updated the constraints to match my Arty A7 version.
Now while analyzing the files, I came across some terms that I’ve heard before but never found the correlation to network and transmissions util now.
Here it comes FCS and CRC
So here’s the thing: just because the Ethernet frame leaves the FPGA doesn’t mean it’s going to make it to the other side unscathed. Stuff happens—electrical noise, bit flips, solar flares (probably), maybe Neo and Morpheus fighting!

That’s where the Frame Check Sequence (FCS)comes in. The FCS is a final stamp on the packet saying, “Hey, this is what I should look like when I get there. If I am not remotely close to how I should be.. get rid of me. ” The FCS is calculated using a Cyclic Redundancy Check (CRC)—basically a fancy math trick that crunches your whole packet into a short checksum value. Its main purpose is to protect against communication errors and integrity of the data.
However, before the frame even gets to that point, there’s the Start Frame Delimiter (SFD) -this pattern tells the receiver, “Hey! Real data starts right after me.” It helps the hardware sync up and know exactly when to start listening.
For network comms, all of this is non-negotiable:
- 🔍 If a single bit goes sideways during transmission, the CRC won’t match— packet gets dropped.
- 🧼 No FCS? Bye Felicia!. The receiver just tosses it out.
- ⚡ It’s fast, hardware-friendly, and absolutely essential when you’re building frames from scratch like.. yours truly.
Now, this is a UDP-based project—there’s no TCP handshake, no retransmission, no error recovery. So if it doesn’t make it… well… it’s out there somewhere, in the ether.

Here is a small breakdown of what the VHDL files do:
add_crc32.vhd
This file is in charge of implementing the CRC32 checksum calculation and appends the Frame Check Sequence (FCS) to ensure data integrity during transmission.add_preamble.vhd
Adds the standard Ethernet preamble and Start Frame Delimiter (SFD) to the beginning of each Ethernet frame, which helps receivers synchronize with incoming data streams.add_seq_num.vhd
Inserts a 16-bit sequence number into the payload of each UDP packet.ethernet_test.vhd
Serves as the top-level module that integrates all components. It manages the overall flow of data, coordinating the generation and transmission of Ethernet frames.nibble_data.vhd
Generates the UDP packet payloads.
So.. What Happened?
I got the VHDL files, an updated constraint file—I was ready to roll. I loaded everything into Vivado, ran synthesis, implementation, and even generated the bitstream without any major issues! 🙌
But when it came time to test the connection… my host couldn’t even recognize the board. 😩
Why?!
I wish I knew! I had all the signals mapped, the PHY looked wired correctly, the CRC was clean and the board was silent.
I realized—this wasn’t just about getting a bitstream or not getting the wall of “UUU” anymore, it was about understanding the hardware: clocks, resets, PHY behavior, voltages, etc.
In conclusion… no, I didn’t finish.
But that’s what makes this journey worth writing about.
The next step? Figuring out what the board actually needs to start talking.
But is going to be a while.. there are some other good stuff in store for you!
Izzny out!
