From: whitequark Date: Thu, 27 Aug 2020 00:33:31 +0000 (+0000) Subject: tests: move out of the main package. X-Git-Url: https://git.libre-soc.org/?a=commitdiff_plain;h=34d52b8cdafaa2018522568459715c3d3bc43bb7;p=nmigen.git tests: move out of the main package. Compared to tests in the repository root, tests in the package have many downsides: * Unless explicitly excluded in find_packages(), tests and their support code effectively become a part of public API. This, unfortunately, happened with FHDLTestCase, which was never intended for downstream use. * Even if explicitly excluded from the setuptools package, using an editable install, or setting PYTHONPATH still allows accessing the tests. * Having a sub-package that is present in the source tree but not exported (or, worse, exported only sometimes) is confusing. * The name `nmigen.test` cannot be used for anything else, such as testing utilities that *are* intended for downstream use. --- diff --git a/.gitignore b/.gitignore index 7c2e9bc..d11a3eb 100644 --- a/.gitignore +++ b/.gitignore @@ -9,7 +9,7 @@ __pycache__/ /htmlcov # tests -**/test/spec_*/ +/tests/spec_*/ *.vcd *.gtkw diff --git a/nmigen/test/__init__.py b/nmigen/test/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/nmigen/test/compat/__init__.py b/nmigen/test/compat/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/nmigen/test/compat/support.py b/nmigen/test/compat/support.py deleted file mode 100644 index 8491652..0000000 --- a/nmigen/test/compat/support.py +++ /dev/null @@ -1,16 +0,0 @@ -from ..._utils import _ignore_deprecated -from ...compat import * -from ...compat.fhdl import verilog - - -class SimCase: - def setUp(self, *args, **kwargs): - with _ignore_deprecated(): - self.tb = self.TestBench(*args, **kwargs) - - def test_to_verilog(self): - verilog.convert(self.tb) - - def run_with(self, generator): - with _ignore_deprecated(): - run_simulation(self.tb, generator) diff --git a/nmigen/test/compat/test_coding.py b/nmigen/test/compat/test_coding.py deleted file mode 100644 index 452a048..0000000 --- a/nmigen/test/compat/test_coding.py +++ /dev/null @@ -1,116 +0,0 @@ -# nmigen: UnusedElaboratable=no - -import unittest - -from ...compat import * -from ...compat.genlib.coding import * - -from .support import SimCase - - -class EncCase(SimCase, unittest.TestCase): - class TestBench(Module): - def __init__(self): - self.submodules.dut = Encoder(8) - - def test_sizes(self): - self.assertEqual(len(self.tb.dut.i), 8) - self.assertEqual(len(self.tb.dut.o), 3) - self.assertEqual(len(self.tb.dut.n), 1) - - def test_run_sequence(self): - seq = list(range(1<<8)) - def gen(): - for _ in range(256): - if seq: - yield self.tb.dut.i.eq(seq.pop(0)) - yield - if (yield self.tb.dut.n): - self.assertNotIn((yield self.tb.dut.i), [1< 0: - self.assertEqual(i & 1<<(o - 1), 0) - self.assertGreaterEqual(i, 1< 0: - self.assertEqual(i & 1<<(o - 1), 0) - self.assertGreaterEqual(i, 1< q, - lambda p, q: p >= q, - lambda p, q: p < q, - lambda p, q: p <= q, - lambda p, q: p == q, - lambda p, q: p != q, - ] - self.vals = [] - for asign in 1, -1: - for bsign in 1, -1: - for f in comps: - r = Signal() - r0 = f(asign*self.a, bsign*self.b) - self.comb += r.eq(r0) - self.vals.append((asign, bsign, f, r, r0.op)) - - def test_comparisons(self): - def gen(): - for i in range(-4, 4): - yield self.tb.a.eq(i) - yield self.tb.b.eq(i) - yield - a = yield self.tb.a - b = yield self.tb.b - for asign, bsign, f, r, op in self.tb.vals: - r, r0 = (yield r), f(asign*a, bsign*b) - self.assertEqual(r, int(r0), - "got {}, want {}*{} {} {}*{} = {}".format( - r, asign, a, op, bsign, b, r0)) - self.run_with(gen()) diff --git a/nmigen/test/compat/test_size.py b/nmigen/test/compat/test_size.py deleted file mode 100644 index e3864f9..0000000 --- a/nmigen/test/compat/test_size.py +++ /dev/null @@ -1,21 +0,0 @@ -import unittest - -from ..._utils import _ignore_deprecated -from ...compat import * - - -def _same_slices(a, b): - return a.value is b.value and a.start == b.start and a.stop == b.stop - - -class SignalSizeCase(unittest.TestCase): - def setUp(self): - self.i = C(0xaa) - self.j = C(-127) - with _ignore_deprecated(): - self.s = Signal((13, True)) - - def test_len(self): - self.assertEqual(len(self.s), 13) - self.assertEqual(len(self.i), 8) - self.assertEqual(len(self.j), 8) diff --git a/nmigen/test/test_build_dsl.py b/nmigen/test/test_build_dsl.py deleted file mode 100644 index 87f082a..0000000 --- a/nmigen/test/test_build_dsl.py +++ /dev/null @@ -1,328 +0,0 @@ -from collections import OrderedDict - -from ..build.dsl import * -from .utils import * - - -class PinsTestCase(FHDLTestCase): - def test_basic(self): - p = Pins("A0 A1 A2") - self.assertEqual(repr(p), "(pins io A0 A1 A2)") - self.assertEqual(len(p.names), 3) - self.assertEqual(p.dir, "io") - self.assertEqual(p.invert, False) - self.assertEqual(list(p), ["A0", "A1", "A2"]) - - def test_invert(self): - p = PinsN("A0") - self.assertEqual(repr(p), "(pins-n io A0)") - self.assertEqual(p.invert, True) - - def test_invert_arg(self): - p = Pins("A0", invert=True) - self.assertEqual(p.invert, True) - - def test_conn(self): - p = Pins("0 1 2", conn=("pmod", 0)) - self.assertEqual(list(p), ["pmod_0:0", "pmod_0:1", "pmod_0:2"]) - p = Pins("0 1 2", conn=("pmod", "a")) - self.assertEqual(list(p), ["pmod_a:0", "pmod_a:1", "pmod_a:2"]) - - def test_map_names(self): - p = Pins("0 1 2", conn=("pmod", 0)) - mapping = { - "pmod_0:0": "A0", - "pmod_0:1": "A1", - "pmod_0:2": "A2", - } - self.assertEqual(p.map_names(mapping, p), ["A0", "A1", "A2"]) - - def test_map_names_recur(self): - p = Pins("0", conn=("pmod", 0)) - mapping = { - "pmod_0:0": "ext_0:1", - "ext_0:1": "A1", - } - self.assertEqual(p.map_names(mapping, p), ["A1"]) - - def test_wrong_names(self): - with self.assertRaisesRegex(TypeError, - r"^Names must be a whitespace-separated string, not \['A0', 'A1', 'A2'\]$"): - p = Pins(["A0", "A1", "A2"]) - - def test_wrong_dir(self): - with self.assertRaisesRegex(TypeError, - r"^Direction must be one of \"i\", \"o\", \"oe\", or \"io\", not 'wrong'$"): - p = Pins("A0 A1", dir="wrong") - - def test_wrong_conn(self): - with self.assertRaisesRegex(TypeError, - (r"^Connector must be None or a pair of string \(connector name\) and " - r"integer\/string \(connector number\), not \('foo', None\)$")): - p = Pins("A0 A1", conn=("foo", None)) - - def test_wrong_map_names(self): - p = Pins("0 1 2", conn=("pmod", 0)) - mapping = { - "pmod_0:0": "A0", - } - with self.assertRaisesRegex(NameError, - (r"^Resource \(pins io pmod_0:0 pmod_0:1 pmod_0:2\) refers to nonexistent " - r"connector pin pmod_0:1$")): - p.map_names(mapping, p) - - def test_wrong_assert_width(self): - with self.assertRaisesRegex(AssertionError, - r"^3 names are specified \(0 1 2\), but 4 names are expected$"): - Pins("0 1 2", assert_width=4) - - -class DiffPairsTestCase(FHDLTestCase): - def test_basic(self): - dp = DiffPairs(p="A0 A1", n="B0 B1") - self.assertEqual(repr(dp), "(diffpairs io (p A0 A1) (n B0 B1))") - self.assertEqual(dp.p.names, ["A0", "A1"]) - self.assertEqual(dp.n.names, ["B0", "B1"]) - self.assertEqual(dp.dir, "io") - self.assertEqual(list(dp), [("A0", "B0"), ("A1", "B1")]) - - def test_invert(self): - dp = DiffPairsN(p="A0", n="B0") - self.assertEqual(repr(dp), "(diffpairs-n io (p A0) (n B0))") - self.assertEqual(dp.p.names, ["A0"]) - self.assertEqual(dp.n.names, ["B0"]) - self.assertEqual(dp.invert, True) - - def test_conn(self): - dp = DiffPairs(p="0 1 2", n="3 4 5", conn=("pmod", 0)) - self.assertEqual(list(dp), [ - ("pmod_0:0", "pmod_0:3"), - ("pmod_0:1", "pmod_0:4"), - ("pmod_0:2", "pmod_0:5"), - ]) - - def test_dir(self): - dp = DiffPairs("A0", "B0", dir="o") - self.assertEqual(dp.dir, "o") - self.assertEqual(dp.p.dir, "o") - self.assertEqual(dp.n.dir, "o") - - def test_wrong_width(self): - with self.assertRaisesRegex(TypeError, - (r"^Positive and negative pins must have the same width, but \(pins io A0\) " - r"and \(pins io B0 B1\) do not$")): - dp = DiffPairs("A0", "B0 B1") - - def test_wrong_assert_width(self): - with self.assertRaisesRegex(AssertionError, - r"^3 names are specified \(0 1 2\), but 4 names are expected$"): - DiffPairs("0 1 2", "3 4 5", assert_width=4) - - -class AttrsTestCase(FHDLTestCase): - def test_basic(self): - a = Attrs(IO_STANDARD="LVCMOS33", PULLUP=1) - self.assertEqual(a["IO_STANDARD"], "LVCMOS33") - self.assertEqual(repr(a), "(attrs IO_STANDARD='LVCMOS33' PULLUP=1)") - - def test_remove(self): - a = Attrs(FOO=None) - self.assertEqual(a["FOO"], None) - self.assertEqual(repr(a), "(attrs !FOO)") - - def test_callable(self): - fn = lambda self: "FOO" - a = Attrs(FOO=fn) - self.assertEqual(a["FOO"], fn) - self.assertEqual(repr(a), "(attrs FOO={!r})".format(fn)) - - def test_wrong_value(self): - with self.assertRaisesRegex(TypeError, - r"^Value of attribute FOO must be None, int, str, or callable, not 1\.0$"): - a = Attrs(FOO=1.0) - - -class ClockTestCase(FHDLTestCase): - def test_basic(self): - c = Clock(1_000_000) - self.assertEqual(c.frequency, 1e6) - self.assertEqual(c.period, 1e-6) - self.assertEqual(repr(c), "(clock 1000000.0)") - - -class SubsignalTestCase(FHDLTestCase): - def test_basic_pins(self): - s = Subsignal("a", Pins("A0"), Attrs(IOSTANDARD="LVCMOS33")) - self.assertEqual(repr(s), - "(subsignal a (pins io A0) (attrs IOSTANDARD='LVCMOS33'))") - - def test_basic_diffpairs(self): - s = Subsignal("a", DiffPairs("A0", "B0")) - self.assertEqual(repr(s), - "(subsignal a (diffpairs io (p A0) (n B0)))") - - def test_basic_subsignals(self): - s = Subsignal("a", - Subsignal("b", Pins("A0")), - Subsignal("c", Pins("A1"))) - self.assertEqual(repr(s), - "(subsignal a (subsignal b (pins io A0)) " - "(subsignal c (pins io A1)))") - - def test_attrs(self): - s = Subsignal("a", - Subsignal("b", Pins("A0")), - Subsignal("c", Pins("A0"), Attrs(SLEW="FAST")), - Attrs(IOSTANDARD="LVCMOS33")) - self.assertEqual(s.attrs, {"IOSTANDARD": "LVCMOS33"}) - self.assertEqual(s.ios[0].attrs, {}) - self.assertEqual(s.ios[1].attrs, {"SLEW": "FAST"}) - - def test_attrs_many(self): - s = Subsignal("a", Pins("A0"), Attrs(SLEW="FAST"), Attrs(PULLUP="1")) - self.assertEqual(s.attrs, {"SLEW": "FAST", "PULLUP": "1"}) - - def test_clock(self): - s = Subsignal("a", Pins("A0"), Clock(1e6)) - self.assertEqual(s.clock.frequency, 1e6) - - def test_wrong_empty_io(self): - with self.assertRaisesRegex(ValueError, r"^Missing I\/O constraints$"): - s = Subsignal("a") - - def test_wrong_io(self): - with self.assertRaisesRegex(TypeError, - (r"^Constraint must be one of Pins, DiffPairs, Subsignal, Attrs, or Clock, " - r"not 'wrong'$")): - s = Subsignal("a", "wrong") - - def test_wrong_pins(self): - with self.assertRaisesRegex(TypeError, - (r"^Pins and DiffPairs are incompatible with other location or subsignal " - r"constraints, but \(pins io A1\) appears after \(pins io A0\)$")): - s = Subsignal("a", Pins("A0"), Pins("A1")) - - def test_wrong_diffpairs(self): - with self.assertRaisesRegex(TypeError, - (r"^Pins and DiffPairs are incompatible with other location or subsignal " - r"constraints, but \(pins io A1\) appears after \(diffpairs io \(p A0\) \(n B0\)\)$")): - s = Subsignal("a", DiffPairs("A0", "B0"), Pins("A1")) - - def test_wrong_subsignals(self): - with self.assertRaisesRegex(TypeError, - (r"^Pins and DiffPairs are incompatible with other location or subsignal " - r"constraints, but \(pins io B0\) appears after \(subsignal b \(pins io A0\)\)$")): - s = Subsignal("a", Subsignal("b", Pins("A0")), Pins("B0")) - - def test_wrong_clock(self): - with self.assertRaisesRegex(TypeError, - (r"^Clock constraint can only be applied to Pins or DiffPairs, not " - r"\(subsignal b \(pins io A0\)\)$")): - s = Subsignal("a", Subsignal("b", Pins("A0")), Clock(1e6)) - - def test_wrong_clock_many(self): - with self.assertRaisesRegex(ValueError, - r"^Clock constraint can be applied only once$"): - s = Subsignal("a", Pins("A0"), Clock(1e6), Clock(1e7)) - - -class ResourceTestCase(FHDLTestCase): - def test_basic(self): - r = Resource("serial", 0, - Subsignal("tx", Pins("A0", dir="o")), - Subsignal("rx", Pins("A1", dir="i")), - Attrs(IOSTANDARD="LVCMOS33")) - self.assertEqual(repr(r), "(resource serial 0" - " (subsignal tx (pins o A0))" - " (subsignal rx (pins i A1))" - " (attrs IOSTANDARD='LVCMOS33'))") - - def test_family(self): - ios = [Subsignal("clk", Pins("A0", dir="o"))] - r1 = Resource.family(0, default_name="spi", ios=ios) - r2 = Resource.family("spi_flash", 0, default_name="spi", ios=ios) - r3 = Resource.family("spi_flash", 0, default_name="spi", ios=ios, name_suffix="4x") - r4 = Resource.family(0, default_name="spi", ios=ios, name_suffix="2x") - self.assertEqual(r1.name, "spi") - self.assertEqual(r1.ios, ios) - self.assertEqual(r2.name, "spi_flash") - self.assertEqual(r2.ios, ios) - self.assertEqual(r3.name, "spi_flash_4x") - self.assertEqual(r3.ios, ios) - self.assertEqual(r4.name, "spi_2x") - self.assertEqual(r4.ios, ios) - - -class ConnectorTestCase(FHDLTestCase): - def test_string(self): - c = Connector("pmod", 0, "A0 A1 A2 A3 - - A4 A5 A6 A7 - -") - self.assertEqual(c.name, "pmod") - self.assertEqual(c.number, 0) - self.assertEqual(c.mapping, OrderedDict([ - ("1", "A0"), - ("2", "A1"), - ("3", "A2"), - ("4", "A3"), - ("7", "A4"), - ("8", "A5"), - ("9", "A6"), - ("10", "A7"), - ])) - self.assertEqual(list(c), [ - ("pmod_0:1", "A0"), - ("pmod_0:2", "A1"), - ("pmod_0:3", "A2"), - ("pmod_0:4", "A3"), - ("pmod_0:7", "A4"), - ("pmod_0:8", "A5"), - ("pmod_0:9", "A6"), - ("pmod_0:10", "A7"), - ]) - self.assertEqual(repr(c), - "(connector pmod 0 1=>A0 2=>A1 3=>A2 4=>A3 7=>A4 8=>A5 9=>A6 10=>A7)") - - def test_dict(self): - c = Connector("ext", 1, {"DP0": "A0", "DP1": "A1"}) - self.assertEqual(c.name, "ext") - self.assertEqual(c.number, 1) - self.assertEqual(c.mapping, OrderedDict([ - ("DP0", "A0"), - ("DP1", "A1"), - ])) - - def test_conn(self): - c = Connector("pmod", 0, "0 1 2 3 - - 4 5 6 7 - -", conn=("expansion", 0)) - self.assertEqual(c.mapping, OrderedDict([ - ("1", "expansion_0:0"), - ("2", "expansion_0:1"), - ("3", "expansion_0:2"), - ("4", "expansion_0:3"), - ("7", "expansion_0:4"), - ("8", "expansion_0:5"), - ("9", "expansion_0:6"), - ("10", "expansion_0:7"), - ])) - - def test_str_name(self): - c = Connector("ext", "A", "0 1 2") - self.assertEqual(c.name, "ext") - self.assertEqual(c.number, "A") - - def test_conn_wrong_name(self): - with self.assertRaisesRegex(TypeError, - (r"^Connector must be None or a pair of string \(connector name\) and " - r"integer\/string \(connector number\), not \('foo', None\)$")): - Connector("ext", "A", "0 1 2", conn=("foo", None)) - - def test_wrong_io(self): - with self.assertRaisesRegex(TypeError, - r"^Connector I\/Os must be a dictionary or a string, not \[\]$"): - Connector("pmod", 0, []) - - def test_wrong_dict_key_value(self): - with self.assertRaisesRegex(TypeError, - r"^Connector pin name must be a string, not 0$"): - Connector("pmod", 0, {0: "A"}) - with self.assertRaisesRegex(TypeError, - r"^Platform pin name must be a string, not 0$"): - Connector("pmod", 0, {"A": 0}) diff --git a/nmigen/test/test_build_plat.py b/nmigen/test/test_build_plat.py deleted file mode 100644 index 1342501..0000000 --- a/nmigen/test/test_build_plat.py +++ /dev/null @@ -1,52 +0,0 @@ -from .. import * -from ..build.plat import * -from .utils import * - - -class MockPlatform(Platform): - resources = [] - connectors = [] - - required_tools = [] - - def toolchain_prepare(self, fragment, name, **kwargs): - raise NotImplementedError - - -class PlatformTestCase(FHDLTestCase): - def setUp(self): - self.platform = MockPlatform() - - def test_add_file_str(self): - self.platform.add_file("x.txt", "foo") - self.assertEqual(self.platform.extra_files["x.txt"], "foo") - - def test_add_file_bytes(self): - self.platform.add_file("x.txt", b"foo") - self.assertEqual(self.platform.extra_files["x.txt"], b"foo") - - def test_add_file_exact_duplicate(self): - self.platform.add_file("x.txt", b"foo") - self.platform.add_file("x.txt", b"foo") - - def test_add_file_io(self): - with open(__file__) as f: - self.platform.add_file("x.txt", f) - with open(__file__) as f: - self.assertEqual(self.platform.extra_files["x.txt"], f.read()) - - def test_add_file_wrong_filename(self): - with self.assertRaisesRegex(TypeError, - r"^File name must be a string, not 1$"): - self.platform.add_file(1, "") - - def test_add_file_wrong_contents(self): - with self.assertRaisesRegex(TypeError, - r"^File contents must be str, bytes, or a file-like object, not 1$"): - self.platform.add_file("foo", 1) - - def test_add_file_wrong_duplicate(self): - self.platform.add_file("foo", "") - with self.assertRaisesRegex(ValueError, - r"^File 'foo' already exists$"): - self.platform.add_file("foo", "bar") diff --git a/nmigen/test/test_build_res.py b/nmigen/test/test_build_res.py deleted file mode 100644 index 740d468..0000000 --- a/nmigen/test/test_build_res.py +++ /dev/null @@ -1,313 +0,0 @@ -# nmigen: UnusedElaboratable=no - -from .. import * -from ..hdl.rec import * -from ..lib.io import * -from ..build.dsl import * -from ..build.res import * -from .utils import * - - -class ResourceManagerTestCase(FHDLTestCase): - def setUp(self): - self.resources = [ - Resource("clk100", 0, DiffPairs("H1", "H2", dir="i"), Clock(100e6)), - Resource("clk50", 0, Pins("K1"), Clock(50e6)), - Resource("user_led", 0, Pins("A0", dir="o")), - Resource("i2c", 0, - Subsignal("scl", Pins("N10", dir="o")), - Subsignal("sda", Pins("N11")) - ) - ] - self.connectors = [ - Connector("pmod", 0, "B0 B1 B2 B3 - -"), - ] - self.cm = ResourceManager(self.resources, self.connectors) - - def test_basic(self): - self.cm = ResourceManager(self.resources, self.connectors) - self.assertEqual(self.cm.resources, { - ("clk100", 0): self.resources[0], - ("clk50", 0): self.resources[1], - ("user_led", 0): self.resources[2], - ("i2c", 0): self.resources[3] - }) - self.assertEqual(self.cm.connectors, { - ("pmod", 0): self.connectors[0], - }) - - def test_add_resources(self): - new_resources = [ - Resource("user_led", 1, Pins("A1", dir="o")) - ] - self.cm.add_resources(new_resources) - self.assertEqual(self.cm.resources, { - ("clk100", 0): self.resources[0], - ("clk50", 0): self.resources[1], - ("user_led", 0): self.resources[2], - ("i2c", 0): self.resources[3], - ("user_led", 1): new_resources[0] - }) - - def test_lookup(self): - r = self.cm.lookup("user_led", 0) - self.assertIs(r, self.cm.resources["user_led", 0]) - - def test_request_basic(self): - r = self.cm.lookup("user_led", 0) - user_led = self.cm.request("user_led", 0) - - self.assertIsInstance(user_led, Pin) - self.assertEqual(user_led.name, "user_led_0") - self.assertEqual(user_led.width, 1) - self.assertEqual(user_led.dir, "o") - - ports = list(self.cm.iter_ports()) - self.assertEqual(len(ports), 1) - - self.assertEqual(list(self.cm.iter_port_constraints()), [ - ("user_led_0__io", ["A0"], {}) - ]) - - def test_request_with_dir(self): - i2c = self.cm.request("i2c", 0, dir={"sda": "o"}) - self.assertIsInstance(i2c, Record) - self.assertIsInstance(i2c.sda, Pin) - self.assertEqual(i2c.sda.dir, "o") - - def test_request_tristate(self): - i2c = self.cm.request("i2c", 0) - self.assertEqual(i2c.sda.dir, "io") - - ports = list(self.cm.iter_ports()) - self.assertEqual(len(ports), 2) - scl, sda = ports - self.assertEqual(ports[1].name, "i2c_0__sda__io") - self.assertEqual(ports[1].width, 1) - - scl_info, sda_info = self.cm.iter_single_ended_pins() - self.assertIs(scl_info[0], i2c.scl) - self.assertIs(scl_info[1].io, scl) - self.assertEqual(scl_info[2], {}) - self.assertEqual(scl_info[3], False) - self.assertIs(sda_info[0], i2c.sda) - self.assertIs(sda_info[1].io, sda) - - self.assertEqual(list(self.cm.iter_port_constraints()), [ - ("i2c_0__scl__io", ["N10"], {}), - ("i2c_0__sda__io", ["N11"], {}) - ]) - - def test_request_diffpairs(self): - clk100 = self.cm.request("clk100", 0) - self.assertIsInstance(clk100, Pin) - self.assertEqual(clk100.dir, "i") - self.assertEqual(clk100.width, 1) - - ports = list(self.cm.iter_ports()) - self.assertEqual(len(ports), 2) - p, n = ports - self.assertEqual(p.name, "clk100_0__p") - self.assertEqual(p.width, clk100.width) - self.assertEqual(n.name, "clk100_0__n") - self.assertEqual(n.width, clk100.width) - - clk100_info, = self.cm.iter_differential_pins() - self.assertIs(clk100_info[0], clk100) - self.assertIs(clk100_info[1].p, p) - self.assertIs(clk100_info[1].n, n) - self.assertEqual(clk100_info[2], {}) - self.assertEqual(clk100_info[3], False) - - self.assertEqual(list(self.cm.iter_port_constraints()), [ - ("clk100_0__p", ["H1"], {}), - ("clk100_0__n", ["H2"], {}), - ]) - - def test_request_inverted(self): - new_resources = [ - Resource("cs", 0, PinsN("X0")), - Resource("clk", 0, DiffPairsN("Y0", "Y1")), - ] - self.cm.add_resources(new_resources) - - cs = self.cm.request("cs") - clk = self.cm.request("clk") - cs_io, clk_p, clk_n = self.cm.iter_ports() - - cs_info, = self.cm.iter_single_ended_pins() - self.assertIs(cs_info[0], cs) - self.assertIs(cs_info[1].io, cs_io) - self.assertEqual(cs_info[2], {}) - self.assertEqual(cs_info[3], True) - - clk_info, = self.cm.iter_differential_pins() - self.assertIs(clk_info[0], clk) - self.assertIs(clk_info[1].p, clk_p) - self.assertIs(clk_info[1].n, clk_n) - self.assertEqual(clk_info[2], {}) - self.assertEqual(clk_info[3], True) - - def test_request_raw(self): - clk50 = self.cm.request("clk50", 0, dir="-") - self.assertIsInstance(clk50, Record) - self.assertIsInstance(clk50.io, Signal) - - ports = list(self.cm.iter_ports()) - self.assertEqual(len(ports), 1) - self.assertIs(ports[0], clk50.io) - - def test_request_raw_diffpairs(self): - clk100 = self.cm.request("clk100", 0, dir="-") - self.assertIsInstance(clk100, Record) - self.assertIsInstance(clk100.p, Signal) - self.assertIsInstance(clk100.n, Signal) - - ports = list(self.cm.iter_ports()) - self.assertEqual(len(ports), 2) - self.assertIs(ports[0], clk100.p) - self.assertIs(ports[1], clk100.n) - - def test_request_via_connector(self): - self.cm.add_resources([ - Resource("spi", 0, - Subsignal("ss", Pins("1", conn=("pmod", 0))), - Subsignal("clk", Pins("2", conn=("pmod", 0))), - Subsignal("miso", Pins("3", conn=("pmod", 0))), - Subsignal("mosi", Pins("4", conn=("pmod", 0))), - ) - ]) - spi0 = self.cm.request("spi", 0) - self.assertEqual(list(self.cm.iter_port_constraints()), [ - ("spi_0__ss__io", ["B0"], {}), - ("spi_0__clk__io", ["B1"], {}), - ("spi_0__miso__io", ["B2"], {}), - ("spi_0__mosi__io", ["B3"], {}), - ]) - - def test_request_via_nested_connector(self): - new_connectors = [ - Connector("pmod_extension", 0, "1 2 3 4 - -", conn=("pmod", 0)), - ] - self.cm.add_connectors(new_connectors) - self.cm.add_resources([ - Resource("spi", 0, - Subsignal("ss", Pins("1", conn=("pmod_extension", 0))), - Subsignal("clk", Pins("2", conn=("pmod_extension", 0))), - Subsignal("miso", Pins("3", conn=("pmod_extension", 0))), - Subsignal("mosi", Pins("4", conn=("pmod_extension", 0))), - ) - ]) - spi0 = self.cm.request("spi", 0) - self.assertEqual(list(self.cm.iter_port_constraints()), [ - ("spi_0__ss__io", ["B0"], {}), - ("spi_0__clk__io", ["B1"], {}), - ("spi_0__miso__io", ["B2"], {}), - ("spi_0__mosi__io", ["B3"], {}), - ]) - - def test_request_clock(self): - clk100 = self.cm.request("clk100", 0) - clk50 = self.cm.request("clk50", 0, dir="i") - clk100_port_p, clk100_port_n, clk50_port = self.cm.iter_ports() - self.assertEqual(list(self.cm.iter_clock_constraints()), [ - (clk100.i, clk100_port_p, 100e6), - (clk50.i, clk50_port, 50e6) - ]) - - def test_add_clock(self): - i2c = self.cm.request("i2c") - self.cm.add_clock_constraint(i2c.scl.o, 100e3) - self.assertEqual(list(self.cm.iter_clock_constraints()), [ - (i2c.scl.o, None, 100e3) - ]) - - def test_wrong_resources(self): - with self.assertRaisesRegex(TypeError, r"^Object 'wrong' is not a Resource$"): - self.cm.add_resources(['wrong']) - - def test_wrong_resources_duplicate(self): - with self.assertRaisesRegex(NameError, - (r"^Trying to add \(resource user_led 0 \(pins o A1\)\), but " - r"\(resource user_led 0 \(pins o A0\)\) has the same name and number$")): - self.cm.add_resources([Resource("user_led", 0, Pins("A1", dir="o"))]) - - def test_wrong_connectors(self): - with self.assertRaisesRegex(TypeError, r"^Object 'wrong' is not a Connector$"): - self.cm.add_connectors(['wrong']) - - def test_wrong_connectors_duplicate(self): - with self.assertRaisesRegex(NameError, - (r"^Trying to add \(connector pmod 0 1=>1 2=>2\), but " - r"\(connector pmod 0 1=>B0 2=>B1 3=>B2 4=>B3\) has the same name and number$")): - self.cm.add_connectors([Connector("pmod", 0, "1 2")]) - - def test_wrong_lookup(self): - with self.assertRaisesRegex(ResourceError, - r"^Resource user_led#1 does not exist$"): - r = self.cm.lookup("user_led", 1) - - def test_wrong_clock_signal(self): - with self.assertRaisesRegex(TypeError, - r"^Object None is not a Signal$"): - self.cm.add_clock_constraint(None, 10e6) - - def test_wrong_clock_frequency(self): - with self.assertRaisesRegex(TypeError, - r"^Frequency must be a number, not None$"): - self.cm.add_clock_constraint(Signal(), None) - - def test_wrong_request_duplicate(self): - with self.assertRaisesRegex(ResourceError, - r"^Resource user_led#0 has already been requested$"): - self.cm.request("user_led", 0) - self.cm.request("user_led", 0) - - def test_wrong_request_duplicate_physical(self): - self.cm.add_resources([ - Resource("clk20", 0, Pins("H1", dir="i")), - ]) - self.cm.request("clk100", 0) - with self.assertRaisesRegex(ResourceError, - (r"^Resource component clk20_0 uses physical pin H1, but it is already " - r"used by resource component clk100_0 that was requested earlier$")): - self.cm.request("clk20", 0) - - def test_wrong_request_with_dir(self): - with self.assertRaisesRegex(TypeError, - (r"^Direction must be one of \"i\", \"o\", \"oe\", \"io\", or \"-\", " - r"not 'wrong'$")): - user_led = self.cm.request("user_led", 0, dir="wrong") - - def test_wrong_request_with_dir_io(self): - with self.assertRaisesRegex(ValueError, - (r"^Direction of \(pins o A0\) cannot be changed from \"o\" to \"i\"; direction " - r"can be changed from \"io\" to \"i\", \"o\", or \"oe\", or from anything " - r"to \"-\"$")): - user_led = self.cm.request("user_led", 0, dir="i") - - def test_wrong_request_with_dir_dict(self): - with self.assertRaisesRegex(TypeError, - (r"^Directions must be a dict, not 'i', because \(resource i2c 0 \(subsignal scl " - r"\(pins o N10\)\) \(subsignal sda \(pins io N11\)\)\) " - r"has subsignals$")): - i2c = self.cm.request("i2c", 0, dir="i") - - def test_wrong_request_with_wrong_xdr(self): - with self.assertRaisesRegex(ValueError, - r"^Data rate of \(pins o A0\) must be a non-negative integer, not -1$"): - user_led = self.cm.request("user_led", 0, xdr=-1) - - def test_wrong_request_with_xdr_dict(self): - with self.assertRaisesRegex(TypeError, - r"^Data rate must be a dict, not 2, because \(resource i2c 0 \(subsignal scl " - r"\(pins o N10\)\) \(subsignal sda \(pins io N11\)\)\) " - r"has subsignals$"): - i2c = self.cm.request("i2c", 0, xdr=2) - - def test_wrong_clock_constraint_twice(self): - clk100 = self.cm.request("clk100") - with self.assertRaisesRegex(ValueError, - (r"^Cannot add clock constraint on \(sig clk100_0__i\), which is already " - r"constrained to 100000000\.0 Hz$")): - self.cm.add_clock_constraint(clk100.i, 1e6) diff --git a/nmigen/test/test_compat.py b/nmigen/test/test_compat.py deleted file mode 100644 index 62e2d92..0000000 --- a/nmigen/test/test_compat.py +++ /dev/null @@ -1,9 +0,0 @@ -from ..hdl.ir import Fragment -from ..compat import * -from .utils import * - - -class CompatTestCase(FHDLTestCase): - def test_fragment_get(self): - m = Module() - f = Fragment.get(m, platform=None) diff --git a/nmigen/test/test_examples.py b/nmigen/test/test_examples.py deleted file mode 100644 index 44211d3..0000000 --- a/nmigen/test/test_examples.py +++ /dev/null @@ -1,34 +0,0 @@ -import sys -import subprocess -from pathlib import Path - -from .utils import * - - -def example_test(name): - path = (Path(__file__).parent / ".." / ".." / "examples" / name).resolve() - def test_function(self): - subprocess.check_call([sys.executable, str(path), "generate", "-t", "v"], - stdout=subprocess.DEVNULL) - return test_function - - -class ExamplesTestCase(FHDLTestCase): - test_alu = example_test("basic/alu.py") - test_alu_hier = example_test("basic/alu_hier.py") - test_arst = example_test("basic/arst.py") - test_cdc = example_test("basic/cdc.py") - test_ctr = example_test("basic/ctr.py") - test_ctr_en = example_test("basic/ctr_en.py") - test_fsm = example_test("basic/fsm.py") - test_gpio = example_test("basic/gpio.py") - test_inst = example_test("basic/inst.py") - test_mem = example_test("basic/mem.py") - test_pmux = example_test("basic/pmux.py") - test_por = example_test("basic/por.py") - - def test_uart(self): - path = (Path(__file__).parent / ".." / ".." / "examples" / "basic" / "uart.py").resolve() - def test_function(self): - subprocess.check_call([sys.executable, str(path), "generate"], - stdout=subprocess.DEVNULL) diff --git a/nmigen/test/test_hdl_ast.py b/nmigen/test/test_hdl_ast.py deleted file mode 100644 index af96e16..0000000 --- a/nmigen/test/test_hdl_ast.py +++ /dev/null @@ -1,1058 +0,0 @@ -import warnings -from enum import Enum - -from ..hdl.ast import * -from .utils import * - - -class UnsignedEnum(Enum): - FOO = 1 - BAR = 2 - BAZ = 3 - - -class SignedEnum(Enum): - FOO = -1 - BAR = 0 - BAZ = +1 - - -class StringEnum(Enum): - FOO = "a" - BAR = "b" - - -class ShapeTestCase(FHDLTestCase): - def test_make(self): - s1 = Shape() - self.assertEqual(s1.width, 1) - self.assertEqual(s1.signed, False) - s2 = Shape(signed=True) - self.assertEqual(s2.width, 1) - self.assertEqual(s2.signed, True) - s3 = Shape(3, True) - self.assertEqual(s3.width, 3) - self.assertEqual(s3.signed, True) - - def test_make_wrong(self): - with self.assertRaisesRegex(TypeError, - r"^Width must be a non-negative integer, not -1$"): - Shape(-1) - - def test_compare_wrong(self): - with self.assertRaisesRegex(TypeError, - r"^Shapes may be compared with other Shapes and \(int, bool\) tuples, not 'hi'$"): - Shape(1, True) == 'hi' - - def test_compare_tuple_wrong(self): - with self.assertRaisesRegex(TypeError, - r"^Shapes may be compared with other Shapes and \(int, bool\) tuples, not \(2, 3\)$"): - Shape(1, True) == (2, 3) - - def test_repr(self): - self.assertEqual(repr(Shape()), "unsigned(1)") - self.assertEqual(repr(Shape(2, True)), "signed(2)") - - def test_tuple(self): - width, signed = Shape() - self.assertEqual(width, 1) - self.assertEqual(signed, False) - - def test_unsigned(self): - s1 = unsigned(2) - self.assertIsInstance(s1, Shape) - self.assertEqual(s1.width, 2) - self.assertEqual(s1.signed, False) - - def test_signed(self): - s1 = signed(2) - self.assertIsInstance(s1, Shape) - self.assertEqual(s1.width, 2) - self.assertEqual(s1.signed, True) - - def test_cast_shape(self): - s1 = Shape.cast(unsigned(1)) - self.assertEqual(s1.width, 1) - self.assertEqual(s1.signed, False) - s2 = Shape.cast(signed(3)) - self.assertEqual(s2.width, 3) - self.assertEqual(s2.signed, True) - - def test_cast_int(self): - s1 = Shape.cast(2) - self.assertEqual(s1.width, 2) - self.assertEqual(s1.signed, False) - - def test_cast_int_wrong(self): - with self.assertRaisesRegex(TypeError, - r"^Width must be a non-negative integer, not -1$"): - Shape.cast(-1) - - def test_cast_tuple(self): - with warnings.catch_warnings(): - warnings.filterwarnings(action="ignore", category=DeprecationWarning) - s1 = Shape.cast((1, True)) - self.assertEqual(s1.width, 1) - self.assertEqual(s1.signed, True) - - def test_cast_tuple_wrong(self): - with warnings.catch_warnings(): - warnings.filterwarnings(action="ignore", category=DeprecationWarning) - with self.assertRaisesRegex(TypeError, - r"^Width must be a non-negative integer, not -1$"): - Shape.cast((-1, True)) - - def test_cast_range(self): - s1 = Shape.cast(range(0, 8)) - self.assertEqual(s1.width, 3) - self.assertEqual(s1.signed, False) - s2 = Shape.cast(range(0, 9)) - self.assertEqual(s2.width, 4) - self.assertEqual(s2.signed, False) - s3 = Shape.cast(range(-7, 8)) - self.assertEqual(s3.width, 4) - self.assertEqual(s3.signed, True) - s4 = Shape.cast(range(0, 1)) - self.assertEqual(s4.width, 1) - self.assertEqual(s4.signed, False) - s5 = Shape.cast(range(-1, 0)) - self.assertEqual(s5.width, 1) - self.assertEqual(s5.signed, True) - s6 = Shape.cast(range(0, 0)) - self.assertEqual(s6.width, 0) - self.assertEqual(s6.signed, False) - s7 = Shape.cast(range(-1, -1)) - self.assertEqual(s7.width, 0) - self.assertEqual(s7.signed, True) - - def test_cast_enum(self): - s1 = Shape.cast(UnsignedEnum) - self.assertEqual(s1.width, 2) - self.assertEqual(s1.signed, False) - s2 = Shape.cast(SignedEnum) - self.assertEqual(s2.width, 2) - self.assertEqual(s2.signed, True) - - def test_cast_enum_bad(self): - with self.assertRaisesRegex(TypeError, - r"^Only enumerations with integer values can be used as value shapes$"): - Shape.cast(StringEnum) - - def test_cast_bad(self): - with self.assertRaisesRegex(TypeError, - r"^Object 'foo' cannot be used as value shape$"): - Shape.cast("foo") - - -class ValueTestCase(FHDLTestCase): - def test_cast(self): - self.assertIsInstance(Value.cast(0), Const) - self.assertIsInstance(Value.cast(True), Const) - c = Const(0) - self.assertIs(Value.cast(c), c) - with self.assertRaisesRegex(TypeError, - r"^Object 'str' cannot be converted to an nMigen value$"): - Value.cast("str") - - def test_cast_enum(self): - e1 = Value.cast(UnsignedEnum.FOO) - self.assertIsInstance(e1, Const) - self.assertEqual(e1.shape(), unsigned(2)) - e2 = Value.cast(SignedEnum.FOO) - self.assertIsInstance(e2, Const) - self.assertEqual(e2.shape(), signed(2)) - - def test_cast_enum_wrong(self): - with self.assertRaisesRegex(TypeError, - r"^Only enumerations with integer values can be used as value shapes$"): - Value.cast(StringEnum.FOO) - - def test_bool(self): - with self.assertRaisesRegex(TypeError, - r"^Attempted to convert nMigen value to Python boolean$"): - if Const(0): - pass - - def test_len(self): - self.assertEqual(len(Const(10)), 4) - - def test_getitem_int(self): - s1 = Const(10)[0] - self.assertIsInstance(s1, Slice) - self.assertEqual(s1.start, 0) - self.assertEqual(s1.stop, 1) - s2 = Const(10)[-1] - self.assertIsInstance(s2, Slice) - self.assertEqual(s2.start, 3) - self.assertEqual(s2.stop, 4) - with self.assertRaisesRegex(IndexError, - r"^Cannot index 5 bits into 4-bit value$"): - Const(10)[5] - - def test_getitem_slice(self): - s1 = Const(10)[1:3] - self.assertIsInstance(s1, Slice) - self.assertEqual(s1.start, 1) - self.assertEqual(s1.stop, 3) - s2 = Const(10)[1:-2] - self.assertIsInstance(s2, Slice) - self.assertEqual(s2.start, 1) - self.assertEqual(s2.stop, 2) - s3 = Const(31)[::2] - self.assertIsInstance(s3, Cat) - self.assertIsInstance(s3.parts[0], Slice) - self.assertEqual(s3.parts[0].start, 0) - self.assertEqual(s3.parts[0].stop, 1) - self.assertIsInstance(s3.parts[1], Slice) - self.assertEqual(s3.parts[1].start, 2) - self.assertEqual(s3.parts[1].stop, 3) - self.assertIsInstance(s3.parts[2], Slice) - self.assertEqual(s3.parts[2].start, 4) - self.assertEqual(s3.parts[2].stop, 5) - - def test_getitem_wrong(self): - with self.assertRaisesRegex(TypeError, - r"^Cannot index value with 'str'$"): - Const(31)["str"] - - def test_shift_left(self): - self.assertRepr(Const(256, unsigned(9)).shift_left(0), - "(cat (const 0'd0) (const 9'd256))") - - self.assertRepr(Const(256, unsigned(9)).shift_left(1), - "(cat (const 1'd0) (const 9'd256))") - self.assertRepr(Const(256, unsigned(9)).shift_left(5), - "(cat (const 5'd0) (const 9'd256))") - self.assertRepr(Const(256, signed(9)).shift_left(1), - "(s (cat (const 1'd0) (const 9'sd-256)))") - self.assertRepr(Const(256, signed(9)).shift_left(5), - "(s (cat (const 5'd0) (const 9'sd-256)))") - - self.assertRepr(Const(256, unsigned(9)).shift_left(-1), - "(slice (const 9'd256) 1:9)") - self.assertRepr(Const(256, unsigned(9)).shift_left(-5), - "(slice (const 9'd256) 5:9)") - self.assertRepr(Const(256, signed(9)).shift_left(-1), - "(s (slice (const 9'sd-256) 1:9))") - self.assertRepr(Const(256, signed(9)).shift_left(-5), - "(s (slice (const 9'sd-256) 5:9))") - self.assertRepr(Const(256, signed(9)).shift_left(-15), - "(s (slice (const 9'sd-256) 9:9))") - - def test_shift_left_wrong(self): - with self.assertRaisesRegex(TypeError, - r"^Shift amount must be an integer, not 'str'$"): - Const(31).shift_left("str") - - def test_shift_right(self): - self.assertRepr(Const(256, unsigned(9)).shift_right(0), - "(slice (const 9'd256) 0:9)") - - self.assertRepr(Const(256, unsigned(9)).shift_right(-1), - "(cat (const 1'd0) (const 9'd256))") - self.assertRepr(Const(256, unsigned(9)).shift_right(-5), - "(cat (const 5'd0) (const 9'd256))") - self.assertRepr(Const(256, signed(9)).shift_right(-1), - "(s (cat (const 1'd0) (const 9'sd-256)))") - self.assertRepr(Const(256, signed(9)).shift_right(-5), - "(s (cat (const 5'd0) (const 9'sd-256)))") - - self.assertRepr(Const(256, unsigned(9)).shift_right(1), - "(slice (const 9'd256) 1:9)") - self.assertRepr(Const(256, unsigned(9)).shift_right(5), - "(slice (const 9'd256) 5:9)") - self.assertRepr(Const(256, signed(9)).shift_right(1), - "(s (slice (const 9'sd-256) 1:9))") - self.assertRepr(Const(256, signed(9)).shift_right(5), - "(s (slice (const 9'sd-256) 5:9))") - self.assertRepr(Const(256, signed(9)).shift_right(15), - "(s (slice (const 9'sd-256) 9:9))") - - def test_shift_right_wrong(self): - with self.assertRaisesRegex(TypeError, - r"^Shift amount must be an integer, not 'str'$"): - Const(31).shift_left("str") - - def test_rotate_left(self): - self.assertRepr(Const(256).rotate_left(1), - "(cat (slice (const 9'd256) 8:9) (slice (const 9'd256) 0:8))") - self.assertRepr(Const(256).rotate_left(7), - "(cat (slice (const 9'd256) 2:9) (slice (const 9'd256) 0:2))") - self.assertRepr(Const(256).rotate_left(-1), - "(cat (slice (const 9'd256) 1:9) (slice (const 9'd256) 0:1))") - self.assertRepr(Const(256).rotate_left(-7), - "(cat (slice (const 9'd256) 7:9) (slice (const 9'd256) 0:7))") - - def test_rotate_left_wrong(self): - with self.assertRaisesRegex(TypeError, - r"^Rotate amount must be an integer, not 'str'$"): - Const(31).rotate_left("str") - - def test_rotate_right(self): - self.assertRepr(Const(256).rotate_right(1), - "(cat (slice (const 9'd256) 1:9) (slice (const 9'd256) 0:1))") - self.assertRepr(Const(256).rotate_right(7), - "(cat (slice (const 9'd256) 7:9) (slice (const 9'd256) 0:7))") - self.assertRepr(Const(256).rotate_right(-1), - "(cat (slice (const 9'd256) 8:9) (slice (const 9'd256) 0:8))") - self.assertRepr(Const(256).rotate_right(-7), - "(cat (slice (const 9'd256) 2:9) (slice (const 9'd256) 0:2))") - - def test_rotate_right_wrong(self): - with self.assertRaisesRegex(TypeError, - r"^Rotate amount must be an integer, not 'str'$"): - Const(31).rotate_right("str") - - -class ConstTestCase(FHDLTestCase): - def test_shape(self): - self.assertEqual(Const(0).shape(), unsigned(1)) - self.assertIsInstance(Const(0).shape(), Shape) - self.assertEqual(Const(1).shape(), unsigned(1)) - self.assertEqual(Const(10).shape(), unsigned(4)) - self.assertEqual(Const(-10).shape(), signed(5)) - - self.assertEqual(Const(1, 4).shape(), unsigned(4)) - self.assertEqual(Const(-1, 4).shape(), signed(4)) - self.assertEqual(Const(1, signed(4)).shape(), signed(4)) - self.assertEqual(Const(0, unsigned(0)).shape(), unsigned(0)) - - def test_shape_wrong(self): - with self.assertRaisesRegex(TypeError, - r"^Width must be a non-negative integer, not -1$"): - Const(1, -1) - - def test_normalization(self): - self.assertEqual(Const(0b10110, signed(5)).value, -10) - - def test_value(self): - self.assertEqual(Const(10).value, 10) - - def test_repr(self): - self.assertEqual(repr(Const(10)), "(const 4'd10)") - self.assertEqual(repr(Const(-10)), "(const 5'sd-10)") - - def test_hash(self): - with self.assertRaises(TypeError): - hash(Const(0)) - - -class OperatorTestCase(FHDLTestCase): - def test_bool(self): - v = Const(0, 4).bool() - self.assertEqual(repr(v), "(b (const 4'd0))") - self.assertEqual(v.shape(), unsigned(1)) - - def test_invert(self): - v = ~Const(0, 4) - self.assertEqual(repr(v), "(~ (const 4'd0))") - self.assertEqual(v.shape(), unsigned(4)) - - def test_as_unsigned(self): - v = Const(-1, signed(4)).as_unsigned() - self.assertEqual(repr(v), "(u (const 4'sd-1))") - self.assertEqual(v.shape(), unsigned(4)) - - def test_as_signed(self): - v = Const(1, unsigned(4)).as_signed() - self.assertEqual(repr(v), "(s (const 4'd1))") - self.assertEqual(v.shape(), signed(4)) - - def test_neg(self): - v1 = -Const(0, unsigned(4)) - self.assertEqual(repr(v1), "(- (const 4'd0))") - self.assertEqual(v1.shape(), signed(5)) - v2 = -Const(0, signed(4)) - self.assertEqual(repr(v2), "(- (const 4'sd0))") - self.assertEqual(v2.shape(), signed(5)) - - def test_add(self): - v1 = Const(0, unsigned(4)) + Const(0, unsigned(6)) - self.assertEqual(repr(v1), "(+ (const 4'd0) (const 6'd0))") - self.assertEqual(v1.shape(), unsigned(7)) - v2 = Const(0, signed(4)) + Const(0, signed(6)) - self.assertEqual(v2.shape(), signed(7)) - v3 = Const(0, signed(4)) + Const(0, unsigned(4)) - self.assertEqual(v3.shape(), signed(6)) - v4 = Const(0, unsigned(4)) + Const(0, signed(4)) - self.assertEqual(v4.shape(), signed(6)) - v5 = 10 + Const(0, 4) - self.assertEqual(v5.shape(), unsigned(5)) - - def test_sub(self): - v1 = Const(0, unsigned(4)) - Const(0, unsigned(6)) - self.assertEqual(repr(v1), "(- (const 4'd0) (const 6'd0))") - self.assertEqual(v1.shape(), unsigned(7)) - v2 = Const(0, signed(4)) - Const(0, signed(6)) - self.assertEqual(v2.shape(), signed(7)) - v3 = Const(0, signed(4)) - Const(0, unsigned(4)) - self.assertEqual(v3.shape(), signed(6)) - v4 = Const(0, unsigned(4)) - Const(0, signed(4)) - self.assertEqual(v4.shape(), signed(6)) - v5 = 10 - Const(0, 4) - self.assertEqual(v5.shape(), unsigned(5)) - - def test_mul(self): - v1 = Const(0, unsigned(4)) * Const(0, unsigned(6)) - self.assertEqual(repr(v1), "(* (const 4'd0) (const 6'd0))") - self.assertEqual(v1.shape(), unsigned(10)) - v2 = Const(0, signed(4)) * Const(0, signed(6)) - self.assertEqual(v2.shape(), signed(10)) - v3 = Const(0, signed(4)) * Const(0, unsigned(4)) - self.assertEqual(v3.shape(), signed(8)) - v5 = 10 * Const(0, 4) - self.assertEqual(v5.shape(), unsigned(8)) - - def test_mod(self): - v1 = Const(0, unsigned(4)) % Const(0, unsigned(6)) - self.assertEqual(repr(v1), "(% (const 4'd0) (const 6'd0))") - self.assertEqual(v1.shape(), unsigned(4)) - v3 = Const(0, signed(4)) % Const(0, unsigned(4)) - self.assertEqual(v3.shape(), signed(4)) - v5 = 10 % Const(0, 4) - self.assertEqual(v5.shape(), unsigned(4)) - - def test_mod_wrong(self): - with self.assertRaisesRegex(NotImplementedError, - r"^Division by a signed value is not supported$"): - Const(0, signed(4)) % Const(0, signed(6)) - - def test_floordiv(self): - v1 = Const(0, unsigned(4)) // Const(0, unsigned(6)) - self.assertEqual(repr(v1), "(// (const 4'd0) (const 6'd0))") - self.assertEqual(v1.shape(), unsigned(4)) - v3 = Const(0, signed(4)) // Const(0, unsigned(4)) - self.assertEqual(v3.shape(), signed(4)) - v5 = 10 // Const(0, 4) - self.assertEqual(v5.shape(), unsigned(4)) - - def test_floordiv_wrong(self): - with self.assertRaisesRegex(NotImplementedError, - r"^Division by a signed value is not supported$"): - Const(0, signed(4)) // Const(0, signed(6)) - - def test_and(self): - v1 = Const(0, unsigned(4)) & Const(0, unsigned(6)) - self.assertEqual(repr(v1), "(& (const 4'd0) (const 6'd0))") - self.assertEqual(v1.shape(), unsigned(6)) - v2 = Const(0, signed(4)) & Const(0, signed(6)) - self.assertEqual(v2.shape(), signed(6)) - v3 = Const(0, signed(4)) & Const(0, unsigned(4)) - self.assertEqual(v3.shape(), signed(5)) - v4 = Const(0, unsigned(4)) & Const(0, signed(4)) - self.assertEqual(v4.shape(), signed(5)) - v5 = 10 & Const(0, 4) - self.assertEqual(v5.shape(), unsigned(4)) - - def test_or(self): - v1 = Const(0, unsigned(4)) | Const(0, unsigned(6)) - self.assertEqual(repr(v1), "(| (const 4'd0) (const 6'd0))") - self.assertEqual(v1.shape(), unsigned(6)) - v2 = Const(0, signed(4)) | Const(0, signed(6)) - self.assertEqual(v2.shape(), signed(6)) - v3 = Const(0, signed(4)) | Const(0, unsigned(4)) - self.assertEqual(v3.shape(), signed(5)) - v4 = Const(0, unsigned(4)) | Const(0, signed(4)) - self.assertEqual(v4.shape(), signed(5)) - v5 = 10 | Const(0, 4) - self.assertEqual(v5.shape(), unsigned(4)) - - def test_xor(self): - v1 = Const(0, unsigned(4)) ^ Const(0, unsigned(6)) - self.assertEqual(repr(v1), "(^ (const 4'd0) (const 6'd0))") - self.assertEqual(v1.shape(), unsigned(6)) - v2 = Const(0, signed(4)) ^ Const(0, signed(6)) - self.assertEqual(v2.shape(), signed(6)) - v3 = Const(0, signed(4)) ^ Const(0, unsigned(4)) - self.assertEqual(v3.shape(), signed(5)) - v4 = Const(0, unsigned(4)) ^ Const(0, signed(4)) - self.assertEqual(v4.shape(), signed(5)) - v5 = 10 ^ Const(0, 4) - self.assertEqual(v5.shape(), unsigned(4)) - - def test_shl(self): - v1 = Const(1, 4) << Const(4) - self.assertEqual(repr(v1), "(<< (const 4'd1) (const 3'd4))") - self.assertEqual(v1.shape(), unsigned(11)) - - def test_shl_wrong(self): - with self.assertRaisesRegex(TypeError, - r"^Shift amount must be unsigned$"): - 1 << Const(0, signed(6)) - with self.assertRaisesRegex(TypeError, - r"^Shift amount must be unsigned$"): - Const(1, unsigned(4)) << -1 - - def test_shr(self): - v1 = Const(1, 4) >> Const(4) - self.assertEqual(repr(v1), "(>> (const 4'd1) (const 3'd4))") - self.assertEqual(v1.shape(), unsigned(4)) - - def test_shr_wrong(self): - with self.assertRaisesRegex(TypeError, - r"^Shift amount must be unsigned$"): - 1 << Const(0, signed(6)) - with self.assertRaisesRegex(TypeError, - r"^Shift amount must be unsigned$"): - Const(1, unsigned(4)) << -1 - - def test_lt(self): - v = Const(0, 4) < Const(0, 6) - self.assertEqual(repr(v), "(< (const 4'd0) (const 6'd0))") - self.assertEqual(v.shape(), unsigned(1)) - - def test_le(self): - v = Const(0, 4) <= Const(0, 6) - self.assertEqual(repr(v), "(<= (const 4'd0) (const 6'd0))") - self.assertEqual(v.shape(), unsigned(1)) - - def test_gt(self): - v = Const(0, 4) > Const(0, 6) - self.assertEqual(repr(v), "(> (const 4'd0) (const 6'd0))") - self.assertEqual(v.shape(), unsigned(1)) - - def test_ge(self): - v = Const(0, 4) >= Const(0, 6) - self.assertEqual(repr(v), "(>= (const 4'd0) (const 6'd0))") - self.assertEqual(v.shape(), unsigned(1)) - - def test_eq(self): - v = Const(0, 4) == Const(0, 6) - self.assertEqual(repr(v), "(== (const 4'd0) (const 6'd0))") - self.assertEqual(v.shape(), unsigned(1)) - - def test_ne(self): - v = Const(0, 4) != Const(0, 6) - self.assertEqual(repr(v), "(!= (const 4'd0) (const 6'd0))") - self.assertEqual(v.shape(), unsigned(1)) - - def test_mux(self): - s = Const(0) - v1 = Mux(s, Const(0, unsigned(4)), Const(0, unsigned(6))) - self.assertEqual(repr(v1), "(m (const 1'd0) (const 4'd0) (const 6'd0))") - self.assertEqual(v1.shape(), unsigned(6)) - v2 = Mux(s, Const(0, signed(4)), Const(0, signed(6))) - self.assertEqual(v2.shape(), signed(6)) - v3 = Mux(s, Const(0, signed(4)), Const(0, unsigned(4))) - self.assertEqual(v3.shape(), signed(5)) - v4 = Mux(s, Const(0, unsigned(4)), Const(0, signed(4))) - self.assertEqual(v4.shape(), signed(5)) - - def test_mux_wide(self): - s = Const(0b100) - v = Mux(s, Const(0, unsigned(4)), Const(0, unsigned(6))) - self.assertEqual(repr(v), "(m (b (const 3'd4)) (const 4'd0) (const 6'd0))") - - def test_mux_bool(self): - v = Mux(True, Const(0), Const(0)) - self.assertEqual(repr(v), "(m (const 1'd1) (const 1'd0) (const 1'd0))") - - def test_bool(self): - v = Const(0).bool() - self.assertEqual(repr(v), "(b (const 1'd0))") - self.assertEqual(v.shape(), unsigned(1)) - - def test_any(self): - v = Const(0b101).any() - self.assertEqual(repr(v), "(r| (const 3'd5))") - - def test_all(self): - v = Const(0b101).all() - self.assertEqual(repr(v), "(r& (const 3'd5))") - - def test_xor(self): - v = Const(0b101).xor() - self.assertEqual(repr(v), "(r^ (const 3'd5))") - - def test_matches(self): - s = Signal(4) - self.assertRepr(s.matches(), "(const 1'd0)") - self.assertRepr(s.matches(1), """ - (== (sig s) (const 1'd1)) - """) - self.assertRepr(s.matches(0, 1), """ - (r| (cat (== (sig s) (const 1'd0)) (== (sig s) (const 1'd1)))) - """) - self.assertRepr(s.matches("10--"), """ - (== (& (sig s) (const 4'd12)) (const 4'd8)) - """) - self.assertRepr(s.matches("1 0--"), """ - (== (& (sig s) (const 4'd12)) (const 4'd8)) - """) - - def test_matches_enum(self): - s = Signal(SignedEnum) - self.assertRepr(s.matches(SignedEnum.FOO), """ - (== (sig s) (const 1'sd-1)) - """) - - def test_matches_width_wrong(self): - s = Signal(4) - with self.assertRaisesRegex(SyntaxError, - r"^Match pattern '--' must have the same width as match value \(which is 4\)$"): - s.matches("--") - with self.assertWarnsRegex(SyntaxWarning, - (r"^Match pattern '10110' is wider than match value \(which has width 4\); " - r"comparison will never be true$")): - s.matches(0b10110) - - def test_matches_bits_wrong(self): - s = Signal(4) - with self.assertRaisesRegex(SyntaxError, - (r"^Match pattern 'abc' must consist of 0, 1, and - \(don't care\) bits, " - r"and may include whitespace$")): - s.matches("abc") - - def test_matches_pattern_wrong(self): - s = Signal(4) - with self.assertRaisesRegex(SyntaxError, - r"^Match pattern must be an integer, a string, or an enumeration, not 1\.0$"): - s.matches(1.0) - - def test_hash(self): - with self.assertRaises(TypeError): - hash(Const(0) + Const(0)) - - -class SliceTestCase(FHDLTestCase): - def test_shape(self): - s1 = Const(10)[2] - self.assertEqual(s1.shape(), unsigned(1)) - self.assertIsInstance(s1.shape(), Shape) - s2 = Const(-10)[0:2] - self.assertEqual(s2.shape(), unsigned(2)) - - def test_start_end_negative(self): - c = Const(0, 8) - s1 = Slice(c, 0, -1) - self.assertEqual((s1.start, s1.stop), (0, 7)) - s1 = Slice(c, -4, -1) - self.assertEqual((s1.start, s1.stop), (4, 7)) - - def test_start_end_wrong(self): - with self.assertRaisesRegex(TypeError, - r"^Slice start must be an integer, not 'x'$"): - Slice(0, "x", 1) - with self.assertRaisesRegex(TypeError, - r"^Slice stop must be an integer, not 'x'$"): - Slice(0, 1, "x") - - def test_start_end_out_of_range(self): - c = Const(0, 8) - with self.assertRaisesRegex(IndexError, - r"^Cannot start slice 10 bits into 8-bit value$"): - Slice(c, 10, 12) - with self.assertRaisesRegex(IndexError, - r"^Cannot stop slice 12 bits into 8-bit value$"): - Slice(c, 0, 12) - with self.assertRaisesRegex(IndexError, - r"^Slice start 4 must be less than slice stop 2$"): - Slice(c, 4, 2) - - def test_repr(self): - s1 = Const(10)[2] - self.assertEqual(repr(s1), "(slice (const 4'd10) 2:3)") - - -class BitSelectTestCase(FHDLTestCase): - def setUp(self): - self.c = Const(0, 8) - self.s = Signal(range(self.c.width)) - - def test_shape(self): - s1 = self.c.bit_select(self.s, 2) - self.assertIsInstance(s1, Part) - self.assertEqual(s1.shape(), unsigned(2)) - self.assertIsInstance(s1.shape(), Shape) - s2 = self.c.bit_select(self.s, 0) - self.assertIsInstance(s2, Part) - self.assertEqual(s2.shape(), unsigned(0)) - - def test_stride(self): - s1 = self.c.bit_select(self.s, 2) - self.assertIsInstance(s1, Part) - self.assertEqual(s1.stride, 1) - - def test_const(self): - s1 = self.c.bit_select(1, 2) - self.assertIsInstance(s1, Slice) - self.assertRepr(s1, """(slice (const 8'd0) 1:3)""") - - def test_width_wrong(self): - with self.assertRaises(TypeError): - self.c.bit_select(self.s, -1) - - def test_repr(self): - s = self.c.bit_select(self.s, 2) - self.assertEqual(repr(s), "(part (const 8'd0) (sig s) 2 1)") - - -class WordSelectTestCase(FHDLTestCase): - def setUp(self): - self.c = Const(0, 8) - self.s = Signal(range(self.c.width)) - - def test_shape(self): - s1 = self.c.word_select(self.s, 2) - self.assertIsInstance(s1, Part) - self.assertEqual(s1.shape(), unsigned(2)) - self.assertIsInstance(s1.shape(), Shape) - - def test_stride(self): - s1 = self.c.word_select(self.s, 2) - self.assertIsInstance(s1, Part) - self.assertEqual(s1.stride, 2) - - def test_const(self): - s1 = self.c.word_select(1, 2) - self.assertIsInstance(s1, Slice) - self.assertRepr(s1, """(slice (const 8'd0) 2:4)""") - - def test_width_wrong(self): - with self.assertRaises(TypeError): - self.c.word_select(self.s, 0) - with self.assertRaises(TypeError): - self.c.word_select(self.s, -1) - - def test_repr(self): - s = self.c.word_select(self.s, 2) - self.assertEqual(repr(s), "(part (const 8'd0) (sig s) 2 2)") - - -class CatTestCase(FHDLTestCase): - def test_shape(self): - c0 = Cat() - self.assertEqual(c0.shape(), unsigned(0)) - self.assertIsInstance(c0.shape(), Shape) - c1 = Cat(Const(10)) - self.assertEqual(c1.shape(), unsigned(4)) - c2 = Cat(Const(10), Const(1)) - self.assertEqual(c2.shape(), unsigned(5)) - c3 = Cat(Const(10), Const(1), Const(0)) - self.assertEqual(c3.shape(), unsigned(6)) - - def test_repr(self): - c1 = Cat(Const(10), Const(1)) - self.assertEqual(repr(c1), "(cat (const 4'd10) (const 1'd1))") - - -class ReplTestCase(FHDLTestCase): - def test_shape(self): - s1 = Repl(Const(10), 3) - self.assertEqual(s1.shape(), unsigned(12)) - self.assertIsInstance(s1.shape(), Shape) - s2 = Repl(Const(10), 0) - self.assertEqual(s2.shape(), unsigned(0)) - - def test_count_wrong(self): - with self.assertRaises(TypeError): - Repl(Const(10), -1) - with self.assertRaises(TypeError): - Repl(Const(10), "str") - - def test_repr(self): - s = Repl(Const(10), 3) - self.assertEqual(repr(s), "(repl (const 4'd10) 3)") - - -class ArrayTestCase(FHDLTestCase): - def test_acts_like_array(self): - a = Array([1,2,3]) - self.assertSequenceEqual(a, [1,2,3]) - self.assertEqual(a[1], 2) - a[1] = 4 - self.assertSequenceEqual(a, [1,4,3]) - del a[1] - self.assertSequenceEqual(a, [1,3]) - a.insert(1, 2) - self.assertSequenceEqual(a, [1,2,3]) - - def test_becomes_immutable(self): - a = Array([1,2,3]) - s1 = Signal(range(len(a))) - s2 = Signal(range(len(a))) - v1 = a[s1] - v2 = a[s2] - with self.assertRaisesRegex(ValueError, - r"^Array can no longer be mutated after it was indexed with a value at "): - a[1] = 2 - with self.assertRaisesRegex(ValueError, - r"^Array can no longer be mutated after it was indexed with a value at "): - del a[1] - with self.assertRaisesRegex(ValueError, - r"^Array can no longer be mutated after it was indexed with a value at "): - a.insert(1, 2) - - def test_repr(self): - a = Array([1,2,3]) - self.assertEqual(repr(a), "(array mutable [1, 2, 3])") - s = Signal(range(len(a))) - v = a[s] - self.assertEqual(repr(a), "(array [1, 2, 3])") - - -class ArrayProxyTestCase(FHDLTestCase): - def test_index_shape(self): - m = Array(Array(x * y for y in range(1, 4)) for x in range(1, 4)) - a = Signal(range(3)) - b = Signal(range(3)) - v = m[a][b] - self.assertEqual(v.shape(), unsigned(4)) - - def test_attr_shape(self): - from collections import namedtuple - pair = namedtuple("pair", ("p", "n")) - a = Array(pair(i, -i) for i in range(10)) - s = Signal(range(len(a))) - v = a[s] - self.assertEqual(v.p.shape(), unsigned(4)) - self.assertEqual(v.n.shape(), signed(5)) - - def test_attr_shape_signed(self): - # [unsigned(1), unsigned(1)] → unsigned(1) - a1 = Array([1, 1]) - v1 = a1[Const(0)] - self.assertEqual(v1.shape(), unsigned(1)) - # [signed(1), signed(1)] → signed(1) - a2 = Array([-1, -1]) - v2 = a2[Const(0)] - self.assertEqual(v2.shape(), signed(1)) - # [unsigned(1), signed(2)] → signed(2) - a3 = Array([1, -2]) - v3 = a3[Const(0)] - self.assertEqual(v3.shape(), signed(2)) - # [unsigned(1), signed(1)] → signed(2); 1st operand padded with sign bit! - a4 = Array([1, -1]) - v4 = a4[Const(0)] - self.assertEqual(v4.shape(), signed(2)) - # [unsigned(2), signed(1)] → signed(3); 1st operand padded with sign bit! - a5 = Array([1, -1]) - v5 = a5[Const(0)] - self.assertEqual(v5.shape(), signed(2)) - - def test_repr(self): - a = Array([1, 2, 3]) - s = Signal(range(3)) - v = a[s] - self.assertEqual(repr(v), "(proxy (array [1, 2, 3]) (sig s))") - - -class SignalTestCase(FHDLTestCase): - def test_shape(self): - s1 = Signal() - self.assertEqual(s1.shape(), unsigned(1)) - self.assertIsInstance(s1.shape(), Shape) - s2 = Signal(2) - self.assertEqual(s2.shape(), unsigned(2)) - s3 = Signal(unsigned(2)) - self.assertEqual(s3.shape(), unsigned(2)) - s4 = Signal(signed(2)) - self.assertEqual(s4.shape(), signed(2)) - s5 = Signal(0) - self.assertEqual(s5.shape(), unsigned(0)) - s6 = Signal(range(16)) - self.assertEqual(s6.shape(), unsigned(4)) - s7 = Signal(range(4, 16)) - self.assertEqual(s7.shape(), unsigned(4)) - s8 = Signal(range(-4, 16)) - self.assertEqual(s8.shape(), signed(5)) - s9 = Signal(range(-20, 16)) - self.assertEqual(s9.shape(), signed(6)) - s10 = Signal(range(0)) - self.assertEqual(s10.shape(), unsigned(0)) - s11 = Signal(range(1)) - self.assertEqual(s11.shape(), unsigned(1)) - - def test_shape_wrong(self): - with self.assertRaisesRegex(TypeError, - r"^Width must be a non-negative integer, not -10$"): - Signal(-10) - - def test_name(self): - s1 = Signal() - self.assertEqual(s1.name, "s1") - s2 = Signal(name="sig") - self.assertEqual(s2.name, "sig") - - def test_reset(self): - s1 = Signal(4, reset=0b111, reset_less=True) - self.assertEqual(s1.reset, 0b111) - self.assertEqual(s1.reset_less, True) - - def test_reset_enum(self): - s1 = Signal(2, reset=UnsignedEnum.BAR) - self.assertEqual(s1.reset, 2) - with self.assertRaisesRegex(TypeError, - r"^Reset value has to be an int or an integral Enum$" - ): - Signal(1, reset=StringEnum.FOO) - - def test_reset_narrow(self): - with self.assertWarnsRegex(SyntaxWarning, - r"^Reset value 8 requires 4 bits to represent, but the signal only has 3 bits$"): - Signal(3, reset=8) - with self.assertWarnsRegex(SyntaxWarning, - r"^Reset value 4 requires 4 bits to represent, but the signal only has 3 bits$"): - Signal(signed(3), reset=4) - with self.assertWarnsRegex(SyntaxWarning, - r"^Reset value -5 requires 4 bits to represent, but the signal only has 3 bits$"): - Signal(signed(3), reset=-5) - - def test_attrs(self): - s1 = Signal() - self.assertEqual(s1.attrs, {}) - s2 = Signal(attrs={"no_retiming": True}) - self.assertEqual(s2.attrs, {"no_retiming": True}) - - def test_repr(self): - s1 = Signal() - self.assertEqual(repr(s1), "(sig s1)") - - def test_like(self): - s1 = Signal.like(Signal(4)) - self.assertEqual(s1.shape(), unsigned(4)) - s2 = Signal.like(Signal(range(-15, 1))) - self.assertEqual(s2.shape(), signed(5)) - s3 = Signal.like(Signal(4, reset=0b111, reset_less=True)) - self.assertEqual(s3.reset, 0b111) - self.assertEqual(s3.reset_less, True) - s4 = Signal.like(Signal(attrs={"no_retiming": True})) - self.assertEqual(s4.attrs, {"no_retiming": True}) - s5 = Signal.like(Signal(decoder=str)) - self.assertEqual(s5.decoder, str) - s6 = Signal.like(10) - self.assertEqual(s6.shape(), unsigned(4)) - s7 = [Signal.like(Signal(4))][0] - self.assertEqual(s7.name, "$like") - s8 = Signal.like(s1, name_suffix="_ff") - self.assertEqual(s8.name, "s1_ff") - - def test_decoder(self): - class Color(Enum): - RED = 1 - BLUE = 2 - s = Signal(decoder=Color) - self.assertEqual(s.decoder(1), "RED/1") - self.assertEqual(s.decoder(3), "3") - - def test_enum(self): - s1 = Signal(UnsignedEnum) - self.assertEqual(s1.shape(), unsigned(2)) - s2 = Signal(SignedEnum) - self.assertEqual(s2.shape(), signed(2)) - self.assertEqual(s2.decoder(SignedEnum.FOO), "FOO/-1") - - -class ClockSignalTestCase(FHDLTestCase): - def test_domain(self): - s1 = ClockSignal() - self.assertEqual(s1.domain, "sync") - s2 = ClockSignal("pix") - self.assertEqual(s2.domain, "pix") - - with self.assertRaisesRegex(TypeError, - r"^Clock domain name must be a string, not 1$"): - ClockSignal(1) - - def test_shape(self): - s1 = ClockSignal() - self.assertEqual(s1.shape(), unsigned(1)) - self.assertIsInstance(s1.shape(), Shape) - - def test_repr(self): - s1 = ClockSignal() - self.assertEqual(repr(s1), "(clk sync)") - - def test_wrong_name_comb(self): - with self.assertRaisesRegex(ValueError, - r"^Domain 'comb' does not have a clock$"): - ClockSignal("comb") - - -class ResetSignalTestCase(FHDLTestCase): - def test_domain(self): - s1 = ResetSignal() - self.assertEqual(s1.domain, "sync") - s2 = ResetSignal("pix") - self.assertEqual(s2.domain, "pix") - - with self.assertRaisesRegex(TypeError, - r"^Clock domain name must be a string, not 1$"): - ResetSignal(1) - - def test_shape(self): - s1 = ResetSignal() - self.assertEqual(s1.shape(), unsigned(1)) - self.assertIsInstance(s1.shape(), Shape) - - def test_repr(self): - s1 = ResetSignal() - self.assertEqual(repr(s1), "(rst sync)") - - def test_wrong_name_comb(self): - with self.assertRaisesRegex(ValueError, - r"^Domain 'comb' does not have a reset$"): - ResetSignal("comb") - - -class MockUserValue(UserValue): - def __init__(self, lowered): - super().__init__() - self.lower_count = 0 - self.lowered = lowered - - def lower(self): - self.lower_count += 1 - return self.lowered - - -class UserValueTestCase(FHDLTestCase): - def test_shape(self): - uv = MockUserValue(1) - self.assertEqual(uv.shape(), unsigned(1)) - self.assertIsInstance(uv.shape(), Shape) - uv.lowered = 2 - self.assertEqual(uv.shape(), unsigned(1)) - self.assertEqual(uv.lower_count, 1) - - def test_lower_to_user_value(self): - uv = MockUserValue(MockUserValue(1)) - self.assertEqual(uv.shape(), unsigned(1)) - self.assertIsInstance(uv.shape(), Shape) - uv.lowered = MockUserValue(2) - self.assertEqual(uv.shape(), unsigned(1)) - self.assertEqual(uv.lower_count, 1) - - -class SampleTestCase(FHDLTestCase): - def test_const(self): - s = Sample(1, 1, "sync") - self.assertEqual(s.shape(), unsigned(1)) - - def test_signal(self): - s1 = Sample(Signal(2), 1, "sync") - self.assertEqual(s1.shape(), unsigned(2)) - s2 = Sample(ClockSignal(), 1, "sync") - s3 = Sample(ResetSignal(), 1, "sync") - - def test_wrong_value_operator(self): - with self.assertRaisesRegex(TypeError, - (r"^Sampled value must be a signal or a constant, not " - r"\(\+ \(sig \$signal\) \(const 1'd1\)\)$")): - Sample(Signal() + 1, 1, "sync") - - def test_wrong_clocks_neg(self): - with self.assertRaisesRegex(ValueError, - r"^Cannot sample a value 1 cycles in the future$"): - Sample(Signal(), -1, "sync") - - def test_wrong_domain(self): - with self.assertRaisesRegex(TypeError, - r"^Domain name must be a string or None, not 0$"): - Sample(Signal(), 1, 0) - - -class InitialTestCase(FHDLTestCase): - def test_initial(self): - i = Initial() - self.assertEqual(i.shape(), unsigned(1)) diff --git a/nmigen/test/test_hdl_cd.py b/nmigen/test/test_hdl_cd.py deleted file mode 100644 index 8927e08..0000000 --- a/nmigen/test/test_hdl_cd.py +++ /dev/null @@ -1,78 +0,0 @@ -from ..hdl.cd import * -from .utils import * - - -class ClockDomainTestCase(FHDLTestCase): - def test_name(self): - sync = ClockDomain() - self.assertEqual(sync.name, "sync") - self.assertEqual(sync.clk.name, "clk") - self.assertEqual(sync.rst.name, "rst") - self.assertEqual(sync.local, False) - pix = ClockDomain() - self.assertEqual(pix.name, "pix") - self.assertEqual(pix.clk.name, "pix_clk") - self.assertEqual(pix.rst.name, "pix_rst") - cd_pix = ClockDomain() - self.assertEqual(pix.name, "pix") - dom = [ClockDomain("foo")][0] - self.assertEqual(dom.name, "foo") - with self.assertRaisesRegex(ValueError, - r"^Clock domain name must be specified explicitly$"): - ClockDomain() - cd_reset = ClockDomain(local=True) - self.assertEqual(cd_reset.local, True) - - def test_edge(self): - sync = ClockDomain() - self.assertEqual(sync.clk_edge, "pos") - sync = ClockDomain(clk_edge="pos") - self.assertEqual(sync.clk_edge, "pos") - sync = ClockDomain(clk_edge="neg") - self.assertEqual(sync.clk_edge, "neg") - - def test_edge_wrong(self): - with self.assertRaisesRegex(ValueError, - r"^Domain clock edge must be one of 'pos' or 'neg', not 'xxx'$"): - ClockDomain("sync", clk_edge="xxx") - - def test_with_reset(self): - pix = ClockDomain() - self.assertIsNotNone(pix.clk) - self.assertIsNotNone(pix.rst) - self.assertFalse(pix.async_reset) - - def test_without_reset(self): - pix = ClockDomain(reset_less=True) - self.assertIsNotNone(pix.clk) - self.assertIsNone(pix.rst) - self.assertFalse(pix.async_reset) - - def test_async_reset(self): - pix = ClockDomain(async_reset=True) - self.assertIsNotNone(pix.clk) - self.assertIsNotNone(pix.rst) - self.assertTrue(pix.async_reset) - - def test_rename(self): - sync = ClockDomain() - self.assertEqual(sync.name, "sync") - self.assertEqual(sync.clk.name, "clk") - self.assertEqual(sync.rst.name, "rst") - sync.rename("pix") - self.assertEqual(sync.name, "pix") - self.assertEqual(sync.clk.name, "pix_clk") - self.assertEqual(sync.rst.name, "pix_rst") - - def test_rename_reset_less(self): - sync = ClockDomain(reset_less=True) - self.assertEqual(sync.name, "sync") - self.assertEqual(sync.clk.name, "clk") - sync.rename("pix") - self.assertEqual(sync.name, "pix") - self.assertEqual(sync.clk.name, "pix_clk") - - def test_wrong_name_comb(self): - with self.assertRaisesRegex(ValueError, - r"^Domain 'comb' may not be clocked$"): - comb = ClockDomain() diff --git a/nmigen/test/test_hdl_dsl.py b/nmigen/test/test_hdl_dsl.py deleted file mode 100644 index 454415d..0000000 --- a/nmigen/test/test_hdl_dsl.py +++ /dev/null @@ -1,769 +0,0 @@ -# nmigen: UnusedElaboratable=no - -from collections import OrderedDict -from enum import Enum - -from ..hdl.ast import * -from ..hdl.cd import * -from ..hdl.dsl import * -from .utils import * - - -class DSLTestCase(FHDLTestCase): - def setUp(self): - self.s1 = Signal() - self.s2 = Signal() - self.s3 = Signal() - self.c1 = Signal() - self.c2 = Signal() - self.c3 = Signal() - self.w1 = Signal(4) - - def test_cant_inherit(self): - with self.assertRaisesRegex(SyntaxError, - (r"^Instead of inheriting from `Module`, inherit from `Elaboratable` and " - r"return a `Module` from the `elaborate\(self, platform\)` method$")): - class ORGate(Module): - pass - - def test_d_comb(self): - m = Module() - m.d.comb += self.c1.eq(1) - m._flush() - self.assertEqual(m._driving[self.c1], None) - self.assertRepr(m._statements, """( - (eq (sig c1) (const 1'd1)) - )""") - - def test_d_sync(self): - m = Module() - m.d.sync += self.c1.eq(1) - m._flush() - self.assertEqual(m._driving[self.c1], "sync") - self.assertRepr(m._statements, """( - (eq (sig c1) (const 1'd1)) - )""") - - def test_d_pix(self): - m = Module() - m.d.pix += self.c1.eq(1) - m._flush() - self.assertEqual(m._driving[self.c1], "pix") - self.assertRepr(m._statements, """( - (eq (sig c1) (const 1'd1)) - )""") - - def test_d_index(self): - m = Module() - m.d["pix"] += self.c1.eq(1) - m._flush() - self.assertEqual(m._driving[self.c1], "pix") - self.assertRepr(m._statements, """( - (eq (sig c1) (const 1'd1)) - )""") - - def test_d_no_conflict(self): - m = Module() - m.d.comb += self.w1[0].eq(1) - m.d.comb += self.w1[1].eq(1) - - def test_d_conflict(self): - m = Module() - with self.assertRaisesRegex(SyntaxError, - (r"^Driver-driver conflict: trying to drive \(sig c1\) from d\.sync, but it " - r"is already driven from d\.comb$")): - m.d.comb += self.c1.eq(1) - m.d.sync += self.c1.eq(1) - - def test_d_wrong(self): - m = Module() - with self.assertRaisesRegex(AttributeError, - r"^Cannot assign 'd\.pix' attribute; did you mean 'd.pix \+='\?$"): - m.d.pix = None - - def test_d_asgn_wrong(self): - m = Module() - with self.assertRaisesRegex(SyntaxError, - r"^Only assignments and property checks may be appended to d\.sync$"): - m.d.sync += Switch(self.s1, {}) - - def test_comb_wrong(self): - m = Module() - with self.assertRaisesRegex(AttributeError, - r"^'Module' object has no attribute 'comb'; did you mean 'd\.comb'\?$"): - m.comb += self.c1.eq(1) - - def test_sync_wrong(self): - m = Module() - with self.assertRaisesRegex(AttributeError, - r"^'Module' object has no attribute 'sync'; did you mean 'd\.sync'\?$"): - m.sync += self.c1.eq(1) - - def test_attr_wrong(self): - m = Module() - with self.assertRaisesRegex(AttributeError, - r"^'Module' object has no attribute 'nonexistentattr'$"): - m.nonexistentattr - - def test_d_suspicious(self): - m = Module() - with self.assertWarnsRegex(SyntaxWarning, - (r"^Using '\.d\.submodules' would add statements to clock domain " - r"'submodules'; did you mean \.submodules instead\?$")): - m.d.submodules += [] - - def test_clock_signal(self): - m = Module() - m.d.comb += ClockSignal("pix").eq(ClockSignal()) - self.assertRepr(m._statements, """ - ( - (eq (clk pix) (clk sync)) - ) - """) - - def test_reset_signal(self): - m = Module() - m.d.comb += ResetSignal("pix").eq(1) - self.assertRepr(m._statements, """ - ( - (eq (rst pix) (const 1'd1)) - ) - """) - - def test_sample_domain(self): - m = Module() - i = Signal() - o1 = Signal() - o2 = Signal() - o3 = Signal() - m.d.sync += o1.eq(Past(i)) - m.d.pix += o2.eq(Past(i)) - m.d.pix += o3.eq(Past(i, domain="sync")) - f = m.elaborate(platform=None) - self.assertRepr(f.statements, """ - ( - (eq (sig o1) (sample (sig i) @ sync[1])) - (eq (sig o2) (sample (sig i) @ pix[1])) - (eq (sig o3) (sample (sig i) @ sync[1])) - ) - """) - - def test_If(self): - m = Module() - with m.If(self.s1): - m.d.comb += self.c1.eq(1) - m._flush() - self.assertRepr(m._statements, """ - ( - (switch (cat (sig s1)) - (case 1 (eq (sig c1) (const 1'd1))) - ) - ) - """) - - def test_If_Elif(self): - m = Module() - with m.If(self.s1): - m.d.comb += self.c1.eq(1) - with m.Elif(self.s2): - m.d.sync += self.c2.eq(0) - m._flush() - self.assertRepr(m._statements, """ - ( - (switch (cat (sig s1) (sig s2)) - (case -1 (eq (sig c1) (const 1'd1))) - (case 1- (eq (sig c2) (const 1'd0))) - ) - ) - """) - - def test_If_Elif_Else(self): - m = Module() - with m.If(self.s1): - m.d.comb += self.c1.eq(1) - with m.Elif(self.s2): - m.d.sync += self.c2.eq(0) - with m.Else(): - m.d.comb += self.c3.eq(1) - m._flush() - self.assertRepr(m._statements, """ - ( - (switch (cat (sig s1) (sig s2)) - (case -1 (eq (sig c1) (const 1'd1))) - (case 1- (eq (sig c2) (const 1'd0))) - (default (eq (sig c3) (const 1'd1))) - ) - ) - """) - - def test_If_If(self): - m = Module() - with m.If(self.s1): - m.d.comb += self.c1.eq(1) - with m.If(self.s2): - m.d.comb += self.c2.eq(1) - m._flush() - self.assertRepr(m._statements, """ - ( - (switch (cat (sig s1)) - (case 1 (eq (sig c1) (const 1'd1))) - ) - (switch (cat (sig s2)) - (case 1 (eq (sig c2) (const 1'd1))) - ) - ) - """) - - def test_If_nested_If(self): - m = Module() - with m.If(self.s1): - m.d.comb += self.c1.eq(1) - with m.If(self.s2): - m.d.comb += self.c2.eq(1) - m._flush() - self.assertRepr(m._statements, """ - ( - (switch (cat (sig s1)) - (case 1 (eq (sig c1) (const 1'd1)) - (switch (cat (sig s2)) - (case 1 (eq (sig c2) (const 1'd1))) - ) - ) - ) - ) - """) - - def test_If_dangling_Else(self): - m = Module() - with m.If(self.s1): - m.d.comb += self.c1.eq(1) - with m.If(self.s2): - m.d.comb += self.c2.eq(1) - with m.Else(): - m.d.comb += self.c3.eq(1) - m._flush() - self.assertRepr(m._statements, """ - ( - (switch (cat (sig s1)) - (case 1 - (eq (sig c1) (const 1'd1)) - (switch (cat (sig s2)) - (case 1 (eq (sig c2) (const 1'd1))) - ) - ) - (default - (eq (sig c3) (const 1'd1)) - ) - ) - ) - """) - - def test_Elif_wrong(self): - m = Module() - with self.assertRaisesRegex(SyntaxError, - r"^Elif without preceding If$"): - with m.Elif(self.s2): - pass - - def test_Else_wrong(self): - m = Module() - with self.assertRaisesRegex(SyntaxError, - r"^Else without preceding If\/Elif$"): - with m.Else(): - pass - - def test_If_wide(self): - m = Module() - with m.If(self.w1): - m.d.comb += self.c1.eq(1) - m._flush() - self.assertRepr(m._statements, """ - ( - (switch (cat (b (sig w1))) - (case 1 (eq (sig c1) (const 1'd1))) - ) - ) - """) - - def test_If_signed_suspicious(self): - m = Module() - with self.assertWarnsRegex(SyntaxWarning, - (r"^Signed values in If\/Elif conditions usually result from inverting Python " - r"booleans with ~, which leads to unexpected results\. Replace `~flag` with " - r"`not flag`\. \(If this is a false positive, silence this warning with " - r"`m\.If\(x\)` → `m\.If\(x\.bool\(\)\)`\.\)$")): - with m.If(~True): - pass - - def test_Elif_signed_suspicious(self): - m = Module() - with m.If(0): - pass - with self.assertWarnsRegex(SyntaxWarning, - (r"^Signed values in If\/Elif conditions usually result from inverting Python " - r"booleans with ~, which leads to unexpected results\. Replace `~flag` with " - r"`not flag`\. \(If this is a false positive, silence this warning with " - r"`m\.If\(x\)` → `m\.If\(x\.bool\(\)\)`\.\)$")): - with m.Elif(~True): - pass - - def test_if_If_Elif_Else(self): - m = Module() - with self.assertRaisesRegex(SyntaxError, - r"^`if m\.If\(\.\.\.\):` does not work; use `with m\.If\(\.\.\.\)`$"): - if m.If(0): - pass - with m.If(0): - pass - with self.assertRaisesRegex(SyntaxError, - r"^`if m\.Elif\(\.\.\.\):` does not work; use `with m\.Elif\(\.\.\.\)`$"): - if m.Elif(0): - pass - with self.assertRaisesRegex(SyntaxError, - r"^`if m\.Else\(\.\.\.\):` does not work; use `with m\.Else\(\.\.\.\)`$"): - if m.Else(): - pass - - def test_Switch(self): - m = Module() - with m.Switch(self.w1): - with m.Case(3): - m.d.comb += self.c1.eq(1) - with m.Case("11--"): - m.d.comb += self.c2.eq(1) - with m.Case("1 0--"): - m.d.comb += self.c2.eq(1) - m._flush() - self.assertRepr(m._statements, """ - ( - (switch (sig w1) - (case 0011 (eq (sig c1) (const 1'd1))) - (case 11-- (eq (sig c2) (const 1'd1))) - (case 10-- (eq (sig c2) (const 1'd1))) - ) - ) - """) - - def test_Switch_default_Case(self): - m = Module() - with m.Switch(self.w1): - with m.Case(3): - m.d.comb += self.c1.eq(1) - with m.Case(): - m.d.comb += self.c2.eq(1) - m._flush() - self.assertRepr(m._statements, """ - ( - (switch (sig w1) - (case 0011 (eq (sig c1) (const 1'd1))) - (default (eq (sig c2) (const 1'd1))) - ) - ) - """) - - def test_Switch_default_Default(self): - m = Module() - with m.Switch(self.w1): - with m.Case(3): - m.d.comb += self.c1.eq(1) - with m.Default(): - m.d.comb += self.c2.eq(1) - m._flush() - self.assertRepr(m._statements, """ - ( - (switch (sig w1) - (case 0011 (eq (sig c1) (const 1'd1))) - (default (eq (sig c2) (const 1'd1))) - ) - ) - """) - - def test_Switch_const_test(self): - m = Module() - with m.Switch(1): - with m.Case(1): - m.d.comb += self.c1.eq(1) - m._flush() - self.assertRepr(m._statements, """ - ( - (switch (const 1'd1) - (case 1 (eq (sig c1) (const 1'd1))) - ) - ) - """) - - def test_Switch_enum(self): - class Color(Enum): - RED = 1 - BLUE = 2 - m = Module() - se = Signal(Color) - with m.Switch(se): - with m.Case(Color.RED): - m.d.comb += self.c1.eq(1) - self.assertRepr(m._statements, """ - ( - (switch (sig se) - (case 01 (eq (sig c1) (const 1'd1))) - ) - ) - """) - - def test_Case_width_wrong(self): - class Color(Enum): - RED = 0b10101010 - m = Module() - with m.Switch(self.w1): - with self.assertRaisesRegex(SyntaxError, - r"^Case pattern '--' must have the same width as switch value \(which is 4\)$"): - with m.Case("--"): - pass - with self.assertWarnsRegex(SyntaxWarning, - (r"^Case pattern '10110' is wider than switch value \(which has width 4\); " - r"comparison will never be true$")): - with m.Case(0b10110): - pass - with self.assertWarnsRegex(SyntaxWarning, - (r"^Case pattern '10101010' \(Color\.RED\) is wider than switch value " - r"\(which has width 4\); comparison will never be true$")): - with m.Case(Color.RED): - pass - self.assertRepr(m._statements, """ - ( - (switch (sig w1) ) - ) - """) - - def test_Case_bits_wrong(self): - m = Module() - with m.Switch(self.w1): - with self.assertRaisesRegex(SyntaxError, - (r"^Case pattern 'abc' must consist of 0, 1, and - \(don't care\) bits, " - r"and may include whitespace$")): - with m.Case("abc"): - pass - - def test_Case_pattern_wrong(self): - m = Module() - with m.Switch(self.w1): - with self.assertRaisesRegex(SyntaxError, - r"^Case pattern must be an integer, a string, or an enumeration, not 1\.0$"): - with m.Case(1.0): - pass - - def test_Case_outside_Switch_wrong(self): - m = Module() - with self.assertRaisesRegex(SyntaxError, - r"^Case is not permitted outside of Switch$"): - with m.Case(): - pass - - def test_If_inside_Switch_wrong(self): - m = Module() - with m.Switch(self.s1): - with self.assertRaisesRegex(SyntaxError, - (r"^If is not permitted directly inside of Switch; " - r"it is permitted inside of Switch Case$")): - with m.If(self.s2): - pass - - def test_FSM_basic(self): - a = Signal() - b = Signal() - c = Signal() - m = Module() - with m.FSM(): - with m.State("FIRST"): - m.d.comb += a.eq(1) - m.next = "SECOND" - with m.State("SECOND"): - m.d.sync += b.eq(~b) - with m.If(c): - m.next = "FIRST" - m._flush() - self.assertRepr(m._statements, """ - ( - (switch (sig fsm_state) - (case 0 - (eq (sig a) (const 1'd1)) - (eq (sig fsm_state) (const 1'd1)) - ) - (case 1 - (eq (sig b) (~ (sig b))) - (switch (cat (sig c)) - (case 1 - (eq (sig fsm_state) (const 1'd0))) - ) - ) - ) - ) - """) - self.assertEqual({repr(k): v for k, v in m._driving.items()}, { - "(sig a)": None, - "(sig fsm_state)": "sync", - "(sig b)": "sync", - }) - - frag = m.elaborate(platform=None) - fsm = frag.find_generated("fsm") - self.assertIsInstance(fsm.state, Signal) - self.assertEqual(fsm.encoding, OrderedDict({ - "FIRST": 0, - "SECOND": 1, - })) - self.assertEqual(fsm.decoding, OrderedDict({ - 0: "FIRST", - 1: "SECOND" - })) - - def test_FSM_reset(self): - a = Signal() - m = Module() - with m.FSM(reset="SECOND"): - with m.State("FIRST"): - m.d.comb += a.eq(0) - m.next = "SECOND" - with m.State("SECOND"): - m.next = "FIRST" - m._flush() - self.assertRepr(m._statements, """ - ( - (switch (sig fsm_state) - (case 0 - (eq (sig a) (const 1'd0)) - (eq (sig fsm_state) (const 1'd1)) - ) - (case 1 - (eq (sig fsm_state) (const 1'd0)) - ) - ) - ) - """) - - def test_FSM_ongoing(self): - a = Signal() - b = Signal() - m = Module() - with m.FSM() as fsm: - m.d.comb += b.eq(fsm.ongoing("SECOND")) - with m.State("FIRST"): - pass - m.d.comb += a.eq(fsm.ongoing("FIRST")) - with m.State("SECOND"): - pass - m._flush() - self.assertEqual(m._generated["fsm"].state.reset, 1) - self.maxDiff = 10000 - self.assertRepr(m._statements, """ - ( - (eq (sig b) (== (sig fsm_state) (const 1'd0))) - (eq (sig a) (== (sig fsm_state) (const 1'd1))) - (switch (sig fsm_state) - (case 1 - ) - (case 0 - ) - ) - ) - """) - - def test_FSM_empty(self): - m = Module() - with m.FSM(): - pass - self.assertRepr(m._statements, """ - () - """) - - def test_FSM_wrong_domain(self): - m = Module() - with self.assertRaisesRegex(ValueError, - r"^FSM may not be driven by the 'comb' domain$"): - with m.FSM(domain="comb"): - pass - - def test_FSM_wrong_undefined(self): - m = Module() - with self.assertRaisesRegex(NameError, - r"^FSM state 'FOO' is referenced but not defined$"): - with m.FSM() as fsm: - fsm.ongoing("FOO") - - def test_FSM_wrong_redefined(self): - m = Module() - with m.FSM(): - with m.State("FOO"): - pass - with self.assertRaisesRegex(NameError, - r"^FSM state 'FOO' is already defined$"): - with m.State("FOO"): - pass - - def test_FSM_wrong_next(self): - m = Module() - with self.assertRaisesRegex(SyntaxError, - r"^Only assignment to `m\.next` is permitted$"): - m.next - with self.assertRaisesRegex(SyntaxError, - r"^`m\.next = <\.\.\.>` is only permitted inside an FSM state$"): - m.next = "FOO" - with self.assertRaisesRegex(SyntaxError, - r"^`m\.next = <\.\.\.>` is only permitted inside an FSM state$"): - with m.FSM(): - m.next = "FOO" - - def test_If_inside_FSM_wrong(self): - m = Module() - with m.FSM(): - with m.State("FOO"): - pass - with self.assertRaisesRegex(SyntaxError, - (r"^If is not permitted directly inside of FSM; " - r"it is permitted inside of FSM State$")): - with m.If(self.s2): - pass - - def test_auto_pop_ctrl(self): - m = Module() - with m.If(self.w1): - m.d.comb += self.c1.eq(1) - m.d.comb += self.c2.eq(1) - self.assertRepr(m._statements, """ - ( - (switch (cat (b (sig w1))) - (case 1 (eq (sig c1) (const 1'd1))) - ) - (eq (sig c2) (const 1'd1)) - ) - """) - - def test_submodule_anon(self): - m1 = Module() - m2 = Module() - m1.submodules += m2 - self.assertEqual(m1._anon_submodules, [m2]) - self.assertEqual(m1._named_submodules, {}) - - def test_submodule_anon_multi(self): - m1 = Module() - m2 = Module() - m3 = Module() - m1.submodules += m2, m3 - self.assertEqual(m1._anon_submodules, [m2, m3]) - self.assertEqual(m1._named_submodules, {}) - - def test_submodule_named(self): - m1 = Module() - m2 = Module() - m1.submodules.foo = m2 - self.assertEqual(m1._anon_submodules, []) - self.assertEqual(m1._named_submodules, {"foo": m2}) - - def test_submodule_named_index(self): - m1 = Module() - m2 = Module() - m1.submodules["foo"] = m2 - self.assertEqual(m1._anon_submodules, []) - self.assertEqual(m1._named_submodules, {"foo": m2}) - - def test_submodule_wrong(self): - m = Module() - with self.assertRaisesRegex(TypeError, - r"^Trying to add 1, which does not implement \.elaborate\(\), as a submodule$"): - m.submodules.foo = 1 - with self.assertRaisesRegex(TypeError, - r"^Trying to add 1, which does not implement \.elaborate\(\), as a submodule$"): - m.submodules += 1 - - def test_submodule_named_conflict(self): - m1 = Module() - m2 = Module() - m1.submodules.foo = m2 - with self.assertRaisesRegex(NameError, r"^Submodule named 'foo' already exists$"): - m1.submodules.foo = m2 - - def test_submodule_get(self): - m1 = Module() - m2 = Module() - m1.submodules.foo = m2 - m3 = m1.submodules.foo - self.assertEqual(m2, m3) - - def test_submodule_get_index(self): - m1 = Module() - m2 = Module() - m1.submodules["foo"] = m2 - m3 = m1.submodules["foo"] - self.assertEqual(m2, m3) - - def test_submodule_get_unset(self): - m1 = Module() - with self.assertRaisesRegex(AttributeError, r"^No submodule named 'foo' exists$"): - m2 = m1.submodules.foo - with self.assertRaisesRegex(AttributeError, r"^No submodule named 'foo' exists$"): - m2 = m1.submodules["foo"] - - def test_domain_named_implicit(self): - m = Module() - m.domains += ClockDomain("sync") - self.assertEqual(len(m._domains), 1) - - def test_domain_named_explicit(self): - m = Module() - m.domains.foo = ClockDomain() - self.assertEqual(len(m._domains), 1) - self.assertEqual(m._domains["foo"].name, "foo") - - def test_domain_add_wrong(self): - m = Module() - with self.assertRaisesRegex(TypeError, - r"^Only clock domains may be added to `m\.domains`, not 1$"): - m.domains.foo = 1 - with self.assertRaisesRegex(TypeError, - r"^Only clock domains may be added to `m\.domains`, not 1$"): - m.domains += 1 - - def test_domain_add_wrong_name(self): - m = Module() - with self.assertRaisesRegex(NameError, - r"^Clock domain name 'bar' must match name in `m\.domains\.foo \+= \.\.\.` syntax$"): - m.domains.foo = ClockDomain("bar") - - def test_domain_add_wrong_duplicate(self): - m = Module() - m.domains += ClockDomain("foo") - with self.assertRaisesRegex(NameError, - r"^Clock domain named 'foo' already exists$"): - m.domains += ClockDomain("foo") - - def test_lower(self): - m1 = Module() - m1.d.comb += self.c1.eq(self.s1) - m2 = Module() - m2.d.comb += self.c2.eq(self.s2) - m2.d.sync += self.c3.eq(self.s3) - m1.submodules.foo = m2 - - f1 = m1.elaborate(platform=None) - self.assertRepr(f1.statements, """ - ( - (eq (sig c1) (sig s1)) - ) - """) - self.assertEqual(f1.drivers, { - None: SignalSet((self.c1,)) - }) - self.assertEqual(len(f1.subfragments), 1) - (f2, f2_name), = f1.subfragments - self.assertEqual(f2_name, "foo") - self.assertRepr(f2.statements, """ - ( - (eq (sig c2) (sig s2)) - (eq (sig c3) (sig s3)) - ) - """) - self.assertEqual(f2.drivers, { - None: SignalSet((self.c2,)), - "sync": SignalSet((self.c3,)) - }) - self.assertEqual(len(f2.subfragments), 0) diff --git a/nmigen/test/test_hdl_ir.py b/nmigen/test/test_hdl_ir.py deleted file mode 100644 index ab8bdd8..0000000 --- a/nmigen/test/test_hdl_ir.py +++ /dev/null @@ -1,862 +0,0 @@ -# nmigen: UnusedElaboratable=no - -from collections import OrderedDict - -from ..hdl.ast import * -from ..hdl.cd import * -from ..hdl.ir import * -from ..hdl.mem import * -from .utils import * - - -class BadElaboratable(Elaboratable): - def elaborate(self, platform): - return - - -class FragmentGetTestCase(FHDLTestCase): - def test_get_wrong(self): - with self.assertRaisesRegex(AttributeError, - r"^Object None cannot be elaborated$"): - Fragment.get(None, platform=None) - - with self.assertWarnsRegex(UserWarning, - r"^\.elaborate\(\) returned None; missing return statement\?$"): - with self.assertRaisesRegex(AttributeError, - r"^Object None cannot be elaborated$"): - Fragment.get(BadElaboratable(), platform=None) - - -class FragmentGeneratedTestCase(FHDLTestCase): - def test_find_subfragment(self): - f1 = Fragment() - f2 = Fragment() - f1.add_subfragment(f2, "f2") - - self.assertEqual(f1.find_subfragment(0), f2) - self.assertEqual(f1.find_subfragment("f2"), f2) - - def test_find_subfragment_wrong(self): - f1 = Fragment() - f2 = Fragment() - f1.add_subfragment(f2, "f2") - - with self.assertRaisesRegex(NameError, - r"^No subfragment at index #1$"): - f1.find_subfragment(1) - with self.assertRaisesRegex(NameError, - r"^No subfragment with name 'fx'$"): - f1.find_subfragment("fx") - - def test_find_generated(self): - f1 = Fragment() - f2 = Fragment() - f2.generated["sig"] = sig = Signal() - f1.add_subfragment(f2, "f2") - - self.assertEqual(SignalKey(f1.find_generated("f2", "sig")), - SignalKey(sig)) - - -class FragmentDriversTestCase(FHDLTestCase): - def test_empty(self): - f = Fragment() - self.assertEqual(list(f.iter_comb()), []) - self.assertEqual(list(f.iter_sync()), []) - - -class FragmentPortsTestCase(FHDLTestCase): - def setUp(self): - self.s1 = Signal() - self.s2 = Signal() - self.s3 = Signal() - self.c1 = Signal() - self.c2 = Signal() - self.c3 = Signal() - - def test_empty(self): - f = Fragment() - self.assertEqual(list(f.iter_ports()), []) - - f._propagate_ports(ports=(), all_undef_as_ports=True) - self.assertEqual(f.ports, SignalDict([])) - - def test_iter_signals(self): - f = Fragment() - f.add_ports(self.s1, self.s2, dir="io") - self.assertEqual(SignalSet((self.s1, self.s2)), f.iter_signals()) - - def test_self_contained(self): - f = Fragment() - f.add_statements( - self.c1.eq(self.s1), - self.s1.eq(self.c1) - ) - - f._propagate_ports(ports=(), all_undef_as_ports=True) - self.assertEqual(f.ports, SignalDict([])) - - def test_infer_input(self): - f = Fragment() - f.add_statements( - self.c1.eq(self.s1) - ) - - f._propagate_ports(ports=(), all_undef_as_ports=True) - self.assertEqual(f.ports, SignalDict([ - (self.s1, "i") - ])) - - def test_request_output(self): - f = Fragment() - f.add_statements( - self.c1.eq(self.s1) - ) - - f._propagate_ports(ports=(self.c1,), all_undef_as_ports=True) - self.assertEqual(f.ports, SignalDict([ - (self.s1, "i"), - (self.c1, "o") - ])) - - def test_input_in_subfragment(self): - f1 = Fragment() - f1.add_statements( - self.c1.eq(self.s1) - ) - f2 = Fragment() - f2.add_statements( - self.s1.eq(0) - ) - f1.add_subfragment(f2) - f1._propagate_ports(ports=(), all_undef_as_ports=True) - self.assertEqual(f1.ports, SignalDict()) - self.assertEqual(f2.ports, SignalDict([ - (self.s1, "o"), - ])) - - def test_input_only_in_subfragment(self): - f1 = Fragment() - f2 = Fragment() - f2.add_statements( - self.c1.eq(self.s1) - ) - f1.add_subfragment(f2) - f1._propagate_ports(ports=(), all_undef_as_ports=True) - self.assertEqual(f1.ports, SignalDict([ - (self.s1, "i"), - ])) - self.assertEqual(f2.ports, SignalDict([ - (self.s1, "i"), - ])) - - def test_output_from_subfragment(self): - f1 = Fragment() - f1.add_statements( - self.c1.eq(0) - ) - f2 = Fragment() - f2.add_statements( - self.c2.eq(1) - ) - f1.add_subfragment(f2) - - f1._propagate_ports(ports=(self.c2,), all_undef_as_ports=True) - self.assertEqual(f1.ports, SignalDict([ - (self.c2, "o"), - ])) - self.assertEqual(f2.ports, SignalDict([ - (self.c2, "o"), - ])) - - def test_output_from_subfragment_2(self): - f1 = Fragment() - f1.add_statements( - self.c1.eq(self.s1) - ) - f2 = Fragment() - f2.add_statements( - self.c2.eq(self.s1) - ) - f1.add_subfragment(f2) - f3 = Fragment() - f3.add_statements( - self.s1.eq(0) - ) - f2.add_subfragment(f3) - - f1._propagate_ports(ports=(), all_undef_as_ports=True) - self.assertEqual(f2.ports, SignalDict([ - (self.s1, "o"), - ])) - - def test_input_output_sibling(self): - f1 = Fragment() - f2 = Fragment() - f2.add_statements( - self.c1.eq(self.c2) - ) - f1.add_subfragment(f2) - f3 = Fragment() - f3.add_statements( - self.c2.eq(0) - ) - f3.add_driver(self.c2) - f1.add_subfragment(f3) - - f1._propagate_ports(ports=(), all_undef_as_ports=True) - self.assertEqual(f1.ports, SignalDict()) - - def test_output_input_sibling(self): - f1 = Fragment() - f2 = Fragment() - f2.add_statements( - self.c2.eq(0) - ) - f2.add_driver(self.c2) - f1.add_subfragment(f2) - f3 = Fragment() - f3.add_statements( - self.c1.eq(self.c2) - ) - f1.add_subfragment(f3) - - f1._propagate_ports(ports=(), all_undef_as_ports=True) - self.assertEqual(f1.ports, SignalDict()) - - def test_input_cd(self): - sync = ClockDomain() - f = Fragment() - f.add_statements( - self.c1.eq(self.s1) - ) - f.add_domains(sync) - f.add_driver(self.c1, "sync") - - f._propagate_ports(ports=(), all_undef_as_ports=True) - self.assertEqual(f.ports, SignalDict([ - (self.s1, "i"), - (sync.clk, "i"), - (sync.rst, "i"), - ])) - - def test_input_cd_reset_less(self): - sync = ClockDomain(reset_less=True) - f = Fragment() - f.add_statements( - self.c1.eq(self.s1) - ) - f.add_domains(sync) - f.add_driver(self.c1, "sync") - - f._propagate_ports(ports=(), all_undef_as_ports=True) - self.assertEqual(f.ports, SignalDict([ - (self.s1, "i"), - (sync.clk, "i"), - ])) - - def test_inout(self): - s = Signal() - f1 = Fragment() - f2 = Instance("foo", io_x=s) - f1.add_subfragment(f2) - - f1._propagate_ports(ports=(), all_undef_as_ports=True) - self.assertEqual(f1.ports, SignalDict([ - (s, "io") - ])) - - def test_in_out_same_signal(self): - s = Signal() - - f1 = Instance("foo", i_x=s, o_y=s) - f2 = Fragment() - f2.add_subfragment(f1) - - f2._propagate_ports(ports=(), all_undef_as_ports=True) - self.assertEqual(f1.ports, SignalDict([ - (s, "o") - ])) - - f3 = Instance("foo", o_y=s, i_x=s) - f4 = Fragment() - f4.add_subfragment(f3) - - f4._propagate_ports(ports=(), all_undef_as_ports=True) - self.assertEqual(f3.ports, SignalDict([ - (s, "o") - ])) - - def test_clk_rst(self): - sync = ClockDomain() - f = Fragment() - f.add_domains(sync) - - f = f.prepare(ports=(ClockSignal("sync"), ResetSignal("sync"))) - self.assertEqual(f.ports, SignalDict([ - (sync.clk, "i"), - (sync.rst, "i"), - ])) - - def test_port_wrong(self): - f = Fragment() - with self.assertRaisesRegex(TypeError, - r"^Only signals may be added as ports, not \(const 1'd1\)$"): - f.prepare(ports=(Const(1),)) - - def test_port_not_iterable(self): - f = Fragment() - with self.assertRaisesRegex(TypeError, - r"^`ports` must be either a list or a tuple, not 1$"): - f.prepare(ports=1) - with self.assertRaisesRegex(TypeError, - (r"^`ports` must be either a list or a tuple, not \(const 1'd1\)" - r" \(did you mean `ports=\(,\)`, rather than `ports=`\?\)$")): - f.prepare(ports=Const(1)) - -class FragmentDomainsTestCase(FHDLTestCase): - def test_iter_signals(self): - cd1 = ClockDomain() - cd2 = ClockDomain(reset_less=True) - s1 = Signal() - s2 = Signal() - - f = Fragment() - f.add_domains(cd1, cd2) - f.add_driver(s1, "cd1") - self.assertEqual(SignalSet((cd1.clk, cd1.rst, s1)), f.iter_signals()) - f.add_driver(s2, "cd2") - self.assertEqual(SignalSet((cd1.clk, cd1.rst, cd2.clk, s1, s2)), f.iter_signals()) - - def test_propagate_up(self): - cd = ClockDomain() - - f1 = Fragment() - f2 = Fragment() - f1.add_subfragment(f2) - f2.add_domains(cd) - - f1._propagate_domains_up() - self.assertEqual(f1.domains, {"cd": cd}) - - def test_propagate_up_local(self): - cd = ClockDomain(local=True) - - f1 = Fragment() - f2 = Fragment() - f1.add_subfragment(f2) - f2.add_domains(cd) - - f1._propagate_domains_up() - self.assertEqual(f1.domains, {}) - - def test_domain_conflict(self): - cda = ClockDomain("sync") - cdb = ClockDomain("sync") - - fa = Fragment() - fa.add_domains(cda) - fb = Fragment() - fb.add_domains(cdb) - f = Fragment() - f.add_subfragment(fa, "a") - f.add_subfragment(fb, "b") - - f._propagate_domains_up() - self.assertEqual(f.domains, {"a_sync": cda, "b_sync": cdb}) - (fa, _), (fb, _) = f.subfragments - self.assertEqual(fa.domains, {"a_sync": cda}) - self.assertEqual(fb.domains, {"b_sync": cdb}) - - def test_domain_conflict_anon(self): - cda = ClockDomain("sync") - cdb = ClockDomain("sync") - - fa = Fragment() - fa.add_domains(cda) - fb = Fragment() - fb.add_domains(cdb) - f = Fragment() - f.add_subfragment(fa, "a") - f.add_subfragment(fb) - - with self.assertRaisesRegex(DomainError, - (r"^Domain 'sync' is defined by subfragments 'a', of fragment " - r"'top'; it is necessary to either rename subfragment domains explicitly, " - r"or give names to subfragments$")): - f._propagate_domains_up() - - def test_domain_conflict_name(self): - cda = ClockDomain("sync") - cdb = ClockDomain("sync") - - fa = Fragment() - fa.add_domains(cda) - fb = Fragment() - fb.add_domains(cdb) - f = Fragment() - f.add_subfragment(fa, "x") - f.add_subfragment(fb, "x") - - with self.assertRaisesRegex(DomainError, - (r"^Domain 'sync' is defined by subfragments #0, #1 of fragment 'top', some " - r"of which have identical names; it is necessary to either rename subfragment " - r"domains explicitly, or give distinct names to subfragments$")): - f._propagate_domains_up() - - def test_domain_conflict_rename_drivers(self): - cda = ClockDomain("sync") - cdb = ClockDomain("sync") - - fa = Fragment() - fa.add_domains(cda) - fb = Fragment() - fb.add_domains(cdb) - fb.add_driver(ResetSignal("sync"), None) - f = Fragment() - f.add_subfragment(fa, "a") - f.add_subfragment(fb, "b") - - f._propagate_domains_up() - fb_new, _ = f.subfragments[1] - self.assertEqual(fb_new.drivers, OrderedDict({ - None: SignalSet((ResetSignal("b_sync"),)) - })) - - def test_domain_conflict_rename_drivers(self): - cda = ClockDomain("sync") - cdb = ClockDomain("sync") - s = Signal() - - fa = Fragment() - fa.add_domains(cda) - fb = Fragment() - fb.add_domains(cdb) - f = Fragment() - f.add_subfragment(fa, "a") - f.add_subfragment(fb, "b") - f.add_driver(s, "b_sync") - - f._propagate_domains(lambda name: ClockDomain(name)) - - def test_propagate_down(self): - cd = ClockDomain() - - f1 = Fragment() - f2 = Fragment() - f1.add_domains(cd) - f1.add_subfragment(f2) - - f1._propagate_domains_down() - self.assertEqual(f2.domains, {"cd": cd}) - - def test_propagate_down_idempotent(self): - cd = ClockDomain() - - f1 = Fragment() - f1.add_domains(cd) - f2 = Fragment() - f2.add_domains(cd) - f1.add_subfragment(f2) - - f1._propagate_domains_down() - self.assertEqual(f1.domains, {"cd": cd}) - self.assertEqual(f2.domains, {"cd": cd}) - - def test_propagate(self): - cd = ClockDomain() - - f1 = Fragment() - f2 = Fragment() - f1.add_domains(cd) - f1.add_subfragment(f2) - - new_domains = f1._propagate_domains(missing_domain=lambda name: None) - self.assertEqual(f1.domains, {"cd": cd}) - self.assertEqual(f2.domains, {"cd": cd}) - self.assertEqual(new_domains, []) - - def test_propagate_missing(self): - s1 = Signal() - f1 = Fragment() - f1.add_driver(s1, "sync") - - with self.assertRaisesRegex(DomainError, - r"^Domain 'sync' is used but not defined$"): - f1._propagate_domains(missing_domain=lambda name: None) - - def test_propagate_create_missing(self): - s1 = Signal() - f1 = Fragment() - f1.add_driver(s1, "sync") - f2 = Fragment() - f1.add_subfragment(f2) - - new_domains = f1._propagate_domains(missing_domain=lambda name: ClockDomain(name)) - self.assertEqual(f1.domains.keys(), {"sync"}) - self.assertEqual(f2.domains.keys(), {"sync"}) - self.assertEqual(f1.domains["sync"], f2.domains["sync"]) - self.assertEqual(new_domains, [f1.domains["sync"]]) - - def test_propagate_create_missing_fragment(self): - s1 = Signal() - f1 = Fragment() - f1.add_driver(s1, "sync") - - cd = ClockDomain("sync") - f2 = Fragment() - f2.add_domains(cd) - - new_domains = f1._propagate_domains(missing_domain=lambda name: f2) - self.assertEqual(f1.domains.keys(), {"sync"}) - self.assertEqual(f1.domains["sync"], f2.domains["sync"]) - self.assertEqual(new_domains, []) - self.assertEqual(f1.subfragments, [ - (f2, "cd_sync") - ]) - - def test_propagate_create_missing_fragment_many_domains(self): - s1 = Signal() - f1 = Fragment() - f1.add_driver(s1, "sync") - - cd_por = ClockDomain("por") - cd_sync = ClockDomain("sync") - f2 = Fragment() - f2.add_domains(cd_por, cd_sync) - - new_domains = f1._propagate_domains(missing_domain=lambda name: f2) - self.assertEqual(f1.domains.keys(), {"sync", "por"}) - self.assertEqual(f2.domains.keys(), {"sync", "por"}) - self.assertEqual(f1.domains["sync"], f2.domains["sync"]) - self.assertEqual(new_domains, []) - self.assertEqual(f1.subfragments, [ - (f2, "cd_sync") - ]) - - def test_propagate_create_missing_fragment_wrong(self): - s1 = Signal() - f1 = Fragment() - f1.add_driver(s1, "sync") - - f2 = Fragment() - f2.add_domains(ClockDomain("foo")) - - with self.assertRaisesRegex(DomainError, - (r"^Fragment returned by missing domain callback does not define requested " - r"domain 'sync' \(defines 'foo'\)\.$")): - f1._propagate_domains(missing_domain=lambda name: f2) - - -class FragmentHierarchyConflictTestCase(FHDLTestCase): - def setUp_self_sub(self): - self.s1 = Signal() - self.c1 = Signal() - self.c2 = Signal() - - self.f1 = Fragment() - self.f1.add_statements(self.c1.eq(0)) - self.f1.add_driver(self.s1) - self.f1.add_driver(self.c1, "sync") - - self.f1a = Fragment() - self.f1.add_subfragment(self.f1a, "f1a") - - self.f2 = Fragment() - self.f2.add_statements(self.c2.eq(1)) - self.f2.add_driver(self.s1) - self.f2.add_driver(self.c2, "sync") - self.f1.add_subfragment(self.f2) - - self.f1b = Fragment() - self.f1.add_subfragment(self.f1b, "f1b") - - self.f2a = Fragment() - self.f2.add_subfragment(self.f2a, "f2a") - - def test_conflict_self_sub(self): - self.setUp_self_sub() - - self.f1._resolve_hierarchy_conflicts(mode="silent") - self.assertEqual(self.f1.subfragments, [ - (self.f1a, "f1a"), - (self.f1b, "f1b"), - (self.f2a, "f2a"), - ]) - self.assertRepr(self.f1.statements, """ - ( - (eq (sig c1) (const 1'd0)) - (eq (sig c2) (const 1'd1)) - ) - """) - self.assertEqual(self.f1.drivers, { - None: SignalSet((self.s1,)), - "sync": SignalSet((self.c1, self.c2)), - }) - - def test_conflict_self_sub_error(self): - self.setUp_self_sub() - - with self.assertRaisesRegex(DriverConflict, - r"^Signal '\(sig s1\)' is driven from multiple fragments: top, top.$"): - self.f1._resolve_hierarchy_conflicts(mode="error") - - def test_conflict_self_sub_warning(self): - self.setUp_self_sub() - - with self.assertWarnsRegex(DriverConflict, - (r"^Signal '\(sig s1\)' is driven from multiple fragments: top, top.; " - r"hierarchy will be flattened$")): - self.f1._resolve_hierarchy_conflicts(mode="warn") - - def setUp_sub_sub(self): - self.s1 = Signal() - self.c1 = Signal() - self.c2 = Signal() - - self.f1 = Fragment() - - self.f2 = Fragment() - self.f2.add_driver(self.s1) - self.f2.add_statements(self.c1.eq(0)) - self.f1.add_subfragment(self.f2) - - self.f3 = Fragment() - self.f3.add_driver(self.s1) - self.f3.add_statements(self.c2.eq(1)) - self.f1.add_subfragment(self.f3) - - def test_conflict_sub_sub(self): - self.setUp_sub_sub() - - self.f1._resolve_hierarchy_conflicts(mode="silent") - self.assertEqual(self.f1.subfragments, []) - self.assertRepr(self.f1.statements, """ - ( - (eq (sig c1) (const 1'd0)) - (eq (sig c2) (const 1'd1)) - ) - """) - - def setUp_self_subsub(self): - self.s1 = Signal() - self.c1 = Signal() - self.c2 = Signal() - - self.f1 = Fragment() - self.f1.add_driver(self.s1) - - self.f2 = Fragment() - self.f2.add_statements(self.c1.eq(0)) - self.f1.add_subfragment(self.f2) - - self.f3 = Fragment() - self.f3.add_driver(self.s1) - self.f3.add_statements(self.c2.eq(1)) - self.f2.add_subfragment(self.f3) - - def test_conflict_self_subsub(self): - self.setUp_self_subsub() - - self.f1._resolve_hierarchy_conflicts(mode="silent") - self.assertEqual(self.f1.subfragments, []) - self.assertRepr(self.f1.statements, """ - ( - (eq (sig c1) (const 1'd0)) - (eq (sig c2) (const 1'd1)) - ) - """) - - def setUp_memory(self): - self.m = Memory(width=8, depth=4) - self.fr = self.m.read_port().elaborate(platform=None) - self.fw = self.m.write_port().elaborate(platform=None) - self.f1 = Fragment() - self.f2 = Fragment() - self.f2.add_subfragment(self.fr) - self.f1.add_subfragment(self.f2) - self.f3 = Fragment() - self.f3.add_subfragment(self.fw) - self.f1.add_subfragment(self.f3) - - def test_conflict_memory(self): - self.setUp_memory() - - self.f1._resolve_hierarchy_conflicts(mode="silent") - self.assertEqual(self.f1.subfragments, [ - (self.fr, None), - (self.fw, None), - ]) - - def test_conflict_memory_error(self): - self.setUp_memory() - - with self.assertRaisesRegex(DriverConflict, - r"^Memory 'm' is accessed from multiple fragments: top\., " - r"top\.$"): - self.f1._resolve_hierarchy_conflicts(mode="error") - - def test_conflict_memory_warning(self): - self.setUp_memory() - - with self.assertWarnsRegex(DriverConflict, - (r"^Memory 'm' is accessed from multiple fragments: top., " - r"top.; hierarchy will be flattened$")): - self.f1._resolve_hierarchy_conflicts(mode="warn") - - def test_explicit_flatten(self): - self.f1 = Fragment() - self.f2 = Fragment() - self.f2.flatten = True - self.f1.add_subfragment(self.f2) - - self.f1._resolve_hierarchy_conflicts(mode="silent") - self.assertEqual(self.f1.subfragments, []) - - def test_no_conflict_local_domains(self): - f1 = Fragment() - cd1 = ClockDomain("d", local=True) - f1.add_domains(cd1) - f1.add_driver(ClockSignal("d")) - f2 = Fragment() - cd2 = ClockDomain("d", local=True) - f2.add_domains(cd2) - f2.add_driver(ClockSignal("d")) - f3 = Fragment() - f3.add_subfragment(f1) - f3.add_subfragment(f2) - f3.prepare() - - -class InstanceTestCase(FHDLTestCase): - def test_construct(self): - s1 = Signal() - s2 = Signal() - s3 = Signal() - s4 = Signal() - s5 = Signal() - s6 = Signal() - inst = Instance("foo", - ("a", "ATTR1", 1), - ("p", "PARAM1", 0x1234), - ("i", "s1", s1), - ("o", "s2", s2), - ("io", "s3", s3), - a_ATTR2=2, - p_PARAM2=0x5678, - i_s4=s4, - o_s5=s5, - io_s6=s6, - ) - self.assertEqual(inst.attrs, OrderedDict([ - ("ATTR1", 1), - ("ATTR2", 2), - ])) - self.assertEqual(inst.parameters, OrderedDict([ - ("PARAM1", 0x1234), - ("PARAM2", 0x5678), - ])) - self.assertEqual(inst.named_ports, OrderedDict([ - ("s1", (s1, "i")), - ("s2", (s2, "o")), - ("s3", (s3, "io")), - ("s4", (s4, "i")), - ("s5", (s5, "o")), - ("s6", (s6, "io")), - ])) - - def test_cast_ports(self): - inst = Instance("foo", - ("i", "s1", 1), - ("o", "s2", 2), - ("io", "s3", 3), - i_s4=4, - o_s5=5, - io_s6=6, - ) - self.assertRepr(inst.named_ports["s1"][0], "(const 1'd1)") - self.assertRepr(inst.named_ports["s2"][0], "(const 2'd2)") - self.assertRepr(inst.named_ports["s3"][0], "(const 2'd3)") - self.assertRepr(inst.named_ports["s4"][0], "(const 3'd4)") - self.assertRepr(inst.named_ports["s5"][0], "(const 3'd5)") - self.assertRepr(inst.named_ports["s6"][0], "(const 3'd6)") - - def test_wrong_construct_arg(self): - s = Signal() - with self.assertRaisesRegex(NameError, - (r"^Instance argument \('', 's1', \(sig s\)\) should be a tuple " - r"\(kind, name, value\) where kind is one of \"p\", \"i\", \"o\", or \"io\"$")): - Instance("foo", ("", "s1", s)) - - def test_wrong_construct_kwarg(self): - s = Signal() - with self.assertRaisesRegex(NameError, - (r"^Instance keyword argument x_s1=\(sig s\) does not start with one of " - r"\"p_\", \"i_\", \"o_\", or \"io_\"$")): - Instance("foo", x_s1=s) - - def setUp_cpu(self): - self.rst = Signal() - self.stb = Signal() - self.pins = Signal(8) - self.datal = Signal(4) - self.datah = Signal(4) - self.inst = Instance("cpu", - p_RESET=0x1234, - i_clk=ClockSignal(), - i_rst=self.rst, - o_stb=self.stb, - o_data=Cat(self.datal, self.datah), - io_pins=self.pins[:] - ) - self.wrap = Fragment() - self.wrap.add_subfragment(self.inst) - - def test_init(self): - self.setUp_cpu() - f = self.inst - self.assertEqual(f.type, "cpu") - self.assertEqual(f.parameters, OrderedDict([("RESET", 0x1234)])) - self.assertEqual(list(f.named_ports.keys()), ["clk", "rst", "stb", "data", "pins"]) - self.assertEqual(f.ports, SignalDict([])) - - def test_prepare(self): - self.setUp_cpu() - f = self.wrap.prepare() - sync_clk = f.domains["sync"].clk - self.assertEqual(f.ports, SignalDict([ - (sync_clk, "i"), - (self.rst, "i"), - (self.pins, "io"), - ])) - - def test_prepare_explicit_ports(self): - self.setUp_cpu() - f = self.wrap.prepare(ports=[self.rst, self.stb]) - sync_clk = f.domains["sync"].clk - sync_rst = f.domains["sync"].rst - self.assertEqual(f.ports, SignalDict([ - (sync_clk, "i"), - (sync_rst, "i"), - (self.rst, "i"), - (self.stb, "o"), - (self.pins, "io"), - ])) - - def test_prepare_slice_in_port(self): - s = Signal(2) - f = Fragment() - f.add_subfragment(Instance("foo", o_O=s[0])) - f.add_subfragment(Instance("foo", o_O=s[1])) - fp = f.prepare(ports=[s], missing_domain=lambda name: None) - self.assertEqual(fp.ports, SignalDict([ - (s, "o"), - ])) - - def test_prepare_attrs(self): - self.setUp_cpu() - self.inst.attrs["ATTR"] = 1 - f = self.inst.prepare() - self.assertEqual(f.attrs, OrderedDict([ - ("ATTR", 1), - ])) diff --git a/nmigen/test/test_hdl_mem.py b/nmigen/test/test_hdl_mem.py deleted file mode 100644 index ed87aec..0000000 --- a/nmigen/test/test_hdl_mem.py +++ /dev/null @@ -1,137 +0,0 @@ -# nmigen: UnusedElaboratable=no - -from ..hdl.ast import * -from ..hdl.mem import * -from .utils import * - - -class MemoryTestCase(FHDLTestCase): - def test_name(self): - m1 = Memory(width=8, depth=4) - self.assertEqual(m1.name, "m1") - m2 = [Memory(width=8, depth=4)][0] - self.assertEqual(m2.name, "$memory") - m3 = Memory(width=8, depth=4, name="foo") - self.assertEqual(m3.name, "foo") - - def test_geometry(self): - m = Memory(width=8, depth=4) - self.assertEqual(m.width, 8) - self.assertEqual(m.depth, 4) - - def test_geometry_wrong(self): - with self.assertRaisesRegex(TypeError, - r"^Memory width must be a non-negative integer, not -1$"): - m = Memory(width=-1, depth=4) - with self.assertRaisesRegex(TypeError, - r"^Memory depth must be a non-negative integer, not -1$"): - m = Memory(width=8, depth=-1) - - def test_init(self): - m = Memory(width=8, depth=4, init=range(4)) - self.assertEqual(m.init, [0, 1, 2, 3]) - - def test_init_wrong_count(self): - with self.assertRaisesRegex(ValueError, - r"^Memory initialization value count exceed memory depth \(8 > 4\)$"): - m = Memory(width=8, depth=4, init=range(8)) - - def test_init_wrong_type(self): - with self.assertRaisesRegex(TypeError, - (r"^Memory initialization value at address 1: " - r"'str' object cannot be interpreted as an integer$")): - m = Memory(width=8, depth=4, init=[1, "0"]) - - def test_attrs(self): - m1 = Memory(width=8, depth=4) - self.assertEqual(m1.attrs, {}) - m2 = Memory(width=8, depth=4, attrs={"ram_block": True}) - self.assertEqual(m2.attrs, {"ram_block": True}) - - def test_read_port_transparent(self): - mem = Memory(width=8, depth=4) - rdport = mem.read_port() - self.assertEqual(rdport.memory, mem) - self.assertEqual(rdport.domain, "sync") - self.assertEqual(rdport.transparent, True) - self.assertEqual(len(rdport.addr), 2) - self.assertEqual(len(rdport.data), 8) - self.assertEqual(len(rdport.en), 1) - self.assertIsInstance(rdport.en, Const) - self.assertEqual(rdport.en.value, 1) - - def test_read_port_non_transparent(self): - mem = Memory(width=8, depth=4) - rdport = mem.read_port(transparent=False) - self.assertEqual(rdport.memory, mem) - self.assertEqual(rdport.domain, "sync") - self.assertEqual(rdport.transparent, False) - self.assertEqual(len(rdport.en), 1) - self.assertIsInstance(rdport.en, Signal) - self.assertEqual(rdport.en.reset, 1) - - def test_read_port_asynchronous(self): - mem = Memory(width=8, depth=4) - rdport = mem.read_port(domain="comb") - self.assertEqual(rdport.memory, mem) - self.assertEqual(rdport.domain, "comb") - self.assertEqual(rdport.transparent, True) - self.assertEqual(len(rdport.en), 1) - self.assertIsInstance(rdport.en, Const) - self.assertEqual(rdport.en.value, 1) - - def test_read_port_wrong(self): - mem = Memory(width=8, depth=4) - with self.assertRaisesRegex(ValueError, - r"^Read port cannot be simultaneously asynchronous and non-transparent$"): - mem.read_port(domain="comb", transparent=False) - - def test_write_port(self): - mem = Memory(width=8, depth=4) - wrport = mem.write_port() - self.assertEqual(wrport.memory, mem) - self.assertEqual(wrport.domain, "sync") - self.assertEqual(wrport.granularity, 8) - self.assertEqual(len(wrport.addr), 2) - self.assertEqual(len(wrport.data), 8) - self.assertEqual(len(wrport.en), 1) - - def test_write_port_granularity(self): - mem = Memory(width=8, depth=4) - wrport = mem.write_port(granularity=2) - self.assertEqual(wrport.memory, mem) - self.assertEqual(wrport.domain, "sync") - self.assertEqual(wrport.granularity, 2) - self.assertEqual(len(wrport.addr), 2) - self.assertEqual(len(wrport.data), 8) - self.assertEqual(len(wrport.en), 4) - - def test_write_port_granularity_wrong(self): - mem = Memory(width=8, depth=4) - with self.assertRaisesRegex(TypeError, - r"^Write port granularity must be a non-negative integer, not -1$"): - mem.write_port(granularity=-1) - with self.assertRaisesRegex(ValueError, - r"^Write port granularity must not be greater than memory width \(10 > 8\)$"): - mem.write_port(granularity=10) - with self.assertRaisesRegex(ValueError, - r"^Write port granularity must divide memory width evenly$"): - mem.write_port(granularity=3) - - -class DummyPortTestCase(FHDLTestCase): - def test_name(self): - p1 = DummyPort(data_width=8, addr_width=2) - self.assertEqual(p1.addr.name, "p1_addr") - p2 = [DummyPort(data_width=8, addr_width=2)][0] - self.assertEqual(p2.addr.name, "dummy_addr") - p3 = DummyPort(data_width=8, addr_width=2, name="foo") - self.assertEqual(p3.addr.name, "foo_addr") - - def test_sizes(self): - p1 = DummyPort(data_width=8, addr_width=2) - self.assertEqual(p1.addr.width, 2) - self.assertEqual(p1.data.width, 8) - self.assertEqual(p1.en.width, 1) - p2 = DummyPort(data_width=8, addr_width=2, granularity=2) - self.assertEqual(p2.en.width, 4) diff --git a/nmigen/test/test_hdl_rec.py b/nmigen/test/test_hdl_rec.py deleted file mode 100644 index dfcadf7..0000000 --- a/nmigen/test/test_hdl_rec.py +++ /dev/null @@ -1,329 +0,0 @@ -from enum import Enum - -from ..hdl.ast import * -from ..hdl.rec import * -from .utils import * - - -class UnsignedEnum(Enum): - FOO = 1 - BAR = 2 - BAZ = 3 - - -class LayoutTestCase(FHDLTestCase): - def assertFieldEqual(self, field, expected): - (shape, dir) = field - shape = Shape.cast(shape) - self.assertEqual((shape, dir), expected) - - def test_fields(self): - layout = Layout.cast([ - ("cyc", 1), - ("data", signed(32)), - ("stb", 1, DIR_FANOUT), - ("ack", 1, DIR_FANIN), - ("info", [ - ("a", 1), - ("b", 1), - ]) - ]) - - self.assertFieldEqual(layout["cyc"], ((1, False), DIR_NONE)) - self.assertFieldEqual(layout["data"], ((32, True), DIR_NONE)) - self.assertFieldEqual(layout["stb"], ((1, False), DIR_FANOUT)) - self.assertFieldEqual(layout["ack"], ((1, False), DIR_FANIN)) - sublayout = layout["info"][0] - self.assertEqual(layout["info"][1], DIR_NONE) - self.assertFieldEqual(sublayout["a"], ((1, False), DIR_NONE)) - self.assertFieldEqual(sublayout["b"], ((1, False), DIR_NONE)) - - def test_enum_field(self): - layout = Layout.cast([ - ("enum", UnsignedEnum), - ("enum_dir", UnsignedEnum, DIR_FANOUT), - ]) - self.assertFieldEqual(layout["enum"], ((2, False), DIR_NONE)) - self.assertFieldEqual(layout["enum_dir"], ((2, False), DIR_FANOUT)) - - def test_range_field(self): - layout = Layout.cast([ - ("range", range(0, 7)), - ]) - self.assertFieldEqual(layout["range"], ((3, False), DIR_NONE)) - - def test_slice_tuple(self): - layout = Layout.cast([ - ("a", 1), - ("b", 2), - ("c", 3) - ]) - expect = Layout.cast([ - ("a", 1), - ("c", 3) - ]) - self.assertEqual(layout["a", "c"], expect) - - def test_repr(self): - self.assertEqual(repr(Layout([("a", unsigned(1)), ("b", signed(2))])), - "Layout([('a', unsigned(1)), ('b', signed(2))])") - self.assertEqual(repr(Layout([("a", unsigned(1)), ("b", [("c", signed(3))])])), - "Layout([('a', unsigned(1)), " - "('b', Layout([('c', signed(3))]))])") - - def test_wrong_field(self): - with self.assertRaisesRegex(TypeError, - (r"^Field \(1,\) has invalid layout: should be either \(name, shape\) or " - r"\(name, shape, direction\)$")): - Layout.cast([(1,)]) - - def test_wrong_name(self): - with self.assertRaisesRegex(TypeError, - r"^Field \(1, 1\) has invalid name: should be a string$"): - Layout.cast([(1, 1)]) - - def test_wrong_name_duplicate(self): - with self.assertRaisesRegex(NameError, - r"^Field \('a', 2\) has a name that is already present in the layout$"): - Layout.cast([("a", 1), ("a", 2)]) - - def test_wrong_direction(self): - with self.assertRaisesRegex(TypeError, - (r"^Field \('a', 1, 0\) has invalid direction: should be a Direction " - r"instance like DIR_FANIN$")): - Layout.cast([("a", 1, 0)]) - - def test_wrong_shape(self): - with self.assertRaisesRegex(TypeError, - (r"^Field \('a', 'x'\) has invalid shape: should be castable to Shape or " - r"a list of fields of a nested record$")): - Layout.cast([("a", "x")]) - - -class RecordTestCase(FHDLTestCase): - def test_basic(self): - r = Record([ - ("stb", 1), - ("data", 32), - ("info", [ - ("a", 1), - ("b", 1), - ]) - ]) - - self.assertEqual(repr(r), "(rec r stb data (rec r__info a b))") - self.assertEqual(len(r), 35) - self.assertIsInstance(r.stb, Signal) - self.assertEqual(r.stb.name, "r__stb") - self.assertEqual(r["stb"].name, "r__stb") - - self.assertTrue(hasattr(r, "stb")) - self.assertFalse(hasattr(r, "xxx")) - - def test_unnamed(self): - r = [Record([ - ("stb", 1) - ])][0] - - self.assertEqual(repr(r), "(rec stb)") - self.assertEqual(r.stb.name, "stb") - - def test_iter(self): - r = Record([ - ("data", 4), - ("stb", 1), - ]) - - self.assertEqual(repr(r[0]), "(slice (rec r data stb) 0:1)") - self.assertEqual(repr(r[0:3]), "(slice (rec r data stb) 0:3)") - - def test_wrong_field(self): - r = Record([ - ("stb", 1), - ("ack", 1), - ]) - with self.assertRaisesRegex(AttributeError, - r"^Record 'r' does not have a field 'en'\. Did you mean one of: stb, ack\?$"): - r["en"] - with self.assertRaisesRegex(AttributeError, - r"^Record 'r' does not have a field 'en'\. Did you mean one of: stb, ack\?$"): - r.en - - def test_wrong_field_unnamed(self): - r = [Record([ - ("stb", 1), - ("ack", 1), - ])][0] - with self.assertRaisesRegex(AttributeError, - r"^Unnamed record does not have a field 'en'\. Did you mean one of: stb, ack\?$"): - r.en - - def test_construct_with_fields(self): - ns = Signal(1) - nr = Record([ - ("burst", 1) - ]) - r = Record([ - ("stb", 1), - ("info", [ - ("burst", 1) - ]) - ], fields={ - "stb": ns, - "info": nr - }) - self.assertIs(r.stb, ns) - self.assertIs(r.info, nr) - - def test_like(self): - r1 = Record([("a", 1), ("b", 2)]) - r2 = Record.like(r1) - self.assertEqual(r1.layout, r2.layout) - self.assertEqual(r2.name, "r2") - r3 = Record.like(r1, name="foo") - self.assertEqual(r3.name, "foo") - r4 = Record.like(r1, name_suffix="foo") - self.assertEqual(r4.name, "r1foo") - - def test_like_modifications(self): - r1 = Record([("a", 1), ("b", [("s", 1)])]) - self.assertEqual(r1.a.name, "r1__a") - self.assertEqual(r1.b.name, "r1__b") - self.assertEqual(r1.b.s.name, "r1__b__s") - r1.a.reset = 1 - r1.b.s.reset = 1 - r2 = Record.like(r1) - self.assertEqual(r2.a.reset, 1) - self.assertEqual(r2.b.s.reset, 1) - self.assertEqual(r2.a.name, "r2__a") - self.assertEqual(r2.b.name, "r2__b") - self.assertEqual(r2.b.s.name, "r2__b__s") - - def test_slice_tuple(self): - r1 = Record([("a", 1), ("b", 2), ("c", 3)]) - r2 = r1["a", "c"] - self.assertEqual(r2.layout, Layout([("a", 1), ("c", 3)])) - self.assertIs(r2.a, r1.a) - self.assertIs(r2.c, r1.c) - - def test_enum_decoder(self): - r1 = Record([("a", UnsignedEnum)]) - self.assertEqual(r1.a.decoder(UnsignedEnum.FOO), "FOO/1") - - -class ConnectTestCase(FHDLTestCase): - def setUp_flat(self): - self.core_layout = [ - ("addr", 32, DIR_FANOUT), - ("data_r", 32, DIR_FANIN), - ("data_w", 32, DIR_FANIN), - ] - self.periph_layout = [ - ("addr", 32, DIR_FANOUT), - ("data_r", 32, DIR_FANIN), - ("data_w", 32, DIR_FANIN), - ] - - def setUp_nested(self): - self.core_layout = [ - ("addr", 32, DIR_FANOUT), - ("data", [ - ("r", 32, DIR_FANIN), - ("w", 32, DIR_FANIN), - ]), - ] - self.periph_layout = [ - ("addr", 32, DIR_FANOUT), - ("data", [ - ("r", 32, DIR_FANIN), - ("w", 32, DIR_FANIN), - ]), - ] - - def test_flat(self): - self.setUp_flat() - - core = Record(self.core_layout) - periph1 = Record(self.periph_layout) - periph2 = Record(self.periph_layout) - - stmts = core.connect(periph1, periph2) - self.assertRepr(stmts, """( - (eq (sig periph1__addr) (sig core__addr)) - (eq (sig periph2__addr) (sig core__addr)) - (eq (sig core__data_r) (| (sig periph1__data_r) (sig periph2__data_r))) - (eq (sig core__data_w) (| (sig periph1__data_w) (sig periph2__data_w))) - )""") - - def test_flat_include(self): - self.setUp_flat() - - core = Record(self.core_layout) - periph1 = Record(self.periph_layout) - periph2 = Record(self.periph_layout) - - stmts = core.connect(periph1, periph2, include={"addr": True}) - self.assertRepr(stmts, """( - (eq (sig periph1__addr) (sig core__addr)) - (eq (sig periph2__addr) (sig core__addr)) - )""") - - def test_flat_exclude(self): - self.setUp_flat() - - core = Record(self.core_layout) - periph1 = Record(self.periph_layout) - periph2 = Record(self.periph_layout) - - stmts = core.connect(periph1, periph2, exclude={"addr": True}) - self.assertRepr(stmts, """( - (eq (sig core__data_r) (| (sig periph1__data_r) (sig periph2__data_r))) - (eq (sig core__data_w) (| (sig periph1__data_w) (sig periph2__data_w))) - )""") - - def test_nested(self): - self.setUp_nested() - - core = Record(self.core_layout) - periph1 = Record(self.periph_layout) - periph2 = Record(self.periph_layout) - - stmts = core.connect(periph1, periph2) - self.maxDiff = None - self.assertRepr(stmts, """( - (eq (sig periph1__addr) (sig core__addr)) - (eq (sig periph2__addr) (sig core__addr)) - (eq (sig core__data__r) (| (sig periph1__data__r) (sig periph2__data__r))) - (eq (sig core__data__w) (| (sig periph1__data__w) (sig periph2__data__w))) - )""") - - def test_wrong_include_exclude(self): - self.setUp_flat() - - core = Record(self.core_layout) - periph = Record(self.periph_layout) - - with self.assertRaisesRegex(AttributeError, - r"^Cannot include field 'foo' because it is not present in record 'core'$"): - core.connect(periph, include={"foo": True}) - - with self.assertRaisesRegex(AttributeError, - r"^Cannot exclude field 'foo' because it is not present in record 'core'$"): - core.connect(periph, exclude={"foo": True}) - - def test_wrong_direction(self): - recs = [Record([("x", 1)]) for _ in range(2)] - - with self.assertRaisesRegex(TypeError, - (r"^Cannot connect field 'x' of unnamed record because it does not have " - r"a direction$")): - recs[0].connect(recs[1]) - - def test_wrong_missing_field(self): - core = Record([("addr", 32, DIR_FANOUT)]) - periph = Record([]) - - with self.assertRaisesRegex(AttributeError, - (r"^Cannot connect field 'addr' of record 'core' to subordinate record 'periph' " - r"because the subordinate record does not have this field$")): - core.connect(periph) diff --git a/nmigen/test/test_hdl_xfrm.py b/nmigen/test/test_hdl_xfrm.py deleted file mode 100644 index 9ec6e1b..0000000 --- a/nmigen/test/test_hdl_xfrm.py +++ /dev/null @@ -1,649 +0,0 @@ -# nmigen: UnusedElaboratable=no - -from ..hdl.ast import * -from ..hdl.cd import * -from ..hdl.ir import * -from ..hdl.xfrm import * -from ..hdl.mem import * -from .utils import * - - -class DomainRenamerTestCase(FHDLTestCase): - def setUp(self): - self.s1 = Signal() - self.s2 = Signal() - self.s3 = Signal() - self.s4 = Signal() - self.s5 = Signal() - self.c1 = Signal() - - def test_rename_signals(self): - f = Fragment() - f.add_statements( - self.s1.eq(ClockSignal()), - ResetSignal().eq(self.s2), - self.s3.eq(0), - self.s4.eq(ClockSignal("other")), - self.s5.eq(ResetSignal("other")), - ) - f.add_driver(self.s1, None) - f.add_driver(self.s2, None) - f.add_driver(self.s3, "sync") - - f = DomainRenamer("pix")(f) - self.assertRepr(f.statements, """ - ( - (eq (sig s1) (clk pix)) - (eq (rst pix) (sig s2)) - (eq (sig s3) (const 1'd0)) - (eq (sig s4) (clk other)) - (eq (sig s5) (rst other)) - ) - """) - self.assertEqual(f.drivers, { - None: SignalSet((self.s1, self.s2)), - "pix": SignalSet((self.s3,)), - }) - - def test_rename_multi(self): - f = Fragment() - f.add_statements( - self.s1.eq(ClockSignal()), - self.s2.eq(ResetSignal("other")), - ) - - f = DomainRenamer({"sync": "pix", "other": "pix2"})(f) - self.assertRepr(f.statements, """ - ( - (eq (sig s1) (clk pix)) - (eq (sig s2) (rst pix2)) - ) - """) - - def test_rename_cd(self): - cd_sync = ClockDomain() - cd_pix = ClockDomain() - - f = Fragment() - f.add_domains(cd_sync, cd_pix) - - f = DomainRenamer("ext")(f) - self.assertEqual(cd_sync.name, "ext") - self.assertEqual(f.domains, { - "ext": cd_sync, - "pix": cd_pix, - }) - - def test_rename_cd_preserves_allow_reset_less(self): - cd_pix = ClockDomain(reset_less=True) - - f = Fragment() - f.add_domains(cd_pix) - f.add_statements( - self.s1.eq(ResetSignal(allow_reset_less=True)), - ) - - f = DomainRenamer("pix")(f) - f = DomainLowerer()(f) - self.assertRepr(f.statements, """ - ( - (eq (sig s1) (const 1'd0)) - ) - """) - - - def test_rename_cd_subfragment(self): - cd_sync = ClockDomain() - cd_pix = ClockDomain() - - f1 = Fragment() - f1.add_domains(cd_sync, cd_pix) - f2 = Fragment() - f2.add_domains(cd_sync) - f1.add_subfragment(f2) - - f1 = DomainRenamer("ext")(f1) - self.assertEqual(cd_sync.name, "ext") - self.assertEqual(f1.domains, { - "ext": cd_sync, - "pix": cd_pix, - }) - - def test_rename_wrong_to_comb(self): - with self.assertRaisesRegex(ValueError, - r"^Domain 'sync' may not be renamed to 'comb'$"): - DomainRenamer("comb") - - def test_rename_wrong_from_comb(self): - with self.assertRaisesRegex(ValueError, - r"^Domain 'comb' may not be renamed$"): - DomainRenamer({"comb": "sync"}) - - -class DomainLowererTestCase(FHDLTestCase): - def setUp(self): - self.s = Signal() - - def test_lower_clk(self): - sync = ClockDomain() - f = Fragment() - f.add_domains(sync) - f.add_statements( - self.s.eq(ClockSignal("sync")) - ) - - f = DomainLowerer()(f) - self.assertRepr(f.statements, """ - ( - (eq (sig s) (sig clk)) - ) - """) - - def test_lower_rst(self): - sync = ClockDomain() - f = Fragment() - f.add_domains(sync) - f.add_statements( - self.s.eq(ResetSignal("sync")) - ) - - f = DomainLowerer()(f) - self.assertRepr(f.statements, """ - ( - (eq (sig s) (sig rst)) - ) - """) - - def test_lower_rst_reset_less(self): - sync = ClockDomain(reset_less=True) - f = Fragment() - f.add_domains(sync) - f.add_statements( - self.s.eq(ResetSignal("sync", allow_reset_less=True)) - ) - - f = DomainLowerer()(f) - self.assertRepr(f.statements, """ - ( - (eq (sig s) (const 1'd0)) - ) - """) - - def test_lower_drivers(self): - sync = ClockDomain() - pix = ClockDomain() - f = Fragment() - f.add_domains(sync, pix) - f.add_driver(ClockSignal("pix"), None) - f.add_driver(ResetSignal("pix"), "sync") - - f = DomainLowerer()(f) - self.assertEqual(f.drivers, { - None: SignalSet((pix.clk,)), - "sync": SignalSet((pix.rst,)) - }) - - def test_lower_wrong_domain(self): - f = Fragment() - f.add_statements( - self.s.eq(ClockSignal("xxx")) - ) - - with self.assertRaisesRegex(DomainError, - r"^Signal \(clk xxx\) refers to nonexistent domain 'xxx'$"): - DomainLowerer()(f) - - def test_lower_wrong_reset_less_domain(self): - sync = ClockDomain(reset_less=True) - f = Fragment() - f.add_domains(sync) - f.add_statements( - self.s.eq(ResetSignal("sync")) - ) - - with self.assertRaisesRegex(DomainError, - r"^Signal \(rst sync\) refers to reset of reset-less domain 'sync'$"): - DomainLowerer()(f) - - -class SampleLowererTestCase(FHDLTestCase): - def setUp(self): - self.i = Signal() - self.o1 = Signal() - self.o2 = Signal() - self.o3 = Signal() - - def test_lower_signal(self): - f = Fragment() - f.add_statements( - self.o1.eq(Sample(self.i, 2, "sync")), - self.o2.eq(Sample(self.i, 1, "sync")), - self.o3.eq(Sample(self.i, 1, "pix")), - ) - - f = SampleLowerer()(f) - self.assertRepr(f.statements, """ - ( - (eq (sig o1) (sig $sample$s$i$sync$2)) - (eq (sig o2) (sig $sample$s$i$sync$1)) - (eq (sig o3) (sig $sample$s$i$pix$1)) - (eq (sig $sample$s$i$sync$1) (sig i)) - (eq (sig $sample$s$i$sync$2) (sig $sample$s$i$sync$1)) - (eq (sig $sample$s$i$pix$1) (sig i)) - ) - """) - self.assertEqual(len(f.drivers["sync"]), 2) - self.assertEqual(len(f.drivers["pix"]), 1) - - def test_lower_const(self): - f = Fragment() - f.add_statements( - self.o1.eq(Sample(1, 2, "sync")), - ) - - f = SampleLowerer()(f) - self.assertRepr(f.statements, """ - ( - (eq (sig o1) (sig $sample$c$1$sync$2)) - (eq (sig $sample$c$1$sync$1) (const 1'd1)) - (eq (sig $sample$c$1$sync$2) (sig $sample$c$1$sync$1)) - ) - """) - self.assertEqual(len(f.drivers["sync"]), 2) - - -class SwitchCleanerTestCase(FHDLTestCase): - def test_clean(self): - a = Signal() - b = Signal() - c = Signal() - stmts = [ - Switch(a, { - 1: a.eq(0), - 0: [ - b.eq(1), - Switch(b, {1: [ - Switch(a|b, {}) - ]}) - ] - }) - ] - - self.assertRepr(SwitchCleaner()(stmts), """ - ( - (switch (sig a) - (case 1 - (eq (sig a) (const 1'd0))) - (case 0 - (eq (sig b) (const 1'd1))) - ) - ) - """) - - -class LHSGroupAnalyzerTestCase(FHDLTestCase): - def test_no_group_unrelated(self): - a = Signal() - b = Signal() - stmts = [ - a.eq(0), - b.eq(0), - ] - - groups = LHSGroupAnalyzer()(stmts) - self.assertEqual(list(groups.values()), [ - SignalSet((a,)), - SignalSet((b,)), - ]) - - def test_group_related(self): - a = Signal() - b = Signal() - stmts = [ - a.eq(0), - Cat(a, b).eq(0), - ] - - groups = LHSGroupAnalyzer()(stmts) - self.assertEqual(list(groups.values()), [ - SignalSet((a, b)), - ]) - - def test_no_loops(self): - a = Signal() - b = Signal() - stmts = [ - a.eq(0), - Cat(a, b).eq(0), - Cat(a, b).eq(0), - ] - - groups = LHSGroupAnalyzer()(stmts) - self.assertEqual(list(groups.values()), [ - SignalSet((a, b)), - ]) - - def test_switch(self): - a = Signal() - b = Signal() - stmts = [ - a.eq(0), - Switch(a, { - 1: b.eq(0), - }) - ] - - groups = LHSGroupAnalyzer()(stmts) - self.assertEqual(list(groups.values()), [ - SignalSet((a,)), - SignalSet((b,)), - ]) - - def test_lhs_empty(self): - stmts = [ - Cat().eq(0) - ] - - groups = LHSGroupAnalyzer()(stmts) - self.assertEqual(list(groups.values()), [ - ]) - - -class LHSGroupFilterTestCase(FHDLTestCase): - def test_filter(self): - a = Signal() - b = Signal() - c = Signal() - stmts = [ - Switch(a, { - 1: a.eq(0), - 0: [ - b.eq(1), - Switch(b, {1: []}) - ] - }) - ] - - self.assertRepr(LHSGroupFilter(SignalSet((a,)))(stmts), """ - ( - (switch (sig a) - (case 1 - (eq (sig a) (const 1'd0))) - (case 0 ) - ) - ) - """) - - def test_lhs_empty(self): - stmts = [ - Cat().eq(0) - ] - - self.assertRepr(LHSGroupFilter(SignalSet())(stmts), "()") - - -class ResetInserterTestCase(FHDLTestCase): - def setUp(self): - self.s1 = Signal() - self.s2 = Signal(reset=1) - self.s3 = Signal(reset=1, reset_less=True) - self.c1 = Signal() - - def test_reset_default(self): - f = Fragment() - f.add_statements( - self.s1.eq(1) - ) - f.add_driver(self.s1, "sync") - - f = ResetInserter(self.c1)(f) - self.assertRepr(f.statements, """ - ( - (eq (sig s1) (const 1'd1)) - (switch (sig c1) - (case 1 (eq (sig s1) (const 1'd0))) - ) - ) - """) - - def test_reset_cd(self): - f = Fragment() - f.add_statements( - self.s1.eq(1), - self.s2.eq(0), - ) - f.add_domains(ClockDomain("sync")) - f.add_driver(self.s1, "sync") - f.add_driver(self.s2, "pix") - - f = ResetInserter({"pix": self.c1})(f) - self.assertRepr(f.statements, """ - ( - (eq (sig s1) (const 1'd1)) - (eq (sig s2) (const 1'd0)) - (switch (sig c1) - (case 1 (eq (sig s2) (const 1'd1))) - ) - ) - """) - - def test_reset_value(self): - f = Fragment() - f.add_statements( - self.s2.eq(0) - ) - f.add_driver(self.s2, "sync") - - f = ResetInserter(self.c1)(f) - self.assertRepr(f.statements, """ - ( - (eq (sig s2) (const 1'd0)) - (switch (sig c1) - (case 1 (eq (sig s2) (const 1'd1))) - ) - ) - """) - - def test_reset_less(self): - f = Fragment() - f.add_statements( - self.s3.eq(0) - ) - f.add_driver(self.s3, "sync") - - f = ResetInserter(self.c1)(f) - self.assertRepr(f.statements, """ - ( - (eq (sig s3) (const 1'd0)) - (switch (sig c1) - (case 1 ) - ) - ) - """) - - -class EnableInserterTestCase(FHDLTestCase): - def setUp(self): - self.s1 = Signal() - self.s2 = Signal() - self.s3 = Signal() - self.c1 = Signal() - - def test_enable_default(self): - f = Fragment() - f.add_statements( - self.s1.eq(1) - ) - f.add_driver(self.s1, "sync") - - f = EnableInserter(self.c1)(f) - self.assertRepr(f.statements, """ - ( - (eq (sig s1) (const 1'd1)) - (switch (sig c1) - (case 0 (eq (sig s1) (sig s1))) - ) - ) - """) - - def test_enable_cd(self): - f = Fragment() - f.add_statements( - self.s1.eq(1), - self.s2.eq(0), - ) - f.add_driver(self.s1, "sync") - f.add_driver(self.s2, "pix") - - f = EnableInserter({"pix": self.c1})(f) - self.assertRepr(f.statements, """ - ( - (eq (sig s1) (const 1'd1)) - (eq (sig s2) (const 1'd0)) - (switch (sig c1) - (case 0 (eq (sig s2) (sig s2))) - ) - ) - """) - - def test_enable_subfragment(self): - f1 = Fragment() - f1.add_statements( - self.s1.eq(1) - ) - f1.add_driver(self.s1, "sync") - - f2 = Fragment() - f2.add_statements( - self.s2.eq(1) - ) - f2.add_driver(self.s2, "sync") - f1.add_subfragment(f2) - - f1 = EnableInserter(self.c1)(f1) - (f2, _), = f1.subfragments - self.assertRepr(f1.statements, """ - ( - (eq (sig s1) (const 1'd1)) - (switch (sig c1) - (case 0 (eq (sig s1) (sig s1))) - ) - ) - """) - self.assertRepr(f2.statements, """ - ( - (eq (sig s2) (const 1'd1)) - (switch (sig c1) - (case 0 (eq (sig s2) (sig s2))) - ) - ) - """) - - def test_enable_read_port(self): - mem = Memory(width=8, depth=4) - f = EnableInserter(self.c1)(mem.read_port(transparent=False)).elaborate(platform=None) - self.assertRepr(f.named_ports["EN"][0], """ - (m (sig c1) (sig mem_r_en) (const 1'd0)) - """) - - def test_enable_write_port(self): - mem = Memory(width=8, depth=4) - f = EnableInserter(self.c1)(mem.write_port()).elaborate(platform=None) - self.assertRepr(f.named_ports["EN"][0], """ - (m (sig c1) (cat (repl (slice (sig mem_w_en) 0:1) 8)) (const 8'd0)) - """) - - -class _MockElaboratable(Elaboratable): - def __init__(self): - self.s1 = Signal() - - def elaborate(self, platform): - f = Fragment() - f.add_statements( - self.s1.eq(1) - ) - f.add_driver(self.s1, "sync") - return f - - -class TransformedElaboratableTestCase(FHDLTestCase): - def setUp(self): - self.c1 = Signal() - self.c2 = Signal() - - def test_getattr(self): - e = _MockElaboratable() - te = EnableInserter(self.c1)(e) - - self.assertIs(te.s1, e.s1) - - def test_composition(self): - e = _MockElaboratable() - te1 = EnableInserter(self.c1)(e) - te2 = ResetInserter(self.c2)(te1) - - self.assertIsInstance(te1, TransformedElaboratable) - self.assertIs(te1, te2) - - f = Fragment.get(te2, None) - self.assertRepr(f.statements, """ - ( - (eq (sig s1) (const 1'd1)) - (switch (sig c1) - (case 0 (eq (sig s1) (sig s1))) - ) - (switch (sig c2) - (case 1 (eq (sig s1) (const 1'd0))) - ) - ) - """) - - -class MockUserValue(UserValue): - def __init__(self, lowered): - super().__init__() - self.lowered = lowered - - def lower(self): - return self.lowered - - -class UserValueTestCase(FHDLTestCase): - def setUp(self): - self.s = Signal() - self.c = Signal() - self.uv = MockUserValue(self.s) - - def test_lower(self): - sync = ClockDomain() - f = Fragment() - f.add_domains(sync) - f.add_statements( - self.uv.eq(1) - ) - for signal in self.uv._lhs_signals(): - f.add_driver(signal, "sync") - - f = ResetInserter(self.c)(f) - f = DomainLowerer()(f) - self.assertRepr(f.statements, """ - ( - (eq (sig s) (const 1'd1)) - (switch (sig c) - (case 1 (eq (sig s) (const 1'd0))) - ) - (switch (sig rst) - (case 1 (eq (sig s) (const 1'd0))) - ) - ) - """) - - -class UserValueRecursiveTestCase(UserValueTestCase): - def setUp(self): - self.s = Signal() - self.c = Signal() - self.uv = MockUserValue(MockUserValue(self.s)) - - # inherit the test_lower method from UserValueTestCase because the checks are the same diff --git a/nmigen/test/test_lib_cdc.py b/nmigen/test/test_lib_cdc.py deleted file mode 100644 index 3647bfa..0000000 --- a/nmigen/test/test_lib_cdc.py +++ /dev/null @@ -1,230 +0,0 @@ -# nmigen: UnusedElaboratable=no - -from .utils import * -from ..hdl import * -from ..back.pysim import * -from ..lib.cdc import * - - -class FFSynchronizerTestCase(FHDLTestCase): - def test_stages_wrong(self): - with self.assertRaisesRegex(TypeError, - r"^Synchronization stage count must be a positive integer, not 0$"): - FFSynchronizer(Signal(), Signal(), stages=0) - with self.assertRaisesRegex(ValueError, - r"^Synchronization stage count may not safely be less than 2$"): - FFSynchronizer(Signal(), Signal(), stages=1) - - def test_basic(self): - i = Signal() - o = Signal() - frag = FFSynchronizer(i, o) - - sim = Simulator(frag) - sim.add_clock(1e-6) - def process(): - self.assertEqual((yield o), 0) - yield i.eq(1) - yield Tick() - self.assertEqual((yield o), 0) - yield Tick() - self.assertEqual((yield o), 0) - yield Tick() - self.assertEqual((yield o), 1) - sim.add_process(process) - sim.run() - - def test_reset_value(self): - i = Signal(reset=1) - o = Signal() - frag = FFSynchronizer(i, o, reset=1) - - sim = Simulator(frag) - sim.add_clock(1e-6) - def process(): - self.assertEqual((yield o), 1) - yield i.eq(0) - yield Tick() - self.assertEqual((yield o), 1) - yield Tick() - self.assertEqual((yield o), 1) - yield Tick() - self.assertEqual((yield o), 0) - sim.add_process(process) - sim.run() - - -class AsyncFFSynchronizerTestCase(FHDLTestCase): - def test_stages_wrong(self): - with self.assertRaisesRegex(TypeError, - r"^Synchronization stage count must be a positive integer, not 0$"): - ResetSynchronizer(Signal(), stages=0) - with self.assertRaisesRegex(ValueError, - r"^Synchronization stage count may not safely be less than 2$"): - ResetSynchronizer(Signal(), stages=1) - - def test_edge_wrong(self): - with self.assertRaisesRegex(ValueError, - r"^AsyncFFSynchronizer async edge must be one of 'pos' or 'neg', not 'xxx'$"): - AsyncFFSynchronizer(Signal(), Signal(), o_domain="sync", async_edge="xxx") - - def test_pos_edge(self): - i = Signal() - o = Signal() - m = Module() - m.domains += ClockDomain("sync") - m.submodules += AsyncFFSynchronizer(i, o) - - sim = Simulator(m) - sim.add_clock(1e-6) - def process(): - # initial reset - self.assertEqual((yield i), 0) - self.assertEqual((yield o), 1) - yield Tick(); yield Delay(1e-8) - self.assertEqual((yield o), 1) - yield Tick(); yield Delay(1e-8) - self.assertEqual((yield o), 0) - yield Tick(); yield Delay(1e-8) - self.assertEqual((yield o), 0) - yield Tick(); yield Delay(1e-8) - - yield i.eq(1) - yield Delay(1e-8) - self.assertEqual((yield o), 1) - yield Tick(); yield Delay(1e-8) - self.assertEqual((yield o), 1) - yield i.eq(0) - yield Tick(); yield Delay(1e-8) - self.assertEqual((yield o), 1) - yield Tick(); yield Delay(1e-8) - self.assertEqual((yield o), 0) - yield Tick(); yield Delay(1e-8) - self.assertEqual((yield o), 0) - yield Tick(); yield Delay(1e-8) - sim.add_process(process) - with sim.write_vcd("test.vcd"): - sim.run() - - def test_neg_edge(self): - i = Signal(reset=1) - o = Signal() - m = Module() - m.domains += ClockDomain("sync") - m.submodules += AsyncFFSynchronizer(i, o, async_edge="neg") - - sim = Simulator(m) - sim.add_clock(1e-6) - def process(): - # initial reset - self.assertEqual((yield i), 1) - self.assertEqual((yield o), 1) - yield Tick(); yield Delay(1e-8) - self.assertEqual((yield o), 1) - yield Tick(); yield Delay(1e-8) - self.assertEqual((yield o), 0) - yield Tick(); yield Delay(1e-8) - self.assertEqual((yield o), 0) - yield Tick(); yield Delay(1e-8) - - yield i.eq(0) - yield Delay(1e-8) - self.assertEqual((yield o), 1) - yield Tick(); yield Delay(1e-8) - self.assertEqual((yield o), 1) - yield i.eq(1) - yield Tick(); yield Delay(1e-8) - self.assertEqual((yield o), 1) - yield Tick(); yield Delay(1e-8) - self.assertEqual((yield o), 0) - yield Tick(); yield Delay(1e-8) - self.assertEqual((yield o), 0) - yield Tick(); yield Delay(1e-8) - sim.add_process(process) - with sim.write_vcd("test.vcd"): - sim.run() - - -class ResetSynchronizerTestCase(FHDLTestCase): - def test_stages_wrong(self): - with self.assertRaisesRegex(TypeError, - r"^Synchronization stage count must be a positive integer, not 0$"): - ResetSynchronizer(Signal(), stages=0) - with self.assertRaisesRegex(ValueError, - r"^Synchronization stage count may not safely be less than 2$"): - ResetSynchronizer(Signal(), stages=1) - - def test_basic(self): - arst = Signal() - m = Module() - m.domains += ClockDomain("sync") - m.submodules += ResetSynchronizer(arst) - s = Signal(reset=1) - m.d.sync += s.eq(0) - - sim = Simulator(m) - sim.add_clock(1e-6) - def process(): - # initial reset - self.assertEqual((yield s), 1) - yield Tick(); yield Delay(1e-8) - self.assertEqual((yield s), 1) - yield Tick(); yield Delay(1e-8) - self.assertEqual((yield s), 1) - yield Tick(); yield Delay(1e-8) - self.assertEqual((yield s), 0) - yield Tick(); yield Delay(1e-8) - - yield arst.eq(1) - yield Delay(1e-8) - self.assertEqual((yield s), 0) - yield Tick(); yield Delay(1e-8) - self.assertEqual((yield s), 1) - yield arst.eq(0) - yield Tick(); yield Delay(1e-8) - self.assertEqual((yield s), 1) - yield Tick(); yield Delay(1e-8) - self.assertEqual((yield s), 1) - yield Tick(); yield Delay(1e-8) - self.assertEqual((yield s), 0) - yield Tick(); yield Delay(1e-8) - sim.add_process(process) - with sim.write_vcd("test.vcd"): - sim.run() - - -# TODO: test with distinct clocks -class PulseSynchronizerTestCase(FHDLTestCase): - def test_stages_wrong(self): - with self.assertRaisesRegex(TypeError, - r"^Synchronization stage count must be a positive integer, not 0$"): - PulseSynchronizer("w", "r", stages=0) - with self.assertRaisesRegex(ValueError, - r"^Synchronization stage count may not safely be less than 2$"): - PulseSynchronizer("w", "r", stages=1) - - def test_smoke(self): - m = Module() - m.domains += ClockDomain("sync") - ps = m.submodules.dut = PulseSynchronizer("sync", "sync") - - sim = Simulator(m) - sim.add_clock(1e-6) - def process(): - yield ps.i.eq(0) - # TODO: think about reset - for n in range(5): - yield Tick() - # Make sure no pulses are generated in quiescent state - for n in range(3): - yield Tick() - self.assertEqual((yield ps.o), 0) - # Check conservation of pulses - accum = 0 - for n in range(10): - yield ps.i.eq(1 if n < 4 else 0) - yield Tick() - accum += yield ps.o - self.assertEqual(accum, 4) - sim.add_process(process) - sim.run() diff --git a/nmigen/test/test_lib_coding.py b/nmigen/test/test_lib_coding.py deleted file mode 100644 index cf582e3..0000000 --- a/nmigen/test/test_lib_coding.py +++ /dev/null @@ -1,126 +0,0 @@ -from .utils import * -from ..hdl import * -from ..asserts import * -from ..back.pysim import * -from ..lib.coding import * - - -class EncoderTestCase(FHDLTestCase): - def test_basic(self): - enc = Encoder(4) - def process(): - self.assertEqual((yield enc.n), 1) - self.assertEqual((yield enc.o), 0) - - yield enc.i.eq(0b0001) - yield Settle() - self.assertEqual((yield enc.n), 0) - self.assertEqual((yield enc.o), 0) - - yield enc.i.eq(0b0100) - yield Settle() - self.assertEqual((yield enc.n), 0) - self.assertEqual((yield enc.o), 2) - - yield enc.i.eq(0b0110) - yield Settle() - self.assertEqual((yield enc.n), 1) - self.assertEqual((yield enc.o), 0) - - sim = Simulator(enc) - sim.add_process(process) - sim.run() - - -class PriorityEncoderTestCase(FHDLTestCase): - def test_basic(self): - enc = PriorityEncoder(4) - def process(): - self.assertEqual((yield enc.n), 1) - self.assertEqual((yield enc.o), 0) - - yield enc.i.eq(0b0001) - yield Settle() - self.assertEqual((yield enc.n), 0) - self.assertEqual((yield enc.o), 0) - - yield enc.i.eq(0b0100) - yield Settle() - self.assertEqual((yield enc.n), 0) - self.assertEqual((yield enc.o), 2) - - yield enc.i.eq(0b0110) - yield Settle() - self.assertEqual((yield enc.n), 0) - self.assertEqual((yield enc.o), 1) - - sim = Simulator(enc) - sim.add_process(process) - sim.run() - - -class DecoderTestCase(FHDLTestCase): - def test_basic(self): - dec = Decoder(4) - def process(): - self.assertEqual((yield dec.o), 0b0001) - - yield dec.i.eq(1) - yield Settle() - self.assertEqual((yield dec.o), 0b0010) - - yield dec.i.eq(3) - yield Settle() - self.assertEqual((yield dec.o), 0b1000) - - yield dec.n.eq(1) - yield Settle() - self.assertEqual((yield dec.o), 0b0000) - - sim = Simulator(dec) - sim.add_process(process) - sim.run() - - -class ReversibleSpec(Elaboratable): - def __init__(self, encoder_cls, decoder_cls, args): - self.encoder_cls = encoder_cls - self.decoder_cls = decoder_cls - self.coder_args = args - - def elaborate(self, platform): - m = Module() - enc, dec = self.encoder_cls(*self.coder_args), self.decoder_cls(*self.coder_args) - m.submodules += enc, dec - m.d.comb += [ - dec.i.eq(enc.o), - Assert(enc.i == dec.o) - ] - return m - - -class HammingDistanceSpec(Elaboratable): - def __init__(self, distance, encoder_cls, args): - self.distance = distance - self.encoder_cls = encoder_cls - self.coder_args = args - - def elaborate(self, platform): - m = Module() - enc1, enc2 = self.encoder_cls(*self.coder_args), self.encoder_cls(*self.coder_args) - m.submodules += enc1, enc2 - m.d.comb += [ - Assume(enc1.i + 1 == enc2.i), - Assert(sum(enc1.o ^ enc2.o) == self.distance) - ] - return m - - -class GrayCoderTestCase(FHDLTestCase): - def test_reversible(self): - spec = ReversibleSpec(encoder_cls=GrayEncoder, decoder_cls=GrayDecoder, args=(16,)) - self.assertFormal(spec, mode="prove") - - def test_distance(self): - spec = HammingDistanceSpec(distance=1, encoder_cls=GrayEncoder, args=(16,)) - self.assertFormal(spec, mode="prove") diff --git a/nmigen/test/test_lib_fifo.py b/nmigen/test/test_lib_fifo.py deleted file mode 100644 index e76c0b0..0000000 --- a/nmigen/test/test_lib_fifo.py +++ /dev/null @@ -1,281 +0,0 @@ -# nmigen: UnusedElaboratable=no - -from .utils import * -from ..hdl import * -from ..asserts import * -from ..back.pysim import * -from ..lib.fifo import * - - -class FIFOTestCase(FHDLTestCase): - def test_depth_wrong(self): - with self.assertRaisesRegex(TypeError, - r"^FIFO width must be a non-negative integer, not -1$"): - FIFOInterface(width=-1, depth=8, fwft=True) - with self.assertRaisesRegex(TypeError, - r"^FIFO depth must be a non-negative integer, not -1$"): - FIFOInterface(width=8, depth=-1, fwft=True) - - def test_sync_depth(self): - self.assertEqual(SyncFIFO(width=8, depth=0).depth, 0) - self.assertEqual(SyncFIFO(width=8, depth=1).depth, 1) - self.assertEqual(SyncFIFO(width=8, depth=2).depth, 2) - - def test_sync_buffered_depth(self): - self.assertEqual(SyncFIFOBuffered(width=8, depth=0).depth, 0) - self.assertEqual(SyncFIFOBuffered(width=8, depth=1).depth, 1) - self.assertEqual(SyncFIFOBuffered(width=8, depth=2).depth, 2) - - def test_async_depth(self): - self.assertEqual(AsyncFIFO(width=8, depth=0 ).depth, 0) - self.assertEqual(AsyncFIFO(width=8, depth=1 ).depth, 1) - self.assertEqual(AsyncFIFO(width=8, depth=2 ).depth, 2) - self.assertEqual(AsyncFIFO(width=8, depth=3 ).depth, 4) - self.assertEqual(AsyncFIFO(width=8, depth=4 ).depth, 4) - self.assertEqual(AsyncFIFO(width=8, depth=15).depth, 16) - self.assertEqual(AsyncFIFO(width=8, depth=16).depth, 16) - self.assertEqual(AsyncFIFO(width=8, depth=17).depth, 32) - - def test_async_depth_wrong(self): - with self.assertRaisesRegex(ValueError, - (r"^AsyncFIFO only supports depths that are powers of 2; " - r"requested exact depth 15 is not$")): - AsyncFIFO(width=8, depth=15, exact_depth=True) - - def test_async_buffered_depth(self): - self.assertEqual(AsyncFIFOBuffered(width=8, depth=0 ).depth, 0) - self.assertEqual(AsyncFIFOBuffered(width=8, depth=1 ).depth, 2) - self.assertEqual(AsyncFIFOBuffered(width=8, depth=2 ).depth, 2) - self.assertEqual(AsyncFIFOBuffered(width=8, depth=3 ).depth, 3) - self.assertEqual(AsyncFIFOBuffered(width=8, depth=4 ).depth, 5) - self.assertEqual(AsyncFIFOBuffered(width=8, depth=15).depth, 17) - self.assertEqual(AsyncFIFOBuffered(width=8, depth=16).depth, 17) - self.assertEqual(AsyncFIFOBuffered(width=8, depth=17).depth, 17) - self.assertEqual(AsyncFIFOBuffered(width=8, depth=18).depth, 33) - - def test_async_buffered_depth_wrong(self): - with self.assertRaisesRegex(ValueError, - (r"^AsyncFIFOBuffered only supports depths that are one higher than powers of 2; " - r"requested exact depth 16 is not$")): - AsyncFIFOBuffered(width=8, depth=16, exact_depth=True) - -class FIFOModel(Elaboratable, FIFOInterface): - """ - Non-synthesizable first-in first-out queue, implemented naively as a chain of registers. - """ - def __init__(self, *, width, depth, fwft, r_domain, w_domain): - super().__init__(width=width, depth=depth, fwft=fwft) - - self.r_domain = r_domain - self.w_domain = w_domain - - self.level = Signal(range(self.depth + 1)) - self.r_level = Signal(range(self.depth + 1)) - self.w_level = Signal(range(self.depth + 1)) - - def elaborate(self, platform): - m = Module() - - storage = Memory(width=self.width, depth=self.depth) - w_port = m.submodules.w_port = storage.write_port(domain=self.w_domain) - r_port = m.submodules.r_port = storage.read_port (domain="comb") - - produce = Signal(range(self.depth)) - consume = Signal(range(self.depth)) - - m.d.comb += self.r_rdy.eq(self.level > 0) - m.d.comb += r_port.addr.eq((consume + 1) % self.depth) - if self.fwft: - m.d.comb += self.r_data.eq(r_port.data) - with m.If(self.r_en & self.r_rdy): - if not self.fwft: - m.d[self.r_domain] += self.r_data.eq(r_port.data) - m.d[self.r_domain] += consume.eq(r_port.addr) - - m.d.comb += self.w_rdy.eq(self.level < self.depth) - m.d.comb += w_port.data.eq(self.w_data) - with m.If(self.w_en & self.w_rdy): - m.d.comb += w_port.addr.eq((produce + 1) % self.depth) - m.d.comb += w_port.en.eq(1) - m.d[self.w_domain] += produce.eq(w_port.addr) - - with m.If(ResetSignal(self.r_domain) | ResetSignal(self.w_domain)): - m.d.sync += self.level.eq(0) - with m.Else(): - m.d.sync += self.level.eq(self.level - + (self.w_rdy & self.w_en) - - (self.r_rdy & self.r_en)) - - m.d.comb += [ - self.r_level.eq(self.level), - self.w_level.eq(self.level), - ] - m.d.comb += Assert(ResetSignal(self.r_domain) == ResetSignal(self.w_domain)) - - return m - - -class FIFOModelEquivalenceSpec(Elaboratable): - """ - The first-in first-out queue model equivalence specification: for any inputs and control - signals, the behavior of the implementation under test exactly matches the ideal model, - except for behavior not defined by the model. - """ - def __init__(self, fifo, r_domain, w_domain): - self.fifo = fifo - - self.r_domain = r_domain - self.w_domain = w_domain - - def elaborate(self, platform): - m = Module() - m.submodules.dut = dut = self.fifo - m.submodules.gold = gold = FIFOModel(width=dut.width, depth=dut.depth, fwft=dut.fwft, - r_domain=self.r_domain, w_domain=self.w_domain) - - m.d.comb += [ - gold.r_en.eq(dut.r_rdy & dut.r_en), - gold.w_en.eq(dut.w_en), - gold.w_data.eq(dut.w_data), - ] - - m.d.comb += Assert(dut.r_rdy.implies(gold.r_rdy)) - m.d.comb += Assert(dut.w_rdy.implies(gold.w_rdy)) - m.d.comb += Assert(dut.r_level == gold.r_level) - m.d.comb += Assert(dut.w_level == gold.w_level) - - if dut.fwft: - m.d.comb += Assert(dut.r_rdy - .implies(dut.r_data == gold.r_data)) - else: - m.d.comb += Assert((Past(dut.r_rdy, domain=self.r_domain) & - Past(dut.r_en, domain=self.r_domain)) - .implies(dut.r_data == gold.r_data)) - - return m - - -class FIFOContractSpec(Elaboratable): - """ - The first-in first-out queue contract specification: if two elements are written to the queue - consecutively, they must be read out consecutively at some later point, no matter all other - circumstances, with the exception of reset. - """ - def __init__(self, fifo, *, r_domain, w_domain, bound): - self.fifo = fifo - self.r_domain = r_domain - self.w_domain = w_domain - self.bound = bound - - def elaborate(self, platform): - m = Module() - m.submodules.dut = fifo = self.fifo - - m.domains += ClockDomain("sync") - m.d.comb += ResetSignal().eq(0) - if self.w_domain != "sync": - m.domains += ClockDomain(self.w_domain) - m.d.comb += ResetSignal(self.w_domain).eq(0) - if self.r_domain != "sync": - m.domains += ClockDomain(self.r_domain) - m.d.comb += ResetSignal(self.r_domain).eq(0) - - entry_1 = AnyConst(fifo.width) - entry_2 = AnyConst(fifo.width) - - with m.FSM(domain=self.w_domain) as write_fsm: - with m.State("WRITE-1"): - with m.If(fifo.w_rdy): - m.d.comb += [ - fifo.w_data.eq(entry_1), - fifo.w_en.eq(1) - ] - m.next = "WRITE-2" - with m.State("WRITE-2"): - with m.If(fifo.w_rdy): - m.d.comb += [ - fifo.w_data.eq(entry_2), - fifo.w_en.eq(1) - ] - m.next = "DONE" - with m.State("DONE"): - pass - - with m.FSM(domain=self.r_domain) as read_fsm: - read_1 = Signal(fifo.width) - read_2 = Signal(fifo.width) - with m.State("READ"): - m.d.comb += fifo.r_en.eq(1) - if fifo.fwft: - r_rdy = fifo.r_rdy - else: - r_rdy = Past(fifo.r_rdy, domain=self.r_domain) - with m.If(r_rdy): - m.d.sync += [ - read_1.eq(read_2), - read_2.eq(fifo.r_data), - ] - with m.If((read_1 == entry_1) & (read_2 == entry_2)): - m.next = "DONE" - with m.State("DONE"): - pass - - with m.If(Initial()): - m.d.comb += Assume(write_fsm.ongoing("WRITE-1")) - m.d.comb += Assume(read_fsm.ongoing("READ")) - with m.If(Past(Initial(), self.bound - 1)): - m.d.comb += Assert(read_fsm.ongoing("DONE")) - - with m.If(ResetSignal(domain=self.w_domain)): - m.d.comb += Assert(~fifo.r_rdy) - - if self.w_domain != "sync" or self.r_domain != "sync": - m.d.comb += Assume(Rose(ClockSignal(self.w_domain)) | - Rose(ClockSignal(self.r_domain))) - - return m - - -class FIFOFormalCase(FHDLTestCase): - def check_sync_fifo(self, fifo): - self.assertFormal(FIFOModelEquivalenceSpec(fifo, r_domain="sync", w_domain="sync"), - mode="bmc", depth=fifo.depth + 1) - self.assertFormal(FIFOContractSpec(fifo, r_domain="sync", w_domain="sync", - bound=fifo.depth * 2 + 1), - mode="hybrid", depth=fifo.depth * 2 + 1) - - def test_sync_fwft_pot(self): - self.check_sync_fifo(SyncFIFO(width=8, depth=4, fwft=True)) - - def test_sync_fwft_npot(self): - self.check_sync_fifo(SyncFIFO(width=8, depth=5, fwft=True)) - - def test_sync_not_fwft_pot(self): - self.check_sync_fifo(SyncFIFO(width=8, depth=4, fwft=False)) - - def test_sync_not_fwft_npot(self): - self.check_sync_fifo(SyncFIFO(width=8, depth=5, fwft=False)) - - def test_sync_buffered_pot(self): - self.check_sync_fifo(SyncFIFOBuffered(width=8, depth=4)) - - def test_sync_buffered_potp1(self): - self.check_sync_fifo(SyncFIFOBuffered(width=8, depth=5)) - - def test_sync_buffered_potm1(self): - self.check_sync_fifo(SyncFIFOBuffered(width=8, depth=3)) - - def check_async_fifo(self, fifo): - # TODO: properly doing model equivalence checking on this likely requires multiclock, - # which is not really documented nor is it clear how to use it. - # self.assertFormal(FIFOModelEquivalenceSpec(fifo, r_domain="read", w_domain="write"), - # mode="bmc", depth=fifo.depth * 3 + 1) - self.assertFormal(FIFOContractSpec(fifo, r_domain="read", w_domain="write", - bound=fifo.depth * 4 + 1), - mode="hybrid", depth=fifo.depth * 4 + 1) - - def test_async(self): - self.check_async_fifo(AsyncFIFO(width=8, depth=4)) - - def test_async_buffered(self): - self.check_async_fifo(AsyncFIFOBuffered(width=8, depth=4)) diff --git a/nmigen/test/test_lib_io.py b/nmigen/test/test_lib_io.py deleted file mode 100644 index 156150e..0000000 --- a/nmigen/test/test_lib_io.py +++ /dev/null @@ -1,208 +0,0 @@ -from .utils import * -from ..hdl import * -from ..hdl.rec import * -from ..back.pysim import * -from ..lib.io import * - - -class PinLayoutTestCase(FHDLTestCase): - def assertLayoutEqual(self, layout, expected): - casted_layout = {} - for name, (shape, dir) in layout.items(): - casted_layout[name] = (Shape.cast(shape), dir) - - self.assertEqual(casted_layout, expected) - - -class PinLayoutCombTestCase(PinLayoutTestCase): - def test_pin_layout_i(self): - layout_1 = pin_layout(1, dir="i") - self.assertLayoutEqual(layout_1.fields, { - "i": ((1, False), DIR_NONE), - }) - - layout_2 = pin_layout(2, dir="i") - self.assertLayoutEqual(layout_2.fields, { - "i": ((2, False), DIR_NONE), - }) - - def test_pin_layout_o(self): - layout_1 = pin_layout(1, dir="o") - self.assertLayoutEqual(layout_1.fields, { - "o": ((1, False), DIR_NONE), - }) - - layout_2 = pin_layout(2, dir="o") - self.assertLayoutEqual(layout_2.fields, { - "o": ((2, False), DIR_NONE), - }) - - def test_pin_layout_oe(self): - layout_1 = pin_layout(1, dir="oe") - self.assertLayoutEqual(layout_1.fields, { - "o": ((1, False), DIR_NONE), - "oe": ((1, False), DIR_NONE), - }) - - layout_2 = pin_layout(2, dir="oe") - self.assertLayoutEqual(layout_2.fields, { - "o": ((2, False), DIR_NONE), - "oe": ((1, False), DIR_NONE), - }) - - def test_pin_layout_io(self): - layout_1 = pin_layout(1, dir="io") - self.assertLayoutEqual(layout_1.fields, { - "i": ((1, False), DIR_NONE), - "o": ((1, False), DIR_NONE), - "oe": ((1, False), DIR_NONE), - }) - - layout_2 = pin_layout(2, dir="io") - self.assertLayoutEqual(layout_2.fields, { - "i": ((2, False), DIR_NONE), - "o": ((2, False), DIR_NONE), - "oe": ((1, False), DIR_NONE), - }) - - -class PinLayoutSDRTestCase(PinLayoutTestCase): - def test_pin_layout_i(self): - layout_1 = pin_layout(1, dir="i", xdr=1) - self.assertLayoutEqual(layout_1.fields, { - "i_clk": ((1, False), DIR_NONE), - "i": ((1, False), DIR_NONE), - }) - - layout_2 = pin_layout(2, dir="i", xdr=1) - self.assertLayoutEqual(layout_2.fields, { - "i_clk": ((1, False), DIR_NONE), - "i": ((2, False), DIR_NONE), - }) - - def test_pin_layout_o(self): - layout_1 = pin_layout(1, dir="o", xdr=1) - self.assertLayoutEqual(layout_1.fields, { - "o_clk": ((1, False), DIR_NONE), - "o": ((1, False), DIR_NONE), - }) - - layout_2 = pin_layout(2, dir="o", xdr=1) - self.assertLayoutEqual(layout_2.fields, { - "o_clk": ((1, False), DIR_NONE), - "o": ((2, False), DIR_NONE), - }) - - def test_pin_layout_oe(self): - layout_1 = pin_layout(1, dir="oe", xdr=1) - self.assertLayoutEqual(layout_1.fields, { - "o_clk": ((1, False), DIR_NONE), - "o": ((1, False), DIR_NONE), - "oe": ((1, False), DIR_NONE), - }) - - layout_2 = pin_layout(2, dir="oe", xdr=1) - self.assertLayoutEqual(layout_2.fields, { - "o_clk": ((1, False), DIR_NONE), - "o": ((2, False), DIR_NONE), - "oe": ((1, False), DIR_NONE), - }) - - def test_pin_layout_io(self): - layout_1 = pin_layout(1, dir="io", xdr=1) - self.assertLayoutEqual(layout_1.fields, { - "i_clk": ((1, False), DIR_NONE), - "i": ((1, False), DIR_NONE), - "o_clk": ((1, False), DIR_NONE), - "o": ((1, False), DIR_NONE), - "oe": ((1, False), DIR_NONE), - }) - - layout_2 = pin_layout(2, dir="io", xdr=1) - self.assertLayoutEqual(layout_2.fields, { - "i_clk": ((1, False), DIR_NONE), - "i": ((2, False), DIR_NONE), - "o_clk": ((1, False), DIR_NONE), - "o": ((2, False), DIR_NONE), - "oe": ((1, False), DIR_NONE), - }) - - -class PinLayoutDDRTestCase(PinLayoutTestCase): - def test_pin_layout_i(self): - layout_1 = pin_layout(1, dir="i", xdr=2) - self.assertLayoutEqual(layout_1.fields, { - "i_clk": ((1, False), DIR_NONE), - "i0": ((1, False), DIR_NONE), - "i1": ((1, False), DIR_NONE), - }) - - layout_2 = pin_layout(2, dir="i", xdr=2) - self.assertLayoutEqual(layout_2.fields, { - "i_clk": ((1, False), DIR_NONE), - "i0": ((2, False), DIR_NONE), - "i1": ((2, False), DIR_NONE), - }) - - def test_pin_layout_o(self): - layout_1 = pin_layout(1, dir="o", xdr=2) - self.assertLayoutEqual(layout_1.fields, { - "o_clk": ((1, False), DIR_NONE), - "o0": ((1, False), DIR_NONE), - "o1": ((1, False), DIR_NONE), - }) - - layout_2 = pin_layout(2, dir="o", xdr=2) - self.assertLayoutEqual(layout_2.fields, { - "o_clk": ((1, False), DIR_NONE), - "o0": ((2, False), DIR_NONE), - "o1": ((2, False), DIR_NONE), - }) - - def test_pin_layout_oe(self): - layout_1 = pin_layout(1, dir="oe", xdr=2) - self.assertLayoutEqual(layout_1.fields, { - "o_clk": ((1, False), DIR_NONE), - "o0": ((1, False), DIR_NONE), - "o1": ((1, False), DIR_NONE), - "oe": ((1, False), DIR_NONE), - }) - - layout_2 = pin_layout(2, dir="oe", xdr=2) - self.assertLayoutEqual(layout_2.fields, { - "o_clk": ((1, False), DIR_NONE), - "o0": ((2, False), DIR_NONE), - "o1": ((2, False), DIR_NONE), - "oe": ((1, False), DIR_NONE), - }) - - def test_pin_layout_io(self): - layout_1 = pin_layout(1, dir="io", xdr=2) - self.assertLayoutEqual(layout_1.fields, { - "i_clk": ((1, False), DIR_NONE), - "i0": ((1, False), DIR_NONE), - "i1": ((1, False), DIR_NONE), - "o_clk": ((1, False), DIR_NONE), - "o0": ((1, False), DIR_NONE), - "o1": ((1, False), DIR_NONE), - "oe": ((1, False), DIR_NONE), - }) - - layout_2 = pin_layout(2, dir="io", xdr=2) - self.assertLayoutEqual(layout_2.fields, { - "i_clk": ((1, False), DIR_NONE), - "i0": ((2, False), DIR_NONE), - "i1": ((2, False), DIR_NONE), - "o_clk": ((1, False), DIR_NONE), - "o0": ((2, False), DIR_NONE), - "o1": ((2, False), DIR_NONE), - "oe": ((1, False), DIR_NONE), - }) - - -class PinTestCase(FHDLTestCase): - def test_attributes(self): - pin = Pin(2, dir="io", xdr=2) - self.assertEqual(pin.width, 2) - self.assertEqual(pin.dir, "io") - self.assertEqual(pin.xdr, 2) diff --git a/nmigen/test/test_lib_scheduler.py b/nmigen/test/test_lib_scheduler.py deleted file mode 100644 index 9ae4cbb..0000000 --- a/nmigen/test/test_lib_scheduler.py +++ /dev/null @@ -1,93 +0,0 @@ -# nmigen: UnusedElaboratable=no -import unittest -from .utils import * -from ..hdl import * -from ..asserts import * -from ..sim.pysim import * -from ..lib.scheduler import * - - -class RoundRobinTestCase(unittest.TestCase): - def test_count(self): - dut = RoundRobin(count=32) - self.assertEqual(dut.count, 32) - self.assertEqual(len(dut.requests), 32) - self.assertEqual(len(dut.grant), 5) - - def test_wrong_count(self): - with self.assertRaisesRegex(ValueError, r"Count must be a non-negative integer, not 'foo'"): - dut = RoundRobin(count="foo") - with self.assertRaisesRegex(ValueError, r"Count must be a non-negative integer, not -1"): - dut = RoundRobin(count=-1) - - -class RoundRobinSimulationTestCase(unittest.TestCase): - def test_count_one(self): - dut = RoundRobin(count=1) - sim = Simulator(dut) - def process(): - yield dut.requests.eq(0) - yield; yield Delay(1e-8) - self.assertEqual((yield dut.grant), 0) - self.assertFalse((yield dut.valid)) - - yield dut.requests.eq(1) - yield; yield Delay(1e-8) - self.assertEqual((yield dut.grant), 0) - self.assertTrue((yield dut.valid)) - sim.add_sync_process(process) - sim.add_clock(1e-6) - with sim.write_vcd("test.vcd"): - sim.run() - - def test_transitions(self): - dut = RoundRobin(count=3) - sim = Simulator(dut) - def process(): - yield dut.requests.eq(0b111) - yield; yield Delay(1e-8) - self.assertEqual((yield dut.grant), 1) - self.assertTrue((yield dut.valid)) - - yield dut.requests.eq(0b110) - yield; yield Delay(1e-8) - self.assertEqual((yield dut.grant), 2) - self.assertTrue((yield dut.valid)) - - yield dut.requests.eq(0b010) - yield; yield Delay(1e-8) - self.assertEqual((yield dut.grant), 1) - self.assertTrue((yield dut.valid)) - - yield dut.requests.eq(0b011) - yield; yield Delay(1e-8) - self.assertEqual((yield dut.grant), 0) - self.assertTrue((yield dut.valid)) - - yield dut.requests.eq(0b001) - yield; yield Delay(1e-8) - self.assertEqual((yield dut.grant), 0) - self.assertTrue((yield dut.valid)) - - yield dut.requests.eq(0b101) - yield; yield Delay(1e-8) - self.assertEqual((yield dut.grant), 2) - self.assertTrue((yield dut.valid)) - - yield dut.requests.eq(0b100) - yield; yield Delay(1e-8) - self.assertEqual((yield dut.grant), 2) - self.assertTrue((yield dut.valid)) - - yield dut.requests.eq(0b000) - yield; yield Delay(1e-8) - self.assertFalse((yield dut.valid)) - - yield dut.requests.eq(0b001) - yield; yield Delay(1e-8) - self.assertEqual((yield dut.grant), 0) - self.assertTrue((yield dut.valid)) - sim.add_sync_process(process) - sim.add_clock(1e-6) - with sim.write_vcd("test.vcd"): - sim.run() diff --git a/nmigen/test/test_sim.py b/nmigen/test/test_sim.py deleted file mode 100644 index 94424f7..0000000 --- a/nmigen/test/test_sim.py +++ /dev/null @@ -1,798 +0,0 @@ -import os -from contextlib import contextmanager - -from .utils import * -from .._utils import flatten, union -from ..hdl.ast import * -from ..hdl.cd import * -from ..hdl.mem import * -from ..hdl.rec import * -from ..hdl.dsl import * -from ..hdl.ir import * -from ..back.pysim import * - - -class SimulatorUnitTestCase(FHDLTestCase): - def assertStatement(self, stmt, inputs, output, reset=0): - inputs = [Value.cast(i) for i in inputs] - output = Value.cast(output) - - isigs = [Signal(i.shape(), name=n) for i, n in zip(inputs, "abcd")] - osig = Signal(output.shape(), name="y", reset=reset) - - stmt = stmt(osig, *isigs) - frag = Fragment() - frag.add_statements(stmt) - for signal in flatten(s._lhs_signals() for s in Statement.cast(stmt)): - frag.add_driver(signal) - - sim = Simulator(frag) - def process(): - for isig, input in zip(isigs, inputs): - yield isig.eq(input) - yield Settle() - self.assertEqual((yield osig), output.value) - sim.add_process(process) - with sim.write_vcd("test.vcd", "test.gtkw", traces=[*isigs, osig]): - sim.run() - - def test_invert(self): - stmt = lambda y, a: y.eq(~a) - self.assertStatement(stmt, [C(0b0000, 4)], C(0b1111, 4)) - self.assertStatement(stmt, [C(0b1010, 4)], C(0b0101, 4)) - self.assertStatement(stmt, [C(0, 4)], C(-1, 4)) - - def test_neg(self): - stmt = lambda y, a: y.eq(-a) - self.assertStatement(stmt, [C(0b0000, 4)], C(0b0000, 4)) - self.assertStatement(stmt, [C(0b0001, 4)], C(0b1111, 4)) - self.assertStatement(stmt, [C(0b1010, 4)], C(0b0110, 4)) - self.assertStatement(stmt, [C(1, 4)], C(-1, 4)) - self.assertStatement(stmt, [C(5, 4)], C(-5, 4)) - - def test_bool(self): - stmt = lambda y, a: y.eq(a.bool()) - self.assertStatement(stmt, [C(0, 4)], C(0)) - self.assertStatement(stmt, [C(1, 4)], C(1)) - self.assertStatement(stmt, [C(2, 4)], C(1)) - - def test_as_unsigned(self): - stmt = lambda y, a, b: y.eq(a.as_unsigned() == b) - self.assertStatement(stmt, [C(0b01, signed(2)), C(0b0001, unsigned(4))], C(1)) - self.assertStatement(stmt, [C(0b11, signed(2)), C(0b0011, unsigned(4))], C(1)) - - def test_as_signed(self): - stmt = lambda y, a, b: y.eq(a.as_signed() == b) - self.assertStatement(stmt, [C(0b01, unsigned(2)), C(0b0001, signed(4))], C(1)) - self.assertStatement(stmt, [C(0b11, unsigned(2)), C(0b1111, signed(4))], C(1)) - - def test_any(self): - stmt = lambda y, a: y.eq(a.any()) - self.assertStatement(stmt, [C(0b00, 2)], C(0)) - self.assertStatement(stmt, [C(0b01, 2)], C(1)) - self.assertStatement(stmt, [C(0b10, 2)], C(1)) - self.assertStatement(stmt, [C(0b11, 2)], C(1)) - - def test_all(self): - stmt = lambda y, a: y.eq(a.all()) - self.assertStatement(stmt, [C(0b00, 2)], C(0)) - self.assertStatement(stmt, [C(0b01, 2)], C(0)) - self.assertStatement(stmt, [C(0b10, 2)], C(0)) - self.assertStatement(stmt, [C(0b11, 2)], C(1)) - - def test_xor_unary(self): - stmt = lambda y, a: y.eq(a.xor()) - self.assertStatement(stmt, [C(0b00, 2)], C(0)) - self.assertStatement(stmt, [C(0b01, 2)], C(1)) - self.assertStatement(stmt, [C(0b10, 2)], C(1)) - self.assertStatement(stmt, [C(0b11, 2)], C(0)) - - def test_add(self): - stmt = lambda y, a, b: y.eq(a + b) - self.assertStatement(stmt, [C(0, 4), C(1, 4)], C(1, 4)) - self.assertStatement(stmt, [C(-5, 4), C(-5, 4)], C(-10, 5)) - - def test_sub(self): - stmt = lambda y, a, b: y.eq(a - b) - self.assertStatement(stmt, [C(2, 4), C(1, 4)], C(1, 4)) - self.assertStatement(stmt, [C(0, 4), C(1, 4)], C(-1, 4)) - self.assertStatement(stmt, [C(0, 4), C(10, 4)], C(-10, 5)) - - def test_mul(self): - stmt = lambda y, a, b: y.eq(a * b) - self.assertStatement(stmt, [C(2, 4), C(1, 4)], C(2, 8)) - self.assertStatement(stmt, [C(2, 4), C(2, 4)], C(4, 8)) - self.assertStatement(stmt, [C(7, 4), C(7, 4)], C(49, 8)) - - def test_floordiv(self): - stmt = lambda y, a, b: y.eq(a // b) - self.assertStatement(stmt, [C(2, 4), C(1, 4)], C(2, 8)) - self.assertStatement(stmt, [C(2, 4), C(2, 4)], C(1, 8)) - self.assertStatement(stmt, [C(7, 4), C(2, 4)], C(3, 8)) - - def test_mod(self): - stmt = lambda y, a, b: y.eq(a % b) - self.assertStatement(stmt, [C(2, 4), C(0, 4)], C(0, 8)) - self.assertStatement(stmt, [C(2, 4), C(1, 4)], C(0, 8)) - self.assertStatement(stmt, [C(2, 4), C(2, 4)], C(0, 8)) - self.assertStatement(stmt, [C(7, 4), C(2, 4)], C(1, 8)) - - def test_and(self): - stmt = lambda y, a, b: y.eq(a & b) - self.assertStatement(stmt, [C(0b1100, 4), C(0b1010, 4)], C(0b1000, 4)) - - def test_or(self): - stmt = lambda y, a, b: y.eq(a | b) - self.assertStatement(stmt, [C(0b1100, 4), C(0b1010, 4)], C(0b1110, 4)) - - def test_xor_binary(self): - stmt = lambda y, a, b: y.eq(a ^ b) - self.assertStatement(stmt, [C(0b1100, 4), C(0b1010, 4)], C(0b0110, 4)) - - def test_shl(self): - stmt = lambda y, a, b: y.eq(a << b) - self.assertStatement(stmt, [C(0b1001, 4), C(0)], C(0b1001, 5)) - self.assertStatement(stmt, [C(0b1001, 4), C(3)], C(0b1001000, 7)) - - def test_shr(self): - stmt = lambda y, a, b: y.eq(a >> b) - self.assertStatement(stmt, [C(0b1001, 4), C(0)], C(0b1001, 4)) - self.assertStatement(stmt, [C(0b1001, 4), C(2)], C(0b10, 4)) - - def test_eq(self): - stmt = lambda y, a, b: y.eq(a == b) - self.assertStatement(stmt, [C(0, 4), C(0, 4)], C(1)) - self.assertStatement(stmt, [C(0, 4), C(1, 4)], C(0)) - self.assertStatement(stmt, [C(1, 4), C(0, 4)], C(0)) - - def test_ne(self): - stmt = lambda y, a, b: y.eq(a != b) - self.assertStatement(stmt, [C(0, 4), C(0, 4)], C(0)) - self.assertStatement(stmt, [C(0, 4), C(1, 4)], C(1)) - self.assertStatement(stmt, [C(1, 4), C(0, 4)], C(1)) - - def test_lt(self): - stmt = lambda y, a, b: y.eq(a < b) - self.assertStatement(stmt, [C(0, 4), C(0, 4)], C(0)) - self.assertStatement(stmt, [C(0, 4), C(1, 4)], C(1)) - self.assertStatement(stmt, [C(1, 4), C(0, 4)], C(0)) - - def test_ge(self): - stmt = lambda y, a, b: y.eq(a >= b) - self.assertStatement(stmt, [C(0, 4), C(0, 4)], C(1)) - self.assertStatement(stmt, [C(0, 4), C(1, 4)], C(0)) - self.assertStatement(stmt, [C(1, 4), C(0, 4)], C(1)) - - def test_gt(self): - stmt = lambda y, a, b: y.eq(a > b) - self.assertStatement(stmt, [C(0, 4), C(0, 4)], C(0)) - self.assertStatement(stmt, [C(0, 4), C(1, 4)], C(0)) - self.assertStatement(stmt, [C(1, 4), C(0, 4)], C(1)) - - def test_le(self): - stmt = lambda y, a, b: y.eq(a <= b) - self.assertStatement(stmt, [C(0, 4), C(0, 4)], C(1)) - self.assertStatement(stmt, [C(0, 4), C(1, 4)], C(1)) - self.assertStatement(stmt, [C(1, 4), C(0, 4)], C(0)) - - def test_mux(self): - stmt = lambda y, a, b, c: y.eq(Mux(c, a, b)) - self.assertStatement(stmt, [C(2, 4), C(3, 4), C(0)], C(3, 4)) - self.assertStatement(stmt, [C(2, 4), C(3, 4), C(1)], C(2, 4)) - - def test_abs(self): - stmt = lambda y, a: y.eq(abs(a)) - self.assertStatement(stmt, [C(3, unsigned(8))], C(3, unsigned(8))) - self.assertStatement(stmt, [C(-3, unsigned(8))], C(-3, unsigned(8))) - self.assertStatement(stmt, [C(3, signed(8))], C(3, signed(8))) - self.assertStatement(stmt, [C(-3, signed(8))], C(3, signed(8))) - - def test_slice(self): - stmt1 = lambda y, a: y.eq(a[2]) - self.assertStatement(stmt1, [C(0b10110100, 8)], C(0b1, 1)) - stmt2 = lambda y, a: y.eq(a[2:4]) - self.assertStatement(stmt2, [C(0b10110100, 8)], C(0b01, 2)) - - def test_slice_lhs(self): - stmt1 = lambda y, a: y[2].eq(a) - self.assertStatement(stmt1, [C(0b0, 1)], C(0b11111011, 8), reset=0b11111111) - stmt2 = lambda y, a: y[2:4].eq(a) - self.assertStatement(stmt2, [C(0b01, 2)], C(0b11110111, 8), reset=0b11111011) - - def test_bit_select(self): - stmt = lambda y, a, b: y.eq(a.bit_select(b, 3)) - self.assertStatement(stmt, [C(0b10110100, 8), C(0)], C(0b100, 3)) - self.assertStatement(stmt, [C(0b10110100, 8), C(2)], C(0b101, 3)) - self.assertStatement(stmt, [C(0b10110100, 8), C(3)], C(0b110, 3)) - - def test_bit_select_lhs(self): - stmt = lambda y, a, b: y.bit_select(a, 3).eq(b) - self.assertStatement(stmt, [C(0), C(0b100, 3)], C(0b11111100, 8), reset=0b11111111) - self.assertStatement(stmt, [C(2), C(0b101, 3)], C(0b11110111, 8), reset=0b11111111) - self.assertStatement(stmt, [C(3), C(0b110, 3)], C(0b11110111, 8), reset=0b11111111) - - def test_word_select(self): - stmt = lambda y, a, b: y.eq(a.word_select(b, 3)) - self.assertStatement(stmt, [C(0b10110100, 8), C(0)], C(0b100, 3)) - self.assertStatement(stmt, [C(0b10110100, 8), C(1)], C(0b110, 3)) - self.assertStatement(stmt, [C(0b10110100, 8), C(2)], C(0b010, 3)) - - def test_word_select_lhs(self): - stmt = lambda y, a, b: y.word_select(a, 3).eq(b) - self.assertStatement(stmt, [C(0), C(0b100, 3)], C(0b11111100, 8), reset=0b11111111) - self.assertStatement(stmt, [C(1), C(0b101, 3)], C(0b11101111, 8), reset=0b11111111) - self.assertStatement(stmt, [C(2), C(0b110, 3)], C(0b10111111, 8), reset=0b11111111) - - def test_cat(self): - stmt = lambda y, *xs: y.eq(Cat(*xs)) - self.assertStatement(stmt, [C(0b10, 2), C(0b01, 2)], C(0b0110, 4)) - - def test_cat_lhs(self): - l = Signal(3) - m = Signal(3) - n = Signal(3) - stmt = lambda y, a: [Cat(l, m, n).eq(a), y.eq(Cat(n, m, l))] - self.assertStatement(stmt, [C(0b100101110, 9)], C(0b110101100, 9)) - - def test_nested_cat_lhs(self): - l = Signal(3) - m = Signal(3) - n = Signal(3) - stmt = lambda y, a: [Cat(Cat(l, Cat(m)), n).eq(a), y.eq(Cat(n, m, l))] - self.assertStatement(stmt, [C(0b100101110, 9)], C(0b110101100, 9)) - - def test_record(self): - rec = Record([ - ("l", 1), - ("m", 2), - ]) - stmt = lambda y, a: [rec.eq(a), y.eq(rec)] - self.assertStatement(stmt, [C(0b101, 3)], C(0b101, 3)) - - def test_repl(self): - stmt = lambda y, a: y.eq(Repl(a, 3)) - self.assertStatement(stmt, [C(0b10, 2)], C(0b101010, 6)) - - def test_array(self): - array = Array([1, 4, 10]) - stmt = lambda y, a: y.eq(array[a]) - self.assertStatement(stmt, [C(0)], C(1)) - self.assertStatement(stmt, [C(1)], C(4)) - self.assertStatement(stmt, [C(2)], C(10)) - - def test_array_oob(self): - array = Array([1, 4, 10]) - stmt = lambda y, a: y.eq(array[a]) - self.assertStatement(stmt, [C(3)], C(10)) - self.assertStatement(stmt, [C(4)], C(10)) - - def test_array_lhs(self): - l = Signal(3, reset=1) - m = Signal(3, reset=4) - n = Signal(3, reset=7) - array = Array([l, m, n]) - stmt = lambda y, a, b: [array[a].eq(b), y.eq(Cat(*array))] - self.assertStatement(stmt, [C(0), C(0b000)], C(0b111100000)) - self.assertStatement(stmt, [C(1), C(0b010)], C(0b111010001)) - self.assertStatement(stmt, [C(2), C(0b100)], C(0b100100001)) - - def test_array_lhs_oob(self): - l = Signal(3) - m = Signal(3) - n = Signal(3) - array = Array([l, m, n]) - stmt = lambda y, a, b: [array[a].eq(b), y.eq(Cat(*array))] - self.assertStatement(stmt, [C(3), C(0b001)], C(0b001000000)) - self.assertStatement(stmt, [C(4), C(0b010)], C(0b010000000)) - - def test_array_index(self): - array = Array(Array(x * y for y in range(10)) for x in range(10)) - stmt = lambda y, a, b: y.eq(array[a][b]) - for x in range(10): - for y in range(10): - self.assertStatement(stmt, [C(x), C(y)], C(x * y)) - - def test_array_attr(self): - from collections import namedtuple - pair = namedtuple("pair", ("p", "n")) - - array = Array(pair(x, -x) for x in range(10)) - stmt = lambda y, a: y.eq(array[a].p + array[a].n) - for i in range(10): - self.assertStatement(stmt, [C(i)], C(0)) - - def test_shift_left(self): - stmt1 = lambda y, a: y.eq(a.shift_left(1)) - self.assertStatement(stmt1, [C(0b10100010, 8)], C( 0b101000100, 9)) - stmt2 = lambda y, a: y.eq(a.shift_left(4)) - self.assertStatement(stmt2, [C(0b10100010, 8)], C(0b101000100000, 12)) - - def test_shift_right(self): - stmt1 = lambda y, a: y.eq(a.shift_right(1)) - self.assertStatement(stmt1, [C(0b10100010, 8)], C(0b1010001, 7)) - stmt2 = lambda y, a: y.eq(a.shift_right(4)) - self.assertStatement(stmt2, [C(0b10100010, 8)], C( 0b1010, 4)) - - def test_rotate_left(self): - stmt = lambda y, a: y.eq(a.rotate_left(1)) - self.assertStatement(stmt, [C(0b1)], C(0b1)) - self.assertStatement(stmt, [C(0b1001000)], C(0b0010001)) - stmt = lambda y, a: y.eq(a.rotate_left(5)) - self.assertStatement(stmt, [C(0b1000000)], C(0b0010000)) - self.assertStatement(stmt, [C(0b1000001)], C(0b0110000)) - stmt = lambda y, a: y.eq(a.rotate_left(7)) - self.assertStatement(stmt, [C(0b1000000)], C(0b1000000)) - self.assertStatement(stmt, [C(0b1000001)], C(0b1000001)) - stmt = lambda y, a: y.eq(a.rotate_left(9)) - self.assertStatement(stmt, [C(0b1000000)], C(0b0000010)) - self.assertStatement(stmt, [C(0b1000001)], C(0b0000110)) - stmt = lambda y, a: y.eq(a.rotate_left(-1)) - self.assertStatement(stmt, [C(0b1)], C(0b1)) - self.assertStatement(stmt, [C(0b1001000)], C(0b0100100)) - stmt = lambda y, a: y.eq(a.rotate_left(-5)) - self.assertStatement(stmt, [C(0b1000000)], C(0b0000010)) - self.assertStatement(stmt, [C(0b1000001)], C(0b0000110)) - stmt = lambda y, a: y.eq(a.rotate_left(-7)) - self.assertStatement(stmt, [C(0b1000000)], C(0b1000000)) - self.assertStatement(stmt, [C(0b1000001)], C(0b1000001)) - stmt = lambda y, a: y.eq(a.rotate_left(-9)) - self.assertStatement(stmt, [C(0b1000000)], C(0b0010000)) - self.assertStatement(stmt, [C(0b1000001)], C(0b0110000)) - - def test_rotate_right(self): - stmt = lambda y, a: y.eq(a.rotate_right(1)) - self.assertStatement(stmt, [C(0b1)], C(0b1)) - self.assertStatement(stmt, [C(0b1001000)], C(0b0100100)) - stmt = lambda y, a: y.eq(a.rotate_right(5)) - self.assertStatement(stmt, [C(0b1000000)], C(0b0000010)) - self.assertStatement(stmt, [C(0b1000001)], C(0b0000110)) - stmt = lambda y, a: y.eq(a.rotate_right(7)) - self.assertStatement(stmt, [C(0b1000000)], C(0b1000000)) - self.assertStatement(stmt, [C(0b1000001)], C(0b1000001)) - stmt = lambda y, a: y.eq(a.rotate_right(9)) - self.assertStatement(stmt, [C(0b1000000)], C(0b0010000)) - self.assertStatement(stmt, [C(0b1000001)], C(0b0110000)) - stmt = lambda y, a: y.eq(a.rotate_right(-1)) - self.assertStatement(stmt, [C(0b1)], C(0b1)) - self.assertStatement(stmt, [C(0b1001000)], C(0b0010001)) - stmt = lambda y, a: y.eq(a.rotate_right(-5)) - self.assertStatement(stmt, [C(0b1000000)], C(0b0010000)) - self.assertStatement(stmt, [C(0b1000001)], C(0b0110000)) - stmt = lambda y, a: y.eq(a.rotate_right(-7)) - self.assertStatement(stmt, [C(0b1000000)], C(0b1000000)) - self.assertStatement(stmt, [C(0b1000001)], C(0b1000001)) - stmt = lambda y, a: y.eq(a.rotate_right(-9)) - self.assertStatement(stmt, [C(0b1000000)], C(0b0000010)) - self.assertStatement(stmt, [C(0b1000001)], C(0b0000110)) - - -class SimulatorIntegrationTestCase(FHDLTestCase): - @contextmanager - def assertSimulation(self, module, deadline=None): - sim = Simulator(module) - yield sim - with sim.write_vcd("test.vcd", "test.gtkw"): - if deadline is None: - sim.run() - else: - sim.run_until(deadline) - - def setUp_counter(self): - self.count = Signal(3, reset=4) - self.sync = ClockDomain() - - self.m = Module() - self.m.d.sync += self.count.eq(self.count + 1) - self.m.domains += self.sync - - def test_counter_process(self): - self.setUp_counter() - with self.assertSimulation(self.m) as sim: - def process(): - self.assertEqual((yield self.count), 4) - yield Delay(1e-6) - self.assertEqual((yield self.count), 4) - yield self.sync.clk.eq(1) - self.assertEqual((yield self.count), 4) - yield Settle() - self.assertEqual((yield self.count), 5) - yield Delay(1e-6) - self.assertEqual((yield self.count), 5) - yield self.sync.clk.eq(0) - self.assertEqual((yield self.count), 5) - yield Settle() - self.assertEqual((yield self.count), 5) - for _ in range(3): - yield Delay(1e-6) - yield self.sync.clk.eq(1) - yield Delay(1e-6) - yield self.sync.clk.eq(0) - self.assertEqual((yield self.count), 0) - sim.add_process(process) - - def test_counter_clock_and_sync_process(self): - self.setUp_counter() - with self.assertSimulation(self.m) as sim: - sim.add_clock(1e-6, domain="sync") - def process(): - self.assertEqual((yield self.count), 4) - self.assertEqual((yield self.sync.clk), 1) - yield - self.assertEqual((yield self.count), 5) - self.assertEqual((yield self.sync.clk), 1) - for _ in range(3): - yield - self.assertEqual((yield self.count), 0) - sim.add_sync_process(process) - - def test_reset(self): - self.setUp_counter() - sim = Simulator(self.m) - sim.add_clock(1e-6) - times = 0 - def process(): - nonlocal times - self.assertEqual((yield self.count), 4) - yield - self.assertEqual((yield self.count), 5) - yield - self.assertEqual((yield self.count), 6) - yield - times += 1 - sim.add_sync_process(process) - sim.run() - sim.reset() - sim.run() - self.assertEqual(times, 2) - - def setUp_alu(self): - self.a = Signal(8) - self.b = Signal(8) - self.o = Signal(8) - self.x = Signal(8) - self.s = Signal(2) - self.sync = ClockDomain(reset_less=True) - - self.m = Module() - self.m.d.comb += self.x.eq(self.a ^ self.b) - with self.m.Switch(self.s): - with self.m.Case(0): - self.m.d.sync += self.o.eq(self.a + self.b) - with self.m.Case(1): - self.m.d.sync += self.o.eq(self.a - self.b) - with self.m.Case(): - self.m.d.sync += self.o.eq(0) - self.m.domains += self.sync - - def test_alu(self): - self.setUp_alu() - with self.assertSimulation(self.m) as sim: - sim.add_clock(1e-6) - def process(): - yield self.a.eq(5) - yield self.b.eq(1) - yield - self.assertEqual((yield self.x), 4) - yield - self.assertEqual((yield self.o), 6) - yield self.s.eq(1) - yield - yield - self.assertEqual((yield self.o), 4) - yield self.s.eq(2) - yield - yield - self.assertEqual((yield self.o), 0) - sim.add_sync_process(process) - - def setUp_multiclock(self): - self.sys = ClockDomain() - self.pix = ClockDomain() - - self.m = Module() - self.m.domains += self.sys, self.pix - - def test_multiclock(self): - self.setUp_multiclock() - with self.assertSimulation(self.m) as sim: - sim.add_clock(1e-6, domain="sys") - sim.add_clock(0.3e-6, domain="pix") - - def sys_process(): - yield Passive() - yield - yield - self.fail() - def pix_process(): - yield - yield - yield - sim.add_sync_process(sys_process, domain="sys") - sim.add_sync_process(pix_process, domain="pix") - - def setUp_lhs_rhs(self): - self.i = Signal(8) - self.o = Signal(8) - - self.m = Module() - self.m.d.comb += self.o.eq(self.i) - - def test_complex_lhs_rhs(self): - self.setUp_lhs_rhs() - with self.assertSimulation(self.m) as sim: - def process(): - yield self.i.eq(0b10101010) - yield self.i[:4].eq(-1) - yield Settle() - self.assertEqual((yield self.i[:4]), 0b1111) - self.assertEqual((yield self.i), 0b10101111) - sim.add_process(process) - - def test_run_until(self): - m = Module() - s = Signal() - m.d.sync += s.eq(0) - with self.assertSimulation(m, deadline=100e-6) as sim: - sim.add_clock(1e-6) - def process(): - for _ in range(101): - yield Delay(1e-6) - self.fail() - sim.add_process(process) - - def test_add_process_wrong(self): - with self.assertSimulation(Module()) as sim: - with self.assertRaisesRegex(TypeError, - r"^Cannot add a process 1 because it is not a generator function$"): - sim.add_process(1) - - def test_add_process_wrong_generator(self): - with self.assertSimulation(Module()) as sim: - with self.assertRaisesRegex(TypeError, - r"^Cannot add a process <.+?> because it is not a generator function$"): - def process(): - yield Delay() - sim.add_process(process()) - - def test_add_clock_wrong_twice(self): - m = Module() - s = Signal() - m.d.sync += s.eq(0) - with self.assertSimulation(m) as sim: - sim.add_clock(1) - with self.assertRaisesRegex(ValueError, - r"^Domain 'sync' already has a clock driving it$"): - sim.add_clock(1) - - def test_add_clock_wrong_missing(self): - m = Module() - with self.assertSimulation(m) as sim: - with self.assertRaisesRegex(ValueError, - r"^Domain 'sync' is not present in simulation$"): - sim.add_clock(1) - - def test_add_clock_if_exists(self): - m = Module() - with self.assertSimulation(m) as sim: - sim.add_clock(1, if_exists=True) - - def test_command_wrong(self): - survived = False - with self.assertSimulation(Module()) as sim: - def process(): - nonlocal survived - with self.assertRaisesRegex(TypeError, - r"Received unsupported command 1 from process .+?"): - yield 1 - yield Settle() - survived = True - sim.add_process(process) - self.assertTrue(survived) - - def setUp_memory(self, rd_synchronous=True, rd_transparent=True, wr_granularity=None): - self.m = Module() - self.memory = Memory(width=8, depth=4, init=[0xaa, 0x55]) - self.m.submodules.rdport = self.rdport = \ - self.memory.read_port(domain="sync" if rd_synchronous else "comb", - transparent=rd_transparent) - self.m.submodules.wrport = self.wrport = \ - self.memory.write_port(granularity=wr_granularity) - - def test_memory_init(self): - self.setUp_memory() - with self.assertSimulation(self.m) as sim: - def process(): - self.assertEqual((yield self.rdport.data), 0xaa) - yield self.rdport.addr.eq(1) - yield - yield - self.assertEqual((yield self.rdport.data), 0x55) - yield self.rdport.addr.eq(2) - yield - yield - self.assertEqual((yield self.rdport.data), 0x00) - sim.add_clock(1e-6) - sim.add_sync_process(process) - - def test_memory_write(self): - self.setUp_memory() - with self.assertSimulation(self.m) as sim: - def process(): - yield self.wrport.addr.eq(4) - yield self.wrport.data.eq(0x33) - yield self.wrport.en.eq(1) - yield - yield self.wrport.en.eq(0) - yield self.rdport.addr.eq(4) - yield - self.assertEqual((yield self.rdport.data), 0x33) - sim.add_clock(1e-6) - sim.add_sync_process(process) - - def test_memory_write_granularity(self): - self.setUp_memory(wr_granularity=4) - with self.assertSimulation(self.m) as sim: - def process(): - yield self.wrport.data.eq(0x50) - yield self.wrport.en.eq(0b00) - yield - yield self.wrport.en.eq(0) - yield - self.assertEqual((yield self.rdport.data), 0xaa) - yield self.wrport.en.eq(0b10) - yield - yield self.wrport.en.eq(0) - yield - self.assertEqual((yield self.rdport.data), 0x5a) - yield self.wrport.data.eq(0x33) - yield self.wrport.en.eq(0b01) - yield - yield self.wrport.en.eq(0) - yield - self.assertEqual((yield self.rdport.data), 0x53) - sim.add_clock(1e-6) - sim.add_sync_process(process) - - def test_memory_read_before_write(self): - self.setUp_memory(rd_transparent=False) - with self.assertSimulation(self.m) as sim: - def process(): - yield self.wrport.data.eq(0x33) - yield self.wrport.en.eq(1) - yield - self.assertEqual((yield self.rdport.data), 0xaa) - yield - self.assertEqual((yield self.rdport.data), 0xaa) - yield Settle() - self.assertEqual((yield self.rdport.data), 0x33) - sim.add_clock(1e-6) - sim.add_sync_process(process) - - def test_memory_write_through(self): - self.setUp_memory(rd_transparent=True) - with self.assertSimulation(self.m) as sim: - def process(): - yield self.wrport.data.eq(0x33) - yield self.wrport.en.eq(1) - yield - self.assertEqual((yield self.rdport.data), 0xaa) - yield Settle() - self.assertEqual((yield self.rdport.data), 0x33) - yield - yield self.rdport.addr.eq(1) - yield Settle() - self.assertEqual((yield self.rdport.data), 0x33) - sim.add_clock(1e-6) - sim.add_sync_process(process) - - def test_memory_async_read_write(self): - self.setUp_memory(rd_synchronous=False) - with self.assertSimulation(self.m) as sim: - def process(): - yield self.rdport.addr.eq(0) - yield Settle() - self.assertEqual((yield self.rdport.data), 0xaa) - yield self.rdport.addr.eq(1) - yield Settle() - self.assertEqual((yield self.rdport.data), 0x55) - yield self.rdport.addr.eq(0) - yield self.wrport.addr.eq(0) - yield self.wrport.data.eq(0x33) - yield self.wrport.en.eq(1) - yield Tick("sync") - self.assertEqual((yield self.rdport.data), 0xaa) - yield Settle() - self.assertEqual((yield self.rdport.data), 0x33) - sim.add_clock(1e-6) - sim.add_process(process) - - def test_memory_read_only(self): - self.m = Module() - self.memory = Memory(width=8, depth=4, init=[0xaa, 0x55]) - self.m.submodules.rdport = self.rdport = self.memory.read_port() - with self.assertSimulation(self.m) as sim: - def process(): - self.assertEqual((yield self.rdport.data), 0xaa) - yield self.rdport.addr.eq(1) - yield - yield - self.assertEqual((yield self.rdport.data), 0x55) - sim.add_clock(1e-6) - sim.add_sync_process(process) - - def test_sample_helpers(self): - m = Module() - s = Signal(2) - def mk(x): - y = Signal.like(x) - m.d.comb += y.eq(x) - return y - p0, r0, f0, s0 = mk(Past(s, 0)), mk(Rose(s)), mk(Fell(s)), mk(Stable(s)) - p1, r1, f1, s1 = mk(Past(s)), mk(Rose(s, 1)), mk(Fell(s, 1)), mk(Stable(s, 1)) - p2, r2, f2, s2 = mk(Past(s, 2)), mk(Rose(s, 2)), mk(Fell(s, 2)), mk(Stable(s, 2)) - p3, r3, f3, s3 = mk(Past(s, 3)), mk(Rose(s, 3)), mk(Fell(s, 3)), mk(Stable(s, 3)) - with self.assertSimulation(m) as sim: - def process_gen(): - yield s.eq(0b10) - yield - yield - yield s.eq(0b01) - yield - def process_check(): - yield - yield - yield - - self.assertEqual((yield p0), 0b01) - self.assertEqual((yield p1), 0b10) - self.assertEqual((yield p2), 0b10) - self.assertEqual((yield p3), 0b00) - - self.assertEqual((yield s0), 0b0) - self.assertEqual((yield s1), 0b1) - self.assertEqual((yield s2), 0b0) - self.assertEqual((yield s3), 0b1) - - self.assertEqual((yield r0), 0b01) - self.assertEqual((yield r1), 0b00) - self.assertEqual((yield r2), 0b10) - self.assertEqual((yield r3), 0b00) - - self.assertEqual((yield f0), 0b10) - self.assertEqual((yield f1), 0b00) - self.assertEqual((yield f2), 0b00) - self.assertEqual((yield f3), 0b00) - sim.add_clock(1e-6) - sim.add_sync_process(process_gen) - sim.add_sync_process(process_check) - - def test_vcd_wrong_nonzero_time(self): - s = Signal() - m = Module() - m.d.sync += s.eq(s) - sim = Simulator(m) - sim.add_clock(1e-6) - sim.run_until(1e-5) - with self.assertRaisesRegex(ValueError, - r"^Cannot start writing waveforms after advancing simulation time$"): - with sim.write_vcd(open(os.path.devnull, "wt")): - pass - - -class SimulatorRegressionTestCase(FHDLTestCase): - def test_bug_325(self): - dut = Module() - dut.d.comb += Signal().eq(Cat()) - Simulator(dut).run() - - def test_bug_325_bis(self): - dut = Module() - dut.d.comb += Signal().eq(Repl(Const(1), 0)) - Simulator(dut).run() - - def test_bug_473(self): - sim = Simulator(Module()) - def process(): - self.assertEqual((yield -(Const(0b11, 2).as_signed())), 1) - sim.add_process(process) - sim.run() diff --git a/nmigen/test/utils.py b/nmigen/test/utils.py deleted file mode 100644 index 3c88403..0000000 --- a/nmigen/test/utils.py +++ /dev/null @@ -1,78 +0,0 @@ -import os -import re -import shutil -import subprocess -import textwrap -import traceback -import unittest -import warnings -from contextlib import contextmanager - -from ..hdl.ast import * -from ..hdl.ir import * -from ..back import rtlil -from .._toolchain import require_tool - - -__all__ = ["FHDLTestCase"] - - -class FHDLTestCase(unittest.TestCase): - def assertRepr(self, obj, repr_str): - if isinstance(obj, list): - obj = Statement.cast(obj) - def prepare_repr(repr_str): - repr_str = re.sub(r"\s+", " ", repr_str) - repr_str = re.sub(r"\( (?=\()", "(", repr_str) - repr_str = re.sub(r"\) (?=\))", ")", repr_str) - return repr_str.strip() - self.assertEqual(prepare_repr(repr(obj)), prepare_repr(repr_str)) - - def assertFormal(self, spec, mode="bmc", depth=1): - caller, *_ = traceback.extract_stack(limit=2) - spec_root, _ = os.path.splitext(caller.filename) - spec_dir = os.path.dirname(spec_root) - spec_name = "{}_{}".format( - os.path.basename(spec_root).replace("test_", "spec_"), - caller.name.replace("test_", "") - ) - - # The sby -f switch seems not fully functional when sby is reading from stdin. - if os.path.exists(os.path.join(spec_dir, spec_name)): - shutil.rmtree(os.path.join(spec_dir, spec_name)) - - if mode == "hybrid": - # A mix of BMC and k-induction, as per personal communication with Clifford Wolf. - script = "setattr -unset init w:* a:nmigen.sample_reg %d" - mode = "bmc" - else: - script = "" - - config = textwrap.dedent("""\ - [options] - mode {mode} - depth {depth} - wait on - - [engines] - smtbmc - - [script] - read_ilang top.il - prep - {script} - - [file top.il] - {rtlil} - """).format( - mode=mode, - depth=depth, - script=script, - rtlil=rtlil.convert(Fragment.get(spec, platform="formal")) - ) - with subprocess.Popen([require_tool("sby"), "-f", "-d", spec_name], cwd=spec_dir, - universal_newlines=True, - stdin=subprocess.PIPE, stdout=subprocess.PIPE) as proc: - stdout, stderr = proc.communicate(config) - if proc.returncode != 0: - self.fail("Formal verification failed:\n" + stdout) diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/compat/__init__.py b/tests/compat/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/compat/support.py b/tests/compat/support.py new file mode 100644 index 0000000..05923f8 --- /dev/null +++ b/tests/compat/support.py @@ -0,0 +1,16 @@ +from nmigen.compat import * +from nmigen.compat.fhdl import verilog +from nmigen._utils import _ignore_deprecated + + +class SimCase: + def setUp(self, *args, **kwargs): + with _ignore_deprecated(): + self.tb = self.TestBench(*args, **kwargs) + + def test_to_verilog(self): + verilog.convert(self.tb) + + def run_with(self, generator): + with _ignore_deprecated(): + run_simulation(self.tb, generator) diff --git a/tests/compat/test_coding.py b/tests/compat/test_coding.py new file mode 100644 index 0000000..a65dea6 --- /dev/null +++ b/tests/compat/test_coding.py @@ -0,0 +1,116 @@ +# nmigen: UnusedElaboratable=no + +import unittest + +from nmigen.compat import * +from nmigen.compat.genlib.coding import * + +from .support import SimCase + + +class EncCase(SimCase, unittest.TestCase): + class TestBench(Module): + def __init__(self): + self.submodules.dut = Encoder(8) + + def test_sizes(self): + self.assertEqual(len(self.tb.dut.i), 8) + self.assertEqual(len(self.tb.dut.o), 3) + self.assertEqual(len(self.tb.dut.n), 1) + + def test_run_sequence(self): + seq = list(range(1<<8)) + def gen(): + for _ in range(256): + if seq: + yield self.tb.dut.i.eq(seq.pop(0)) + yield + if (yield self.tb.dut.n): + self.assertNotIn((yield self.tb.dut.i), [1< 0: + self.assertEqual(i & 1<<(o - 1), 0) + self.assertGreaterEqual(i, 1< 0: + self.assertEqual(i & 1<<(o - 1), 0) + self.assertGreaterEqual(i, 1< q, + lambda p, q: p >= q, + lambda p, q: p < q, + lambda p, q: p <= q, + lambda p, q: p == q, + lambda p, q: p != q, + ] + self.vals = [] + for asign in 1, -1: + for bsign in 1, -1: + for f in comps: + r = Signal() + r0 = f(asign*self.a, bsign*self.b) + self.comb += r.eq(r0) + self.vals.append((asign, bsign, f, r, r0.op)) + + def test_comparisons(self): + def gen(): + for i in range(-4, 4): + yield self.tb.a.eq(i) + yield self.tb.b.eq(i) + yield + a = yield self.tb.a + b = yield self.tb.b + for asign, bsign, f, r, op in self.tb.vals: + r, r0 = (yield r), f(asign*a, bsign*b) + self.assertEqual(r, int(r0), + "got {}, want {}*{} {} {}*{} = {}".format( + r, asign, a, op, bsign, b, r0)) + self.run_with(gen()) diff --git a/tests/compat/test_size.py b/tests/compat/test_size.py new file mode 100644 index 0000000..8211aa9 --- /dev/null +++ b/tests/compat/test_size.py @@ -0,0 +1,21 @@ +import unittest + +from nmigen._utils import _ignore_deprecated +from nmigen.compat import * + + +def _same_slices(a, b): + return a.value is b.value and a.start == b.start and a.stop == b.stop + + +class SignalSizeCase(unittest.TestCase): + def setUp(self): + self.i = C(0xaa) + self.j = C(-127) + with _ignore_deprecated(): + self.s = Signal((13, True)) + + def test_len(self): + self.assertEqual(len(self.s), 13) + self.assertEqual(len(self.i), 8) + self.assertEqual(len(self.j), 8) diff --git a/tests/test_build_dsl.py b/tests/test_build_dsl.py new file mode 100644 index 0000000..76cd2e9 --- /dev/null +++ b/tests/test_build_dsl.py @@ -0,0 +1,329 @@ +from collections import OrderedDict + +from nmigen.build.dsl import * + +from .utils import * + + +class PinsTestCase(FHDLTestCase): + def test_basic(self): + p = Pins("A0 A1 A2") + self.assertEqual(repr(p), "(pins io A0 A1 A2)") + self.assertEqual(len(p.names), 3) + self.assertEqual(p.dir, "io") + self.assertEqual(p.invert, False) + self.assertEqual(list(p), ["A0", "A1", "A2"]) + + def test_invert(self): + p = PinsN("A0") + self.assertEqual(repr(p), "(pins-n io A0)") + self.assertEqual(p.invert, True) + + def test_invert_arg(self): + p = Pins("A0", invert=True) + self.assertEqual(p.invert, True) + + def test_conn(self): + p = Pins("0 1 2", conn=("pmod", 0)) + self.assertEqual(list(p), ["pmod_0:0", "pmod_0:1", "pmod_0:2"]) + p = Pins("0 1 2", conn=("pmod", "a")) + self.assertEqual(list(p), ["pmod_a:0", "pmod_a:1", "pmod_a:2"]) + + def test_map_names(self): + p = Pins("0 1 2", conn=("pmod", 0)) + mapping = { + "pmod_0:0": "A0", + "pmod_0:1": "A1", + "pmod_0:2": "A2", + } + self.assertEqual(p.map_names(mapping, p), ["A0", "A1", "A2"]) + + def test_map_names_recur(self): + p = Pins("0", conn=("pmod", 0)) + mapping = { + "pmod_0:0": "ext_0:1", + "ext_0:1": "A1", + } + self.assertEqual(p.map_names(mapping, p), ["A1"]) + + def test_wrong_names(self): + with self.assertRaisesRegex(TypeError, + r"^Names must be a whitespace-separated string, not \['A0', 'A1', 'A2'\]$"): + p = Pins(["A0", "A1", "A2"]) + + def test_wrong_dir(self): + with self.assertRaisesRegex(TypeError, + r"^Direction must be one of \"i\", \"o\", \"oe\", or \"io\", not 'wrong'$"): + p = Pins("A0 A1", dir="wrong") + + def test_wrong_conn(self): + with self.assertRaisesRegex(TypeError, + (r"^Connector must be None or a pair of string \(connector name\) and " + r"integer\/string \(connector number\), not \('foo', None\)$")): + p = Pins("A0 A1", conn=("foo", None)) + + def test_wrong_map_names(self): + p = Pins("0 1 2", conn=("pmod", 0)) + mapping = { + "pmod_0:0": "A0", + } + with self.assertRaisesRegex(NameError, + (r"^Resource \(pins io pmod_0:0 pmod_0:1 pmod_0:2\) refers to nonexistent " + r"connector pin pmod_0:1$")): + p.map_names(mapping, p) + + def test_wrong_assert_width(self): + with self.assertRaisesRegex(AssertionError, + r"^3 names are specified \(0 1 2\), but 4 names are expected$"): + Pins("0 1 2", assert_width=4) + + +class DiffPairsTestCase(FHDLTestCase): + def test_basic(self): + dp = DiffPairs(p="A0 A1", n="B0 B1") + self.assertEqual(repr(dp), "(diffpairs io (p A0 A1) (n B0 B1))") + self.assertEqual(dp.p.names, ["A0", "A1"]) + self.assertEqual(dp.n.names, ["B0", "B1"]) + self.assertEqual(dp.dir, "io") + self.assertEqual(list(dp), [("A0", "B0"), ("A1", "B1")]) + + def test_invert(self): + dp = DiffPairsN(p="A0", n="B0") + self.assertEqual(repr(dp), "(diffpairs-n io (p A0) (n B0))") + self.assertEqual(dp.p.names, ["A0"]) + self.assertEqual(dp.n.names, ["B0"]) + self.assertEqual(dp.invert, True) + + def test_conn(self): + dp = DiffPairs(p="0 1 2", n="3 4 5", conn=("pmod", 0)) + self.assertEqual(list(dp), [ + ("pmod_0:0", "pmod_0:3"), + ("pmod_0:1", "pmod_0:4"), + ("pmod_0:2", "pmod_0:5"), + ]) + + def test_dir(self): + dp = DiffPairs("A0", "B0", dir="o") + self.assertEqual(dp.dir, "o") + self.assertEqual(dp.p.dir, "o") + self.assertEqual(dp.n.dir, "o") + + def test_wrong_width(self): + with self.assertRaisesRegex(TypeError, + (r"^Positive and negative pins must have the same width, but \(pins io A0\) " + r"and \(pins io B0 B1\) do not$")): + dp = DiffPairs("A0", "B0 B1") + + def test_wrong_assert_width(self): + with self.assertRaisesRegex(AssertionError, + r"^3 names are specified \(0 1 2\), but 4 names are expected$"): + DiffPairs("0 1 2", "3 4 5", assert_width=4) + + +class AttrsTestCase(FHDLTestCase): + def test_basic(self): + a = Attrs(IO_STANDARD="LVCMOS33", PULLUP=1) + self.assertEqual(a["IO_STANDARD"], "LVCMOS33") + self.assertEqual(repr(a), "(attrs IO_STANDARD='LVCMOS33' PULLUP=1)") + + def test_remove(self): + a = Attrs(FOO=None) + self.assertEqual(a["FOO"], None) + self.assertEqual(repr(a), "(attrs !FOO)") + + def test_callable(self): + fn = lambda self: "FOO" + a = Attrs(FOO=fn) + self.assertEqual(a["FOO"], fn) + self.assertEqual(repr(a), "(attrs FOO={!r})".format(fn)) + + def test_wrong_value(self): + with self.assertRaisesRegex(TypeError, + r"^Value of attribute FOO must be None, int, str, or callable, not 1\.0$"): + a = Attrs(FOO=1.0) + + +class ClockTestCase(FHDLTestCase): + def test_basic(self): + c = Clock(1_000_000) + self.assertEqual(c.frequency, 1e6) + self.assertEqual(c.period, 1e-6) + self.assertEqual(repr(c), "(clock 1000000.0)") + + +class SubsignalTestCase(FHDLTestCase): + def test_basic_pins(self): + s = Subsignal("a", Pins("A0"), Attrs(IOSTANDARD="LVCMOS33")) + self.assertEqual(repr(s), + "(subsignal a (pins io A0) (attrs IOSTANDARD='LVCMOS33'))") + + def test_basic_diffpairs(self): + s = Subsignal("a", DiffPairs("A0", "B0")) + self.assertEqual(repr(s), + "(subsignal a (diffpairs io (p A0) (n B0)))") + + def test_basic_subsignals(self): + s = Subsignal("a", + Subsignal("b", Pins("A0")), + Subsignal("c", Pins("A1"))) + self.assertEqual(repr(s), + "(subsignal a (subsignal b (pins io A0)) " + "(subsignal c (pins io A1)))") + + def test_attrs(self): + s = Subsignal("a", + Subsignal("b", Pins("A0")), + Subsignal("c", Pins("A0"), Attrs(SLEW="FAST")), + Attrs(IOSTANDARD="LVCMOS33")) + self.assertEqual(s.attrs, {"IOSTANDARD": "LVCMOS33"}) + self.assertEqual(s.ios[0].attrs, {}) + self.assertEqual(s.ios[1].attrs, {"SLEW": "FAST"}) + + def test_attrs_many(self): + s = Subsignal("a", Pins("A0"), Attrs(SLEW="FAST"), Attrs(PULLUP="1")) + self.assertEqual(s.attrs, {"SLEW": "FAST", "PULLUP": "1"}) + + def test_clock(self): + s = Subsignal("a", Pins("A0"), Clock(1e6)) + self.assertEqual(s.clock.frequency, 1e6) + + def test_wrong_empty_io(self): + with self.assertRaisesRegex(ValueError, r"^Missing I\/O constraints$"): + s = Subsignal("a") + + def test_wrong_io(self): + with self.assertRaisesRegex(TypeError, + (r"^Constraint must be one of Pins, DiffPairs, Subsignal, Attrs, or Clock, " + r"not 'wrong'$")): + s = Subsignal("a", "wrong") + + def test_wrong_pins(self): + with self.assertRaisesRegex(TypeError, + (r"^Pins and DiffPairs are incompatible with other location or subsignal " + r"constraints, but \(pins io A1\) appears after \(pins io A0\)$")): + s = Subsignal("a", Pins("A0"), Pins("A1")) + + def test_wrong_diffpairs(self): + with self.assertRaisesRegex(TypeError, + (r"^Pins and DiffPairs are incompatible with other location or subsignal " + r"constraints, but \(pins io A1\) appears after \(diffpairs io \(p A0\) \(n B0\)\)$")): + s = Subsignal("a", DiffPairs("A0", "B0"), Pins("A1")) + + def test_wrong_subsignals(self): + with self.assertRaisesRegex(TypeError, + (r"^Pins and DiffPairs are incompatible with other location or subsignal " + r"constraints, but \(pins io B0\) appears after \(subsignal b \(pins io A0\)\)$")): + s = Subsignal("a", Subsignal("b", Pins("A0")), Pins("B0")) + + def test_wrong_clock(self): + with self.assertRaisesRegex(TypeError, + (r"^Clock constraint can only be applied to Pins or DiffPairs, not " + r"\(subsignal b \(pins io A0\)\)$")): + s = Subsignal("a", Subsignal("b", Pins("A0")), Clock(1e6)) + + def test_wrong_clock_many(self): + with self.assertRaisesRegex(ValueError, + r"^Clock constraint can be applied only once$"): + s = Subsignal("a", Pins("A0"), Clock(1e6), Clock(1e7)) + + +class ResourceTestCase(FHDLTestCase): + def test_basic(self): + r = Resource("serial", 0, + Subsignal("tx", Pins("A0", dir="o")), + Subsignal("rx", Pins("A1", dir="i")), + Attrs(IOSTANDARD="LVCMOS33")) + self.assertEqual(repr(r), "(resource serial 0" + " (subsignal tx (pins o A0))" + " (subsignal rx (pins i A1))" + " (attrs IOSTANDARD='LVCMOS33'))") + + def test_family(self): + ios = [Subsignal("clk", Pins("A0", dir="o"))] + r1 = Resource.family(0, default_name="spi", ios=ios) + r2 = Resource.family("spi_flash", 0, default_name="spi", ios=ios) + r3 = Resource.family("spi_flash", 0, default_name="spi", ios=ios, name_suffix="4x") + r4 = Resource.family(0, default_name="spi", ios=ios, name_suffix="2x") + self.assertEqual(r1.name, "spi") + self.assertEqual(r1.ios, ios) + self.assertEqual(r2.name, "spi_flash") + self.assertEqual(r2.ios, ios) + self.assertEqual(r3.name, "spi_flash_4x") + self.assertEqual(r3.ios, ios) + self.assertEqual(r4.name, "spi_2x") + self.assertEqual(r4.ios, ios) + + +class ConnectorTestCase(FHDLTestCase): + def test_string(self): + c = Connector("pmod", 0, "A0 A1 A2 A3 - - A4 A5 A6 A7 - -") + self.assertEqual(c.name, "pmod") + self.assertEqual(c.number, 0) + self.assertEqual(c.mapping, OrderedDict([ + ("1", "A0"), + ("2", "A1"), + ("3", "A2"), + ("4", "A3"), + ("7", "A4"), + ("8", "A5"), + ("9", "A6"), + ("10", "A7"), + ])) + self.assertEqual(list(c), [ + ("pmod_0:1", "A0"), + ("pmod_0:2", "A1"), + ("pmod_0:3", "A2"), + ("pmod_0:4", "A3"), + ("pmod_0:7", "A4"), + ("pmod_0:8", "A5"), + ("pmod_0:9", "A6"), + ("pmod_0:10", "A7"), + ]) + self.assertEqual(repr(c), + "(connector pmod 0 1=>A0 2=>A1 3=>A2 4=>A3 7=>A4 8=>A5 9=>A6 10=>A7)") + + def test_dict(self): + c = Connector("ext", 1, {"DP0": "A0", "DP1": "A1"}) + self.assertEqual(c.name, "ext") + self.assertEqual(c.number, 1) + self.assertEqual(c.mapping, OrderedDict([ + ("DP0", "A0"), + ("DP1", "A1"), + ])) + + def test_conn(self): + c = Connector("pmod", 0, "0 1 2 3 - - 4 5 6 7 - -", conn=("expansion", 0)) + self.assertEqual(c.mapping, OrderedDict([ + ("1", "expansion_0:0"), + ("2", "expansion_0:1"), + ("3", "expansion_0:2"), + ("4", "expansion_0:3"), + ("7", "expansion_0:4"), + ("8", "expansion_0:5"), + ("9", "expansion_0:6"), + ("10", "expansion_0:7"), + ])) + + def test_str_name(self): + c = Connector("ext", "A", "0 1 2") + self.assertEqual(c.name, "ext") + self.assertEqual(c.number, "A") + + def test_conn_wrong_name(self): + with self.assertRaisesRegex(TypeError, + (r"^Connector must be None or a pair of string \(connector name\) and " + r"integer\/string \(connector number\), not \('foo', None\)$")): + Connector("ext", "A", "0 1 2", conn=("foo", None)) + + def test_wrong_io(self): + with self.assertRaisesRegex(TypeError, + r"^Connector I\/Os must be a dictionary or a string, not \[\]$"): + Connector("pmod", 0, []) + + def test_wrong_dict_key_value(self): + with self.assertRaisesRegex(TypeError, + r"^Connector pin name must be a string, not 0$"): + Connector("pmod", 0, {0: "A"}) + with self.assertRaisesRegex(TypeError, + r"^Platform pin name must be a string, not 0$"): + Connector("pmod", 0, {"A": 0}) diff --git a/tests/test_build_plat.py b/tests/test_build_plat.py new file mode 100644 index 0000000..31dc5c9 --- /dev/null +++ b/tests/test_build_plat.py @@ -0,0 +1,53 @@ +from nmigen import * +from nmigen.build.plat import * + +from .utils import * + + +class MockPlatform(Platform): + resources = [] + connectors = [] + + required_tools = [] + + def toolchain_prepare(self, fragment, name, **kwargs): + raise NotImplementedError + + +class PlatformTestCase(FHDLTestCase): + def setUp(self): + self.platform = MockPlatform() + + def test_add_file_str(self): + self.platform.add_file("x.txt", "foo") + self.assertEqual(self.platform.extra_files["x.txt"], "foo") + + def test_add_file_bytes(self): + self.platform.add_file("x.txt", b"foo") + self.assertEqual(self.platform.extra_files["x.txt"], b"foo") + + def test_add_file_exact_duplicate(self): + self.platform.add_file("x.txt", b"foo") + self.platform.add_file("x.txt", b"foo") + + def test_add_file_io(self): + with open(__file__) as f: + self.platform.add_file("x.txt", f) + with open(__file__) as f: + self.assertEqual(self.platform.extra_files["x.txt"], f.read()) + + def test_add_file_wrong_filename(self): + with self.assertRaisesRegex(TypeError, + r"^File name must be a string, not 1$"): + self.platform.add_file(1, "") + + def test_add_file_wrong_contents(self): + with self.assertRaisesRegex(TypeError, + r"^File contents must be str, bytes, or a file-like object, not 1$"): + self.platform.add_file("foo", 1) + + def test_add_file_wrong_duplicate(self): + self.platform.add_file("foo", "") + with self.assertRaisesRegex(ValueError, + r"^File 'foo' already exists$"): + self.platform.add_file("foo", "bar") diff --git a/tests/test_build_res.py b/tests/test_build_res.py new file mode 100644 index 0000000..ac8f7bd --- /dev/null +++ b/tests/test_build_res.py @@ -0,0 +1,314 @@ +# nmigen: UnusedElaboratable=no + +from nmigen import * +from nmigen.hdl.rec import * +from nmigen.lib.io import * +from nmigen.build.dsl import * +from nmigen.build.res import * + +from .utils import * + + +class ResourceManagerTestCase(FHDLTestCase): + def setUp(self): + self.resources = [ + Resource("clk100", 0, DiffPairs("H1", "H2", dir="i"), Clock(100e6)), + Resource("clk50", 0, Pins("K1"), Clock(50e6)), + Resource("user_led", 0, Pins("A0", dir="o")), + Resource("i2c", 0, + Subsignal("scl", Pins("N10", dir="o")), + Subsignal("sda", Pins("N11")) + ) + ] + self.connectors = [ + Connector("pmod", 0, "B0 B1 B2 B3 - -"), + ] + self.cm = ResourceManager(self.resources, self.connectors) + + def test_basic(self): + self.cm = ResourceManager(self.resources, self.connectors) + self.assertEqual(self.cm.resources, { + ("clk100", 0): self.resources[0], + ("clk50", 0): self.resources[1], + ("user_led", 0): self.resources[2], + ("i2c", 0): self.resources[3] + }) + self.assertEqual(self.cm.connectors, { + ("pmod", 0): self.connectors[0], + }) + + def test_add_resources(self): + new_resources = [ + Resource("user_led", 1, Pins("A1", dir="o")) + ] + self.cm.add_resources(new_resources) + self.assertEqual(self.cm.resources, { + ("clk100", 0): self.resources[0], + ("clk50", 0): self.resources[1], + ("user_led", 0): self.resources[2], + ("i2c", 0): self.resources[3], + ("user_led", 1): new_resources[0] + }) + + def test_lookup(self): + r = self.cm.lookup("user_led", 0) + self.assertIs(r, self.cm.resources["user_led", 0]) + + def test_request_basic(self): + r = self.cm.lookup("user_led", 0) + user_led = self.cm.request("user_led", 0) + + self.assertIsInstance(user_led, Pin) + self.assertEqual(user_led.name, "user_led_0") + self.assertEqual(user_led.width, 1) + self.assertEqual(user_led.dir, "o") + + ports = list(self.cm.iter_ports()) + self.assertEqual(len(ports), 1) + + self.assertEqual(list(self.cm.iter_port_constraints()), [ + ("user_led_0__io", ["A0"], {}) + ]) + + def test_request_with_dir(self): + i2c = self.cm.request("i2c", 0, dir={"sda": "o"}) + self.assertIsInstance(i2c, Record) + self.assertIsInstance(i2c.sda, Pin) + self.assertEqual(i2c.sda.dir, "o") + + def test_request_tristate(self): + i2c = self.cm.request("i2c", 0) + self.assertEqual(i2c.sda.dir, "io") + + ports = list(self.cm.iter_ports()) + self.assertEqual(len(ports), 2) + scl, sda = ports + self.assertEqual(ports[1].name, "i2c_0__sda__io") + self.assertEqual(ports[1].width, 1) + + scl_info, sda_info = self.cm.iter_single_ended_pins() + self.assertIs(scl_info[0], i2c.scl) + self.assertIs(scl_info[1].io, scl) + self.assertEqual(scl_info[2], {}) + self.assertEqual(scl_info[3], False) + self.assertIs(sda_info[0], i2c.sda) + self.assertIs(sda_info[1].io, sda) + + self.assertEqual(list(self.cm.iter_port_constraints()), [ + ("i2c_0__scl__io", ["N10"], {}), + ("i2c_0__sda__io", ["N11"], {}) + ]) + + def test_request_diffpairs(self): + clk100 = self.cm.request("clk100", 0) + self.assertIsInstance(clk100, Pin) + self.assertEqual(clk100.dir, "i") + self.assertEqual(clk100.width, 1) + + ports = list(self.cm.iter_ports()) + self.assertEqual(len(ports), 2) + p, n = ports + self.assertEqual(p.name, "clk100_0__p") + self.assertEqual(p.width, clk100.width) + self.assertEqual(n.name, "clk100_0__n") + self.assertEqual(n.width, clk100.width) + + clk100_info, = self.cm.iter_differential_pins() + self.assertIs(clk100_info[0], clk100) + self.assertIs(clk100_info[1].p, p) + self.assertIs(clk100_info[1].n, n) + self.assertEqual(clk100_info[2], {}) + self.assertEqual(clk100_info[3], False) + + self.assertEqual(list(self.cm.iter_port_constraints()), [ + ("clk100_0__p", ["H1"], {}), + ("clk100_0__n", ["H2"], {}), + ]) + + def test_request_inverted(self): + new_resources = [ + Resource("cs", 0, PinsN("X0")), + Resource("clk", 0, DiffPairsN("Y0", "Y1")), + ] + self.cm.add_resources(new_resources) + + cs = self.cm.request("cs") + clk = self.cm.request("clk") + cs_io, clk_p, clk_n = self.cm.iter_ports() + + cs_info, = self.cm.iter_single_ended_pins() + self.assertIs(cs_info[0], cs) + self.assertIs(cs_info[1].io, cs_io) + self.assertEqual(cs_info[2], {}) + self.assertEqual(cs_info[3], True) + + clk_info, = self.cm.iter_differential_pins() + self.assertIs(clk_info[0], clk) + self.assertIs(clk_info[1].p, clk_p) + self.assertIs(clk_info[1].n, clk_n) + self.assertEqual(clk_info[2], {}) + self.assertEqual(clk_info[3], True) + + def test_request_raw(self): + clk50 = self.cm.request("clk50", 0, dir="-") + self.assertIsInstance(clk50, Record) + self.assertIsInstance(clk50.io, Signal) + + ports = list(self.cm.iter_ports()) + self.assertEqual(len(ports), 1) + self.assertIs(ports[0], clk50.io) + + def test_request_raw_diffpairs(self): + clk100 = self.cm.request("clk100", 0, dir="-") + self.assertIsInstance(clk100, Record) + self.assertIsInstance(clk100.p, Signal) + self.assertIsInstance(clk100.n, Signal) + + ports = list(self.cm.iter_ports()) + self.assertEqual(len(ports), 2) + self.assertIs(ports[0], clk100.p) + self.assertIs(ports[1], clk100.n) + + def test_request_via_connector(self): + self.cm.add_resources([ + Resource("spi", 0, + Subsignal("ss", Pins("1", conn=("pmod", 0))), + Subsignal("clk", Pins("2", conn=("pmod", 0))), + Subsignal("miso", Pins("3", conn=("pmod", 0))), + Subsignal("mosi", Pins("4", conn=("pmod", 0))), + ) + ]) + spi0 = self.cm.request("spi", 0) + self.assertEqual(list(self.cm.iter_port_constraints()), [ + ("spi_0__ss__io", ["B0"], {}), + ("spi_0__clk__io", ["B1"], {}), + ("spi_0__miso__io", ["B2"], {}), + ("spi_0__mosi__io", ["B3"], {}), + ]) + + def test_request_via_nested_connector(self): + new_connectors = [ + Connector("pmod_extension", 0, "1 2 3 4 - -", conn=("pmod", 0)), + ] + self.cm.add_connectors(new_connectors) + self.cm.add_resources([ + Resource("spi", 0, + Subsignal("ss", Pins("1", conn=("pmod_extension", 0))), + Subsignal("clk", Pins("2", conn=("pmod_extension", 0))), + Subsignal("miso", Pins("3", conn=("pmod_extension", 0))), + Subsignal("mosi", Pins("4", conn=("pmod_extension", 0))), + ) + ]) + spi0 = self.cm.request("spi", 0) + self.assertEqual(list(self.cm.iter_port_constraints()), [ + ("spi_0__ss__io", ["B0"], {}), + ("spi_0__clk__io", ["B1"], {}), + ("spi_0__miso__io", ["B2"], {}), + ("spi_0__mosi__io", ["B3"], {}), + ]) + + def test_request_clock(self): + clk100 = self.cm.request("clk100", 0) + clk50 = self.cm.request("clk50", 0, dir="i") + clk100_port_p, clk100_port_n, clk50_port = self.cm.iter_ports() + self.assertEqual(list(self.cm.iter_clock_constraints()), [ + (clk100.i, clk100_port_p, 100e6), + (clk50.i, clk50_port, 50e6) + ]) + + def test_add_clock(self): + i2c = self.cm.request("i2c") + self.cm.add_clock_constraint(i2c.scl.o, 100e3) + self.assertEqual(list(self.cm.iter_clock_constraints()), [ + (i2c.scl.o, None, 100e3) + ]) + + def test_wrong_resources(self): + with self.assertRaisesRegex(TypeError, r"^Object 'wrong' is not a Resource$"): + self.cm.add_resources(['wrong']) + + def test_wrong_resources_duplicate(self): + with self.assertRaisesRegex(NameError, + (r"^Trying to add \(resource user_led 0 \(pins o A1\)\), but " + r"\(resource user_led 0 \(pins o A0\)\) has the same name and number$")): + self.cm.add_resources([Resource("user_led", 0, Pins("A1", dir="o"))]) + + def test_wrong_connectors(self): + with self.assertRaisesRegex(TypeError, r"^Object 'wrong' is not a Connector$"): + self.cm.add_connectors(['wrong']) + + def test_wrong_connectors_duplicate(self): + with self.assertRaisesRegex(NameError, + (r"^Trying to add \(connector pmod 0 1=>1 2=>2\), but " + r"\(connector pmod 0 1=>B0 2=>B1 3=>B2 4=>B3\) has the same name and number$")): + self.cm.add_connectors([Connector("pmod", 0, "1 2")]) + + def test_wrong_lookup(self): + with self.assertRaisesRegex(ResourceError, + r"^Resource user_led#1 does not exist$"): + r = self.cm.lookup("user_led", 1) + + def test_wrong_clock_signal(self): + with self.assertRaisesRegex(TypeError, + r"^Object None is not a Signal$"): + self.cm.add_clock_constraint(None, 10e6) + + def test_wrong_clock_frequency(self): + with self.assertRaisesRegex(TypeError, + r"^Frequency must be a number, not None$"): + self.cm.add_clock_constraint(Signal(), None) + + def test_wrong_request_duplicate(self): + with self.assertRaisesRegex(ResourceError, + r"^Resource user_led#0 has already been requested$"): + self.cm.request("user_led", 0) + self.cm.request("user_led", 0) + + def test_wrong_request_duplicate_physical(self): + self.cm.add_resources([ + Resource("clk20", 0, Pins("H1", dir="i")), + ]) + self.cm.request("clk100", 0) + with self.assertRaisesRegex(ResourceError, + (r"^Resource component clk20_0 uses physical pin H1, but it is already " + r"used by resource component clk100_0 that was requested earlier$")): + self.cm.request("clk20", 0) + + def test_wrong_request_with_dir(self): + with self.assertRaisesRegex(TypeError, + (r"^Direction must be one of \"i\", \"o\", \"oe\", \"io\", or \"-\", " + r"not 'wrong'$")): + user_led = self.cm.request("user_led", 0, dir="wrong") + + def test_wrong_request_with_dir_io(self): + with self.assertRaisesRegex(ValueError, + (r"^Direction of \(pins o A0\) cannot be changed from \"o\" to \"i\"; direction " + r"can be changed from \"io\" to \"i\", \"o\", or \"oe\", or from anything " + r"to \"-\"$")): + user_led = self.cm.request("user_led", 0, dir="i") + + def test_wrong_request_with_dir_dict(self): + with self.assertRaisesRegex(TypeError, + (r"^Directions must be a dict, not 'i', because \(resource i2c 0 \(subsignal scl " + r"\(pins o N10\)\) \(subsignal sda \(pins io N11\)\)\) " + r"has subsignals$")): + i2c = self.cm.request("i2c", 0, dir="i") + + def test_wrong_request_with_wrong_xdr(self): + with self.assertRaisesRegex(ValueError, + r"^Data rate of \(pins o A0\) must be a non-negative integer, not -1$"): + user_led = self.cm.request("user_led", 0, xdr=-1) + + def test_wrong_request_with_xdr_dict(self): + with self.assertRaisesRegex(TypeError, + r"^Data rate must be a dict, not 2, because \(resource i2c 0 \(subsignal scl " + r"\(pins o N10\)\) \(subsignal sda \(pins io N11\)\)\) " + r"has subsignals$"): + i2c = self.cm.request("i2c", 0, xdr=2) + + def test_wrong_clock_constraint_twice(self): + clk100 = self.cm.request("clk100") + with self.assertRaisesRegex(ValueError, + (r"^Cannot add clock constraint on \(sig clk100_0__i\), which is already " + r"constrained to 100000000\.0 Hz$")): + self.cm.add_clock_constraint(clk100.i, 1e6) diff --git a/tests/test_compat.py b/tests/test_compat.py new file mode 100644 index 0000000..aeb1617 --- /dev/null +++ b/tests/test_compat.py @@ -0,0 +1,10 @@ +from nmigen.hdl.ir import Fragment +from nmigen.compat import * + +from .utils import * + + +class CompatTestCase(FHDLTestCase): + def test_fragment_get(self): + m = Module() + f = Fragment.get(m, platform=None) diff --git a/tests/test_examples.py b/tests/test_examples.py new file mode 100644 index 0000000..7bc1198 --- /dev/null +++ b/tests/test_examples.py @@ -0,0 +1,33 @@ +import sys +import subprocess +from pathlib import Path + +from .utils import * + + +def example_test(name): + path = (Path(__file__).parent / ".." / "examples" / name).resolve() + def test_function(self): + subprocess.check_call([sys.executable, str(path), "generate", "-t", "v"], + stdout=subprocess.DEVNULL) + return test_function + + +class ExamplesTestCase(FHDLTestCase): + test_alu = example_test("basic/alu.py") + test_alu_hier = example_test("basic/alu_hier.py") + test_arst = example_test("basic/arst.py") + test_cdc = example_test("basic/cdc.py") + test_ctr = example_test("basic/ctr.py") + test_ctr_en = example_test("basic/ctr_en.py") + test_fsm = example_test("basic/fsm.py") + test_gpio = example_test("basic/gpio.py") + test_inst = example_test("basic/inst.py") + test_mem = example_test("basic/mem.py") + test_pmux = example_test("basic/pmux.py") + test_por = example_test("basic/por.py") + + def test_uart(self): + path = (Path(__file__).parent / ".." / "examples" / "basic" / "uart.py").resolve() + subprocess.check_call([sys.executable, str(path), "generate"], + stdout=subprocess.DEVNULL) diff --git a/tests/test_hdl_ast.py b/tests/test_hdl_ast.py new file mode 100644 index 0000000..f14c07b --- /dev/null +++ b/tests/test_hdl_ast.py @@ -0,0 +1,1059 @@ +import warnings +from enum import Enum + +from nmigen.hdl.ast import * + +from .utils import * + + +class UnsignedEnum(Enum): + FOO = 1 + BAR = 2 + BAZ = 3 + + +class SignedEnum(Enum): + FOO = -1 + BAR = 0 + BAZ = +1 + + +class StringEnum(Enum): + FOO = "a" + BAR = "b" + + +class ShapeTestCase(FHDLTestCase): + def test_make(self): + s1 = Shape() + self.assertEqual(s1.width, 1) + self.assertEqual(s1.signed, False) + s2 = Shape(signed=True) + self.assertEqual(s2.width, 1) + self.assertEqual(s2.signed, True) + s3 = Shape(3, True) + self.assertEqual(s3.width, 3) + self.assertEqual(s3.signed, True) + + def test_make_wrong(self): + with self.assertRaisesRegex(TypeError, + r"^Width must be a non-negative integer, not -1$"): + Shape(-1) + + def test_compare_wrong(self): + with self.assertRaisesRegex(TypeError, + r"^Shapes may be compared with other Shapes and \(int, bool\) tuples, not 'hi'$"): + Shape(1, True) == 'hi' + + def test_compare_tuple_wrong(self): + with self.assertRaisesRegex(TypeError, + r"^Shapes may be compared with other Shapes and \(int, bool\) tuples, not \(2, 3\)$"): + Shape(1, True) == (2, 3) + + def test_repr(self): + self.assertEqual(repr(Shape()), "unsigned(1)") + self.assertEqual(repr(Shape(2, True)), "signed(2)") + + def test_tuple(self): + width, signed = Shape() + self.assertEqual(width, 1) + self.assertEqual(signed, False) + + def test_unsigned(self): + s1 = unsigned(2) + self.assertIsInstance(s1, Shape) + self.assertEqual(s1.width, 2) + self.assertEqual(s1.signed, False) + + def test_signed(self): + s1 = signed(2) + self.assertIsInstance(s1, Shape) + self.assertEqual(s1.width, 2) + self.assertEqual(s1.signed, True) + + def test_cast_shape(self): + s1 = Shape.cast(unsigned(1)) + self.assertEqual(s1.width, 1) + self.assertEqual(s1.signed, False) + s2 = Shape.cast(signed(3)) + self.assertEqual(s2.width, 3) + self.assertEqual(s2.signed, True) + + def test_cast_int(self): + s1 = Shape.cast(2) + self.assertEqual(s1.width, 2) + self.assertEqual(s1.signed, False) + + def test_cast_int_wrong(self): + with self.assertRaisesRegex(TypeError, + r"^Width must be a non-negative integer, not -1$"): + Shape.cast(-1) + + def test_cast_tuple(self): + with warnings.catch_warnings(): + warnings.filterwarnings(action="ignore", category=DeprecationWarning) + s1 = Shape.cast((1, True)) + self.assertEqual(s1.width, 1) + self.assertEqual(s1.signed, True) + + def test_cast_tuple_wrong(self): + with warnings.catch_warnings(): + warnings.filterwarnings(action="ignore", category=DeprecationWarning) + with self.assertRaisesRegex(TypeError, + r"^Width must be a non-negative integer, not -1$"): + Shape.cast((-1, True)) + + def test_cast_range(self): + s1 = Shape.cast(range(0, 8)) + self.assertEqual(s1.width, 3) + self.assertEqual(s1.signed, False) + s2 = Shape.cast(range(0, 9)) + self.assertEqual(s2.width, 4) + self.assertEqual(s2.signed, False) + s3 = Shape.cast(range(-7, 8)) + self.assertEqual(s3.width, 4) + self.assertEqual(s3.signed, True) + s4 = Shape.cast(range(0, 1)) + self.assertEqual(s4.width, 1) + self.assertEqual(s4.signed, False) + s5 = Shape.cast(range(-1, 0)) + self.assertEqual(s5.width, 1) + self.assertEqual(s5.signed, True) + s6 = Shape.cast(range(0, 0)) + self.assertEqual(s6.width, 0) + self.assertEqual(s6.signed, False) + s7 = Shape.cast(range(-1, -1)) + self.assertEqual(s7.width, 0) + self.assertEqual(s7.signed, True) + + def test_cast_enum(self): + s1 = Shape.cast(UnsignedEnum) + self.assertEqual(s1.width, 2) + self.assertEqual(s1.signed, False) + s2 = Shape.cast(SignedEnum) + self.assertEqual(s2.width, 2) + self.assertEqual(s2.signed, True) + + def test_cast_enum_bad(self): + with self.assertRaisesRegex(TypeError, + r"^Only enumerations with integer values can be used as value shapes$"): + Shape.cast(StringEnum) + + def test_cast_bad(self): + with self.assertRaisesRegex(TypeError, + r"^Object 'foo' cannot be used as value shape$"): + Shape.cast("foo") + + +class ValueTestCase(FHDLTestCase): + def test_cast(self): + self.assertIsInstance(Value.cast(0), Const) + self.assertIsInstance(Value.cast(True), Const) + c = Const(0) + self.assertIs(Value.cast(c), c) + with self.assertRaisesRegex(TypeError, + r"^Object 'str' cannot be converted to an nMigen value$"): + Value.cast("str") + + def test_cast_enum(self): + e1 = Value.cast(UnsignedEnum.FOO) + self.assertIsInstance(e1, Const) + self.assertEqual(e1.shape(), unsigned(2)) + e2 = Value.cast(SignedEnum.FOO) + self.assertIsInstance(e2, Const) + self.assertEqual(e2.shape(), signed(2)) + + def test_cast_enum_wrong(self): + with self.assertRaisesRegex(TypeError, + r"^Only enumerations with integer values can be used as value shapes$"): + Value.cast(StringEnum.FOO) + + def test_bool(self): + with self.assertRaisesRegex(TypeError, + r"^Attempted to convert nMigen value to Python boolean$"): + if Const(0): + pass + + def test_len(self): + self.assertEqual(len(Const(10)), 4) + + def test_getitem_int(self): + s1 = Const(10)[0] + self.assertIsInstance(s1, Slice) + self.assertEqual(s1.start, 0) + self.assertEqual(s1.stop, 1) + s2 = Const(10)[-1] + self.assertIsInstance(s2, Slice) + self.assertEqual(s2.start, 3) + self.assertEqual(s2.stop, 4) + with self.assertRaisesRegex(IndexError, + r"^Cannot index 5 bits into 4-bit value$"): + Const(10)[5] + + def test_getitem_slice(self): + s1 = Const(10)[1:3] + self.assertIsInstance(s1, Slice) + self.assertEqual(s1.start, 1) + self.assertEqual(s1.stop, 3) + s2 = Const(10)[1:-2] + self.assertIsInstance(s2, Slice) + self.assertEqual(s2.start, 1) + self.assertEqual(s2.stop, 2) + s3 = Const(31)[::2] + self.assertIsInstance(s3, Cat) + self.assertIsInstance(s3.parts[0], Slice) + self.assertEqual(s3.parts[0].start, 0) + self.assertEqual(s3.parts[0].stop, 1) + self.assertIsInstance(s3.parts[1], Slice) + self.assertEqual(s3.parts[1].start, 2) + self.assertEqual(s3.parts[1].stop, 3) + self.assertIsInstance(s3.parts[2], Slice) + self.assertEqual(s3.parts[2].start, 4) + self.assertEqual(s3.parts[2].stop, 5) + + def test_getitem_wrong(self): + with self.assertRaisesRegex(TypeError, + r"^Cannot index value with 'str'$"): + Const(31)["str"] + + def test_shift_left(self): + self.assertRepr(Const(256, unsigned(9)).shift_left(0), + "(cat (const 0'd0) (const 9'd256))") + + self.assertRepr(Const(256, unsigned(9)).shift_left(1), + "(cat (const 1'd0) (const 9'd256))") + self.assertRepr(Const(256, unsigned(9)).shift_left(5), + "(cat (const 5'd0) (const 9'd256))") + self.assertRepr(Const(256, signed(9)).shift_left(1), + "(s (cat (const 1'd0) (const 9'sd-256)))") + self.assertRepr(Const(256, signed(9)).shift_left(5), + "(s (cat (const 5'd0) (const 9'sd-256)))") + + self.assertRepr(Const(256, unsigned(9)).shift_left(-1), + "(slice (const 9'd256) 1:9)") + self.assertRepr(Const(256, unsigned(9)).shift_left(-5), + "(slice (const 9'd256) 5:9)") + self.assertRepr(Const(256, signed(9)).shift_left(-1), + "(s (slice (const 9'sd-256) 1:9))") + self.assertRepr(Const(256, signed(9)).shift_left(-5), + "(s (slice (const 9'sd-256) 5:9))") + self.assertRepr(Const(256, signed(9)).shift_left(-15), + "(s (slice (const 9'sd-256) 9:9))") + + def test_shift_left_wrong(self): + with self.assertRaisesRegex(TypeError, + r"^Shift amount must be an integer, not 'str'$"): + Const(31).shift_left("str") + + def test_shift_right(self): + self.assertRepr(Const(256, unsigned(9)).shift_right(0), + "(slice (const 9'd256) 0:9)") + + self.assertRepr(Const(256, unsigned(9)).shift_right(-1), + "(cat (const 1'd0) (const 9'd256))") + self.assertRepr(Const(256, unsigned(9)).shift_right(-5), + "(cat (const 5'd0) (const 9'd256))") + self.assertRepr(Const(256, signed(9)).shift_right(-1), + "(s (cat (const 1'd0) (const 9'sd-256)))") + self.assertRepr(Const(256, signed(9)).shift_right(-5), + "(s (cat (const 5'd0) (const 9'sd-256)))") + + self.assertRepr(Const(256, unsigned(9)).shift_right(1), + "(slice (const 9'd256) 1:9)") + self.assertRepr(Const(256, unsigned(9)).shift_right(5), + "(slice (const 9'd256) 5:9)") + self.assertRepr(Const(256, signed(9)).shift_right(1), + "(s (slice (const 9'sd-256) 1:9))") + self.assertRepr(Const(256, signed(9)).shift_right(5), + "(s (slice (const 9'sd-256) 5:9))") + self.assertRepr(Const(256, signed(9)).shift_right(15), + "(s (slice (const 9'sd-256) 9:9))") + + def test_shift_right_wrong(self): + with self.assertRaisesRegex(TypeError, + r"^Shift amount must be an integer, not 'str'$"): + Const(31).shift_left("str") + + def test_rotate_left(self): + self.assertRepr(Const(256).rotate_left(1), + "(cat (slice (const 9'd256) 8:9) (slice (const 9'd256) 0:8))") + self.assertRepr(Const(256).rotate_left(7), + "(cat (slice (const 9'd256) 2:9) (slice (const 9'd256) 0:2))") + self.assertRepr(Const(256).rotate_left(-1), + "(cat (slice (const 9'd256) 1:9) (slice (const 9'd256) 0:1))") + self.assertRepr(Const(256).rotate_left(-7), + "(cat (slice (const 9'd256) 7:9) (slice (const 9'd256) 0:7))") + + def test_rotate_left_wrong(self): + with self.assertRaisesRegex(TypeError, + r"^Rotate amount must be an integer, not 'str'$"): + Const(31).rotate_left("str") + + def test_rotate_right(self): + self.assertRepr(Const(256).rotate_right(1), + "(cat (slice (const 9'd256) 1:9) (slice (const 9'd256) 0:1))") + self.assertRepr(Const(256).rotate_right(7), + "(cat (slice (const 9'd256) 7:9) (slice (const 9'd256) 0:7))") + self.assertRepr(Const(256).rotate_right(-1), + "(cat (slice (const 9'd256) 8:9) (slice (const 9'd256) 0:8))") + self.assertRepr(Const(256).rotate_right(-7), + "(cat (slice (const 9'd256) 2:9) (slice (const 9'd256) 0:2))") + + def test_rotate_right_wrong(self): + with self.assertRaisesRegex(TypeError, + r"^Rotate amount must be an integer, not 'str'$"): + Const(31).rotate_right("str") + + +class ConstTestCase(FHDLTestCase): + def test_shape(self): + self.assertEqual(Const(0).shape(), unsigned(1)) + self.assertIsInstance(Const(0).shape(), Shape) + self.assertEqual(Const(1).shape(), unsigned(1)) + self.assertEqual(Const(10).shape(), unsigned(4)) + self.assertEqual(Const(-10).shape(), signed(5)) + + self.assertEqual(Const(1, 4).shape(), unsigned(4)) + self.assertEqual(Const(-1, 4).shape(), signed(4)) + self.assertEqual(Const(1, signed(4)).shape(), signed(4)) + self.assertEqual(Const(0, unsigned(0)).shape(), unsigned(0)) + + def test_shape_wrong(self): + with self.assertRaisesRegex(TypeError, + r"^Width must be a non-negative integer, not -1$"): + Const(1, -1) + + def test_normalization(self): + self.assertEqual(Const(0b10110, signed(5)).value, -10) + + def test_value(self): + self.assertEqual(Const(10).value, 10) + + def test_repr(self): + self.assertEqual(repr(Const(10)), "(const 4'd10)") + self.assertEqual(repr(Const(-10)), "(const 5'sd-10)") + + def test_hash(self): + with self.assertRaises(TypeError): + hash(Const(0)) + + +class OperatorTestCase(FHDLTestCase): + def test_bool(self): + v = Const(0, 4).bool() + self.assertEqual(repr(v), "(b (const 4'd0))") + self.assertEqual(v.shape(), unsigned(1)) + + def test_invert(self): + v = ~Const(0, 4) + self.assertEqual(repr(v), "(~ (const 4'd0))") + self.assertEqual(v.shape(), unsigned(4)) + + def test_as_unsigned(self): + v = Const(-1, signed(4)).as_unsigned() + self.assertEqual(repr(v), "(u (const 4'sd-1))") + self.assertEqual(v.shape(), unsigned(4)) + + def test_as_signed(self): + v = Const(1, unsigned(4)).as_signed() + self.assertEqual(repr(v), "(s (const 4'd1))") + self.assertEqual(v.shape(), signed(4)) + + def test_neg(self): + v1 = -Const(0, unsigned(4)) + self.assertEqual(repr(v1), "(- (const 4'd0))") + self.assertEqual(v1.shape(), signed(5)) + v2 = -Const(0, signed(4)) + self.assertEqual(repr(v2), "(- (const 4'sd0))") + self.assertEqual(v2.shape(), signed(5)) + + def test_add(self): + v1 = Const(0, unsigned(4)) + Const(0, unsigned(6)) + self.assertEqual(repr(v1), "(+ (const 4'd0) (const 6'd0))") + self.assertEqual(v1.shape(), unsigned(7)) + v2 = Const(0, signed(4)) + Const(0, signed(6)) + self.assertEqual(v2.shape(), signed(7)) + v3 = Const(0, signed(4)) + Const(0, unsigned(4)) + self.assertEqual(v3.shape(), signed(6)) + v4 = Const(0, unsigned(4)) + Const(0, signed(4)) + self.assertEqual(v4.shape(), signed(6)) + v5 = 10 + Const(0, 4) + self.assertEqual(v5.shape(), unsigned(5)) + + def test_sub(self): + v1 = Const(0, unsigned(4)) - Const(0, unsigned(6)) + self.assertEqual(repr(v1), "(- (const 4'd0) (const 6'd0))") + self.assertEqual(v1.shape(), unsigned(7)) + v2 = Const(0, signed(4)) - Const(0, signed(6)) + self.assertEqual(v2.shape(), signed(7)) + v3 = Const(0, signed(4)) - Const(0, unsigned(4)) + self.assertEqual(v3.shape(), signed(6)) + v4 = Const(0, unsigned(4)) - Const(0, signed(4)) + self.assertEqual(v4.shape(), signed(6)) + v5 = 10 - Const(0, 4) + self.assertEqual(v5.shape(), unsigned(5)) + + def test_mul(self): + v1 = Const(0, unsigned(4)) * Const(0, unsigned(6)) + self.assertEqual(repr(v1), "(* (const 4'd0) (const 6'd0))") + self.assertEqual(v1.shape(), unsigned(10)) + v2 = Const(0, signed(4)) * Const(0, signed(6)) + self.assertEqual(v2.shape(), signed(10)) + v3 = Const(0, signed(4)) * Const(0, unsigned(4)) + self.assertEqual(v3.shape(), signed(8)) + v5 = 10 * Const(0, 4) + self.assertEqual(v5.shape(), unsigned(8)) + + def test_mod(self): + v1 = Const(0, unsigned(4)) % Const(0, unsigned(6)) + self.assertEqual(repr(v1), "(% (const 4'd0) (const 6'd0))") + self.assertEqual(v1.shape(), unsigned(4)) + v3 = Const(0, signed(4)) % Const(0, unsigned(4)) + self.assertEqual(v3.shape(), signed(4)) + v5 = 10 % Const(0, 4) + self.assertEqual(v5.shape(), unsigned(4)) + + def test_mod_wrong(self): + with self.assertRaisesRegex(NotImplementedError, + r"^Division by a signed value is not supported$"): + Const(0, signed(4)) % Const(0, signed(6)) + + def test_floordiv(self): + v1 = Const(0, unsigned(4)) // Const(0, unsigned(6)) + self.assertEqual(repr(v1), "(// (const 4'd0) (const 6'd0))") + self.assertEqual(v1.shape(), unsigned(4)) + v3 = Const(0, signed(4)) // Const(0, unsigned(4)) + self.assertEqual(v3.shape(), signed(4)) + v5 = 10 // Const(0, 4) + self.assertEqual(v5.shape(), unsigned(4)) + + def test_floordiv_wrong(self): + with self.assertRaisesRegex(NotImplementedError, + r"^Division by a signed value is not supported$"): + Const(0, signed(4)) // Const(0, signed(6)) + + def test_and(self): + v1 = Const(0, unsigned(4)) & Const(0, unsigned(6)) + self.assertEqual(repr(v1), "(& (const 4'd0) (const 6'd0))") + self.assertEqual(v1.shape(), unsigned(6)) + v2 = Const(0, signed(4)) & Const(0, signed(6)) + self.assertEqual(v2.shape(), signed(6)) + v3 = Const(0, signed(4)) & Const(0, unsigned(4)) + self.assertEqual(v3.shape(), signed(5)) + v4 = Const(0, unsigned(4)) & Const(0, signed(4)) + self.assertEqual(v4.shape(), signed(5)) + v5 = 10 & Const(0, 4) + self.assertEqual(v5.shape(), unsigned(4)) + + def test_or(self): + v1 = Const(0, unsigned(4)) | Const(0, unsigned(6)) + self.assertEqual(repr(v1), "(| (const 4'd0) (const 6'd0))") + self.assertEqual(v1.shape(), unsigned(6)) + v2 = Const(0, signed(4)) | Const(0, signed(6)) + self.assertEqual(v2.shape(), signed(6)) + v3 = Const(0, signed(4)) | Const(0, unsigned(4)) + self.assertEqual(v3.shape(), signed(5)) + v4 = Const(0, unsigned(4)) | Const(0, signed(4)) + self.assertEqual(v4.shape(), signed(5)) + v5 = 10 | Const(0, 4) + self.assertEqual(v5.shape(), unsigned(4)) + + def test_xor(self): + v1 = Const(0, unsigned(4)) ^ Const(0, unsigned(6)) + self.assertEqual(repr(v1), "(^ (const 4'd0) (const 6'd0))") + self.assertEqual(v1.shape(), unsigned(6)) + v2 = Const(0, signed(4)) ^ Const(0, signed(6)) + self.assertEqual(v2.shape(), signed(6)) + v3 = Const(0, signed(4)) ^ Const(0, unsigned(4)) + self.assertEqual(v3.shape(), signed(5)) + v4 = Const(0, unsigned(4)) ^ Const(0, signed(4)) + self.assertEqual(v4.shape(), signed(5)) + v5 = 10 ^ Const(0, 4) + self.assertEqual(v5.shape(), unsigned(4)) + + def test_shl(self): + v1 = Const(1, 4) << Const(4) + self.assertEqual(repr(v1), "(<< (const 4'd1) (const 3'd4))") + self.assertEqual(v1.shape(), unsigned(11)) + + def test_shl_wrong(self): + with self.assertRaisesRegex(TypeError, + r"^Shift amount must be unsigned$"): + 1 << Const(0, signed(6)) + with self.assertRaisesRegex(TypeError, + r"^Shift amount must be unsigned$"): + Const(1, unsigned(4)) << -1 + + def test_shr(self): + v1 = Const(1, 4) >> Const(4) + self.assertEqual(repr(v1), "(>> (const 4'd1) (const 3'd4))") + self.assertEqual(v1.shape(), unsigned(4)) + + def test_shr_wrong(self): + with self.assertRaisesRegex(TypeError, + r"^Shift amount must be unsigned$"): + 1 << Const(0, signed(6)) + with self.assertRaisesRegex(TypeError, + r"^Shift amount must be unsigned$"): + Const(1, unsigned(4)) << -1 + + def test_lt(self): + v = Const(0, 4) < Const(0, 6) + self.assertEqual(repr(v), "(< (const 4'd0) (const 6'd0))") + self.assertEqual(v.shape(), unsigned(1)) + + def test_le(self): + v = Const(0, 4) <= Const(0, 6) + self.assertEqual(repr(v), "(<= (const 4'd0) (const 6'd0))") + self.assertEqual(v.shape(), unsigned(1)) + + def test_gt(self): + v = Const(0, 4) > Const(0, 6) + self.assertEqual(repr(v), "(> (const 4'd0) (const 6'd0))") + self.assertEqual(v.shape(), unsigned(1)) + + def test_ge(self): + v = Const(0, 4) >= Const(0, 6) + self.assertEqual(repr(v), "(>= (const 4'd0) (const 6'd0))") + self.assertEqual(v.shape(), unsigned(1)) + + def test_eq(self): + v = Const(0, 4) == Const(0, 6) + self.assertEqual(repr(v), "(== (const 4'd0) (const 6'd0))") + self.assertEqual(v.shape(), unsigned(1)) + + def test_ne(self): + v = Const(0, 4) != Const(0, 6) + self.assertEqual(repr(v), "(!= (const 4'd0) (const 6'd0))") + self.assertEqual(v.shape(), unsigned(1)) + + def test_mux(self): + s = Const(0) + v1 = Mux(s, Const(0, unsigned(4)), Const(0, unsigned(6))) + self.assertEqual(repr(v1), "(m (const 1'd0) (const 4'd0) (const 6'd0))") + self.assertEqual(v1.shape(), unsigned(6)) + v2 = Mux(s, Const(0, signed(4)), Const(0, signed(6))) + self.assertEqual(v2.shape(), signed(6)) + v3 = Mux(s, Const(0, signed(4)), Const(0, unsigned(4))) + self.assertEqual(v3.shape(), signed(5)) + v4 = Mux(s, Const(0, unsigned(4)), Const(0, signed(4))) + self.assertEqual(v4.shape(), signed(5)) + + def test_mux_wide(self): + s = Const(0b100) + v = Mux(s, Const(0, unsigned(4)), Const(0, unsigned(6))) + self.assertEqual(repr(v), "(m (b (const 3'd4)) (const 4'd0) (const 6'd0))") + + def test_mux_bool(self): + v = Mux(True, Const(0), Const(0)) + self.assertEqual(repr(v), "(m (const 1'd1) (const 1'd0) (const 1'd0))") + + def test_bool(self): + v = Const(0).bool() + self.assertEqual(repr(v), "(b (const 1'd0))") + self.assertEqual(v.shape(), unsigned(1)) + + def test_any(self): + v = Const(0b101).any() + self.assertEqual(repr(v), "(r| (const 3'd5))") + + def test_all(self): + v = Const(0b101).all() + self.assertEqual(repr(v), "(r& (const 3'd5))") + + def test_xor(self): + v = Const(0b101).xor() + self.assertEqual(repr(v), "(r^ (const 3'd5))") + + def test_matches(self): + s = Signal(4) + self.assertRepr(s.matches(), "(const 1'd0)") + self.assertRepr(s.matches(1), """ + (== (sig s) (const 1'd1)) + """) + self.assertRepr(s.matches(0, 1), """ + (r| (cat (== (sig s) (const 1'd0)) (== (sig s) (const 1'd1)))) + """) + self.assertRepr(s.matches("10--"), """ + (== (& (sig s) (const 4'd12)) (const 4'd8)) + """) + self.assertRepr(s.matches("1 0--"), """ + (== (& (sig s) (const 4'd12)) (const 4'd8)) + """) + + def test_matches_enum(self): + s = Signal(SignedEnum) + self.assertRepr(s.matches(SignedEnum.FOO), """ + (== (sig s) (const 1'sd-1)) + """) + + def test_matches_width_wrong(self): + s = Signal(4) + with self.assertRaisesRegex(SyntaxError, + r"^Match pattern '--' must have the same width as match value \(which is 4\)$"): + s.matches("--") + with self.assertWarnsRegex(SyntaxWarning, + (r"^Match pattern '10110' is wider than match value \(which has width 4\); " + r"comparison will never be true$")): + s.matches(0b10110) + + def test_matches_bits_wrong(self): + s = Signal(4) + with self.assertRaisesRegex(SyntaxError, + (r"^Match pattern 'abc' must consist of 0, 1, and - \(don't care\) bits, " + r"and may include whitespace$")): + s.matches("abc") + + def test_matches_pattern_wrong(self): + s = Signal(4) + with self.assertRaisesRegex(SyntaxError, + r"^Match pattern must be an integer, a string, or an enumeration, not 1\.0$"): + s.matches(1.0) + + def test_hash(self): + with self.assertRaises(TypeError): + hash(Const(0) + Const(0)) + + +class SliceTestCase(FHDLTestCase): + def test_shape(self): + s1 = Const(10)[2] + self.assertEqual(s1.shape(), unsigned(1)) + self.assertIsInstance(s1.shape(), Shape) + s2 = Const(-10)[0:2] + self.assertEqual(s2.shape(), unsigned(2)) + + def test_start_end_negative(self): + c = Const(0, 8) + s1 = Slice(c, 0, -1) + self.assertEqual((s1.start, s1.stop), (0, 7)) + s1 = Slice(c, -4, -1) + self.assertEqual((s1.start, s1.stop), (4, 7)) + + def test_start_end_wrong(self): + with self.assertRaisesRegex(TypeError, + r"^Slice start must be an integer, not 'x'$"): + Slice(0, "x", 1) + with self.assertRaisesRegex(TypeError, + r"^Slice stop must be an integer, not 'x'$"): + Slice(0, 1, "x") + + def test_start_end_out_of_range(self): + c = Const(0, 8) + with self.assertRaisesRegex(IndexError, + r"^Cannot start slice 10 bits into 8-bit value$"): + Slice(c, 10, 12) + with self.assertRaisesRegex(IndexError, + r"^Cannot stop slice 12 bits into 8-bit value$"): + Slice(c, 0, 12) + with self.assertRaisesRegex(IndexError, + r"^Slice start 4 must be less than slice stop 2$"): + Slice(c, 4, 2) + + def test_repr(self): + s1 = Const(10)[2] + self.assertEqual(repr(s1), "(slice (const 4'd10) 2:3)") + + +class BitSelectTestCase(FHDLTestCase): + def setUp(self): + self.c = Const(0, 8) + self.s = Signal(range(self.c.width)) + + def test_shape(self): + s1 = self.c.bit_select(self.s, 2) + self.assertIsInstance(s1, Part) + self.assertEqual(s1.shape(), unsigned(2)) + self.assertIsInstance(s1.shape(), Shape) + s2 = self.c.bit_select(self.s, 0) + self.assertIsInstance(s2, Part) + self.assertEqual(s2.shape(), unsigned(0)) + + def test_stride(self): + s1 = self.c.bit_select(self.s, 2) + self.assertIsInstance(s1, Part) + self.assertEqual(s1.stride, 1) + + def test_const(self): + s1 = self.c.bit_select(1, 2) + self.assertIsInstance(s1, Slice) + self.assertRepr(s1, """(slice (const 8'd0) 1:3)""") + + def test_width_wrong(self): + with self.assertRaises(TypeError): + self.c.bit_select(self.s, -1) + + def test_repr(self): + s = self.c.bit_select(self.s, 2) + self.assertEqual(repr(s), "(part (const 8'd0) (sig s) 2 1)") + + +class WordSelectTestCase(FHDLTestCase): + def setUp(self): + self.c = Const(0, 8) + self.s = Signal(range(self.c.width)) + + def test_shape(self): + s1 = self.c.word_select(self.s, 2) + self.assertIsInstance(s1, Part) + self.assertEqual(s1.shape(), unsigned(2)) + self.assertIsInstance(s1.shape(), Shape) + + def test_stride(self): + s1 = self.c.word_select(self.s, 2) + self.assertIsInstance(s1, Part) + self.assertEqual(s1.stride, 2) + + def test_const(self): + s1 = self.c.word_select(1, 2) + self.assertIsInstance(s1, Slice) + self.assertRepr(s1, """(slice (const 8'd0) 2:4)""") + + def test_width_wrong(self): + with self.assertRaises(TypeError): + self.c.word_select(self.s, 0) + with self.assertRaises(TypeError): + self.c.word_select(self.s, -1) + + def test_repr(self): + s = self.c.word_select(self.s, 2) + self.assertEqual(repr(s), "(part (const 8'd0) (sig s) 2 2)") + + +class CatTestCase(FHDLTestCase): + def test_shape(self): + c0 = Cat() + self.assertEqual(c0.shape(), unsigned(0)) + self.assertIsInstance(c0.shape(), Shape) + c1 = Cat(Const(10)) + self.assertEqual(c1.shape(), unsigned(4)) + c2 = Cat(Const(10), Const(1)) + self.assertEqual(c2.shape(), unsigned(5)) + c3 = Cat(Const(10), Const(1), Const(0)) + self.assertEqual(c3.shape(), unsigned(6)) + + def test_repr(self): + c1 = Cat(Const(10), Const(1)) + self.assertEqual(repr(c1), "(cat (const 4'd10) (const 1'd1))") + + +class ReplTestCase(FHDLTestCase): + def test_shape(self): + s1 = Repl(Const(10), 3) + self.assertEqual(s1.shape(), unsigned(12)) + self.assertIsInstance(s1.shape(), Shape) + s2 = Repl(Const(10), 0) + self.assertEqual(s2.shape(), unsigned(0)) + + def test_count_wrong(self): + with self.assertRaises(TypeError): + Repl(Const(10), -1) + with self.assertRaises(TypeError): + Repl(Const(10), "str") + + def test_repr(self): + s = Repl(Const(10), 3) + self.assertEqual(repr(s), "(repl (const 4'd10) 3)") + + +class ArrayTestCase(FHDLTestCase): + def test_acts_like_array(self): + a = Array([1,2,3]) + self.assertSequenceEqual(a, [1,2,3]) + self.assertEqual(a[1], 2) + a[1] = 4 + self.assertSequenceEqual(a, [1,4,3]) + del a[1] + self.assertSequenceEqual(a, [1,3]) + a.insert(1, 2) + self.assertSequenceEqual(a, [1,2,3]) + + def test_becomes_immutable(self): + a = Array([1,2,3]) + s1 = Signal(range(len(a))) + s2 = Signal(range(len(a))) + v1 = a[s1] + v2 = a[s2] + with self.assertRaisesRegex(ValueError, + r"^Array can no longer be mutated after it was indexed with a value at "): + a[1] = 2 + with self.assertRaisesRegex(ValueError, + r"^Array can no longer be mutated after it was indexed with a value at "): + del a[1] + with self.assertRaisesRegex(ValueError, + r"^Array can no longer be mutated after it was indexed with a value at "): + a.insert(1, 2) + + def test_repr(self): + a = Array([1,2,3]) + self.assertEqual(repr(a), "(array mutable [1, 2, 3])") + s = Signal(range(len(a))) + v = a[s] + self.assertEqual(repr(a), "(array [1, 2, 3])") + + +class ArrayProxyTestCase(FHDLTestCase): + def test_index_shape(self): + m = Array(Array(x * y for y in range(1, 4)) for x in range(1, 4)) + a = Signal(range(3)) + b = Signal(range(3)) + v = m[a][b] + self.assertEqual(v.shape(), unsigned(4)) + + def test_attr_shape(self): + from collections import namedtuple + pair = namedtuple("pair", ("p", "n")) + a = Array(pair(i, -i) for i in range(10)) + s = Signal(range(len(a))) + v = a[s] + self.assertEqual(v.p.shape(), unsigned(4)) + self.assertEqual(v.n.shape(), signed(5)) + + def test_attr_shape_signed(self): + # [unsigned(1), unsigned(1)] → unsigned(1) + a1 = Array([1, 1]) + v1 = a1[Const(0)] + self.assertEqual(v1.shape(), unsigned(1)) + # [signed(1), signed(1)] → signed(1) + a2 = Array([-1, -1]) + v2 = a2[Const(0)] + self.assertEqual(v2.shape(), signed(1)) + # [unsigned(1), signed(2)] → signed(2) + a3 = Array([1, -2]) + v3 = a3[Const(0)] + self.assertEqual(v3.shape(), signed(2)) + # [unsigned(1), signed(1)] → signed(2); 1st operand padded with sign bit! + a4 = Array([1, -1]) + v4 = a4[Const(0)] + self.assertEqual(v4.shape(), signed(2)) + # [unsigned(2), signed(1)] → signed(3); 1st operand padded with sign bit! + a5 = Array([1, -1]) + v5 = a5[Const(0)] + self.assertEqual(v5.shape(), signed(2)) + + def test_repr(self): + a = Array([1, 2, 3]) + s = Signal(range(3)) + v = a[s] + self.assertEqual(repr(v), "(proxy (array [1, 2, 3]) (sig s))") + + +class SignalTestCase(FHDLTestCase): + def test_shape(self): + s1 = Signal() + self.assertEqual(s1.shape(), unsigned(1)) + self.assertIsInstance(s1.shape(), Shape) + s2 = Signal(2) + self.assertEqual(s2.shape(), unsigned(2)) + s3 = Signal(unsigned(2)) + self.assertEqual(s3.shape(), unsigned(2)) + s4 = Signal(signed(2)) + self.assertEqual(s4.shape(), signed(2)) + s5 = Signal(0) + self.assertEqual(s5.shape(), unsigned(0)) + s6 = Signal(range(16)) + self.assertEqual(s6.shape(), unsigned(4)) + s7 = Signal(range(4, 16)) + self.assertEqual(s7.shape(), unsigned(4)) + s8 = Signal(range(-4, 16)) + self.assertEqual(s8.shape(), signed(5)) + s9 = Signal(range(-20, 16)) + self.assertEqual(s9.shape(), signed(6)) + s10 = Signal(range(0)) + self.assertEqual(s10.shape(), unsigned(0)) + s11 = Signal(range(1)) + self.assertEqual(s11.shape(), unsigned(1)) + + def test_shape_wrong(self): + with self.assertRaisesRegex(TypeError, + r"^Width must be a non-negative integer, not -10$"): + Signal(-10) + + def test_name(self): + s1 = Signal() + self.assertEqual(s1.name, "s1") + s2 = Signal(name="sig") + self.assertEqual(s2.name, "sig") + + def test_reset(self): + s1 = Signal(4, reset=0b111, reset_less=True) + self.assertEqual(s1.reset, 0b111) + self.assertEqual(s1.reset_less, True) + + def test_reset_enum(self): + s1 = Signal(2, reset=UnsignedEnum.BAR) + self.assertEqual(s1.reset, 2) + with self.assertRaisesRegex(TypeError, + r"^Reset value has to be an int or an integral Enum$" + ): + Signal(1, reset=StringEnum.FOO) + + def test_reset_narrow(self): + with self.assertWarnsRegex(SyntaxWarning, + r"^Reset value 8 requires 4 bits to represent, but the signal only has 3 bits$"): + Signal(3, reset=8) + with self.assertWarnsRegex(SyntaxWarning, + r"^Reset value 4 requires 4 bits to represent, but the signal only has 3 bits$"): + Signal(signed(3), reset=4) + with self.assertWarnsRegex(SyntaxWarning, + r"^Reset value -5 requires 4 bits to represent, but the signal only has 3 bits$"): + Signal(signed(3), reset=-5) + + def test_attrs(self): + s1 = Signal() + self.assertEqual(s1.attrs, {}) + s2 = Signal(attrs={"no_retiming": True}) + self.assertEqual(s2.attrs, {"no_retiming": True}) + + def test_repr(self): + s1 = Signal() + self.assertEqual(repr(s1), "(sig s1)") + + def test_like(self): + s1 = Signal.like(Signal(4)) + self.assertEqual(s1.shape(), unsigned(4)) + s2 = Signal.like(Signal(range(-15, 1))) + self.assertEqual(s2.shape(), signed(5)) + s3 = Signal.like(Signal(4, reset=0b111, reset_less=True)) + self.assertEqual(s3.reset, 0b111) + self.assertEqual(s3.reset_less, True) + s4 = Signal.like(Signal(attrs={"no_retiming": True})) + self.assertEqual(s4.attrs, {"no_retiming": True}) + s5 = Signal.like(Signal(decoder=str)) + self.assertEqual(s5.decoder, str) + s6 = Signal.like(10) + self.assertEqual(s6.shape(), unsigned(4)) + s7 = [Signal.like(Signal(4))][0] + self.assertEqual(s7.name, "$like") + s8 = Signal.like(s1, name_suffix="_ff") + self.assertEqual(s8.name, "s1_ff") + + def test_decoder(self): + class Color(Enum): + RED = 1 + BLUE = 2 + s = Signal(decoder=Color) + self.assertEqual(s.decoder(1), "RED/1") + self.assertEqual(s.decoder(3), "3") + + def test_enum(self): + s1 = Signal(UnsignedEnum) + self.assertEqual(s1.shape(), unsigned(2)) + s2 = Signal(SignedEnum) + self.assertEqual(s2.shape(), signed(2)) + self.assertEqual(s2.decoder(SignedEnum.FOO), "FOO/-1") + + +class ClockSignalTestCase(FHDLTestCase): + def test_domain(self): + s1 = ClockSignal() + self.assertEqual(s1.domain, "sync") + s2 = ClockSignal("pix") + self.assertEqual(s2.domain, "pix") + + with self.assertRaisesRegex(TypeError, + r"^Clock domain name must be a string, not 1$"): + ClockSignal(1) + + def test_shape(self): + s1 = ClockSignal() + self.assertEqual(s1.shape(), unsigned(1)) + self.assertIsInstance(s1.shape(), Shape) + + def test_repr(self): + s1 = ClockSignal() + self.assertEqual(repr(s1), "(clk sync)") + + def test_wrong_name_comb(self): + with self.assertRaisesRegex(ValueError, + r"^Domain 'comb' does not have a clock$"): + ClockSignal("comb") + + +class ResetSignalTestCase(FHDLTestCase): + def test_domain(self): + s1 = ResetSignal() + self.assertEqual(s1.domain, "sync") + s2 = ResetSignal("pix") + self.assertEqual(s2.domain, "pix") + + with self.assertRaisesRegex(TypeError, + r"^Clock domain name must be a string, not 1$"): + ResetSignal(1) + + def test_shape(self): + s1 = ResetSignal() + self.assertEqual(s1.shape(), unsigned(1)) + self.assertIsInstance(s1.shape(), Shape) + + def test_repr(self): + s1 = ResetSignal() + self.assertEqual(repr(s1), "(rst sync)") + + def test_wrong_name_comb(self): + with self.assertRaisesRegex(ValueError, + r"^Domain 'comb' does not have a reset$"): + ResetSignal("comb") + + +class MockUserValue(UserValue): + def __init__(self, lowered): + super().__init__() + self.lower_count = 0 + self.lowered = lowered + + def lower(self): + self.lower_count += 1 + return self.lowered + + +class UserValueTestCase(FHDLTestCase): + def test_shape(self): + uv = MockUserValue(1) + self.assertEqual(uv.shape(), unsigned(1)) + self.assertIsInstance(uv.shape(), Shape) + uv.lowered = 2 + self.assertEqual(uv.shape(), unsigned(1)) + self.assertEqual(uv.lower_count, 1) + + def test_lower_to_user_value(self): + uv = MockUserValue(MockUserValue(1)) + self.assertEqual(uv.shape(), unsigned(1)) + self.assertIsInstance(uv.shape(), Shape) + uv.lowered = MockUserValue(2) + self.assertEqual(uv.shape(), unsigned(1)) + self.assertEqual(uv.lower_count, 1) + + +class SampleTestCase(FHDLTestCase): + def test_const(self): + s = Sample(1, 1, "sync") + self.assertEqual(s.shape(), unsigned(1)) + + def test_signal(self): + s1 = Sample(Signal(2), 1, "sync") + self.assertEqual(s1.shape(), unsigned(2)) + s2 = Sample(ClockSignal(), 1, "sync") + s3 = Sample(ResetSignal(), 1, "sync") + + def test_wrong_value_operator(self): + with self.assertRaisesRegex(TypeError, + (r"^Sampled value must be a signal or a constant, not " + r"\(\+ \(sig \$signal\) \(const 1'd1\)\)$")): + Sample(Signal() + 1, 1, "sync") + + def test_wrong_clocks_neg(self): + with self.assertRaisesRegex(ValueError, + r"^Cannot sample a value 1 cycles in the future$"): + Sample(Signal(), -1, "sync") + + def test_wrong_domain(self): + with self.assertRaisesRegex(TypeError, + r"^Domain name must be a string or None, not 0$"): + Sample(Signal(), 1, 0) + + +class InitialTestCase(FHDLTestCase): + def test_initial(self): + i = Initial() + self.assertEqual(i.shape(), unsigned(1)) diff --git a/tests/test_hdl_cd.py b/tests/test_hdl_cd.py new file mode 100644 index 0000000..4e93fbc --- /dev/null +++ b/tests/test_hdl_cd.py @@ -0,0 +1,79 @@ +from nmigen.hdl.cd import * + +from .utils import * + + +class ClockDomainTestCase(FHDLTestCase): + def test_name(self): + sync = ClockDomain() + self.assertEqual(sync.name, "sync") + self.assertEqual(sync.clk.name, "clk") + self.assertEqual(sync.rst.name, "rst") + self.assertEqual(sync.local, False) + pix = ClockDomain() + self.assertEqual(pix.name, "pix") + self.assertEqual(pix.clk.name, "pix_clk") + self.assertEqual(pix.rst.name, "pix_rst") + cd_pix = ClockDomain() + self.assertEqual(pix.name, "pix") + dom = [ClockDomain("foo")][0] + self.assertEqual(dom.name, "foo") + with self.assertRaisesRegex(ValueError, + r"^Clock domain name must be specified explicitly$"): + ClockDomain() + cd_reset = ClockDomain(local=True) + self.assertEqual(cd_reset.local, True) + + def test_edge(self): + sync = ClockDomain() + self.assertEqual(sync.clk_edge, "pos") + sync = ClockDomain(clk_edge="pos") + self.assertEqual(sync.clk_edge, "pos") + sync = ClockDomain(clk_edge="neg") + self.assertEqual(sync.clk_edge, "neg") + + def test_edge_wrong(self): + with self.assertRaisesRegex(ValueError, + r"^Domain clock edge must be one of 'pos' or 'neg', not 'xxx'$"): + ClockDomain("sync", clk_edge="xxx") + + def test_with_reset(self): + pix = ClockDomain() + self.assertIsNotNone(pix.clk) + self.assertIsNotNone(pix.rst) + self.assertFalse(pix.async_reset) + + def test_without_reset(self): + pix = ClockDomain(reset_less=True) + self.assertIsNotNone(pix.clk) + self.assertIsNone(pix.rst) + self.assertFalse(pix.async_reset) + + def test_async_reset(self): + pix = ClockDomain(async_reset=True) + self.assertIsNotNone(pix.clk) + self.assertIsNotNone(pix.rst) + self.assertTrue(pix.async_reset) + + def test_rename(self): + sync = ClockDomain() + self.assertEqual(sync.name, "sync") + self.assertEqual(sync.clk.name, "clk") + self.assertEqual(sync.rst.name, "rst") + sync.rename("pix") + self.assertEqual(sync.name, "pix") + self.assertEqual(sync.clk.name, "pix_clk") + self.assertEqual(sync.rst.name, "pix_rst") + + def test_rename_reset_less(self): + sync = ClockDomain(reset_less=True) + self.assertEqual(sync.name, "sync") + self.assertEqual(sync.clk.name, "clk") + sync.rename("pix") + self.assertEqual(sync.name, "pix") + self.assertEqual(sync.clk.name, "pix_clk") + + def test_wrong_name_comb(self): + with self.assertRaisesRegex(ValueError, + r"^Domain 'comb' may not be clocked$"): + comb = ClockDomain() diff --git a/tests/test_hdl_dsl.py b/tests/test_hdl_dsl.py new file mode 100644 index 0000000..8ec1be7 --- /dev/null +++ b/tests/test_hdl_dsl.py @@ -0,0 +1,770 @@ +# nmigen: UnusedElaboratable=no + +from collections import OrderedDict +from enum import Enum + +from nmigen.hdl.ast import * +from nmigen.hdl.cd import * +from nmigen.hdl.dsl import * + +from .utils import * + + +class DSLTestCase(FHDLTestCase): + def setUp(self): + self.s1 = Signal() + self.s2 = Signal() + self.s3 = Signal() + self.c1 = Signal() + self.c2 = Signal() + self.c3 = Signal() + self.w1 = Signal(4) + + def test_cant_inherit(self): + with self.assertRaisesRegex(SyntaxError, + (r"^Instead of inheriting from `Module`, inherit from `Elaboratable` and " + r"return a `Module` from the `elaborate\(self, platform\)` method$")): + class ORGate(Module): + pass + + def test_d_comb(self): + m = Module() + m.d.comb += self.c1.eq(1) + m._flush() + self.assertEqual(m._driving[self.c1], None) + self.assertRepr(m._statements, """( + (eq (sig c1) (const 1'd1)) + )""") + + def test_d_sync(self): + m = Module() + m.d.sync += self.c1.eq(1) + m._flush() + self.assertEqual(m._driving[self.c1], "sync") + self.assertRepr(m._statements, """( + (eq (sig c1) (const 1'd1)) + )""") + + def test_d_pix(self): + m = Module() + m.d.pix += self.c1.eq(1) + m._flush() + self.assertEqual(m._driving[self.c1], "pix") + self.assertRepr(m._statements, """( + (eq (sig c1) (const 1'd1)) + )""") + + def test_d_index(self): + m = Module() + m.d["pix"] += self.c1.eq(1) + m._flush() + self.assertEqual(m._driving[self.c1], "pix") + self.assertRepr(m._statements, """( + (eq (sig c1) (const 1'd1)) + )""") + + def test_d_no_conflict(self): + m = Module() + m.d.comb += self.w1[0].eq(1) + m.d.comb += self.w1[1].eq(1) + + def test_d_conflict(self): + m = Module() + with self.assertRaisesRegex(SyntaxError, + (r"^Driver-driver conflict: trying to drive \(sig c1\) from d\.sync, but it " + r"is already driven from d\.comb$")): + m.d.comb += self.c1.eq(1) + m.d.sync += self.c1.eq(1) + + def test_d_wrong(self): + m = Module() + with self.assertRaisesRegex(AttributeError, + r"^Cannot assign 'd\.pix' attribute; did you mean 'd.pix \+='\?$"): + m.d.pix = None + + def test_d_asgn_wrong(self): + m = Module() + with self.assertRaisesRegex(SyntaxError, + r"^Only assignments and property checks may be appended to d\.sync$"): + m.d.sync += Switch(self.s1, {}) + + def test_comb_wrong(self): + m = Module() + with self.assertRaisesRegex(AttributeError, + r"^'Module' object has no attribute 'comb'; did you mean 'd\.comb'\?$"): + m.comb += self.c1.eq(1) + + def test_sync_wrong(self): + m = Module() + with self.assertRaisesRegex(AttributeError, + r"^'Module' object has no attribute 'sync'; did you mean 'd\.sync'\?$"): + m.sync += self.c1.eq(1) + + def test_attr_wrong(self): + m = Module() + with self.assertRaisesRegex(AttributeError, + r"^'Module' object has no attribute 'nonexistentattr'$"): + m.nonexistentattr + + def test_d_suspicious(self): + m = Module() + with self.assertWarnsRegex(SyntaxWarning, + (r"^Using '\.d\.submodules' would add statements to clock domain " + r"'submodules'; did you mean \.submodules instead\?$")): + m.d.submodules += [] + + def test_clock_signal(self): + m = Module() + m.d.comb += ClockSignal("pix").eq(ClockSignal()) + self.assertRepr(m._statements, """ + ( + (eq (clk pix) (clk sync)) + ) + """) + + def test_reset_signal(self): + m = Module() + m.d.comb += ResetSignal("pix").eq(1) + self.assertRepr(m._statements, """ + ( + (eq (rst pix) (const 1'd1)) + ) + """) + + def test_sample_domain(self): + m = Module() + i = Signal() + o1 = Signal() + o2 = Signal() + o3 = Signal() + m.d.sync += o1.eq(Past(i)) + m.d.pix += o2.eq(Past(i)) + m.d.pix += o3.eq(Past(i, domain="sync")) + f = m.elaborate(platform=None) + self.assertRepr(f.statements, """ + ( + (eq (sig o1) (sample (sig i) @ sync[1])) + (eq (sig o2) (sample (sig i) @ pix[1])) + (eq (sig o3) (sample (sig i) @ sync[1])) + ) + """) + + def test_If(self): + m = Module() + with m.If(self.s1): + m.d.comb += self.c1.eq(1) + m._flush() + self.assertRepr(m._statements, """ + ( + (switch (cat (sig s1)) + (case 1 (eq (sig c1) (const 1'd1))) + ) + ) + """) + + def test_If_Elif(self): + m = Module() + with m.If(self.s1): + m.d.comb += self.c1.eq(1) + with m.Elif(self.s2): + m.d.sync += self.c2.eq(0) + m._flush() + self.assertRepr(m._statements, """ + ( + (switch (cat (sig s1) (sig s2)) + (case -1 (eq (sig c1) (const 1'd1))) + (case 1- (eq (sig c2) (const 1'd0))) + ) + ) + """) + + def test_If_Elif_Else(self): + m = Module() + with m.If(self.s1): + m.d.comb += self.c1.eq(1) + with m.Elif(self.s2): + m.d.sync += self.c2.eq(0) + with m.Else(): + m.d.comb += self.c3.eq(1) + m._flush() + self.assertRepr(m._statements, """ + ( + (switch (cat (sig s1) (sig s2)) + (case -1 (eq (sig c1) (const 1'd1))) + (case 1- (eq (sig c2) (const 1'd0))) + (default (eq (sig c3) (const 1'd1))) + ) + ) + """) + + def test_If_If(self): + m = Module() + with m.If(self.s1): + m.d.comb += self.c1.eq(1) + with m.If(self.s2): + m.d.comb += self.c2.eq(1) + m._flush() + self.assertRepr(m._statements, """ + ( + (switch (cat (sig s1)) + (case 1 (eq (sig c1) (const 1'd1))) + ) + (switch (cat (sig s2)) + (case 1 (eq (sig c2) (const 1'd1))) + ) + ) + """) + + def test_If_nested_If(self): + m = Module() + with m.If(self.s1): + m.d.comb += self.c1.eq(1) + with m.If(self.s2): + m.d.comb += self.c2.eq(1) + m._flush() + self.assertRepr(m._statements, """ + ( + (switch (cat (sig s1)) + (case 1 (eq (sig c1) (const 1'd1)) + (switch (cat (sig s2)) + (case 1 (eq (sig c2) (const 1'd1))) + ) + ) + ) + ) + """) + + def test_If_dangling_Else(self): + m = Module() + with m.If(self.s1): + m.d.comb += self.c1.eq(1) + with m.If(self.s2): + m.d.comb += self.c2.eq(1) + with m.Else(): + m.d.comb += self.c3.eq(1) + m._flush() + self.assertRepr(m._statements, """ + ( + (switch (cat (sig s1)) + (case 1 + (eq (sig c1) (const 1'd1)) + (switch (cat (sig s2)) + (case 1 (eq (sig c2) (const 1'd1))) + ) + ) + (default + (eq (sig c3) (const 1'd1)) + ) + ) + ) + """) + + def test_Elif_wrong(self): + m = Module() + with self.assertRaisesRegex(SyntaxError, + r"^Elif without preceding If$"): + with m.Elif(self.s2): + pass + + def test_Else_wrong(self): + m = Module() + with self.assertRaisesRegex(SyntaxError, + r"^Else without preceding If\/Elif$"): + with m.Else(): + pass + + def test_If_wide(self): + m = Module() + with m.If(self.w1): + m.d.comb += self.c1.eq(1) + m._flush() + self.assertRepr(m._statements, """ + ( + (switch (cat (b (sig w1))) + (case 1 (eq (sig c1) (const 1'd1))) + ) + ) + """) + + def test_If_signed_suspicious(self): + m = Module() + with self.assertWarnsRegex(SyntaxWarning, + (r"^Signed values in If\/Elif conditions usually result from inverting Python " + r"booleans with ~, which leads to unexpected results\. Replace `~flag` with " + r"`not flag`\. \(If this is a false positive, silence this warning with " + r"`m\.If\(x\)` → `m\.If\(x\.bool\(\)\)`\.\)$")): + with m.If(~True): + pass + + def test_Elif_signed_suspicious(self): + m = Module() + with m.If(0): + pass + with self.assertWarnsRegex(SyntaxWarning, + (r"^Signed values in If\/Elif conditions usually result from inverting Python " + r"booleans with ~, which leads to unexpected results\. Replace `~flag` with " + r"`not flag`\. \(If this is a false positive, silence this warning with " + r"`m\.If\(x\)` → `m\.If\(x\.bool\(\)\)`\.\)$")): + with m.Elif(~True): + pass + + def test_if_If_Elif_Else(self): + m = Module() + with self.assertRaisesRegex(SyntaxError, + r"^`if m\.If\(\.\.\.\):` does not work; use `with m\.If\(\.\.\.\)`$"): + if m.If(0): + pass + with m.If(0): + pass + with self.assertRaisesRegex(SyntaxError, + r"^`if m\.Elif\(\.\.\.\):` does not work; use `with m\.Elif\(\.\.\.\)`$"): + if m.Elif(0): + pass + with self.assertRaisesRegex(SyntaxError, + r"^`if m\.Else\(\.\.\.\):` does not work; use `with m\.Else\(\.\.\.\)`$"): + if m.Else(): + pass + + def test_Switch(self): + m = Module() + with m.Switch(self.w1): + with m.Case(3): + m.d.comb += self.c1.eq(1) + with m.Case("11--"): + m.d.comb += self.c2.eq(1) + with m.Case("1 0--"): + m.d.comb += self.c2.eq(1) + m._flush() + self.assertRepr(m._statements, """ + ( + (switch (sig w1) + (case 0011 (eq (sig c1) (const 1'd1))) + (case 11-- (eq (sig c2) (const 1'd1))) + (case 10-- (eq (sig c2) (const 1'd1))) + ) + ) + """) + + def test_Switch_default_Case(self): + m = Module() + with m.Switch(self.w1): + with m.Case(3): + m.d.comb += self.c1.eq(1) + with m.Case(): + m.d.comb += self.c2.eq(1) + m._flush() + self.assertRepr(m._statements, """ + ( + (switch (sig w1) + (case 0011 (eq (sig c1) (const 1'd1))) + (default (eq (sig c2) (const 1'd1))) + ) + ) + """) + + def test_Switch_default_Default(self): + m = Module() + with m.Switch(self.w1): + with m.Case(3): + m.d.comb += self.c1.eq(1) + with m.Default(): + m.d.comb += self.c2.eq(1) + m._flush() + self.assertRepr(m._statements, """ + ( + (switch (sig w1) + (case 0011 (eq (sig c1) (const 1'd1))) + (default (eq (sig c2) (const 1'd1))) + ) + ) + """) + + def test_Switch_const_test(self): + m = Module() + with m.Switch(1): + with m.Case(1): + m.d.comb += self.c1.eq(1) + m._flush() + self.assertRepr(m._statements, """ + ( + (switch (const 1'd1) + (case 1 (eq (sig c1) (const 1'd1))) + ) + ) + """) + + def test_Switch_enum(self): + class Color(Enum): + RED = 1 + BLUE = 2 + m = Module() + se = Signal(Color) + with m.Switch(se): + with m.Case(Color.RED): + m.d.comb += self.c1.eq(1) + self.assertRepr(m._statements, """ + ( + (switch (sig se) + (case 01 (eq (sig c1) (const 1'd1))) + ) + ) + """) + + def test_Case_width_wrong(self): + class Color(Enum): + RED = 0b10101010 + m = Module() + with m.Switch(self.w1): + with self.assertRaisesRegex(SyntaxError, + r"^Case pattern '--' must have the same width as switch value \(which is 4\)$"): + with m.Case("--"): + pass + with self.assertWarnsRegex(SyntaxWarning, + (r"^Case pattern '10110' is wider than switch value \(which has width 4\); " + r"comparison will never be true$")): + with m.Case(0b10110): + pass + with self.assertWarnsRegex(SyntaxWarning, + (r"^Case pattern '10101010' \(Color\.RED\) is wider than switch value " + r"\(which has width 4\); comparison will never be true$")): + with m.Case(Color.RED): + pass + self.assertRepr(m._statements, """ + ( + (switch (sig w1) ) + ) + """) + + def test_Case_bits_wrong(self): + m = Module() + with m.Switch(self.w1): + with self.assertRaisesRegex(SyntaxError, + (r"^Case pattern 'abc' must consist of 0, 1, and - \(don't care\) bits, " + r"and may include whitespace$")): + with m.Case("abc"): + pass + + def test_Case_pattern_wrong(self): + m = Module() + with m.Switch(self.w1): + with self.assertRaisesRegex(SyntaxError, + r"^Case pattern must be an integer, a string, or an enumeration, not 1\.0$"): + with m.Case(1.0): + pass + + def test_Case_outside_Switch_wrong(self): + m = Module() + with self.assertRaisesRegex(SyntaxError, + r"^Case is not permitted outside of Switch$"): + with m.Case(): + pass + + def test_If_inside_Switch_wrong(self): + m = Module() + with m.Switch(self.s1): + with self.assertRaisesRegex(SyntaxError, + (r"^If is not permitted directly inside of Switch; " + r"it is permitted inside of Switch Case$")): + with m.If(self.s2): + pass + + def test_FSM_basic(self): + a = Signal() + b = Signal() + c = Signal() + m = Module() + with m.FSM(): + with m.State("FIRST"): + m.d.comb += a.eq(1) + m.next = "SECOND" + with m.State("SECOND"): + m.d.sync += b.eq(~b) + with m.If(c): + m.next = "FIRST" + m._flush() + self.assertRepr(m._statements, """ + ( + (switch (sig fsm_state) + (case 0 + (eq (sig a) (const 1'd1)) + (eq (sig fsm_state) (const 1'd1)) + ) + (case 1 + (eq (sig b) (~ (sig b))) + (switch (cat (sig c)) + (case 1 + (eq (sig fsm_state) (const 1'd0))) + ) + ) + ) + ) + """) + self.assertEqual({repr(k): v for k, v in m._driving.items()}, { + "(sig a)": None, + "(sig fsm_state)": "sync", + "(sig b)": "sync", + }) + + frag = m.elaborate(platform=None) + fsm = frag.find_generated("fsm") + self.assertIsInstance(fsm.state, Signal) + self.assertEqual(fsm.encoding, OrderedDict({ + "FIRST": 0, + "SECOND": 1, + })) + self.assertEqual(fsm.decoding, OrderedDict({ + 0: "FIRST", + 1: "SECOND" + })) + + def test_FSM_reset(self): + a = Signal() + m = Module() + with m.FSM(reset="SECOND"): + with m.State("FIRST"): + m.d.comb += a.eq(0) + m.next = "SECOND" + with m.State("SECOND"): + m.next = "FIRST" + m._flush() + self.assertRepr(m._statements, """ + ( + (switch (sig fsm_state) + (case 0 + (eq (sig a) (const 1'd0)) + (eq (sig fsm_state) (const 1'd1)) + ) + (case 1 + (eq (sig fsm_state) (const 1'd0)) + ) + ) + ) + """) + + def test_FSM_ongoing(self): + a = Signal() + b = Signal() + m = Module() + with m.FSM() as fsm: + m.d.comb += b.eq(fsm.ongoing("SECOND")) + with m.State("FIRST"): + pass + m.d.comb += a.eq(fsm.ongoing("FIRST")) + with m.State("SECOND"): + pass + m._flush() + self.assertEqual(m._generated["fsm"].state.reset, 1) + self.maxDiff = 10000 + self.assertRepr(m._statements, """ + ( + (eq (sig b) (== (sig fsm_state) (const 1'd0))) + (eq (sig a) (== (sig fsm_state) (const 1'd1))) + (switch (sig fsm_state) + (case 1 + ) + (case 0 + ) + ) + ) + """) + + def test_FSM_empty(self): + m = Module() + with m.FSM(): + pass + self.assertRepr(m._statements, """ + () + """) + + def test_FSM_wrong_domain(self): + m = Module() + with self.assertRaisesRegex(ValueError, + r"^FSM may not be driven by the 'comb' domain$"): + with m.FSM(domain="comb"): + pass + + def test_FSM_wrong_undefined(self): + m = Module() + with self.assertRaisesRegex(NameError, + r"^FSM state 'FOO' is referenced but not defined$"): + with m.FSM() as fsm: + fsm.ongoing("FOO") + + def test_FSM_wrong_redefined(self): + m = Module() + with m.FSM(): + with m.State("FOO"): + pass + with self.assertRaisesRegex(NameError, + r"^FSM state 'FOO' is already defined$"): + with m.State("FOO"): + pass + + def test_FSM_wrong_next(self): + m = Module() + with self.assertRaisesRegex(SyntaxError, + r"^Only assignment to `m\.next` is permitted$"): + m.next + with self.assertRaisesRegex(SyntaxError, + r"^`m\.next = <\.\.\.>` is only permitted inside an FSM state$"): + m.next = "FOO" + with self.assertRaisesRegex(SyntaxError, + r"^`m\.next = <\.\.\.>` is only permitted inside an FSM state$"): + with m.FSM(): + m.next = "FOO" + + def test_If_inside_FSM_wrong(self): + m = Module() + with m.FSM(): + with m.State("FOO"): + pass + with self.assertRaisesRegex(SyntaxError, + (r"^If is not permitted directly inside of FSM; " + r"it is permitted inside of FSM State$")): + with m.If(self.s2): + pass + + def test_auto_pop_ctrl(self): + m = Module() + with m.If(self.w1): + m.d.comb += self.c1.eq(1) + m.d.comb += self.c2.eq(1) + self.assertRepr(m._statements, """ + ( + (switch (cat (b (sig w1))) + (case 1 (eq (sig c1) (const 1'd1))) + ) + (eq (sig c2) (const 1'd1)) + ) + """) + + def test_submodule_anon(self): + m1 = Module() + m2 = Module() + m1.submodules += m2 + self.assertEqual(m1._anon_submodules, [m2]) + self.assertEqual(m1._named_submodules, {}) + + def test_submodule_anon_multi(self): + m1 = Module() + m2 = Module() + m3 = Module() + m1.submodules += m2, m3 + self.assertEqual(m1._anon_submodules, [m2, m3]) + self.assertEqual(m1._named_submodules, {}) + + def test_submodule_named(self): + m1 = Module() + m2 = Module() + m1.submodules.foo = m2 + self.assertEqual(m1._anon_submodules, []) + self.assertEqual(m1._named_submodules, {"foo": m2}) + + def test_submodule_named_index(self): + m1 = Module() + m2 = Module() + m1.submodules["foo"] = m2 + self.assertEqual(m1._anon_submodules, []) + self.assertEqual(m1._named_submodules, {"foo": m2}) + + def test_submodule_wrong(self): + m = Module() + with self.assertRaisesRegex(TypeError, + r"^Trying to add 1, which does not implement \.elaborate\(\), as a submodule$"): + m.submodules.foo = 1 + with self.assertRaisesRegex(TypeError, + r"^Trying to add 1, which does not implement \.elaborate\(\), as a submodule$"): + m.submodules += 1 + + def test_submodule_named_conflict(self): + m1 = Module() + m2 = Module() + m1.submodules.foo = m2 + with self.assertRaisesRegex(NameError, r"^Submodule named 'foo' already exists$"): + m1.submodules.foo = m2 + + def test_submodule_get(self): + m1 = Module() + m2 = Module() + m1.submodules.foo = m2 + m3 = m1.submodules.foo + self.assertEqual(m2, m3) + + def test_submodule_get_index(self): + m1 = Module() + m2 = Module() + m1.submodules["foo"] = m2 + m3 = m1.submodules["foo"] + self.assertEqual(m2, m3) + + def test_submodule_get_unset(self): + m1 = Module() + with self.assertRaisesRegex(AttributeError, r"^No submodule named 'foo' exists$"): + m2 = m1.submodules.foo + with self.assertRaisesRegex(AttributeError, r"^No submodule named 'foo' exists$"): + m2 = m1.submodules["foo"] + + def test_domain_named_implicit(self): + m = Module() + m.domains += ClockDomain("sync") + self.assertEqual(len(m._domains), 1) + + def test_domain_named_explicit(self): + m = Module() + m.domains.foo = ClockDomain() + self.assertEqual(len(m._domains), 1) + self.assertEqual(m._domains["foo"].name, "foo") + + def test_domain_add_wrong(self): + m = Module() + with self.assertRaisesRegex(TypeError, + r"^Only clock domains may be added to `m\.domains`, not 1$"): + m.domains.foo = 1 + with self.assertRaisesRegex(TypeError, + r"^Only clock domains may be added to `m\.domains`, not 1$"): + m.domains += 1 + + def test_domain_add_wrong_name(self): + m = Module() + with self.assertRaisesRegex(NameError, + r"^Clock domain name 'bar' must match name in `m\.domains\.foo \+= \.\.\.` syntax$"): + m.domains.foo = ClockDomain("bar") + + def test_domain_add_wrong_duplicate(self): + m = Module() + m.domains += ClockDomain("foo") + with self.assertRaisesRegex(NameError, + r"^Clock domain named 'foo' already exists$"): + m.domains += ClockDomain("foo") + + def test_lower(self): + m1 = Module() + m1.d.comb += self.c1.eq(self.s1) + m2 = Module() + m2.d.comb += self.c2.eq(self.s2) + m2.d.sync += self.c3.eq(self.s3) + m1.submodules.foo = m2 + + f1 = m1.elaborate(platform=None) + self.assertRepr(f1.statements, """ + ( + (eq (sig c1) (sig s1)) + ) + """) + self.assertEqual(f1.drivers, { + None: SignalSet((self.c1,)) + }) + self.assertEqual(len(f1.subfragments), 1) + (f2, f2_name), = f1.subfragments + self.assertEqual(f2_name, "foo") + self.assertRepr(f2.statements, """ + ( + (eq (sig c2) (sig s2)) + (eq (sig c3) (sig s3)) + ) + """) + self.assertEqual(f2.drivers, { + None: SignalSet((self.c2,)), + "sync": SignalSet((self.c3,)) + }) + self.assertEqual(len(f2.subfragments), 0) diff --git a/tests/test_hdl_ir.py b/tests/test_hdl_ir.py new file mode 100644 index 0000000..dc3b730 --- /dev/null +++ b/tests/test_hdl_ir.py @@ -0,0 +1,863 @@ +# nmigen: UnusedElaboratable=no + +from collections import OrderedDict + +from nmigen.hdl.ast import * +from nmigen.hdl.cd import * +from nmigen.hdl.ir import * +from nmigen.hdl.mem import * + +from .utils import * + + +class BadElaboratable(Elaboratable): + def elaborate(self, platform): + return + + +class FragmentGetTestCase(FHDLTestCase): + def test_get_wrong(self): + with self.assertRaisesRegex(AttributeError, + r"^Object None cannot be elaborated$"): + Fragment.get(None, platform=None) + + with self.assertWarnsRegex(UserWarning, + r"^\.elaborate\(\) returned None; missing return statement\?$"): + with self.assertRaisesRegex(AttributeError, + r"^Object None cannot be elaborated$"): + Fragment.get(BadElaboratable(), platform=None) + + +class FragmentGeneratedTestCase(FHDLTestCase): + def test_find_subfragment(self): + f1 = Fragment() + f2 = Fragment() + f1.add_subfragment(f2, "f2") + + self.assertEqual(f1.find_subfragment(0), f2) + self.assertEqual(f1.find_subfragment("f2"), f2) + + def test_find_subfragment_wrong(self): + f1 = Fragment() + f2 = Fragment() + f1.add_subfragment(f2, "f2") + + with self.assertRaisesRegex(NameError, + r"^No subfragment at index #1$"): + f1.find_subfragment(1) + with self.assertRaisesRegex(NameError, + r"^No subfragment with name 'fx'$"): + f1.find_subfragment("fx") + + def test_find_generated(self): + f1 = Fragment() + f2 = Fragment() + f2.generated["sig"] = sig = Signal() + f1.add_subfragment(f2, "f2") + + self.assertEqual(SignalKey(f1.find_generated("f2", "sig")), + SignalKey(sig)) + + +class FragmentDriversTestCase(FHDLTestCase): + def test_empty(self): + f = Fragment() + self.assertEqual(list(f.iter_comb()), []) + self.assertEqual(list(f.iter_sync()), []) + + +class FragmentPortsTestCase(FHDLTestCase): + def setUp(self): + self.s1 = Signal() + self.s2 = Signal() + self.s3 = Signal() + self.c1 = Signal() + self.c2 = Signal() + self.c3 = Signal() + + def test_empty(self): + f = Fragment() + self.assertEqual(list(f.iter_ports()), []) + + f._propagate_ports(ports=(), all_undef_as_ports=True) + self.assertEqual(f.ports, SignalDict([])) + + def test_iter_signals(self): + f = Fragment() + f.add_ports(self.s1, self.s2, dir="io") + self.assertEqual(SignalSet((self.s1, self.s2)), f.iter_signals()) + + def test_self_contained(self): + f = Fragment() + f.add_statements( + self.c1.eq(self.s1), + self.s1.eq(self.c1) + ) + + f._propagate_ports(ports=(), all_undef_as_ports=True) + self.assertEqual(f.ports, SignalDict([])) + + def test_infer_input(self): + f = Fragment() + f.add_statements( + self.c1.eq(self.s1) + ) + + f._propagate_ports(ports=(), all_undef_as_ports=True) + self.assertEqual(f.ports, SignalDict([ + (self.s1, "i") + ])) + + def test_request_output(self): + f = Fragment() + f.add_statements( + self.c1.eq(self.s1) + ) + + f._propagate_ports(ports=(self.c1,), all_undef_as_ports=True) + self.assertEqual(f.ports, SignalDict([ + (self.s1, "i"), + (self.c1, "o") + ])) + + def test_input_in_subfragment(self): + f1 = Fragment() + f1.add_statements( + self.c1.eq(self.s1) + ) + f2 = Fragment() + f2.add_statements( + self.s1.eq(0) + ) + f1.add_subfragment(f2) + f1._propagate_ports(ports=(), all_undef_as_ports=True) + self.assertEqual(f1.ports, SignalDict()) + self.assertEqual(f2.ports, SignalDict([ + (self.s1, "o"), + ])) + + def test_input_only_in_subfragment(self): + f1 = Fragment() + f2 = Fragment() + f2.add_statements( + self.c1.eq(self.s1) + ) + f1.add_subfragment(f2) + f1._propagate_ports(ports=(), all_undef_as_ports=True) + self.assertEqual(f1.ports, SignalDict([ + (self.s1, "i"), + ])) + self.assertEqual(f2.ports, SignalDict([ + (self.s1, "i"), + ])) + + def test_output_from_subfragment(self): + f1 = Fragment() + f1.add_statements( + self.c1.eq(0) + ) + f2 = Fragment() + f2.add_statements( + self.c2.eq(1) + ) + f1.add_subfragment(f2) + + f1._propagate_ports(ports=(self.c2,), all_undef_as_ports=True) + self.assertEqual(f1.ports, SignalDict([ + (self.c2, "o"), + ])) + self.assertEqual(f2.ports, SignalDict([ + (self.c2, "o"), + ])) + + def test_output_from_subfragment_2(self): + f1 = Fragment() + f1.add_statements( + self.c1.eq(self.s1) + ) + f2 = Fragment() + f2.add_statements( + self.c2.eq(self.s1) + ) + f1.add_subfragment(f2) + f3 = Fragment() + f3.add_statements( + self.s1.eq(0) + ) + f2.add_subfragment(f3) + + f1._propagate_ports(ports=(), all_undef_as_ports=True) + self.assertEqual(f2.ports, SignalDict([ + (self.s1, "o"), + ])) + + def test_input_output_sibling(self): + f1 = Fragment() + f2 = Fragment() + f2.add_statements( + self.c1.eq(self.c2) + ) + f1.add_subfragment(f2) + f3 = Fragment() + f3.add_statements( + self.c2.eq(0) + ) + f3.add_driver(self.c2) + f1.add_subfragment(f3) + + f1._propagate_ports(ports=(), all_undef_as_ports=True) + self.assertEqual(f1.ports, SignalDict()) + + def test_output_input_sibling(self): + f1 = Fragment() + f2 = Fragment() + f2.add_statements( + self.c2.eq(0) + ) + f2.add_driver(self.c2) + f1.add_subfragment(f2) + f3 = Fragment() + f3.add_statements( + self.c1.eq(self.c2) + ) + f1.add_subfragment(f3) + + f1._propagate_ports(ports=(), all_undef_as_ports=True) + self.assertEqual(f1.ports, SignalDict()) + + def test_input_cd(self): + sync = ClockDomain() + f = Fragment() + f.add_statements( + self.c1.eq(self.s1) + ) + f.add_domains(sync) + f.add_driver(self.c1, "sync") + + f._propagate_ports(ports=(), all_undef_as_ports=True) + self.assertEqual(f.ports, SignalDict([ + (self.s1, "i"), + (sync.clk, "i"), + (sync.rst, "i"), + ])) + + def test_input_cd_reset_less(self): + sync = ClockDomain(reset_less=True) + f = Fragment() + f.add_statements( + self.c1.eq(self.s1) + ) + f.add_domains(sync) + f.add_driver(self.c1, "sync") + + f._propagate_ports(ports=(), all_undef_as_ports=True) + self.assertEqual(f.ports, SignalDict([ + (self.s1, "i"), + (sync.clk, "i"), + ])) + + def test_inout(self): + s = Signal() + f1 = Fragment() + f2 = Instance("foo", io_x=s) + f1.add_subfragment(f2) + + f1._propagate_ports(ports=(), all_undef_as_ports=True) + self.assertEqual(f1.ports, SignalDict([ + (s, "io") + ])) + + def test_in_out_same_signal(self): + s = Signal() + + f1 = Instance("foo", i_x=s, o_y=s) + f2 = Fragment() + f2.add_subfragment(f1) + + f2._propagate_ports(ports=(), all_undef_as_ports=True) + self.assertEqual(f1.ports, SignalDict([ + (s, "o") + ])) + + f3 = Instance("foo", o_y=s, i_x=s) + f4 = Fragment() + f4.add_subfragment(f3) + + f4._propagate_ports(ports=(), all_undef_as_ports=True) + self.assertEqual(f3.ports, SignalDict([ + (s, "o") + ])) + + def test_clk_rst(self): + sync = ClockDomain() + f = Fragment() + f.add_domains(sync) + + f = f.prepare(ports=(ClockSignal("sync"), ResetSignal("sync"))) + self.assertEqual(f.ports, SignalDict([ + (sync.clk, "i"), + (sync.rst, "i"), + ])) + + def test_port_wrong(self): + f = Fragment() + with self.assertRaisesRegex(TypeError, + r"^Only signals may be added as ports, not \(const 1'd1\)$"): + f.prepare(ports=(Const(1),)) + + def test_port_not_iterable(self): + f = Fragment() + with self.assertRaisesRegex(TypeError, + r"^`ports` must be either a list or a tuple, not 1$"): + f.prepare(ports=1) + with self.assertRaisesRegex(TypeError, + (r"^`ports` must be either a list or a tuple, not \(const 1'd1\)" + r" \(did you mean `ports=\(,\)`, rather than `ports=`\?\)$")): + f.prepare(ports=Const(1)) + +class FragmentDomainsTestCase(FHDLTestCase): + def test_iter_signals(self): + cd1 = ClockDomain() + cd2 = ClockDomain(reset_less=True) + s1 = Signal() + s2 = Signal() + + f = Fragment() + f.add_domains(cd1, cd2) + f.add_driver(s1, "cd1") + self.assertEqual(SignalSet((cd1.clk, cd1.rst, s1)), f.iter_signals()) + f.add_driver(s2, "cd2") + self.assertEqual(SignalSet((cd1.clk, cd1.rst, cd2.clk, s1, s2)), f.iter_signals()) + + def test_propagate_up(self): + cd = ClockDomain() + + f1 = Fragment() + f2 = Fragment() + f1.add_subfragment(f2) + f2.add_domains(cd) + + f1._propagate_domains_up() + self.assertEqual(f1.domains, {"cd": cd}) + + def test_propagate_up_local(self): + cd = ClockDomain(local=True) + + f1 = Fragment() + f2 = Fragment() + f1.add_subfragment(f2) + f2.add_domains(cd) + + f1._propagate_domains_up() + self.assertEqual(f1.domains, {}) + + def test_domain_conflict(self): + cda = ClockDomain("sync") + cdb = ClockDomain("sync") + + fa = Fragment() + fa.add_domains(cda) + fb = Fragment() + fb.add_domains(cdb) + f = Fragment() + f.add_subfragment(fa, "a") + f.add_subfragment(fb, "b") + + f._propagate_domains_up() + self.assertEqual(f.domains, {"a_sync": cda, "b_sync": cdb}) + (fa, _), (fb, _) = f.subfragments + self.assertEqual(fa.domains, {"a_sync": cda}) + self.assertEqual(fb.domains, {"b_sync": cdb}) + + def test_domain_conflict_anon(self): + cda = ClockDomain("sync") + cdb = ClockDomain("sync") + + fa = Fragment() + fa.add_domains(cda) + fb = Fragment() + fb.add_domains(cdb) + f = Fragment() + f.add_subfragment(fa, "a") + f.add_subfragment(fb) + + with self.assertRaisesRegex(DomainError, + (r"^Domain 'sync' is defined by subfragments 'a', of fragment " + r"'top'; it is necessary to either rename subfragment domains explicitly, " + r"or give names to subfragments$")): + f._propagate_domains_up() + + def test_domain_conflict_name(self): + cda = ClockDomain("sync") + cdb = ClockDomain("sync") + + fa = Fragment() + fa.add_domains(cda) + fb = Fragment() + fb.add_domains(cdb) + f = Fragment() + f.add_subfragment(fa, "x") + f.add_subfragment(fb, "x") + + with self.assertRaisesRegex(DomainError, + (r"^Domain 'sync' is defined by subfragments #0, #1 of fragment 'top', some " + r"of which have identical names; it is necessary to either rename subfragment " + r"domains explicitly, or give distinct names to subfragments$")): + f._propagate_domains_up() + + def test_domain_conflict_rename_drivers(self): + cda = ClockDomain("sync") + cdb = ClockDomain("sync") + + fa = Fragment() + fa.add_domains(cda) + fb = Fragment() + fb.add_domains(cdb) + fb.add_driver(ResetSignal("sync"), None) + f = Fragment() + f.add_subfragment(fa, "a") + f.add_subfragment(fb, "b") + + f._propagate_domains_up() + fb_new, _ = f.subfragments[1] + self.assertEqual(fb_new.drivers, OrderedDict({ + None: SignalSet((ResetSignal("b_sync"),)) + })) + + def test_domain_conflict_rename_drivers(self): + cda = ClockDomain("sync") + cdb = ClockDomain("sync") + s = Signal() + + fa = Fragment() + fa.add_domains(cda) + fb = Fragment() + fb.add_domains(cdb) + f = Fragment() + f.add_subfragment(fa, "a") + f.add_subfragment(fb, "b") + f.add_driver(s, "b_sync") + + f._propagate_domains(lambda name: ClockDomain(name)) + + def test_propagate_down(self): + cd = ClockDomain() + + f1 = Fragment() + f2 = Fragment() + f1.add_domains(cd) + f1.add_subfragment(f2) + + f1._propagate_domains_down() + self.assertEqual(f2.domains, {"cd": cd}) + + def test_propagate_down_idempotent(self): + cd = ClockDomain() + + f1 = Fragment() + f1.add_domains(cd) + f2 = Fragment() + f2.add_domains(cd) + f1.add_subfragment(f2) + + f1._propagate_domains_down() + self.assertEqual(f1.domains, {"cd": cd}) + self.assertEqual(f2.domains, {"cd": cd}) + + def test_propagate(self): + cd = ClockDomain() + + f1 = Fragment() + f2 = Fragment() + f1.add_domains(cd) + f1.add_subfragment(f2) + + new_domains = f1._propagate_domains(missing_domain=lambda name: None) + self.assertEqual(f1.domains, {"cd": cd}) + self.assertEqual(f2.domains, {"cd": cd}) + self.assertEqual(new_domains, []) + + def test_propagate_missing(self): + s1 = Signal() + f1 = Fragment() + f1.add_driver(s1, "sync") + + with self.assertRaisesRegex(DomainError, + r"^Domain 'sync' is used but not defined$"): + f1._propagate_domains(missing_domain=lambda name: None) + + def test_propagate_create_missing(self): + s1 = Signal() + f1 = Fragment() + f1.add_driver(s1, "sync") + f2 = Fragment() + f1.add_subfragment(f2) + + new_domains = f1._propagate_domains(missing_domain=lambda name: ClockDomain(name)) + self.assertEqual(f1.domains.keys(), {"sync"}) + self.assertEqual(f2.domains.keys(), {"sync"}) + self.assertEqual(f1.domains["sync"], f2.domains["sync"]) + self.assertEqual(new_domains, [f1.domains["sync"]]) + + def test_propagate_create_missing_fragment(self): + s1 = Signal() + f1 = Fragment() + f1.add_driver(s1, "sync") + + cd = ClockDomain("sync") + f2 = Fragment() + f2.add_domains(cd) + + new_domains = f1._propagate_domains(missing_domain=lambda name: f2) + self.assertEqual(f1.domains.keys(), {"sync"}) + self.assertEqual(f1.domains["sync"], f2.domains["sync"]) + self.assertEqual(new_domains, []) + self.assertEqual(f1.subfragments, [ + (f2, "cd_sync") + ]) + + def test_propagate_create_missing_fragment_many_domains(self): + s1 = Signal() + f1 = Fragment() + f1.add_driver(s1, "sync") + + cd_por = ClockDomain("por") + cd_sync = ClockDomain("sync") + f2 = Fragment() + f2.add_domains(cd_por, cd_sync) + + new_domains = f1._propagate_domains(missing_domain=lambda name: f2) + self.assertEqual(f1.domains.keys(), {"sync", "por"}) + self.assertEqual(f2.domains.keys(), {"sync", "por"}) + self.assertEqual(f1.domains["sync"], f2.domains["sync"]) + self.assertEqual(new_domains, []) + self.assertEqual(f1.subfragments, [ + (f2, "cd_sync") + ]) + + def test_propagate_create_missing_fragment_wrong(self): + s1 = Signal() + f1 = Fragment() + f1.add_driver(s1, "sync") + + f2 = Fragment() + f2.add_domains(ClockDomain("foo")) + + with self.assertRaisesRegex(DomainError, + (r"^Fragment returned by missing domain callback does not define requested " + r"domain 'sync' \(defines 'foo'\)\.$")): + f1._propagate_domains(missing_domain=lambda name: f2) + + +class FragmentHierarchyConflictTestCase(FHDLTestCase): + def setUp_self_sub(self): + self.s1 = Signal() + self.c1 = Signal() + self.c2 = Signal() + + self.f1 = Fragment() + self.f1.add_statements(self.c1.eq(0)) + self.f1.add_driver(self.s1) + self.f1.add_driver(self.c1, "sync") + + self.f1a = Fragment() + self.f1.add_subfragment(self.f1a, "f1a") + + self.f2 = Fragment() + self.f2.add_statements(self.c2.eq(1)) + self.f2.add_driver(self.s1) + self.f2.add_driver(self.c2, "sync") + self.f1.add_subfragment(self.f2) + + self.f1b = Fragment() + self.f1.add_subfragment(self.f1b, "f1b") + + self.f2a = Fragment() + self.f2.add_subfragment(self.f2a, "f2a") + + def test_conflict_self_sub(self): + self.setUp_self_sub() + + self.f1._resolve_hierarchy_conflicts(mode="silent") + self.assertEqual(self.f1.subfragments, [ + (self.f1a, "f1a"), + (self.f1b, "f1b"), + (self.f2a, "f2a"), + ]) + self.assertRepr(self.f1.statements, """ + ( + (eq (sig c1) (const 1'd0)) + (eq (sig c2) (const 1'd1)) + ) + """) + self.assertEqual(self.f1.drivers, { + None: SignalSet((self.s1,)), + "sync": SignalSet((self.c1, self.c2)), + }) + + def test_conflict_self_sub_error(self): + self.setUp_self_sub() + + with self.assertRaisesRegex(DriverConflict, + r"^Signal '\(sig s1\)' is driven from multiple fragments: top, top.$"): + self.f1._resolve_hierarchy_conflicts(mode="error") + + def test_conflict_self_sub_warning(self): + self.setUp_self_sub() + + with self.assertWarnsRegex(DriverConflict, + (r"^Signal '\(sig s1\)' is driven from multiple fragments: top, top.; " + r"hierarchy will be flattened$")): + self.f1._resolve_hierarchy_conflicts(mode="warn") + + def setUp_sub_sub(self): + self.s1 = Signal() + self.c1 = Signal() + self.c2 = Signal() + + self.f1 = Fragment() + + self.f2 = Fragment() + self.f2.add_driver(self.s1) + self.f2.add_statements(self.c1.eq(0)) + self.f1.add_subfragment(self.f2) + + self.f3 = Fragment() + self.f3.add_driver(self.s1) + self.f3.add_statements(self.c2.eq(1)) + self.f1.add_subfragment(self.f3) + + def test_conflict_sub_sub(self): + self.setUp_sub_sub() + + self.f1._resolve_hierarchy_conflicts(mode="silent") + self.assertEqual(self.f1.subfragments, []) + self.assertRepr(self.f1.statements, """ + ( + (eq (sig c1) (const 1'd0)) + (eq (sig c2) (const 1'd1)) + ) + """) + + def setUp_self_subsub(self): + self.s1 = Signal() + self.c1 = Signal() + self.c2 = Signal() + + self.f1 = Fragment() + self.f1.add_driver(self.s1) + + self.f2 = Fragment() + self.f2.add_statements(self.c1.eq(0)) + self.f1.add_subfragment(self.f2) + + self.f3 = Fragment() + self.f3.add_driver(self.s1) + self.f3.add_statements(self.c2.eq(1)) + self.f2.add_subfragment(self.f3) + + def test_conflict_self_subsub(self): + self.setUp_self_subsub() + + self.f1._resolve_hierarchy_conflicts(mode="silent") + self.assertEqual(self.f1.subfragments, []) + self.assertRepr(self.f1.statements, """ + ( + (eq (sig c1) (const 1'd0)) + (eq (sig c2) (const 1'd1)) + ) + """) + + def setUp_memory(self): + self.m = Memory(width=8, depth=4) + self.fr = self.m.read_port().elaborate(platform=None) + self.fw = self.m.write_port().elaborate(platform=None) + self.f1 = Fragment() + self.f2 = Fragment() + self.f2.add_subfragment(self.fr) + self.f1.add_subfragment(self.f2) + self.f3 = Fragment() + self.f3.add_subfragment(self.fw) + self.f1.add_subfragment(self.f3) + + def test_conflict_memory(self): + self.setUp_memory() + + self.f1._resolve_hierarchy_conflicts(mode="silent") + self.assertEqual(self.f1.subfragments, [ + (self.fr, None), + (self.fw, None), + ]) + + def test_conflict_memory_error(self): + self.setUp_memory() + + with self.assertRaisesRegex(DriverConflict, + r"^Memory 'm' is accessed from multiple fragments: top\., " + r"top\.$"): + self.f1._resolve_hierarchy_conflicts(mode="error") + + def test_conflict_memory_warning(self): + self.setUp_memory() + + with self.assertWarnsRegex(DriverConflict, + (r"^Memory 'm' is accessed from multiple fragments: top., " + r"top.; hierarchy will be flattened$")): + self.f1._resolve_hierarchy_conflicts(mode="warn") + + def test_explicit_flatten(self): + self.f1 = Fragment() + self.f2 = Fragment() + self.f2.flatten = True + self.f1.add_subfragment(self.f2) + + self.f1._resolve_hierarchy_conflicts(mode="silent") + self.assertEqual(self.f1.subfragments, []) + + def test_no_conflict_local_domains(self): + f1 = Fragment() + cd1 = ClockDomain("d", local=True) + f1.add_domains(cd1) + f1.add_driver(ClockSignal("d")) + f2 = Fragment() + cd2 = ClockDomain("d", local=True) + f2.add_domains(cd2) + f2.add_driver(ClockSignal("d")) + f3 = Fragment() + f3.add_subfragment(f1) + f3.add_subfragment(f2) + f3.prepare() + + +class InstanceTestCase(FHDLTestCase): + def test_construct(self): + s1 = Signal() + s2 = Signal() + s3 = Signal() + s4 = Signal() + s5 = Signal() + s6 = Signal() + inst = Instance("foo", + ("a", "ATTR1", 1), + ("p", "PARAM1", 0x1234), + ("i", "s1", s1), + ("o", "s2", s2), + ("io", "s3", s3), + a_ATTR2=2, + p_PARAM2=0x5678, + i_s4=s4, + o_s5=s5, + io_s6=s6, + ) + self.assertEqual(inst.attrs, OrderedDict([ + ("ATTR1", 1), + ("ATTR2", 2), + ])) + self.assertEqual(inst.parameters, OrderedDict([ + ("PARAM1", 0x1234), + ("PARAM2", 0x5678), + ])) + self.assertEqual(inst.named_ports, OrderedDict([ + ("s1", (s1, "i")), + ("s2", (s2, "o")), + ("s3", (s3, "io")), + ("s4", (s4, "i")), + ("s5", (s5, "o")), + ("s6", (s6, "io")), + ])) + + def test_cast_ports(self): + inst = Instance("foo", + ("i", "s1", 1), + ("o", "s2", 2), + ("io", "s3", 3), + i_s4=4, + o_s5=5, + io_s6=6, + ) + self.assertRepr(inst.named_ports["s1"][0], "(const 1'd1)") + self.assertRepr(inst.named_ports["s2"][0], "(const 2'd2)") + self.assertRepr(inst.named_ports["s3"][0], "(const 2'd3)") + self.assertRepr(inst.named_ports["s4"][0], "(const 3'd4)") + self.assertRepr(inst.named_ports["s5"][0], "(const 3'd5)") + self.assertRepr(inst.named_ports["s6"][0], "(const 3'd6)") + + def test_wrong_construct_arg(self): + s = Signal() + with self.assertRaisesRegex(NameError, + (r"^Instance argument \('', 's1', \(sig s\)\) should be a tuple " + r"\(kind, name, value\) where kind is one of \"p\", \"i\", \"o\", or \"io\"$")): + Instance("foo", ("", "s1", s)) + + def test_wrong_construct_kwarg(self): + s = Signal() + with self.assertRaisesRegex(NameError, + (r"^Instance keyword argument x_s1=\(sig s\) does not start with one of " + r"\"p_\", \"i_\", \"o_\", or \"io_\"$")): + Instance("foo", x_s1=s) + + def setUp_cpu(self): + self.rst = Signal() + self.stb = Signal() + self.pins = Signal(8) + self.datal = Signal(4) + self.datah = Signal(4) + self.inst = Instance("cpu", + p_RESET=0x1234, + i_clk=ClockSignal(), + i_rst=self.rst, + o_stb=self.stb, + o_data=Cat(self.datal, self.datah), + io_pins=self.pins[:] + ) + self.wrap = Fragment() + self.wrap.add_subfragment(self.inst) + + def test_init(self): + self.setUp_cpu() + f = self.inst + self.assertEqual(f.type, "cpu") + self.assertEqual(f.parameters, OrderedDict([("RESET", 0x1234)])) + self.assertEqual(list(f.named_ports.keys()), ["clk", "rst", "stb", "data", "pins"]) + self.assertEqual(f.ports, SignalDict([])) + + def test_prepare(self): + self.setUp_cpu() + f = self.wrap.prepare() + sync_clk = f.domains["sync"].clk + self.assertEqual(f.ports, SignalDict([ + (sync_clk, "i"), + (self.rst, "i"), + (self.pins, "io"), + ])) + + def test_prepare_explicit_ports(self): + self.setUp_cpu() + f = self.wrap.prepare(ports=[self.rst, self.stb]) + sync_clk = f.domains["sync"].clk + sync_rst = f.domains["sync"].rst + self.assertEqual(f.ports, SignalDict([ + (sync_clk, "i"), + (sync_rst, "i"), + (self.rst, "i"), + (self.stb, "o"), + (self.pins, "io"), + ])) + + def test_prepare_slice_in_port(self): + s = Signal(2) + f = Fragment() + f.add_subfragment(Instance("foo", o_O=s[0])) + f.add_subfragment(Instance("foo", o_O=s[1])) + fp = f.prepare(ports=[s], missing_domain=lambda name: None) + self.assertEqual(fp.ports, SignalDict([ + (s, "o"), + ])) + + def test_prepare_attrs(self): + self.setUp_cpu() + self.inst.attrs["ATTR"] = 1 + f = self.inst.prepare() + self.assertEqual(f.attrs, OrderedDict([ + ("ATTR", 1), + ])) diff --git a/tests/test_hdl_mem.py b/tests/test_hdl_mem.py new file mode 100644 index 0000000..ef0a885 --- /dev/null +++ b/tests/test_hdl_mem.py @@ -0,0 +1,138 @@ +# nmigen: UnusedElaboratable=no + +from nmigen.hdl.ast import * +from nmigen.hdl.mem import * + +from .utils import * + + +class MemoryTestCase(FHDLTestCase): + def test_name(self): + m1 = Memory(width=8, depth=4) + self.assertEqual(m1.name, "m1") + m2 = [Memory(width=8, depth=4)][0] + self.assertEqual(m2.name, "$memory") + m3 = Memory(width=8, depth=4, name="foo") + self.assertEqual(m3.name, "foo") + + def test_geometry(self): + m = Memory(width=8, depth=4) + self.assertEqual(m.width, 8) + self.assertEqual(m.depth, 4) + + def test_geometry_wrong(self): + with self.assertRaisesRegex(TypeError, + r"^Memory width must be a non-negative integer, not -1$"): + m = Memory(width=-1, depth=4) + with self.assertRaisesRegex(TypeError, + r"^Memory depth must be a non-negative integer, not -1$"): + m = Memory(width=8, depth=-1) + + def test_init(self): + m = Memory(width=8, depth=4, init=range(4)) + self.assertEqual(m.init, [0, 1, 2, 3]) + + def test_init_wrong_count(self): + with self.assertRaisesRegex(ValueError, + r"^Memory initialization value count exceed memory depth \(8 > 4\)$"): + m = Memory(width=8, depth=4, init=range(8)) + + def test_init_wrong_type(self): + with self.assertRaisesRegex(TypeError, + (r"^Memory initialization value at address 1: " + r"'str' object cannot be interpreted as an integer$")): + m = Memory(width=8, depth=4, init=[1, "0"]) + + def test_attrs(self): + m1 = Memory(width=8, depth=4) + self.assertEqual(m1.attrs, {}) + m2 = Memory(width=8, depth=4, attrs={"ram_block": True}) + self.assertEqual(m2.attrs, {"ram_block": True}) + + def test_read_port_transparent(self): + mem = Memory(width=8, depth=4) + rdport = mem.read_port() + self.assertEqual(rdport.memory, mem) + self.assertEqual(rdport.domain, "sync") + self.assertEqual(rdport.transparent, True) + self.assertEqual(len(rdport.addr), 2) + self.assertEqual(len(rdport.data), 8) + self.assertEqual(len(rdport.en), 1) + self.assertIsInstance(rdport.en, Const) + self.assertEqual(rdport.en.value, 1) + + def test_read_port_non_transparent(self): + mem = Memory(width=8, depth=4) + rdport = mem.read_port(transparent=False) + self.assertEqual(rdport.memory, mem) + self.assertEqual(rdport.domain, "sync") + self.assertEqual(rdport.transparent, False) + self.assertEqual(len(rdport.en), 1) + self.assertIsInstance(rdport.en, Signal) + self.assertEqual(rdport.en.reset, 1) + + def test_read_port_asynchronous(self): + mem = Memory(width=8, depth=4) + rdport = mem.read_port(domain="comb") + self.assertEqual(rdport.memory, mem) + self.assertEqual(rdport.domain, "comb") + self.assertEqual(rdport.transparent, True) + self.assertEqual(len(rdport.en), 1) + self.assertIsInstance(rdport.en, Const) + self.assertEqual(rdport.en.value, 1) + + def test_read_port_wrong(self): + mem = Memory(width=8, depth=4) + with self.assertRaisesRegex(ValueError, + r"^Read port cannot be simultaneously asynchronous and non-transparent$"): + mem.read_port(domain="comb", transparent=False) + + def test_write_port(self): + mem = Memory(width=8, depth=4) + wrport = mem.write_port() + self.assertEqual(wrport.memory, mem) + self.assertEqual(wrport.domain, "sync") + self.assertEqual(wrport.granularity, 8) + self.assertEqual(len(wrport.addr), 2) + self.assertEqual(len(wrport.data), 8) + self.assertEqual(len(wrport.en), 1) + + def test_write_port_granularity(self): + mem = Memory(width=8, depth=4) + wrport = mem.write_port(granularity=2) + self.assertEqual(wrport.memory, mem) + self.assertEqual(wrport.domain, "sync") + self.assertEqual(wrport.granularity, 2) + self.assertEqual(len(wrport.addr), 2) + self.assertEqual(len(wrport.data), 8) + self.assertEqual(len(wrport.en), 4) + + def test_write_port_granularity_wrong(self): + mem = Memory(width=8, depth=4) + with self.assertRaisesRegex(TypeError, + r"^Write port granularity must be a non-negative integer, not -1$"): + mem.write_port(granularity=-1) + with self.assertRaisesRegex(ValueError, + r"^Write port granularity must not be greater than memory width \(10 > 8\)$"): + mem.write_port(granularity=10) + with self.assertRaisesRegex(ValueError, + r"^Write port granularity must divide memory width evenly$"): + mem.write_port(granularity=3) + + +class DummyPortTestCase(FHDLTestCase): + def test_name(self): + p1 = DummyPort(data_width=8, addr_width=2) + self.assertEqual(p1.addr.name, "p1_addr") + p2 = [DummyPort(data_width=8, addr_width=2)][0] + self.assertEqual(p2.addr.name, "dummy_addr") + p3 = DummyPort(data_width=8, addr_width=2, name="foo") + self.assertEqual(p3.addr.name, "foo_addr") + + def test_sizes(self): + p1 = DummyPort(data_width=8, addr_width=2) + self.assertEqual(p1.addr.width, 2) + self.assertEqual(p1.data.width, 8) + self.assertEqual(p1.en.width, 1) + p2 = DummyPort(data_width=8, addr_width=2, granularity=2) + self.assertEqual(p2.en.width, 4) diff --git a/tests/test_hdl_rec.py b/tests/test_hdl_rec.py new file mode 100644 index 0000000..718fa4a --- /dev/null +++ b/tests/test_hdl_rec.py @@ -0,0 +1,330 @@ +from enum import Enum + +from nmigen.hdl.ast import * +from nmigen.hdl.rec import * + +from .utils import * + + +class UnsignedEnum(Enum): + FOO = 1 + BAR = 2 + BAZ = 3 + + +class LayoutTestCase(FHDLTestCase): + def assertFieldEqual(self, field, expected): + (shape, dir) = field + shape = Shape.cast(shape) + self.assertEqual((shape, dir), expected) + + def test_fields(self): + layout = Layout.cast([ + ("cyc", 1), + ("data", signed(32)), + ("stb", 1, DIR_FANOUT), + ("ack", 1, DIR_FANIN), + ("info", [ + ("a", 1), + ("b", 1), + ]) + ]) + + self.assertFieldEqual(layout["cyc"], ((1, False), DIR_NONE)) + self.assertFieldEqual(layout["data"], ((32, True), DIR_NONE)) + self.assertFieldEqual(layout["stb"], ((1, False), DIR_FANOUT)) + self.assertFieldEqual(layout["ack"], ((1, False), DIR_FANIN)) + sublayout = layout["info"][0] + self.assertEqual(layout["info"][1], DIR_NONE) + self.assertFieldEqual(sublayout["a"], ((1, False), DIR_NONE)) + self.assertFieldEqual(sublayout["b"], ((1, False), DIR_NONE)) + + def test_enum_field(self): + layout = Layout.cast([ + ("enum", UnsignedEnum), + ("enum_dir", UnsignedEnum, DIR_FANOUT), + ]) + self.assertFieldEqual(layout["enum"], ((2, False), DIR_NONE)) + self.assertFieldEqual(layout["enum_dir"], ((2, False), DIR_FANOUT)) + + def test_range_field(self): + layout = Layout.cast([ + ("range", range(0, 7)), + ]) + self.assertFieldEqual(layout["range"], ((3, False), DIR_NONE)) + + def test_slice_tuple(self): + layout = Layout.cast([ + ("a", 1), + ("b", 2), + ("c", 3) + ]) + expect = Layout.cast([ + ("a", 1), + ("c", 3) + ]) + self.assertEqual(layout["a", "c"], expect) + + def test_repr(self): + self.assertEqual(repr(Layout([("a", unsigned(1)), ("b", signed(2))])), + "Layout([('a', unsigned(1)), ('b', signed(2))])") + self.assertEqual(repr(Layout([("a", unsigned(1)), ("b", [("c", signed(3))])])), + "Layout([('a', unsigned(1)), " + "('b', Layout([('c', signed(3))]))])") + + def test_wrong_field(self): + with self.assertRaisesRegex(TypeError, + (r"^Field \(1,\) has invalid layout: should be either \(name, shape\) or " + r"\(name, shape, direction\)$")): + Layout.cast([(1,)]) + + def test_wrong_name(self): + with self.assertRaisesRegex(TypeError, + r"^Field \(1, 1\) has invalid name: should be a string$"): + Layout.cast([(1, 1)]) + + def test_wrong_name_duplicate(self): + with self.assertRaisesRegex(NameError, + r"^Field \('a', 2\) has a name that is already present in the layout$"): + Layout.cast([("a", 1), ("a", 2)]) + + def test_wrong_direction(self): + with self.assertRaisesRegex(TypeError, + (r"^Field \('a', 1, 0\) has invalid direction: should be a Direction " + r"instance like DIR_FANIN$")): + Layout.cast([("a", 1, 0)]) + + def test_wrong_shape(self): + with self.assertRaisesRegex(TypeError, + (r"^Field \('a', 'x'\) has invalid shape: should be castable to Shape or " + r"a list of fields of a nested record$")): + Layout.cast([("a", "x")]) + + +class RecordTestCase(FHDLTestCase): + def test_basic(self): + r = Record([ + ("stb", 1), + ("data", 32), + ("info", [ + ("a", 1), + ("b", 1), + ]) + ]) + + self.assertEqual(repr(r), "(rec r stb data (rec r__info a b))") + self.assertEqual(len(r), 35) + self.assertIsInstance(r.stb, Signal) + self.assertEqual(r.stb.name, "r__stb") + self.assertEqual(r["stb"].name, "r__stb") + + self.assertTrue(hasattr(r, "stb")) + self.assertFalse(hasattr(r, "xxx")) + + def test_unnamed(self): + r = [Record([ + ("stb", 1) + ])][0] + + self.assertEqual(repr(r), "(rec stb)") + self.assertEqual(r.stb.name, "stb") + + def test_iter(self): + r = Record([ + ("data", 4), + ("stb", 1), + ]) + + self.assertEqual(repr(r[0]), "(slice (rec r data stb) 0:1)") + self.assertEqual(repr(r[0:3]), "(slice (rec r data stb) 0:3)") + + def test_wrong_field(self): + r = Record([ + ("stb", 1), + ("ack", 1), + ]) + with self.assertRaisesRegex(AttributeError, + r"^Record 'r' does not have a field 'en'\. Did you mean one of: stb, ack\?$"): + r["en"] + with self.assertRaisesRegex(AttributeError, + r"^Record 'r' does not have a field 'en'\. Did you mean one of: stb, ack\?$"): + r.en + + def test_wrong_field_unnamed(self): + r = [Record([ + ("stb", 1), + ("ack", 1), + ])][0] + with self.assertRaisesRegex(AttributeError, + r"^Unnamed record does not have a field 'en'\. Did you mean one of: stb, ack\?$"): + r.en + + def test_construct_with_fields(self): + ns = Signal(1) + nr = Record([ + ("burst", 1) + ]) + r = Record([ + ("stb", 1), + ("info", [ + ("burst", 1) + ]) + ], fields={ + "stb": ns, + "info": nr + }) + self.assertIs(r.stb, ns) + self.assertIs(r.info, nr) + + def test_like(self): + r1 = Record([("a", 1), ("b", 2)]) + r2 = Record.like(r1) + self.assertEqual(r1.layout, r2.layout) + self.assertEqual(r2.name, "r2") + r3 = Record.like(r1, name="foo") + self.assertEqual(r3.name, "foo") + r4 = Record.like(r1, name_suffix="foo") + self.assertEqual(r4.name, "r1foo") + + def test_like_modifications(self): + r1 = Record([("a", 1), ("b", [("s", 1)])]) + self.assertEqual(r1.a.name, "r1__a") + self.assertEqual(r1.b.name, "r1__b") + self.assertEqual(r1.b.s.name, "r1__b__s") + r1.a.reset = 1 + r1.b.s.reset = 1 + r2 = Record.like(r1) + self.assertEqual(r2.a.reset, 1) + self.assertEqual(r2.b.s.reset, 1) + self.assertEqual(r2.a.name, "r2__a") + self.assertEqual(r2.b.name, "r2__b") + self.assertEqual(r2.b.s.name, "r2__b__s") + + def test_slice_tuple(self): + r1 = Record([("a", 1), ("b", 2), ("c", 3)]) + r2 = r1["a", "c"] + self.assertEqual(r2.layout, Layout([("a", 1), ("c", 3)])) + self.assertIs(r2.a, r1.a) + self.assertIs(r2.c, r1.c) + + def test_enum_decoder(self): + r1 = Record([("a", UnsignedEnum)]) + self.assertEqual(r1.a.decoder(UnsignedEnum.FOO), "FOO/1") + + +class ConnectTestCase(FHDLTestCase): + def setUp_flat(self): + self.core_layout = [ + ("addr", 32, DIR_FANOUT), + ("data_r", 32, DIR_FANIN), + ("data_w", 32, DIR_FANIN), + ] + self.periph_layout = [ + ("addr", 32, DIR_FANOUT), + ("data_r", 32, DIR_FANIN), + ("data_w", 32, DIR_FANIN), + ] + + def setUp_nested(self): + self.core_layout = [ + ("addr", 32, DIR_FANOUT), + ("data", [ + ("r", 32, DIR_FANIN), + ("w", 32, DIR_FANIN), + ]), + ] + self.periph_layout = [ + ("addr", 32, DIR_FANOUT), + ("data", [ + ("r", 32, DIR_FANIN), + ("w", 32, DIR_FANIN), + ]), + ] + + def test_flat(self): + self.setUp_flat() + + core = Record(self.core_layout) + periph1 = Record(self.periph_layout) + periph2 = Record(self.periph_layout) + + stmts = core.connect(periph1, periph2) + self.assertRepr(stmts, """( + (eq (sig periph1__addr) (sig core__addr)) + (eq (sig periph2__addr) (sig core__addr)) + (eq (sig core__data_r) (| (sig periph1__data_r) (sig periph2__data_r))) + (eq (sig core__data_w) (| (sig periph1__data_w) (sig periph2__data_w))) + )""") + + def test_flat_include(self): + self.setUp_flat() + + core = Record(self.core_layout) + periph1 = Record(self.periph_layout) + periph2 = Record(self.periph_layout) + + stmts = core.connect(periph1, periph2, include={"addr": True}) + self.assertRepr(stmts, """( + (eq (sig periph1__addr) (sig core__addr)) + (eq (sig periph2__addr) (sig core__addr)) + )""") + + def test_flat_exclude(self): + self.setUp_flat() + + core = Record(self.core_layout) + periph1 = Record(self.periph_layout) + periph2 = Record(self.periph_layout) + + stmts = core.connect(periph1, periph2, exclude={"addr": True}) + self.assertRepr(stmts, """( + (eq (sig core__data_r) (| (sig periph1__data_r) (sig periph2__data_r))) + (eq (sig core__data_w) (| (sig periph1__data_w) (sig periph2__data_w))) + )""") + + def test_nested(self): + self.setUp_nested() + + core = Record(self.core_layout) + periph1 = Record(self.periph_layout) + periph2 = Record(self.periph_layout) + + stmts = core.connect(periph1, periph2) + self.maxDiff = None + self.assertRepr(stmts, """( + (eq (sig periph1__addr) (sig core__addr)) + (eq (sig periph2__addr) (sig core__addr)) + (eq (sig core__data__r) (| (sig periph1__data__r) (sig periph2__data__r))) + (eq (sig core__data__w) (| (sig periph1__data__w) (sig periph2__data__w))) + )""") + + def test_wrong_include_exclude(self): + self.setUp_flat() + + core = Record(self.core_layout) + periph = Record(self.periph_layout) + + with self.assertRaisesRegex(AttributeError, + r"^Cannot include field 'foo' because it is not present in record 'core'$"): + core.connect(periph, include={"foo": True}) + + with self.assertRaisesRegex(AttributeError, + r"^Cannot exclude field 'foo' because it is not present in record 'core'$"): + core.connect(periph, exclude={"foo": True}) + + def test_wrong_direction(self): + recs = [Record([("x", 1)]) for _ in range(2)] + + with self.assertRaisesRegex(TypeError, + (r"^Cannot connect field 'x' of unnamed record because it does not have " + r"a direction$")): + recs[0].connect(recs[1]) + + def test_wrong_missing_field(self): + core = Record([("addr", 32, DIR_FANOUT)]) + periph = Record([]) + + with self.assertRaisesRegex(AttributeError, + (r"^Cannot connect field 'addr' of record 'core' to subordinate record 'periph' " + r"because the subordinate record does not have this field$")): + core.connect(periph) diff --git a/tests/test_hdl_xfrm.py b/tests/test_hdl_xfrm.py new file mode 100644 index 0000000..c8b885f --- /dev/null +++ b/tests/test_hdl_xfrm.py @@ -0,0 +1,650 @@ +# nmigen: UnusedElaboratable=no + +from nmigen.hdl.ast import * +from nmigen.hdl.cd import * +from nmigen.hdl.ir import * +from nmigen.hdl.xfrm import * +from nmigen.hdl.mem import * + +from .utils import * + + +class DomainRenamerTestCase(FHDLTestCase): + def setUp(self): + self.s1 = Signal() + self.s2 = Signal() + self.s3 = Signal() + self.s4 = Signal() + self.s5 = Signal() + self.c1 = Signal() + + def test_rename_signals(self): + f = Fragment() + f.add_statements( + self.s1.eq(ClockSignal()), + ResetSignal().eq(self.s2), + self.s3.eq(0), + self.s4.eq(ClockSignal("other")), + self.s5.eq(ResetSignal("other")), + ) + f.add_driver(self.s1, None) + f.add_driver(self.s2, None) + f.add_driver(self.s3, "sync") + + f = DomainRenamer("pix")(f) + self.assertRepr(f.statements, """ + ( + (eq (sig s1) (clk pix)) + (eq (rst pix) (sig s2)) + (eq (sig s3) (const 1'd0)) + (eq (sig s4) (clk other)) + (eq (sig s5) (rst other)) + ) + """) + self.assertEqual(f.drivers, { + None: SignalSet((self.s1, self.s2)), + "pix": SignalSet((self.s3,)), + }) + + def test_rename_multi(self): + f = Fragment() + f.add_statements( + self.s1.eq(ClockSignal()), + self.s2.eq(ResetSignal("other")), + ) + + f = DomainRenamer({"sync": "pix", "other": "pix2"})(f) + self.assertRepr(f.statements, """ + ( + (eq (sig s1) (clk pix)) + (eq (sig s2) (rst pix2)) + ) + """) + + def test_rename_cd(self): + cd_sync = ClockDomain() + cd_pix = ClockDomain() + + f = Fragment() + f.add_domains(cd_sync, cd_pix) + + f = DomainRenamer("ext")(f) + self.assertEqual(cd_sync.name, "ext") + self.assertEqual(f.domains, { + "ext": cd_sync, + "pix": cd_pix, + }) + + def test_rename_cd_preserves_allow_reset_less(self): + cd_pix = ClockDomain(reset_less=True) + + f = Fragment() + f.add_domains(cd_pix) + f.add_statements( + self.s1.eq(ResetSignal(allow_reset_less=True)), + ) + + f = DomainRenamer("pix")(f) + f = DomainLowerer()(f) + self.assertRepr(f.statements, """ + ( + (eq (sig s1) (const 1'd0)) + ) + """) + + + def test_rename_cd_subfragment(self): + cd_sync = ClockDomain() + cd_pix = ClockDomain() + + f1 = Fragment() + f1.add_domains(cd_sync, cd_pix) + f2 = Fragment() + f2.add_domains(cd_sync) + f1.add_subfragment(f2) + + f1 = DomainRenamer("ext")(f1) + self.assertEqual(cd_sync.name, "ext") + self.assertEqual(f1.domains, { + "ext": cd_sync, + "pix": cd_pix, + }) + + def test_rename_wrong_to_comb(self): + with self.assertRaisesRegex(ValueError, + r"^Domain 'sync' may not be renamed to 'comb'$"): + DomainRenamer("comb") + + def test_rename_wrong_from_comb(self): + with self.assertRaisesRegex(ValueError, + r"^Domain 'comb' may not be renamed$"): + DomainRenamer({"comb": "sync"}) + + +class DomainLowererTestCase(FHDLTestCase): + def setUp(self): + self.s = Signal() + + def test_lower_clk(self): + sync = ClockDomain() + f = Fragment() + f.add_domains(sync) + f.add_statements( + self.s.eq(ClockSignal("sync")) + ) + + f = DomainLowerer()(f) + self.assertRepr(f.statements, """ + ( + (eq (sig s) (sig clk)) + ) + """) + + def test_lower_rst(self): + sync = ClockDomain() + f = Fragment() + f.add_domains(sync) + f.add_statements( + self.s.eq(ResetSignal("sync")) + ) + + f = DomainLowerer()(f) + self.assertRepr(f.statements, """ + ( + (eq (sig s) (sig rst)) + ) + """) + + def test_lower_rst_reset_less(self): + sync = ClockDomain(reset_less=True) + f = Fragment() + f.add_domains(sync) + f.add_statements( + self.s.eq(ResetSignal("sync", allow_reset_less=True)) + ) + + f = DomainLowerer()(f) + self.assertRepr(f.statements, """ + ( + (eq (sig s) (const 1'd0)) + ) + """) + + def test_lower_drivers(self): + sync = ClockDomain() + pix = ClockDomain() + f = Fragment() + f.add_domains(sync, pix) + f.add_driver(ClockSignal("pix"), None) + f.add_driver(ResetSignal("pix"), "sync") + + f = DomainLowerer()(f) + self.assertEqual(f.drivers, { + None: SignalSet((pix.clk,)), + "sync": SignalSet((pix.rst,)) + }) + + def test_lower_wrong_domain(self): + f = Fragment() + f.add_statements( + self.s.eq(ClockSignal("xxx")) + ) + + with self.assertRaisesRegex(DomainError, + r"^Signal \(clk xxx\) refers to nonexistent domain 'xxx'$"): + DomainLowerer()(f) + + def test_lower_wrong_reset_less_domain(self): + sync = ClockDomain(reset_less=True) + f = Fragment() + f.add_domains(sync) + f.add_statements( + self.s.eq(ResetSignal("sync")) + ) + + with self.assertRaisesRegex(DomainError, + r"^Signal \(rst sync\) refers to reset of reset-less domain 'sync'$"): + DomainLowerer()(f) + + +class SampleLowererTestCase(FHDLTestCase): + def setUp(self): + self.i = Signal() + self.o1 = Signal() + self.o2 = Signal() + self.o3 = Signal() + + def test_lower_signal(self): + f = Fragment() + f.add_statements( + self.o1.eq(Sample(self.i, 2, "sync")), + self.o2.eq(Sample(self.i, 1, "sync")), + self.o3.eq(Sample(self.i, 1, "pix")), + ) + + f = SampleLowerer()(f) + self.assertRepr(f.statements, """ + ( + (eq (sig o1) (sig $sample$s$i$sync$2)) + (eq (sig o2) (sig $sample$s$i$sync$1)) + (eq (sig o3) (sig $sample$s$i$pix$1)) + (eq (sig $sample$s$i$sync$1) (sig i)) + (eq (sig $sample$s$i$sync$2) (sig $sample$s$i$sync$1)) + (eq (sig $sample$s$i$pix$1) (sig i)) + ) + """) + self.assertEqual(len(f.drivers["sync"]), 2) + self.assertEqual(len(f.drivers["pix"]), 1) + + def test_lower_const(self): + f = Fragment() + f.add_statements( + self.o1.eq(Sample(1, 2, "sync")), + ) + + f = SampleLowerer()(f) + self.assertRepr(f.statements, """ + ( + (eq (sig o1) (sig $sample$c$1$sync$2)) + (eq (sig $sample$c$1$sync$1) (const 1'd1)) + (eq (sig $sample$c$1$sync$2) (sig $sample$c$1$sync$1)) + ) + """) + self.assertEqual(len(f.drivers["sync"]), 2) + + +class SwitchCleanerTestCase(FHDLTestCase): + def test_clean(self): + a = Signal() + b = Signal() + c = Signal() + stmts = [ + Switch(a, { + 1: a.eq(0), + 0: [ + b.eq(1), + Switch(b, {1: [ + Switch(a|b, {}) + ]}) + ] + }) + ] + + self.assertRepr(SwitchCleaner()(stmts), """ + ( + (switch (sig a) + (case 1 + (eq (sig a) (const 1'd0))) + (case 0 + (eq (sig b) (const 1'd1))) + ) + ) + """) + + +class LHSGroupAnalyzerTestCase(FHDLTestCase): + def test_no_group_unrelated(self): + a = Signal() + b = Signal() + stmts = [ + a.eq(0), + b.eq(0), + ] + + groups = LHSGroupAnalyzer()(stmts) + self.assertEqual(list(groups.values()), [ + SignalSet((a,)), + SignalSet((b,)), + ]) + + def test_group_related(self): + a = Signal() + b = Signal() + stmts = [ + a.eq(0), + Cat(a, b).eq(0), + ] + + groups = LHSGroupAnalyzer()(stmts) + self.assertEqual(list(groups.values()), [ + SignalSet((a, b)), + ]) + + def test_no_loops(self): + a = Signal() + b = Signal() + stmts = [ + a.eq(0), + Cat(a, b).eq(0), + Cat(a, b).eq(0), + ] + + groups = LHSGroupAnalyzer()(stmts) + self.assertEqual(list(groups.values()), [ + SignalSet((a, b)), + ]) + + def test_switch(self): + a = Signal() + b = Signal() + stmts = [ + a.eq(0), + Switch(a, { + 1: b.eq(0), + }) + ] + + groups = LHSGroupAnalyzer()(stmts) + self.assertEqual(list(groups.values()), [ + SignalSet((a,)), + SignalSet((b,)), + ]) + + def test_lhs_empty(self): + stmts = [ + Cat().eq(0) + ] + + groups = LHSGroupAnalyzer()(stmts) + self.assertEqual(list(groups.values()), [ + ]) + + +class LHSGroupFilterTestCase(FHDLTestCase): + def test_filter(self): + a = Signal() + b = Signal() + c = Signal() + stmts = [ + Switch(a, { + 1: a.eq(0), + 0: [ + b.eq(1), + Switch(b, {1: []}) + ] + }) + ] + + self.assertRepr(LHSGroupFilter(SignalSet((a,)))(stmts), """ + ( + (switch (sig a) + (case 1 + (eq (sig a) (const 1'd0))) + (case 0 ) + ) + ) + """) + + def test_lhs_empty(self): + stmts = [ + Cat().eq(0) + ] + + self.assertRepr(LHSGroupFilter(SignalSet())(stmts), "()") + + +class ResetInserterTestCase(FHDLTestCase): + def setUp(self): + self.s1 = Signal() + self.s2 = Signal(reset=1) + self.s3 = Signal(reset=1, reset_less=True) + self.c1 = Signal() + + def test_reset_default(self): + f = Fragment() + f.add_statements( + self.s1.eq(1) + ) + f.add_driver(self.s1, "sync") + + f = ResetInserter(self.c1)(f) + self.assertRepr(f.statements, """ + ( + (eq (sig s1) (const 1'd1)) + (switch (sig c1) + (case 1 (eq (sig s1) (const 1'd0))) + ) + ) + """) + + def test_reset_cd(self): + f = Fragment() + f.add_statements( + self.s1.eq(1), + self.s2.eq(0), + ) + f.add_domains(ClockDomain("sync")) + f.add_driver(self.s1, "sync") + f.add_driver(self.s2, "pix") + + f = ResetInserter({"pix": self.c1})(f) + self.assertRepr(f.statements, """ + ( + (eq (sig s1) (const 1'd1)) + (eq (sig s2) (const 1'd0)) + (switch (sig c1) + (case 1 (eq (sig s2) (const 1'd1))) + ) + ) + """) + + def test_reset_value(self): + f = Fragment() + f.add_statements( + self.s2.eq(0) + ) + f.add_driver(self.s2, "sync") + + f = ResetInserter(self.c1)(f) + self.assertRepr(f.statements, """ + ( + (eq (sig s2) (const 1'd0)) + (switch (sig c1) + (case 1 (eq (sig s2) (const 1'd1))) + ) + ) + """) + + def test_reset_less(self): + f = Fragment() + f.add_statements( + self.s3.eq(0) + ) + f.add_driver(self.s3, "sync") + + f = ResetInserter(self.c1)(f) + self.assertRepr(f.statements, """ + ( + (eq (sig s3) (const 1'd0)) + (switch (sig c1) + (case 1 ) + ) + ) + """) + + +class EnableInserterTestCase(FHDLTestCase): + def setUp(self): + self.s1 = Signal() + self.s2 = Signal() + self.s3 = Signal() + self.c1 = Signal() + + def test_enable_default(self): + f = Fragment() + f.add_statements( + self.s1.eq(1) + ) + f.add_driver(self.s1, "sync") + + f = EnableInserter(self.c1)(f) + self.assertRepr(f.statements, """ + ( + (eq (sig s1) (const 1'd1)) + (switch (sig c1) + (case 0 (eq (sig s1) (sig s1))) + ) + ) + """) + + def test_enable_cd(self): + f = Fragment() + f.add_statements( + self.s1.eq(1), + self.s2.eq(0), + ) + f.add_driver(self.s1, "sync") + f.add_driver(self.s2, "pix") + + f = EnableInserter({"pix": self.c1})(f) + self.assertRepr(f.statements, """ + ( + (eq (sig s1) (const 1'd1)) + (eq (sig s2) (const 1'd0)) + (switch (sig c1) + (case 0 (eq (sig s2) (sig s2))) + ) + ) + """) + + def test_enable_subfragment(self): + f1 = Fragment() + f1.add_statements( + self.s1.eq(1) + ) + f1.add_driver(self.s1, "sync") + + f2 = Fragment() + f2.add_statements( + self.s2.eq(1) + ) + f2.add_driver(self.s2, "sync") + f1.add_subfragment(f2) + + f1 = EnableInserter(self.c1)(f1) + (f2, _), = f1.subfragments + self.assertRepr(f1.statements, """ + ( + (eq (sig s1) (const 1'd1)) + (switch (sig c1) + (case 0 (eq (sig s1) (sig s1))) + ) + ) + """) + self.assertRepr(f2.statements, """ + ( + (eq (sig s2) (const 1'd1)) + (switch (sig c1) + (case 0 (eq (sig s2) (sig s2))) + ) + ) + """) + + def test_enable_read_port(self): + mem = Memory(width=8, depth=4) + f = EnableInserter(self.c1)(mem.read_port(transparent=False)).elaborate(platform=None) + self.assertRepr(f.named_ports["EN"][0], """ + (m (sig c1) (sig mem_r_en) (const 1'd0)) + """) + + def test_enable_write_port(self): + mem = Memory(width=8, depth=4) + f = EnableInserter(self.c1)(mem.write_port()).elaborate(platform=None) + self.assertRepr(f.named_ports["EN"][0], """ + (m (sig c1) (cat (repl (slice (sig mem_w_en) 0:1) 8)) (const 8'd0)) + """) + + +class _MockElaboratable(Elaboratable): + def __init__(self): + self.s1 = Signal() + + def elaborate(self, platform): + f = Fragment() + f.add_statements( + self.s1.eq(1) + ) + f.add_driver(self.s1, "sync") + return f + + +class TransformedElaboratableTestCase(FHDLTestCase): + def setUp(self): + self.c1 = Signal() + self.c2 = Signal() + + def test_getattr(self): + e = _MockElaboratable() + te = EnableInserter(self.c1)(e) + + self.assertIs(te.s1, e.s1) + + def test_composition(self): + e = _MockElaboratable() + te1 = EnableInserter(self.c1)(e) + te2 = ResetInserter(self.c2)(te1) + + self.assertIsInstance(te1, TransformedElaboratable) + self.assertIs(te1, te2) + + f = Fragment.get(te2, None) + self.assertRepr(f.statements, """ + ( + (eq (sig s1) (const 1'd1)) + (switch (sig c1) + (case 0 (eq (sig s1) (sig s1))) + ) + (switch (sig c2) + (case 1 (eq (sig s1) (const 1'd0))) + ) + ) + """) + + +class MockUserValue(UserValue): + def __init__(self, lowered): + super().__init__() + self.lowered = lowered + + def lower(self): + return self.lowered + + +class UserValueTestCase(FHDLTestCase): + def setUp(self): + self.s = Signal() + self.c = Signal() + self.uv = MockUserValue(self.s) + + def test_lower(self): + sync = ClockDomain() + f = Fragment() + f.add_domains(sync) + f.add_statements( + self.uv.eq(1) + ) + for signal in self.uv._lhs_signals(): + f.add_driver(signal, "sync") + + f = ResetInserter(self.c)(f) + f = DomainLowerer()(f) + self.assertRepr(f.statements, """ + ( + (eq (sig s) (const 1'd1)) + (switch (sig c) + (case 1 (eq (sig s) (const 1'd0))) + ) + (switch (sig rst) + (case 1 (eq (sig s) (const 1'd0))) + ) + ) + """) + + +class UserValueRecursiveTestCase(UserValueTestCase): + def setUp(self): + self.s = Signal() + self.c = Signal() + self.uv = MockUserValue(MockUserValue(self.s)) + + # inherit the test_lower method from UserValueTestCase because the checks are the same diff --git a/tests/test_lib_cdc.py b/tests/test_lib_cdc.py new file mode 100644 index 0000000..f29052a --- /dev/null +++ b/tests/test_lib_cdc.py @@ -0,0 +1,231 @@ +# nmigen: UnusedElaboratable=no + +from nmigen.hdl import * +from nmigen.back.pysim import * +from nmigen.lib.cdc import * + +from .utils import * + + +class FFSynchronizerTestCase(FHDLTestCase): + def test_stages_wrong(self): + with self.assertRaisesRegex(TypeError, + r"^Synchronization stage count must be a positive integer, not 0$"): + FFSynchronizer(Signal(), Signal(), stages=0) + with self.assertRaisesRegex(ValueError, + r"^Synchronization stage count may not safely be less than 2$"): + FFSynchronizer(Signal(), Signal(), stages=1) + + def test_basic(self): + i = Signal() + o = Signal() + frag = FFSynchronizer(i, o) + + sim = Simulator(frag) + sim.add_clock(1e-6) + def process(): + self.assertEqual((yield o), 0) + yield i.eq(1) + yield Tick() + self.assertEqual((yield o), 0) + yield Tick() + self.assertEqual((yield o), 0) + yield Tick() + self.assertEqual((yield o), 1) + sim.add_process(process) + sim.run() + + def test_reset_value(self): + i = Signal(reset=1) + o = Signal() + frag = FFSynchronizer(i, o, reset=1) + + sim = Simulator(frag) + sim.add_clock(1e-6) + def process(): + self.assertEqual((yield o), 1) + yield i.eq(0) + yield Tick() + self.assertEqual((yield o), 1) + yield Tick() + self.assertEqual((yield o), 1) + yield Tick() + self.assertEqual((yield o), 0) + sim.add_process(process) + sim.run() + + +class AsyncFFSynchronizerTestCase(FHDLTestCase): + def test_stages_wrong(self): + with self.assertRaisesRegex(TypeError, + r"^Synchronization stage count must be a positive integer, not 0$"): + ResetSynchronizer(Signal(), stages=0) + with self.assertRaisesRegex(ValueError, + r"^Synchronization stage count may not safely be less than 2$"): + ResetSynchronizer(Signal(), stages=1) + + def test_edge_wrong(self): + with self.assertRaisesRegex(ValueError, + r"^AsyncFFSynchronizer async edge must be one of 'pos' or 'neg', not 'xxx'$"): + AsyncFFSynchronizer(Signal(), Signal(), o_domain="sync", async_edge="xxx") + + def test_pos_edge(self): + i = Signal() + o = Signal() + m = Module() + m.domains += ClockDomain("sync") + m.submodules += AsyncFFSynchronizer(i, o) + + sim = Simulator(m) + sim.add_clock(1e-6) + def process(): + # initial reset + self.assertEqual((yield i), 0) + self.assertEqual((yield o), 1) + yield Tick(); yield Delay(1e-8) + self.assertEqual((yield o), 1) + yield Tick(); yield Delay(1e-8) + self.assertEqual((yield o), 0) + yield Tick(); yield Delay(1e-8) + self.assertEqual((yield o), 0) + yield Tick(); yield Delay(1e-8) + + yield i.eq(1) + yield Delay(1e-8) + self.assertEqual((yield o), 1) + yield Tick(); yield Delay(1e-8) + self.assertEqual((yield o), 1) + yield i.eq(0) + yield Tick(); yield Delay(1e-8) + self.assertEqual((yield o), 1) + yield Tick(); yield Delay(1e-8) + self.assertEqual((yield o), 0) + yield Tick(); yield Delay(1e-8) + self.assertEqual((yield o), 0) + yield Tick(); yield Delay(1e-8) + sim.add_process(process) + with sim.write_vcd("test.vcd"): + sim.run() + + def test_neg_edge(self): + i = Signal(reset=1) + o = Signal() + m = Module() + m.domains += ClockDomain("sync") + m.submodules += AsyncFFSynchronizer(i, o, async_edge="neg") + + sim = Simulator(m) + sim.add_clock(1e-6) + def process(): + # initial reset + self.assertEqual((yield i), 1) + self.assertEqual((yield o), 1) + yield Tick(); yield Delay(1e-8) + self.assertEqual((yield o), 1) + yield Tick(); yield Delay(1e-8) + self.assertEqual((yield o), 0) + yield Tick(); yield Delay(1e-8) + self.assertEqual((yield o), 0) + yield Tick(); yield Delay(1e-8) + + yield i.eq(0) + yield Delay(1e-8) + self.assertEqual((yield o), 1) + yield Tick(); yield Delay(1e-8) + self.assertEqual((yield o), 1) + yield i.eq(1) + yield Tick(); yield Delay(1e-8) + self.assertEqual((yield o), 1) + yield Tick(); yield Delay(1e-8) + self.assertEqual((yield o), 0) + yield Tick(); yield Delay(1e-8) + self.assertEqual((yield o), 0) + yield Tick(); yield Delay(1e-8) + sim.add_process(process) + with sim.write_vcd("test.vcd"): + sim.run() + + +class ResetSynchronizerTestCase(FHDLTestCase): + def test_stages_wrong(self): + with self.assertRaisesRegex(TypeError, + r"^Synchronization stage count must be a positive integer, not 0$"): + ResetSynchronizer(Signal(), stages=0) + with self.assertRaisesRegex(ValueError, + r"^Synchronization stage count may not safely be less than 2$"): + ResetSynchronizer(Signal(), stages=1) + + def test_basic(self): + arst = Signal() + m = Module() + m.domains += ClockDomain("sync") + m.submodules += ResetSynchronizer(arst) + s = Signal(reset=1) + m.d.sync += s.eq(0) + + sim = Simulator(m) + sim.add_clock(1e-6) + def process(): + # initial reset + self.assertEqual((yield s), 1) + yield Tick(); yield Delay(1e-8) + self.assertEqual((yield s), 1) + yield Tick(); yield Delay(1e-8) + self.assertEqual((yield s), 1) + yield Tick(); yield Delay(1e-8) + self.assertEqual((yield s), 0) + yield Tick(); yield Delay(1e-8) + + yield arst.eq(1) + yield Delay(1e-8) + self.assertEqual((yield s), 0) + yield Tick(); yield Delay(1e-8) + self.assertEqual((yield s), 1) + yield arst.eq(0) + yield Tick(); yield Delay(1e-8) + self.assertEqual((yield s), 1) + yield Tick(); yield Delay(1e-8) + self.assertEqual((yield s), 1) + yield Tick(); yield Delay(1e-8) + self.assertEqual((yield s), 0) + yield Tick(); yield Delay(1e-8) + sim.add_process(process) + with sim.write_vcd("test.vcd"): + sim.run() + + +# TODO: test with distinct clocks +class PulseSynchronizerTestCase(FHDLTestCase): + def test_stages_wrong(self): + with self.assertRaisesRegex(TypeError, + r"^Synchronization stage count must be a positive integer, not 0$"): + PulseSynchronizer("w", "r", stages=0) + with self.assertRaisesRegex(ValueError, + r"^Synchronization stage count may not safely be less than 2$"): + PulseSynchronizer("w", "r", stages=1) + + def test_smoke(self): + m = Module() + m.domains += ClockDomain("sync") + ps = m.submodules.dut = PulseSynchronizer("sync", "sync") + + sim = Simulator(m) + sim.add_clock(1e-6) + def process(): + yield ps.i.eq(0) + # TODO: think about reset + for n in range(5): + yield Tick() + # Make sure no pulses are generated in quiescent state + for n in range(3): + yield Tick() + self.assertEqual((yield ps.o), 0) + # Check conservation of pulses + accum = 0 + for n in range(10): + yield ps.i.eq(1 if n < 4 else 0) + yield Tick() + accum += yield ps.o + self.assertEqual(accum, 4) + sim.add_process(process) + sim.run() diff --git a/tests/test_lib_coding.py b/tests/test_lib_coding.py new file mode 100644 index 0000000..c914e9e --- /dev/null +++ b/tests/test_lib_coding.py @@ -0,0 +1,127 @@ +from nmigen.hdl import * +from nmigen.asserts import * +from nmigen.back.pysim import * +from nmigen.lib.coding import * + +from .utils import * + + +class EncoderTestCase(FHDLTestCase): + def test_basic(self): + enc = Encoder(4) + def process(): + self.assertEqual((yield enc.n), 1) + self.assertEqual((yield enc.o), 0) + + yield enc.i.eq(0b0001) + yield Settle() + self.assertEqual((yield enc.n), 0) + self.assertEqual((yield enc.o), 0) + + yield enc.i.eq(0b0100) + yield Settle() + self.assertEqual((yield enc.n), 0) + self.assertEqual((yield enc.o), 2) + + yield enc.i.eq(0b0110) + yield Settle() + self.assertEqual((yield enc.n), 1) + self.assertEqual((yield enc.o), 0) + + sim = Simulator(enc) + sim.add_process(process) + sim.run() + + +class PriorityEncoderTestCase(FHDLTestCase): + def test_basic(self): + enc = PriorityEncoder(4) + def process(): + self.assertEqual((yield enc.n), 1) + self.assertEqual((yield enc.o), 0) + + yield enc.i.eq(0b0001) + yield Settle() + self.assertEqual((yield enc.n), 0) + self.assertEqual((yield enc.o), 0) + + yield enc.i.eq(0b0100) + yield Settle() + self.assertEqual((yield enc.n), 0) + self.assertEqual((yield enc.o), 2) + + yield enc.i.eq(0b0110) + yield Settle() + self.assertEqual((yield enc.n), 0) + self.assertEqual((yield enc.o), 1) + + sim = Simulator(enc) + sim.add_process(process) + sim.run() + + +class DecoderTestCase(FHDLTestCase): + def test_basic(self): + dec = Decoder(4) + def process(): + self.assertEqual((yield dec.o), 0b0001) + + yield dec.i.eq(1) + yield Settle() + self.assertEqual((yield dec.o), 0b0010) + + yield dec.i.eq(3) + yield Settle() + self.assertEqual((yield dec.o), 0b1000) + + yield dec.n.eq(1) + yield Settle() + self.assertEqual((yield dec.o), 0b0000) + + sim = Simulator(dec) + sim.add_process(process) + sim.run() + + +class ReversibleSpec(Elaboratable): + def __init__(self, encoder_cls, decoder_cls, args): + self.encoder_cls = encoder_cls + self.decoder_cls = decoder_cls + self.coder_args = args + + def elaborate(self, platform): + m = Module() + enc, dec = self.encoder_cls(*self.coder_args), self.decoder_cls(*self.coder_args) + m.submodules += enc, dec + m.d.comb += [ + dec.i.eq(enc.o), + Assert(enc.i == dec.o) + ] + return m + + +class HammingDistanceSpec(Elaboratable): + def __init__(self, distance, encoder_cls, args): + self.distance = distance + self.encoder_cls = encoder_cls + self.coder_args = args + + def elaborate(self, platform): + m = Module() + enc1, enc2 = self.encoder_cls(*self.coder_args), self.encoder_cls(*self.coder_args) + m.submodules += enc1, enc2 + m.d.comb += [ + Assume(enc1.i + 1 == enc2.i), + Assert(sum(enc1.o ^ enc2.o) == self.distance) + ] + return m + + +class GrayCoderTestCase(FHDLTestCase): + def test_reversible(self): + spec = ReversibleSpec(encoder_cls=GrayEncoder, decoder_cls=GrayDecoder, args=(16,)) + self.assertFormal(spec, mode="prove") + + def test_distance(self): + spec = HammingDistanceSpec(distance=1, encoder_cls=GrayEncoder, args=(16,)) + self.assertFormal(spec, mode="prove") diff --git a/tests/test_lib_fifo.py b/tests/test_lib_fifo.py new file mode 100644 index 0000000..83dc825 --- /dev/null +++ b/tests/test_lib_fifo.py @@ -0,0 +1,282 @@ +# nmigen: UnusedElaboratable=no + +from nmigen.hdl import * +from nmigen.asserts import * +from nmigen.back.pysim import * +from nmigen.lib.fifo import * + +from .utils import * + + +class FIFOTestCase(FHDLTestCase): + def test_depth_wrong(self): + with self.assertRaisesRegex(TypeError, + r"^FIFO width must be a non-negative integer, not -1$"): + FIFOInterface(width=-1, depth=8, fwft=True) + with self.assertRaisesRegex(TypeError, + r"^FIFO depth must be a non-negative integer, not -1$"): + FIFOInterface(width=8, depth=-1, fwft=True) + + def test_sync_depth(self): + self.assertEqual(SyncFIFO(width=8, depth=0).depth, 0) + self.assertEqual(SyncFIFO(width=8, depth=1).depth, 1) + self.assertEqual(SyncFIFO(width=8, depth=2).depth, 2) + + def test_sync_buffered_depth(self): + self.assertEqual(SyncFIFOBuffered(width=8, depth=0).depth, 0) + self.assertEqual(SyncFIFOBuffered(width=8, depth=1).depth, 1) + self.assertEqual(SyncFIFOBuffered(width=8, depth=2).depth, 2) + + def test_async_depth(self): + self.assertEqual(AsyncFIFO(width=8, depth=0 ).depth, 0) + self.assertEqual(AsyncFIFO(width=8, depth=1 ).depth, 1) + self.assertEqual(AsyncFIFO(width=8, depth=2 ).depth, 2) + self.assertEqual(AsyncFIFO(width=8, depth=3 ).depth, 4) + self.assertEqual(AsyncFIFO(width=8, depth=4 ).depth, 4) + self.assertEqual(AsyncFIFO(width=8, depth=15).depth, 16) + self.assertEqual(AsyncFIFO(width=8, depth=16).depth, 16) + self.assertEqual(AsyncFIFO(width=8, depth=17).depth, 32) + + def test_async_depth_wrong(self): + with self.assertRaisesRegex(ValueError, + (r"^AsyncFIFO only supports depths that are powers of 2; " + r"requested exact depth 15 is not$")): + AsyncFIFO(width=8, depth=15, exact_depth=True) + + def test_async_buffered_depth(self): + self.assertEqual(AsyncFIFOBuffered(width=8, depth=0 ).depth, 0) + self.assertEqual(AsyncFIFOBuffered(width=8, depth=1 ).depth, 2) + self.assertEqual(AsyncFIFOBuffered(width=8, depth=2 ).depth, 2) + self.assertEqual(AsyncFIFOBuffered(width=8, depth=3 ).depth, 3) + self.assertEqual(AsyncFIFOBuffered(width=8, depth=4 ).depth, 5) + self.assertEqual(AsyncFIFOBuffered(width=8, depth=15).depth, 17) + self.assertEqual(AsyncFIFOBuffered(width=8, depth=16).depth, 17) + self.assertEqual(AsyncFIFOBuffered(width=8, depth=17).depth, 17) + self.assertEqual(AsyncFIFOBuffered(width=8, depth=18).depth, 33) + + def test_async_buffered_depth_wrong(self): + with self.assertRaisesRegex(ValueError, + (r"^AsyncFIFOBuffered only supports depths that are one higher than powers of 2; " + r"requested exact depth 16 is not$")): + AsyncFIFOBuffered(width=8, depth=16, exact_depth=True) + +class FIFOModel(Elaboratable, FIFOInterface): + """ + Non-synthesizable first-in first-out queue, implemented naively as a chain of registers. + """ + def __init__(self, *, width, depth, fwft, r_domain, w_domain): + super().__init__(width=width, depth=depth, fwft=fwft) + + self.r_domain = r_domain + self.w_domain = w_domain + + self.level = Signal(range(self.depth + 1)) + self.r_level = Signal(range(self.depth + 1)) + self.w_level = Signal(range(self.depth + 1)) + + def elaborate(self, platform): + m = Module() + + storage = Memory(width=self.width, depth=self.depth) + w_port = m.submodules.w_port = storage.write_port(domain=self.w_domain) + r_port = m.submodules.r_port = storage.read_port (domain="comb") + + produce = Signal(range(self.depth)) + consume = Signal(range(self.depth)) + + m.d.comb += self.r_rdy.eq(self.level > 0) + m.d.comb += r_port.addr.eq((consume + 1) % self.depth) + if self.fwft: + m.d.comb += self.r_data.eq(r_port.data) + with m.If(self.r_en & self.r_rdy): + if not self.fwft: + m.d[self.r_domain] += self.r_data.eq(r_port.data) + m.d[self.r_domain] += consume.eq(r_port.addr) + + m.d.comb += self.w_rdy.eq(self.level < self.depth) + m.d.comb += w_port.data.eq(self.w_data) + with m.If(self.w_en & self.w_rdy): + m.d.comb += w_port.addr.eq((produce + 1) % self.depth) + m.d.comb += w_port.en.eq(1) + m.d[self.w_domain] += produce.eq(w_port.addr) + + with m.If(ResetSignal(self.r_domain) | ResetSignal(self.w_domain)): + m.d.sync += self.level.eq(0) + with m.Else(): + m.d.sync += self.level.eq(self.level + + (self.w_rdy & self.w_en) + - (self.r_rdy & self.r_en)) + + m.d.comb += [ + self.r_level.eq(self.level), + self.w_level.eq(self.level), + ] + m.d.comb += Assert(ResetSignal(self.r_domain) == ResetSignal(self.w_domain)) + + return m + + +class FIFOModelEquivalenceSpec(Elaboratable): + """ + The first-in first-out queue model equivalence specification: for any inputs and control + signals, the behavior of the implementation under test exactly matches the ideal model, + except for behavior not defined by the model. + """ + def __init__(self, fifo, r_domain, w_domain): + self.fifo = fifo + + self.r_domain = r_domain + self.w_domain = w_domain + + def elaborate(self, platform): + m = Module() + m.submodules.dut = dut = self.fifo + m.submodules.gold = gold = FIFOModel(width=dut.width, depth=dut.depth, fwft=dut.fwft, + r_domain=self.r_domain, w_domain=self.w_domain) + + m.d.comb += [ + gold.r_en.eq(dut.r_rdy & dut.r_en), + gold.w_en.eq(dut.w_en), + gold.w_data.eq(dut.w_data), + ] + + m.d.comb += Assert(dut.r_rdy.implies(gold.r_rdy)) + m.d.comb += Assert(dut.w_rdy.implies(gold.w_rdy)) + m.d.comb += Assert(dut.r_level == gold.r_level) + m.d.comb += Assert(dut.w_level == gold.w_level) + + if dut.fwft: + m.d.comb += Assert(dut.r_rdy + .implies(dut.r_data == gold.r_data)) + else: + m.d.comb += Assert((Past(dut.r_rdy, domain=self.r_domain) & + Past(dut.r_en, domain=self.r_domain)) + .implies(dut.r_data == gold.r_data)) + + return m + + +class FIFOContractSpec(Elaboratable): + """ + The first-in first-out queue contract specification: if two elements are written to the queue + consecutively, they must be read out consecutively at some later point, no matter all other + circumstances, with the exception of reset. + """ + def __init__(self, fifo, *, r_domain, w_domain, bound): + self.fifo = fifo + self.r_domain = r_domain + self.w_domain = w_domain + self.bound = bound + + def elaborate(self, platform): + m = Module() + m.submodules.dut = fifo = self.fifo + + m.domains += ClockDomain("sync") + m.d.comb += ResetSignal().eq(0) + if self.w_domain != "sync": + m.domains += ClockDomain(self.w_domain) + m.d.comb += ResetSignal(self.w_domain).eq(0) + if self.r_domain != "sync": + m.domains += ClockDomain(self.r_domain) + m.d.comb += ResetSignal(self.r_domain).eq(0) + + entry_1 = AnyConst(fifo.width) + entry_2 = AnyConst(fifo.width) + + with m.FSM(domain=self.w_domain) as write_fsm: + with m.State("WRITE-1"): + with m.If(fifo.w_rdy): + m.d.comb += [ + fifo.w_data.eq(entry_1), + fifo.w_en.eq(1) + ] + m.next = "WRITE-2" + with m.State("WRITE-2"): + with m.If(fifo.w_rdy): + m.d.comb += [ + fifo.w_data.eq(entry_2), + fifo.w_en.eq(1) + ] + m.next = "DONE" + with m.State("DONE"): + pass + + with m.FSM(domain=self.r_domain) as read_fsm: + read_1 = Signal(fifo.width) + read_2 = Signal(fifo.width) + with m.State("READ"): + m.d.comb += fifo.r_en.eq(1) + if fifo.fwft: + r_rdy = fifo.r_rdy + else: + r_rdy = Past(fifo.r_rdy, domain=self.r_domain) + with m.If(r_rdy): + m.d.sync += [ + read_1.eq(read_2), + read_2.eq(fifo.r_data), + ] + with m.If((read_1 == entry_1) & (read_2 == entry_2)): + m.next = "DONE" + with m.State("DONE"): + pass + + with m.If(Initial()): + m.d.comb += Assume(write_fsm.ongoing("WRITE-1")) + m.d.comb += Assume(read_fsm.ongoing("READ")) + with m.If(Past(Initial(), self.bound - 1)): + m.d.comb += Assert(read_fsm.ongoing("DONE")) + + with m.If(ResetSignal(domain=self.w_domain)): + m.d.comb += Assert(~fifo.r_rdy) + + if self.w_domain != "sync" or self.r_domain != "sync": + m.d.comb += Assume(Rose(ClockSignal(self.w_domain)) | + Rose(ClockSignal(self.r_domain))) + + return m + + +class FIFOFormalCase(FHDLTestCase): + def check_sync_fifo(self, fifo): + self.assertFormal(FIFOModelEquivalenceSpec(fifo, r_domain="sync", w_domain="sync"), + mode="bmc", depth=fifo.depth + 1) + self.assertFormal(FIFOContractSpec(fifo, r_domain="sync", w_domain="sync", + bound=fifo.depth * 2 + 1), + mode="hybrid", depth=fifo.depth * 2 + 1) + + def test_sync_fwft_pot(self): + self.check_sync_fifo(SyncFIFO(width=8, depth=4, fwft=True)) + + def test_sync_fwft_npot(self): + self.check_sync_fifo(SyncFIFO(width=8, depth=5, fwft=True)) + + def test_sync_not_fwft_pot(self): + self.check_sync_fifo(SyncFIFO(width=8, depth=4, fwft=False)) + + def test_sync_not_fwft_npot(self): + self.check_sync_fifo(SyncFIFO(width=8, depth=5, fwft=False)) + + def test_sync_buffered_pot(self): + self.check_sync_fifo(SyncFIFOBuffered(width=8, depth=4)) + + def test_sync_buffered_potp1(self): + self.check_sync_fifo(SyncFIFOBuffered(width=8, depth=5)) + + def test_sync_buffered_potm1(self): + self.check_sync_fifo(SyncFIFOBuffered(width=8, depth=3)) + + def check_async_fifo(self, fifo): + # TODO: properly doing model equivalence checking on this likely requires multiclock, + # which is not really documented nor is it clear how to use it. + # self.assertFormal(FIFOModelEquivalenceSpec(fifo, r_domain="read", w_domain="write"), + # mode="bmc", depth=fifo.depth * 3 + 1) + self.assertFormal(FIFOContractSpec(fifo, r_domain="read", w_domain="write", + bound=fifo.depth * 4 + 1), + mode="hybrid", depth=fifo.depth * 4 + 1) + + def test_async(self): + self.check_async_fifo(AsyncFIFO(width=8, depth=4)) + + def test_async_buffered(self): + self.check_async_fifo(AsyncFIFOBuffered(width=8, depth=4)) diff --git a/tests/test_lib_io.py b/tests/test_lib_io.py new file mode 100644 index 0000000..234df1d --- /dev/null +++ b/tests/test_lib_io.py @@ -0,0 +1,209 @@ +from nmigen.hdl import * +from nmigen.hdl.rec import * +from nmigen.back.pysim import * +from nmigen.lib.io import * + +from .utils import * + + +class PinLayoutTestCase(FHDLTestCase): + def assertLayoutEqual(self, layout, expected): + casted_layout = {} + for name, (shape, dir) in layout.items(): + casted_layout[name] = (Shape.cast(shape), dir) + + self.assertEqual(casted_layout, expected) + + +class PinLayoutCombTestCase(PinLayoutTestCase): + def test_pin_layout_i(self): + layout_1 = pin_layout(1, dir="i") + self.assertLayoutEqual(layout_1.fields, { + "i": ((1, False), DIR_NONE), + }) + + layout_2 = pin_layout(2, dir="i") + self.assertLayoutEqual(layout_2.fields, { + "i": ((2, False), DIR_NONE), + }) + + def test_pin_layout_o(self): + layout_1 = pin_layout(1, dir="o") + self.assertLayoutEqual(layout_1.fields, { + "o": ((1, False), DIR_NONE), + }) + + layout_2 = pin_layout(2, dir="o") + self.assertLayoutEqual(layout_2.fields, { + "o": ((2, False), DIR_NONE), + }) + + def test_pin_layout_oe(self): + layout_1 = pin_layout(1, dir="oe") + self.assertLayoutEqual(layout_1.fields, { + "o": ((1, False), DIR_NONE), + "oe": ((1, False), DIR_NONE), + }) + + layout_2 = pin_layout(2, dir="oe") + self.assertLayoutEqual(layout_2.fields, { + "o": ((2, False), DIR_NONE), + "oe": ((1, False), DIR_NONE), + }) + + def test_pin_layout_io(self): + layout_1 = pin_layout(1, dir="io") + self.assertLayoutEqual(layout_1.fields, { + "i": ((1, False), DIR_NONE), + "o": ((1, False), DIR_NONE), + "oe": ((1, False), DIR_NONE), + }) + + layout_2 = pin_layout(2, dir="io") + self.assertLayoutEqual(layout_2.fields, { + "i": ((2, False), DIR_NONE), + "o": ((2, False), DIR_NONE), + "oe": ((1, False), DIR_NONE), + }) + + +class PinLayoutSDRTestCase(PinLayoutTestCase): + def test_pin_layout_i(self): + layout_1 = pin_layout(1, dir="i", xdr=1) + self.assertLayoutEqual(layout_1.fields, { + "i_clk": ((1, False), DIR_NONE), + "i": ((1, False), DIR_NONE), + }) + + layout_2 = pin_layout(2, dir="i", xdr=1) + self.assertLayoutEqual(layout_2.fields, { + "i_clk": ((1, False), DIR_NONE), + "i": ((2, False), DIR_NONE), + }) + + def test_pin_layout_o(self): + layout_1 = pin_layout(1, dir="o", xdr=1) + self.assertLayoutEqual(layout_1.fields, { + "o_clk": ((1, False), DIR_NONE), + "o": ((1, False), DIR_NONE), + }) + + layout_2 = pin_layout(2, dir="o", xdr=1) + self.assertLayoutEqual(layout_2.fields, { + "o_clk": ((1, False), DIR_NONE), + "o": ((2, False), DIR_NONE), + }) + + def test_pin_layout_oe(self): + layout_1 = pin_layout(1, dir="oe", xdr=1) + self.assertLayoutEqual(layout_1.fields, { + "o_clk": ((1, False), DIR_NONE), + "o": ((1, False), DIR_NONE), + "oe": ((1, False), DIR_NONE), + }) + + layout_2 = pin_layout(2, dir="oe", xdr=1) + self.assertLayoutEqual(layout_2.fields, { + "o_clk": ((1, False), DIR_NONE), + "o": ((2, False), DIR_NONE), + "oe": ((1, False), DIR_NONE), + }) + + def test_pin_layout_io(self): + layout_1 = pin_layout(1, dir="io", xdr=1) + self.assertLayoutEqual(layout_1.fields, { + "i_clk": ((1, False), DIR_NONE), + "i": ((1, False), DIR_NONE), + "o_clk": ((1, False), DIR_NONE), + "o": ((1, False), DIR_NONE), + "oe": ((1, False), DIR_NONE), + }) + + layout_2 = pin_layout(2, dir="io", xdr=1) + self.assertLayoutEqual(layout_2.fields, { + "i_clk": ((1, False), DIR_NONE), + "i": ((2, False), DIR_NONE), + "o_clk": ((1, False), DIR_NONE), + "o": ((2, False), DIR_NONE), + "oe": ((1, False), DIR_NONE), + }) + + +class PinLayoutDDRTestCase(PinLayoutTestCase): + def test_pin_layout_i(self): + layout_1 = pin_layout(1, dir="i", xdr=2) + self.assertLayoutEqual(layout_1.fields, { + "i_clk": ((1, False), DIR_NONE), + "i0": ((1, False), DIR_NONE), + "i1": ((1, False), DIR_NONE), + }) + + layout_2 = pin_layout(2, dir="i", xdr=2) + self.assertLayoutEqual(layout_2.fields, { + "i_clk": ((1, False), DIR_NONE), + "i0": ((2, False), DIR_NONE), + "i1": ((2, False), DIR_NONE), + }) + + def test_pin_layout_o(self): + layout_1 = pin_layout(1, dir="o", xdr=2) + self.assertLayoutEqual(layout_1.fields, { + "o_clk": ((1, False), DIR_NONE), + "o0": ((1, False), DIR_NONE), + "o1": ((1, False), DIR_NONE), + }) + + layout_2 = pin_layout(2, dir="o", xdr=2) + self.assertLayoutEqual(layout_2.fields, { + "o_clk": ((1, False), DIR_NONE), + "o0": ((2, False), DIR_NONE), + "o1": ((2, False), DIR_NONE), + }) + + def test_pin_layout_oe(self): + layout_1 = pin_layout(1, dir="oe", xdr=2) + self.assertLayoutEqual(layout_1.fields, { + "o_clk": ((1, False), DIR_NONE), + "o0": ((1, False), DIR_NONE), + "o1": ((1, False), DIR_NONE), + "oe": ((1, False), DIR_NONE), + }) + + layout_2 = pin_layout(2, dir="oe", xdr=2) + self.assertLayoutEqual(layout_2.fields, { + "o_clk": ((1, False), DIR_NONE), + "o0": ((2, False), DIR_NONE), + "o1": ((2, False), DIR_NONE), + "oe": ((1, False), DIR_NONE), + }) + + def test_pin_layout_io(self): + layout_1 = pin_layout(1, dir="io", xdr=2) + self.assertLayoutEqual(layout_1.fields, { + "i_clk": ((1, False), DIR_NONE), + "i0": ((1, False), DIR_NONE), + "i1": ((1, False), DIR_NONE), + "o_clk": ((1, False), DIR_NONE), + "o0": ((1, False), DIR_NONE), + "o1": ((1, False), DIR_NONE), + "oe": ((1, False), DIR_NONE), + }) + + layout_2 = pin_layout(2, dir="io", xdr=2) + self.assertLayoutEqual(layout_2.fields, { + "i_clk": ((1, False), DIR_NONE), + "i0": ((2, False), DIR_NONE), + "i1": ((2, False), DIR_NONE), + "o_clk": ((1, False), DIR_NONE), + "o0": ((2, False), DIR_NONE), + "o1": ((2, False), DIR_NONE), + "oe": ((1, False), DIR_NONE), + }) + + +class PinTestCase(FHDLTestCase): + def test_attributes(self): + pin = Pin(2, dir="io", xdr=2) + self.assertEqual(pin.width, 2) + self.assertEqual(pin.dir, "io") + self.assertEqual(pin.xdr, 2) diff --git a/tests/test_lib_scheduler.py b/tests/test_lib_scheduler.py new file mode 100644 index 0000000..3128ad1 --- /dev/null +++ b/tests/test_lib_scheduler.py @@ -0,0 +1,96 @@ +# nmigen: UnusedElaboratable=no + +import unittest + +from nmigen.hdl import * +from nmigen.asserts import * +from nmigen.sim.pysim import * +from nmigen.lib.scheduler import * + +from .utils import * + + +class RoundRobinTestCase(unittest.TestCase): + def test_count(self): + dut = RoundRobin(count=32) + self.assertEqual(dut.count, 32) + self.assertEqual(len(dut.requests), 32) + self.assertEqual(len(dut.grant), 5) + + def test_wrong_count(self): + with self.assertRaisesRegex(ValueError, r"Count must be a non-negative integer, not 'foo'"): + dut = RoundRobin(count="foo") + with self.assertRaisesRegex(ValueError, r"Count must be a non-negative integer, not -1"): + dut = RoundRobin(count=-1) + + +class RoundRobinSimulationTestCase(unittest.TestCase): + def test_count_one(self): + dut = RoundRobin(count=1) + sim = Simulator(dut) + def process(): + yield dut.requests.eq(0) + yield; yield Delay(1e-8) + self.assertEqual((yield dut.grant), 0) + self.assertFalse((yield dut.valid)) + + yield dut.requests.eq(1) + yield; yield Delay(1e-8) + self.assertEqual((yield dut.grant), 0) + self.assertTrue((yield dut.valid)) + sim.add_sync_process(process) + sim.add_clock(1e-6) + with sim.write_vcd("test.vcd"): + sim.run() + + def test_transitions(self): + dut = RoundRobin(count=3) + sim = Simulator(dut) + def process(): + yield dut.requests.eq(0b111) + yield; yield Delay(1e-8) + self.assertEqual((yield dut.grant), 1) + self.assertTrue((yield dut.valid)) + + yield dut.requests.eq(0b110) + yield; yield Delay(1e-8) + self.assertEqual((yield dut.grant), 2) + self.assertTrue((yield dut.valid)) + + yield dut.requests.eq(0b010) + yield; yield Delay(1e-8) + self.assertEqual((yield dut.grant), 1) + self.assertTrue((yield dut.valid)) + + yield dut.requests.eq(0b011) + yield; yield Delay(1e-8) + self.assertEqual((yield dut.grant), 0) + self.assertTrue((yield dut.valid)) + + yield dut.requests.eq(0b001) + yield; yield Delay(1e-8) + self.assertEqual((yield dut.grant), 0) + self.assertTrue((yield dut.valid)) + + yield dut.requests.eq(0b101) + yield; yield Delay(1e-8) + self.assertEqual((yield dut.grant), 2) + self.assertTrue((yield dut.valid)) + + yield dut.requests.eq(0b100) + yield; yield Delay(1e-8) + self.assertEqual((yield dut.grant), 2) + self.assertTrue((yield dut.valid)) + + yield dut.requests.eq(0b000) + yield; yield Delay(1e-8) + self.assertFalse((yield dut.valid)) + + yield dut.requests.eq(0b001) + yield; yield Delay(1e-8) + self.assertEqual((yield dut.grant), 0) + self.assertTrue((yield dut.valid)) + sim.add_sync_process(process) + sim.add_clock(1e-6) + with sim.write_vcd("test.vcd"): + sim.run() diff --git a/tests/test_sim.py b/tests/test_sim.py new file mode 100644 index 0000000..6c125ba --- /dev/null +++ b/tests/test_sim.py @@ -0,0 +1,799 @@ +import os +from contextlib import contextmanager + +from nmigen._utils import flatten, union +from nmigen.hdl.ast import * +from nmigen.hdl.cd import * +from nmigen.hdl.mem import * +from nmigen.hdl.rec import * +from nmigen.hdl.dsl import * +from nmigen.hdl.ir import * +from nmigen.back.pysim import * + +from .utils import * + + +class SimulatorUnitTestCase(FHDLTestCase): + def assertStatement(self, stmt, inputs, output, reset=0): + inputs = [Value.cast(i) for i in inputs] + output = Value.cast(output) + + isigs = [Signal(i.shape(), name=n) for i, n in zip(inputs, "abcd")] + osig = Signal(output.shape(), name="y", reset=reset) + + stmt = stmt(osig, *isigs) + frag = Fragment() + frag.add_statements(stmt) + for signal in flatten(s._lhs_signals() for s in Statement.cast(stmt)): + frag.add_driver(signal) + + sim = Simulator(frag) + def process(): + for isig, input in zip(isigs, inputs): + yield isig.eq(input) + yield Settle() + self.assertEqual((yield osig), output.value) + sim.add_process(process) + with sim.write_vcd("test.vcd", "test.gtkw", traces=[*isigs, osig]): + sim.run() + + def test_invert(self): + stmt = lambda y, a: y.eq(~a) + self.assertStatement(stmt, [C(0b0000, 4)], C(0b1111, 4)) + self.assertStatement(stmt, [C(0b1010, 4)], C(0b0101, 4)) + self.assertStatement(stmt, [C(0, 4)], C(-1, 4)) + + def test_neg(self): + stmt = lambda y, a: y.eq(-a) + self.assertStatement(stmt, [C(0b0000, 4)], C(0b0000, 4)) + self.assertStatement(stmt, [C(0b0001, 4)], C(0b1111, 4)) + self.assertStatement(stmt, [C(0b1010, 4)], C(0b0110, 4)) + self.assertStatement(stmt, [C(1, 4)], C(-1, 4)) + self.assertStatement(stmt, [C(5, 4)], C(-5, 4)) + + def test_bool(self): + stmt = lambda y, a: y.eq(a.bool()) + self.assertStatement(stmt, [C(0, 4)], C(0)) + self.assertStatement(stmt, [C(1, 4)], C(1)) + self.assertStatement(stmt, [C(2, 4)], C(1)) + + def test_as_unsigned(self): + stmt = lambda y, a, b: y.eq(a.as_unsigned() == b) + self.assertStatement(stmt, [C(0b01, signed(2)), C(0b0001, unsigned(4))], C(1)) + self.assertStatement(stmt, [C(0b11, signed(2)), C(0b0011, unsigned(4))], C(1)) + + def test_as_signed(self): + stmt = lambda y, a, b: y.eq(a.as_signed() == b) + self.assertStatement(stmt, [C(0b01, unsigned(2)), C(0b0001, signed(4))], C(1)) + self.assertStatement(stmt, [C(0b11, unsigned(2)), C(0b1111, signed(4))], C(1)) + + def test_any(self): + stmt = lambda y, a: y.eq(a.any()) + self.assertStatement(stmt, [C(0b00, 2)], C(0)) + self.assertStatement(stmt, [C(0b01, 2)], C(1)) + self.assertStatement(stmt, [C(0b10, 2)], C(1)) + self.assertStatement(stmt, [C(0b11, 2)], C(1)) + + def test_all(self): + stmt = lambda y, a: y.eq(a.all()) + self.assertStatement(stmt, [C(0b00, 2)], C(0)) + self.assertStatement(stmt, [C(0b01, 2)], C(0)) + self.assertStatement(stmt, [C(0b10, 2)], C(0)) + self.assertStatement(stmt, [C(0b11, 2)], C(1)) + + def test_xor_unary(self): + stmt = lambda y, a: y.eq(a.xor()) + self.assertStatement(stmt, [C(0b00, 2)], C(0)) + self.assertStatement(stmt, [C(0b01, 2)], C(1)) + self.assertStatement(stmt, [C(0b10, 2)], C(1)) + self.assertStatement(stmt, [C(0b11, 2)], C(0)) + + def test_add(self): + stmt = lambda y, a, b: y.eq(a + b) + self.assertStatement(stmt, [C(0, 4), C(1, 4)], C(1, 4)) + self.assertStatement(stmt, [C(-5, 4), C(-5, 4)], C(-10, 5)) + + def test_sub(self): + stmt = lambda y, a, b: y.eq(a - b) + self.assertStatement(stmt, [C(2, 4), C(1, 4)], C(1, 4)) + self.assertStatement(stmt, [C(0, 4), C(1, 4)], C(-1, 4)) + self.assertStatement(stmt, [C(0, 4), C(10, 4)], C(-10, 5)) + + def test_mul(self): + stmt = lambda y, a, b: y.eq(a * b) + self.assertStatement(stmt, [C(2, 4), C(1, 4)], C(2, 8)) + self.assertStatement(stmt, [C(2, 4), C(2, 4)], C(4, 8)) + self.assertStatement(stmt, [C(7, 4), C(7, 4)], C(49, 8)) + + def test_floordiv(self): + stmt = lambda y, a, b: y.eq(a // b) + self.assertStatement(stmt, [C(2, 4), C(1, 4)], C(2, 8)) + self.assertStatement(stmt, [C(2, 4), C(2, 4)], C(1, 8)) + self.assertStatement(stmt, [C(7, 4), C(2, 4)], C(3, 8)) + + def test_mod(self): + stmt = lambda y, a, b: y.eq(a % b) + self.assertStatement(stmt, [C(2, 4), C(0, 4)], C(0, 8)) + self.assertStatement(stmt, [C(2, 4), C(1, 4)], C(0, 8)) + self.assertStatement(stmt, [C(2, 4), C(2, 4)], C(0, 8)) + self.assertStatement(stmt, [C(7, 4), C(2, 4)], C(1, 8)) + + def test_and(self): + stmt = lambda y, a, b: y.eq(a & b) + self.assertStatement(stmt, [C(0b1100, 4), C(0b1010, 4)], C(0b1000, 4)) + + def test_or(self): + stmt = lambda y, a, b: y.eq(a | b) + self.assertStatement(stmt, [C(0b1100, 4), C(0b1010, 4)], C(0b1110, 4)) + + def test_xor_binary(self): + stmt = lambda y, a, b: y.eq(a ^ b) + self.assertStatement(stmt, [C(0b1100, 4), C(0b1010, 4)], C(0b0110, 4)) + + def test_shl(self): + stmt = lambda y, a, b: y.eq(a << b) + self.assertStatement(stmt, [C(0b1001, 4), C(0)], C(0b1001, 5)) + self.assertStatement(stmt, [C(0b1001, 4), C(3)], C(0b1001000, 7)) + + def test_shr(self): + stmt = lambda y, a, b: y.eq(a >> b) + self.assertStatement(stmt, [C(0b1001, 4), C(0)], C(0b1001, 4)) + self.assertStatement(stmt, [C(0b1001, 4), C(2)], C(0b10, 4)) + + def test_eq(self): + stmt = lambda y, a, b: y.eq(a == b) + self.assertStatement(stmt, [C(0, 4), C(0, 4)], C(1)) + self.assertStatement(stmt, [C(0, 4), C(1, 4)], C(0)) + self.assertStatement(stmt, [C(1, 4), C(0, 4)], C(0)) + + def test_ne(self): + stmt = lambda y, a, b: y.eq(a != b) + self.assertStatement(stmt, [C(0, 4), C(0, 4)], C(0)) + self.assertStatement(stmt, [C(0, 4), C(1, 4)], C(1)) + self.assertStatement(stmt, [C(1, 4), C(0, 4)], C(1)) + + def test_lt(self): + stmt = lambda y, a, b: y.eq(a < b) + self.assertStatement(stmt, [C(0, 4), C(0, 4)], C(0)) + self.assertStatement(stmt, [C(0, 4), C(1, 4)], C(1)) + self.assertStatement(stmt, [C(1, 4), C(0, 4)], C(0)) + + def test_ge(self): + stmt = lambda y, a, b: y.eq(a >= b) + self.assertStatement(stmt, [C(0, 4), C(0, 4)], C(1)) + self.assertStatement(stmt, [C(0, 4), C(1, 4)], C(0)) + self.assertStatement(stmt, [C(1, 4), C(0, 4)], C(1)) + + def test_gt(self): + stmt = lambda y, a, b: y.eq(a > b) + self.assertStatement(stmt, [C(0, 4), C(0, 4)], C(0)) + self.assertStatement(stmt, [C(0, 4), C(1, 4)], C(0)) + self.assertStatement(stmt, [C(1, 4), C(0, 4)], C(1)) + + def test_le(self): + stmt = lambda y, a, b: y.eq(a <= b) + self.assertStatement(stmt, [C(0, 4), C(0, 4)], C(1)) + self.assertStatement(stmt, [C(0, 4), C(1, 4)], C(1)) + self.assertStatement(stmt, [C(1, 4), C(0, 4)], C(0)) + + def test_mux(self): + stmt = lambda y, a, b, c: y.eq(Mux(c, a, b)) + self.assertStatement(stmt, [C(2, 4), C(3, 4), C(0)], C(3, 4)) + self.assertStatement(stmt, [C(2, 4), C(3, 4), C(1)], C(2, 4)) + + def test_abs(self): + stmt = lambda y, a: y.eq(abs(a)) + self.assertStatement(stmt, [C(3, unsigned(8))], C(3, unsigned(8))) + self.assertStatement(stmt, [C(-3, unsigned(8))], C(-3, unsigned(8))) + self.assertStatement(stmt, [C(3, signed(8))], C(3, signed(8))) + self.assertStatement(stmt, [C(-3, signed(8))], C(3, signed(8))) + + def test_slice(self): + stmt1 = lambda y, a: y.eq(a[2]) + self.assertStatement(stmt1, [C(0b10110100, 8)], C(0b1, 1)) + stmt2 = lambda y, a: y.eq(a[2:4]) + self.assertStatement(stmt2, [C(0b10110100, 8)], C(0b01, 2)) + + def test_slice_lhs(self): + stmt1 = lambda y, a: y[2].eq(a) + self.assertStatement(stmt1, [C(0b0, 1)], C(0b11111011, 8), reset=0b11111111) + stmt2 = lambda y, a: y[2:4].eq(a) + self.assertStatement(stmt2, [C(0b01, 2)], C(0b11110111, 8), reset=0b11111011) + + def test_bit_select(self): + stmt = lambda y, a, b: y.eq(a.bit_select(b, 3)) + self.assertStatement(stmt, [C(0b10110100, 8), C(0)], C(0b100, 3)) + self.assertStatement(stmt, [C(0b10110100, 8), C(2)], C(0b101, 3)) + self.assertStatement(stmt, [C(0b10110100, 8), C(3)], C(0b110, 3)) + + def test_bit_select_lhs(self): + stmt = lambda y, a, b: y.bit_select(a, 3).eq(b) + self.assertStatement(stmt, [C(0), C(0b100, 3)], C(0b11111100, 8), reset=0b11111111) + self.assertStatement(stmt, [C(2), C(0b101, 3)], C(0b11110111, 8), reset=0b11111111) + self.assertStatement(stmt, [C(3), C(0b110, 3)], C(0b11110111, 8), reset=0b11111111) + + def test_word_select(self): + stmt = lambda y, a, b: y.eq(a.word_select(b, 3)) + self.assertStatement(stmt, [C(0b10110100, 8), C(0)], C(0b100, 3)) + self.assertStatement(stmt, [C(0b10110100, 8), C(1)], C(0b110, 3)) + self.assertStatement(stmt, [C(0b10110100, 8), C(2)], C(0b010, 3)) + + def test_word_select_lhs(self): + stmt = lambda y, a, b: y.word_select(a, 3).eq(b) + self.assertStatement(stmt, [C(0), C(0b100, 3)], C(0b11111100, 8), reset=0b11111111) + self.assertStatement(stmt, [C(1), C(0b101, 3)], C(0b11101111, 8), reset=0b11111111) + self.assertStatement(stmt, [C(2), C(0b110, 3)], C(0b10111111, 8), reset=0b11111111) + + def test_cat(self): + stmt = lambda y, *xs: y.eq(Cat(*xs)) + self.assertStatement(stmt, [C(0b10, 2), C(0b01, 2)], C(0b0110, 4)) + + def test_cat_lhs(self): + l = Signal(3) + m = Signal(3) + n = Signal(3) + stmt = lambda y, a: [Cat(l, m, n).eq(a), y.eq(Cat(n, m, l))] + self.assertStatement(stmt, [C(0b100101110, 9)], C(0b110101100, 9)) + + def test_nested_cat_lhs(self): + l = Signal(3) + m = Signal(3) + n = Signal(3) + stmt = lambda y, a: [Cat(Cat(l, Cat(m)), n).eq(a), y.eq(Cat(n, m, l))] + self.assertStatement(stmt, [C(0b100101110, 9)], C(0b110101100, 9)) + + def test_record(self): + rec = Record([ + ("l", 1), + ("m", 2), + ]) + stmt = lambda y, a: [rec.eq(a), y.eq(rec)] + self.assertStatement(stmt, [C(0b101, 3)], C(0b101, 3)) + + def test_repl(self): + stmt = lambda y, a: y.eq(Repl(a, 3)) + self.assertStatement(stmt, [C(0b10, 2)], C(0b101010, 6)) + + def test_array(self): + array = Array([1, 4, 10]) + stmt = lambda y, a: y.eq(array[a]) + self.assertStatement(stmt, [C(0)], C(1)) + self.assertStatement(stmt, [C(1)], C(4)) + self.assertStatement(stmt, [C(2)], C(10)) + + def test_array_oob(self): + array = Array([1, 4, 10]) + stmt = lambda y, a: y.eq(array[a]) + self.assertStatement(stmt, [C(3)], C(10)) + self.assertStatement(stmt, [C(4)], C(10)) + + def test_array_lhs(self): + l = Signal(3, reset=1) + m = Signal(3, reset=4) + n = Signal(3, reset=7) + array = Array([l, m, n]) + stmt = lambda y, a, b: [array[a].eq(b), y.eq(Cat(*array))] + self.assertStatement(stmt, [C(0), C(0b000)], C(0b111100000)) + self.assertStatement(stmt, [C(1), C(0b010)], C(0b111010001)) + self.assertStatement(stmt, [C(2), C(0b100)], C(0b100100001)) + + def test_array_lhs_oob(self): + l = Signal(3) + m = Signal(3) + n = Signal(3) + array = Array([l, m, n]) + stmt = lambda y, a, b: [array[a].eq(b), y.eq(Cat(*array))] + self.assertStatement(stmt, [C(3), C(0b001)], C(0b001000000)) + self.assertStatement(stmt, [C(4), C(0b010)], C(0b010000000)) + + def test_array_index(self): + array = Array(Array(x * y for y in range(10)) for x in range(10)) + stmt = lambda y, a, b: y.eq(array[a][b]) + for x in range(10): + for y in range(10): + self.assertStatement(stmt, [C(x), C(y)], C(x * y)) + + def test_array_attr(self): + from collections import namedtuple + pair = namedtuple("pair", ("p", "n")) + + array = Array(pair(x, -x) for x in range(10)) + stmt = lambda y, a: y.eq(array[a].p + array[a].n) + for i in range(10): + self.assertStatement(stmt, [C(i)], C(0)) + + def test_shift_left(self): + stmt1 = lambda y, a: y.eq(a.shift_left(1)) + self.assertStatement(stmt1, [C(0b10100010, 8)], C( 0b101000100, 9)) + stmt2 = lambda y, a: y.eq(a.shift_left(4)) + self.assertStatement(stmt2, [C(0b10100010, 8)], C(0b101000100000, 12)) + + def test_shift_right(self): + stmt1 = lambda y, a: y.eq(a.shift_right(1)) + self.assertStatement(stmt1, [C(0b10100010, 8)], C(0b1010001, 7)) + stmt2 = lambda y, a: y.eq(a.shift_right(4)) + self.assertStatement(stmt2, [C(0b10100010, 8)], C( 0b1010, 4)) + + def test_rotate_left(self): + stmt = lambda y, a: y.eq(a.rotate_left(1)) + self.assertStatement(stmt, [C(0b1)], C(0b1)) + self.assertStatement(stmt, [C(0b1001000)], C(0b0010001)) + stmt = lambda y, a: y.eq(a.rotate_left(5)) + self.assertStatement(stmt, [C(0b1000000)], C(0b0010000)) + self.assertStatement(stmt, [C(0b1000001)], C(0b0110000)) + stmt = lambda y, a: y.eq(a.rotate_left(7)) + self.assertStatement(stmt, [C(0b1000000)], C(0b1000000)) + self.assertStatement(stmt, [C(0b1000001)], C(0b1000001)) + stmt = lambda y, a: y.eq(a.rotate_left(9)) + self.assertStatement(stmt, [C(0b1000000)], C(0b0000010)) + self.assertStatement(stmt, [C(0b1000001)], C(0b0000110)) + stmt = lambda y, a: y.eq(a.rotate_left(-1)) + self.assertStatement(stmt, [C(0b1)], C(0b1)) + self.assertStatement(stmt, [C(0b1001000)], C(0b0100100)) + stmt = lambda y, a: y.eq(a.rotate_left(-5)) + self.assertStatement(stmt, [C(0b1000000)], C(0b0000010)) + self.assertStatement(stmt, [C(0b1000001)], C(0b0000110)) + stmt = lambda y, a: y.eq(a.rotate_left(-7)) + self.assertStatement(stmt, [C(0b1000000)], C(0b1000000)) + self.assertStatement(stmt, [C(0b1000001)], C(0b1000001)) + stmt = lambda y, a: y.eq(a.rotate_left(-9)) + self.assertStatement(stmt, [C(0b1000000)], C(0b0010000)) + self.assertStatement(stmt, [C(0b1000001)], C(0b0110000)) + + def test_rotate_right(self): + stmt = lambda y, a: y.eq(a.rotate_right(1)) + self.assertStatement(stmt, [C(0b1)], C(0b1)) + self.assertStatement(stmt, [C(0b1001000)], C(0b0100100)) + stmt = lambda y, a: y.eq(a.rotate_right(5)) + self.assertStatement(stmt, [C(0b1000000)], C(0b0000010)) + self.assertStatement(stmt, [C(0b1000001)], C(0b0000110)) + stmt = lambda y, a: y.eq(a.rotate_right(7)) + self.assertStatement(stmt, [C(0b1000000)], C(0b1000000)) + self.assertStatement(stmt, [C(0b1000001)], C(0b1000001)) + stmt = lambda y, a: y.eq(a.rotate_right(9)) + self.assertStatement(stmt, [C(0b1000000)], C(0b0010000)) + self.assertStatement(stmt, [C(0b1000001)], C(0b0110000)) + stmt = lambda y, a: y.eq(a.rotate_right(-1)) + self.assertStatement(stmt, [C(0b1)], C(0b1)) + self.assertStatement(stmt, [C(0b1001000)], C(0b0010001)) + stmt = lambda y, a: y.eq(a.rotate_right(-5)) + self.assertStatement(stmt, [C(0b1000000)], C(0b0010000)) + self.assertStatement(stmt, [C(0b1000001)], C(0b0110000)) + stmt = lambda y, a: y.eq(a.rotate_right(-7)) + self.assertStatement(stmt, [C(0b1000000)], C(0b1000000)) + self.assertStatement(stmt, [C(0b1000001)], C(0b1000001)) + stmt = lambda y, a: y.eq(a.rotate_right(-9)) + self.assertStatement(stmt, [C(0b1000000)], C(0b0000010)) + self.assertStatement(stmt, [C(0b1000001)], C(0b0000110)) + + +class SimulatorIntegrationTestCase(FHDLTestCase): + @contextmanager + def assertSimulation(self, module, deadline=None): + sim = Simulator(module) + yield sim + with sim.write_vcd("test.vcd", "test.gtkw"): + if deadline is None: + sim.run() + else: + sim.run_until(deadline) + + def setUp_counter(self): + self.count = Signal(3, reset=4) + self.sync = ClockDomain() + + self.m = Module() + self.m.d.sync += self.count.eq(self.count + 1) + self.m.domains += self.sync + + def test_counter_process(self): + self.setUp_counter() + with self.assertSimulation(self.m) as sim: + def process(): + self.assertEqual((yield self.count), 4) + yield Delay(1e-6) + self.assertEqual((yield self.count), 4) + yield self.sync.clk.eq(1) + self.assertEqual((yield self.count), 4) + yield Settle() + self.assertEqual((yield self.count), 5) + yield Delay(1e-6) + self.assertEqual((yield self.count), 5) + yield self.sync.clk.eq(0) + self.assertEqual((yield self.count), 5) + yield Settle() + self.assertEqual((yield self.count), 5) + for _ in range(3): + yield Delay(1e-6) + yield self.sync.clk.eq(1) + yield Delay(1e-6) + yield self.sync.clk.eq(0) + self.assertEqual((yield self.count), 0) + sim.add_process(process) + + def test_counter_clock_and_sync_process(self): + self.setUp_counter() + with self.assertSimulation(self.m) as sim: + sim.add_clock(1e-6, domain="sync") + def process(): + self.assertEqual((yield self.count), 4) + self.assertEqual((yield self.sync.clk), 1) + yield + self.assertEqual((yield self.count), 5) + self.assertEqual((yield self.sync.clk), 1) + for _ in range(3): + yield + self.assertEqual((yield self.count), 0) + sim.add_sync_process(process) + + def test_reset(self): + self.setUp_counter() + sim = Simulator(self.m) + sim.add_clock(1e-6) + times = 0 + def process(): + nonlocal times + self.assertEqual((yield self.count), 4) + yield + self.assertEqual((yield self.count), 5) + yield + self.assertEqual((yield self.count), 6) + yield + times += 1 + sim.add_sync_process(process) + sim.run() + sim.reset() + sim.run() + self.assertEqual(times, 2) + + def setUp_alu(self): + self.a = Signal(8) + self.b = Signal(8) + self.o = Signal(8) + self.x = Signal(8) + self.s = Signal(2) + self.sync = ClockDomain(reset_less=True) + + self.m = Module() + self.m.d.comb += self.x.eq(self.a ^ self.b) + with self.m.Switch(self.s): + with self.m.Case(0): + self.m.d.sync += self.o.eq(self.a + self.b) + with self.m.Case(1): + self.m.d.sync += self.o.eq(self.a - self.b) + with self.m.Case(): + self.m.d.sync += self.o.eq(0) + self.m.domains += self.sync + + def test_alu(self): + self.setUp_alu() + with self.assertSimulation(self.m) as sim: + sim.add_clock(1e-6) + def process(): + yield self.a.eq(5) + yield self.b.eq(1) + yield + self.assertEqual((yield self.x), 4) + yield + self.assertEqual((yield self.o), 6) + yield self.s.eq(1) + yield + yield + self.assertEqual((yield self.o), 4) + yield self.s.eq(2) + yield + yield + self.assertEqual((yield self.o), 0) + sim.add_sync_process(process) + + def setUp_multiclock(self): + self.sys = ClockDomain() + self.pix = ClockDomain() + + self.m = Module() + self.m.domains += self.sys, self.pix + + def test_multiclock(self): + self.setUp_multiclock() + with self.assertSimulation(self.m) as sim: + sim.add_clock(1e-6, domain="sys") + sim.add_clock(0.3e-6, domain="pix") + + def sys_process(): + yield Passive() + yield + yield + self.fail() + def pix_process(): + yield + yield + yield + sim.add_sync_process(sys_process, domain="sys") + sim.add_sync_process(pix_process, domain="pix") + + def setUp_lhs_rhs(self): + self.i = Signal(8) + self.o = Signal(8) + + self.m = Module() + self.m.d.comb += self.o.eq(self.i) + + def test_complex_lhs_rhs(self): + self.setUp_lhs_rhs() + with self.assertSimulation(self.m) as sim: + def process(): + yield self.i.eq(0b10101010) + yield self.i[:4].eq(-1) + yield Settle() + self.assertEqual((yield self.i[:4]), 0b1111) + self.assertEqual((yield self.i), 0b10101111) + sim.add_process(process) + + def test_run_until(self): + m = Module() + s = Signal() + m.d.sync += s.eq(0) + with self.assertSimulation(m, deadline=100e-6) as sim: + sim.add_clock(1e-6) + def process(): + for _ in range(101): + yield Delay(1e-6) + self.fail() + sim.add_process(process) + + def test_add_process_wrong(self): + with self.assertSimulation(Module()) as sim: + with self.assertRaisesRegex(TypeError, + r"^Cannot add a process 1 because it is not a generator function$"): + sim.add_process(1) + + def test_add_process_wrong_generator(self): + with self.assertSimulation(Module()) as sim: + with self.assertRaisesRegex(TypeError, + r"^Cannot add a process <.+?> because it is not a generator function$"): + def process(): + yield Delay() + sim.add_process(process()) + + def test_add_clock_wrong_twice(self): + m = Module() + s = Signal() + m.d.sync += s.eq(0) + with self.assertSimulation(m) as sim: + sim.add_clock(1) + with self.assertRaisesRegex(ValueError, + r"^Domain 'sync' already has a clock driving it$"): + sim.add_clock(1) + + def test_add_clock_wrong_missing(self): + m = Module() + with self.assertSimulation(m) as sim: + with self.assertRaisesRegex(ValueError, + r"^Domain 'sync' is not present in simulation$"): + sim.add_clock(1) + + def test_add_clock_if_exists(self): + m = Module() + with self.assertSimulation(m) as sim: + sim.add_clock(1, if_exists=True) + + def test_command_wrong(self): + survived = False + with self.assertSimulation(Module()) as sim: + def process(): + nonlocal survived + with self.assertRaisesRegex(TypeError, + r"Received unsupported command 1 from process .+?"): + yield 1 + yield Settle() + survived = True + sim.add_process(process) + self.assertTrue(survived) + + def setUp_memory(self, rd_synchronous=True, rd_transparent=True, wr_granularity=None): + self.m = Module() + self.memory = Memory(width=8, depth=4, init=[0xaa, 0x55]) + self.m.submodules.rdport = self.rdport = \ + self.memory.read_port(domain="sync" if rd_synchronous else "comb", + transparent=rd_transparent) + self.m.submodules.wrport = self.wrport = \ + self.memory.write_port(granularity=wr_granularity) + + def test_memory_init(self): + self.setUp_memory() + with self.assertSimulation(self.m) as sim: + def process(): + self.assertEqual((yield self.rdport.data), 0xaa) + yield self.rdport.addr.eq(1) + yield + yield + self.assertEqual((yield self.rdport.data), 0x55) + yield self.rdport.addr.eq(2) + yield + yield + self.assertEqual((yield self.rdport.data), 0x00) + sim.add_clock(1e-6) + sim.add_sync_process(process) + + def test_memory_write(self): + self.setUp_memory() + with self.assertSimulation(self.m) as sim: + def process(): + yield self.wrport.addr.eq(4) + yield self.wrport.data.eq(0x33) + yield self.wrport.en.eq(1) + yield + yield self.wrport.en.eq(0) + yield self.rdport.addr.eq(4) + yield + self.assertEqual((yield self.rdport.data), 0x33) + sim.add_clock(1e-6) + sim.add_sync_process(process) + + def test_memory_write_granularity(self): + self.setUp_memory(wr_granularity=4) + with self.assertSimulation(self.m) as sim: + def process(): + yield self.wrport.data.eq(0x50) + yield self.wrport.en.eq(0b00) + yield + yield self.wrport.en.eq(0) + yield + self.assertEqual((yield self.rdport.data), 0xaa) + yield self.wrport.en.eq(0b10) + yield + yield self.wrport.en.eq(0) + yield + self.assertEqual((yield self.rdport.data), 0x5a) + yield self.wrport.data.eq(0x33) + yield self.wrport.en.eq(0b01) + yield + yield self.wrport.en.eq(0) + yield + self.assertEqual((yield self.rdport.data), 0x53) + sim.add_clock(1e-6) + sim.add_sync_process(process) + + def test_memory_read_before_write(self): + self.setUp_memory(rd_transparent=False) + with self.assertSimulation(self.m) as sim: + def process(): + yield self.wrport.data.eq(0x33) + yield self.wrport.en.eq(1) + yield + self.assertEqual((yield self.rdport.data), 0xaa) + yield + self.assertEqual((yield self.rdport.data), 0xaa) + yield Settle() + self.assertEqual((yield self.rdport.data), 0x33) + sim.add_clock(1e-6) + sim.add_sync_process(process) + + def test_memory_write_through(self): + self.setUp_memory(rd_transparent=True) + with self.assertSimulation(self.m) as sim: + def process(): + yield self.wrport.data.eq(0x33) + yield self.wrport.en.eq(1) + yield + self.assertEqual((yield self.rdport.data), 0xaa) + yield Settle() + self.assertEqual((yield self.rdport.data), 0x33) + yield + yield self.rdport.addr.eq(1) + yield Settle() + self.assertEqual((yield self.rdport.data), 0x33) + sim.add_clock(1e-6) + sim.add_sync_process(process) + + def test_memory_async_read_write(self): + self.setUp_memory(rd_synchronous=False) + with self.assertSimulation(self.m) as sim: + def process(): + yield self.rdport.addr.eq(0) + yield Settle() + self.assertEqual((yield self.rdport.data), 0xaa) + yield self.rdport.addr.eq(1) + yield Settle() + self.assertEqual((yield self.rdport.data), 0x55) + yield self.rdport.addr.eq(0) + yield self.wrport.addr.eq(0) + yield self.wrport.data.eq(0x33) + yield self.wrport.en.eq(1) + yield Tick("sync") + self.assertEqual((yield self.rdport.data), 0xaa) + yield Settle() + self.assertEqual((yield self.rdport.data), 0x33) + sim.add_clock(1e-6) + sim.add_process(process) + + def test_memory_read_only(self): + self.m = Module() + self.memory = Memory(width=8, depth=4, init=[0xaa, 0x55]) + self.m.submodules.rdport = self.rdport = self.memory.read_port() + with self.assertSimulation(self.m) as sim: + def process(): + self.assertEqual((yield self.rdport.data), 0xaa) + yield self.rdport.addr.eq(1) + yield + yield + self.assertEqual((yield self.rdport.data), 0x55) + sim.add_clock(1e-6) + sim.add_sync_process(process) + + def test_sample_helpers(self): + m = Module() + s = Signal(2) + def mk(x): + y = Signal.like(x) + m.d.comb += y.eq(x) + return y + p0, r0, f0, s0 = mk(Past(s, 0)), mk(Rose(s)), mk(Fell(s)), mk(Stable(s)) + p1, r1, f1, s1 = mk(Past(s)), mk(Rose(s, 1)), mk(Fell(s, 1)), mk(Stable(s, 1)) + p2, r2, f2, s2 = mk(Past(s, 2)), mk(Rose(s, 2)), mk(Fell(s, 2)), mk(Stable(s, 2)) + p3, r3, f3, s3 = mk(Past(s, 3)), mk(Rose(s, 3)), mk(Fell(s, 3)), mk(Stable(s, 3)) + with self.assertSimulation(m) as sim: + def process_gen(): + yield s.eq(0b10) + yield + yield + yield s.eq(0b01) + yield + def process_check(): + yield + yield + yield + + self.assertEqual((yield p0), 0b01) + self.assertEqual((yield p1), 0b10) + self.assertEqual((yield p2), 0b10) + self.assertEqual((yield p3), 0b00) + + self.assertEqual((yield s0), 0b0) + self.assertEqual((yield s1), 0b1) + self.assertEqual((yield s2), 0b0) + self.assertEqual((yield s3), 0b1) + + self.assertEqual((yield r0), 0b01) + self.assertEqual((yield r1), 0b00) + self.assertEqual((yield r2), 0b10) + self.assertEqual((yield r3), 0b00) + + self.assertEqual((yield f0), 0b10) + self.assertEqual((yield f1), 0b00) + self.assertEqual((yield f2), 0b00) + self.assertEqual((yield f3), 0b00) + sim.add_clock(1e-6) + sim.add_sync_process(process_gen) + sim.add_sync_process(process_check) + + def test_vcd_wrong_nonzero_time(self): + s = Signal() + m = Module() + m.d.sync += s.eq(s) + sim = Simulator(m) + sim.add_clock(1e-6) + sim.run_until(1e-5) + with self.assertRaisesRegex(ValueError, + r"^Cannot start writing waveforms after advancing simulation time$"): + with sim.write_vcd(open(os.path.devnull, "wt")): + pass + + +class SimulatorRegressionTestCase(FHDLTestCase): + def test_bug_325(self): + dut = Module() + dut.d.comb += Signal().eq(Cat()) + Simulator(dut).run() + + def test_bug_325_bis(self): + dut = Module() + dut.d.comb += Signal().eq(Repl(Const(1), 0)) + Simulator(dut).run() + + def test_bug_473(self): + sim = Simulator(Module()) + def process(): + self.assertEqual((yield -(Const(0b11, 2).as_signed())), 1) + sim.add_process(process) + sim.run() diff --git a/tests/utils.py b/tests/utils.py new file mode 100644 index 0000000..c165cf9 --- /dev/null +++ b/tests/utils.py @@ -0,0 +1,77 @@ +import os +import re +import shutil +import subprocess +import textwrap +import traceback +import unittest +from contextlib import contextmanager + +from nmigen.hdl.ast import * +from nmigen.hdl.ir import * +from nmigen.back import rtlil +from nmigen._toolchain import require_tool + + +__all__ = ["FHDLTestCase"] + + +class FHDLTestCase(unittest.TestCase): + def assertRepr(self, obj, repr_str): + if isinstance(obj, list): + obj = Statement.cast(obj) + def prepare_repr(repr_str): + repr_str = re.sub(r"\s+", " ", repr_str) + repr_str = re.sub(r"\( (?=\()", "(", repr_str) + repr_str = re.sub(r"\) (?=\))", ")", repr_str) + return repr_str.strip() + self.assertEqual(prepare_repr(repr(obj)), prepare_repr(repr_str)) + + def assertFormal(self, spec, mode="bmc", depth=1): + caller, *_ = traceback.extract_stack(limit=2) + spec_root, _ = os.path.splitext(caller.filename) + spec_dir = os.path.dirname(spec_root) + spec_name = "{}_{}".format( + os.path.basename(spec_root).replace("test_", "spec_"), + caller.name.replace("test_", "") + ) + + # The sby -f switch seems not fully functional when sby is reading from stdin. + if os.path.exists(os.path.join(spec_dir, spec_name)): + shutil.rmtree(os.path.join(spec_dir, spec_name)) + + if mode == "hybrid": + # A mix of BMC and k-induction, as per personal communication with Clifford Wolf. + script = "setattr -unset init w:* a:nmigen.sample_reg %d" + mode = "bmc" + else: + script = "" + + config = textwrap.dedent("""\ + [options] + mode {mode} + depth {depth} + wait on + + [engines] + smtbmc + + [script] + read_ilang top.il + prep + {script} + + [file top.il] + {rtlil} + """).format( + mode=mode, + depth=depth, + script=script, + rtlil=rtlil.convert(Fragment.get(spec, platform="formal")) + ) + with subprocess.Popen([require_tool("sby"), "-f", "-d", spec_name], cwd=spec_dir, + universal_newlines=True, + stdin=subprocess.PIPE, stdout=subprocess.PIPE) as proc: + stdout, stderr = proc.communicate(config) + if proc.returncode != 0: + self.fail("Formal verification failed:\n" + stdout)