APB interface modules for Cocotb
GitHub repository: https://github.com/daxzio/cocotbext-apb
Introduction
APB simulation models for cocotb.
The APB protocol is cover in these documents APB Protocol Specification and APB Architecture Specification
Installation
Installation from pip (release version, stable):
$ pip install cocotbext-apb
Installation from git (latest development version, potentially unstable):
$ pip install https://github.com/daxzio/cocotbext-apb/archive/main.zip
Installation for active development:
$ git clone https://github.com/daxzio/cocotbext-apb
$ pip install -e cocotbext-apb
Documentation and usage examples
See the tests directory for complete testbenches using these modules.
APB Write
APB Read
APB Bus
The APBBus is used to map to a APB interface on the dut. Class methods from_entity and from_prefix are provided to facilitate signal default name matching.
Required:
- psel
- pwrite
- paddr
- pwdata
- pready
- prdata
Optional:
- pstrb
- pprot
- pslverr
APB Host
The ApbHost class implements an APB driver and is capable of generating read and write operations against APB devices.
The host automatically handles data wider than the bus width by splitting transactions into multiple sequential APB accesses at consecutive addresses. This allows seamless transfers of wide data values across narrower APB interfaces.
To use these modules, import the one you need and connect it to the DUT:
from cocotbext.apb import ApbHost, ApbBus
bus = ApbBus.from_prefix(dut, "s_apb")
apb_driver = ApbHost(bus, dut.clk)
The first argument to the constructor accepts an ApbBus object. These objects are containers for the interface signals and include class methods to automate connections.
Once the module is instantiated, read and write operations can be initiated in a couple of different ways.
ApbMaster is a subclass of ApbHost and remains available for existing testbenches.
ApbHost constructor parameters
- bus:
ApbBusobject containing APB interface signals - clock: clock signal
- timeout_max: Maximum clock cycles to wait for
preadysignal before timing out (optional, default1000). Set to-1to disable timeout. - reset: reset signal (optional)
- reset_active_level: reset active level (optional, default
True)
Additional optional arguments for ApbHost
- seednum: For random testing a seed can be supplied, default
None, random seed.
Address Mapping
The ApbHost supports address mapping through its addrmap attribute, an
AddressMap instance. Register names can be used instead of
numeric addresses in read(), write(), read_nowait(), write_nowait(), and
poll(), which makes testbenches easier to read and maintain.
Configure the map with addaddrmap() or by assigning directly to a device index:
from cocotbext.apb import ApbHost, ApbBus
bus = ApbBus.from_prefix(dut, "s_apb")
apb_driver = ApbHost(bus, dut.clk)
# Preferred: addaddrmap() updates log column alignment automatically
apb_driver.addaddrmap({
'STATUS' : 0x00,
'BUSY' : 0x04,
'CONFIG' : 0x08,
'INTERRUPT' : 0x0c,
})
# Equivalent for device 0:
# apb_driver.addrmap[0] = { ... }
# Use string names instead of numeric addresses
await apb_driver.write('STATUS', 0x12)
await apb_driver.read('CONFIG')
# Indexed access using string format
await apb_driver.read('STATUS[0]', 0x12)
await apb_driver.read('STATUS[1]', 0x34)
# Indexed access using index parameter (useful with variables)
for i in range(4):
await apb_driver.write('STATUS', data[i], index=i)
await apb_driver.read('STATUS', expected[i], index=i)
When a map is configured, transaction logs show register names instead of raw
addresses (for example Read STATUS : 0x00000012 rather than
Read 0x00000000: 0x00000012). See tests/test_addrmap
for a complete cocotb example.
Indexed register access: for register arrays, use either bracket notation
("STATUS[0]", "STATUS[1]", …) or the index parameter
(read("STATUS", data, index=0)). Both add index * wbytes to the base address,
where wbytes is the bus data width in bytes.
Multi-device: pass device=N to addaddrmap() or assign addrmap[N] = {...}
for each slave. Use the device parameter on read/write calls to select the target.
Methods
enable_logging(): Enable debug loggingdisable_logging(): Disable debug loggingenable_backpressure(seednum=None): Enable random delays on the interfacedisable_backpressure(): Disable random delays on the interfacewait(): blocking wait until all outstanding operations completewrite(addr, data, strb=-1, prot=ApbProt.NONSECURE, error_expected=False, device=0, length=-1, index=-1): write data (bytes or int), to addr (int or string whenaddrmapis configured), wait for result. If an slverr is experienced a critical warning will be issued by default, but will reduced this to an info warning iferror_expected=True. If data is wider than the bus width, it will automatically be split into multiple sequential APB write accesses at consecutive addresses. The optional length parameter can override the automatic length calculation, should be a multiple of the number of bytes in the wdata bus. The optional device parameter specifies the slave index to target. The optional index parameter adds an offset to the address equal toindex * wbytes, useful for accessing indexed registers (alternative to using string format like"STATUS[0]").write_nowait(addr, data, strb=-1, prot=ApbProt.NONSECURE, error_expected=False, device=0, length=-1, index=-1): write data (bytes or int), to addr (int or string whenaddrmapis configured), submit to queue. If an slverr is experienced a critical warning will be issued by default, but will reduced this to an info warning iferror_expected=True. If data is wider than the bus width, it will automatically be split into multiple sequential APB write accesses at consecutive addresses. The optional length parameter can override the automatic length calculation, should be a multiple of the number of bytes in the wdata bus. The optional device parameter specifies the slave index to target. The optional index parameter adds an offset to the address equal toindex * wbytes, useful for accessing indexed registers (alternative to using string format like"STATUS[0]").read(addr, data=bytes(), prot=ApbProt.NONSECURE, error_expected=False, device=0, index=-1, length=-1): read bytes, at addr (int or string whenaddrmapis configured), if data supplied check for match, wait for result. If an slverr is experienced a critical warning will be issued by default, but will reduced this to an info warning iferror_expected=True. If data is wider than the bus width, it will automatically be split into multiple sequential APB read accesses at consecutive addresses. The optional length parameter can override the automatic length calculation, should be a multiple of the number of bytes in the wdata bus. The optional device parameter specifies the slave index to target. The optional index parameter adds an offset to the address equal toindex * wbytes, useful for accessing indexed registers (alternative to using string format like"STATUS[0]").read_nowait(addr, data=bytes(), prot=ApbProt.NONSECURE, error_expected=False, device=0, index=-1, length=-1): read bytes, at addr (int or string whenaddrmapis configured), if data supplied check for match, submit to queue. If an slverr is experienced a critical warning will be issued by default, but will reduced this to an info warning iferror_expected=True. If data is wider than the bus width, it will automatically be split into multiple sequential APB read accesses at consecutive addresses. The optional length parameter can override the automatic length calculation, should be a multiple of the number of bytes in the wdata bus. The optional device parameter specifies the slave index to target. The optional index parameter adds an offset to the address equal toindex * wbytes, useful for accessing indexed registers (alternative to using string format like"STATUS[0]").poll(addr, data=bytes(), device=0): poll address, at addr (int or string whenaddrmapis configured), until data at address matches data. The optional device parameter specifies the slave index to target.addaddrmap(addrmap, device=0): register a name-to-address map for device. Preferred over direct assignment because it updates log column alignment.format_addr(addr, device=0): reverse lookup — return the register name for addr, or0x........if unmapped.
AddressMap
AddressMap is a protocol-agnostic helper for name-to-address resolution on
memory-mapped register maps. It is used internally by ApbHost (via the
addrmap attribute) and is also exported for standalone use or integration with
other bus masters (for example OBI).
Import:
from cocotbext.apb import AddressMap
Data model
AddressMap is a dict subclass keyed by device index. Each value is a
plain dict mapping register name (str) to byte address (int):
AddressMap
├── 0 → {"STATUS": 0x00, "CONFIG": 0x08, ...} # device 0
├── 1 → {"CTRL": 0x1000, ...} # device 1 (multi-device)
└── word_bytes, multi_device, _label_width # configuration
Constructor parameters:
- word_bytes: bus data width in bytes (default
4). Used for indexed register offsets and reverse lookup alignment. - multi_device: reserve extra column width in log output when multiple slaves
are present (default
False).
Forward lookup (name → address)
resolve(addr, device=0, index=-1) converts a register name or integer address
to a byte address:
- If
addris anint, it is returned unchanged (plus anyindexoffset). - If
addris astr, the base name is looked up in the map for device. Bracket notation addsN * word_bytesfor each[N]suffix (e.g."AES_KEY_SHARE0[3]"→ base + 3 × word_bytes). - If
index != -1,index * word_bytesis added after name resolution.
am = AddressMap(word_bytes=4)
am.add({"STATUS": 0x00, "CONFIG": 0x08})
am.resolve(0x08) # 0x08 (integer passthrough)
am.resolve("STATUS") # 0x00
am.resolve("STATUS[2]") # 0x08
am.resolve("STATUS", index=1) # 0x04
Reverse lookup (address → name)
format(addr, device=0) returns the register name for a byte address. When the
address falls within a mapped register array (aligned to word_bytes), bracket
notation is used for non-zero indices. Unmapped addresses are formatted as
0x.........
am.format(0x00) # "STATUS"
am.format(0x08) # "STATUS[2]" (if STATUS base is 0x00, word_bytes=4)
am.format(0x99) # "0x00000099" (unmapped)
Registering maps
add(addrmap, device=0): store a name→address dict for device and recompute log column width. This is whatApbHost.addaddrmap()delegates to.- Direct assignment
am[device] = {...}also works (dict subclass), but does not update column width unlessadd()or_update_label_width()is called.
Log formatting
format_col(label, prefix="") pads a register label so read/write data columns
align in log output. ApbHost uses this internally when logging transactions.
Standalone example
from cocotbext.apb import AddressMap
REGS = {
"STATUS": 0x00,
"BUSY": 0x04,
"CONFIG": 0x08,
}
am = AddressMap(word_bytes=4)
am.add(REGS)
# Forward lookup for a custom driver
addr = am.resolve("CONFIG")
# Reverse lookup for debug output
label = am.format(addr) # "CONFIG"
col = am.format_col(label) # padded for aligned columns
Unit tests for reverse lookup live in tests/test_format_addr.py. Cocotb integration tests are in tests/test_addrmap.
APB Monitor
The ApbMonitor class tracks APB bus transactions and verifies signal synchronization.
Usage
from cocotbext.apb import ApbMonitor
monitor = ApbMonitor(bus, dut.clk)
Methods
enable_check_sync(): Enable checking that signal changes are aligned with the clock edge, default.disable_check_sync(): Disable the synchronous signal check.
Multi-Device Support
The ApbHost supports multiple devices on the same bus instance. To use this feature:
-
Signal Connection:
pselmust be a vector (e.g.,[1:0]for 2 slaves).prdatamust be a concatenated vector of all slave read data outputs (e.g.,[63:0]for 2 slaves with 32-bit data width).
-
Access:
- Use the
deviceparameter inread,write,read_nowait, andwrite_nowaitmethods to specify the target slave index (integer). - The
ApbHostwill assert the corresponding bit inpsel(1 << device) and slice theprdataappropriately.
- Use the
Example:
# Write to slave 0
await tb.intf.write(0x100, 0xDEADBEEF, device=0)
# Read from slave 1
val = await tb.intf.read(0x200, device=1)
APB Device
The ApbDevice class implements an APB device and is capable of completing read and write operations from upstream APB hosts. This module can either be used to perform memory reads and writes on a MemoryInterface on behalf of the DUT, or it can be extended to implement customized functionality.
To use these modules, import the one you need and connect it to the DUT:
from cocotbext.apb import ApbBus, ApbDevice, MemoryRegion
apb_device = ApbDevice(ApbBus.from_prefix(dut, "m_apb"), dut.clk, dut.rst)
region = MemoryRegion(2**apb_device.read_if.address_width)
apb_device.target = region
The first argument to the constructor accepts an ApbBus object. These objects are containers for the interface signals and include class methods to automate connections.
It is also possible to extend these modules; operation can be customized by overriding the internal _read() and _write() methods. See ApbRam for an example.
ApbSlave is a subclass of ApbDevice and remains available for existing testbenches.
ApbDevice constructor parameters
- bus:
ApbBusobject containing APB interface signals - clock: clock signal
- reset: reset signal (optional)
- reset_active_level: reset active level (optional, default
True) - target: target region (optional, default
None)
ApbDevice editable attibutes
It is possible to set area of addressable memory to be treated a priviledged address space or instruction address space. If an APB host tries to access these regions, but has not set the correct prot value, NONSECURE for example, the ApbDevice will issue a slverr duting the pready phase of it response.
The ApbDevice has two attributes that can be edited by the user to allocate addresses and/or address ranges to the priviledged or instruction space.
- privileged_addrs
- instruction_addrs
Both attributes are arrays, and each element can be a single address, or a two element list, with a low address to a high address:
tb.ram.privileged_addrs = [[0x1000, 0x1fff], 0x3000]
tb.ram.instruction_addrs = [[0x2000, 0x2fff], 0x4000]
If there is a read or a write with an address in this space, and the prot from the master does not match, it will report the type of error, as a warning, and assert slverr. The access will also be unsuccessful, the write will not occur and a read will result in all zeros being returned.
APB RAM
The ApbRam class implements APB RAMs and is capable of completing read and write operations from upstream APB hosts. These modules are extensions of ApbDevice. Internally, SparseMemory is used to support emulating very large memories.
To use these modules, import and connect it to the DUT:
from cocotbext.apb import ApbBus, ApbRam
apb_ram = ApbRam(ApbBus.from_prefix(dut, "m_apb"), dut.clk, dut.rst, size=2**32)
The first argument to the constructor accepts an ApbBus object. These objects are containers for the interface signals and include class methods to automate connections.
Once the module is instantiated, the memory contents can be accessed in a couple of different ways. First, the mmap object can be accessed directly via the mem attribute. Second, read(), write(), and various word-access wrappers are available. Hex dump helper methods are also provided for debugging. For example:
apb_ram.write(0x0000, b'test')
data = apb_ram.read(0x0000, 4)
apb_ram.hexdump(0x0000, 4, prefix="RAM")
Multi-port memories can be constructed by passing the mem object of the first instance to the other instances. For example, here is how to create a four-port RAM:
apb_ram_p1 = ApbRam(ApbBus.from_prefix(dut, "m00_apb"), dut.clk, dut.rst, size=2**32)
apb_ram_p2 = ApbRam(ApbBus.from_prefix(dut, "m01_apb"), dut.clk, dut.rst, mem=apb_ram_p1.mem)
apb_ram_p3 = ApbRam(ApbBus.from_prefix(dut, "m02_apb"), dut.clk, dut.rst, mem=apb_ram_p1.mem)
apb_ram_p4 = ApbRam(ApbBus.from_prefix(dut, "m03_apb"), dut.clk, dut.rst, mem=apb_ram_p1.mem)
ApbRam and ApbLiteRam constructor parameters
- bus:
ApbBusobject containing APB interface signals - clock: clock signal
- reset: reset signal (optional)
- reset_active_level: reset active level (optional, default
True) - size: memory size in bytes (optional, default
2**32) - mem:
mmaporSparseMemorybacking object to use (optional, overrides size)
Attributes:
- mem: directly access shared
mmaporSparseMemorybacking object
Methods
read(address, length): read length bytes, starting at addressread_words(address, count, byteorder='little', ws=2): read count ws-byte words, starting at addressread_dwords(address, count, byteorder='little'): read count 4-byte dwords, starting at addressread_qwords(address, count, byteorder='little'): read count 8-byte qwords, starting at addressread_byte(address): read single byte at addressread_word(address, byteorder='little', ws=2): read single ws-byte word at addressread_dword(address, byteorder='little'): read single 4-byte dword at addressread_qword(address, byteorder='little'): read single 8-byte qword at addresswrite(address, data): write data (bytes), starting at addresswrite_words(address, data, byteorder='little', ws=2): write data (ws-byte words), starting at addresswrite_dwords(address, data, byteorder='little'): write data (4-byte dwords), starting at addresswrite_qwords(address, data, byteorder='little'): write data (8-byte qwords), starting at addresswrite_byte(address, data): write single byte at addresswrite_word(address, data, byteorder='little', ws=2): write single ws-byte word at addresswrite_dword(address, data, byteorder='little'): write single 4-byte dword at addresswrite_qword(address, data, byteorder='little'): write single 8-byte qword at addresshexdump(address, length, prefix=''): print hex dump of length bytes starting from address, prefix lines with optional prefixhexdump_line(address, length, prefix=''): return hex dump (list of str) of length bytes starting from address, prefix lines with optional prefixhexdump_str(address, length, prefix=''): return hex dump (str) of length bytes starting from address, prefix lines with optional prefix
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
File details
Details for the file cocotbext_apb-1.1.0.tar.gz.
File metadata
- Download URL: cocotbext_apb-1.1.0.tar.gz
- Upload date:
- Size: 31.3 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
397561cbf5ddcb0d63a16cfb42e762564acdb5e5385b8e8d2a8c7665cedc8e33
|
|
| MD5 |
8cefa9ba083a9e73915335d126d315b2
|
|
| BLAKE2b-256 |
04b5503fc75edc564176b1a9bc118e981e8a378ed32f3f1312ab23903fb30f79
|
Provenance
The following attestation bundles were made for cocotbext_apb-1.1.0.tar.gz:
Publisher:
test_checkin.yml on daxzio/cocotbext-apb
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
cocotbext_apb-1.1.0.tar.gz -
Subject digest:
397561cbf5ddcb0d63a16cfb42e762564acdb5e5385b8e8d2a8c7665cedc8e33 - Sigstore transparency entry: 2498601702
- Sigstore integration time:
-
Permalink:
daxzio/cocotbext-apb@1581202e6f432607a3eec68c3ab0df7e3e51f9f2 -
Branch / Tag:
refs/tags/v1.1.0 - Owner: https://github.com/daxzio
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
test_checkin.yml@1581202e6f432607a3eec68c3ab0df7e3e51f9f2 -
Trigger Event:
push
-
Statement type: