openocd-python/tests/test_svd.py

262 lines
8.3 KiB
Python

"""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