CASE STUDY · REMOTE FPGA UPDATE

Updating a deployed FPGA over its own data link

A second FPGA on the same board used to need a service cable for every firmware change. The update now runs over the 10-gigabit link the two chips already share: the image is written into the configuration flash beside the target, verified byte by byte, and one command reboots the chip into it.

AlgoSilicon engineering · 2026

On one board sit two large FPGAs: a Zynq UltraScale+ RFSoC running Linux, and a Kintex UltraScale beside it, joined by a 10-gigabit serial link. Changing the second chip's logic used to mean attaching a programming pod, because that chip forgets everything on power-off. We moved the whole update onto the link the two chips already use for data. An AI did the hands-on work in our Python2Verilog framework; an engineer set the direction at each step.

The remote update loop: firmware crosses the link into the configuration flash, one in-band register write reboots the target, the chip reloads from its own flash and the link relocks
The loop that removes the cable. Firmware crosses the link into the flash, one in-band command reboots the target, and the chip comes back on the image it just received.

Why the obvious path cannot ride the link

An FPGA holds its logic in volatile memory, so there are two ways to load it. One is to push the image straight into the chip's configuration memory, which takes effect at once and is lost at the next power-off. The other is to write the image into the flash chip beside it, which the FPGA reads by itself every time it powers up. Debug had always used the first, over a separate cable from a workstation.

The first path cannot ride the link. Loading the configuration memory rewrites the entire chip, the logic carrying the link included, so the transfer cuts itself off part way through. Writing the flash and then rebooting is the standard route for a remote update, and it is the one we built.

A flash channel on the data link

The flash beside the second chip is 64 megabytes and sits on the dedicated configuration pins. Those pins have a useful property: once a design is running, it can keep reaching them through an internal port, so the flash costs no ordinary package pins at all.

Frames whose header carries a tag divert into a flash channel while ordinary traffic passes through untouched. Behind the tag sits an SPI controller in its own free-running clock domain, so a stutter on the link cannot interrupt an erase already in progress. Bulk work moves 128 kilobytes per round trip. The flash command set stays on the host, so the logic on the FPGA knows no vendor opcodes and the same channel works against a different flash device.

A tagged frame header diverts firmware into a flash channel driven by an SPI controller in its own free-running clock domain, while ordinary data frames stay on the data path
One stream, two destinations. The tag on the frame header decides, and the SPI controller runs on its own clock so link jitter cannot corrupt an erase.

Three checks on the real board, each with a number. The device identifier read back over the link matched the exact part printed on the schematic. A single-sector test at a high address erased, programmed, read back and erased again, byte for byte. Then the full image: 8.56 megabytes written at address zero and read back in 165 seconds with no byte different.

The boot test refused, and the register argued with itself

Where an FPGA looks for its image at power-up is set by three mode pins, and the schematic printed a default that meant flash boot. A commanded reconfigure from those pins left the chip dark. Reading the configuration status register reported the mode pins as 000, which is not flash boot. The same register also reported that configuration had never finished, on a design that was demonstrably running and answering over the link.

A register that contradicts itself settles nothing, so the reconfigure attempt was the evidence that counted, and it had already answered. Back in the schematic, geometric extraction put the three mode pins on a 4-position DIP switch, with the printed default never set on the assembled board. The setting we wanted differed from the assembled one in a single bit, so one position had to move, and which direction means "on" did not matter.

Proving which copy the chip booted

One switch position later the chip configured itself. Configuration finishing is a weak proof, though: it does not say whether the chip loaded the flash copy or an earlier image left behind by the cable. A counter inside the design records how many bytes the SPI interface has sent, and it read 64, exactly the warm-up bytes a fresh load sends and not one more. The counter had a zero point, so this was a load that started from nothing.

A status register reporting mode pins 000 and configuration unfinished on a running design, beside a byte counter reading exactly the 64 warm-up bytes of a fresh load
The register disagreed with itself and settled nothing. The counter had to start from zero, so its reading of exactly the warm-up bytes named the boot source.

Then the whole thing went remote. One in-band register write, with the acknowledgement leaving the link before the chip restarted, so a silent failure could not be mistaken for a silent success. The internal reconfiguration port reloaded the design from flash, the link relocked a few seconds later, and both endpoints answered on the new image.

0 bytes wrong
9.54 MB of firmware programmed into the flash over the link and read back byte by byte, 146 sectors erased, programmed and verified in 295 seconds

The same flow on a wider link

The workflow was then re-proven on a wider path: two lanes bonded together, with the datapath inside the chip widened to 128 bits so the bonded capacity could be used. The same 256-kilobyte loopback went from 3.0 to 5.24 gigabits per second, and the link's user-side ceiling moved from 9.2 to 18.6 gigabits per second, so the limit now sits inside the chip rather than on the wire. Over that bonded path the full update ran again: 9.54 megabytes, 146 sectors erased, programmed and verified with no byte different, in 295 seconds, followed by a remote reboot with every endpoint answering on the first read. Programming and rebooting now run on a system that programmed itself.

Three rules we kept

Each of these went back into the framework as a check that now runs on every build.

Trust behaviour, not records. A status register read from a running design reported two things that cannot both be true. A record like that is not evidence of anything. Command the action for real and let the hardware answer.

Send the receipt before the action. A command that ends in a reboot loses its own reply, so success and failure look identical from the host. The acknowledgement leaves the link first, and only then does the chip restart.

A boot-source proof needs a zero point. To know which image a chip is running, watch a counter that must reset on a fresh load. Whether configuration reported that it finished says nothing about where the image came from.

Why this matters

An update that needs a person and a cable puts a floor under the cost of every fix in the field, and it decides what you are willing to ship. Moving the update onto the link the system already has removes that floor: the host programs the target's flash, sends one command, and the target comes back on the new image, power cycles included. What made it work was the same discipline throughout. Prove each layer against a real reading, and trust the state of the hardware over the document that describes it.

Do you need to update FPGAs that are already deployed?

We bring up multi-FPGA boards, high-speed links, and radio front ends from bare hardware to a measured, field-updatable system, one verified layer at a time. Tell us what you are standing up.

Design services