GitHub repository: https://github.com/daxzio/cocotbext-apb
APB simulation models for cocotb.
The APB protocol is cover in these documents APB Protocol Specification and APB Architecture Specification
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
See the tests
directory for complete testbenches using these modules.
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.
- psel
- pwrite
- paddr
- pwdata
- pready
- prdata
- pstrb
- pprot
- pslverr
The ApbMaster
class implement a APB driver and is capable of generating read and write operations against APB slaves.
To use these modules, import the one you need and connect it to the DUT:
from cocotbext.apb import ApbMaster, ApbBus
bus = ApbBus.from_prefix(dut, "s_apb")
apb_driver = ApbMaster(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.
- bus:
ApbBus
object containing APB interface signals - clock: clock signal
- reset: reset signal (optional)
- reset_active_level: reset active level (optional, default
True
)
- seednum: For random testing a seed can be supplied, default
None
, random seed.
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)
: write data (bytes), to addr, 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
write_nowait(addr, data, strb=-1, prot=ApbProt.NONSECURE, error_expected=False)
:write data (bytes), to addr, 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
read(addr, data=-1, prot=ApbProt.NONSECURE, error_expected=False)
: read bytes, at addr, id _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
read_nowait(addr, data, prot=ApbProt.NONSECURE, error_expected=False)
: read bytes, at addr, id _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
The ApbSlave
classe implement an APB slaves and is capable of completing read and write operations from upstream APB masters. This modules can either be used to perform memory reads and writes on a MemoryInterface
on behalf of the DUT, or they 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, ApbSlave, MemoryRegion
apb_slave = ApbSlave(ApbBus.from_prefix(dut, "m_apb"), dut.clk, dut.rst)
region = MemoryRegion(2**apb_slave.read_if.address_width)
apb_slave.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.
- bus:
ApbBus
object 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
)
It is possible to set area of addressable memory to be treated a priviledged address space or instruction address space. If an APB master tries to access these regions, but has not set the correct prot
value, NONSECURE
for example, the ApbSlave
will issue a slverr
duting the pready
phase of it response.
The ApbSlave
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.
The ApbRam
class implements APB RAMs and is capable of completing read and write operations from upstream APB masters. These modules are extensions of the corresponding ApbSlave
module. 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)
- bus:
ApbBus
object 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:
mmap
orSparseMemory
backing object to use (optional, overrides size)
- mem: directly access shared
mmap
orSparseMemory
backing object
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