426 lines
11 KiB
Python
426 lines
11 KiB
Python
# amaranth: UnusedElaboratable=no
|
|
|
|
from amaranth.hdl._ast import *
|
|
from amaranth.hdl._cd import *
|
|
from amaranth.hdl._dsl import *
|
|
from amaranth.hdl._ir import *
|
|
from amaranth.hdl._xfrm import *
|
|
from amaranth.hdl._mem import *
|
|
from amaranth.hdl._mem import MemoryInstance
|
|
|
|
from .utils import *
|
|
from amaranth._utils import _ignore_deprecated
|
|
|
|
|
|
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(
|
|
"comb",
|
|
self.s1.eq(ClockSignal()),
|
|
ResetSignal().eq(self.s2),
|
|
self.s4.eq(ClockSignal("other")),
|
|
self.s5.eq(ResetSignal("other")),
|
|
)
|
|
f.add_statements(
|
|
"sync",
|
|
self.s3.eq(0),
|
|
)
|
|
|
|
f = DomainRenamer("pix")(f)
|
|
self.assertRepr(f.statements["comb"], """
|
|
(
|
|
(eq (sig s1) (clk pix))
|
|
(eq (rst pix) (sig s2))
|
|
(eq (sig s4) (clk other))
|
|
(eq (sig s5) (rst other))
|
|
)
|
|
""")
|
|
self.assertRepr(f.statements["pix"], """
|
|
(
|
|
(eq (sig s3) (const 1'd0))
|
|
)
|
|
""")
|
|
self.assertFalse("sync" in f.statements)
|
|
|
|
def test_rename_multi(self):
|
|
f = Fragment()
|
|
f.add_statements(
|
|
"comb",
|
|
self.s1.eq(ClockSignal()),
|
|
self.s2.eq(ResetSignal("other")),
|
|
)
|
|
|
|
f = DomainRenamer({"sync": "pix", "other": "pix2"})(f)
|
|
self.assertRepr(f.statements["comb"], """
|
|
(
|
|
(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(
|
|
"comb",
|
|
self.s1.eq(ResetSignal(allow_reset_less=True)),
|
|
)
|
|
|
|
f = DomainRenamer("pix")(f)
|
|
f = DomainLowerer()(f)
|
|
self.assertRepr(f.statements["comb"], """
|
|
(
|
|
(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_mem_ports(self):
|
|
m = Module()
|
|
with _ignore_deprecated():
|
|
mem = Memory(depth=4, width=16)
|
|
m.submodules.mem = mem
|
|
mem.read_port(domain="a")
|
|
mem.read_port(domain="b")
|
|
mem.write_port(domain="c")
|
|
|
|
f = Fragment.get(m, None)
|
|
f = DomainRenamer({"a": "d", "c": "e"})(f)
|
|
mem = f.subfragments[0][0]
|
|
self.assertIsInstance(mem, MemoryInstance)
|
|
self.assertEqual(mem._read_ports[0]._domain, "d")
|
|
self.assertEqual(mem._read_ports[1]._domain, "b")
|
|
self.assertEqual(mem._write_ports[0]._domain, "e")
|
|
|
|
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(
|
|
"comb",
|
|
self.s.eq(ClockSignal("sync"))
|
|
)
|
|
|
|
f = DomainLowerer()(f)
|
|
self.assertRepr(f.statements["comb"], """
|
|
(
|
|
(eq (sig s) (sig clk))
|
|
)
|
|
""")
|
|
|
|
def test_lower_rst(self):
|
|
sync = ClockDomain()
|
|
f = Fragment()
|
|
f.add_domains(sync)
|
|
f.add_statements(
|
|
"comb",
|
|
self.s.eq(ResetSignal("sync"))
|
|
)
|
|
|
|
f = DomainLowerer()(f)
|
|
self.assertRepr(f.statements["comb"], """
|
|
(
|
|
(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(
|
|
"comb",
|
|
self.s.eq(ResetSignal("sync", allow_reset_less=True))
|
|
)
|
|
|
|
f = DomainLowerer()(f)
|
|
self.assertRepr(f.statements["comb"], """
|
|
(
|
|
(eq (sig s) (const 1'd0))
|
|
)
|
|
""")
|
|
|
|
def test_lower_wrong_domain(self):
|
|
f = Fragment()
|
|
f.add_statements(
|
|
"comb",
|
|
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(
|
|
"comb",
|
|
self.s.eq(ResetSignal("sync"))
|
|
)
|
|
|
|
with self.assertRaisesRegex(DomainError,
|
|
r"^Signal \(rst sync\) refers to reset of reset-less domain 'sync'$"):
|
|
DomainLowerer()(f)
|
|
|
|
|
|
class ResetInserterTestCase(FHDLTestCase):
|
|
def setUp(self):
|
|
self.s1 = Signal()
|
|
self.s2 = Signal(init=1)
|
|
self.s3 = Signal(init=1, reset_less=True)
|
|
self.c1 = Signal()
|
|
|
|
def test_reset_default(self):
|
|
f = Fragment()
|
|
f.add_statements(
|
|
"sync",
|
|
self.s1.eq(1)
|
|
)
|
|
|
|
f = ResetInserter(self.c1)(f)
|
|
self.assertRepr(f.statements["sync"], """
|
|
(
|
|
(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("sync", self.s1.eq(1))
|
|
f.add_statements("pix", self.s2.eq(0))
|
|
f.add_domains(ClockDomain("sync"))
|
|
|
|
f = ResetInserter({"pix": self.c1})(f)
|
|
self.assertRepr(f.statements["sync"], """
|
|
(
|
|
(eq (sig s1) (const 1'd1))
|
|
)
|
|
""")
|
|
self.assertRepr(f.statements["pix"], """
|
|
(
|
|
(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("sync", self.s2.eq(0))
|
|
|
|
f = ResetInserter(self.c1)(f)
|
|
self.assertRepr(f.statements["sync"], """
|
|
(
|
|
(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("sync", self.s3.eq(0))
|
|
|
|
f = ResetInserter(self.c1)(f)
|
|
self.assertRepr(f.statements["sync"], """
|
|
(
|
|
(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("sync", self.s1.eq(1))
|
|
|
|
f = EnableInserter(self.c1)(f)
|
|
self.assertRepr(f.statements["sync"], """
|
|
(
|
|
(switch (sig c1)
|
|
(case 1 (eq (sig s1) (const 1'd1)))
|
|
)
|
|
)
|
|
""")
|
|
|
|
def test_enable_cd(self):
|
|
f = Fragment()
|
|
f.add_statements("sync", self.s1.eq(1))
|
|
f.add_statements("pix", self.s2.eq(0))
|
|
|
|
f = EnableInserter({"pix": self.c1})(f)
|
|
self.assertRepr(f.statements["sync"], """
|
|
(
|
|
(eq (sig s1) (const 1'd1))
|
|
)
|
|
""")
|
|
self.assertRepr(f.statements["pix"], """
|
|
(
|
|
(switch (sig c1)
|
|
(case 1 (eq (sig s2) (const 1'd0)))
|
|
)
|
|
)
|
|
""")
|
|
|
|
def test_enable_subfragment(self):
|
|
f1 = Fragment()
|
|
f1.add_statements("sync", self.s1.eq(1))
|
|
|
|
f2 = Fragment()
|
|
f2.add_statements("sync", self.s2.eq(1))
|
|
f1.add_subfragment(f2)
|
|
|
|
f1 = EnableInserter(self.c1)(f1)
|
|
(f2, _, _), = f1.subfragments
|
|
self.assertRepr(f1.statements["sync"], """
|
|
(
|
|
(switch (sig c1)
|
|
(case 1 (eq (sig s1) (const 1'd1)))
|
|
)
|
|
)
|
|
""")
|
|
self.assertRepr(f2.statements["sync"], """
|
|
(
|
|
(switch (sig c1)
|
|
(case 1 (eq (sig s2) (const 1'd1)))
|
|
)
|
|
)
|
|
""")
|
|
|
|
def test_enable_read_port(self):
|
|
with _ignore_deprecated():
|
|
mem = Memory(width=8, depth=4)
|
|
mem.read_port(transparent=False)
|
|
f = EnableInserter(self.c1)(mem).elaborate(platform=None)
|
|
self.assertRepr(f._read_ports[0]._en, """
|
|
(& (sig mem_r_en) (sig c1))
|
|
""")
|
|
|
|
def test_enable_write_port(self):
|
|
with _ignore_deprecated():
|
|
mem = Memory(width=8, depth=4)
|
|
mem.write_port(granularity=2)
|
|
f = EnableInserter(self.c1)(mem).elaborate(platform=None)
|
|
self.assertRepr(f._write_ports[0]._en, """
|
|
(switch-value
|
|
(sig c1)
|
|
(case 0 (const 4'd0))
|
|
(default (sig mem_w_en))
|
|
)
|
|
""")
|
|
|
|
|
|
class _MockElaboratable(Elaboratable):
|
|
def __init__(self):
|
|
self.s1 = Signal()
|
|
|
|
def elaborate(self, platform):
|
|
f = Fragment()
|
|
f.add_statements("sync", self.s1.eq(1))
|
|
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["sync"], """
|
|
(
|
|
(switch (sig c1)
|
|
(case 1 (eq (sig s1) (const 1'd1)))
|
|
)
|
|
(switch (sig c2)
|
|
(case 1 (eq (sig s1) (const 1'd0)))
|
|
)
|
|
)
|
|
""")
|