@@ -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