hdl.ast: add Sample.
authorwhitequark <cz@m-labs.hk>
Thu, 17 Jan 2019 01:36:27 +0000 (01:36 +0000)
committerwhitequark <cz@m-labs.hk>
Thu, 17 Jan 2019 01:36:27 +0000 (01:36 +0000)
nmigen/hdl/ast.py
nmigen/test/test_hdl_ast.py

index be1440b7b50112733223cb56954b1765a3bc1ed5..bee4a1dfa6ba7219bc5c4acd74c9814662e8a2a8 100644 (file)
@@ -10,7 +10,7 @@ from ..tools import *
 
 __all__ = [
     "Value", "Const", "C", "AnyConst", "AnySeq", "Operator", "Mux", "Part", "Slice", "Cat", "Repl",
-    "Array", "ArrayProxy",
+    "Array", "ArrayProxy", "Sample",
     "Signal", "ClockSignal", "ResetSignal",
     "Statement", "Assign", "Assert", "Assume", "Switch", "Delay", "Tick",
     "Passive", "ValueKey", "ValueDict", "ValueSet", "SignalKey", "SignalDict",
@@ -831,6 +831,30 @@ class ArrayProxy(Value):
         return "(proxy (array [{}]) {!r})".format(", ".join(map(repr, self.elems)), self.index)
 
 
+class Sample(Value):
+    def __init__(self, value, clocks, domain):
+        super().__init__(src_loc_at=1)
+        self.value  = Value.wrap(value)
+        self.clocks = int(clocks)
+        self.domain = domain
+        if not isinstance(self.value, (Const, Signal)):
+            raise TypeError("Sampled value may only be a signal or a constant, not {!r}"
+                            .format(self.value))
+        if self.clocks < 0:
+            raise ValueError("Cannot sample a value {} cycles in the future"
+                             .format(-self.clocks))
+
+    def shape(self):
+        return self.value.shape()
+
+    def _rhs_signals(self):
+        return ValueSet((self,))
+
+    def __repr__(self):
+        return "(sample {!r} @ {}[{}])".format(
+            self.value, "<default>" if self.domain is None else self.domain, self.clocks)
+
+
 class _StatementList(list):
     def __repr__(self):
         return "({})".format(" ".join(map(repr, self)))
@@ -1088,6 +1112,8 @@ class ValueKey:
         elif isinstance(self.value, ArrayProxy):
             return hash((ValueKey(self.value.index),
                          tuple(ValueKey(e) for e in self.value._iter_as_values())))
+        elif isinstance(self.value, Sample):
+            return hash((ValueKey(self.value.value), self.value.clocks, self.value.domain))
         else: # :nocov:
             raise TypeError("Object '{!r}' cannot be used as a key in value collections"
                             .format(self.value))
@@ -1126,6 +1152,10 @@ class ValueKey:
                     all(ValueKey(a) == ValueKey(b)
                         for a, b in zip(self.value._iter_as_values(),
                                         other.value._iter_as_values())))
+        elif isinstance(self.value, Sample):
+            return (ValueKey(self.value.value) == ValueKey(other.value.value) and
+                    self.value.clocks == other.value.clocks and
+                    self.value.domain == self.value.domain)
         else: # :nocov:
             raise TypeError("Object '{!r}' cannot be used as a key in value collections"
                             .format(self.value))
index 603a6cc13818105111fa1a526c518f59cb5b43e3..1aee24b18745d608857c699b558c3eb07435a9ab 100644 (file)
@@ -496,3 +496,24 @@ class ResetSignalTestCase(FHDLTestCase):
     def test_repr(self):
         s1 = ResetSignal()
         self.assertEqual(repr(s1), "(rst sync)")
+
+
+class SampleTestCase(FHDLTestCase):
+    def test_const(self):
+        s = Sample(1, 1, "sync")
+        self.assertEqual(s.shape(), (1, False))
+
+    def test_signal(self):
+        s = Sample(Signal(2), 1, "sync")
+        self.assertEqual(s.shape(), (2, False))
+
+    def test_wrong_value_operator(self):
+        with self.assertRaises(TypeError,
+                "Sampled value may only be a signal or a constant, not "
+                "(+ (sig $signal) (const 1'd1))"):
+            Sample(Signal() + 1, 1, "sync")
+
+    def test_wrong_clocks_neg(self):
+        with self.assertRaises(ValueError,
+                "Cannot sample a value 1 cycles in the future"):
+            Sample(Signal(), -1, "sync")