• Bluesky
  • Etsy
  • GitHub
Hexxed BitHeadz

Hexxed BitHeadz

  • Blogs
  • Project Show Room
  • Jokes
  • Contact Us
  • About Us
FPGA, Informative, Technical

Wake Up, Arty: The PHY is Calling

Izzny
April 11, 2025

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:

  1. 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.
  2. 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:

  1. 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.
  2. 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.
  3. add_seq_num.vhd
    Inserts a 16-bit sequence number into the payload of each UDP packet.
  4. 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.
  5. 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!

Author

Izzny Avatar

Written by

Izzny
Marluan (Izzny) – Penetration Tester with a deep love for breaking things (legally😎) and understanding how they tick. I’m currently pursuing my bachelor’s degree while constantly leveling up through hands-on research, labs and other projects. My switch into cybersecurity was fueled by pure curiosity—the need to understand systems, uncover vulnerabilities, and sharpen the skills to both attack and defend.

Recent Posts

  • GonkWare v0.49
    Malware, Python, Resources, Technical

    GonkWare v0.49

    Hexxed BitHeadz
  • Out Of Office – BSides Buffalo
    Informative, Uncategorized

    Out Of Office – BSides Buffalo

    Hexxed BitHeadz
  • OOO – DEFCON
    Informative

    OOO – DEFCON

    Hexxed BitHeadz
  • GonkWare v0.43
    Malware, Python, Resources, Technical

    GonkWare v0.43

    Hexxed BitHeadz

Categories

  • Android
  • FPGA
  • Informative
  • Malware
  • Personal
  • Pi-Party
  • Python
  • Resources
  • Technical
  • Uncategorized
←Re-Vita-Lized
Available Quickhack: GonkWare→
Click to Copy