Register Map
This page documents the AXI-Lite addressing model used across all axi-soc-ultra-plus-core
platform designs. The same hierarchical crossbar pattern appears in every Simple-*-Example
application repo; only the per-application sub-indices (documented in each application repo’s
own reference pages) differ.
Hierarchical AXI-Lite crossbar
The Zynq UltraScale+ PS initiates all AXI-Lite register transactions. These are forwarded
across the PS-to-PL boundary through the AxiSocUltraPlusCore block and dispatched by a
two-level crossbar tree:
Top-level crossbar (inside the application’s top-level VHDL entity):
PS AXI-Lite master
│
▼
AxiSocUltraPlusCore (AXI-Lite bridge)
│
▼
Top-level AxiLiteCrossbar (3 slaves, 28-bit decode)
├─ [0] HW_INDEX_C → AxiSocUltraPlusCore registers
├─ [1] RFDC_INDEX_C → RfDataConverter / RFDC IP registers
└─ [2] APP_INDEX_C → Application crossbar (second level)
Application-level crossbar (inside Application.vhd):
APP_INDEX_C slave
│
▼
Application AxiLiteCrossbar (N slaves, 24-bit decode)
├─ [0] → AppRingBuffer registers
└─ [1] → DacSigGen registers
└─ ... (per-application; documented in each application repo)
The full address of any register is the sum of offsets along the path from the PS base address
to the register’s leaf node. Python-side offsets in pr.Device subclasses must match the
VHDL crossbar configuration exactly.
genAxiLiteConfig pattern
The surf library function genAxiLiteConfig generates the AXIL_CONFIG_C constant
array that parameterizes every AxiLiteCrossbar instance:
-- Declare the number of slaves and index constants
constant NUM_AXIL_MASTERS_C : natural := 3;
constant HW_INDEX_C : natural := 0;
constant RFDC_INDEX_C : natural := 1;
constant APP_INDEX_C : natural := 2;
-- Generate the crossbar configuration
constant AXIL_CONFIG_C : AxiLiteCrossbarMasterConfigArray(NUM_AXIL_MASTERS_C-1 downto 0) :=
genAxiLiteConfig(NUM_AXIL_MASTERS_C, AXIL_BASE_ADDR_G, 28, 26);
Arguments to genAxiLiteConfig:
NUM_MASTERS— number of slave slotsBASE_ADDR— base address of the crossbar (passed in as a generic)ADDR_BITS— total decode width in bitsDECODE_BITS— bits used to select among slaves (upper bits of the decode window)
Each slave’s base address is then AXIL_CONFIG_C(INDEX).baseAddr.
The Python device tree mirrors these addresses:
# Top-level offsets from the PS base address 0x04_0000_0000
# HW_INDEX_C=0 → AxiSocCore at offset 0x0000_0000
# RFDC_INDEX_C=1 → Rfdc device at offset determined by AXIL_CONFIG_C(1).baseAddr
# APP_INDEX_C=2 → Application device at offset 0xA000_0000
self.add(soc_core.AxiSocCore(
offset = 0x0000_0000,
))
self.add(Application(
offset = 0xA000_0000,
))
Signal initialization for undriven slaves uses the DECERR constant:
signal axilReadSlaves : AxiLiteReadSlaveArray(NUM_AXIL_MASTERS_C-1 downto 0) :=
(others => AXI_LITE_READ_SLAVE_EMPTY_DECERR_C);
signal axilWriteSlaves : AxiLiteWriteSlaveArray(NUM_AXIL_MASTERS_C-1 downto 0) :=
(others => AXI_LITE_WRITE_SLAVE_EMPTY_DECERR_C);
Any unmapped address returns DECERR rather than hanging the bus.
Slv256Array sample bus
ADC and DAC sample data between the RFDC wrapper and the application logic flows on a
256-bit parallel bus at dspClk (312.5 MHz). The type is defined in surf.StdRtlPkg:
-- 4 ADC channels, each 256 bits wide
dspAdc : in Slv256Array(3 downto 0);
-- 2 DAC channels, each 256 bits wide
dspDac : out Slv256Array(1 downto 0);
Each 256-bit word carries SAMPLE_PER_CYCLE_C = 16 samples of 16 bits each:
16 samples × 16 bits = 256 bits per channel per
dspClkcycleAt 312.5 MHz, this yields an effective sample rate of 312.5 MHz × 16 = 5 GS/s throughput (the actual ADC/DAC sample rate is set by the RFDC tile configuration)
The Slv256Array type is generic across all channel counts. Each application’s top-level
entity declares the port widths to match its board’s RFDC configuration.
AppPkg constants pattern
Each application repo contains a VHDL package AppPkg.vhd that declares the
design-wide constants tying RTL generics to Python parameters:
package AppPkg is
-- DMA lane count: lane 0 = ring buffer data, lane 1 = loopback debug
constant DMA_SIZE_C : positive := 2;
-- Samples per dspClk cycle — must match smplPerCycle in Python Application device
constant SAMPLE_PER_CYCLE_C : positive := 16;
-- AXI-Lite clock frequency (used by surf timing primitives)
constant AXIL_CLK_FREQ_C : real := 100.0E+6;
end package AppPkg;
These constants are consumed by RTL generics throughout the design:
U_AppRingBuffer : entity axi_soc_ultra_plus_core.AppRingBuffer
generic map (
ADC_SAMPLE_PER_CYCLE_G => SAMPLE_PER_CYCLE_C,
DAC_SAMPLE_PER_CYCLE_G => SAMPLE_PER_CYCLE_C,
DMA_SIZE_G => DMA_SIZE_C)
port map (...);
The Python Application device must pass a matching value:
self.add(hardware.AppRingBuffer(
numAdcCh = NUM_ADC_CH_C,
numDacCh = NUM_DAC_CH_C,
smplPerCycle = SAMPLE_PER_CYCLE_C, # must equal AppPkg.SAMPLE_PER_CYCLE_C
))
Changing SAMPLE_PER_CYCLE_C requires synchronized updates in both the VHDL package and
the Python device tree. The two values are not automatically cross-checked at build time.
External references
Zynq UltraScale+ Devices Register Reference (UG1087) — authoritative PS register map for boot-mode, PMU, and diagnostic register addresses referenced in platform debug recipes.