From a384ad2a8a0802b3f8096662340ab6e52bd9f711 Mon Sep 17 00:00:00 2001 From: whitequark Date: Sat, 3 Aug 2019 14:54:20 +0000 Subject: [PATCH] hdl.ir: call back from Fragment.prepare if a clock domain is missing. See #57. --- nmigen/build/plat.py | 2 +- nmigen/compat/fhdl/verilog.py | 6 ++- nmigen/compat/sim/__init__.py | 2 + nmigen/hdl/ir.py | 23 ++++++---- nmigen/hdl/xfrm.py | 81 ++++++++++++++++++++++++++++++++++- nmigen/test/test_hdl_ir.py | 10 +++-- nmigen/test/test_sim.py | 10 ++++- 7 files changed, 116 insertions(+), 18 deletions(-) diff --git a/nmigen/build/plat.py b/nmigen/build/plat.py index 9a21bad..f4c5cd5 100644 --- a/nmigen/build/plat.py +++ b/nmigen/build/plat.py @@ -211,7 +211,7 @@ class TemplatedPlatform(Platform): def emit_design(backend): return {"rtlil": rtlil, "verilog": verilog}[backend].convert( fragment, name=name, platform=self, ports=list(self.iter_ports()), - ensure_sync_exists=False) + missing_domain=lambda name: None) def emit_commands(format): commands = [] diff --git a/nmigen/compat/fhdl/verilog.py b/nmigen/compat/fhdl/verilog.py index ad2f588..29e5daa 100644 --- a/nmigen/compat/fhdl/verilog.py +++ b/nmigen/compat/fhdl/verilog.py @@ -1,6 +1,7 @@ import warnings from ...hdl.ir import Fragment +from ...hdl.cd import ClockDomain from ...back import verilog from .conv_output import ConvOutput @@ -16,11 +17,14 @@ def convert(fi, ios=None, name="top", special_overrides=dict(), DeprecationWarning, stacklevel=1) # TODO: attr_translate + def missing_domain(name): + if create_clock_domains: + return ClockDomain(name) v_output = verilog.convert( fragment=Fragment.get(fi.get_fragment(), platform=None), name=name, ports=ios or (), - ensure_sync_exists=create_clock_domains + missing_domain=missing_domain ) output = ConvOutput() output.set_main_source(v_output) diff --git a/nmigen/compat/sim/__init__.py b/nmigen/compat/sim/__init__.py index 009492f..9a3a5aa 100644 --- a/nmigen/compat/sim/__init__.py +++ b/nmigen/compat/sim/__init__.py @@ -1,6 +1,7 @@ import functools import inspect from collections.abc import Iterable +from ...hdl.cd import ClockDomain from ...back.pysim import * @@ -18,6 +19,7 @@ def run_simulation(fragment_or_module, generators, clocks={"sync": 10}, vcd_name if not isinstance(generators, dict): generators = {"sync": generators} + fragment.domains += ClockDomain("sync") with Simulator(fragment, vcd_file=open(vcd_name, "w") if vcd_name else None) as sim: for domain, period in clocks.items(): diff --git a/nmigen/hdl/ir.py b/nmigen/hdl/ir.py index bb2cd0e..805c31c 100644 --- a/nmigen/hdl/ir.py +++ b/nmigen/hdl/ir.py @@ -351,14 +351,19 @@ class Fragment: subfrag._propagate_domains_down() - def _propagate_domains(self, ensure_sync_exists): + def _propagate_domains(self, missing_domain): + from .xfrm import DomainCollector + self._propagate_domains_up() - if ensure_sync_exists and not self.domains: - cd_sync = ClockDomain() - self.add_domains(cd_sync) - new_domains = (cd_sync,) - else: - new_domains = () + new_domains = [] + for domain_name in DomainCollector()(self): + if domain_name is None: + continue + if domain_name not in self.domains: + domain = missing_domain(domain_name) + if domain is not None: + self.add_domains(domain) + new_domains.append(domain) self._propagate_domains_down() return new_domains @@ -513,11 +518,11 @@ class Fragment: else: self.add_ports(sig, dir="i") - def prepare(self, ports=None, ensure_sync_exists=True): + def prepare(self, ports=None, missing_domain=lambda name: ClockDomain(name)): from .xfrm import SampleLowerer fragment = SampleLowerer()(self) - new_domains = fragment._propagate_domains(ensure_sync_exists) + new_domains = fragment._propagate_domains(missing_domain) fragment._resolve_hierarchy_conflicts() fragment = fragment._insert_domain_resets() fragment = fragment._lower_domain_signals() diff --git a/nmigen/hdl/xfrm.py b/nmigen/hdl/xfrm.py index 25f2825..8d853a6 100644 --- a/nmigen/hdl/xfrm.py +++ b/nmigen/hdl/xfrm.py @@ -15,7 +15,7 @@ __all__ = ["ValueVisitor", "ValueTransformer", "StatementVisitor", "StatementTransformer", "FragmentTransformer", "TransformedElaboratable", - "DomainRenamer", "DomainLowerer", + "DomainCollector", "DomainRenamer", "DomainLowerer", "SampleDomainInjector", "SampleLowerer", "SwitchCleaner", "LHSGroupAnalyzer", "LHSGroupFilter", "ResetInserter", "CEInserter"] @@ -325,6 +325,85 @@ class TransformedElaboratable(Elaboratable): return fragment +class DomainCollector(ValueVisitor, StatementVisitor): + def __init__(self): + self.domains = set() + + def on_ignore(self, value): + pass + + on_Const = on_ignore + on_AnyConst = on_ignore + on_AnySeq = on_ignore + on_Signal = on_ignore + + def on_ClockSignal(self, value): + self.domains.add(value.domain) + + def on_ResetSignal(self, value): + self.domains.add(value.domain) + + on_Record = on_ignore + + def on_Operator(self, value): + for o in value.operands: + self.on_value(o) + + def on_Slice(self, value): + self.on_value(value.value) + + def on_Part(self, value): + self.on_value(value.value) + self.on_value(value.offset) + + def on_Cat(self, value): + for o in value.parts: + self.on_value(o) + + def on_Repl(self, value): + self.on_value(value.value) + + def on_ArrayProxy(self, value): + for elem in value._iter_as_values(): + self.on_value(elem) + self.on_value(value.index) + + def on_Sample(self, value): + self.on_value(value.value) + + def on_Assign(self, stmt): + self.on_value(stmt.lhs) + self.on_value(stmt.rhs) + + def on_Assert(self, stmt): + self.on_value(stmt.test) + + def on_Assume(self, stmt): + self.on_value(stmt.test) + + def on_Switch(self, stmt): + self.on_value(stmt.test) + for stmts in stmt.cases.values(): + self.on_statement(stmts) + + def on_statements(self, stmts): + for stmt in stmts: + self.on_statement(stmt) + + def on_fragment(self, fragment): + if isinstance(fragment, Instance): + for name, (value, dir) in fragment.named_ports.items(): + self.on_value(value) + self.on_statements(fragment.statements) + self.domains.update(fragment.drivers.keys()) + for subfragment, name in fragment.subfragments: + self.on_fragment(subfragment) + + def __call__(self, fragment): + self.on_fragment(fragment) + return self.domains + + class DomainRenamer(FragmentTransformer, ValueTransformer, StatementTransformer): def __init__(self, domain_map): if isinstance(domain_map, str): diff --git a/nmigen/test/test_hdl_ir.py b/nmigen/test/test_hdl_ir.py index 439b791..f8efd8c 100644 --- a/nmigen/test/test_hdl_ir.py +++ b/nmigen/test/test_hdl_ir.py @@ -376,16 +376,18 @@ class FragmentDomainsTestCase(FHDLTestCase): f1.add_domains(cd) f1.add_subfragment(f2) - f1._propagate_domains(ensure_sync_exists=False) + f1._propagate_domains(missing_domain=lambda name: None) self.assertEqual(f1.domains, {"cd": cd}) self.assertEqual(f2.domains, {"cd": cd}) - def test_propagate_ensure_sync(self): + def test_propagate_create_missing(self): + s1 = Signal() f1 = Fragment() + f1.add_driver(s1, "sync") f2 = Fragment() f1.add_subfragment(f2) - f1._propagate_domains(ensure_sync_exists=True) + 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"]) @@ -661,7 +663,7 @@ class InstanceTestCase(FHDLTestCase): 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], ensure_sync_exists=False) + fp = f.prepare(ports=[s], missing_domain=lambda name: None) self.assertEqual(fp.ports, SignalDict([ (s, "o"), ])) diff --git a/nmigen/test/test_sim.py b/nmigen/test/test_sim.py index 2a3d93d..d796e88 100644 --- a/nmigen/test/test_sim.py +++ b/nmigen/test/test_sim.py @@ -385,7 +385,10 @@ class SimulatorIntegrationTestCase(FHDLTestCase): sim.add_process(process) def test_run_until(self): - with self.assertSimulation(Module(), deadline=100e-6) as sim: + 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): @@ -401,7 +404,10 @@ class SimulatorIntegrationTestCase(FHDLTestCase): sim.add_process(1) def test_add_clock_wrong(self): - with self.assertSimulation(Module()) as sim: + m = Module() + s = Signal() + m.d.sync += s.eq(0) + with self.assertSimulation(m) as sim: sim.add_clock(1) with self.assertRaises(ValueError, msg="Domain 'sync' already has a clock driving it"): -- 2.30.2