Skip to content

Commit

Permalink
periph: add a ConstantMap container for configuration constants.
Browse files Browse the repository at this point in the history
Resolves #24.
  • Loading branch information
Jean-François Nguyen committed Aug 17, 2020
1 parent e352e2d commit 0c6405f
Show file tree
Hide file tree
Showing 2 changed files with 257 additions and 4 deletions.
145 changes: 142 additions & 3 deletions nmigen_soc/periph.py
@@ -1,24 +1,146 @@
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 ascending order.
Parameters
----------
**constants : dict(str : :class:`ConstantValue`)
Named constants.
Examples
--------
>>> constant_map = ConstantMap(RX_FIFO_DEPTH=16)
{'RX_FIFO_DEPTH': ConstantInt(16, width=5, signed=False)}
"""
def __init__(self, **constants):
self._storage = dict()
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 sorted(self._storage)

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

def __repr__(self):
return repr(self._storage)


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 +152,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 +186,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
@@ -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),
"{'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()), [
("A", a),
("B", b),
])

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

0 comments on commit 0c6405f

Please sign in to comment.