Skip to content

Commit 3c4d133

Browse files
committed
Improve wire_struct documentation:
* Explain delayed assignment, where concatenated or component values are specified after construction. * Document the `_value` constructor argument
1 parent 3df3313 commit 3c4d133

1 file changed

Lines changed: 97 additions & 7 deletions

File tree

pyrtl/helperfuncs.py

Lines changed: 97 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1462,6 +1462,8 @@ def wire_struct(wire_struct_spec):
14621462
... high: 4
14631463
... low: 4
14641464
1465+
.. _wire_struct_construction:
1466+
14651467
Construction
14661468
------------
14671469
@@ -1470,22 +1472,17 @@ def wire_struct(wire_struct_spec):
14701472
14711473
1. Provide a driver for *each* component wire, for example::
14721474
1473-
>>> byte = Byte(high=0xA, low=0xB)
1475+
>>> byte = Byte(high=0xA, low=0xB)
14741476
14751477
Note how the component names (``high``, ``low``) are used as keyword args for the
14761478
constructor. Drivers must be provided for *all* components.
14771479
14781480
2. Provide a driver for the entire ``@wire_struct``, for example::
14791481
1480-
>>> byte = Byte(Byte=0xAB)
1482+
>>> byte = Byte(Byte=0xAB)
14811483
14821484
Note how the class name (``Byte``) is used as a keyword arg for the constructor.
14831485
1484-
If the class name is not known, the special name ``_value`` can be used instead::
1485-
1486-
>>> UnknownDynamicType = Byte
1487-
>>> unknown_dynamic_type = UnknownDynamicType(_value=0xAB)
1488-
14891486
Accessing Slices
14901487
----------------
14911488
@@ -1620,6 +1617,99 @@ class CacheLine:
16201617
16211618
No values are specified for ``input_byte`` because its value is not known until
16221619
simulation time.
1620+
1621+
Delayed Assignment
1622+
------------------
1623+
1624+
A ``@wire_struct``'s drivers can be specified later, after construction::
1625+
1626+
>>> conditional_concat = Byte(name="conditional_concat", high=0xa, low=None)
1627+
>>> with pyrtl.conditional_assignment:
1628+
... with input_byte == 0:
1629+
... conditional_concat.low |= 0xb
1630+
... with pyrtl.otherwise:
1631+
... conditional_concat.low |= 0xc
1632+
1633+
>>> sim = pyrtl.Simulation()
1634+
>>> sim.step({"input_byte": 0})
1635+
>>> hex(sim.inspect("conditional_concat"))
1636+
'0xab'
1637+
1638+
>>> sim.step({"input_byte": 1})
1639+
>>> hex(sim.inspect("conditional_concat"))
1640+
'0xac'
1641+
1642+
In the example above, ``conditional_concat``'s ``low`` component is initially set to
1643+
``None`` when constructed, and ``conditional_concat.low`` is later driven by a
1644+
:ref:`conditional_assignment`.
1645+
1646+
Similarly, a ``@wire_struct``'s concatenated value can be specified later::
1647+
1648+
>>> conditional_chop = Byte(name="conditional_chop", Byte=None)
1649+
>>> with pyrtl.conditional_assignment:
1650+
... with input_byte == 0:
1651+
... conditional_chop |= 0xde
1652+
... with pyrtl.otherwise:
1653+
... conditional_chop |= 0xdf
1654+
1655+
>>> sim = pyrtl.Simulation()
1656+
>>> sim.step({"input_byte": 0})
1657+
>>> hex(sim.inspect("conditional_chop.low"))
1658+
'0xe'
1659+
1660+
>>> sim.step({"input_byte": 1})
1661+
>>> hex(sim.inspect("conditional_chop.low"))
1662+
'0xf'
1663+
1664+
.. NOTE::
1665+
1666+
All delay-assigned values must be explicitly set to ``None`` when constructing
1667+
the ``@wire_struct``. Omitting a delay-assigned value's ``kwarg`` entirely will
1668+
raise a :class:`.PyrtlError`.
1669+
1670+
``@wire_struct`` operates in one of two modes, as described in
1671+
:ref:`wire_struct_construction`. The instance will either :func:`.concat`
1672+
several component values together, or :func:`chop` one concatenated value apart.
1673+
This mode is set during construction, so ``@wire_struct``'s constructor must
1674+
know whether the instance is expecting several component values, or one
1675+
concatenated value.
1676+
1677+
Generic Usage
1678+
-------------
1679+
1680+
.. doctest only::
1681+
1682+
>>> import pyrtl
1683+
>>> pyrtl.reset_working_block()
1684+
1685+
Functions can work with ``@wire_struct`` instances generically. For example, we can
1686+
define a function that accepts any ``@wire_struct`` and returns the same type of
1687+
``@wire_struct``, with all of the argument's components bitwise-inverted::
1688+
1689+
>>> def invert_all_components(output_name, any_wire_struct):
1690+
... # Retrieve the argument wire_struct's class.
1691+
... OutputClass = type(any_wire_struct)
1692+
... # Instantiate that class, specifying its full value.
1693+
... return OutputClass(name=output_name, _value=~any_wire_struct)
1694+
1695+
>>> input_byte = Byte(name="input_byte", concatenated_type=pyrtl.Input)
1696+
>>> output_byte = invert_all_components("output_byte", input_byte)
1697+
1698+
>>> sim = pyrtl.Simulation()
1699+
>>> sim.step({"input_byte": 0})
1700+
>>> hex(sim.inspect("output_byte"))
1701+
'0xff'
1702+
>>> hex(sim.inspect("output_byte.low"))
1703+
'0xf'
1704+
1705+
``invert_all_components`` uses ``type(any_wire_struct)`` to retrieve the argument
1706+
``@wire_struct``'s class, and instantiates that class for the function's return
1707+
value.
1708+
1709+
This uses the special constructor ``kwarg`` ``_value``, rather than the name of the
1710+
class, to specify the full value for the returned ``@wire_struct`` object. In this
1711+
example, we must use ``_value`` instead of the name of the class (``Byte``) because
1712+
``any_wire_struct`` might not be a ``Byte``.
16231713
"""
16241714
# Convert the decorated class' annotations (dict of attr_name: attr_value)
16251715
# to a list of _ComponentMetas.

0 commit comments

Comments
 (0)