Skip to content

Commit

Permalink
Allow val_to_signed_integer as a render_trace repr_func. This m…
Browse files Browse the repository at this point in the history
…akes it

easier to display signed values in traces.

Other small cleanups:
- Fix a warning about negating bools.
- Simplify definition of _twos_comp_conditional.
- pyproject.toml: Add graphviz as an optional dependency. It's needed for SVG
  output (`block_to_svg`).
- Minor documentation README updates.
  • Loading branch information
fdxmw committed Apr 19, 2024
1 parent 40b4382 commit ac9bfbb
Show file tree
Hide file tree
Showing 6 changed files with 108 additions and 50 deletions.
10 changes: 7 additions & 3 deletions docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,12 @@ PyRTL's documentation is in this `docs` directory. It is built with
The main Sphinx configuration file is
[`docs/conf.py`](https://github.com/UCSBarchlab/PyRTL/blob/development/docs/conf.py).

Most of PyRTL's documentation is automatically extracted from Python docstrings, see
[docstring formating](https://www.sphinx-doc.org/en/master/usage/restructuredtext/domains.html#signatures) for supported directives and fields.
Most of PyRTL's documentation is automatically extracted from Python
docstrings, see [docstring
formating](https://www.sphinx-doc.org/en/master/usage/restructuredtext/domains.html#signatures)
for supported directives and fields. Sphinx parses [Python type
annotations](https://docs.python.org/3/library/typing.html), so put type
information into annotations instead of docstrings.

Follow the instructions on this page to build a local copy of PyRTL's
documentation. This is useful for verifying that PyRTL's documentation still
Expand Down Expand Up @@ -42,7 +46,7 @@ repository root:

```shell
# Install Sphinx.
$ pip install -r docs/requirements.txt
$ pip install --upgrade -r docs/requirements.txt
```

## Installing Graphviz
Expand Down
3 changes: 3 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,10 @@ classifiers = [
]

[project.optional-dependencies]
# Required by `input_from_blif`.
blif = ["pyparsing"]
# Required by `block_to_svg`.
svg = ["graphviz"]

[project.urls]
Homepage = "http://ucsbarchlab.github.io/PyRTL/"
Expand Down
31 changes: 24 additions & 7 deletions pyrtl/helperfuncs.py
Original file line number Diff line number Diff line change
Expand Up @@ -519,19 +519,36 @@ def wirevector_list(names, bitwidth=None, wvtype=WireVector):
return wirelist


def val_to_signed_integer(value, bitwidth):
""" Return value as intrepreted as a signed integer under two's complement.
def val_to_signed_integer(value: int, bitwidth: int) -> int:
"""Return value as intrepreted as a signed integer under two's complement.
:param int value: a Python integer holding the value to convert
:param int bitwidth: the length of the integer in bits to assume for conversion
:return: `value` as a signed integer
:rtype: int
:param value: A Python integer holding the value to convert.
:param bitwidth: The length of the integer in bits to assume for
conversion.
:return: ``value`` as a signed integer
Given an unsigned integer (not a WireVector!) convert that to a signed
Given an unsigned integer (not a ``WireVector``!) convert that to a signed
integer. This is useful for printing and interpreting values which are
negative numbers in two's complement. ::
val_to_signed_integer(0xff, 8) == -1
``val_to_signed_integer`` can also be used as an ``repr_func`` for
:py: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)
# 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)
"""
if isinstance(value, WireVector) or isinstance(bitwidth, WireVector):
raise PyrtlError('inputs must not be wirevectors')
Expand Down
17 changes: 6 additions & 11 deletions pyrtl/rtllib/multipliers.py
Original file line number Diff line number Diff line change
Expand Up @@ -170,17 +170,12 @@ def signed_tree_multiplier(A, B, reducer=adders.wallace_reducer, adder_func=adde
return _twos_comp_conditional(res, aneg ^ bneg)


def _twos_comp_conditional(orig_wire, sign_bit, bw=None):
"""Returns two's complement of wire (using bitwidth bw) if sign_bit == 1"""
if bw is None:
bw = len(orig_wire)
new_wire = pyrtl.WireVector(bw)
with pyrtl.conditional_assignment:
with sign_bit:
new_wire |= ~orig_wire + 1
with pyrtl.otherwise:
new_wire |= orig_wire
return new_wire
def _twos_comp_conditional(orig_wire: pyrtl.WireVector,
sign_bit: pyrtl.WireVector) -> pyrtl.WireVector:
"""Returns two's complement of ``orig_wire`` if ``sign_bit`` == 1"""
return pyrtl.select(sign_bit,
(~orig_wire + 1).truncate(len(orig_wire)),
orig_wire)


def fused_multiply_adder(mult_A, mult_B, add, signed=False, reducer=adders.wallace_reducer,
Expand Down
81 changes: 52 additions & 29 deletions pyrtl/simulation.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
"""Classes for executing and tracing circuit simulations."""

from __future__ import annotations

import copy
import math
import numbers
Expand All @@ -13,6 +15,7 @@
from .wire import Input, Register, Const, Output, WireVector
from .memory import RomBlock
from .helperfuncs import check_rtl_assertions, _currently_in_jupyter_notebook
from .helperfuncs import val_to_signed_integer
from .importexport import _VerilogSanitizer

try:
Expand Down Expand Up @@ -63,7 +66,7 @@ class Simulation(object):

simple_func = { # OPS
'w': lambda x: x,
'~': lambda x: ~x,
'~': lambda x: ~int(x),
'&': lambda left, right: left & right,
'|': lambda left, right: left | right,
'^': lambda left, right: left ^ right,
Expand Down Expand Up @@ -1026,11 +1029,12 @@ def render_ruler_segment(self, n, cycle_len, segment_size, maxtracelen):
ticks = major_tick.ljust(cycle_len * segment_size)
return ticks

def val_to_str(self, value, wire_name, repr_func, repr_per_name):
def val_to_str(self, value: int, wire: WireVector,
repr_func: typing.Callable, repr_per_name: dict) -> str:
"""Return a string representing 'value'.
:param value: The value to convert to string.
:param wire_name: Name of the wire that produced this value.
:param wire: Wire that produced this value.
:param repr_func: function to use for representing the current_val;
examples are 'hex', 'oct', 'bin', 'str' (for decimal), or
the function returned by :py:func:`enum_name`. Defaults to 'hex'.
Expand All @@ -1041,11 +1045,18 @@ def val_to_str(self, value, wire_name, repr_func, repr_per_name):
:return: a string representing 'value'.
"""
f = repr_per_name.get(wire_name)
f = repr_per_name.get(wire.name)

def invoke_f(f, value):
if f is val_to_signed_integer:
return str(val_to_signed_integer(value=value,
bitwidth=wire.bitwidth))
else:
return str(f(value))
if f is not None:
return str(f(value))
return invoke_f(f, value)
else:
return str(repr_func(value))
return invoke_f(repr_func, value)

def render_val(self, w, prior_val, current_val, symbol_len, cycle_len,
repr_func, repr_per_name, prev_line, is_last):
Expand Down Expand Up @@ -1081,7 +1092,8 @@ def render_val(self, w, prior_val, current_val, symbol_len, cycle_len,
flat_zero = (w.name not in repr_per_name
and (repr_func is hex or repr_func is oct
or repr_func is int or repr_func is str
or repr_func is bin))
or repr_func is bin
or repr_func is val_to_signed_integer))
if prev_line:
# Bus wires are currently never rendered across multiple lines.
return ''
Expand All @@ -1105,7 +1117,7 @@ def render_val(self, w, prior_val, current_val, symbol_len, cycle_len,
if prior_val is None:
out += self.constants._bus_start
# Display the current non-zero value.
out += (self.val_to_str(current_val, w.name, repr_func,
out += (self.val_to_str(current_val, w, repr_func,
repr_per_name).rstrip('L')
.ljust(symbol_len)[:symbol_len])
if is_last:
Expand Down Expand Up @@ -1591,28 +1603,36 @@ def print_trace_strs(time):
file.flush()

def render_trace(
self, trace_list=None, file=sys.stdout, renderer=default_renderer(),
symbol_len=None, repr_func=hex, repr_per_name={}, segment_size=1):
self, trace_list: list[str] = None, file=sys.stdout,
renderer: WaveRenderer = default_renderer(), symbol_len: int = None,
repr_func: typing.Callable = hex, repr_per_name: dict = {},
segment_size: int = 1):

""" Render the trace to a file using unicode and ASCII escape sequences.
"""Render the trace to a file using unicode and ASCII escape sequences.
:param list[str] trace_list: A list of signal names to be output in the specified order.
:param trace_list: A list of signal names to be output in the specified
order.
:param file: The place to write output, default to stdout.
:param WaveRenderer renderer: An object that translates traces into output bytes.
:param int symbol_len: The "length" of each rendered value in characters.
If None, the length will be automatically set such that the largest
represented value fits.
:param repr_func: Function to use for representing each value in the trace;
examples are ``hex``, ``oct``, ``bin``, and ``str`` (for decimal), or
the function returned by :py:func:`enum_name`. Defaults to 'hex'.
:param repr_per_name: Map from signal name to a function that takes in the signal's
value and returns a user-defined representation. If a signal name is
not found in the map, the argument `repr_func` will be used instead.
:param int segment_size: Traces are broken in the segments of this number of cycles.
:param renderer: An object that translates traces into output bytes.
:param symbol_len: The "length" of each rendered value in characters.
If ``None``, the length will be automatically set such that the
largest represented value fits.
:param repr_func: Function to use for representing each value in the
trace. Examples include ``hex``, ``oct``, ``bin``, and ``str`` (for
decimal), :py:func:`.val_to_signed_integer` (for signed decimal) or
the function returned by :py:func:`enum_name` (for ``IntEnum``).
Defaults to ``hex``.
:param repr_per_name: Map from signal name to a function that takes in
the signal's value and returns a user-defined representation. If a
signal name is not found in the map, the argument ``repr_func``
will be used instead.
:param segment_size: Traces are broken in the segments of this number
of cycles.
The resulting output can be viewed directly on the terminal or looked
at with :program:`more` or :program:`less -R` which both should handle the ASCII escape
sequences used in rendering.
at with :program:`more` or :program:`less -R` which both should handle
the ASCII escape sequences used in rendering.
"""
if _currently_in_jupyter_notebook():
from IPython.display import display, HTML, Javascript # pylint: disable=import-error
Expand Down Expand Up @@ -1693,12 +1713,15 @@ def formatted_trace_line(wire, trace):
"if a CompiledSimulation was used.")

if symbol_len is None:
maxvallen = 0
max_symbol_len = 0
for trace_name in trace_list:
trace = self.trace[trace_name]
maxvallen = max(maxvallen, max(len(renderer.val_to_str(
v, trace_name, repr_func, repr_per_name)) for v in trace))
symbol_len = maxvallen
current_symbol_len = max(
len(renderer.val_to_str(
v, self._wires[trace_name], repr_func, repr_per_name))
for v in trace)
max_symbol_len = max(max_symbol_len, current_symbol_len)
symbol_len = max_symbol_len

cycle_len = symbol_len + renderer.constants._chars_between_cycles

Expand Down
16 changes: 16 additions & 0 deletions tests/test_simulation.py
Original file line number Diff line number Diff line change
Expand Up @@ -254,6 +254,22 @@ class State(enum.IntEnum):
)
self.assertEqual(buff.getvalue(), expected)

def test_val_to_signed_integer(self):
bitwidth = 2
counter = pyrtl.Register(name='counter', bitwidth=bitwidth)
counter.next <<= counter + 1
sim = pyrtl.Simulation()
sim.step_multiple(nsteps=2 ** bitwidth)
buff = io.StringIO()
sim.tracer.render_trace(file=buff, renderer=self.renderer,
repr_func=pyrtl.val_to_signed_integer)
expected = (
" |0 |1 |2 |3 \n"
" \n"
"counter --|1 |-2|-1\n"
)
self.assertEqual(buff.getvalue(), expected)

def test_custom_repr_per_wire(self):
class Foo(enum.IntEnum):
A = 0
Expand Down

0 comments on commit ac9bfbb

Please sign in to comment.