Skip to content

Commit 863c5ef

Browse files
authored
Merge pull request #390 from pllab/render-base
Add ability to specify how values render in trace; improve symbol_len calculation.
2 parents 6a86dc4 + 8bd006b commit 863c5ef

File tree

4 files changed

+265
-25
lines changed

4 files changed

+265
-25
lines changed

pyrtl/simulation.py

Lines changed: 47 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -876,33 +876,46 @@ def tick_segment(self, n, symbol_len, segment_size):
876876
num_tick = self._tick + str(n)
877877
return num_tick.ljust(symbol_len * segment_size)
878878

879-
def render_val(self, w, n, current_val, symbol_len):
879+
def render_val(self, w, n, current_val, symbol_len, repr_func, repr_per_name):
880880
if w is not self.prev_wire:
881881
self.prev_wire = w
882882
self.prior_val = current_val
883-
out = self._render_val_with_prev(w, n, current_val, symbol_len)
883+
out = self._render_val_with_prev(w, n, current_val, symbol_len, repr_func, repr_per_name)
884884
self.prior_val = current_val
885885
return out
886886

887-
def _render_val_with_prev(self, w, n, current_val, symbol_len):
887+
def _render_val_with_prev(self, w, n, current_val, symbol_len, repr_func, repr_per_name):
888888
"""Return a string encoding the given value in a waveform.
889889
890890
:param w: The WireVector we are rendering to a waveform
891891
:param n: An integer from 0 to segment_len-1
892892
:param current_val: the value to be rendered
893893
:param symbol_len: and integer for how big to draw the current value
894+
:param repr_func: function to use for representing the current_val;
895+
examples are 'hex', 'oct', 'bin', 'str' (for decimal), or even the name
896+
of an IntEnum class you know the value will belong to. Defaults to 'hex'.
897+
:param repr_per_name: Map from signal name to a function that takes in the signal's
898+
value and returns a user-defined representation. If a signal name is
899+
not found in the map, the argument `repr_func` will be used instead.
894900
895901
Returns a string of printed length symbol_len that will draw the
896902
representation of current_val. The input prior_val is used to
897903
render transitions.
898904
"""
905+
def to_str(v):
906+
f = repr_per_name.get(w.name)
907+
if f is None:
908+
return str(repr_func(v))
909+
else:
910+
return str(f(v))
911+
899912
sl = symbol_len - 1
900913
if len(w) > 1:
901914
out = self._revstart
902915
if current_val != self.prior_val:
903-
out += self._x + hex(current_val).rstrip('L').ljust(sl)[:sl]
916+
out += self._x + to_str(current_val).rstrip('L').ljust(sl)[:sl]
904917
elif n == 0:
905-
out += hex(current_val).rstrip('L').ljust(symbol_len)[:symbol_len]
918+
out += to_str(current_val).rstrip('L').ljust(symbol_len)[:symbol_len]
906919
else:
907920
out += ' ' * symbol_len
908921
out += self._revstop
@@ -1130,14 +1143,22 @@ def print_trace_strs(time):
11301143

11311144
def render_trace(
11321145
self, trace_list=None, file=sys.stdout, render_cls=default_renderer(),
1133-
symbol_len=5, segment_size=5, segment_delim=' ', extra_line=True):
1146+
symbol_len=5, repr_func=hex, repr_per_name={}, segment_size=5,
1147+
segment_delim=' ', extra_line=True):
11341148

11351149
""" Render the trace to a file using unicode and ASCII escape sequences.
11361150
11371151
:param trace_list: A list of signal names to be output in the specified order.
11381152
:param file: The place to write output, default to stdout.
11391153
:param render_cls: A class that translates traces into output bytes.
11401154
:param symbol_len: The "length" of each rendered cycle in characters.
1155+
If None, the length will be automatically set such that the largest
1156+
represented value fits.
1157+
:param repr_func: Function to use for representing each value in the trace;
1158+
examples are 'hex', 'oct', 'bin', and 'str' (for decimal). Defaults to 'hex'.
1159+
:param repr_per_name: Map from signal name to a function that takes in the signal's
1160+
value and returns a user-defined representation. If a signal name is
1161+
not found in the map, the argument `repr_func` will be used instead.
11411162
:param segment_size: Traces are broken in the segments of this number of cycles.
11421163
:param segment_delim: The character to be output between segments.
11431164
:param extra_line: A Boolean to determine if we should print a blank line between signals.
@@ -1166,12 +1187,12 @@ def render_trace(
11661187
else:
11671188
self.render_trace_to_text(
11681189
trace_list=trace_list, file=file, render_cls=render_cls,
1169-
symbol_len=symbol_len, segment_size=segment_size,
1170-
segment_delim=segment_delim, extra_line=extra_line)
1190+
symbol_len=symbol_len, repr_func=repr_func, repr_per_name=repr_per_name,
1191+
segment_size=segment_size, segment_delim=segment_delim, extra_line=extra_line)
11711192

11721193
def render_trace_to_text(
11731194
self, trace_list, file, render_cls,
1174-
symbol_len, segment_size, segment_delim, extra_line):
1195+
symbol_len, repr_func, repr_per_name, segment_size, segment_delim, extra_line):
11751196

11761197
renderer = render_cls()
11771198

@@ -1185,7 +1206,9 @@ def formatted_trace_line(wire, trace):
11851206
self._wires[wire],
11861207
i % segment_size,
11871208
trace[i],
1188-
symbol_len)
1209+
symbol_len,
1210+
repr_func,
1211+
repr_per_name)
11891212
return heading + trace_line
11901213

11911214
# default to printing all signals in sorted order
@@ -1204,6 +1227,20 @@ def formatted_trace_line(wire, trace):
12041227
"untraceable wires were removed prior to simulation, "
12051228
"if a CompiledSimulation was used.")
12061229

1230+
if symbol_len is None:
1231+
1232+
def to_str(v, name):
1233+
f = repr_per_name.get(name)
1234+
if f is None:
1235+
return str(repr_func(v))
1236+
else:
1237+
return str(f(v))
1238+
1239+
maxvallen = 0
1240+
for name, trace in self.trace.items():
1241+
maxvallen = max(maxvallen, max(len(to_str(v, name)) for v in trace))
1242+
symbol_len = maxvallen + 1
1243+
12071244
# print the 'ruler' which is just a list of 'ticks'
12081245
# mapped by the pretty map
12091246

pyrtl/visualization.py

Lines changed: 36 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -459,12 +459,18 @@ def block_to_svg(block=None, split_state=True, maintain_arg_order=False):
459459
# |__| | |\/| |
460460
# | | | | | |___
461461

462-
def trace_to_html(simtrace, trace_list=None, sortkey=None):
462+
def trace_to_html(simtrace, trace_list=None, sortkey=None, repr_func=hex, repr_per_name={}):
463463
""" Return a HTML block showing the trace.
464464
465465
:param simtrace: A SimulationTrace object
466466
:param trace_list: (optional) A list of wires to display
467467
:param sortkey: (optional) The key with which to sort the trace_list
468+
:param repr_func: function to use for representing the current_val;
469+
examples are 'hex', 'oct', 'bin', 'str' (for decimal), or even the name
470+
of an IntEnum class you know the value will belong to. Defaults to 'hex'.
471+
:param repr_per_name: Map from signal name to a function that takes in the signal's
472+
value and returns a user-defined representation. If a signal name is
473+
not found in the map, the argument `repr_func` will be used instead.
468474
:return: An HTML block showing the trace
469475
"""
470476

@@ -481,15 +487,19 @@ def trace_to_html(simtrace, trace_list=None, sortkey=None):
481487

482488
wave_template = (
483489
"""\
484-
<script type="WaveDrom">
485-
{ signal : [
486-
%s
487-
]}
488-
</script>
489-
490-
"""
490+
<script type="WaveDrom">
491+
{
492+
signal : [
493+
%s
494+
],
495+
config: { hscale: %d }
496+
}
497+
</script>
498+
"""
491499
)
492500

501+
vallens = [] # For determining longest value length
502+
493503
def extract(w):
494504
wavelist = []
495505
datalist = []
@@ -498,24 +508,35 @@ def extract(w):
498508
if last == value:
499509
wavelist.append('.')
500510
else:
501-
if len(w) == 1:
511+
if len(simtrace._wires[w]) == 1:
502512
wavelist.append(str(value))
503513
else:
504514
wavelist.append('=')
505-
datalist.append(value)
515+
datalist.append((value, w))
506516
last = value
507517

518+
def to_str(v, name):
519+
f = repr_per_name.get(name)
520+
if f is None:
521+
return str(repr_func(v))
522+
else:
523+
return str(f(v))
524+
508525
wavestring = ''.join(wavelist)
509-
datastring = ', '.join(['"%d"' % data for data in datalist])
510-
if len(w) == 1:
526+
datastring = ', '.join(['"%s"' % to_str(data, name) for data, name in datalist])
527+
if len(simtrace._wires[w]) == 1:
528+
vallens.append(1) # all are the same length
511529
return bool_signal_template % (w, wavestring)
512530
else:
531+
vallens.extend([len(to_str(data, name)) for data, name in datalist])
513532
return int_signal_template % (w, wavestring, datastring)
514533

515-
bool_signal_template = '{ name: "%s", wave: "%s" },'
516-
int_signal_template = '{ name: "%s", wave: "%s", data: [%s] },'
534+
bool_signal_template = ' { name: "%s", wave: "%s" },'
535+
int_signal_template = ' { name: "%s", wave: "%s", data: [%s] },'
517536
signals = [extract(w) for w in trace_list]
518537
all_signals = '\n'.join(signals)
519-
wave = wave_template % all_signals
538+
maxvallen = max(vallens)
539+
scale = (maxvallen // 5) + 1
540+
wave = wave_template % (all_signals, scale)
520541
# print(wave)
521542
return wave

tests/test_simulation.py

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import unittest
22
import six
3+
import io
34

45
import pyrtl
56
from pyrtl.corecircuits import _basic_add
@@ -128,6 +129,101 @@ def test_reg_to_reg_simulation(self):
128129
self.check_trace(' r 00224466\nr2 02244660\n')
129130

130131

132+
class RenderTraceBase(unittest.TestCase):
133+
def setUp(self):
134+
pyrtl.reset_working_block()
135+
a, b = pyrtl.input_list('a/8 b/8')
136+
o = pyrtl.Output()
137+
o <<= a + b
138+
139+
def check_rendered_trace(self, expected, **kwargs):
140+
sim = pyrtl.Simulation()
141+
sim.step_multiple({
142+
'a': [1, 4, 9, 11, 12],
143+
'b': [2, 23, 43, 120, 0],
144+
})
145+
buff = io.StringIO()
146+
sim.tracer.render_trace(file=buff, render_cls=pyrtl.simulation.AsciiWaveRenderer,
147+
extra_line=False, **kwargs)
148+
self.assertEqual(buff.getvalue(), expected)
149+
150+
def test_hex_trace(self):
151+
expected = (
152+
" -0 \n"
153+
"a 0x1 x0x4 x0x9 x0xb x0xc \n"
154+
"b 0x2 x0x17 x0x2b x0x78 x0x0 \n"
155+
)
156+
self.check_rendered_trace(expected)
157+
158+
def test_hex_trace(self):
159+
expected = (
160+
" -0 \n"
161+
"a 0o1 x0o4 x0o11 x0o13 x0o14 \n"
162+
"b 0o2 x0o27 x0o53 x0o170 x0o0 \n"
163+
)
164+
self.check_rendered_trace(expected, repr_func=oct, symbol_len=None)
165+
166+
def test_bin_trace(self):
167+
expected = (
168+
" -0 \n"
169+
"a 0b1 x0b100 x0b1001 x0b1011 x0b1100 \n"
170+
"b 0b10 x0b10111 x0b101011 x0b1111000 x0b0 \n"
171+
)
172+
self.check_rendered_trace(expected, repr_func=bin, symbol_len=None)
173+
174+
def test_decimal_trace(self):
175+
expected = (
176+
" -0 \n"
177+
"a 1 x4 x9 x11 x12 \n"
178+
"b 2 x23 x43 x120 x0 \n"
179+
)
180+
self.check_rendered_trace(expected, repr_func=str, symbol_len=None)
181+
182+
183+
class RenderTraceCustomBase(unittest.TestCase):
184+
def setUp(self):
185+
pyrtl.reset_working_block()
186+
187+
def test_custom_repr_per_wire(self):
188+
from enum import IntEnum
189+
190+
class Foo(IntEnum):
191+
A = 0
192+
B = 1
193+
C = 2
194+
D = 3
195+
196+
i = pyrtl.Input(4, 'i')
197+
state = pyrtl.Register(max(Foo).bit_length(), name='state')
198+
o = pyrtl.Output(name='o')
199+
o <<= state
200+
201+
with pyrtl.conditional_assignment:
202+
with i == 0b0001:
203+
state.next |= Foo.A
204+
with i == 0b0010:
205+
state.next |= Foo.B
206+
with i == 0b0100:
207+
state.next |= Foo.C
208+
with i == 0b1000:
209+
state.next |= Foo.D
210+
211+
sim = pyrtl.Simulation()
212+
sim.step_multiple({
213+
'i': [1, 2, 4, 8, 0]
214+
})
215+
buff = io.StringIO()
216+
sim.tracer.render_trace(file=buff, render_cls=pyrtl.simulation.AsciiWaveRenderer,
217+
extra_line=None, repr_per_name={'state': Foo}, symbol_len=None)
218+
expected = (
219+
" -0 \n"
220+
" i 0x1 x0x2 x0x4 x0x8 x0x0 \n"
221+
" o 0x0 x0x1 x0x2 x0x3 \n"
222+
"state Foo.A xFoo.B xFoo.C xFoo.D \n"
223+
)
224+
self.assertEqual(buff.getvalue(), expected)
225+
226+
131227
class PrintTraceBase(unittest.TestCase):
132228
# note: doesn't include tests for compact=True because all the tests test that
133229
def setUp(self):

0 commit comments

Comments
 (0)