262 lines
8.3 KiB
Python
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
|