hdl.ast: implement values with custom lowering.
This commit is contained in:
		
							parent
							
								
									066dd799e8
								
							
						
					
					
						commit
						ad1a40c934
					
				|  | @ -13,6 +13,7 @@ __all__ = [ | |||
|     "Array", "ArrayProxy", | ||||
|     "Sample", "Past", "Stable", "Rose", "Fell", | ||||
|     "Signal", "ClockSignal", "ResetSignal", | ||||
|     "UserValue", | ||||
|     "Statement", "Assign", "Assert", "Assume", "Switch", "Delay", "Tick", | ||||
|     "Passive", "ValueKey", "ValueDict", "ValueSet", "SignalKey", "SignalDict", | ||||
|     "SignalSet", | ||||
|  | @ -848,6 +849,50 @@ class ArrayProxy(Value): | |||
|         return "(proxy (array [{}]) {!r})".format(", ".join(map(repr, self.elems)), self.index) | ||||
| 
 | ||||
| 
 | ||||
| class UserValue(Value): | ||||
|     """Value with custom lowering. | ||||
| 
 | ||||
|     A ``UserValue`` is a value whose precise representation does not have to be immediately known, | ||||
|     which is useful in certain metaprogramming scenarios. Instead of providing fixed semantics | ||||
|     upfront, it is kept abstract for as long as possible, only being lowered to a concrete nMigen | ||||
|     value when required. | ||||
| 
 | ||||
|     Note that the ``lower`` method will only be called once; this is necessary to ensure that | ||||
|     nMigen's view of representation of all values stays internally consistent. If the class | ||||
|     deriving from  ``UserValue`` is mutable, then it must ensure that after ``lower`` is called, | ||||
|     it is not mutated in a way that changes its representation. | ||||
| 
 | ||||
|     The following is an incomplete list of actions that, when applied to an ``UserValue`` directly | ||||
|     or indirectly, will cause it to be lowered, provided as an illustrative reference: | ||||
|         * Querying the shape using ``.shape()`` or ``len()``; | ||||
|         * Creating a similarly shaped signal using ``Signal.like``; | ||||
|         * Indexing or iterating through individual bits; | ||||
|         * Adding an assignment to the value to a ``Module`` using ``m.d.<domain> +=``. | ||||
|     """ | ||||
|     def __init__(self, src_loc_at=1): | ||||
|         super().__init__(src_loc_at=1 + src_loc_at) | ||||
|         self.__lowered = None | ||||
| 
 | ||||
|     @abstractmethod | ||||
|     def lower(self): | ||||
|         """Conversion to a concrete representation.""" | ||||
|         pass # :nocov: | ||||
| 
 | ||||
|     def _lazy_lower(self): | ||||
|         if self.__lowered is None: | ||||
|             self.__lowered = Value.wrap(self.lower()) | ||||
|         return self.__lowered | ||||
| 
 | ||||
|     def shape(self): | ||||
|         return self._lazy_lower().shape() | ||||
| 
 | ||||
|     def _lhs_signals(self): | ||||
|         return self._lazy_lower()._lhs_signals() | ||||
| 
 | ||||
|     def _rhs_signals(self): | ||||
|         return self._lazy_lower()._rhs_signals() | ||||
| 
 | ||||
| 
 | ||||
| @final | ||||
| class Sample(Value): | ||||
|     """Value from the past. | ||||
|  |  | |||
|  | @ -111,6 +111,9 @@ class ValueVisitor(metaclass=ABCMeta): | |||
|             new_value = self.on_ArrayProxy(value) | ||||
|         elif type(value) is Sample: | ||||
|             new_value = self.on_Sample(value) | ||||
|         elif isinstance(value, UserValue): | ||||
|             # Uses `isinstance()` and not `type() is` to allow inheriting. | ||||
|             new_value = self.on_value(value._lazy_lower()) | ||||
|         else: | ||||
|             new_value = self.on_unknown_value(value) | ||||
|         if isinstance(new_value, Value): | ||||
|  |  | |||
|  | @ -523,6 +523,26 @@ class ResetSignalTestCase(FHDLTestCase): | |||
|         self.assertEqual(repr(s1), "(rst sync)") | ||||
| 
 | ||||
| 
 | ||||
| class MockUserValue(UserValue): | ||||
|     def __init__(self, lowered): | ||||
|         super().__init__() | ||||
|         self.lower_count = 0 | ||||
|         self.lowered     = lowered | ||||
| 
 | ||||
|     def lower(self): | ||||
|         self.lower_count += 1 | ||||
|         return self.lowered | ||||
| 
 | ||||
| 
 | ||||
| class UserValueTestCase(FHDLTestCase): | ||||
|     def test_shape(self): | ||||
|         uv = MockUserValue(1) | ||||
|         self.assertEqual(uv.shape(), (1, False)) | ||||
|         uv.lowered = 2 | ||||
|         self.assertEqual(uv.shape(), (1, False)) | ||||
|         self.assertEqual(uv.lower_count, 1) | ||||
| 
 | ||||
| 
 | ||||
| class SampleTestCase(FHDLTestCase): | ||||
|     def test_const(self): | ||||
|         s = Sample(1, 1, "sync") | ||||
|  |  | |||
|  | @ -548,3 +548,39 @@ class TransformedElaboratableTestCase(FHDLTestCase): | |||
|             ) | ||||
|         ) | ||||
|         """) | ||||
| 
 | ||||
| 
 | ||||
| class MockUserValue(UserValue): | ||||
|     def __init__(self, lowered): | ||||
|         super().__init__() | ||||
|         self.lowered = lowered | ||||
| 
 | ||||
|     def lower(self): | ||||
|         return self.lowered | ||||
| 
 | ||||
| 
 | ||||
| class UserValueTestCase(FHDLTestCase): | ||||
|     def setUp(self): | ||||
|         self.s  = Signal() | ||||
|         self.c  = Signal() | ||||
|         self.uv = MockUserValue(self.s) | ||||
| 
 | ||||
|     def test_lower(self): | ||||
|         sync = ClockDomain() | ||||
|         f = Fragment() | ||||
|         f.add_statements( | ||||
|             self.uv.eq(1) | ||||
|         ) | ||||
|         for signal in self.uv._lhs_signals(): | ||||
|             f.add_driver(signal, "sync") | ||||
| 
 | ||||
|         f = ResetInserter(self.c)(f) | ||||
|         f = DomainLowerer({"sync": sync})(f) | ||||
|         self.assertRepr(f.statements, """ | ||||
|         ( | ||||
|             (eq (sig s) (const 1'd1)) | ||||
|             (switch (sig c) | ||||
|                 (case 1 (eq (sig s) (const 1'd0))) | ||||
|             ) | ||||
|         ) | ||||
|         """) | ||||
|  |  | |||
		Loading…
	
		Reference in a new issue
	
	 whitequark
						whitequark