From 1e6c504e02281a53537e86ee280f77a71a8f98e4 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Jean-Fran=C3=A7ois=20Nguyen?= Date: Wed, 25 Mar 2020 17:41:41 +0100 Subject: [PATCH] periph.intc: add GenericInterruptController --- lambdasoc/periph/intc.py | 114 +++++++++++++++++++++++++++++ lambdasoc/test/test_periph_intc.py | 108 +++++++++++++++++++++++++++ 2 files changed, 222 insertions(+) create mode 100644 lambdasoc/periph/intc.py create mode 100644 lambdasoc/test/test_periph_intc.py diff --git a/lambdasoc/periph/intc.py b/lambdasoc/periph/intc.py new file mode 100644 index 0000000..5d75ff9 --- /dev/null +++ b/lambdasoc/periph/intc.py @@ -0,0 +1,114 @@ +from nmigen import * +from . import Peripheral, IRQLine + + +__all__ = ["InterruptController", "GenericInterruptController"] + + +class InterruptController(Peripheral): + """Interrupt controller base class.""" + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.__irq_lines = set() + self.__irq_map = dict() + + def add_irq(self, line, index): + """Add an IRQ line. + + Parameters + ---------- + line : :class:`IRQLine` + IRQ line. + index : int + IRQ index. + + Exceptions + ---------- + Raises :exn:`ValueError` if ``line`` is added twice, or if ``index`` is already used. + """ + if not isinstance(line, IRQLine): + raise TypeError("IRQ line must be an instance of IRQLine, not {!r}" + .format(line)) + if not isinstance(index, int) or index < 0: + raise ValueError("IRQ index must be a non-negative integer, not {!r}" + .format(index)) + if line in self.__irq_lines: + raise ValueError("IRQ line {!r} has already been mapped to IRQ index {}" + .format(line, self.find_index(line))) + if index in self.__irq_map: + raise ValueError("IRQ index {} has already been mapped to IRQ line {!r}" + .format(index, self.__irq_map[index])) + self.__irq_lines.add(line) + self.__irq_map[index] = line + + def iter_irqs(self): + """Iterate IRQ lines. + + Yield values + ------------ + A tuple ``index, line`` describing an IRQ line and its index. + """ + yield from sorted(self.__irq_map.items()) + + def find_index(self, line): + """Find the index at which an IRQ line is mapped. + + Parameters + ---------- + line : :class:`IRQLine` + IRQ line. + + Return value + ------------ + The index at which ``line`` is mapped, if present. + + Exceptions + ---------- + Raises :exn:`KeyError` if ``line`` is not present. + """ + for irq_index, irq_line in self.iter_irqs(): + if line is irq_line: + return irq_index + raise KeyError(line) + + +class GenericInterruptController(InterruptController, Elaboratable): + """Generic interrupt controller. + + An interrupt "controller" acting as a passthrough for IRQ lines. Useful for CPU cores that do + interrupt management themselves. + + Parameters + ---------- + width : int + Output width. + + Attributes + ---------- + width : int + Output width. + ip : Signal, out + Pending IRQs. + """ + def __init__(self, *, width): + super().__init__(src_loc_at=2) + if not isinstance(width, int) or width <= 0: + raise ValueError("Width must be a strictly positive integer, not {!r}" + .format(width)) + self.width = width + self.ip = Signal(width) + + def add_irq(self, line, index): + __doc__ = InterruptController.add_irq.__doc__ + if not isinstance(index, int) or index not in range(0, self.width): + raise ValueError("IRQ index must be an integer ranging from 0 to {}, not {!r}" + .format(self.width - 1, index)) + super().add_irq(line, index) + + def elaborate(self, platform): + m = Module() + + for irq_index, irq_line in self.iter_irqs(): + m.d.comb += self.ip[irq_index].eq(irq_line) + + return m diff --git a/lambdasoc/test/test_periph_intc.py b/lambdasoc/test/test_periph_intc.py new file mode 100644 index 0000000..add61e9 --- /dev/null +++ b/lambdasoc/test/test_periph_intc.py @@ -0,0 +1,108 @@ +#nmigen: UnusedElaboratable=no + +import unittest + +from nmigen import * +from nmigen.back.pysim import * + +from ..periph import IRQLine +from ..periph.intc import * + + +class InterruptControllerTestCase(unittest.TestCase): + def test_add_irq_wrong_line(self): + intc = InterruptController() + with self.assertRaisesRegex(TypeError, + r"IRQ line must be an instance of IRQLine, not 'foo'"): + intc.add_irq("foo", 0) + + def test_add_irq_wrong_index(self): + intc = InterruptController() + with self.assertRaisesRegex(ValueError, + r"IRQ index must be a non-negative integer, not 'bar'"): + intc.add_irq(IRQLine(name="foo"), "bar") + with self.assertRaisesRegex(ValueError, + r"IRQ index must be a non-negative integer, not -1"): + intc.add_irq(IRQLine(name="foo"), -1) + + def test_add_irq_line_twice(self): + intc = InterruptController() + line = IRQLine() + with self.assertRaisesRegex(ValueError, + r"IRQ line \(sig line\) has already been mapped to IRQ index 0"): + intc.add_irq(line, 0) + intc.add_irq(line, 1) + + def test_add_irq_index_twice(self): + intc = InterruptController() + line_0 = IRQLine() + line_1 = IRQLine() + with self.assertRaisesRegex(ValueError, + r"IRQ index 0 has already been mapped to IRQ line \(sig line_0\)"): + intc.add_irq(line_0, 0) + intc.add_irq(line_1, 0) + + def test_iter_irqs(self): + intc = InterruptController() + line_0 = IRQLine() + line_1 = IRQLine() + intc.add_irq(line_0, 0) + intc.add_irq(line_1, 1) + self.assertEqual(list(intc.iter_irqs()), [ + (0, line_0), + (1, line_1), + ]) + + def test_find_index(self): + intc = InterruptController() + line_0 = IRQLine() + line_1 = IRQLine() + intc.add_irq(line_0, 0) + intc.add_irq(line_1, 1) + self.assertEqual(intc.find_index(line_0), 0) + self.assertEqual(intc.find_index(line_1), 1) + + def test_find_index_absent(self): + intc = InterruptController() + line = IRQLine() + with self.assertRaises(KeyError): + intc.find_index(line) + + +class GenericInterruptControllerTestCase(unittest.TestCase): + def test_wrong_width(self): + with self.assertRaisesRegex(ValueError, + r"Width must be a strictly positive integer, not 'foo'"): + intc = GenericInterruptController(width="foo") + with self.assertRaisesRegex(ValueError, + r"Width must be a strictly positive integer, not 0"): + intc = GenericInterruptController(width=0) + + def test_add_irq_wrong_index(self): + intc = GenericInterruptController(width=8) + line = IRQLine() + with self.assertRaisesRegex(ValueError, + r"IRQ index must be an integer ranging from 0 to 7, not 8"): + intc.add_irq(line, 8) + + def test_passthrough(self): + dut = GenericInterruptController(width=8) + line_0 = IRQLine() + line_1 = IRQLine() + dut.add_irq(line_0, 0) + dut.add_irq(line_1, 1) + + def process(): + self.assertEqual((yield dut.ip), 0b00) + + yield line_0.eq(1) + yield Delay(1e-6) + self.assertEqual((yield dut.ip), 0b01) + + yield line_1.eq(1) + yield Delay(1e-6) + self.assertEqual((yield dut.ip), 0b11) + + with Simulator(dut, vcd_file=open("test.vcd", "w")) as sim: + sim.add_process(process) + sim.run() -- 2.30.2