From 36195de3ef75d6528062df28bf8db8a44b201d16 Mon Sep 17 00:00:00 2001 From: Jeremy Lau <30300826+fdxmw@users.noreply.github.com> Date: Sun, 28 Jun 2026 17:54:02 -0700 Subject: [PATCH 1/4] `rtllib` documentation cleanup: * Add AES doctest examples. * Make AES method names more consistent. Forward calls to the deprecated old method names to the new method names. * Convert PRNG examples to doctest examples. Also: * Update PRNGs to use State Registers. * Include `render_trace` output directly in examples. * `render_trace`: Remove trailing whitespace from output. * Always use the `pyrtl.` package prefix in examples. * Add more documentation links to specific `examples/`. * Stop publishing documentation for: * half_adder, one_bit_add, ripple_add, ripple_half_add (undocumented) * testingutils (nondeterministic tests are generally a bad idea) --- docs/basic.rst | 8 +- docs/helpers.rst | 9 +- docs/rtllib.rst | 8 +- docs/screenshots/render_trace.png | Bin 13780 -> 0 bytes pyrtl/core.py | 6 + pyrtl/corecircuits.py | 6 +- pyrtl/helperfuncs.py | 146 +++++++++++++--------- pyrtl/importexport.py | 34 +++--- pyrtl/passes.py | 2 +- pyrtl/rtllib/aes.py | 195 +++++++++++++++++++++++------- pyrtl/rtllib/prngs.py | 173 +++++++++++++++++--------- pyrtl/simulation.py | 74 +++++++----- pyrtl/wire.py | 14 +-- tests/rtllib/test_aes.py | 10 ++ tests/rtllib/test_prngs.py | 10 ++ tests/test_simulation.py | 24 ++-- 16 files changed, 487 insertions(+), 232 deletions(-) delete mode 100644 docs/screenshots/render_trace.png diff --git a/docs/basic.rst b/docs/basic.rst index 04dd40d2..fcc0f2a9 100644 --- a/docs/basic.rst +++ b/docs/basic.rst @@ -122,9 +122,11 @@ maps from :class:`.WireVector` to its default value for the :data:`.conditional_assignment` block. ``defaults`` are not supported for :class:`.MemBlock`. See :ref:`conditional_assignment_defaults` for more details. -See `the state machine example -`_ -for more examples of :data:`.conditional_assignment`. +.. note:: + + See `example3-statemachine + `_ + for more :data:`.conditional_assignment` examples. .. autodata:: pyrtl.otherwise diff --git a/docs/helpers.rst b/docs/helpers.rst index cce16d48..ab2bae6d 100644 --- a/docs/helpers.rst +++ b/docs/helpers.rst @@ -23,11 +23,10 @@ many operators such as addition and multiplication). Coercion to WireVector ---------------------- -In PyRTL there is only one function in charge of coercing values into -:class:`WireVectors<.WireVector>`, and that is :func:`.as_wires`. This function -is called in almost all helper functions and classes to manage the mixture of -constants and :class:`WireVectors<.WireVector>` that naturally occur in -hardware development. +:func:`.as_wires` coerces values to :class:`WireVectors<.WireVector>`. Most +PyRTL helper functions and classes call :func:`.as_wires` on their inputs, to +manage the mixture of constants and :class:`WireVectors<.WireVector>` that +naturally occur in hardware development. See :ref:`wirevector_coercion` for examples and more details. diff --git a/docs/rtllib.rst b/docs/rtllib.rst index 6b8204f7..c684a6e3 100644 --- a/docs/rtllib.rst +++ b/docs/rtllib.rst @@ -16,6 +16,7 @@ Adders .. automodule:: pyrtl.rtllib.adders :members: :undoc-members: + :exclude-members: half_adder, one_bit_add, ripple_add, ripple_half_add Multipliers ----------- @@ -55,10 +56,3 @@ AES-128 .. autoclass:: pyrtl.rtllib.aes.AES :members: - -Testing Utilities ------------------ - -.. automodule:: pyrtl.rtllib.testingutils - :members: - :exclude-members: generate_in_wire_and_values, sim_and_ret_out, sim_and_ret_outws, sim_multicycle, multi_sim_multicycle diff --git a/docs/screenshots/render_trace.png b/docs/screenshots/render_trace.png deleted file mode 100644 index 347734faf00b3026a683f79582f05b45d0a5d250..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 13780 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

jGhm*fsN~)^(SFc_jZhq>C5;Pozs6id6 z13?xrBNg+6yd-E6I>maIY;9M4-gL~W7*CT6UAVOytb%^S=drF85*~gXJ{D*@)TzR1 zRUrB%t^Rljko#A|-`7a_8ndBlqlz^IN|2>Pq068B{rLPDm~`oP-Iyrh7}WT5q=}k( zDmS|n85NaTRu&7+7#U`r;LPk+uP323ga4X6d>C=Gw|bqM`*+n&1=LjFojbpOh^F&#Az{Xond9LNc;YM!7KFGjck}Z|13qiHSsh>&UDk zLluzBQBk>aW-2$n=zD(Zjf|)3!Ysu>X=7oxH^c}#J3FFw)?DZGL$t23(eiM6-eY|) z^C66K0WgHZf{xUI8z#MrAi#*M-#G=Y4+^E*&$dW_{dx(Bi;I(e{IS7f<5~>86h6Ys z$7fe=N)Lx6?6Dr-wg|ohXpW+R>kclkCfA~1u)_ry`vE=Jvk(aW4>vr4Rn%jB|EnuR zA-pc_p##8ikXa67Y*#UErT~;g2Qu-pj=!ud8p^GLd!2($NBmfKG`a{%5nXKf%a?>e zaf14mnV=xSuvCV(Uee+5@dVYD^!Q>5INab+!>#XB+S}+Z3-8swgrHx9qCeH(aRVZL zeeACG2nI3szlL_77Ru0OhVeE=#-PvnhvUqQh%=bAamyfDE3FCb0z%~EZM%ba-iNo_ z#Fo(NS)Q*#gVrB?O`@0%Djip`02wNQL4bbGz#x@g_I-wK~?$*j5omBN52_ zepk64QHFa8oL2)>0>y3LE=uVL3JS(ek{=Bz0fvw?EG>TZ39*vrB72jT4SXI?rY&9Z0&=?)gJxZg3U^ucl% z(3jrRDQ$_tQ$XAi;H+H-{{&odS(wI@m%8@m5*~AoENWnDe@z{{dX5Ip_c6*UDnt|% z5eYn|y^rWYV(J&PbgBLE+|tet2h8P_3ka(lR{Y#{OG`^Izexz76yO@*8OR^>A~uR1}5YSEliVnB?ybJ+g)V#hr41P)1E&MA8QoL=Zl& zj##X>6Le&u@;%e?K_W}Z!_LMlk0{>nO{ zbIuz#D!^6)!JOYR6{X)e3N=o+^=1K(S9!}t^ps6YGj2 z&HZYUl1sG;@oiH~Pfkt_0((zRc2VJ{dXUG_=SWpuQ{bISw?EGV!mF`P#E z4Y?%mDvf%lTKvR-6J6gqzJGHNMw32%490?)gqRp$`lGh1LRrEpxq z2l1-DHb(>K05wGfDFt&mybp&NghfSB`X#u`9WZ^e{i}BNS;u@}_u_o(#aUU;Odhxx zFkX(CJP-zh9H7`|(9qG7{>~I?7uo-C zE6dHrgm#z$LzPC{pI@fNXG$t`!s(w&&hY?o`F|hu;{$=MqB?13v)M?PrLoogD|@ zR>nV6$>K1UK!c8XH4r9O?%ludfHz{04RXI(DPcy#^G9$qdeCWJ%U`7v*E4G#&3m9C zu7ja&3}Y<3yu2@cRD*CxSTn#E!@{SL1}zoF+CSc#Hwp#=03-yI);`RC7!Zm_b?c5* z)FO07gYxQv$Xv?3C4 zE)Gr?Y8o6YN63<;)zw9aEEvYZg0DUL%3(q;=^FvG5~3y(Oe7Thf;Vgf&IP=R4?7#b z_6>&dhhUxqS19=fOegX#Lz|leu&dYND*_TUgX>f8YH5_*46lLb44WbijPdgCLc-cs zSllTvnCh4)0BWF2LIvxB7=&qG#wUU7_CXBN9z_%2yNwiRvK81KZhXnX6_=1eO=SUH z20aNimzcP57*55{s0E; z%_fTV`5RTD7Px4-^y^U;Bxr^GEYkW}LGQ8cEZ~POC|q8#mzDnx;2nw*^EY=TFK|UH z6dQot`W~luU_e1yS~{D8L}A9hC9v~;maZ=8YJhr*HW+jvx72|H0fo}|Dai-9kCHWD z{%5I%AX1=9U;Fu{{9SCj)AOTM`X;X(G>9Vgdd5+y)LEG{c91!(S%y^O|B*{20?ug^ z6cqZS0}7dL-HNa8Zeav#Zo6lz7y7gb#2%!EL0UweQ3@1nOmH5-@=vhNj9UfTb~ d`_ + for ``debug_mode`` examples. + :param debug: Optional boolean parameter to which the debug mode will be set. """ global debug_mode diff --git a/pyrtl/corecircuits.py b/pyrtl/corecircuits.py index 1da8eee3..cdb156e7 100644 --- a/pyrtl/corecircuits.py +++ b/pyrtl/corecircuits.py @@ -717,7 +717,7 @@ def match_bitwidth(*args: WireVector, signed: bool = False) -> tuple[WireVector] >>> a = pyrtl.Const(-1, name="a_short", signed=True, bitwidth=2) >>> b = pyrtl.Const(-3, name="b", signed=True, bitwidth=4) - >>> a, b = match_bitwidth(a, b, signed=True) + >>> a, b = pyrtl.match_bitwidth(a, b, signed=True) >>> a.name = "a_long" >>> a.bitwidth, b.bitwidth (4, 4) @@ -768,8 +768,8 @@ def as_wires( :class:`Const` :class:`WireVector`). See :ref:`wirevector_coercion`. An example:: >>> def make_my_hardware(a, b): - ... a = as_wires(a) - ... b = as_wires(b) + ... a = pyrtl.as_wires(a) + ... b = pyrtl.as_wires(b) ... return (a + b) & 0xf >>> input = pyrtl.Input(name="input", bitwidth=8) diff --git a/pyrtl/helperfuncs.py b/pyrtl/helperfuncs.py index 1bc5cd6d..cbbe270c 100644 --- a/pyrtl/helperfuncs.py +++ b/pyrtl/helperfuncs.py @@ -50,22 +50,28 @@ def probe(w: WireVector, name: str | None = None) -> WireVector: could be rewritten as:: - y <<= probe(x)[0:3] + 4 + y <<= pyrtl.probe(x)[0:3] + 4 to give visibility into both the origin of ``x`` (including the line that :class:`WireVector` was originally created) and the run-time values of ``x`` (which will be named and thus show up by default in a trace). Likewise:: - y <<= probe(x[0:3]) + 4 - y <<= probe(x[0:3] + 4) - probe(y) <<= x[0:3] + 4 + y <<= pyrtl.probe(x[0:3]) + 4 + y <<= pyrtl.probe(x[0:3] + 4) + pyrtl.probe(y) <<= x[0:3] + 4 are all valid uses of ``probe``. .. note:: - ``probe`` actually adds an :class:`Output` wire to the :ref:`working_block` of - ``w``, which can confuse various post-processing transforms such as + See `example4-debuggingtools + `_ + for more ``probe`` examples. + + .. note:: + + ``probe`` actually adds an :class:`Output` wire to the :ref:`working_block`, + which can confuse various post-processing transforms such as :func:`output_to_verilog`. :param w: :class:`WireVector` from which to get info @@ -184,13 +190,18 @@ def log2(integer_val: int) -> int: Useful when checking that powers of 2 are provided as function inputs. + .. doctest only:: + + >>> import pyrtl + >>> pyrtl.reset_working_block() + Examples:: - >>> log2(2) + >>> pyrtl.log2(2) 1 - >>> log2(256) + >>> pyrtl.log2(256) 8 - >>> log2(100) + >>> pyrtl.log2(100) Traceback (most recent call last): ... pyrtl.pyrtlexceptions.PyrtlError: this function can only take even powers of 2 @@ -233,20 +244,20 @@ def truncate( Examples:: - >>> truncate(0b101_001, bitwidth=3) + >>> pyrtl.truncate(0b101_001, bitwidth=3) 1 - >>> bin(truncate(0b111_101, bitwidth=3)) + >>> bin(pyrtl.truncate(0b111_101, bitwidth=3)) '0b101' >>> # -1 is 0b1111111... with the number of 1-bits equal to the bitwidth. Python >>> # ints are arbitrary-precision, so this can produce any number of 1-bits. - >>> bin(truncate(-1, bitwidth=3)) + >>> bin(pyrtl.truncate(-1, bitwidth=3)) '0b111' >>> input = pyrtl.Input(name="input", bitwidth=8) - >>> output = truncate(input, bitwidth=4) + >>> output = pyrtl.truncate(input, bitwidth=4) >>> output.name = "output" >>> output.bitwidth 4 @@ -740,31 +751,37 @@ def wirevector_list( def val_to_signed_integer(value: int, bitwidth: int) -> int: """Return ``value`` interpreted as a two's complement signed integer. + .. doctest only:: + + >>> import os + >>> import pyrtl + >>> os.environ["PYRTL_RENDERER"] = "cp437" + >>> pyrtl.reset_working_block() + Reinterpret an unsigned integer (not a :class:`WireVector`!) as a signed integer. This is useful for printing and interpreting two's complement values:: - >>> val_to_signed_integer(0xff, bitwidth=8) + >>> pyrtl.val_to_signed_integer(0xff, bitwidth=8) -1 ``val_to_signed_integer`` can also be used as an ``repr_func`` for - :meth:`SimulationTrace.render_trace`, to display signed integers in traces:: + :meth:`~SimulationTrace.render_trace`, to display signed integers in traces:: - bitwidth = 3 - counter = Register(name='counter', bitwidth=bitwidth) - counter.next <<= counter + 1 - sim = Simulation() - sim.step_multiple(nsteps=2 ** bitwidth) + >>> bitwidth = 3 + >>> counter = pyrtl.Register(name="counter", bitwidth=bitwidth) + >>> counter.next <<= counter + 1 - # Generates a trace like: - # │0 │1 │2 │3 │4 │5 │6 │7 - # - # counter ──┤1 │2 │3 │-4│-3│-2│-1 - sim.tracer.render_trace(repr_func=val_to_signed_integer) + >>> sim = pyrtl.Simulation() + >>> sim.step_multiple(nsteps=2 ** bitwidth) + >>> sim.tracer.render_trace(repr_func=val_to_signed_integer) + │0 │1 │2 │3 │4 │5 │6 │7 + + counter ──┤1 │2 │3 │-4│-3│-2│-1 :func:`infer_val_and_bitwidth` performs the opposite conversion:: - >>> integer = val_to_signed_integer(0xff, bitwidth=8) - >>> hex(infer_val_and_bitwidth(integer, bitwidth=8).value) + >>> integer = pyrtl.val_to_signed_integer(0xff, bitwidth=8) + >>> hex(pyrtl.infer_val_and_bitwidth(integer, bitwidth=8).value) '0xff' :param value: A Python integer holding the value to convert. @@ -791,32 +808,37 @@ def val_to_signed_integer(value: int, bitwidth: int) -> int: def formatted_str_to_val(data: str, format: str, enum_set=None) -> int: """Return an unsigned integer representation of ``data`` in a specified ``format``. + .. doctest only:: + + >>> import pyrtl + >>> pyrtl.reset_working_block() + Given a string (not a :class:`WireVector`!) convert that to an unsigned integer ready for input to the simulation environment. This helps deal with signed/unsigned numbers (simulation assumes the values have been converted via two's complement already), but it also takes hex, binary, and enum types as inputs. It is easiest to see how it works with some examples:: - >>> formatted_str_to_val('2', 's3') + >>> pyrtl.formatted_str_to_val('2', 's3') 2 - >>> bin(formatted_str_to_val('-1', 's3')) + >>> bin(pyrtl.formatted_str_to_val('-1', 's3')) '0b111' - >>> bin(formatted_str_to_val('101', 'b3')) + >>> bin(pyrtl.formatted_str_to_val('101', 'b3')) '0b101' - >>> formatted_str_to_val('5', 'u3') + >>> pyrtl.formatted_str_to_val('5', 'u3') 5 - >>> bin(formatted_str_to_val('-3', 's3')) + >>> bin(pyrtl.formatted_str_to_val('-3', 's3')) '0b101' - >>> formatted_str_to_val('a', 'x3') + >>> pyrtl.formatted_str_to_val('a', 'x3') 10 >>> from enum import IntEnum >>> class Ctl(IntEnum): ... ADD = 5 ... SUB = 12 - >>> formatted_str_to_val('ADD', 'e3/Ctl', [Ctl]) + >>> pyrtl.formatted_str_to_val('ADD', 'e3/Ctl', [Ctl]) 5 - >>> formatted_str_to_val('SUB', 'e3/Ctl', [Ctl]) + >>> pyrtl.formatted_str_to_val('SUB', 'e3/Ctl', [Ctl]) 12 :func:`val_to_formatted_str` performs the opposite conversion. @@ -859,32 +881,37 @@ def formatted_str_to_val(data: str, format: str, enum_set=None) -> int: def val_to_formatted_str(val: int, format: str, enum_set=None) -> str: """Return a string representation of the value given format specified. + .. doctest only:: + + >>> import pyrtl + >>> pyrtl.reset_working_block() + Given an unsigned integer (not a :class:`WireVector`!) convert that to a human-readable string. This helps deal with signed/unsigned numbers (simulation operates on values that have been converted via two's complement), but it also generates hex, binary, and enum types as outputs. It is easiest to see how it works with some examples:: - >>> val_to_formatted_str(2, 's3') + >>> pyrtl.val_to_formatted_str(2, 's3') '2' - >>> val_to_formatted_str(7, 's3') + >>> pyrtl.val_to_formatted_str(7, 's3') '-1' - >>> val_to_formatted_str(5, 'b3') + >>> pyrtl.val_to_formatted_str(5, 'b3') '101' - >>> val_to_formatted_str(5, 'u3') + >>> pyrtl.val_to_formatted_str(5, 'u3') '5' - >>> val_to_formatted_str(5, 's3') + >>> pyrtl.val_to_formatted_str(5, 's3') '-3' - >>> val_to_formatted_str(10, 'x3') + >>> pyrtl.val_to_formatted_str(10, 'x3') 'a' >>> from enum import IntEnum >>> class Ctl(IntEnum): ... ADD = 5 ... SUB = 12 - >>> val_to_formatted_str(5, 'e3/Ctl', [Ctl]) + >>> pyrtl.val_to_formatted_str(5, 'e3/Ctl', [Ctl]) 'ADD' - >>> val_to_formatted_str(12, 'e3/Ctl', [Ctl]) + >>> pyrtl.val_to_formatted_str(12, 'e3/Ctl', [Ctl]) 'SUB' :func:`formatted_str_to_val` performs the opposite conversion. @@ -934,6 +961,11 @@ def infer_val_and_bitwidth( ) -> ValueBitwidthTuple: """Return a ``(value, bitwidth)`` :class:`tuple` inferred from the specified input. + .. doctest only:: + + >>> import pyrtl + >>> pyrtl.reset_working_block() + Given a boolean, integer, or Verilog-style string constant, this function returns a :class:`ValueBitwidthTuple` ``(value, bitwidth)`` which are inferred from the specified ``rawinput``. If ``signed`` is ``True``, bits will be included to ensure a @@ -941,30 +973,30 @@ def infer_val_and_bitwidth( unsigned representation. Error checks are performed that determine if the bitwidths specified are sufficient and appropriate for the values specified. Examples:: - >>> infer_val_and_bitwidth(2, bitwidth=5) + >>> pyrtl.infer_val_and_bitwidth(2, bitwidth=5) ValueBitwidthTuple(value=2, bitwidth=5) >>> # Infer bitwidth from value. - >>> infer_val_and_bitwidth(3) + >>> pyrtl.infer_val_and_bitwidth(3) ValueBitwidthTuple(value=3, bitwidth=2) - >>> infer_val_and_bitwidth(3).bitwidth + >>> pyrtl.infer_val_and_bitwidth(3).bitwidth 2 >>> # Signed values need an additional sign bit. - >>> infer_val_and_bitwidth(3, signed=True) + >>> pyrtl.infer_val_and_bitwidth(3, signed=True) ValueBitwidthTuple(value=3, bitwidth=3) - >>> val, bitwidth = infer_val_and_bitwidth(-1, bitwidth=3) + >>> val, bitwidth = pyrtl.infer_val_and_bitwidth(-1, bitwidth=3) >>> (bin(val), bitwidth) ('0b111', 3) - >>> infer_val_and_bitwidth("5'd12") + >>> pyrtl.infer_val_and_bitwidth("5'd12") ValueBitwidthTuple(value=12, bitwidth=5) :func:`val_to_signed_integer` performs the opposite conversion:: - >>> val, bitwidth = infer_val_and_bitwidth(-1, bitwidth=3) - >>> val_to_signed_integer(val, bitwidth) + >>> val, bitwidth = pyrtl.infer_val_and_bitwidth(-1, bitwidth=3) + >>> pyrtl.val_to_signed_integer(val, bitwidth) -1 :param rawinput: a bool, int, or Verilog-style string constant @@ -1479,7 +1511,7 @@ def wire_struct(wire_struct_spec): The example ``Byte`` ``@wire_struct`` can be defined as:: - >>> @wire_struct + >>> @pyrtl.wire_struct ... class Byte: ... high: 4 ... low: 4 @@ -1970,7 +2002,7 @@ def wire_matrix(component_schema, size: int, class_name: str | None = None): An example 32-bit ``Word`` ``wire_matrix``, which represents a group of four bytes, can be defined as:: - >>> Word = wire_matrix(component_schema=8, size=4, class_name="Word") + >>> Word = pyrtl.wire_matrix(component_schema=8, size=4, class_name="Word") .. NOTE:: @@ -2045,8 +2077,10 @@ def wire_matrix(component_schema, size: int, class_name: str | None = None): ``wire_matrix`` can be composed with itself and :func:`wire_struct`. For example, we can define some multi-dimensional byte arrays:: - Array1D = wire_matrix(component_schema=8, size=2, class_name="Array1D") - Array2D = wire_matrix(component_schema=Array1D, size=2, class_name="Array2D") + Array1D = pyrtl.wire_matrix(component_schema=8, size=2, class_name="Array1D") + Array2D = pyrtl.wire_matrix(component_schema=Array1D, + size=2, + class_name="Array2D") Drivers must be specified for all components, but they can be specified at any level. All these examples construct an equivalent ``wire_matrix``:: @@ -2068,7 +2102,7 @@ def wire_matrix(component_schema, size: int, class_name: str | None = None): When ``wire_matrix`` is composed with :func:`wire_struct`, components can be accessed by combining the ``[]`` and ``.`` operators:: - @wire_struct + @pyrtl.wire_struct class Byte: high: 4 low: 4 diff --git a/pyrtl/importexport.py b/pyrtl/importexport.py index fe1979e9..ed9375bf 100644 --- a/pyrtl/importexport.py +++ b/pyrtl/importexport.py @@ -659,23 +659,29 @@ def input_from_verilog( to try and produce BLIF yourself. Then you can import BLIF directly via :func:`input_from_blif`. + .. doctest only:: + + >>> import pyrtl + >>> pyrtl.reset_working_block() + Example:: - verilog = ''' - module toplevel(clk, a, b, sum); - input clk; - input[2:0] a; - input[2:0] b; - output[3:0] sum; - assign sum = (a + b); - endmodule - ''' + >>> verilog = ''' + ... module toplevel(clk, a, b, sum); + ... input clk; + ... input[2:0] a; + ... input[2:0] b; + ... output[3:0] sum; + ... assign sum = (a + b); + ... endmodule + ... ''' - pyrtl.input_from_verilog(verilog) + >>> pyrtl.input_from_verilog(verilog) - sim = pyrtl.Simulation() - sim.step({"a": 1, "b": 2}) - sim.inspect("sum") + >>> sim = pyrtl.Simulation() + >>> sim.step({"a": 1, "b": 2}) + >>> sim.inspect("sum") + 3 :param verilog: An open Verilog file to read, or a string containing Verilog data. :param clock_name: The name of the clock (defaults to ``"clk"``). @@ -1488,7 +1494,7 @@ def output_to_verilog( :attr:`~.Register.reset_value`. When this argument is ``True``, this :class:`Register`:: - Register(name="foo", bitwidth=8, reset_value=4) + pyrtl.Register(name="foo", bitwidth=8, reset_value=4) generates this Verilog: diff --git a/pyrtl/passes.py b/pyrtl/passes.py index caf8dea4..c69ef944 100644 --- a/pyrtl/passes.py +++ b/pyrtl/passes.py @@ -685,7 +685,7 @@ def synthesize( which is identical in function but uses only single bit gates and excludes many of the more complicated :class:`LogicNet` primitives. The new block should consist *almost* exclusively of ``w``, ``&``, ``\\|``, ``^``, and ``~`` - :attr:`ops`, and sequential elements of :class`Registers`, + :attr:`ops`, and sequential elements of :class:`Registers`, which are one bit as well. The two exceptions are for :class:`Inputs` and :class:`Outputs`, to diff --git a/pyrtl/rtllib/aes.py b/pyrtl/rtllib/aes.py index d7620664..6a9c6d7e 100644 --- a/pyrtl/rtllib/aes.py +++ b/pyrtl/rtllib/aes.py @@ -13,49 +13,49 @@ class AES: - """A class for building a PyRTL AES circuit. - - Currently this class only supports 128 bit AES encryption/decryption. - - Example:: - - import pyrtl - from pyrtl.rtllib.aes import AES - - aes = AES() - plaintext = pyrtl.Input(bitwidth=128, name='aes_plaintext') - key = pyrtl.Input(bitwidth=128, name='aes_key') - aes_ciphertext = pyrtl.Output(bitwidth=128, name='aes_ciphertext') - reset = pyrtl.Input(1, name='reset') - ready = pyrtl.Output(1, name='ready') - - ready_out, aes_cipher = aes.encrypt_state_m(plaintext, key, reset) - ready <<= ready_out - aes_ciphertext <<= aes_cipher - - sim = pyrtl.Simulation() - sim.step ({ - 'aes_plaintext': 0x00112233445566778899aabbccddeeff, - 'aes_key': 0x000102030405060708090a0b0c0d0e0f, - 'reset': 1 - }) - for cycle in range(1,10): - sim.step ({ - 'aes_plaintext': 0x00112233445566778899aabbccddeeff, - 'aes_key': 0x000102030405060708090a0b0c0d0e0f, - 'reset': 0 - }) - sim.tracer.render_trace(symbol_len=40, segment_size=1) + """Builds `AES `_ + encryption and decryption circuits. + + Currently this class only supports 128-bit AES encryption and decryption, with + single-cycle and multi-cycle implementations. """ def __init__(self): self.memories_built = False self._key_len = 128 - def encryption( + def single_cycle_encrypt( self, plaintext: pyrtl.WireVector, key: pyrtl.WireVector ) -> pyrtl.WireVector: - """Builds a single cycle AES Encryption circuit. + """Build a single-cycle AES encryption circuit. + + See also :meth:`~AES.multi_cycle_encrypt`, which builds a multi-cycle AES + encryption circuit. + + .. doctest only:: + + >>> import pyrtl + >>> pyrtl.reset_working_block() + + Example:: + + >>> plaintext = pyrtl.Input(name="plaintext", bitwidth=128) + >>> key = pyrtl.Input(name="key", bitwidth=128) + >>> ciphertext = pyrtl.Output(name="ciphertext", bitwidth=128) + + >>> aes = pyrtl.rtllib.aes.AES() + >>> ciphertext <<= aes.single_cycle_encrypt(plaintext, key) + + >>> plaintext_value = 0x112233445566778899aabbccddeeff + >>> key_value = 0x102030405060708090a0b0c0d0e0f + + >>> sim = pyrtl.Simulation() + >>> sim.step({"plaintext": plaintext_value, "key": key_value}) + >>> sim.inspect("ciphertext") + 140591190147677442632770771134392354138 + + The ``ciphertext`` produced by this example should match the ``ciphertext`` + produced by the :meth:`~AES.multi_cycle_encrypt` example. :param plaintext: Text to encrypt. :param key: AES key to use to encrypt. @@ -80,13 +80,51 @@ def encryption( t = self._add_round_key(t, key_list[round]) return t - def encrypt_state_m( + def multi_cycle_encrypt( self, plaintext_in: pyrtl.WireVector, key_in: pyrtl.WireVector, reset: pyrtl.WireVector, ) -> tuple[pyrtl.WireVector, pyrtl.WireVector]: - """Builds a multiple cycle AES Encryption state machine circuit. + """Build a multi-cycle AES encryption circuit. + + See also :meth:`~AES.single_cycle_encrypt`, which builds a single-cycle AES + encryption circuit. + + .. doctest only:: + + >>> import pyrtl + >>> pyrtl.reset_working_block() + + Example:: + + >>> plaintext = pyrtl.Input(name="plaintext", bitwidth=128) + >>> key = pyrtl.Input(name="key", bitwidth=128) + >>> reset = pyrtl.Input(name="reset", bitwidth=1) + >>> ready = pyrtl.Output(name="ready", bitwidth=1) + >>> ciphertext = pyrtl.Output(name="ciphertext", bitwidth=128) + + >>> aes = pyrtl.rtllib.aes.AES() + >>> ready_, ciphertext_ = aes.multi_cycle_encrypt(plaintext, key, reset) + >>> ready <<= ready_ + >>> ciphertext <<= ciphertext_ + + >>> plaintext_value = 0x112233445566778899aabbccddeeff + >>> key_value = 0x102030405060708090a0b0c0d0e0f + + >>> sim = pyrtl.Simulation() + >>> sim.step({ + ... "plaintext": plaintext_value, + ... "key": key_value, + ... "reset": True + ... }) + >>> while not sim.inspect("ready"): + ... sim.step({"plaintext": 0, "key": 0, "reset": False}) + >>> sim.inspect("ciphertext") + 140591190147677442632770771134392354138 + + The ``ciphertext`` produced by this example should match the ``ciphertext`` + produced by the :meth:`~AES.single_cycle_encrypt` example. :param plaintext: Text to encrypt. :param key: AES key to use to encrypt. @@ -138,10 +176,36 @@ def encrypt_state_m( ready = counter == 10 return ready, plain_text - def decryption( + def single_cycle_decrypt( self, ciphertext: pyrtl.WireVector, key: pyrtl.WireVector ) -> pyrtl.WireVector: - """Builds a single cycle AES Decryption circuit. + """Build a single-cycle AES decryption circuit. + + See also :meth:`~AES.multi_cycle_decrypt`, which builds a multi-cycle AES + decryption circuit. + + .. doctest only:: + + >>> import pyrtl + >>> pyrtl.reset_working_block() + + Example that decrypts the ciphertext from the :meth:`~AES.single_cycle_encrypt` + example:: + + >>> ciphertext = pyrtl.Input(name="ciphertext", bitwidth=128) + >>> key = pyrtl.Input(name="key", bitwidth=128) + >>> plaintext = pyrtl.Output(name="plaintext", bitwidth=128) + + >>> aes = pyrtl.rtllib.aes.AES() + >>> plaintext <<= aes.single_cycle_decrypt(ciphertext, key) + + >>> ciphertext_value = 140591190147677442632770771134392354138 + >>> key_value = 0x102030405060708090a0b0c0d0e0f + + >>> sim = pyrtl.Simulation() + >>> sim.step({"ciphertext": ciphertext_value, "key": key_value}) + >>> print(hex(sim.inspect("plaintext"))) + 0x112233445566778899aabbccddeeff :param ciphertext: Data to decrypt. :param key: AES key to use to encrypt (AES is symmetric). @@ -166,13 +230,49 @@ def decryption( return t - def decryption_statem( + def multi_cycle_decrypt( self, ciphertext_in: pyrtl.WireVector, key_in: pyrtl.WireVector, reset: pyrtl.WireVector, ) -> tuple[pyrtl.WireVector, pyrtl.WireVector]: - """Builds a multiple cycle AES Decryption state machine circuit. + """Build a multi-cycle AES decryption circuit. + + See also :meth:`~AES.single_cycle_decrypt`, which builds a single-cycle AES + decryption circuit. + + .. doctest only:: + + >>> import pyrtl + >>> pyrtl.reset_working_block() + + Example that decrypts the ciphertext from the :meth:`~AES.single_cycle_encrypt` + example:: + + >>> ciphertext = pyrtl.Input(name="ciphertext", bitwidth=128) + >>> key = pyrtl.Input(name="key", bitwidth=128) + >>> reset = pyrtl.Input(name="reset", bitwidth=1) + >>> ready = pyrtl.Output(name="ready", bitwidth=1) + >>> plaintext = pyrtl.Output(name="plaintext", bitwidth=128) + + >>> aes = pyrtl.rtllib.aes.AES() + >>> ready_, plaintext_ = aes.multi_cycle_decrypt(ciphertext, key, reset) + >>> ready <<= ready_ + >>> plaintext <<= plaintext_ + + >>> ciphertext_value = 140591190147677442632770771134392354138 + >>> key_value = 0x102030405060708090a0b0c0d0e0f + + >>> sim = pyrtl.Simulation() + >>> sim.step({ + ... "ciphertext": ciphertext_value, + ... "key": key_value, + ... "reset": True + ... }) + >>> while not sim.inspect("ready"): + ... sim.step({"ciphertext": 0, "key": 0, "reset": False}) + >>> print(hex(sim.inspect("plaintext"))) + 0x112233445566778899aabbccddeeff :param ciphertext: Data to decrypt. :param key: AES key to use to encrypt (AES is symmetric). @@ -230,6 +330,19 @@ def decryption_statem( ready = counter == 10 return ready, cipher_text + # Aliases for deprecated method names. + def encryption(self, *args, **kwargs): + return self.single_cycle_encrypt(*args, **kwargs) + + def encrypt_state_m(self, *args, **kwargs): + return self.multi_cycle_encrypt(*args, **kwargs) + + def decryption(self, *args, **kwargs): + return self.single_cycle_decrypt(*args, **kwargs) + + def decryption_statem(self, *args, **kwargs): + return self.multi_cycle_decrypt(*args, **kwargs) + def _key_gen(self, key): keys = [key] for enc_round in range(10): diff --git a/pyrtl/rtllib/prngs.py b/pyrtl/rtllib/prngs.py index 447c33be..51395e42 100644 --- a/pyrtl/rtllib/prngs.py +++ b/pyrtl/rtllib/prngs.py @@ -13,41 +13,50 @@ def prng_lfsr( req: pyrtl.WireVector, seed: pyrtl.WireVector = None, ) -> pyrtl.Register: - """Builds a single-cycle PRNG using a 127 bits Fibonacci LFSR. + """Builds a single-cycle PRNG using a 127 bits Fibonacci `LFSR + `_. A very fast and compact PRNG that generates a random number using only one clock cycle. Has a period of ``2**127 - 1``. Its linearity makes it a bit statistically weak, but it should be good enough for any noncryptographic purpose like test pattern generation. - Example:: + .. doctest only:: + + >>> import pyrtl + >>> pyrtl.reset_working_block() - load, req = pyrtl.Input(1, 'load'), pyrtl.Input(1, 'req') - rand = pyrtl.Output(64, 'rand') + Example:: - rand <<= prngs.prng_lfsr(64, load, req) + >>> load = pyrtl.Input(1, "load") + >>> req = pyrtl.Input(1, "req") + >>> seed = pyrtl.Input(127, "seed") + >>> rand = pyrtl.Output(32, "rand") - sim = pyrtl.Simulation() - sim.step({'load': 1, 'req': 0}) # seed once at the beginning - sim.step({'load': 0, 'req': 1}) - sim.step({'load': 0, 'req': 0}) - print(sim.inspect(rand)) - sim.tracer.render_trace(symbol_len=40, segment_size=1) + >>> rand <<= pyrtl.rtllib.prngs.prng_lfsr( + ... bitwidth=32, + ... load=load, + ... req=req, + ... seed=seed + ... ) - Example with explicit seeding:: + >>> seed_value = 0x70123456789012345678901234567890 + >>> seed_value.bit_length() + 127 - seed = pyrtl.Input(127, 'seed') - load, req = pyrtl.Input(1, 'load'), pyrtl.Input(1, 'req') - rand = pyrtl.Output(32, 'rand') + >>> # Start `Simulation` and load `seed`. + >>> sim = pyrtl.Simulation() + >>> sim.step({"load": True, "req": False, "seed": seed_value}) - rand <<= prngs.prng_lfsr(32, load, req, seed) + >>> # Generate a random number. + >>> sim.step({"load": False, "req": True, "seed": 0}) + >>> sim.inspect("rand") + 878082192 - sim = pyrtl.Simulation() - sim.step({'load': 1, 'req': 0, 'seed': 0x102030405060708090a0b0c0d0e0f010}) - sim.step({'load': 0, 'req': 1, 'seed': 0x102030405060708090a0b0c0d0e0f010}) - sim.step({'load': 0, 'req': 0, 'seed': 0x102030405060708090a0b0c0d0e0f010}) - print(sim.inspect(rand)) - sim.tracer.render_trace(symbol_len=40, segment_size=1) + >>> # Generate a random number. + >>> sim.step({"load": False, "req": True, "seed": 0}) + >>> sim.inspect("rand") + 543996405 :param bitwidth: The desired bitwidth of the random number. :param load: One bit signal to load the seed into the PRNG. @@ -95,23 +104,49 @@ def prng_xoroshiro128( See also http://xoroshiro.di.unimi.it/ - Example:: - - load, req = pyrtl.Input(1, 'load'), pyrtl.Input(1, 'req') - ready, rand = pyrtl.Output(1, 'ready'), pyrtl.Output(128, 'rand') + .. doctest only:: - ready_out, rand_out = prngs.prng_xoroshiro128(128, load, req) - ready <<= ready_out - rand <<= rand_out + >>> import pyrtl + >>> pyrtl.reset_working_block() - sim = pyrtl.Simulation() - sim.step({'load': 1, 'req': 0}) # seed once at the beginning - sim.step({'load': 0, 'req': 1}) - while sim.value[ready] == 0: # or loop 2 cycles - sim.step({'load': 0, 'req': 0}) + Example:: - print(sim.inspect(rand)) - sim.tracer.render_trace(symbol_len=40, segment_size=1) + >>> load = pyrtl.Input(1, "load") + >>> req = pyrtl.Input(1, "req") + >>> seed = pyrtl.Input(128, "seed") + >>> ready = pyrtl.Output(1, "ready") + >>> rand = pyrtl.Output(128, "rand") + + >>> ready_, rand_ = pyrtl.rtllib.prngs.prng_xoroshiro128( + ... bitwidth=128, + ... load=load, + ... req=req, + ... seed=seed + ... ) + >>> ready <<= ready_ + >>> rand <<= rand_ + + >>> seed_value = 0x90123456789012345678901234567890 + >>> seed_value.bit_length() + 128 + + >>> # Start `Simulation` and load `seed`. + >>> sim = pyrtl.Simulation() + >>> sim.step({"load": True, "req": False, "seed": seed_value}) + + >>> # Generate a random number. + >>> sim.step({"load": False, "req": True, "seed": 0}) + >>> while not sim.inspect("ready"): + ... sim.step({"load": False, "req": False, "seed": 0}) + >>> sim.inspect("rand") + 306442959642529804138987790876643657180 + + >>> # Generate a random number. + >>> sim.step({"load": False, "req": True, "seed": 0}) + >>> while not sim.inspect("ready"): + ... sim.step({"load": False, "req": False, "seed": 0}) + >>> sim.inspect("rand") + 102902340102369319506108813713726960781 :param bitwidth: The desired bitwidth of the random number. :param load: One bit signal to load the seed into the PRNG. @@ -145,12 +180,13 @@ def prng_xoroshiro128( rand = pyrtl.Register(gen_cycles * 64) counter = pyrtl.Register(counter_bitwidth, "counter") gen_done = counter == gen_cycles - 1 - state = pyrtl.Register(1) class State(enum.IntEnum): WAIT = 0 GEN = 1 + state = pyrtl.Register(State=State) + with pyrtl.conditional_assignment: with load: s0.next |= seed[:64] @@ -196,27 +232,51 @@ def csprng_trivium( See also the eSTREAM portfolio page: http://www.ecrypt.eu.org/stream/e2-trivium.html - Example:: - - load, req = pyrtl.Input(1, 'load'), pyrtl.Input(1, 'req') - ready, rand = pyrtl.Output(1, 'ready'), pyrtl.Output(128, 'rand') + .. doctest only:: - ready_out, rand_out = prngs.csprng_trivium(128, load, req) - ready <<= ready_out - rand <<= rand_out + >>> import pyrtl + >>> pyrtl.reset_working_block() - sim = pyrtl.Simulation() - # Seed only in the first cycle. - sim.step({'load': 1, 'req': 0}) - while sim.value[ready] == 0: # or loop 19 cycles - sim.step({'load': 0, 'req': 0}) - - sim.step({'load': 0, 'req': 1}) - while sim.value[ready] == 0: # or loop 2 cycles - sim.step({'load': 0, 'req': 0}) + Example:: - print(sim.inspect(rand)) - sim.tracer.render_trace(symbol_len=45, segment_size=5) + >>> load = pyrtl.Input(1, "load") + >>> req = pyrtl.Input(1, "req") + >>> seed = pyrtl.Input(160, "seed") + >>> ready = pyrtl.Output(1, "ready") + >>> rand = pyrtl.Output(128, "rand") + + >>> ready_, rand_ = pyrtl.rtllib.prngs.csprng_trivium( + ... bitwidth=128, + ... load=load, + ... req=req, + ... seed=seed + ... ) + >>> ready <<= ready_ + >>> rand <<= rand_ + + >>> seed_value = 0x8234567890123456789012345678901234567890 + >>> seed_value.bit_length() + 160 + + >>> # Start `Simulation` and load `seed`. + >>> sim = pyrtl.Simulation() + >>> sim.step({"load": True, "req": False, "seed": seed_value}) + >>> while not sim.inspect("ready"): + ... sim.step({"load": False, "req": False, "seed": 0}) + + >>> # Generate a random number. + >>> sim.step({"load": False, "req": True, "seed": 0}) + >>> while not sim.inspect("ready"): + ... sim.step({"load": False, "req": False, "seed": 0}) + >>> sim.inspect("rand") + 204619253771835913275124539130029978896 + + >>> # Generate a random number. + >>> sim.step({"load": False, "req": True, "seed": 0}) + >>> while not sim.inspect("ready"): + ... sim.step({"load": False, "req": False, "seed": 0}) + >>> sim.inspect("rand") + 98933539811155255006597373162055402029 :param bitwidth: The desired bitwidth of the random number. :param load: One bit signal to load the seed into the PRNG @@ -267,13 +327,14 @@ def csprng_trivium( counter = pyrtl.Register(counter_bitwidth, "counter") init_done = counter == init_cycles gen_done = counter == gen_cycles - 1 - state = pyrtl.Register(2) class State(enum.IntEnum): WAIT = 0 INIT = 1 GEN = 2 + state = pyrtl.Register(State=State) + with pyrtl.conditional_assignment: with load: counter.next |= 0 diff --git a/pyrtl/simulation.py b/pyrtl/simulation.py index 54659028..fefcaab7 100644 --- a/pyrtl/simulation.py +++ b/pyrtl/simulation.py @@ -74,12 +74,26 @@ class Simulation: """ Stores the simulation results for each cycle. + .. doctest only:: + + >>> import os + >>> import pyrtl + >>> os.environ["PYRTL_RENDERER"] = "cp437" + >>> pyrtl.reset_working_block() + ``tracer`` is typically used to render simulation waveforms with :meth:`~SimulationTrace.render_trace`, for example:: - sim = pyrtl.Simulation() - sim.step_multiple(nsteps=10) - sim.tracer.render_trace() + >>> counter = pyrtl.Register(name="counter", bitwidth=2) + >>> counter.next <<= counter + 1 + + >>> sim = pyrtl.Simulation() + >>> sim.step_multiple(nsteps=8) + + >>> sim.tracer.render_trace() + │0 │1 │2 │3 │4 │5 │6 │7 + + counter ───┤0x1│0x2│0x3├───┤0x1│0x2│0x3 See :class:`SimulationTrace` for more display options. """ @@ -1514,9 +1528,6 @@ def __getitem__(self, key): return self.__data[key] -_default_renderer = default_renderer() - - class SimulationTrace: """Storage and presentation of simulation waveforms. @@ -1788,7 +1799,7 @@ def render_trace( self, trace_list: list[str] | None = None, file: TextIO | None = None, - renderer: WaveRenderer = _default_renderer, + renderer: WaveRenderer | None = None, symbol_len: int | None = None, repr_func: Callable[[int], str] = hex, repr_per_name: dict[str, Callable[[int], str]] | None = None, @@ -1796,20 +1807,25 @@ def render_trace( ): """Render the trace with Unicode and ASCII escape sequences. - The resulting output can be viewed directly in a terminal. Example:: + .. doctest only:: - counter = pyrtl.Register(name="counter", bitwidth=2) - counter.next <<= counter + 1 + >>> import os + >>> import pyrtl + >>> os.environ["PYRTL_RENDERER"] = "cp437" + >>> pyrtl.reset_working_block() - sim = pyrtl.Simulation() - sim.step_multiple(nsteps=4) + The resulting output can be viewed directly in a terminal. Example:: - sim.tracer.render_trace() + >>> counter = pyrtl.Register(name="counter", bitwidth=2) + >>> counter.next <<= counter + 1 - Which displays: + >>> sim = pyrtl.Simulation() + >>> sim.step_multiple(nsteps=8) - .. image:: ../docs/screenshots/render_trace.png - :scale: 66% + >>> sim.tracer.render_trace() + │0 │1 │2 │3 │4 │5 │6 │7 + + counter ───┤0x1│0x2│0x3├───┤0x1│0x2│0x3 Many trace formats are available, and can be configured with the ``PYRTL_RENDERER`` environment variable. See :class:`.WaveRenderer`'s @@ -1839,6 +1855,8 @@ def render_trace( if len(self) == 0: msg = "You need to step the simulation at least once to render a trace." raise PyrtlError(msg) + if renderer is None: + renderer = default_renderer() if repr_per_name is None: repr_per_name = {} if _currently_in_jupyter_notebook(): @@ -1983,16 +2001,19 @@ def formatted_trace_line(wire, trace): maxtracelen = max(len(self.trace[trace_name]) for trace_name in trace_list) if segment_size is None: segment_size = maxtracelen - spaces = " " * (maxnamelen) ticks = [ renderer.render_ruler_segment(n, cycle_len, segment_size, maxtracelen) for n in range(0, maxtracelen, segment_size) ] - print(spaces + "".join(ticks), file=file) + ruler_line = " " * (maxnamelen) + "".join(ticks) + print(ruler_line.rstrip(), file=file) # now all the traces for trace_name in trace_list: - print(formatted_trace_line(trace_name, self.trace[trace_name]), file=file) + print( + formatted_trace_line(trace_name, self.trace[trace_name]).rstrip(), + file=file, + ) def _set_initial_values( self, @@ -2072,8 +2093,10 @@ def enum_name(EnumClass: type) -> Callable[[int], str]: .. doctest only:: + >>> import os >>> import pyrtl >>> import enum + >>> os.environ["PYRTL_RENDERER"] = "cp437" >>> pyrtl.reset_working_block() Use ``enum_name`` as a ``repr_func`` or ``repr_per_name`` for @@ -2088,16 +2111,13 @@ def enum_name(EnumClass: type) -> Callable[[int], str]: :meth:`~SimulationTrace.render_trace` example:: - option = pyrtl.Input(name="option", bitwidth=1) - - sim = pyrtl.Simulation() - sim.step_multiple({"option": [Option.FOO, Option.BAR]}) - sim.tracer.render_trace(repr_per_name={"option": pyrtl.enum_name(Option)}) - - Which prints:: + >>> option = pyrtl.Input(name="option", bitwidth=1) + >>> sim = pyrtl.Simulation() + >>> sim.step_multiple({"option": [Option.FOO, Option.BAR]}) + >>> sim.tracer.render_trace(repr_per_name={"option": pyrtl.enum_name(Option)}) │0 │1 - + option FOO│BAR .. note:: diff --git a/pyrtl/wire.py b/pyrtl/wire.py index a0e2e424..6abef0c1 100644 --- a/pyrtl/wire.py +++ b/pyrtl/wire.py @@ -271,7 +271,7 @@ class WireVector: """ block: Block - """The ``Block`` that this :class:`WireVector` belongs to.""" + """The :class:`.Block` that this :class:`WireVector` belongs to.""" # "code" is a static variable used when output as string. # Each class inheriting from WireVector should overload accordingly @@ -1817,7 +1817,9 @@ def __init__( .. doctest only:: + >>> import os >>> import pyrtl + >>> os.environ["PYRTL_RENDERER"] = "cp437" >>> pyrtl.reset_working_block() See :class:`Register`'s documentation above for a basic example. The example @@ -1845,13 +1847,11 @@ def __init__( When a ``Register`` is constructed with ``State``, :meth:`~.SimulationTrace.render_trace` displays ``State`` names by default:: - sim = pyrtl.Simulation() - sim.step_multiple(nsteps=4) - sim.tracer.render_trace() - - Which prints:: - + >>> sim = pyrtl.Simulation() + >>> sim.step_multiple(nsteps=4) + >>> sim.tracer.render_trace() │0 │1 │2 │3 + state ONE │TWO │THREE│ZERO :param bitwidth: Number of bits to represent this ``Register``. diff --git a/tests/rtllib/test_aes.py b/tests/rtllib/test_aes.py index e196c09a..356fe4f4 100644 --- a/tests/rtllib/test_aes.py +++ b/tests/rtllib/test_aes.py @@ -1,3 +1,4 @@ +import doctest import unittest import pyrtl @@ -5,6 +6,15 @@ from pyrtl.rtllib import aes, testingutils +class TestDocTests(unittest.TestCase): + """Test documentation examples.""" + + def test_aes_doctests(self): + failures, tests = doctest.testmod(m=aes) + self.assertGreater(tests, 0) + self.assertEqual(failures, 0) + + class TestAESDecrypt(unittest.TestCase): """ Test vectors are retrieved from: diff --git a/tests/rtllib/test_prngs.py b/tests/rtllib/test_prngs.py index ba440155..426cb062 100644 --- a/tests/rtllib/test_prngs.py +++ b/tests/rtllib/test_prngs.py @@ -1,3 +1,4 @@ +import doctest import random import unittest from itertools import islice @@ -6,6 +7,15 @@ from pyrtl.rtllib import prngs +class TestDocTests(unittest.TestCase): + """Test documentation examples.""" + + def test_prngs_doctests(self): + failures, tests = doctest.testmod(m=prngs) + self.assertGreater(tests, 0) + self.assertEqual(failures, 0) + + class TestPrngs(unittest.TestCase): def setUp(self): pyrtl.reset_working_block() diff --git a/tests/test_simulation.py b/tests/test_simulation.py index 2205bc79..5220e066 100644 --- a/tests/test_simulation.py +++ b/tests/test_simulation.py @@ -173,9 +173,9 @@ def check_rendered_trace(self, expected, **kwargs): def test_hex_trace(self): expected = ( - " |0 |1 |2 |3 |4 \n" + " |0 |1 |2 |3 |4\n" " \n" - "a 0x1 |0x4 |0x9 |0xb |0xc \n" + "a 0x1 |0x4 |0x9 |0xb |0xc\n" " \n" "b 0x2 |0x17|0x2b|0x78|----\n" " \n" @@ -185,9 +185,9 @@ def test_hex_trace(self): def test_oct_trace(self): expected = ( - " |0 |1 |2 |3 |4 \n" + " |0 |1 |2 |3 |4\n" " \n" - "a 0o1 |0o4 |0o11 |0o13 |0o14 \n" + "a 0o1 |0o4 |0o11 |0o13 |0o14\n" " \n" "b 0o2 |0o27 |0o53 |0o170|-----\n" " \n" @@ -198,9 +198,9 @@ def test_oct_trace(self): def test_bin_trace(self): expected = ( - " |0 |1 |2 |3 |4 \n" + " |0 |1 |2 |3 |4\n" " \n" - "a 0b1 |0b100 |0b1001 |0b1011 |0b1100 \n" + "a 0b1 |0b100 |0b1001 |0b1011 |0b1100\n" " \n" "b 0b10 |0b10111 |0b101011 |0b1111000|---------\n" " \n" @@ -211,9 +211,9 @@ def test_bin_trace(self): def test_decimal_trace(self): expected = ( - " |0 |1 |2 |3 |4 \n" + " |0 |1 |2 |3 |4\n" " \n" - "a 1 |4 |9 |11 |12 \n" + "a 1 |4 |9 |11 |12\n" " \n" "b 2 |23 |43 |120|---\n" " \n" @@ -279,7 +279,7 @@ class State(enum.IntEnum): repr_per_name={state.name: state_name}, ) expected = ( - " |0 |1 \n" + " |0 |1\n" " \n" "state FOO|BAR\n" ) # fmt: skip @@ -316,7 +316,7 @@ def test_val_to_signed_integer(self): file=buff, renderer=self.renderer, repr_func=pyrtl.val_to_signed_integer ) expected = ( - " |0 |1 |2 |3 \n" + " |0 |1 |2 |3\n" " \n" "counter --|1 |-2|-1\n" ) # fmt: skip @@ -352,13 +352,13 @@ class Foo(enum.IntEnum): renderer=self.renderer, ) expected = ( - " |0 |1 |2 |3 |4 \n" + " |0 |1 |2 |3 |4\n" " \n" " i 0x1|0x2|0x4|0x8|---\n" " \n" " o -------|0x1|0x2|0x3\n" " \n" - "state A |B |C |D \n" + "state A |B |C |D\n" ) self.assertEqual(buff.getvalue(), expected) From 20d3c58dbb0fcf4448300444ecb256591c43648d Mon Sep 17 00:00:00 2001 From: Jeremy Lau <30300826+fdxmw@users.noreply.github.com> Date: Mon, 29 Jun 2026 10:25:29 -0700 Subject: [PATCH 2/4] Revert the `input_from_verilog` example back to a non-doctest example, and add a comment. The example shouldn't be doctested because it depends on Yosys, which may not be installed. --- pyrtl/importexport.py | 33 ++++++++++++++++----------------- 1 file changed, 16 insertions(+), 17 deletions(-) diff --git a/pyrtl/importexport.py b/pyrtl/importexport.py index ed9375bf..71c2df51 100644 --- a/pyrtl/importexport.py +++ b/pyrtl/importexport.py @@ -659,29 +659,28 @@ def input_from_verilog( to try and produce BLIF yourself. Then you can import BLIF directly via :func:`input_from_blif`. - .. doctest only:: + .. doctest comment:: - >>> import pyrtl - >>> pyrtl.reset_working_block() + This example is not a `doctest` because it depends on Yosys, which might not be + installed. Example:: - >>> verilog = ''' - ... module toplevel(clk, a, b, sum); - ... input clk; - ... input[2:0] a; - ... input[2:0] b; - ... output[3:0] sum; - ... assign sum = (a + b); - ... endmodule - ... ''' + verilog = ''' + module toplevel(clk, a, b, sum); + input clk; + input[2:0] a; + input[2:0] b; + output[3:0] sum; + assign sum = (a + b); + endmodule + ''' - >>> pyrtl.input_from_verilog(verilog) + pyrtl.input_from_verilog(verilog) - >>> sim = pyrtl.Simulation() - >>> sim.step({"a": 1, "b": 2}) - >>> sim.inspect("sum") - 3 + sim = pyrtl.Simulation() + sim.step({"a": 1, "b": 2}) + sim.inspect("sum") :param verilog: An open Verilog file to read, or a string containing Verilog data. :param clock_name: The name of the clock (defaults to ``"clk"``). From 7db9bb22982b2cf139ec03652be420823b6f2eb3 Mon Sep 17 00:00:00 2001 From: Jeremy Lau <30300826+fdxmw@users.noreply.github.com> Date: Mon, 29 Jun 2026 10:51:41 -0700 Subject: [PATCH 3/4] Improve documentation for functions with additional software requirements. --- pyrtl/analysis.py | 5 +++++ pyrtl/importexport.py | 31 +++++++++++++++++++++++++++++-- pyrtl/visualization.py | 12 +++++++++--- 3 files changed, 43 insertions(+), 5 deletions(-) diff --git a/pyrtl/analysis.py b/pyrtl/analysis.py index 672f2001..fa9ba38c 100644 --- a/pyrtl/analysis.py +++ b/pyrtl/analysis.py @@ -344,6 +344,11 @@ def yosys_area_delay( """Synthesize with `Yosys `_ and return estimate of area and delay. + .. warning:: + + ``yosys_area_delay`` requires ``yosys``, which must be `separately installed + `_. + If ``leave_in_dir`` is specified, that directory will be used to create any temporary files, and the resulting files will be left behind there (which can be useful for manual exploration or debugging) diff --git a/pyrtl/importexport.py b/pyrtl/importexport.py index 71c2df51..c8aa7246 100644 --- a/pyrtl/importexport.py +++ b/pyrtl/importexport.py @@ -123,6 +123,14 @@ def input_from_blif( `_ file or string as input, updating the block appropriately. + .. warning:: + + ``input_from_blif`` requires the `pyparsing + `_ ``pip`` package, which is an optional + PyRTL dependency. Install with:: + + $ pip install pyrtl[blif] + If ``merge_io_vectors`` is ``True``, then given 1-bit :class:`Input` wires ``a[0]`` and ``a[1]``, these wires will be combined into a single 2-bit :class:`Input` wire ``a`` that can be accessed by name ``a`` in the block. Otherwise if @@ -649,8 +657,19 @@ def input_from_verilog( block: Block = None, ): """Read an open Verilog file or string as input via `Yosys - `_ conversion, updating the block. Yosys must be - installed. + `_ conversion, updating the block. + + .. warning:: + + ``input_from_verilog`` requires: + + 1. ``yosys``, which must be `separately installed + `_. + + 2. The `pyparsing `_ ``pip`` package, which + is an optional PyRTL dependency. Install with:: + + $ pip install pyrtl[blif] This function runs Yosys with a script to convert Verilog to BLIF, then calls :func:`input_from_blif` on the converted BLIF file. @@ -1922,6 +1941,14 @@ def output_to_firrtl( def input_from_iscas_bench(bench, block: Block = None): """Import an ISCAS .bench file + .. warning:: + + ``input_from_iscas_bench`` requires the `pyparsing + `_ ``pip`` package, which is an optional + PyRTL dependency. Install with:: + + $ pip install pyrtl[blif] + :param bench: an open ISCAS .bench file to read :param block: block to add the imported logic (defaults to current :ref:`working_block`) diff --git a/pyrtl/visualization.py b/pyrtl/visualization.py index 0cc59e1f..96e1b27a 100644 --- a/pyrtl/visualization.py +++ b/pyrtl/visualization.py @@ -581,8 +581,14 @@ def output_to_svg(file: TextIO, *args, **kwargs): def block_to_svg(*args, **kwargs) -> str: - """Return a SVG rendering of a ``block``. Requires the `graphviz - `_ ``pip`` package. + """Return a SVG rendering of a ``block``. + + .. warning:: + + ``block_to_svg`` requires the `graphviz `_ + ``pip`` package, which is an optional PyRTL dependency. Install with:: + + $ pip install pyrtl[svg] .. doctest only:: @@ -628,7 +634,7 @@ def block_to_svg(*args, **kwargs) -> str: # py-graphviz 0.19 or later return svg except ImportError as exc: - msg = 'need graphviz installed (try "pip install graphviz")' + msg = 'need graphviz installed (try "pip install pyrtl[svg]")' raise PyrtlError(msg) from exc From 0eb29edcebff839f051ea101b0a8dce11c5e1fea Mon Sep 17 00:00:00 2001 From: Jeremy Lau <30300826+fdxmw@users.noreply.github.com> Date: Mon, 29 Jun 2026 11:05:10 -0700 Subject: [PATCH 4/4] Make the main-page installation instructions consistent with the other `pip install` examples. --- docs/index.rst | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/docs/index.rst b/docs/index.rst index 90f4947f..f17cad47 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -23,12 +23,10 @@ Quick links Installation ============ -**Automatic installation**:: +PyRTL is availble in `PyPI `_ and can be +installed with :program:`pip`:: - pip install pyrtl - -PyRTL is listed in `PyPI `_ and can be -installed with :program:`pip`. + $ pip install pyrtl Design, Simulate, and Inspect in 15 lines =========================================