Architecture Overview
This page describes the application-specific topology of
Simple-ZCU111-Example on the Xilinx ZCU111 evaluation board
(Xilinx ZU28DR RFSoC). It covers what is unique to this application: the
8-channel ADC capture path, the 8-channel DAC output path, the
ring-buffer DMA model, the three local clock domains, and the startup
discipline that ties RTL and PyRogue together.
For platform-level RFSoC architecture (clock-domain CDC philosophy, DMA platform model, AXI-Lite hierarchy in general), see explanation/architecture.html.
Topology
The application is built around the Xilinx RFDC IP wrapped by
RfDataConverter, an AppRingBuffer block for capture and
loopback, and a RAM-based SigGen for DAC waveforms. Data movement
is split across two DMA lanes:
8-channel ADC capture path: RFDC ->
AppRingBuffer-> DMA lane 0 -> host TCP stream. Per-channel ring buffers are armed, triggered, and read back via PyRogue.8-channel DAC output path: DAC waveform RAM in
SigGen-> RFDC -> physical DAC pins. Loopback samples are also captured back intoAppRingBufferfor closed-loop debug.Ring-buffer DMA model with
DMA_SIZE_C = 2: lane 0 carries the ADC and DAC ring-buffer data; lane 1 is hard-wired loopback for debug. See AppPkg Constants for the constants and Top-Level RTL Entity for the entity surface.
Clock domains
Three asynchronous clock domains drive this application:
Clock |
Frequency |
Role |
|---|---|---|
|
100 MHz |
Register access (PS-facing AXI-Lite); also drives the platform
core’s |
|
DSP rate |
DSP and DAC sample bus ( |
|
ADC sample rate |
RFDC ADC output clock. Generated from the second RFDC PLL output. |
All three are declared as asynchronous clock groups in the XDC, so
Vivado’s timing engine does not attempt to close timing across them.
CDC crossings between any two domains use surf primitives
(Synchronizer for control/status, Ssr12ToSsr16Gearbox for the
ADC sample bus). For the platform-level CDC philosophy and the hub’s
treatment of clock-group declarations, see
explanation/architecture.html#clock-domains. Concrete signal
names and the XDC excerpt live in Top-Level RTL Entity.
DMA model
The application uses two DMA lanes (DMA_SIZE_C = 2 in
AppPkg Constants):
Lane 0 carries the ADC and DAC ring-buffer streams between the
AppRingBufferblock and the host. The PyRogueRootwires this lane to per-channelRingBufferProcessorconsumers and to the data-writer file sink.Lane 1 is a hard-wired loopback used for DMA debug; the host pushes a frame down lane 1 and reads the same frame back, which exercises the DMA engine independently of the RFDC and ring-buffer logic.
Adding lanes is a coordinated change: DMA_SIZE_C in
firmware/shared/rtl/AppPkg.vhd and the stream wiring in
firmware/python/simple_zcu111_example/_Root.py must be
updated together. For the platform-level DMA model (engine, AXI
Stream framing, host TCP-bridge convention), see
explanation/architecture.html#dma-model.
Sample bus
The parallel sample bus between RFDC and Application is a
Slv256Array: 16 samples * 16 bits = 256 bits per channel per
dspClk cycle. The SAMPLE_PER_CYCLE_C constant in
AppPkg Constants ties the RTL generics
(ADC_SAMPLE_PER_CYCLE_G, DAC_SAMPLE_PER_CYCLE_G,
SAMPLE_PER_CYCLE_G) to the PyRogue smplPerCycle parameter.
The 16-sample width is fixed for this application; changing it
requires synchronized RTL and Python updates. The PyRogue offsets and
register addresses tied to this bus are documented in
Application Register Map.
Startup discipline
The application’s PyRogue Application device is instantiated with
enabled=False and is only enabled after the dspClk is stable.
The Root.start() method enforces the order: user-logic reset,
clock chip initialization, DSP-reset wait, application enable, RFDC
initialization and MTS sync, YAML config load, and finally the
SigGen waveform load. Bypassing this sequence (for example, enabling
Application before dspClk is running, or loading the SigGen
waveform before MTS sync) is a known anti-pattern; registers that
depend on dspClk will read back zero or DECERR.