Repository: amaranth-lang/amaranth
Pull request #1031 (2024-01-13 11:37:02)

Implement RFC 39: Change semantics of no-argument `m.Case()`.

codecov[bot]: ## [Codecov](https://app.codecov.io/gh/amaranth-lang/amaranth/pull/1031?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=amaranth-lang) Report
Attention: `1 lines` in your changes are missing coverage. Please review.
> Comparison is base [(`7f76914`)](https://app.codecov.io/gh/amaranth-lang/amaranth/commit/7f76914b7455d1c4d21d00b6f7b8893ad2dbd788?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=amaranth-lang) 86.57% compared to head [(`37e385b`)](https://app.codecov.io/gh/amaranth-lang/amaranth/pull/1031?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=amaranth-lang) 86.58%.

> :exclamation: Current head 37e385b differs from pull request most recent head b5077cc. Consider uploading reports for the commit b5077cc to get more accurate results

| [Files](https://app.codecov.io/gh/amaranth-lang/amaranth/pull/1031?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=amaranth-lang) | Patch % | Lines |
|---|---|---|
| [amaranth/hdl/dsl.py](https://app.codecov.io/gh/amaranth-lang/amaranth/pull/1031?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=amaranth-lang#diff-YW1hcmFudGgvaGRsL2RzbC5weQ==) | 93.33% | [0 Missing and 1 partial :warning: ](https://app.codecov.io/gh/amaranth-lang/amaranth/pull/1031?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=amaranth-lang) |

<details><summary>Additional details and impacted files</summary>


```diff
@@            Coverage Diff             @@
##             main    #1031      +/-   ##
==========================================
+ Coverage   86.57%   86.58%   +0.01%     
==========================================
  Files          41       41              
  Lines        7071     7084      +13     
  Branches     1685     1687       +2     
==========================================
+ Hits         6122     6134      +12     
- Misses        787      788       +1     
  Partials      162      162              
```



</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/amaranth-lang/amaranth/pull/1031?src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=amaranth-lang).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=amaranth-lang).


Patch:
diff --git a/amaranth/back/rtlil.py b/amaranth/back/rtlil.py
index 1966407..8179c3c 100644
--- a/amaranth/back/rtlil.py
+++ b/amaranth/back/rtlil.py
@@ -888,8 +888,13 @@ def _convert_fragment(builder, fragment, name_map, hierarchy):
                 if len(value) > 0 or sub_type == "$mem_v2":
                     sub_ports[port] = rhs_compiler(value)
 
+            if isinstance(subfragment, ir.Instance):
+                src = _src(subfragment.src_loc)
+            else:
+                src = ""
+
             module.cell(sub_type, name=sub_name, ports=sub_ports, params=sub_params,
-                        attrs=subfragment.attrs)
+                        attrs=subfragment.attrs, src=src)
 
         # 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
diff --git a/amaranth/hdl/ast.py b/amaranth/hdl/ast.py
index 957d71b..26be377 100644
--- a/amaranth/hdl/ast.py
+++ b/amaranth/hdl/ast.py
@@ -542,7 +542,7 @@ class Value(metaclass=ABCMeta):
                     continue
                 matches.append(self == pattern)
         if not matches:
-            return Const(1)
+            return Const(0)
         elif len(matches) == 1:
             return matches[0]
         else:
diff --git a/amaranth/hdl/dsl.py b/amaranth/hdl/dsl.py
index e23d161..2da0dac 100644
--- a/amaranth/hdl/dsl.py
+++ b/amaranth/hdl/dsl.py
@@ -343,19 +343,31 @@ class Module(_ModuleBuilderRoot, Elaboratable):
             yield
             self._flush_ctrl()
             # If none of the provided cases can possibly be true, omit this branch completely.
-            # This needs to be differentiated from no cases being provided in the first place,
-            # which means the branch will always match.
             # Likewise, omit this branch if another branch with this exact set of patterns already
             # exists (since otherwise we'd overwrite the previous branch's slot in the dict).
-            if not (patterns and not new_patterns) and new_patterns not in switch_data["cases"]:
+            if new_patterns and new_patterns not in switch_data["cases"]:
                 switch_data["cases"][new_patterns] = self._statements
                 switch_data["case_src_locs"][new_patterns] = src_loc
         finally:
             self._ctrl_context = "Switch"
             self._statements = _outer_case
 
+    @contextmanager
     def Default(self):
-        return self.Case()
+        self._check_context("Default", context="Switch")
+        src_loc = tracer.get_src_loc(src_loc_at=1)
+        switch_data = self._get_ctrl("Switch")
+        try:
+            _outer_case, self._statements = self._statements, []
+            self._ctrl_context = None
+            yield
+            self._flush_ctrl()
+            if () not in switch_data["cases"]:
+                switch_data["cases"][()] = self._statements
+                switch_data["case_src_locs"][()] = src_loc
+        finally:
+            self._ctrl_context = "Switch"
+            self._statements = _outer_case
 
     @contextmanager
     def FSM(self, reset=None, domain="sync", name="fsm"):
diff --git a/amaranth/hdl/ir.py b/amaranth/hdl/ir.py
index 16aab52..0b7139b 100644
--- a/amaranth/hdl/ir.py
+++ b/amaranth/hdl/ir.py
@@ -3,6 +3,7 @@ from collections import defaultdict, OrderedDict
 from functools import reduce
 import warnings
 
+from .. import tracer
 from .._utils import *
 from .._unused import *
 from .ast import *
@@ -617,12 +618,13 @@ class Fragment:
 
 
 class Instance(Fragment):
-    def __init__(self, type, *args, **kwargs):
+    def __init__(self, type, *args, src_loc=None, src_loc_at=0, **kwargs):
         super().__init__()
 
         self.type        = type
         self.parameters  = OrderedDict()
         self.named_ports = OrderedDict()
+        self.src_loc     = src_loc or tracer.get_src_loc(src_loc_at)
 
         for (kind, name, value) in args:
             if kind == "a":
diff --git a/amaranth/hdl/mem.py b/amaranth/hdl/mem.py
index da59cdb..94ffe8f 100644
--- a/amaranth/hdl/mem.py
+++ b/amaranth/hdl/mem.py
@@ -166,6 +166,7 @@ class Memory(Elaboratable):
             i_WR_EN=Cat(Cat(en_bit.replicate(port.granularity) for en_bit in port.en) for port in self._write_ports),
             i_WR_ADDR=Cat(port.addr for port in self._write_ports),
             i_WR_DATA=Cat(port.data for port in self._write_ports),
+            src_loc=self.src_loc,
         )
         for port in self._read_ports:
             port._MustUse__used = True
diff --git a/amaranth/hdl/xfrm.py b/amaranth/hdl/xfrm.py
index 66e6c20..c56dbe0 100644
--- a/amaranth/hdl/xfrm.py
+++ b/amaranth/hdl/xfrm.py
@@ -263,7 +263,7 @@ class FragmentTransformer:
 
     def on_fragment(self, fragment):
         if isinstance(fragment, Instance):
-            new_fragment = Instance(fragment.type)
+            new_fragment = Instance(fragment.type, src_loc=fragment.src_loc)
             new_fragment.parameters = OrderedDict(fragment.parameters)
             self.map_named_ports(fragment, new_fragment)
         else:
diff --git a/docs/changes.rst b/docs/changes.rst
index dac65f2..8735cee 100644
--- a/docs/changes.rst
+++ b/docs/changes.rst
@@ -15,6 +15,8 @@ Migrating from version 0.4
 
 Apply the following changes to code written against Amaranth 0.4 to migrate it to version 0.5:
 
+* Replace uses of ``m.Case()`` with no patterns with ``m.Default()``
+* Replace uses of ``Value.matches()`` with no patterns with ``Const(1)``
 * Update uses of :func:`amaranth.utils.log2_int(need_pow2=False)` to :func:`amaranth.utils.ceil_log2`
 * Update uses of :func:`amaranth.utils.log2_int(need_pow2=True)` to :func:`amaranth.utils.exact_log2`
 
@@ -23,8 +25,10 @@ Implemented RFCs
 ----------------
 
 .. _RFC 17: https://amaranth-lang.org/rfcs/0017-remove-log2-int.html
+.. _RFC 39: https://amaranth-lang.org/rfcs/0039-empty-case.html
 
 * `RFC 17`_: Remove ``log2_int``
+* `RFC 39`_: Change semantics of no-argument ``m.Case()``
 
 
 Language changes
@@ -34,6 +38,8 @@ Language changes
 
 * Added: :class:`ast.Slice` objects have been made const-castable.
 * Added: :func:`amaranth.utils.ceil_log2`, :func:`amaranth.utils.exact_log2`. (`RFC 17`_)
+* Changed: ``m.Case()`` with no patterns is never active instead of always active. (`RFC 39`_)
+* Changed: ``Value.matches()`` with no patterns is ``Const(0)`` instead of ``Const(1)``. (`RFC 39`_)
 * Deprecated: :func:`amaranth.utils.log2_int`. (`RFC 17`_)
 * Removed: (deprecated in 0.4) :meth:`Const.normalize`. (`RFC 5`_)
 * Removed: (deprecated in 0.4) :class:`ast.Sample`, :class:`ast.Past`, :class:`ast.Stable`, :class:`ast.Rose`, :class:`ast.Fell`.


Test patch:
diff --git a/tests/test_hdl_ast.py b/tests/test_hdl_ast.py
index 2d9ef2a..4e52c6b 100644
--- a/tests/test_hdl_ast.py
+++ b/tests/test_hdl_ast.py
@@ -682,7 +682,7 @@ class OperatorTestCase(FHDLTestCase):
 
     def test_matches(self):
         s = Signal(4)
-        self.assertRepr(s.matches(), "(const 1'd1)")
+        self.assertRepr(s.matches(), "(const 1'd0)")
         self.assertRepr(s.matches(1), """
         (== (sig s) (const 1'd1))
         """)
diff --git a/tests/test_hdl_dsl.py b/tests/test_hdl_dsl.py
index 9558ef6..dddae25 100644
--- a/tests/test_hdl_dsl.py
+++ b/tests/test_hdl_dsl.py
@@ -366,7 +366,7 @@ class DSLTestCase(FHDLTestCase):
         )
         """)
 
-    def test_Switch_default_Case(self):
+    def test_Switch_empty_Case(self):
         m = Module()
         with m.Switch(self.w1):
             with m.Case(3):
@@ -378,7 +378,6 @@ class DSLTestCase(FHDLTestCase):
         (
             (switch (sig w1)
                 (case 0011 (eq (sig c1) (const 1'd1)))
-                (default (eq (sig c2) (const 1'd1)))
             )
         )
         """)