Skip to content

Commit 7dfa485

Browse files
committed
Register code refactoring, no functional change:
- Move code from _next_ilshift to _Next.__ilshift__ - Move code from _next_ior to _Next.__ior__ - Merge _NextSetter into _Next - Minor documentation improvements This simplifies control flow and removes some unnecessary methods.
1 parent 51fe85b commit 7dfa485

File tree

2 files changed

+90
-73
lines changed

2 files changed

+90
-73
lines changed

docs/regmem.rst

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,6 @@ Registers and Memories
44
Registers
55
---------
66

7-
The class :class:`.Register` is derived from :class:`.WireVector`, and so can
8-
be used just like any other :class:`.WireVector`. Reading a register produces
9-
the stored value available in the current cycle. The stored value for the
10-
following cycle can be set by assigning to property
11-
:attr:`~pyrtl.wire.Register.next` with the
12-
``<<=`` (:meth:`~pyrtl.wire.WireVector.__ilshift__`) operator. Registers reset
13-
to zero by default, and reside in the same clock domain.
14-
157
.. autoclass:: pyrtl.wire.Register
168
:members:
179
:show-inheritance:

pyrtl/wire.py

Lines changed: 90 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -729,41 +729,96 @@ def __ior__(self, _):
729729

730730

731731
class Register(WireVector):
732-
"""A WireVector with a register state element embedded.
732+
"""A WireVector with an embedded register state element.
733733
734-
Registers only update their outputs on posedge of an implicit clock signal.
735-
The "value" in the current cycle can be accessed by just referencing the
736-
Register itself. To set the value for the next cycle (after the next
737-
posedge) you write to the property :attr:`~.Register.next` with the ``<<=``
738-
operator. For example, if you want to specify a counter it would look like:
739-
``a.next <<= a + 1``
734+
Registers only update their outputs on the rising edges of an implicit clock signal.
735+
The "value" in the current cycle can be accessed by referencing the Register itself.
736+
To set the value for the next cycle (after the next rising clock edge), set the
737+
:attr:`.Register.next` property with the ``<<=`` operator.
738+
739+
Registers reset to zero by default, and reside in the same clock domain.
740+
741+
Example::
742+
743+
counter = pyrtl.Register(bitwidth=8)
744+
counter.next <<= counter + 1
745+
746+
This builds a zero-initialized 8-bit counter. The second line sets the counter's
747+
value in the next cycle (``counter.next``) to the counter's value in the current
748+
cycle (``counter``), plus one.
740749
741750
"""
742751
_code = 'R'
743752

744-
# When the register is called as such: r.next <<= foo
745-
# the sequence of actions that happens is:
746-
# 1) The property .next is called to get the "value" of r.next
747-
# 2) The "value" is then passed to __ilshift__
753+
# When a register's next value is assigned, the following occurs:
748754
#
749-
# The resulting behavior should enforce the following:
750-
# r.next <<= 5 -- good
751-
# a <<= r -- good
752-
# r <<= 5 -- error
753-
# a <<= r.next -- error
754-
# r.next = 5 -- error
755-
755+
# 1. The register's `.next` property is retrieved. Register.next returns an instance
756+
# of Register._Next.
757+
# 2. __ilshift__ is invoked on the returned instance of Register._Next.
758+
#
759+
# So `reg.next <<= foo` effectively does the following:
760+
#
761+
# reg.next = Register._Next(reg)
762+
# reg.next.__ilshift__(reg, foo)
763+
#
764+
# The following behavior is expected:
765+
#
766+
# reg.next <<= 5 # good
767+
# a <<= reg # good
768+
# reg <<= 5 # error
769+
# a <<= reg.next # error
770+
# reg.next = 5 # error
756771
class _Next(object):
757-
""" This is the type returned by "r.next". """
772+
"""Type returned by the ``Register.next`` property.
758773
774+
This class allows unconditional assignments (``<<=``, ``__ilshift__``) and
775+
conditional assignments (``|=``, ``__ior__``) on ``Register.next``. Registers
776+
themselves do not support assignments, so ``Register.__ilshift__`` and
777+
``Register.__ior__`` throw errors.
778+
779+
``__ilshift__`` and ``__ior__`` must both return ``self`` because::
780+
781+
x <<= y
782+
783+
is equivalent to::
784+
785+
x = x.__ilshift__(y)
786+
787+
Note how ``__ilshift__``'s return value is assigned to ``x``, see
788+
https://docs.python.org/3/library/operator.html#in-place-operators
789+
790+
``__ilshift__`` and ``__ior__`` both return ``self`` and Register's @next.setter
791+
checks that Register.next is assigned to an instance of _Next.
792+
793+
"""
759794
def __init__(self, reg):
760795
self.reg = reg
761796

762797
def __ilshift__(self, other):
763-
return self.reg._next_ilshift(other)
798+
from .corecircuits import as_wires
799+
other = as_wires(other, bitwidth=self.reg.bitwidth)
800+
if self.reg.bitwidth is None:
801+
self.reg.bitwidth = other.bitwidth
802+
803+
if self.reg.reg_in is not None:
804+
raise PyrtlError('error, .next value should be set once and only once')
805+
self.reg._build(other)
806+
807+
return self
764808

765809
def __ior__(self, other):
766-
return self.reg._next_ior(other)
810+
from .conditional import _build
811+
from .corecircuits import as_wires
812+
other = as_wires(other, bitwidth=self.reg.bitwidth)
813+
if not self.reg.bitwidth:
814+
raise PyrtlError('Conditional assignment only defined on '
815+
'Registers with pre-defined bitwidths')
816+
817+
if self.reg.reg_in is not None:
818+
raise PyrtlError('error, .next value should be set once and only once')
819+
_build(self.reg, other)
820+
821+
return self
767822

768823
def __bool__(self):
769824
""" Use of a _next in a statement like "a or b" is forbidden."""
@@ -774,23 +829,17 @@ def __bool__(self):
774829

775830
__nonzero__ = __bool__ # for Python 2 and 3 compatibility
776831

777-
class _NextSetter(object):
778-
""" This is the type returned by __ilshift__ which r.next will be assigned. """
779-
780-
def __init__(self, rhs, is_conditional):
781-
self.rhs = rhs
782-
self.is_conditional = is_conditional
783-
784832
def __init__(self, bitwidth: int, name: str = '', reset_value: int = None,
785833
block: Block = None):
786834
"""Construct a register.
787835
788836
:param bitwidth: Number of bits to represent this register.
789-
:param name: The name of the wire. Must be unique. If none is provided,
790-
one will be autogenerated.
837+
:param name: The name of the register's current value (``reg``, not
838+
``reg.next``). Must be unique. If none is provided, one will be
839+
autogenerated.
791840
:param reset_value: Value to initialize this register to during
792841
simulation and in any code (e.g. Verilog) that is exported.
793-
Defaults to 0, but can be explicitly overridden at simulation time.
842+
Defaults to 0. Can be overridden at simulation time.
794843
:param block: The block under which the wire should be placed. Defaults
795844
to the working block.
796845
@@ -812,10 +861,7 @@ def __init__(self, bitwidth: int, name: str = '', reset_value: int = None,
812861

813862
@property
814863
def next(self):
815-
"""
816-
This property is the way to set what the WireVector will be the next
817-
cycle (aka, it is before the D-Latch)
818-
"""
864+
"""Sets the Register's value for the next cycle (it is before the D-Latch)."""
819865
return Register._Next(self)
820866

821867
def __ilshift__(self, other):
@@ -824,36 +870,15 @@ def __ilshift__(self, other):
824870
def __ior__(self, other):
825871
raise PyrtlError('error, you cannot set registers directly, net .next instead')
826872

827-
def _next_ilshift(self, other):
828-
from .corecircuits import as_wires
829-
other = as_wires(other, bitwidth=self.bitwidth)
830-
if self.bitwidth is None:
831-
self.bitwidth = other.bitwidth
832-
return Register._NextSetter(other, is_conditional=False)
833-
834-
def _next_ior(self, other):
835-
from .corecircuits import as_wires
836-
other = as_wires(other, bitwidth=self.bitwidth)
837-
if not self.bitwidth:
838-
raise PyrtlError('Conditional assignment only defined on '
839-
'Registers with pre-defined bitwidths')
840-
return Register._NextSetter(other, is_conditional=True)
841-
842873
@next.setter
843-
def next(self, nextsetter):
844-
from .conditional import _build
845-
if not isinstance(nextsetter, Register._NextSetter):
874+
def next(self, other):
875+
if not isinstance(other, Register._Next):
846876
raise PyrtlError('error, .next should be set with "<<=" or "|=" operators')
847-
elif self.reg_in is not None:
848-
raise PyrtlError('error, .next value should be set once and only once')
849-
elif nextsetter.is_conditional:
850-
_build(self, nextsetter.rhs)
851-
else:
852-
self._build(nextsetter.rhs)
853877

854878
def _build(self, next):
855-
# this actually builds the register which might be from directly setting
856-
# the property "next" or delayed when there is a conditional assignement
879+
# Actually build the register. This happens immediately when setting the `next`
880+
# property. Under conditional assignment, register build is delayed until the
881+
# conditional assignment is _finalized.
857882
self.reg_in = next
858883
net = LogicNet('r', None, args=(self.reg_in,), dests=(self,))
859884
working_block().add_net(net)
@@ -888,13 +913,13 @@ def __getattr__(self, name: str):
888913
def __setattr__(self, name, value):
889914
'''Forward all attribute assignments to the wrapped WireVector.
890915
891-
This is needed to make ``reg.next <<= foo`` work, because that
892-
expands to::
916+
This is needed to make ``reg.next <<= foo`` work, because that expands to::
893917
894918
reg.next = reg.next.__ilshift__(foo)
895919
896-
And this attribute assignment must be forwarded to the underlying
897-
Register.
920+
See https://docs.python.org/3/library/operator.html#in-place-operators
921+
922+
This attribute assignment must be forwarded to the underlying Register.
898923
899924
'''
900925
self.wire.__setattr__(name, value)

0 commit comments

Comments
 (0)