periph.timer: add TimerPeripheral
authorJean-François Nguyen <jf@lambdaconcept.com>
Wed, 25 Mar 2020 12:22:08 +0000 (13:22 +0100)
committerJean-François Nguyen <jf@lambdaconcept.com>
Wed, 25 Mar 2020 12:22:08 +0000 (13:22 +0100)
lambdasoc/periph/timer.py [new file with mode: 0644]
lambdasoc/test/test_periph_timer.py [new file with mode: 0644]

diff --git a/lambdasoc/periph/timer.py b/lambdasoc/periph/timer.py
new file mode 100644 (file)
index 0000000..7b0abdc
--- /dev/null
@@ -0,0 +1,80 @@
+from nmigen import *
+
+from . import Peripheral
+
+
+__all__ = ["TimerPeripheral"]
+
+
+class TimerPeripheral(Peripheral, Elaboratable):
+    """Timer peripheral.
+
+    A general purpose down-counting timer peripheral.
+
+    CSR registers
+    -------------
+    reload : read/write
+        Reload value of counter. When `ctr` reaches 0, it is automatically reloaded with this value.
+    en : read/write
+        Counter enable.
+    ctr : read/write
+        Counter value.
+
+    Events
+    ------
+    zero : edge-triggered (rising)
+        Counter value reached 0.
+
+    Parameters
+    ----------
+    width : int
+        Counter width.
+
+    Attributes
+    ----------
+    bus : :class:`nmigen_soc.wishbone.Interface`
+        Wishbone bus interface.
+    irq : :class:`IRQLine`
+        Interrupt request.
+    """
+    def __init__(self, width):
+        super().__init__()
+
+        if not isinstance(width, int) or width < 0:
+            raise ValueError("Counter width must be a non-negative integer, not {!r}"
+                             .format(width))
+        if width > 32:
+            raise ValueError("Counter width cannot be greater than 32 (was: {})"
+                             .format(width))
+        self.width   = width
+
+        bank          = self.csr_bank()
+        self._reload  = bank.csr(width, "rw")
+        self._en      = bank.csr(    1, "rw")
+        self._ctr     = bank.csr(width, "rw")
+
+        self._zero_ev = self.event(mode="rise")
+
+        self._bridge  = self.bridge(data_width=32, granularity=8, alignment=2)
+        self.bus      = self._bridge.bus
+        self.irq      = self._bridge.irq
+
+    def elaborate(self, platform):
+        m = Module()
+        m.submodules.bridge = self._bridge
+
+        with m.If(self._en.r_data):
+            with m.If(self._ctr.r_data == 0):
+                m.d.comb += self._zero_ev.stb.eq(1)
+                m.d.sync += self._ctr.r_data.eq(self._reload.r_data)
+            with m.Else():
+                m.d.sync += self._ctr.r_data.eq(self._ctr.r_data - 1)
+
+        with m.If(self._reload.w_stb):
+            m.d.sync += self._reload.r_data.eq(self._reload.w_data)
+        with m.If(self._en.w_stb):
+            m.d.sync += self._en.r_data.eq(self._en.w_data)
+        with m.If(self._ctr.w_stb):
+            m.d.sync += self._ctr.r_data.eq(self._ctr.w_data)
+
+        return m
diff --git a/lambdasoc/test/test_periph_timer.py b/lambdasoc/test/test_periph_timer.py
new file mode 100644 (file)
index 0000000..21a406a
--- /dev/null
@@ -0,0 +1,94 @@
+#nmigen: UnusedElaboratable=no
+
+import unittest
+
+from nmigen import *
+from nmigen.back.pysim import *
+
+from ._wishbone import *
+from ..periph.timer import TimerPeripheral
+
+
+def simulation_test(dut, process):
+    with Simulator(dut, vcd_file=open("test.vcd", "w")) as sim:
+        sim.add_clock(1e-6)
+        sim.add_sync_process(process)
+        sim.run()
+
+
+reload_addr     = 0x00 >> 2
+en_addr         = 0x04 >> 2
+ctr_addr        = 0x08 >> 2
+ev_status_addr  = 0x10 >> 2
+ev_pending_addr = 0x14 >> 2
+ev_enable_addr  = 0x18 >> 2
+
+
+class TimerPeripheralTestCase(unittest.TestCase):
+    def test_invalid_width(self):
+        with self.assertRaisesRegex(ValueError,
+                r"Counter width must be a non-negative integer, not 'foo'"):
+            dut = TimerPeripheral(width="foo")
+
+    def test_invalid_width_32(self):
+        with self.assertRaisesRegex(ValueError,
+                r"Counter width cannot be greater than 32 \(was: 33\)"):
+            dut = TimerPeripheral(width=33)
+
+    def test_simple(self):
+        dut = TimerPeripheral(width=4)
+        def process():
+            yield from wb_write(dut.bus, addr=ctr_addr, data=15, sel=0xf)
+            yield
+            ctr = yield from wb_read(dut.bus, addr=ctr_addr, sel=0xf)
+            self.assertEqual(ctr, 15)
+            yield
+            yield from wb_write(dut.bus, addr=en_addr,  data=1,  sel=0xf)
+            yield
+            for i in range(16):
+                yield
+            ctr = yield from wb_read(dut.bus, addr=ctr_addr, sel=0xf)
+            self.assertEqual(ctr, 0)
+        simulation_test(dut, process)
+
+    def test_irq(self):
+        dut = TimerPeripheral(width=4)
+        def process():
+            yield from wb_write(dut.bus, addr=ctr_addr, data=15, sel=0xf)
+            yield
+            yield from wb_write(dut.bus, addr=ev_enable_addr, data=1, sel=0xf)
+            yield
+            yield from wb_write(dut.bus, addr=en_addr,  data=1,  sel=0xf)
+            yield
+            done = False
+            for i in range(16):
+                if (yield dut.irq):
+                    self.assertFalse(done)
+                    done = True
+                    ctr = yield from wb_read(dut.bus, addr=ctr_addr, sel=0xf)
+                    self.assertEqual(ctr, 0)
+                yield
+            self.assertTrue(done)
+        simulation_test(dut, process)
+
+    def test_reload(self):
+        dut = TimerPeripheral(width=4)
+        def process():
+            yield from wb_write(dut.bus, addr=reload_addr, data=15, sel=0xf)
+            yield
+            yield from wb_write(dut.bus, addr=ctr_addr, data=15, sel=0xf)
+            yield
+            yield from wb_write(dut.bus, addr=ev_enable_addr, data=1, sel=0xf)
+            yield
+            yield from wb_write(dut.bus, addr=en_addr,  data=1,  sel=0xf)
+            yield
+            irqs = 0
+            for i in range(32):
+                if (yield dut.irq):
+                    irqs += 1
+                    yield from wb_write(dut.bus, addr=ev_pending_addr, data=1, sel=0xf)
+                yield
+            # not an accurate measure, since each call to wb_write() adds a few cycles,
+            # but we can at least check that reloading the timer works.
+            self.assertEqual(irqs, 2)
+        simulation_test(dut, process)