"""Tests for SVD decoding (no hardware required). These tests exercise the bitfield decoder and DecodedRegister formatting using synthetic data, without needing an SVD file or a mock server. """ from __future__ import annotations from dataclasses import dataclass import pytest from openocd.svd.decoder import decode_register from openocd.types import BitField, DecodedRegister # -- Fake SVD objects to avoid needing a real .svd file ----------------------- @dataclass class FakeSVDField: name: str bit_offset: int bit_width: int description: str @dataclass class FakeSVDRegister: name: str address_offset: int fields: list[FakeSVDField] @dataclass class FakeSVDPeripheral: name: str base_address: int registers: list[FakeSVDRegister] @pytest.fixture def gpioa_odr(): """A fake GPIOA.ODR register with two bitfields.""" fields = [ FakeSVDField( name="ODR0", bit_offset=0, bit_width=1, description="Port output data bit 0", ), FakeSVDField( name="ODR1", bit_offset=1, bit_width=1, description="Port output data bit 1", ), FakeSVDField( name="ODR15_2", bit_offset=2, bit_width=14, description="Port output data bits 15:2", ), ] register = FakeSVDRegister(name="ODR", address_offset=0x14, fields=fields) peripheral = FakeSVDPeripheral( name="GPIOA", base_address=0x40010800, registers=[register] ) return peripheral, register @pytest.fixture def usart_cr1(): """A fake USART1.CR1 register with multiple bitfields.""" fields = [ FakeSVDField(name="UE", bit_offset=0, bit_width=1, description="USART enable"), FakeSVDField(name="RE", bit_offset=2, bit_width=1, description="Receiver enable"), FakeSVDField(name="TE", bit_offset=3, bit_width=1, description="Transmitter enable"), FakeSVDField( name="RXNEIE", bit_offset=5, bit_width=1, description="RXNE interrupt enable", ), FakeSVDField( name="TCIE", bit_offset=6, bit_width=1, description="Transmission complete IE", ), FakeSVDField( name="TXEIE", bit_offset=7, bit_width=1, description="TXE interrupt enable", ), FakeSVDField(name="M", bit_offset=12, bit_width=1, description="Word length"), FakeSVDField( name="OVER8", bit_offset=15, bit_width=1, description="Oversampling mode", ), ] register = FakeSVDRegister(name="CR1", address_offset=0x0C, fields=fields) peripheral = FakeSVDPeripheral( name="USART1", base_address=0x40013800, registers=[register] ) return peripheral, register def test_decode_register_basic(gpioa_odr): """decode_register should extract bitfield values from a raw integer.""" peripheral, register = gpioa_odr # Set ODR0=1, ODR1=0, ODR15_2 = 0x0005 (bits 2..15 = 0b00000000010100 = 0x14 shifted) raw = 0b0000000000010101 # ODR0=1, ODR1=0, ODR15_2=0x0005 decoded = decode_register(peripheral, register, raw) assert isinstance(decoded, DecodedRegister) assert decoded.peripheral == "GPIOA" assert decoded.register == "ODR" assert decoded.address == 0x40010800 + 0x14 assert decoded.raw_value == raw def test_decode_bitfield_extraction(gpioa_odr): """Individual bitfield values should be correctly masked and shifted.""" peripheral, register = gpioa_odr raw = 0b0000000000010101 decoded = decode_register(peripheral, register, raw) fields_by_name = {f.name: f for f in decoded.fields} assert fields_by_name["ODR0"].value == 1 assert fields_by_name["ODR1"].value == 0 assert fields_by_name["ODR15_2"].value == 0x0005 def test_decode_all_ones(gpioa_odr): """All-ones value should set all fields to their max values.""" peripheral, register = gpioa_odr raw = 0xFFFF decoded = decode_register(peripheral, register, raw) fields_by_name = {f.name: f for f in decoded.fields} assert fields_by_name["ODR0"].value == 1 assert fields_by_name["ODR1"].value == 1 assert fields_by_name["ODR15_2"].value == (1 << 14) - 1 # 0x3FFF def test_decode_all_zeros(gpioa_odr): """All-zeros value should yield all-zero fields.""" peripheral, register = gpioa_odr decoded = decode_register(peripheral, register, 0x0000) for field in decoded.fields: assert field.value == 0 def test_bitfield_type(gpioa_odr): """Each field in a DecodedRegister should be a BitField dataclass.""" peripheral, register = gpioa_odr decoded = decode_register(peripheral, register, 0xAAAA) for field in decoded.fields: assert isinstance(field, BitField) assert isinstance(field.name, str) assert isinstance(field.offset, int) assert isinstance(field.width, int) assert isinstance(field.value, int) assert isinstance(field.description, str) def test_fields_sorted_by_offset(gpioa_odr): """Decoded fields should be sorted by bit offset (low to high).""" peripheral, register = gpioa_odr decoded = decode_register(peripheral, register, 0x1234) offsets = [f.offset for f in decoded.fields] assert offsets == sorted(offsets) def test_decoded_register_str(gpioa_odr): """__str__ should produce a multi-line representation with field details.""" peripheral, register = gpioa_odr raw = 0b0000000000010101 decoded = decode_register(peripheral, register, raw) text = str(decoded) assert "GPIOA.ODR" in text assert "0X40010814" in text.upper() assert "ODR0" in text assert "ODR1" in text assert "ODR15_2" in text def test_decoded_register_str_shows_values(gpioa_odr): """The string representation should show each field's hex value.""" peripheral, register = gpioa_odr decoded = decode_register(peripheral, register, 0x0001) text = str(decoded) # ODR0 = 1 should appear as "0x1" assert "0x1" in text def test_decode_complex_register(usart_cr1): """Decode a multi-field register and verify specific field values.""" peripheral, register = usart_cr1 # UE=1, RE=1, TE=1 -> bits 0,2,3 set -> raw = 0x000D raw = 0x000D decoded = decode_register(peripheral, register, raw) fields_by_name = {f.name: f for f in decoded.fields} assert fields_by_name["UE"].value == 1 assert fields_by_name["RE"].value == 1 assert fields_by_name["TE"].value == 1 assert fields_by_name["RXNEIE"].value == 0 assert fields_by_name["M"].value == 0 assert fields_by_name["OVER8"].value == 0 def test_decode_address_calculation(usart_cr1): """The decoded address should be base + offset.""" peripheral, register = usart_cr1 decoded = decode_register(peripheral, register, 0) assert decoded.address == 0x40013800 + 0x0C def test_decoded_register_fields_list(gpioa_odr): """fields should be a plain list, not some other iterable.""" peripheral, register = gpioa_odr decoded = decode_register(peripheral, register, 0) assert isinstance(decoded.fields, list) assert len(decoded.fields) == 3 def test_bitfield_frozen(): """BitField should be immutable (frozen dataclass).""" bf = BitField(name="TEST", offset=0, width=1, value=1, description="test") with pytest.raises(AttributeError): bf.value = 2 # type: ignore[misc] def test_decoded_register_str_single_bit_range(gpioa_odr): """Single-bit fields should show just the bit number, not a range.""" peripheral, register = gpioa_odr decoded = decode_register(peripheral, register, 0x0001) text = str(decoded) # ODR0 is at offset 0, width 1 -> should show "[ 0]" not "[0:0]" lines = text.strip().splitlines() # Find the ODR0 line odr0_line = [ln for ln in lines if "ODR0 " in ln or "ODR0" in ln.split()][0] assert "0]" in odr0_line def test_decoded_register_str_multi_bit_range(gpioa_odr): """Multi-bit fields should show a bit range like [15:2].""" peripheral, register = gpioa_odr decoded = decode_register(peripheral, register, 0xFFFF) text = str(decoded) lines = text.strip().splitlines() odr15_2_line = [ln for ln in lines if "ODR15_2" in ln][0] assert "15:2" in odr15_2_line