Tutorial
Building a Multi-Master System with Axion Bridge & Cocotb
By Axion Team • 15 min read
Complex FPGA designs often require multiple distinct register spaces. In this tutorial, we'll generate
two separate register modules using Axion-HDL, connect them using the axion-common bridge,
and verify everything with Cocotb.
1. Define Register Spaces
We'll define two separate modules in YAML: master_ctrl (Base: 0x1000) and
peripheral_status (Base: 0x2000).
master_ctrl.yaml
module: master_ctrl
base_addr: "0x1000"
registers:
- name: bridge_enable
addr: "0x00"
peripheral_status.yaml
module: peripheral_status
base_addr: "0x2000"
registers:
- name: device_id
addr: "0x00"
access: RO
2. Generate Everything
$ axion-hdl -s src/ -o output/ --all
Found 2 modules: master_ctrl, peripheral_status
✓ master_ctrl_axion_reg.vhd
✓ peripheral_status_axion_reg.vhd
3. System Integration
We use the axi_lite_crossbar from the axion-common library to route traffic to
the correct slave based on the address.
library axion_common;
use axion_common.axion_common_pkg.all;
-- ... Architecture ...
-- Instantiate Axion Common Bridge
inst_bridge : entity work.axion_axi_lite_bridge
generic map (
G_NUM_SLAVES => 2
-- Address decoding handled by slave modules
)
port map (
i_clk => clk,
i_rst_n => rst_n,
-- Upstream (Host)
M_AXI_M2S => upstream_m2s,
M_AXI_S2M => upstream_s2m,
-- Downstream (Slaves)
S_AXI_ARR_M2S => downstream_m2s,
S_AXI_ARR_S2M => downstream_s2m
);
4. Verification with Cocotb
We write a Python testbench to verify we can access both address spaces through the single slave interface.
@cocotb.test()
async def test_bridge_access(dut):
# 1. Enable Bridge (0x1000)
await axi_write(dut, 0x1000, 0x1)
# 2. Read Device ID from Peripheral (0x2000)
val = await axi_read(dut, 0x2000)
assert val == 0xDEADBEEF
$ make simulation
0.00ns INFO Starting Bridge Test...
150.00ns INFO Writing to Bridge Enable (0x1000)...
350.00ns INFO Reading Device ID (0x2000)...
650.00ns INFO Device ID Verified: 0xdeadbeef
1050.00ns INFO test_bridge_access passed