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.
This commit is contained in:
parent
ef7a3bcfb1
commit
67b957d4f4
32 changed files with 103 additions and 82 deletions
0
tests/__init__.py
Normal file
0
tests/__init__.py
Normal file
0
tests/compat/__init__.py
Normal file
0
tests/compat/__init__.py
Normal file
16
tests/compat/support.py
Normal file
16
tests/compat/support.py
Normal file
|
|
@ -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)
|
||||
116
tests/compat/test_coding.py
Normal file
116
tests/compat/test_coding.py
Normal file
|
|
@ -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<<i for i in range(8)])
|
||||
else:
|
||||
self.assertEqual((yield self.tb.dut.i), 1<<(yield self.tb.dut.o))
|
||||
self.run_with(gen())
|
||||
|
||||
|
||||
class PrioEncCase(SimCase, unittest.TestCase):
|
||||
class TestBench(Module):
|
||||
def __init__(self):
|
||||
self.submodules.dut = PriorityEncoder(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
|
||||
i = yield self.tb.dut.i
|
||||
if (yield self.tb.dut.n):
|
||||
self.assertEqual(i, 0)
|
||||
else:
|
||||
o = yield self.tb.dut.o
|
||||
if o > 0:
|
||||
self.assertEqual(i & 1<<(o - 1), 0)
|
||||
self.assertGreaterEqual(i, 1<<o)
|
||||
self.run_with(gen())
|
||||
|
||||
|
||||
class DecCase(SimCase, unittest.TestCase):
|
||||
class TestBench(Module):
|
||||
def __init__(self):
|
||||
self.submodules.dut = Decoder(8)
|
||||
|
||||
def test_sizes(self):
|
||||
self.assertEqual(len(self.tb.dut.i), 3)
|
||||
self.assertEqual(len(self.tb.dut.o), 8)
|
||||
self.assertEqual(len(self.tb.dut.n), 1)
|
||||
|
||||
def test_run_sequence(self):
|
||||
seq = list(range(8*2))
|
||||
def gen():
|
||||
for _ in range(256):
|
||||
if seq:
|
||||
i = seq.pop()
|
||||
yield self.tb.dut.i.eq(i//2)
|
||||
yield self.tb.dut.n.eq(i%2)
|
||||
yield
|
||||
i = yield self.tb.dut.i
|
||||
o = yield self.tb.dut.o
|
||||
if (yield self.tb.dut.n):
|
||||
self.assertEqual(o, 0)
|
||||
else:
|
||||
self.assertEqual(o, 1<<i)
|
||||
self.run_with(gen())
|
||||
|
||||
|
||||
class SmallPrioEncCase(SimCase, unittest.TestCase):
|
||||
class TestBench(Module):
|
||||
def __init__(self):
|
||||
self.submodules.dut = PriorityEncoder(1)
|
||||
|
||||
def test_sizes(self):
|
||||
self.assertEqual(len(self.tb.dut.i), 1)
|
||||
self.assertEqual(len(self.tb.dut.o), 1)
|
||||
self.assertEqual(len(self.tb.dut.n), 1)
|
||||
|
||||
def test_run_sequence(self):
|
||||
seq = list(range(1))
|
||||
def gen():
|
||||
for _ in range(5):
|
||||
if seq:
|
||||
yield self.tb.dut.i.eq(seq.pop(0))
|
||||
yield
|
||||
i = yield self.tb.dut.i
|
||||
if (yield self.tb.dut.n):
|
||||
self.assertEqual(i, 0)
|
||||
else:
|
||||
o = yield self.tb.dut.o
|
||||
if o > 0:
|
||||
self.assertEqual(i & 1<<(o - 1), 0)
|
||||
self.assertGreaterEqual(i, 1<<o)
|
||||
self.run_with(gen())
|
||||
30
tests/compat/test_constant.py
Normal file
30
tests/compat/test_constant.py
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
import unittest
|
||||
|
||||
from nmigen.compat import *
|
||||
|
||||
from .support import SimCase
|
||||
|
||||
|
||||
class ConstantCase(SimCase, unittest.TestCase):
|
||||
class TestBench(Module):
|
||||
def __init__(self):
|
||||
self.sigs = [
|
||||
(Signal(3), Constant(0), 0),
|
||||
(Signal(3), Constant(5), 5),
|
||||
(Signal(3), Constant(1, 2), 1),
|
||||
(Signal(3), Constant(-1, 7), 7),
|
||||
(Signal(3), Constant(0b10101)[:3], 0b101),
|
||||
(Signal(3), Constant(0b10101)[1:4], 0b10),
|
||||
(Signal(4), Constant(0b1100)[::-1], 0b0011),
|
||||
]
|
||||
self.comb += [a.eq(b) for a, b, c in self.sigs]
|
||||
|
||||
def test_comparisons(self):
|
||||
def gen():
|
||||
for s, l, v in self.tb.sigs:
|
||||
s = yield s
|
||||
self.assertEqual(
|
||||
s, int(v),
|
||||
"got {}, want {} from literal {}".format(
|
||||
s, v, l))
|
||||
self.run_with(gen())
|
||||
38
tests/compat/test_fifo.py
Normal file
38
tests/compat/test_fifo.py
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
import unittest
|
||||
from itertools import count
|
||||
|
||||
from nmigen.compat import *
|
||||
from nmigen.compat.genlib.fifo import SyncFIFO
|
||||
|
||||
from .support import SimCase
|
||||
|
||||
|
||||
class SyncFIFOCase(SimCase, unittest.TestCase):
|
||||
class TestBench(Module):
|
||||
def __init__(self):
|
||||
self.submodules.dut = SyncFIFO(64, 2)
|
||||
|
||||
self.sync += [
|
||||
If(self.dut.we & self.dut.writable,
|
||||
self.dut.din[:32].eq(self.dut.din[:32] + 1),
|
||||
self.dut.din[32:].eq(self.dut.din[32:] + 2)
|
||||
)
|
||||
]
|
||||
|
||||
def test_run_sequence(self):
|
||||
seq = list(range(20))
|
||||
def gen():
|
||||
for cycle in count():
|
||||
# fire re and we at "random"
|
||||
yield self.tb.dut.we.eq(cycle % 2 == 0)
|
||||
yield self.tb.dut.re.eq(cycle % 3 == 0)
|
||||
# the output if valid must be correct
|
||||
if (yield self.tb.dut.readable) and (yield self.tb.dut.re):
|
||||
try:
|
||||
i = seq.pop(0)
|
||||
except IndexError:
|
||||
break
|
||||
self.assertEqual((yield self.tb.dut.dout[:32]), i)
|
||||
self.assertEqual((yield self.tb.dut.dout[32:]), i*2)
|
||||
yield
|
||||
self.run_with(gen())
|
||||
87
tests/compat/test_fsm.py
Normal file
87
tests/compat/test_fsm.py
Normal file
|
|
@ -0,0 +1,87 @@
|
|||
import unittest
|
||||
from itertools import count
|
||||
|
||||
from nmigen.compat import *
|
||||
from nmigen.compat.genlib.fsm import FSM
|
||||
|
||||
from .support import SimCase
|
||||
|
||||
|
||||
class FSMCase(SimCase, unittest.TestCase):
|
||||
class TestBench(Module):
|
||||
def __init__(self):
|
||||
self.ctrl = Signal()
|
||||
self.data = Signal()
|
||||
self.status = Signal(8)
|
||||
|
||||
self.submodules.dut = FSM()
|
||||
self.dut.act("IDLE",
|
||||
If(self.ctrl,
|
||||
NextState("START")
|
||||
)
|
||||
)
|
||||
self.dut.act("START",
|
||||
If(self.data,
|
||||
NextState("SET-STATUS-LOW")
|
||||
).Else(
|
||||
NextState("SET-STATUS")
|
||||
)
|
||||
)
|
||||
self.dut.act("SET-STATUS",
|
||||
NextValue(self.status, 0xaa),
|
||||
NextState("IDLE")
|
||||
)
|
||||
self.dut.act("SET-STATUS-LOW",
|
||||
NextValue(self.status[:4], 0xb),
|
||||
NextState("IDLE")
|
||||
)
|
||||
|
||||
def assertState(self, fsm, state):
|
||||
self.assertEqual(fsm.decoding[(yield fsm.state)], state)
|
||||
|
||||
def test_next_state(self):
|
||||
def gen():
|
||||
yield from self.assertState(self.tb.dut, "IDLE")
|
||||
yield
|
||||
yield from self.assertState(self.tb.dut, "IDLE")
|
||||
yield self.tb.ctrl.eq(1)
|
||||
yield
|
||||
yield from self.assertState(self.tb.dut, "IDLE")
|
||||
yield self.tb.ctrl.eq(0)
|
||||
yield
|
||||
yield from self.assertState(self.tb.dut, "START")
|
||||
yield
|
||||
yield from self.assertState(self.tb.dut, "SET-STATUS")
|
||||
yield self.tb.ctrl.eq(1)
|
||||
yield
|
||||
yield from self.assertState(self.tb.dut, "IDLE")
|
||||
yield self.tb.ctrl.eq(0)
|
||||
yield self.tb.data.eq(1)
|
||||
yield
|
||||
yield from self.assertState(self.tb.dut, "START")
|
||||
yield self.tb.data.eq(0)
|
||||
yield
|
||||
yield from self.assertState(self.tb.dut, "SET-STATUS-LOW")
|
||||
self.run_with(gen())
|
||||
|
||||
def test_next_value(self):
|
||||
def gen():
|
||||
self.assertEqual((yield self.tb.status), 0x00)
|
||||
yield self.tb.ctrl.eq(1)
|
||||
yield
|
||||
yield self.tb.ctrl.eq(0)
|
||||
yield
|
||||
yield
|
||||
yield from self.assertState(self.tb.dut, "SET-STATUS")
|
||||
yield self.tb.ctrl.eq(1)
|
||||
yield
|
||||
self.assertEqual((yield self.tb.status), 0xaa)
|
||||
yield self.tb.ctrl.eq(0)
|
||||
yield self.tb.data.eq(1)
|
||||
yield
|
||||
yield self.tb.data.eq(0)
|
||||
yield
|
||||
yield from self.assertState(self.tb.dut, "SET-STATUS-LOW")
|
||||
yield
|
||||
self.assertEqual((yield self.tb.status), 0xab)
|
||||
self.run_with(gen())
|
||||
23
tests/compat/test_passive.py
Normal file
23
tests/compat/test_passive.py
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
import unittest
|
||||
|
||||
from nmigen.compat import *
|
||||
|
||||
|
||||
class PassiveCase(unittest.TestCase):
|
||||
def test_terminates_correctly(self):
|
||||
n = 5
|
||||
|
||||
count = 0
|
||||
@passive
|
||||
def counter():
|
||||
nonlocal count
|
||||
while True:
|
||||
yield
|
||||
count += 1
|
||||
|
||||
def terminator():
|
||||
for i in range(n):
|
||||
yield
|
||||
|
||||
run_simulation(Module(), [counter(), terminator()])
|
||||
self.assertEqual(count, n)
|
||||
28
tests/compat/test_run_simulation.py
Normal file
28
tests/compat/test_run_simulation.py
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
import unittest
|
||||
|
||||
from nmigen import Signal, Module, Elaboratable
|
||||
|
||||
from .support import SimCase
|
||||
|
||||
|
||||
class RunSimulation(SimCase, unittest.TestCase):
|
||||
""" test for https://github.com/nmigen/nmigen/issues/344 """
|
||||
|
||||
class TestBench(Elaboratable):
|
||||
def __init__(self):
|
||||
self.a = Signal()
|
||||
|
||||
def elaborate(self, platform):
|
||||
m = Module()
|
||||
m.d.sync += self.a.eq(~self.a)
|
||||
return m
|
||||
|
||||
def test_run_simulation(self):
|
||||
def gen():
|
||||
yield
|
||||
for i in range(10):
|
||||
yield
|
||||
a = (yield self.tb.a)
|
||||
self.assertEqual(a, i % 2)
|
||||
|
||||
self.run_with(gen())
|
||||
43
tests/compat/test_signed.py
Normal file
43
tests/compat/test_signed.py
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
import unittest
|
||||
|
||||
from nmigen.compat import *
|
||||
|
||||
from .support import SimCase
|
||||
|
||||
|
||||
class SignedCase(SimCase, unittest.TestCase):
|
||||
class TestBench(Module):
|
||||
def __init__(self):
|
||||
self.a = Signal((3, True))
|
||||
self.b = Signal((4, True))
|
||||
comps = [
|
||||
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,
|
||||
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())
|
||||
21
tests/compat/test_size.py
Normal file
21
tests/compat/test_size.py
Normal file
|
|
@ -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)
|
||||
329
tests/test_build_dsl.py
Normal file
329
tests/test_build_dsl.py
Normal file
|
|
@ -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})
|
||||
53
tests/test_build_plat.py
Normal file
53
tests/test_build_plat.py
Normal file
|
|
@ -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")
|
||||
314
tests/test_build_res.py
Normal file
314
tests/test_build_res.py
Normal file
|
|
@ -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)
|
||||
10
tests/test_compat.py
Normal file
10
tests/test_compat.py
Normal file
|
|
@ -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)
|
||||
33
tests/test_examples.py
Normal file
33
tests/test_examples.py
Normal file
|
|
@ -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)
|
||||
1059
tests/test_hdl_ast.py
Normal file
1059
tests/test_hdl_ast.py
Normal file
File diff suppressed because it is too large
Load diff
79
tests/test_hdl_cd.py
Normal file
79
tests/test_hdl_cd.py
Normal file
|
|
@ -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()
|
||||
770
tests/test_hdl_dsl.py
Normal file
770
tests/test_hdl_dsl.py
Normal file
|
|
@ -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 '<module>\.d\.submodules' would add statements to clock domain "
|
||||
r"'submodules'; did you mean <module>\.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)
|
||||
863
tests/test_hdl_ir.py
Normal file
863
tests/test_hdl_ir.py
Normal file
|
|
@ -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=\(<signal>,\)`, rather than `ports=<signal>`\?\)$")):
|
||||
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', <unnamed #1> 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.<unnamed #1>$"):
|
||||
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.<unnamed #1>; "
|
||||
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\.<unnamed #0>, "
|
||||
r"top\.<unnamed #1>$"):
|
||||
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.<unnamed #0>, "
|
||||
r"top.<unnamed #1>; 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),
|
||||
]))
|
||||
138
tests/test_hdl_mem.py
Normal file
138
tests/test_hdl_mem.py
Normal file
|
|
@ -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)
|
||||
330
tests/test_hdl_rec.py
Normal file
330
tests/test_hdl_rec.py
Normal file
|
|
@ -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 <unnamed> 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)
|
||||
650
tests/test_hdl_xfrm.py
Normal file
650
tests/test_hdl_xfrm.py
Normal file
|
|
@ -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
|
||||
231
tests/test_lib_cdc.py
Normal file
231
tests/test_lib_cdc.py
Normal file
|
|
@ -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()
|
||||
127
tests/test_lib_coding.py
Normal file
127
tests/test_lib_coding.py
Normal file
|
|
@ -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")
|
||||
282
tests/test_lib_fifo.py
Normal file
282
tests/test_lib_fifo.py
Normal file
|
|
@ -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))
|
||||
209
tests/test_lib_io.py
Normal file
209
tests/test_lib_io.py
Normal file
|
|
@ -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)
|
||||
96
tests/test_lib_scheduler.py
Normal file
96
tests/test_lib_scheduler.py
Normal file
|
|
@ -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()
|
||||
799
tests/test_sim.py
Normal file
799
tests/test_sim.py
Normal file
|
|
@ -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()
|
||||
77
tests/utils.py
Normal file
77
tests/utils.py
Normal file
|
|
@ -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)
|
||||
Loading…
Add table
Add a link
Reference in a new issue