--- /dev/null
+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
--- /dev/null
+#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)