Repository: amaranth-lang/amaranth
Pull request #1029 (2024-01-11 04:34:17)

Backports to v0.4.x


Patch:
diff --git a/amaranth/hdl/ast.py b/amaranth/hdl/ast.py
index c698f0c..75ebbc6 100644
--- a/amaranth/hdl/ast.py
+++ b/amaranth/hdl/ast.py
@@ -2,6 +2,7 @@ from abc import ABCMeta, abstractmethod
 import inspect
 import warnings
 import functools
+import operator
 from collections import OrderedDict
 from collections.abc import Iterable, MutableMapping, MutableSet, MutableSequence
 from enum import Enum, EnumMeta
@@ -737,7 +738,8 @@ class Const(Value):
             width = 0
             for part in obj.parts:
                 const  = Const.cast(part)
-                value |= const.value << width
+                part_value = Const(const.value, unsigned(const.width)).value
+                value |= part_value << width
                 width += len(const)
             return Const(value, width)
         else:
@@ -745,7 +747,7 @@ class Const(Value):
 
     def __init__(self, value, shape=None, *, src_loc_at=0):
         # We deliberately do not call Value.__init__ here.
-        self.value = int(value)
+        self.value = int(operator.index(value))
         if shape is None:
             shape = Shape(bits_for(self.value), signed=self.value < 0)
         elif isinstance(shape, int):
@@ -925,8 +927,8 @@ class Slice(Value):
 
         super().__init__(src_loc_at=src_loc_at)
         self.value = value
-        self.start = int(start)
-        self.stop  = int(stop)
+        self.start = int(operator.index(start))
+        self.stop  = int(operator.index(stop))
 
     def shape(self):
         return Shape(self.stop - self.start)
@@ -1389,6 +1391,8 @@ class Array(MutableSequence):
         self._mutable  = True
 
     def __getitem__(self, index):
+        if isinstance(index, ValueCastable):
+            index = Value.cast(index)
         if isinstance(index, Value):
             if self._mutable:
                 self._proxy_at = tracer.get_src_loc()
diff --git a/amaranth/hdl/dsl.py b/amaranth/hdl/dsl.py
index e6412ca..467fd1e 100644
--- a/amaranth/hdl/dsl.py
+++ b/amaranth/hdl/dsl.py
@@ -344,7 +344,9 @@ class Module(_ModuleBuilderRoot, Elaboratable):
             # 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.
-            if not (patterns and not new_patterns):
+            # 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"]:
                 switch_data["cases"][new_patterns] = self._statements
                 switch_data["case_src_locs"][new_patterns] = src_loc
         finally:
diff --git a/amaranth/sim/_pycoro.py b/amaranth/sim/_pycoro.py
index 10ce1ca..e2714a2 100644
--- a/amaranth/sim/_pycoro.py
+++ b/amaranth/sim/_pycoro.py
@@ -1,7 +1,7 @@
 import inspect
 
 from ..hdl import *
-from ..hdl.ast import Statement, SignalSet
+from ..hdl.ast import Statement, SignalSet, ValueCastable
 from .core import Tick, Settle, Delay, Passive, Active
 from ._base import BaseProcess
 from ._pyrtl import _ValueCompiler, _RHSValueCompiler, _StatementCompiler
@@ -66,6 +66,8 @@ class PyCoroProcess(BaseProcess):
                     command = self.default_cmd
                 response = None
 
+                if isinstance(command, ValueCastable):
+                    command = Value.cast(command)
                 if isinstance(command, Value):
                     exec(_RHSValueCompiler.compile(self.state, command, mode="curr"),
                         self.exec_locals)


Test patch:
diff --git a/tests/test_hdl_ast.py b/tests/test_hdl_ast.py
index 56dfca3..1e4d8a4 100644
--- a/tests/test_hdl_ast.py
+++ b/tests/test_hdl_ast.py
@@ -935,6 +935,16 @@ class CatTestCase(FHDLTestCase):
                 r"specify the width explicitly using C\(2, 2\)$"):
             Cat(2)
 
+    def test_const(self):
+        a = Const.cast(Cat(Const(1, 1), Const(0, 1), Const(3, 2), Const(2, 2)))
+        self.assertEqual(a.value, 0x2d)
+        self.assertEqual(a.width, 6)
+        self.assertEqual(a.signed, False)
+        a = Const.cast(Cat(Const(-4, 8), Const(-3, 8)))
+        self.assertEqual(a.value, 0xfdfc)
+        self.assertEqual(a.width, 16)
+        self.assertEqual(a.signed, False)
+
 
 class ReplTestCase(FHDLTestCase):
     @_ignore_deprecated
@@ -985,6 +995,18 @@ class ArrayTestCase(FHDLTestCase):
                 r"^Array can no longer be mutated after it was indexed with a value at "):
             a.insert(1, 2)
 
+    def test_index_value_castable(self):
+        class MyValue(ValueCastable):
+            @ValueCastable.lowermethod
+            def as_value(self):
+                return Signal()
+            
+            def shape():
+                return unsigned(1)
+        
+        a = Array([1,2,3])
+        a[MyValue()]
+
     def test_repr(self):
         a = Array([1,2,3])
         self.assertEqual(repr(a), "(array mutable [1, 2, 3])")
diff --git a/tests/test_sim.py b/tests/test_sim.py
index 60fd58e..50ba38a 100644
--- a/tests/test_sim.py
+++ b/tests/test_sim.py
@@ -690,6 +690,27 @@ class SimulatorIntegrationTestCase(FHDLTestCase):
             sim.add_process(process)
         self.assertTrue(survived)
 
+    def test_value_castable(self):
+        class MyValue(ValueCastable):
+            @ValueCastable.lowermethod
+            def as_value(self):
+                return Signal()
+            
+            def shape():
+                return unsigned(1)
+        
+        a = Array([1,2,3])
+        a[MyValue()]
+
+        survived = False
+        with self.assertSimulation(Module()) as sim:
+            def process():
+                nonlocal survived
+                yield MyValue()
+                survived = True
+            sim.add_process(process)
+        self.assertTrue(survived)
+
     def setUp_memory(self, rd_synchronous=True, rd_transparent=True, wr_granularity=None):
         self.m = Module()
         self.memory = Memory(width=8, depth=4, init=[0xaa, 0x55])