AXI4 Memory-Mapped Analysis
The VcdParser class has specific methods for identifying AXI4 memory-mapped signals in a VCD, plotting those signals on a timing diagram, and extracting accepted read and write bursts.
This page uses the histogram example as the reference design. In that example, the hist accelerator uses an AXI4 memory-mapped master interface named m_axi_gmem to read input samples and bin edges from memory and to write histogram counts back to memory.
Loading the AXI4 Memory-Mapped Signals
After you have created a VcdParser, you can load the AXI4 memory-mapped signals into the parser as follows.
The histogram example uses signals with names like:
apatb_hist_top.AESL_inst_hist.m_axi_gmem_ARADDR[63:0]apatb_hist_top.AESL_inst_hist.m_axi_gmem_RDATA[31:0]apatb_hist_top.AESL_inst_hist.m_axi_gmem_AWADDR[63:0]apatb_hist_top.AESL_inst_hist.m_axi_gmem_WDATA[31:0]
You can find these names from the printout of the VCD signals as described in parsing the VCD outputs. After identifying the AXI-MM prefix, you can load the interface signals with:
# Create a parsing class
vp = VcdParser(vcd)
# Get the clock signal name
clk_name = vp.add_clock_signal()
top_name = 'AESL_inst_hist'
gmem_prefix = f"{top_name}.m_axi_gmem_"
# Load the AXI-MM interface signals
aximm_sigs, aximm_bw = vp.add_aximm_signals(
prefix=gmem_prefix,
dir='both',
lite_only=False,
short_name_prefix='gmem',
)
print(aximm_sigs)
print(aximm_bw)
This loads the standard AXI4 read and write address, data, and handshake signals. For AXI4-Full interfaces it will also load burst-length and last-beat signals such as ARLEN, AWLEN, RLAST, and WLAST when they are present.
The short_name_prefix option keeps the labels compact on the timing diagram.
Plotting the Timing Diagram
Once the AXI-MM signals are loaded, you can plot the timing diagram using the same flow as for other VCD signals:
# Get the timing signals
sig_list = vp.get_td_signals()
# Create the timing diagram
td = TimingDiagram()
td.add_signals(sig_list)
trange = None
ax = td.plot_signals(
add_clk_grid=True,
trange=trange,
text_scale_factor=1e4,
text_mode='never',
)
_ = ax.set_xlabel('Time [ns]')
For AXI-MM analysis it is often useful to zoom into a smaller range after burst extraction so you can inspect:
ARVALIDandARREADYfor accepted read-address requestsRVALIDandRREADYfor returned read dataAWVALIDandAWREADYfor accepted write-address requestsWVALIDandWREADYfor write-data beats
Extracting AXI4-MM Bursts
You can extract both write bursts and read bursts directly from the AXI-MM signals:
write_bursts, read_bursts, clk_period = vp.extract_aximm_bursts(
clk_name=clk_name,
aximm_sigs=aximm_sigs,
)
print('Write bursts:')
for i, burst in enumerate(write_bursts):
print(
f"Burst {i}: addr=0x{int(burst['addr']):x}, "
f"tstart={burst['tstart']}, data_tstart={burst['data_tstart']}, "
f"data_tend={burst['data_tend']}, beat_type={burst['beat_type']}"
)
print('\nRead bursts:')
for i, burst in enumerate(read_bursts):
print(
f"Burst {i}: addr=0x{int(burst['addr']):x}, "
f"tstart={burst['tstart']}, data_tstart={burst['data_tstart']}, "
f"data_tend={burst['data_tend']}, beat_type={burst['beat_type']}"
)
For the histogram example, the extracted read bursts typically correspond to:
- input sample data reads
- bin-edge reads
and the extracted write bursts correspond to:
- histogram count writes
If you want a JSON report, the histogram example wrapper already does this through:
report = hist_test.extract_bursts()
The JSON includes both decimal data and fixed-width hexadecimal data_hex so the bus words are easier to inspect.
Meaning of Burst Timing Fields
Each extracted burst includes several timing fields. These fields distinguish the address phase from the data phase.
tstart
tstart is the time of the accepted address handshake for the burst.
- For reads, this is the cycle where
ARVALID && ARREADYis true. - For writes, this is the cycle where
AWVALID && AWREADYis true.
This means tstart marks when the request was accepted, not when the first data beat for that burst appeared.
With AXI4 it is legal for multiple read addresses to be accepted in consecutive cycles. In that case, several bursts can have back-to-back tstart values even though their returned data beats do not overlap.
data_tstart
data_tstart is the time of the first cycle represented in beat_type for this burst.
- For reads, this is the first cycle when this burst becomes the active burst on the
Rchannel. - For writes, this is the first cycle when this burst becomes the active burst on the
Wchannel.
If the burst starts immediately, then data_tstart == tstart. If the burst waits behind earlier requests, then data_tstart > tstart.
data_tend
data_tend is the time of the final cycle represented in beat_type for this burst.
This is the final active cycle tracked for the burst on the data channel, including transfer, idle, or stall cycles while that burst is active.
If all beats transfer without stalls, then the total active duration is approximately:
\[\text{data\_tend} - \text{data\_tstart} = (N - 1) \cdot T_{clk}\]where $N$ is the length of beat_type and $T_{clk}$ is the extracted clock period.
beat_type
beat_type is a per-cycle list describing what happened on the data channel while this burst was active.
The numeric values are defined by the beat_type_enum section of the JSON report:
0 = transfer1 = idle2 = stall
The JSON also includes beat_type_names, which is usually easier to read.
For read bursts:
transfermeansRVALID && RREADYidlemeansRVALID == 0stallmeansRREADY == 0
For write bursts:
transfermeansWVALID && WREADYidlemeansWVALID == 0stallmeansWREADY == 0
The important point is that beat_type only refers to the cycles when this burst is the active burst on the data channel. It does not mean that other outstanding requests do not exist.
Queueing and Outstanding Reads
When analyzing AXI4 read traffic, it is common to see:
- several bursts with consecutive
tstartvalues - later
data_tstartvalues for the second or third burst
This means the design issued multiple read requests quickly on the address channel and the bursts then completed later on the data channel in FIFO order.
The queue_wait_cycles field is useful here. It measures how many clock cycles elapsed between address acceptance and the start of the burst’s active data phase.
For example:
tstart = 425 nsdata_tstart = 585 nsclk_period_ns = 10 ns
implies:
\[\text{queue\_wait\_cycles} = \frac{585 - 425}{10} = 16\]so that burst waited 16 clock cycles after the address handshake before its data phase began.
Reading the Burst Data
Each burst includes:
data: bus words in decimal formdata_hex: the same words in fixed-width hexadecimal form
The hex form is often easier to compare against bus waveforms, while the decimal form is convenient for direct deserialization.
For example, if a read burst returns 32-bit float payload words, you can deserialize the decimal data list directly:
words = np.asarray(read_bursts[0]['data'], dtype=np.uint32)
values = read_array(words, elem_type=Float32, word_bw=32, shape=(16,))
print(values)
If you only want to inspect the raw bus traffic, data_hex is usually the better starting point.