Skip to content

periph: add a ConstantMap container for configuration constants. #25

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Aug 17, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
146 changes: 143 additions & 3 deletions nmigen_soc/periph.py
Original file line number Diff line number Diff line change
@@ -1,24 +1,147 @@
from collections import OrderedDict
from collections.abc import Mapping

from nmigen.utils import bits_for

from .memory import MemoryMap
from . import event


__all__ = ["PeripheralInfo"]
__all__ = ["ConstantValue", "ConstantBool", "ConstantInt", "ConstantMap", "PeripheralInfo"]


class ConstantValue:
pass


class ConstantBool(ConstantValue):
"""Boolean constant.

Parameters
----------
value : bool
Constant value.
"""
def __init__(self, value):
if not isinstance(value, bool):
raise TypeError("Value must be a bool, not {!r}".format(value))
self._value = value

@property
def value(self):
return self._value

def __repr__(self):
return "ConstantBool({})".format(self.value)


class ConstantInt(ConstantValue):
"""Integer constant.

Parameters
----------
value : int
Constant value.
width : int
Width in bits. Optional. ``bits_for(value)`` by default.
signed : bool
Signedness. Optional. ``value < 0`` by default.
"""
def __init__(self, value, *, width=None, signed=None):
if not isinstance(value, int):
raise TypeError("Value must be an integer, not {!r}"
.format(value))
self._value = value

if width is None:
width = bits_for(value)
if not isinstance(width, int):
raise TypeError("Width must be an integer, not {!r}"
.format(width))
if width < bits_for(value):
raise ValueError("Width must be greater than or equal to the number of bits needed to"
" represent {}".format(value))
self._width = width

if signed is None:
signed = value < 0
if not isinstance(signed, bool):
raise TypeError("Signedness must be a bool, not {!r}"
.format(signed))
self._signed = signed

@property
def value(self):
return self._value

@property
def width(self):
return self._width

@property
def signed(self):
return self._signed

def __repr__(self):
return "ConstantInt({}, width={}, signed={})".format(self.value, self.width, self.signed)


class ConstantMap(Mapping):
"""Named constant map.

A read-only container for named constants. Keys are iterated in insertion order.

Parameters
----------
**constants : dict(str : :class:`ConstantValue`)
Named constants.

Examples
--------
>>> ConstantMap(RX_FIFO_DEPTH=16)
ConstantMap([('RX_FIFO_DEPTH', ConstantInt(16, width=5, signed=False))])
"""
def __init__(self, **constants):
self._storage = OrderedDict()
for key, value in constants.items():
if isinstance(value, bool):
value = ConstantBool(value)
if isinstance(value, int):
value = ConstantInt(value)
if not isinstance(value, ConstantValue):
raise TypeError("Constant value must be an instance of ConstantValue, not {!r}"
.format(value))
self._storage[key] = value

def __getitem__(self, key):
return self._storage[key]

def __iter__(self):
yield from self._storage

def __len__(self):
return len(self._storage)

def __repr__(self):
return "ConstantMap({})".format(list(self._storage.items()))


class PeripheralInfo:
"""Peripheral metadata.

A unified description of the local resources of a peripheral. It may be queried in order to
recover its memory windows, CSR registers and event sources.
recover its memory windows, CSR registers, event sources and configuration constants.

Parameters
----------
memory_map : :class:`MemoryMap`
Memory map of the peripheral.
irq : :class:`event.Source`
IRQ line of the peripheral. Optional.
constant_map : :class:`ConstantMap`
Constant map of the peripheral. Optional.
"""
def __init__(self, *, memory_map, irq=None):
def __init__(self, *, memory_map, irq=None, constant_map=None):
if not isinstance(memory_map, MemoryMap):
raise TypeError("Memory map must be an instance of MemoryMap, not {!r}"
.format(memory_map))
Expand All @@ -30,6 +153,13 @@ def __init__(self, *, memory_map, irq=None):
.format(irq))
self._irq = irq

if constant_map is None:
constant_map = ConstantMap()
if not isinstance(constant_map, ConstantMap):
raise TypeError("Constant map must be an instance of ConstantMap, not {!r}"
.format(constant_map))
self._constant_map = constant_map

@property
def memory_map(self):
"""Memory map.
Expand Down Expand Up @@ -57,3 +187,13 @@ def irq(self):
raise NotImplementedError("Peripheral info does not have an IRQ line"
.format(self))
return self._irq

@property
def constant_map(self):
"""Constant map.

Return value
------------
A :class:`ConstantMap` containing configuration constants of the peripheral.
"""
return self._constant_map
116 changes: 115 additions & 1 deletion nmigen_soc/test/test_periph.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,100 @@
import unittest

from ..periph import PeripheralInfo
from ..periph import *
from ..memory import MemoryMap
from .. import event


class ConstantBoolTestCase(unittest.TestCase):
def test_init(self):
a = ConstantBool(True)
b = ConstantBool(False)
self.assertTrue(a.value)
self.assertFalse(b.value)

def test_value_wrong(self):
with self.assertRaisesRegex(TypeError, r"Value must be a bool, not 'foo'"):
ConstantBool("foo")

def test_repr(self):
self.assertEqual(repr(ConstantBool(True)), "ConstantBool(True)")


class ConstantIntTestCase(unittest.TestCase):
def test_init(self):
c = ConstantInt(5, width=8, signed=True)
self.assertEqual(c.value, 5)
self.assertEqual(c.width, 8)
self.assertEqual(c.signed, True)

def test_init_default(self):
c = ConstantInt(5)
self.assertEqual(c.value, 5)
self.assertEqual(c.width, 3)
self.assertEqual(c.signed, False)

def test_value_wrong(self):
with self.assertRaisesRegex(TypeError, r"Value must be an integer, not 'foo'"):
ConstantInt("foo")

def test_width_wrong(self):
with self.assertRaisesRegex(TypeError, r"Width must be an integer, not 'foo'"):
ConstantInt(5, width="foo")

def test_width_overflow(self):
with self.assertRaisesRegex(ValueError,
r"Width must be greater than or equal to the number of bits needed to represent 5"):
ConstantInt(5, width=1)

def test_signed_wrong(self):
with self.assertRaisesRegex(TypeError, r"Signedness must be a bool, not 'foo'"):
ConstantInt(5, signed="foo")

def test_repr(self):
self.assertEqual(
repr(ConstantInt(-5, width=8, signed=True)),
"ConstantInt(-5, width=8, signed=True)"
)


class ConstantMapTestCase(unittest.TestCase):
def test_init(self):
constant_map = ConstantMap(A=5, B=True, C=ConstantBool(False))
self.assertEqual(
repr(constant_map), "ConstantMap(["
"('A', ConstantInt(5, width=3, signed=False)), "
"('B', ConstantBool(True)), "
"('C', ConstantBool(False))])",
)

def test_init_wrong_value(self):
with self.assertRaisesRegex(TypeError,
r"Constant value must be an instance of ConstantValue, not \('foo', 'bar'\)"):
ConstantMap(A=("foo", "bar"))

def test_getitem(self):
a = ConstantInt(1)
b = ConstantBool(False)
constant_map = ConstantMap(A=a, B=b)
self.assertIs(constant_map["A"], a)
self.assertIs(constant_map["B"], b)

def test_iter(self):
a = ConstantInt(1)
b = ConstantBool(False)
constant_map = ConstantMap(B=b, A=a)
self.assertEqual(list(constant_map.items()), [
("B", b),
("A", a),
])

def test_len(self):
a = ConstantInt(1)
b = ConstantBool(False)
constant_map = ConstantMap(B=b, A=a)
self.assertEqual(len(constant_map), 2)


class PeripheralInfoTestCase(unittest.TestCase):
def test_memory_map(self):
memory_map = MemoryMap(addr_width=1, data_width=8)
Expand Down Expand Up @@ -48,3 +138,27 @@ def test_irq_wrong(self):
with self.assertRaisesRegex(TypeError,
r"IRQ line must be an instance of event.Source, not 'foo'"):
info = PeripheralInfo(memory_map=memory_map, irq="foo")

def test_constant_map(self):
constant_map = ConstantMap()
memory_map = MemoryMap(addr_width=1, data_width=8)
info = PeripheralInfo(memory_map=memory_map, constant_map=constant_map)
self.assertIs(info.constant_map, constant_map)

def test_constant_map_none(self):
memory_map = MemoryMap(addr_width=1, data_width=8)
info = PeripheralInfo(memory_map=memory_map, constant_map=None)
self.assertIsInstance(info.constant_map, ConstantMap)
self.assertEqual(info.constant_map, {})

def test_constant_map_default(self):
memory_map = MemoryMap(addr_width=1, data_width=8)
info = PeripheralInfo(memory_map=memory_map)
self.assertIsInstance(info.constant_map, ConstantMap)
self.assertEqual(info.constant_map, {})

def test_constant_map_wrong(self):
memory_map = MemoryMap(addr_width=1, data_width=8)
with self.assertRaisesRegex(TypeError,
r"Constant map must be an instance of ConstantMap, not 'foo'"):
info = PeripheralInfo(memory_map=memory_map, constant_map="foo")