From 8aa22797b6e2a7c86ecb079dcd12c518daf8af47 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Jean-Fran=C3=A7ois=20Nguyen?= Date: Wed, 25 Mar 2020 13:22:08 +0100 Subject: [PATCH] periph.timer: add TimerPeripheral --- lambdasoc/periph/timer.py | 80 ++++++++++++++++++++++++ lambdasoc/test/test_periph_timer.py | 94 +++++++++++++++++++++++++++++ 2 files changed, 174 insertions(+) create mode 100644 lambdasoc/periph/timer.py create mode 100644 lambdasoc/test/test_periph_timer.py diff --git a/lambdasoc/periph/timer.py b/lambdasoc/periph/timer.py new file mode 100644 index 0000000..7b0abdc --- /dev/null +++ b/lambdasoc/periph/timer.py @@ -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 index 0000000..21a406a --- /dev/null +++ b/lambdasoc/test/test_periph_timer.py @@ -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) -- 2.30.2