From 187c9f56dd822c9149c465c5d3ca025fa8d0666e Mon Sep 17 00:00:00 2001 From: Yash Anand <164618230+YashAnand69@users.noreply.github.com> Date: Fri, 9 Oct 2026 16:31:55 +0530 Subject: [PATCH] fix: read SPI response words in bytes --- pslab/bus/spi.py | 2 +- tests/test_spi_response_framing.py | 76 ++++++++++++++++++++++++++++++ 2 files changed, 77 insertions(+), 1 deletion(-) create mode 100644 tests/test_spi_response_framing.py diff --git a/pslab/bus/spi.py b/pslab/bus/spi.py index ad885ce..c96f6e1 100644 --- a/pslab/bus/spi.py +++ b/pslab/bus/spi.py @@ -275,7 +275,7 @@ def _transfer(self, data: int, bits: int) -> int: self._device.send_byte(CP.SPI_HEADER) self._device.send_byte(command) self._device.write(interger_type.pack(data)) - data_in = interger_type.unpack(self._device.read(bits))[0] + data_in = interger_type.unpack(self._device.read(interger_type.size))[0] self._device.get_ack() return data_in diff --git a/tests/test_spi_response_framing.py b/tests/test_spi_response_framing.py new file mode 100644 index 0000000..898f563 --- /dev/null +++ b/tests/test_spi_response_framing.py @@ -0,0 +1,76 @@ +"""SPI response words and acknowledgement bytes stay separately framed.""" + +from io import BytesIO + +import pytest + +import pslab.protocol as CP +from pslab.bus.spi import SPISlave +from pslab.connection import ConnectionHandler + + +class ResponseStream(ConnectionHandler): + """Byte-oriented transport with preloaded firmware response words.""" + + def __init__(self, responses): + self.responses = BytesIO(responses) + self.writes = [] + self.read_sizes = [] + + def connect(self): + """No physical connection is needed.""" + + def disconnect(self): + """No physical connection is needed.""" + + def read(self, numbytes): + """Read bytes, including ACKs if the caller requests too many.""" + self.read_sizes.append(numbytes) + return self.responses.read(numbytes) + + def write(self, data): + """Record the exact command stream.""" + self.writes.append(data) + return len(data) + + +@pytest.mark.parametrize("bits,word", [(8, CP.Byte), (16, CP.ShortInt)]) +@pytest.mark.parametrize("operation", ["transfer", "read", "write"]) +@pytest.mark.parametrize("bulk", [False, True]) +def test_public_spi_operations_keep_word_and_ack_separate(bits, word, operation, bulk): + values = [0x35, 0xA5] if bits == 8 else [0x1234, 0xFEDC] + if not bulk: + values = values[:1] + wire = ResponseStream(b"".join(word.pack(value) + b"\x01" for value in values)) + slave = SPISlave(wire) + method = getattr(slave, f"{operation}{bits}" + ("_bulk" if bulk else "")) + if operation == "read": + result = method(len(values)) if bulk else method() + else: + result = method(values if bulk else values[0]) + expected = values if bulk else values[0] + assert result == (None if operation == "write" else expected) + assert wire.responses.read() == b"" + assert wire.read_sizes == [size for _ in values for size in (word.size, 1)] + assert wire.writes[:3] == [CP.SPI_HEADER, CP.START_SPI, b"\x07"] + assert wire.writes[-3:] == [CP.SPI_HEADER, CP.STOP_SPI, b"\x07"] + transmitted = ( + [b"\x00" * word.size] * len(values) + if operation == "read" + else [word.pack(value) for value in values] + ) + command = CP.SEND_SPI8 if bits == 8 else CP.SEND_SPI16 + expected_payload = [ + part for value in transmitted for part in (CP.SPI_HEADER, command, value) + ] + assert wire.writes[3:-3] == expected_payload + + +@pytest.mark.parametrize("bits,word", [(8, CP.Byte), (16, CP.ShortInt)]) +def test_sequential_spi_transfers_leave_the_next_reply_available(bits, word): + values = [0x25, 0x37] + wire = ResponseStream(b"".join(word.pack(value) + b"\x01" for value in values)) + slave = SPISlave(wire) + transfer = getattr(slave, f"transfer{bits}") + assert [transfer(0) for _ in values] == values + assert wire.responses.read() == b""