hdl.ast: implement values with custom lowering.
This commit is contained in:
		
							parent
							
								
									066dd799e8
								
							
						
					
					
						commit
						ad1a40c934
					
				|  | @ -13,6 +13,7 @@ __all__ = [ | ||||||
|     "Array", "ArrayProxy", |     "Array", "ArrayProxy", | ||||||
|     "Sample", "Past", "Stable", "Rose", "Fell", |     "Sample", "Past", "Stable", "Rose", "Fell", | ||||||
|     "Signal", "ClockSignal", "ResetSignal", |     "Signal", "ClockSignal", "ResetSignal", | ||||||
|  |     "UserValue", | ||||||
|     "Statement", "Assign", "Assert", "Assume", "Switch", "Delay", "Tick", |     "Statement", "Assign", "Assert", "Assume", "Switch", "Delay", "Tick", | ||||||
|     "Passive", "ValueKey", "ValueDict", "ValueSet", "SignalKey", "SignalDict", |     "Passive", "ValueKey", "ValueDict", "ValueSet", "SignalKey", "SignalDict", | ||||||
|     "SignalSet", |     "SignalSet", | ||||||
|  | @ -848,6 +849,50 @@ class ArrayProxy(Value): | ||||||
|         return "(proxy (array [{}]) {!r})".format(", ".join(map(repr, self.elems)), self.index) |         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 | @final | ||||||
| class Sample(Value): | class Sample(Value): | ||||||
|     """Value from the past. |     """Value from the past. | ||||||
|  |  | ||||||
|  | @ -111,6 +111,9 @@ class ValueVisitor(metaclass=ABCMeta): | ||||||
|             new_value = self.on_ArrayProxy(value) |             new_value = self.on_ArrayProxy(value) | ||||||
|         elif type(value) is Sample: |         elif type(value) is Sample: | ||||||
|             new_value = self.on_Sample(value) |             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: |         else: | ||||||
|             new_value = self.on_unknown_value(value) |             new_value = self.on_unknown_value(value) | ||||||
|         if isinstance(new_value, Value): |         if isinstance(new_value, Value): | ||||||
|  |  | ||||||
|  | @ -523,6 +523,26 @@ class ResetSignalTestCase(FHDLTestCase): | ||||||
|         self.assertEqual(repr(s1), "(rst sync)") |         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): | class SampleTestCase(FHDLTestCase): | ||||||
|     def test_const(self): |     def test_const(self): | ||||||
|         s = Sample(1, 1, "sync") |         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