1- """Conditional assignment of registers and WireVectors based on a predicate.
2-
3- The management of selected assignments is expected to happen through
4- the "with" blocks which will ensure that the region of execution for
5- which the condition should apply is well defined. It is easiest
6- to see with an example::
7-
8- r1 = Register()
9- r2 = Register()
10- w3 = WireVector()
11- with conditional_assignment:
12- with a:
13- r1.next |= i # set when a is true
14- with b:
15- r2.next |= j # set when a and b are true
16- with c:
17- r1.next |= k # set when a is false and c is true
18- r2.next |= k
19- with otherwise:
20- r2.next |= l # a is false and c is false
21-
22- with d:
23- w3.next |= m # d is true (assignments must be independent)
24-
25- This is equivalent to::
26-
27- r1.next <<= select(a, i, select(c, k, default))
28- r2.next <<= select(a, select(b, j, default), select(c, k, l))
29- w3 <<= select(d, m, 0)
30-
31- This functionality is provided through two instances: ``conditional_update``,
32- which is a context manager (under which conditional assignements can be made),
33- and ``otherwise``, which is an instance that stands in for a 'fall through'
34- case. The details of how these should be used, and the difference between
35- normal assignments and condtional assignments, described in more detail in the
36- state machine example in ``examples/example3-statemachine.py``.
37-
38- There are instances where you might want a wirevector to be set to a certain
39- value in all but certain with blocks. For example, say you have a processor
40- with a PC register that is normally updated to PC + 1 after each cycle, except
41- when the current instruction is a branch or jump. You could represent that as
42- follows::
43-
44- pc = pyrtl.Register(32)
45- instr = pyrtl.WireVector(32)
46- res = pyrtl.WireVector(32)
1+ """Register and WireVectors can be conditionally assigned values based on predicates.
2+
3+ Conditional assignments are written with `Python with statements
4+ <https://docs.python.org/3/reference/compound_stmts.html#with>`_, using two context
5+ managers:
6+
7+ #. :data:`conditional_assignment`, which provides the framework for specifying
8+ conditional assignments.
9+ #. :data:`otherwise`, which specifies the 'fall through' case.
10+
11+ Conditional assignments are easiest to understand with an example::
12+
13+ r1 = pyrtl.Register(bitwidth=8)
14+ r2 = pyrtl.Register(bitwidth=8)
15+ w = pyrtl.WireVector(bitwidth=8)
16+ mem = pyrtl.MemBlock(bitwidth=8, addrwidth=4)
17+
18+ a = pyrtl.Input(bitwidth=1)
19+ b = pyrtl.Input(bitwidth=1)
20+ c = pyrtl.Input(bitwidth=1)
21+ d = pyrtl.Input(bitwidth=1)
22+
23+ with pyrtl.conditional_assignment:
24+ with a:
25+ # Set when a is True.
26+ r1.next |= 1
27+ mem[0] |= 2
28+ with b:
29+ # Set when a and b are both True.
30+ r2.next |= 3
31+ with c:
32+ # Set when a is False and c is True.
33+ r1.next |= 4
34+ r2.next |= 5
35+ with pyrtl.otherwise:
36+ # Set when a and c are both False.
37+ r2.next |= 6
38+
39+ with d:
40+ # Set when d is True. A `with` block after an `otherwise` starts a new
41+ # set of conditional assignments.
42+ w |= 7
43+
44+ This :data:`conditional_assignment` is equivalent to::
45+
46+ r1.next <<= pyrtl.select(a, 1, pyrtl.select(c, 4, r1))
47+ r2.next <<= pyrtl.select(a, pyrtl.select(b, 3, r2), pyrtl.select(c, 5, 6))
48+ w <<= pyrtl.select(d, 7, 0)
49+ mem[0] <<= pyrtl.MemBlock.EnabledWrite(data=2, enable=a)
50+
51+ Conditional assignments are generally recommended over nested :func:`.select` statements
52+ because conditional assignments are easier to read and write.
53+
54+ -------------------------------
55+ Conditional Assignment Defaults
56+ -------------------------------
57+
58+ Every PyRTL wire, register, and memory must have a value in every cycle. PyRTL does not
59+ support "don't care" or ``X`` values. To satisfy this requirement, conditional
60+ assignment must assign some value to wires in :data:`conditional_assignment` blocks when
61+ a value is not specified. This can happen when:
62+
63+ #. A condition is ``True``, but no value is specified for a wire or register in that
64+ condition's ``with`` block. In the example above, no value is specified for ``r1`` in
65+ the :data:`otherwise` block.
66+ #. No conditions are ``True``, and there is no :data:`otherwise` block. In the example
67+ above, there is no :data:`otherwise` block to for the case when ``d`` is ``False``,
68+ so no value is specified for ``w`` when ``d`` is ``False``.
69+
70+ When this happens for a wire, ``0`` is assigned as a default value. See how a ``0``
71+ appears in the final ``select`` in the equivalent example above.
72+
73+ When this happens for a register, the register's current value is assigned as a default
74+ value. See how ``r1`` and ``r2`` appear within the ``select`` s in the first and second
75+ lines of the example above.
76+
77+ When this happens for a memory, the memory's write port is disabled. See how the example
78+ above uses a :class:`.EnabledWrite` to disable writes to ``mem[0]`` when ``a`` is
79+ ``False``.
80+
81+ These default values can be changed by passing a ``defaults`` dict to
82+ :data:`conditional_assignment`, as seen in this example::
83+
84+ # Most instructions advance the program counter (`pc`) by one instruction. A few
85+ # instructions change `pc` in special ways.
86+ pc = pyrtl.Register(bitwidth=32)
87+ instr = pyrtl.WireVector(bitwidth=32)
88+ res = pyrtl.WireVector(bitwidth=32)
4789
4890 op = instr[:7]
4991 ADD = 0b0110011
5092 JMP = 0b1101111
5193
52- with conditional_assignment(
53- defaults={
54- pc: pc + 1,
55- res: 0
56- }
57- ):
94+ # Use conditional_assignment's `defaults` to advance `pc` by one instruction by
95+ # default.
96+ with pyrtl.conditional_assignment(defaults={pc: pc + 1}):
5897 with op == ADD:
5998 res |= instr[15:20] + instr[20:25]
60- # pc will be updated to pc + 1
99+ # pc.next will be updated to pc + 1
61100 with op == JMP:
62101 pc.next |= pc + instr[7:]
63102 # res will be set to 0
64103
65- In addition to the conditional context, there is a helper function
66- :func:`~.currently_under_condition` which will test if the code where it is
67- called is currently elaborating hardware under a condition.
104+ .. WARNING::
105+ :data:`conditional_assignment` ``defaults`` are not supported for
106+ :class:`.MemBlock`.
107+
108+ -------------------------------------------
109+ The Conditional Assigment Operator (``|=``)
110+ -------------------------------------------
111+
112+ Conditional assignments are written with the ``|=`` operator, and not the usual ``<<=``
113+ operator.
114+
115+ * The ``|=`` operator is a *conditional* assignment. Conditional assignments can only be
116+ written in a :data:`conditional_assignment` block.
117+ * The ``<<=`` operator is an *unconditional* assignment, *even if* it is written in a
118+ :data:`conditional_assignment` block.
119+
120+ Consider this example::
121+
122+ w1 = pyrtl.WireVector()
123+ w2 = pyrtl.WireVector()
124+ with pyrtl.conditional_assignment:
125+ with a:
126+ w1 |= 1
127+ w2 <<= 2
128+
129+ Which is equivalent to::
130+
131+ w1 <<= pyrtl.select(a, 1, 0)
132+ w2 <<= 2
133+
134+ This behavior may seem undesirable, but consider this example::
135+
136+ def make_adder(x: pyrtl.WireVector) -> pyrtl.WireVector:
137+ output = pyrtl.WireVector(bitwidth=a.bitwidth + 1)
138+ output <<= x + 2
139+ return output
140+
141+ w = pyrtl.WireVector()
142+ with pyrtl.conditional_assignment:
143+ with a:
144+ w |= make_adder(b)
145+
146+ Which is equivalent to::
147+
148+ # The assignment to `output` in `make_adder` is unconditional.
149+ w <<= pyrtl.select(a, make_adder(b), 0)
150+
151+ In this example the ``<<=`` in ``make_adder`` should be unconditional, even though
152+ ``make_adder`` is called from a :data:`conditional_assignment`, because the top-level
153+ assignment to ``w`` is already conditional. Making the lower-level assignment to
154+ ``output`` conditional would not make sense, especially if ``output`` is used elsewhere
155+ in the circuit.
156+
157+ For more :data:`conditional_assignment` examples, see the state machine example in
158+ ``examples/example3-statemachine.py``.
68159
69160"""
70- # Access should be done through instances "conditional_update " and "otherwise",
71- # as described above, not through the classes themselves .
161+ # Use the objects "conditional_assignment " and "otherwise" as described above. The
162+ # classes below are internal implementation details .
72163
73164from .pyrtlexceptions import PyrtlError , PyrtlInternalError
74165from .wire import WireVector , Const , Register
82173
83174
84175def currently_under_condition ():
85- """ Returns True if execution is currently in the context of a ``_ConditionalAssignment.`` """
86- return _depth > 0
176+ """Returns `` True`` if execution is currently in the context of a
177+ :data:`conditional_assignment`.
87178
179+ """
180+ return _depth > 0
88181
89- # -----------------------------------------------------------------------
90- # conditional_assignment and otherwise, both visible in the pyrtl module, are defineded as
91- # instances (hopefully the only and unchanging instances) of the following two types.
92182
183+ # `conditional_assignment` and `otherwise`, both visible in the pyrtl module, are
184+ # defined as instances (hopefully the only and unchanging instances) of the following
185+ # two types.
93186class _ConditionalAssignment (object ):
94187 def __init__ (self ):
95188 self .defaults = {}
@@ -114,7 +207,6 @@ def __exit__(self, *exc_info):
114207
115208
116209class _Otherwise (object ):
117- """ Context providing functionality of PyRTL ``otherwise``. """
118210 def __enter__ (self ):
119211 _push_condition (otherwise )
120212
@@ -142,7 +234,19 @@ def _reset_conditional_state():
142234
143235_reset_conditional_state ()
144236conditional_assignment = _ConditionalAssignment ()
237+ """Context manager implementing PyRTL's ``conditional_assignment``.
238+
239+ :param dict defaults: Dictionary mapping from WireVector to its default value in this
240+ ``conditional_assignment`` block. ``defaults`` are not supported for
241+ :class:`.MemBlock`.
242+
243+ """
244+
145245otherwise = _Otherwise ()
246+ """Context manager implementing PyRTL's ``otherwise`` under
247+ :data:`conditional_assignment`.
248+
249+ """
146250
147251
148252# -----------------------------------------------------------------------
0 commit comments