CH1The DHO's Zynq and its golden image
The DHO800/900's acquisition FPGA is a Xilinx Zynq XC7Z015: programmable logic plus two ARM cores of its own. It boots from its own QSPI flash, which holds three standard Zynq boot images (first-stage loader, bitstream, a small bare-metal ARM program). The factory's golden image sits at offset 0. Every Rigol firmware update writes the acquisition image at 0x400000. Rigol's memory calibration writes and boots a third build at 0x800000. NovaOS runs the image that is already on the unit; it ships no FPGA image, and nothing in a NovaOS boot writes this flash.
At power-on the boot ROM always starts the golden image. Its ARM program is a command server on an SPI slave port wired to the RK3399's SPI1 (we decompiled the copy in the update image; the golden one behaves the same). To switch images, the RK3399 sends 0D 42 4F 4F 54 0D (carriage return, BOOT, carriage return) and then, as a second transfer, the target offset as four big-endian bytes, 00 40 00 00, in SPI mode 0 at 10 MHz. The program writes the offset in 32 KiB units into the Zynq's MULTIBOOT register and issues a system soft reset. The boot ROM then starts the image at that offset, its loader configures the logic, and the PCIe endpoint appears. The leading carriage return is required: the server matches commands in a five-byte buffer whose index is left part-way after every command, and the extra CR flushes it.
Nothing ever comes back. The program never writes its transmit FIFO, so the RK3399 cannot tell whether the command arrived, whether it was taken, or which image is running. Rigol's boot tool sleeps 5 seconds after the command and the start-up script sleeps 10 more, then loads the PCIe host driver once. If the link does not train, the script prints FAILED and the scope runs without its FPGA.
CH2Three signals the FPGA does give
The SPI return line. Rigol's soft power-on asks the golden image for its build date with \rVER?\r and clocks out 19 bytes of 0x0A to read the reply. Our unit's golden image returns no date, but the line still says something: it reads all ones (0xFF) while no command loop drives it, and 0x00 once one does. NovaOS resets the Zynq into its golden image at shutdown, and after such a reset we read 0xFF for about 10 seconds. When we tried sending the boot command from the early boot stage, 2.1 s after the kernel started, the Zynq dropped it on 6 of 6 boots. The bring-up now asks every 100 ms while the line reads 0xFF and sends the boot command at the first other reply. On six boots that reply came at the first ask, about 4 s after the kernel started, and every boot command was taken.
The PCIe endpoint. The RK3399's PCIe host trains the link once, when its driver probes. So the host driver is a module that NovaOS loads only after the boot command and unloads before anything resets the Zynq. The first probe comes 2 s after the boot command; while the endpoint is missing, the host is unloaded and loaded again every 2.5 s. Booting the update image into itself, we probed once at 1, 2, 3, 4 or 5 s, and every probe trained (at 1 s the endpoint was there 1.4 s after the command). From the golden image at boot, a 1 s probe still found the golden's own endpoint on 4 of 5 boots, and the retry that followed unloaded the XDMA driver while the Zynq was resetting, which can crash the kernel (the PCIe note has why). That is why the first probe stays at 2 s.
The build stamp. Once the endpoint enumerates, the bring-up reads the 32-bit word at offset 0x4000 of the FPGA's register window. Rigol's About page decodes it: the top four bits are a variant letter, the next four the year after 2021, then BCD month, day and hour. Our update image reads 0xE2072115, 2023-07-21 15:00, variant E. The golden image reads 0xE1080313, 2022-08-03 13:00, variant E. All ones means a dead link and zero means no register block behind the window; either fails the bring-up, and NovaOS unloads both drivers again so nothing writes registers into logic it cannot identify. The golden's stamp after a boot command means the command was dropped, so the bring-up sends it again, at most twice, then falls back to Rigol's timing (wait until 17.3 s of uptime, probe from 5 s). A golden stamp after that fails the bring-up, and the scope screen says why. Rigol's app logs this word and carries on whatever it reads.
Over seven measured boots every boot command was taken the first time, the endpoint enumerated 2.4 to 2.8 s after it, and the stamp read 0xE2072115 each time. Where that sits in the whole boot, and the PCIe driver bug this probing exposed, are in the boot note.
CH3The clock comes first
Both scopes program the sample clock before the FPGA starts. On the DHO it is a TI LMX2582 on a three-wire bus that the RK3399 bit-bangs on three GPIOs: 46 writes of 24 bits, R70 down to R0, and the last R0 sets FCAL_EN to start the VCO calibration. Rigol's tool waits 50 ms after every write, 2.3 s in all. We tried 1 ms between writes and 50 ms after R0, 0.11 s in all.
The first comparison, five boots of each image taken one image after the other, showed the fs/2 interleave spur about 5 dB worse with the fast load. We then booted one image 20 times, rotating through four timings five times over, with a 1 MHz sine on CH4. Every timing spread across the same -53 to -65 dBc, and the ADC clock check and the ADC core alignment came out identical on every boot. The spur follows the acquisition engine's start-up: restarting the engine alone, with the clock untouched, moves it over a similar range. The bring-up now uses the fast load. We keep Rigol's order, clock before FPGA, without having tested whether the Zynq's image needs it.
CH4The MHO: a bitstream file at every boot
The MHO900's FPGA has no configuration flash. At every boot the RK3399 reads Rigol's bitstream file from the scope's own storage and configures the FPGA in Xilinx slave-serial mode over SPI5, with the program, init and done lines on GPIOs. Our MHO934 reports hardware code 3, which selects the image whose header names a Fudan FMK230T (ID code 0x02236143); it takes the 7-series configuration protocol unchanged. Before sending anything, NovaOS checks the file's header and ID code against the part that hardware code implies.
The clock chip here is an LMK04826, programmed after its supplies settle and its chip ID reads back. Then the FPGA's supply comes on. Rigol waits a fixed 0.5 s; NovaOS waits for the FPGA's own init line, which went low and came back high 24 to 26 ms after the supply. Then the program pulse, init high again, the stream, and 0xFF bytes until done goes high. After that the version word: on the MHO it is a BCD date, 0x20260110 on ours, the build date in the bitstream's header.
The stream is 8,292,523 bytes, and Rigol's boot sends it in 2.45 s. Our first version took 3.46 to 3.49 s, about 1 ms lost around each of 2,025 transfers of 4 KiB. Transfers of 60 KiB brought that to 3.04 s. The rest was the bus clock. The SPI controller ran from 88.889 MHz; Linux 6.12's driver caps a transfer at half the controller clock, rounded down to 44,444,444 Hz, and computing the divider from that rounded figure gives 4 instead of 2, so the bus ran at 22.2 MHz. We gave the controller an even 80 MHz, so the divider is 2 and the bus runs at 40.000 MHz. The stream now takes 1.71 to 1.73 s, 1.659 s of it on the wire, and the FPGA is ready 1.2 s sooner in the boot. Sixteen-bit SPI words were no faster, so the stream stays in 8-bit words.
Measured on our own DHO924S and MHO934 in October 2026. The stock DHO figure is from Rigol's start-up log over 29 boots of the same unit; the stock MHO stream time is from a trace of Rigol's own boot on our MHO934. How the samples then cross the link: PCIe and XDMA.