Skip to content

I2C 8-bit Address Memory Target

The PxI2C8BitAddressMemoryTarget class emulates a byte-addressable memory device on an I2C (or I3C) bus. A controller accesses the device using a 7-bit I2C address plus an 8-bit memory offset (sub-address), then reads or writes a contiguous block of bytes.

This target is useful when you want to test controller drivers, EEPROM-style peripherals, or register files backed by a linear memory array rather than a fixed 32-bit register layout.

Memory model

  • 256 bytes of backing store (0x000xFF).
  • Static I2C address configured at creation time (for example 0x50).
  • 8-bit sub-address sent as the first data byte of a write transaction, or written before a read-with-sub-address sequence.

On the bus, a typical write looks like:

START → ADDR+W → SUB_ADDR → DATA[0] → DATA[1] → … → STOP

A typical read-with-sub-address looks like:

START → ADDR+W → SUB_ADDR → Sr → ADDR+R → DATA[0] → DATA[1] → … → STOP

During bus transfers, the backing memory is updated automatically. Python can inspect or preload memory through read_mem / write_mem without going through the I2C controller.

Quick start

Attach a target to an I2C bus and read/write memory from Python:

from aqpxlib import AqProtocolExerciser
from aqpxlib.i2c import PxI2CBus, PxI2C8BitAddressMemoryTarget

with AqProtocolExerciser.connect(port=60600) as px:
    i2c_bus = PxI2CBus(px, scl=0, sda=1)
    target = PxI2C8BitAddressMemoryTarget(px, address=0x50)
    target.attach_to_bus(i2c_bus)

    target.write_mem(0x10, [0x01, 0x02, 0x03, 0x04])
    assert target.read_mem(0x10, 4) == [0x01, 0x02, 0x03, 0x04]

    target.detach_from_bus()

Controller access

Use a PxI2CController on the same bus to exercise the target as a real controller would:

from aqpxlib import AqProtocolExerciser
from aqpxlib.i2c import PxI2CBus, PxI2CController, PxI2C8BitAddressMemoryTarget

with AqProtocolExerciser.connect(port=60600) as px:
    i2c_bus = PxI2CBus(px, scl=0, sda=1)
    target = PxI2C8BitAddressMemoryTarget(px, address=0x50)
    target.attach_to_bus(i2c_bus)

    i2c_controller = PxI2CController(px)
    i2c_controller.attach_to_bus(i2c_bus)

    sub_address = 8
    data = [0xDE, 0xAD, 0xBE, 0xEF]

    i2c_controller.i2c_write_addr(target.address, sub_address, data)
    read_back = i2c_controller.i2c_read_addr(target.address, sub_address, len(data))
    assert read_back == data

    # Confirm the backing store matches what the controller wrote.
    assert target.read_mem(sub_address, len(data)) == data

    i2c_controller.detach_from_bus()
    target.detach_from_bus()

Bus access events

When another device on the bus reads or writes the target, the exerciser emits memory_target_write_event and memory_target_read_event on the target instance. These are shared event types (also used by other memory targets on I3C, SPI, and so on):

Event Payload fields When emitted
memory_target_write_event addr, data After a completed bus write transfer
memory_target_read_event addr, count After a completed bus read transfer

Subscribe with separate handlers for each event type:

from aqpxlib.event import PxEventMsg

@target.on_event("memory_target_write_event")
def on_memory_target_write(event: PxEventMsg):
    write = event.device_event.memory_target_write_event
    print(f"Bus write @ 0x{write.addr:02x}: {write.data.hex()}")

@target.on_event("memory_target_read_event")
def on_memory_target_read(event: PxEventMsg):
    read = event.device_event.memory_target_read_event
    print(f"Bus read @ 0x{read.addr:02x}, {read.count} bytes")

Events reflect bus activity only. Calls to read_mem / write_mem from Python do not emit memory target events.

See also: Event Registration and Handler Setup.

I3C bus support

PxI2C8BitAddressMemoryTarget may be attached to an I3C bus (valid_buses includes "i3c" and "i2c"). The device behaves as an I2C-style target on the I3C SDA line.

API reference

PxI2C8BitAddressMemoryTarget

PxI2C8BitAddressMemoryTarget(
    exerciser: AqProtocolExerciser,
    address: int = 0,
    config: I2C8BitAddressMemoryTargetConfig | None = None,
    *args,
    **kwargs
)

Bases: PxAbstractTarget

I2C byte-addressable memory target.

Emulates a 256-byte memory device on an I2C or I3C bus. Controllers access memory using a 7-bit device address plus an 8-bit sub-address (memory offset).

Use read_mem and write_mem to access backing store from Python. Bus reads and writes from a controller emit memory_target_read_event and memory_target_write_event notifications.

METHOD DESCRIPTION
add_event_handler

Add an event handler to the current device instance.

remove_event_handler

Remove an event handler.

get_event_handlers

Retreive handlers registerd on this device.

on_event

Add an event handler to the instance by decorator.

config

Set the config of the target.

detach_from_bus

Detach the device from the bus.

perform_operation

Send an operation.

attach_to_bus

Attach the target to a bus.

read_mem

Read bytes from backing store via the Protocol Exerciser API.

write_mem

Write bytes to backing store via the Protocol Exerciser API.

ATTRIBUTE DESCRIPTION
available_events_map

A reverse mapping of field_name_by_number, which uses field name as key.

TYPE: dict[str, str]

is_attached

Check if the device is attached to a bus.

TYPE: bool

state

Get the state of the device.

TYPE: Message | None

name

Get the name of the device.

TYPE: str

cts_op_name

Get the name of CTS operation. Currently not available now.

TYPE: str | None

address

Static address of the device.

TYPE: int

available_events_map

available_events_map: dict[str, str]

A reverse mapping of field_name_by_number, which uses field name as key.

is_attached

is_attached: bool

Check if the device is attached to a bus.

state

state: Message | None

Get the state of the device.

name

name: str

Get the name of the device.

cts_op_name

cts_op_name: str | None

Get the name of CTS operation. Currently not available now.

address

address: int

Static address of the device.

add_event_handler

add_event_handler(event: str, handler: Callable[[Any], Any]) -> None

Add an event handler to the current device instance.

remove_event_handler

remove_event_handler(event: str) -> None

Remove an event handler.

get_event_handlers

get_event_handlers() -> dict[str, Callable]

Retreive handlers registerd on this device.

on_event

on_event(event_type: str) -> Callable[[Callable[..., Any]], Callable[..., Any]]

Add an event handler to the instance by decorator.

config

config(config: Message) -> None

Set the config of the target.

detach_from_bus

detach_from_bus() -> None

Detach the device from the bus.

The opposite of the attach_to_bus method.

perform_operation

perform_operation(operation: PxDeviceOperation) -> PxDeviceOperation

Send an operation.

attach_to_bus

attach_to_bus(bus: PxAbstractBus) -> None

Attach the target to a bus.

This method is used to make the device aware of the bus it is connected to. It is used to set the bus attribute of the device.

Note

This method will not automatically create an actual instance on the Protocol Exerciser until user call the attach_to_bus method.

PARAMETER DESCRIPTION

bus

The bus to attach the device to

TYPE: PxAbstractBus

RAISES DESCRIPTION
ValueError

If the device is already attached to a bus

read_mem

read_mem(addr: int, count: int) -> list[int]

Read bytes from backing store via the Protocol Exerciser API.

This does not perform an I2C bus transaction. Use a PxI2CController to exercise the target over the bus.

PARAMETER DESCRIPTION

addr

Starting 8-bit memory address (0x00-0xFF).

TYPE: int

count

Number of bytes to read.

TYPE: int

RETURNS DESCRIPTION
list[int]

Bytes read from backing store as a list of integers (0-255).

write_mem

write_mem(addr: int, data: list[int]) -> None

Write bytes to backing store via the Protocol Exerciser API.

This does not perform an I2C bus transaction. Use a PxI2CController to exercise the target over the bus.

PARAMETER DESCRIPTION

addr

Starting 8-bit memory address (0x00-0xFF).

TYPE: int

data

Bytes to write as a list of integers (0-255). Must fit within the 256-byte memory map.

TYPE: list[int]