@@ -61,5 +61,179 @@ Constants
6161Conditional Assignment
6262----------------------
6363
64- .. automodule :: pyrtl.conditional
65- :members:
64+ .. autodata :: pyrtl.conditional_assignment
65+
66+ :class: `WireVectors<.WireVector> `, :class: `Registers<.Register> `, and
67+ :class: `MemBlocks<.MemBlock> ` can be conditionally assigned values based on predicates.
68+
69+ Conditional assignments are written with `Python with statements
70+ <https://docs.python.org/3/reference/compound_stmts.html#with> `_, using two
71+ context managers:
72+
73+ #. :data: `.conditional_assignment `, which provides the framework for specifying
74+ conditional assignments.
75+ #. :data: `.otherwise `, which specifies the 'fall through' case.
76+
77+ Conditional assignments are easiest to understand with an example::
78+
79+ r1 = pyrtl.Register(bitwidth=8)
80+ r2 = pyrtl.Register(bitwidth=8)
81+ w = pyrtl.WireVector(bitwidth=8)
82+ mem = pyrtl.MemBlock(bitwidth=8, addrwidth=4)
83+
84+ a = pyrtl.Input(bitwidth=1)
85+ b = pyrtl.Input(bitwidth=1)
86+ c = pyrtl.Input(bitwidth=1)
87+ d = pyrtl.Input(bitwidth=1)
88+
89+ with pyrtl.conditional_assignment:
90+ with a:
91+ # Set when a is True.
92+ r1.next |= 1
93+ mem[0] |= 2
94+ with b:
95+ # Set when a and b are both True.
96+ r2.next |= 3
97+ with c:
98+ # Set when a is False and c is True.
99+ r1.next |= 4
100+ r2.next |= 5
101+ with pyrtl.otherwise:
102+ # Set when a and c are both False.
103+ r2.next |= 6
104+
105+ with d:
106+ # Set when d is True. A `with` block after an `otherwise` starts a new
107+ # set of conditional assignments.
108+ w |= 7
109+
110+ This :data: `.conditional_assignment ` is equivalent to::
111+
112+ r1.next <<= pyrtl.select(a, 1, pyrtl.select(c, 4, r1))
113+ r2.next <<= pyrtl.select(a, pyrtl.select(b, 3, r2), pyrtl.select(c, 5, 6))
114+ w <<= pyrtl.select(d, 7, 0)
115+ mem[0] <<= pyrtl.MemBlock.EnabledWrite(data=2, enable=a)
116+
117+ Conditional assignments are generally recommended over nested :func: `.select ` statements
118+ because conditional assignments are easier to read and write.
119+
120+ :data: `.conditional_assignment ` accepts an optional ``default `` argument that
121+ maps from :class: `.WireVector ` to its default value for the
122+ :data: `.conditional_assignment ` block. ``defaults `` are not supported for
123+ :class: `.MemBlock `. See :ref: `conditional_assignment_defaults ` for more details.
124+
125+ See `the state machine example
126+ <https://github.com/UCSBarchlab/PyRTL/blob/development/examples/example3-statemachine.py> `_
127+ for more examples of :data: `.conditional_assignment `.
128+
129+ .. autodata :: pyrtl.otherwise
130+
131+ Context manager implementing PyRTL's ``otherwise `` under :data: `.conditional_assignment `.
132+
133+ .. autofunction :: pyrtl.currently_under_condition
134+
135+ .. _conditional_assignment_defaults :
136+
137+ -------------------------------
138+ Conditional Assignment Defaults
139+ -------------------------------
140+
141+ Every PyRTL wire, register, and memory must have a value in every cycle. PyRTL does not
142+ support "don't care" or ``X `` values. To satisfy this requirement, conditional
143+ assignment must always assign a value to every wire in a :data: `.conditional_assignment `
144+ block, even if the :data: `.conditional_assignment ` does not specify a value. This can
145+ happen when:
146+
147+ #. A condition is ``True ``, but no value is specified for a wire or register in that
148+ condition's ``with `` block. In the example above, no value is specified for ``r1 `` in
149+ the :data: `.otherwise ` block.
150+ #. No conditions are ``True ``, and there is no :data: `.otherwise ` block. In the example
151+ above, there is no :data: `.otherwise ` block to for the case when ``d `` is ``False ``,
152+ so no value is specified for ``w `` when ``d `` is ``False ``.
153+
154+ When this happens for a wire, ``0 `` is assigned as a default value. See how a ``0 ``
155+ appears in the final :func: `.select ` in the equivalent example above.
156+
157+ When this happens for a register, the register's current value is assigned as a default
158+ value. See how ``r1 `` and ``r2 `` appear within the :func: `.select ` s in the first and second
159+ lines of the example above.
160+
161+ When this happens for a memory, the memory's write port is disabled. See how the example
162+ above uses a :class: `.EnabledWrite ` to disable writes to ``mem[0] `` when ``a `` is
163+ ``False ``.
164+
165+ These default values can be changed by passing a ``defaults `` dict to
166+ :data: `.conditional_assignment `, as seen in this example::
167+
168+ # Most instructions advance the program counter (`pc`) by one instruction. A few
169+ # instructions change `pc` in special ways.
170+ pc = pyrtl.Register(bitwidth=32)
171+ instr = pyrtl.WireVector(bitwidth=32)
172+ res = pyrtl.WireVector(bitwidth=32)
173+
174+ op = instr[:7]
175+ ADD = 0b0110011
176+ JMP = 0b1101111
177+
178+ # Use conditional_assignment's `defaults` to advance `pc` by one instruction by
179+ # default.
180+ with pyrtl.conditional_assignment(defaults={pc: pc + 1}):
181+ with op == ADD:
182+ res |= instr[15:20] + instr[20:25]
183+ # pc.next will be updated to pc + 1
184+ with op == JMP:
185+ pc.next |= pc + instr[7:]
186+ # res will be set to 0
187+
188+ .. WARNING ::
189+ :data: `.conditional_assignment ` ``defaults `` are not supported for
190+ :class: `.MemBlock `.
191+
192+ -------------------------------------------
193+ The Conditional Assigment Operator (``|= ``)
194+ -------------------------------------------
195+
196+ Conditional assignments are written with the ``|= `` operator, and not the usual ``<<= ``
197+ operator.
198+
199+ * The ``|= `` operator is a *conditional * assignment. Conditional assignments can only be
200+ written in a :data: `.conditional_assignment ` block.
201+ * The ``<<= `` operator is an *unconditional * assignment, *even if * it is written in a
202+ :data: `.conditional_assignment ` block.
203+
204+ Consider this example::
205+
206+ w1 = pyrtl.WireVector()
207+ w2 = pyrtl.WireVector()
208+ with pyrtl.conditional_assignment:
209+ with a:
210+ w1 |= 1
211+ w2 <<= 2
212+
213+ Which is equivalent to::
214+
215+ w1 <<= pyrtl.select(a, 1, 0)
216+ w2 <<= 2
217+
218+ This behavior may seem undesirable, but consider this example::
219+
220+ def make_adder(x: pyrtl.WireVector) -> pyrtl.WireVector:
221+ output = pyrtl.WireVector(bitwidth=a.bitwidth + 1)
222+ output <<= x + 2
223+ return output
224+
225+ w = pyrtl.WireVector()
226+ with pyrtl.conditional_assignment:
227+ with a:
228+ w |= make_adder(b)
229+
230+ Which is equivalent to::
231+
232+ # The assignment to `output` in `make_adder` is unconditional.
233+ w <<= pyrtl.select(a, make_adder(b), 0)
234+
235+ In this example the ``<<= `` in ``make_adder `` should be unconditional, even though
236+ ``make_adder `` is called from a :data: `.conditional_assignment `, because the top-level
237+ assignment to ``w `` is already conditional. Making the lower-level assignment to
238+ ``output `` conditional would not make sense, especially if ``output `` is used elsewhere
239+ in the circuit.
0 commit comments