back.rtlil: emit dummy logic to work around Verilog deficiencies.
authorwhitequark <whitequark@whitequark.org>
Sun, 23 Dec 2018 10:14:05 +0000 (10:14 +0000)
committerwhitequark <whitequark@whitequark.org>
Sun, 23 Dec 2018 10:14:42 +0000 (10:14 +0000)
nmigen/back/rtlil.py

index d4a011c51de026f339455808f771254428b4f30d..0bd5f08629321702fd7df06261a35abadebc4373 100644 (file)
@@ -531,6 +531,9 @@ class _StatementCompiler(xfrm.StatementVisitor):
 
         self._test_cache = {}
 
+        self._has_rhs = False
+        self._has_assign = False
+
     @contextmanager
     def case(self, switch, value):
         try:
@@ -547,6 +550,10 @@ class _StatementCompiler(xfrm.StatementVisitor):
         if any_lhs_signal not in self._group:
             return
 
+        if self._has_rhs or next(iter(stmt.rhs._rhs_signals()), None) is not None:
+            self._has_rhs = True
+        self._has_assign = True
+
         lhs_bits, lhs_sign = stmt.lhs.shape()
         rhs_bits, rhs_sign = stmt.rhs.shape()
         if lhs_bits == rhs_bits:
@@ -562,10 +569,20 @@ class _StatementCompiler(xfrm.StatementVisitor):
             self._test_cache[stmt] = self.rhs_compiler(stmt.test)
         test_sigspec = self._test_cache[stmt]
 
-        with self._case.switch(test_sigspec) as switch:
-            for value, stmts in stmt.cases.items():
-                with self.case(switch, value):
-                    self.on_statements(stmts)
+        try:
+            self._has_assign, old_has_assign = False, self._has_assign
+
+            with self._case.switch(test_sigspec) as switch:
+                for value, stmts in stmt.cases.items():
+                    with self.case(switch, value):
+                        self.on_statements(stmts)
+
+        finally:
+            if self._has_assign:
+                if self._has_rhs or next(iter(stmt.test._rhs_signals()), None) is not None:
+                    self._has_rhs = True
+
+            self._has_assign = old_has_assign
 
     def on_statement(self, stmt):
         try:
@@ -603,6 +620,9 @@ def convert_fragment(builder, fragment, name, top):
         lhs_compiler   = _LHSValueCompiler(compiler_state)
         stmt_compiler  = _StatementCompiler(compiler_state, rhs_compiler, lhs_compiler)
 
+        verilog_trigger = None
+        verilog_trigger_sync_emitted = False
+
         # Register all signals driven in the current fragment. This must be done first, as it
         # affects further codegen; e.g. whether sig$next signals will be generated and used.
         for domain, signal in fragment.iter_drivers():
@@ -670,8 +690,16 @@ def convert_fragment(builder, fragment, name, top):
 
             module.cell(sub_type, name=sub_name, ports=sub_ports, params=sub_params)
 
+        # If we emit all of our combinatorial logic into a single RTLIL process, Verilog
+        # simulators will break horribly, because Yosys write_verilog transforms RTLIL processes
+        # into always @* blocks with blocking assignment, and that does not create delta cycles.
+        #
+        # Therefore, we translate the fragment as many times as there are independent groups
+        # of signals (a group is a transitive closure of signals that appear together on LHS),
+        # splitting them into many RTLIL (and thus Verilog) processes.
         lhs_grouper = xfrm.LHSGroupAnalyzer()
         lhs_grouper.on_statements(fragment.statements)
+
         for group, group_signals in lhs_grouper.groups().items():
             with module.process(name="$group_{}".format(group)) as process:
                 with process.case() as case:
@@ -690,8 +718,20 @@ def convert_fragment(builder, fragment, name, top):
                     # Convert statements into decision trees.
                     stmt_compiler._group = group_signals
                     stmt_compiler._case = case
+                    stmt_compiler._has_rhs = False
                     stmt_compiler(fragment.statements)
 
+                    # Verilog `always @*` blocks will not run if `*` does not match anythng, i.e.
+                    # if the implicit sensitivity list is empty. We check this while translating,
+                    # by looking at any signals on RHS. If this is not true, we add some logic
+                    # whose only purpose is to trigger Verilog simulators when it converts
+                    # through RTLIL and to Verilog.
+                    if not stmt_compiler._has_rhs:
+                        if verilog_trigger is None:
+                            verilog_trigger = \
+                                module.wire(1, name="$verilog_initial_trigger")
+                        case.assign(verilog_trigger, verilog_trigger)
+
                 # For every signal in the sync domain, assign \sig's initial value (which will
                 # end up as the \init reg attribute) to the reset value.
                 with process.sync("init") as sync:
@@ -701,6 +741,10 @@ def convert_fragment(builder, fragment, name, top):
                         wire_curr, wire_next = compiler_state.resolve(signal)
                         sync.update(wire_curr, rhs_compiler(ast.Const(signal.reset, signal.nbits)))
 
+                    if verilog_trigger and not verilog_trigger_sync_emitted:
+                        sync.update(verilog_trigger, "1'0")
+                        verilog_trigger_sync_emitted = True
+
                 # For every signal in every domain, assign \sig to \sig$next. The sensitivity list,
                 # however, differs between domains: for comb domains, it is `always`, for sync
                 # domains with sync reset, it is `posedge clk`, for sync domains with async reset