Blog
[one board on one desk; received over Wi-Fi; the beacon only transmits, no ARP or ping yet] A USB camera now watches our FPGA bench. Its first frame read "ALINX" off a board our notes had called a QMTech Wukong for months: the same chip in a different package, so every pin we had probed was the wrong pin. Rebuilt for the ALINX AX7203, a beacon written in t27 put the board on the network. A Mac received 2929 of 3168 of its UDP frames over seven minutes, at the 7.45 a second the spec's timing predicts, and the board's LEDs, which the spec drives, showed link, gigabit and a heartbeat.

The FPGA bench now has an eye: a USB camera pointed at the board. The loop is build, load, look. t27c turns a spec into Verilog, the open toolchain (yosys, nextpnr-xilinx, prjxray) turns that into a bitstream, openFPGALoader loads it into the FPGA, and the camera reads what the board shows. On its first day the eye found that we had the wrong board in our notes. After that, a beacon written in t27 put the right board on the network.
The first frame read "ALINX" on the circuit board. Our hardware notes had called this board a QMTech Wukong for months, an XC7A200T chip in an FGG676 package. It is an ALINX AX7203: the same XC7A200T chip in an FBG484 package. Over JTAG the chip reports the same ID code, 0x3636093, in both packages, so no tool in the flow could tell them apart.
[measured] A bitstream made earlier for the AX7203 settled it. Clocked from the AX7203's 200 MHz oscillator, it answered 512 of 512 additions bit-exact over the AX7203's serial port.
Results that used only JTAG still stand, because those designs touch no package pin. Every search done on the pins was looking at the wrong ones, which is why a day of probing had found no clock on any pin.
specs/fpga/eth_beacon.t27 builds one UDP broadcast frame, byte by byte: the preamble, the broadcast address, an IPv4 header with its checksum, a UDP header, a payload of "T27E" and a frame counter, and the Ethernet checksum (CRC-32). Its 9 tests pin the IPv4 checksum, the header bytes, the CRC of "123456789" and the last four bytes of frames 0 and 1. Those four bytes were first computed with zlib, before the spec was written.
The Verilog around the spec is 40 lines of Xilinx primitives and nothing else: an input buffer for the 200 MHz clock, a PLL that makes 125 MHz plus a copy shifted by 90 degrees, and output cells that put each byte on the four RGMII wires as two 4-bit halves. The shifted copy is the transmit clock. It lands in the middle of each data bit, so the network chip needs no extra setup.
[measured] Gigabit Ethernet over RGMII needs the logic to run at 125 MHz. The first build reached 64.2 MHz: the position inside the frame was a 32-bit number, and subtracting from it put chains of adders in front of the CRC. With an 8-bit position and each byte fetched one clock ahead, it reached 94.6 MHz. With a table indexed by the byte's position in the frame and no arithmetic on that position, it reached 179.7 MHz. Every rewrite had to produce the same checksum bytes, and the tests checked that it did.
One rewrite failed a test before it reached the board. It wrote the byte going out and read that same byte within the same clock, which a test runs in one order and hardware in another. The checksum test caught the difference.
The spec drives the four user LEDs: link on port 1, link on port 2, a gigabit link, and a heartbeat that changes every four frames. The camera watched them. At first only the heartbeat blinked: the clock and the logic were alive, but neither port had a link.
[measured] A small sniffer, also written in t27 (specs/fpga/rgmii_sniff.t27) and read over JTAG, measured both network chips. Port 1 ran its receive clock at 25 MHz and saw nothing. Port 2 ran at 125 MHz and reported link, 1000 Mb/s and full duplex. The frames it received started with the expected preamble, 0x55555555. The cable was in port 2.
[measured] With the beacon loaded, a Mac on the same network received the board's datagrams from 192.168.1.227. Over seven unbroken minutes it got 2929 of the 3168 frames the board numbered (92.5%). They arrived at 7.45 a second, which is 125 MHz divided by the spec's gap of 2^24 clock cycles. The operating system only accepts a frame whose Ethernet checksum, IPv4 checksum and UDP header are all right, and it accepted every frame it received. The missing 7.5% were lost on the way to a Mac on Wi-Fi, where broadcast frames are never acknowledged or sent again.
The camera then showed what the spec said it would: the port 2 and gigabit LEDs lit, and the heartbeat blinking.
macOS gave no camera access to the command-line process that runs the builds, and the privacy settings have no button to add it. The same ffmpeg command, typed into the Claude app's own terminal, works, because the app itself asks for the camera. So the pipeline takes its pictures there.
Work with me
I work contract and part-time on hardware-AI, FPGA/RTL and ML systems — from specification and open toolchains to reproducible measurements.