diff --git a/amaranth_soc/csr/bus.py b/amaranth_soc/csr/bus.py index a41f276..3367129 100644 --- a/amaranth_soc/csr/bus.py +++ b/amaranth_soc/csr/bus.py @@ -1,13 +1,13 @@ from collections import defaultdict from amaranth import * -from amaranth.lib import enum, wiring -from amaranth.lib.wiring import In, Out, flipped +from amaranth.lib import enum, wiring, meta +from amaranth.lib.wiring import In, Out, flipped, FlippedInterface from amaranth.utils import ceil_log2 from ..memory import MemoryMap -__all__ = ["Element", "Signature", "Interface", "Decoder", "Multiplexer"] +__all__ = ["Element", "Signature", "Annotation", "Interface", "Decoder", "Multiplexer"] class Element(wiring.PureInterface): @@ -89,6 +89,35 @@ def create(self, *, path=None, src_loc_at=0): """ return Element(self.width, self.access, path=path, src_loc_at=1 + src_loc_at) + def annotations(self, element, /): + """Get annotations of a compatible CSR element. + + Parameters + ---------- + element : :class:`Element` + A CSR element compatible with this signature. + + Returns + ------- + iterator of :class:`meta.Annotation` + Annotations attached to ``element``. + + Raises + ------ + :exc:`TypeError` + If ``element`` is not an :class:`Element` object. + :exc:`ValueError` + If ``element.signature`` is not equal to ``self``. + """ + + if isinstance(element, FlippedInterface): + element = flipped(element) + if not isinstance(element, Element): + raise TypeError(f"Element must be a csr.Element object, not {element!r}") + if element.signature != self: + raise ValueError(f"Element signature is not equal to this signature") + return (*super().annotations(element), Element.Annotation(element.signature)) + def __eq__(self, other): """Compare signatures. @@ -101,6 +130,64 @@ def __eq__(self, other): def __repr__(self): return f"csr.Element.Signature({self.members!r})" + class Annotation(meta.Annotation): + schema = { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://amaranth-lang.org/schema/amaranth-soc/0.1/csr/element.json", + "type": "object", + "properties": { + "width": { + "type": "integer", + "minimum": 0, + }, + "access": { + "enum": ["r", "w", "rw"], + }, + }, + "additionalProperties": False, + "required": [ + "width", + "access", + ], + } + + """Peripheral-side CSR signature annotation. + + Parameters + ---------- + origin : :class:`Element.Signature` + The signature described by this annotation instance. + + Raises + ------ + :exc:`TypeError` + If ``origin`` is not a :class:`Element.Signature`. + """ + def __init__(self, origin): + if not isinstance(origin, Element.Signature): + raise TypeError(f"Origin must be a csr.Element.Signature object, not {origin!r}") + self._origin = origin + + @property + def origin(self): + return self._origin + + def as_json(self): + """Translate to JSON. + + Returns + ------- + :class:`dict` + A JSON representation of :attr:`~Element.Annotation.origin`, describing its width + and access mode. + """ + instance = { + "width": self.origin.width, + "access": self.origin.access.value, + } + self.validate(instance) + return instance + """Peripheral-side CSR interface. A low-level interface to a single atomically readable and writable register in a peripheral. @@ -199,6 +286,35 @@ def create(self, *, path=None, src_loc_at=0): return Interface(addr_width=self.addr_width, data_width=self.data_width, path=path, src_loc_at=1 + src_loc_at) + def annotations(self, interface, /): + """Get annotations of a compatible CSR bus interface. + + Parameters + ---------- + interface : :class:`Interface` + A CSR bus interface compatible with this signature. + + Returns + ------- + iterator of :class:`meta.Annotation` + Annotations attached to ``interface``. + + Raises + ------ + :exc:`TypeError` + If ``interface`` is not an :class:`Interface` object. + :exc:`ValueError` + If ``interface.signature`` is not equal to ``self``. + """ + if not isinstance(interface, Interface): + raise TypeError(f"Interface must be a csr.Interface object, not {interface!r}") + if interface.signature != self: + raise ValueError(f"Interface signature is not equal to this signature") + annotations = [*super().annotations(interface), Annotation(interface.signature)] + if interface._memory_map is not None: + annotations.append(interface._memory_map.annotation) + return annotations + def __eq__(self, other): """Compare signatures. @@ -212,6 +328,66 @@ def __repr__(self): return f"csr.Signature({self.members!r})" +class Annotation(meta.Annotation): + schema = { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://amaranth-lang.org/schema/amaranth-soc/0.1/csr/bus.json", + "type": "object", + "properties": { + "addr_width": { + "type": "integer", + "minimum": 0, + }, + "data_width": { + "type": "integer", + "minimum": 0, + }, + }, + "additionalProperties": False, + "required": [ + "addr_width", + "data_width", + ], + } + + """CPU-side CSR signature annotation. + + Parameters + ---------- + origin : :class:`Signature` + The signature described by this annotation instance. + + Raises + ------ + :exc:`TypeError` + If ``origin`` is not a :class:`Signature`. + """ + def __init__(self, origin): + if not isinstance(origin, Signature): + raise TypeError(f"Origin must be a csr.Signature object, not {origin!r}") + self._origin = origin + + @property + def origin(self): + return self._origin + + def as_json(self): + """Translate to JSON. + + Returns + ------- + :class:`dict` + A JSON representation of :attr:`~Annotation.origin`, describing its address width + and data width. + """ + instance = { + "addr_width": self.origin.addr_width, + "data_width": self.origin.data_width, + } + self.validate(instance) + return instance + + class Interface(wiring.PureInterface): """CPU-side CSR interface. diff --git a/amaranth_soc/memory.py b/amaranth_soc/memory.py index dddfac6..f99dec8 100644 --- a/amaranth_soc/memory.py +++ b/amaranth_soc/memory.py @@ -1,9 +1,9 @@ import bisect +from amaranth.lib import wiring, meta +from amaranth.utils import bits_for -from amaranth.lib import wiring - -__all__ = ["ResourceInfo", "MemoryMap"] +__all__ = ["ResourceInfo", "MemoryMap", "MemoryMapAnnotation"] class _RangeMap: @@ -230,6 +230,10 @@ def __init__(self, *, addr_width, data_width, alignment=0): self._next_addr = 0 self._frozen = False + @property + def annotation(self): + return MemoryMapAnnotation(self) + @property def addr_width(self): return self._addr_width @@ -441,7 +445,8 @@ def add_window(self, window, *, name=None, addr=None, sparse=None): Return value ------------ A tuple ``(start, end, ratio)`` describing the address range assigned to the window. - When bridging buses of unequal data width, ``ratio`` is the amount of contiguous addresses + + When bridging buses of unequal data width, ``ratio`` is the number of contiguous addresses on the narrower bus that are accessed for each transaction on the wider bus. Otherwise, it is always 1. @@ -550,7 +555,7 @@ def windows(self): Yield values ------------ A tuple ``window, name, (start, end, ratio)`` describing the address range assigned to - the window. When bridging buses of unequal data width, ``ratio`` is the amount of + the window. When bridging buses of unequal data width, ``ratio`` is the number of contiguous addresses on the narrower bus that are accessed for each transaction on the wider bus. Otherwise, it is always 1. """ @@ -571,7 +576,7 @@ def window_patterns(self): A tuple ``window, name, (pattern, ratio)`` describing the address range assigned to the window. ``pattern`` is a ``self.addr_width`` wide pattern that may be used in ``Case`` or ``match`` to determine if an address signal is within the address range of ``window``. When - bridging buses of unequal data width, ``ratio`` is the amount of contiguous addresses on + bridging buses of unequal data width, ``ratio`` is the number of contiguous addresses on the narrower bus that are accessed for each transaction on the wider bus. Otherwise, it is always 1. """ @@ -684,3 +689,175 @@ def decode_address(self, address): def __repr__(self): return f"MemoryMap({self._namespace!r})" + + +class MemoryMapAnnotation(meta.Annotation): + schema = { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://amaranth-lang.org/schema/amaranth-soc/0.1/memory/memory-map.json", + "type": "object", + "properties": { + "addr_width": { + "type": "integer", + "minimum": 0, + }, + "data_width": { + "type": "integer", + "minimum": 0, + }, + "alignment": { + "$comment": "power-of-2 exponent", + "type": "integer", + "minimum": 0, + }, + "windows": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "array", + "items": { + "type": "string", + }, + }, + "start": { + "type": "integer", + "minimum": 0, + }, + "end": { + "type": "integer", + "minimum": 0, + }, + "ratio": { + "$comment": "The number of contiguous addresses on the narrower bus that are accessed" + "for each transaction on the wider bus.", + "type": "integer", + "minimum": 1, + }, + "annotations": { + "type": "object", + "patternProperties": { + "^.+$": { "$ref": "#" }, + }, + }, + }, + "additionalProperties": False, + "required": [ + "start", + "end", + "ratio", + "annotations", + ], + }, + }, + "resources": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "array", + "items": { + "type": "string", + }, + }, + "start": { + "type": "integer", + "minimum": 0, + }, + "end": { + "type": "integer", + "minimum": 0, + }, + "annotations": { + "type": "object", + "patternProperties": { + "^.+$": { "type": "object" }, + }, + }, + }, + "additionalProperties": False, + "required": [ + "name", + "start", + "end", + "annotations", + ], + }, + }, + }, + "additionalProperties": False, + "required": [ + "addr_width", + "data_width", + "alignment", + "windows", + "resources", + ], + } + + """Memory map annotation. + + Parameters + ---------- + origin : :class:`MemoryMap` + The memory map described by this annotation instance. It is frozen as a side-effect of + this instantiation. + + Raises + ------ + :exc:`TypeError` + If ``origin`` is not a :class:`MemoryMap`. + """ + def __init__(self, origin): + if not isinstance(origin, MemoryMap): + raise TypeError(f"Origin must be a MemoryMap object, not {origin!r}") + origin.freeze() + self._origin = origin + + @property + def origin(self): + return self._origin + + def as_json(self): + """Translate to JSON. + + Returns + ------- + :class:`dict` + A JSON representation of :attr:`~MemoryMapAnnotation.origin`, describing its address width, + data width, address range alignment, and a hierarchical description of its local windows + and resources. + """ + instance = {} + instance.update({ + "addr_width": self.origin.addr_width, + "data_width": self.origin.data_width, + "alignment": self.origin.alignment, + "windows": [ + { + "name": list(window_name) if window_name else [], + "start": start, + "end": end, + "ratio": ratio, + "annotations": { + window.annotation.schema["$id"]: window.annotation.as_json() + }, + } for window, window_name, (start, end, ratio) in self.origin.windows() + ], + "resources": [ + { + "name": list(name), + "start": start, + "end": end, + "annotations": { + annotation.schema["$id"]: annotation.as_json() + for annotation in resource.signature.annotations(resource) + }, + } for resource, name, (start, end) in self.origin.resources() + ], + }) + + self.validate(instance) + return instance diff --git a/amaranth_soc/wishbone/bus.py b/amaranth_soc/wishbone/bus.py index 581936e..b83dbf0 100644 --- a/amaranth_soc/wishbone/bus.py +++ b/amaranth_soc/wishbone/bus.py @@ -1,12 +1,15 @@ from amaranth import * -from amaranth.lib import enum, wiring -from amaranth.lib.wiring import In, Out, flipped +from amaranth.lib import enum, wiring, meta +from amaranth.lib.wiring import In, Out, flipped, FlippedInterface from amaranth.utils import exact_log2 from ..memory import MemoryMap -__all__ = ["CycleType", "BurstTypeExt", "Feature", "Signature", "Interface", "Decoder", "Arbiter"] +__all__ = [ + "CycleType", "BurstTypeExt", "Feature", "Signature", "Annotation", "Interface", + "Decoder", "Arbiter" +] class CycleType(enum.Enum): @@ -167,6 +170,37 @@ def create(self, *, path=None, src_loc_at=0): granularity=self.granularity, features=self.features, path=path, src_loc_at=1 + src_loc_at) + def annotations(self, interface, /): + """Get annotations of a compatible Wishbone bus interface. + + Parameters + ---------- + interface : :class:`Interface` + A Wishbone bus interface compatible with this signature. + + Returns + ------- + iterator of :class:`meta.Annotation` + Annotations attached to ``interface``. + + Raises + ------ + :exc:`TypeError` + If ``interface`` is not an :class:`Interface` object. + :exc:`ValueError` + If ``interface.signature`` is not equal to ``self``. + """ + if isinstance(interface, FlippedInterface): + interface = flipped(interface) + if not isinstance(interface, Interface): + raise TypeError(f"Interface must be a wishbone.Interface object, not {interface!r}") + if interface.signature != self: + raise ValueError(f"Interface signature is not equal to this signature") + annotations = [*super().annotations(interface), Annotation(interface.signature)] + if interface._memory_map is not None: + annotations.append(interface._memory_map.annotation) + return annotations + def __eq__(self, other): """Compare signatures. @@ -183,6 +217,79 @@ def __repr__(self): return f"wishbone.Signature({self.members!r})" +class Annotation(meta.Annotation): + schema = { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://amaranth-lang.org/schema/amaranth-soc/0.1/wishbone/bus.json", + "type": "object", + "properties": { + "addr_width": { + "type": "integer", + "minimum": 0, + }, + "data_width": { + "enum": [8, 16, 32, 64], + }, + "granularity": { + "enum": [8, 16, 32, 64], + }, + "features": { + "type": "array", + "items": { + "enum": ["err", "rty", "stall", "lock", "cti", "bte"], + }, + "uniqueItems": True, + }, + }, + "additionalProperties": False, + "required": [ + "addr_width", + "data_width", + "granularity", + "features", + ], + } + + """Wishbone bus signature annotation. + + Parameters + ---------- + origin : :class:`Signature` + The signature described by this annotation instance. + + Raises + ------ + :exc:`TypeError` + If ``origin`` is not a :class:`Signature`. + """ + def __init__(self, origin): + if not isinstance(origin, Signature): + raise TypeError(f"Origin must be a wishbone.Signature object, not {origin!r}") + self._origin = origin + + @property + def origin(self): + return self._origin + + def as_json(self): + """Translate to JSON. + + Returns + ------- + :class:`dict` + A JSON representation of :attr:`~Annotation.origin`, describing its address width, + data width, granularity and features. + """ + instance = { + "addr_width": self.origin.addr_width, + "data_width": self.origin.data_width, + "granularity": self.origin.granularity, + "features": sorted(feature.value for feature in self.origin.features), + } + self.validate(instance) + return instance + + class Interface(wiring.PureInterface): """Wishbone bus interface. diff --git a/tests/test_csr_bus.py b/tests/test_csr_bus.py index 84206c0..0df4c6e 100644 --- a/tests/test_csr_bus.py +++ b/tests/test_csr_bus.py @@ -65,6 +65,13 @@ def test_create(self): self.assertEqual(elem.r_stb.name, "foo__bar__r_stb") self.assertEqual(elem.signature, sig) + def test_annotations(self): + sig = csr.Element.Signature(8, csr.Element.Access.RW) + elem = sig.create() + self.assertEqual([a.as_json() for a in sig.annotations(elem)], [ + { "width": 8, "access": "rw" }, + ]), + def test_eq(self): self.assertEqual(csr.Element.Signature(8, "r"), csr.Element.Signature(8, "r")) self.assertEqual(csr.Element.Signature(8, "r"), @@ -86,6 +93,35 @@ def test_wrong_access(self): r"'wo' is not a valid Element.Access"): csr.Element.Signature(width=1, access="wo") + def test_annotations_wrong_type(self): + sig = csr.Element.Signature(8, "rw") + with self.assertRaisesRegex(TypeError, + r"Element must be a csr\.Element object, not 'foo'"): + sig.annotations("foo") + + def test_annotations_incompatible(self): + sig1 = csr.Element.Signature(8, "rw") + elem = sig1.create() + sig2 = csr.Element.Signature(4, "rw") + with self.assertRaisesRegex(ValueError, + r"Element signature is not equal to this signature"): + sig2.annotations(elem) + + +class ElementAnnotationTestCase(unittest.TestCase): + def test_as_json(self): + sig = csr.Element.Signature(8, access="rw") + annotation = csr.Element.Annotation(sig) + self.assertEqual(annotation.as_json(), { + "width": 8, + "access": "rw", + }) + + def test_wrong_origin(self): + with self.assertRaisesRegex(TypeError, + r"Origin must be a csr.Element.Signature object, not 'foo'"): + csr.Element.Annotation("foo") + class ElementTestCase(unittest.TestCase): def test_simple(self): @@ -117,6 +153,22 @@ def test_create(self): self.assertEqual(iface.r_stb.name, "foo__bar__r_stb") self.assertEqual(iface.signature, sig) + def test_annotations(self): + sig = csr.Signature(addr_width=16, data_width=8) + iface = sig.create() + self.assertEqual([a.as_json() for a in sig.annotations(iface)], [ + { "addr_width": 16, "data_width": 8 }, + ]) + + def test_annotations_memory_map(self): + sig = csr.Signature(addr_width=16, data_width=8) + iface = sig.create() + iface.memory_map = MemoryMap(addr_width=16, data_width=8) + self.assertEqual([a.as_json() for a in sig.annotations(iface)], [ + { "addr_width": 16, "data_width": 8 }, + { "addr_width": 16, "data_width": 8, "alignment": 0, "windows": [], "resources": [] } + ]) + def test_eq(self): self.assertEqual(csr.Signature(addr_width=32, data_width=8), csr.Signature(addr_width=32, data_width=8)) @@ -137,6 +189,35 @@ def test_wrong_data_width(self): r"Data width must be a positive integer, not -1"): csr.Signature(addr_width=16, data_width=-1) + def test_annotations_wrong_type(self): + sig = csr.Signature(addr_width=8, data_width=8) + with self.assertRaisesRegex(TypeError, + r"Interface must be a csr\.Interface object, not 'foo'"): + sig.annotations("foo") + + def test_annotations_incompatible(self): + sig1 = csr.Signature(addr_width=8, data_width=8) + iface = sig1.create() + sig2 = csr.Signature(addr_width=4, data_width=8) + with self.assertRaisesRegex(ValueError, + r"Interface signature is not equal to this signature"): + sig2.annotations(iface) + + +class AnnotationTestCase(unittest.TestCase): + def test_as_json(self): + sig = csr.Signature(addr_width=16, data_width=8) + annotation = csr.Annotation(sig) + self.assertEqual(annotation.as_json(), { + "addr_width": 16, + "data_width": 8, + }) + + def test_wrong_origin(self): + with self.assertRaisesRegex(TypeError, + r"Origin must be a csr.Signature object, not 'foo'"): + csr.Annotation("foo") + class InterfaceTestCase(unittest.TestCase): def test_simple(self): diff --git a/tests/test_memory.py b/tests/test_memory.py index 94ddd6a..825e992 100644 --- a/tests/test_memory.py +++ b/tests/test_memory.py @@ -1,11 +1,15 @@ # amaranth: UnusedElaboratable=no - +# +import logging import unittest +from types import SimpleNamespace + from amaranth import * from amaranth.lib import wiring from amaranth.lib.wiring import Out -from amaranth_soc.memory import _RangeMap, _Namespace, ResourceInfo, MemoryMap +from amaranth_soc.memory import _RangeMap, _Namespace, ResourceInfo, MemoryMap, MemoryMapAnnotation +from amaranth_soc import csr class _MockResource(wiring.Component): @@ -18,6 +22,11 @@ def __repr__(self): return f"_MockResource('{self._name}')" +class _MockRegister(wiring.Component): + def __init__(self, width, access): + super().__init__({"element": Out(csr.Element.Signature(width, access))}) + + class RangeMapTestCase(unittest.TestCase): def test_insert(self): range_map = _RangeMap() @@ -628,3 +637,94 @@ def test_decode_address(self): def test_decode_address_missing(self): self.assertIsNone(self.root.decode_address(address=0x00000100)) + + +class MemoryMapAnnotationTestCase(unittest.TestCase): + def test_origin_freeze(self): + memory_map = MemoryMap(addr_width=2, data_width=8) + res = _MockResource("res") + MemoryMapAnnotation(memory_map) + with self.assertRaisesRegex(ValueError, + r"Memory map has been frozen. Cannot add resource _MockResource\('res'\)"): + memory_map.add_resource(res, name="foo", size=0) + + def test_as_json(self): + elem_1_1 = _MockRegister(8, "rw") + elem_1_2 = _MockRegister(16, "r") + memorymap_1 = MemoryMap(addr_width=2, data_width=8) + memorymap_1.add_resource(elem_1_1, name=("memorymap_1", "elem_1"), size=1) + memorymap_1.add_resource(elem_1_2, name=("memorymap_1", "elem_2"), size=2) + mux_1 = csr.Multiplexer(memorymap_1) + + elem_2_1 = _MockRegister(8, "rw") + elem_2_2 = _MockRegister(16, "w") + memorymap_2 = MemoryMap(addr_width=2, data_width=8) + memorymap_2.add_resource(elem_2_1, name=("memorymap_2", "elem_1",), size=1) + memorymap_2.add_resource(elem_2_2, name=("memorymap_2", "elem_2",), size=2) + mux_2 = csr.Multiplexer(memorymap_2) + + decoder = csr.Decoder(addr_width=4, data_width=8) + decoder.add(mux_1.bus) + decoder.add(mux_2.bus) + + annotation = MemoryMapAnnotation(decoder.bus.memory_map) + self.assertEqual(annotation.as_json(), { + 'addr_width': 4, + 'data_width': 8, + 'alignment': 0, + 'windows': [{ + 'name': [], + 'start': 0, + 'end': 4, + 'ratio': 1, + 'annotations': { + 'https://amaranth-lang.org/schema/amaranth-soc/0.1/memory/memory-map.json': { + 'addr_width': 2, + 'data_width': 8, + 'alignment': 0, + 'windows': [], + 'resources': [{ + 'name': ['memorymap_1', 'elem_1'], + 'start': 0, + 'end': 1, + 'annotations': {} + }, { + 'name': ['memorymap_1', 'elem_2'], + 'start': 1, + 'end': 3, + 'annotations': {} + }] + } + } + }, { + 'name': [], + 'start': 4, + 'end': 8, + 'ratio': 1, + 'annotations': { + 'https://amaranth-lang.org/schema/amaranth-soc/0.1/memory/memory-map.json': { + 'addr_width': 2, + 'data_width': 8, + 'alignment': 0, + 'windows': [], + 'resources': [{ + 'name': ['memorymap_2', 'elem_1'], + 'start': 0, + 'end': 1, + 'annotations': {} + }, { + 'name': ['memorymap_2', 'elem_2'], + 'start': 1, + 'end': 3, + 'annotations': {} + }] + } + } + }], + 'resources': [] + }) + + def test_wrong_origin(self): + with self.assertRaisesRegex(TypeError, + r"Origin must be a MemoryMap object, not 'foo'"): + MemoryMapAnnotation("foo") diff --git a/tests/test_wishbone_bus.py b/tests/test_wishbone_bus.py index c27a159..7b2716b 100644 --- a/tests/test_wishbone_bus.py +++ b/tests/test_wishbone_bus.py @@ -79,6 +79,40 @@ def test_create(self): self.assertEqual(iface.granularity, 8) self.assertEqual(iface.signature, sig) + def test_annotations(self): + sig = wishbone.Signature(addr_width=30, data_width=32, granularity=8, + features={"bte", "cti"}) + iface = sig.create() + self.assertEqual([a.as_json() for a in sig.annotations(iface)], [ + { + "addr_width": 30, + "data_width": 32, + "granularity": 8, + "features": [ "bte", "cti" ], + }, + ]) + + def test_annotations_memory_map(self): + sig = wishbone.Signature(addr_width=30, data_width=32, granularity=8, + features={"bte", "cti"}) + iface = sig.create() + iface.memory_map = MemoryMap(addr_width=32, data_width=8) + self.assertEqual([a.as_json() for a in sig.annotations(iface)], [ + { + "addr_width": 30, + "data_width": 32, + "granularity": 8, + "features": [ "bte", "cti" ], + }, + { + "addr_width": 32, + "data_width": 8, + "alignment": 0, + "windows": [], + "resources": [], + }, + ]) + def test_eq(self): self.assertEqual(wishbone.Signature(addr_width=32, data_width=8, features={"err"}), wishbone.Signature(addr_width=32, data_width=8, features={"err"})) @@ -126,6 +160,38 @@ def test_wrong_features(self): with self.assertRaisesRegex(ValueError, r"'foo' is not a valid Feature"): wishbone.Signature(addr_width=0, data_width=8, features={"foo"}) + def test_annotations_wrong_type(self): + sig = wishbone.Signature(addr_width=30, data_width=32, granularity=8) + with self.assertRaisesRegex(TypeError, + r"Interface must be a wishbone\.Interface object, not 'foo'"): + sig.annotations("foo") + + def test_annotations_incompatible(self): + sig1 = wishbone.Signature(addr_width=30, data_width=32, granularity=8) + iface = sig1.create() + sig2 = wishbone.Signature(addr_width=32, data_width=8) + with self.assertRaisesRegex(ValueError, + r"Interface signature is not equal to this signature"): + sig2.annotations(iface) + + +class AnnotationTestCase(unittest.TestCase): + def test_as_json(self): + sig = wishbone.Signature(addr_width=30, data_width=32, granularity=8, + features={"cti", "bte"}) + annotation = wishbone.Annotation(sig) + self.assertEqual(annotation.as_json(), { + "addr_width": 30, + "data_width": 32, + "granularity": 8, + "features": [ "bte", "cti" ], + }) + + def test_wrong_origin(self): + with self.assertRaisesRegex(TypeError, + r"Origin must be a wishbone.Signature object, not 'foo'"): + wishbone.Annotation("foo") + class InterfaceTestCase(unittest.TestCase): def test_simple(self):