back.rtlil: do not squash empty modules.
authorwhitequark <whitequark@whitequark.org>
Wed, 26 Aug 2020 22:45:19 +0000 (22:45 +0000)
committerLuke Kenneth Casson Leighton <lkcl@lkcl.net>
Fri, 31 Dec 2021 15:06:50 +0000 (15:06 +0000)
In commit 9faa1d37, the RTLIL backend was changed to ignore modules
without ports completely, since Yosys would recognize empty modules
as black boxes without explicit `write_verilog -noblackbox` and break
the design. That change had many flaws:
  * It removed instances without ports, which are used in e.g. SoC
    FPGAs to instantiate a dummy CPU.
  * It removed fragments without ports, which can appear in e.g. SoC
    FPGAs in case the fabric is not connected to any I/O ports.
  * Finally, it was just conceptually unjustified.

This commit changes the logic to actually check for empty fragments,
and instead of removing them, it adds a dummy wire inside. It would
be possible to use the Yosys-specific (*noblackbox*) attribute.
However, it would be necessary to strip it for most targets right
away, and also the wire doubles as documentation.

Fixes #441.

nmigen/back/rtlil.py

index 100df5ab5b98b0256f73a199968002d339b5d717..05f219ab79921598422f10d4250e1d17847443f6 100644 (file)
@@ -831,6 +831,11 @@ def _convert_fragment(builder, fragment, name_map, hierarchy):
         verilog_trigger = None
         verilog_trigger_sync_emitted = False
 
+        # If the fragment is completely empty, add a dummy wire to it, or Yosys will interpret
+        # it as a black box by default (when read as Verilog).
+        if not fragment.ports and not fragment.statements and not fragment.subfragments:
+            module.wire(1, name="$empty_module_filler")
+
         # 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():
@@ -855,9 +860,6 @@ def _convert_fragment(builder, fragment, name_map, hierarchy):
         # name) names.
         memories = OrderedDict()
         for subfragment, sub_name in fragment.subfragments:
-            if not subfragment.ports:
-                continue
-
             if sub_name is None:
                 sub_name = module.anonymous()