Skip to content

Commit 8bd006b

Browse files
committed
Add ability to specify render per wire name in trace
1 parent c9eb984 commit 8bd006b

File tree

4 files changed

+131
-26
lines changed

4 files changed

+131
-26
lines changed

pyrtl/simulation.py

Lines changed: 37 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -876,35 +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, repr_func):
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, repr_func)
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, repr_func):
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
894894
:param repr_func: function to use for representing the current_val;
895-
examples are 'hex', 'oct', 'bin', and 'str' (for decimal). Defaults to 'hex'.
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.
896900
897901
Returns a string of printed length symbol_len that will draw the
898902
representation of current_val. The input prior_val is used to
899903
render transitions.
900904
"""
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+
901912
sl = symbol_len - 1
902913
if len(w) > 1:
903914
out = self._revstart
904915
if current_val != self.prior_val:
905-
out += self._x + repr_func(current_val).rstrip('L').ljust(sl)[:sl]
916+
out += self._x + to_str(current_val).rstrip('L').ljust(sl)[:sl]
906917
elif n == 0:
907-
out += repr_func(current_val).rstrip('L').ljust(symbol_len)[:symbol_len]
918+
out += to_str(current_val).rstrip('L').ljust(symbol_len)[:symbol_len]
908919
else:
909920
out += ' ' * symbol_len
910921
out += self._revstop
@@ -1132,7 +1143,8 @@ def print_trace_strs(time):
11321143

11331144
def render_trace(
11341145
self, trace_list=None, file=sys.stdout, render_cls=default_renderer(),
1135-
symbol_len=5, repr_func=hex, 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):
11361148

11371149
""" Render the trace to a file using unicode and ASCII escape sequences.
11381150
@@ -1144,6 +1156,9 @@ def render_trace(
11441156
represented value fits.
11451157
:param repr_func: Function to use for representing each value in the trace;
11461158
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.
11471162
:param segment_size: Traces are broken in the segments of this number of cycles.
11481163
:param segment_delim: The character to be output between segments.
11491164
:param extra_line: A Boolean to determine if we should print a blank line between signals.
@@ -1172,12 +1187,12 @@ def render_trace(
11721187
else:
11731188
self.render_trace_to_text(
11741189
trace_list=trace_list, file=file, render_cls=render_cls,
1175-
symbol_len=symbol_len, repr_func=repr_func, segment_size=segment_size,
1176-
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)
11771192

11781193
def render_trace_to_text(
11791194
self, trace_list, file, render_cls,
1180-
symbol_len, repr_func, segment_size, segment_delim, extra_line):
1195+
symbol_len, repr_func, repr_per_name, segment_size, segment_delim, extra_line):
11811196

11821197
renderer = render_cls()
11831198

@@ -1192,7 +1207,8 @@ def formatted_trace_line(wire, trace):
11921207
i % segment_size,
11931208
trace[i],
11941209
symbol_len,
1195-
repr_func)
1210+
repr_func,
1211+
repr_per_name)
11961212
return heading + trace_line
11971213

11981214
# default to printing all signals in sorted order
@@ -1212,9 +1228,17 @@ def formatted_trace_line(wire, trace):
12121228
"if a CompiledSimulation was used.")
12131229

12141230
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+
12151239
maxvallen = 0
1216-
for trace in self.trace.values():
1217-
maxvallen = max(maxvallen, max(len(repr_func(v)) for v in trace))
1240+
for name, trace in self.trace.items():
1241+
maxvallen = max(maxvallen, max(len(to_str(v, name)) for v in trace))
12181242
symbol_len = maxvallen + 1
12191243

12201244
# print the 'ruler' which is just a list of 'ticks'

pyrtl/visualization.py

Lines changed: 17 additions & 4 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, repr_func=hex):
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

@@ -506,16 +512,23 @@ def extract(w):
506512
wavelist.append(str(value))
507513
else:
508514
wavelist.append('=')
509-
datalist.append(value)
515+
datalist.append((value, w))
510516
last = value
511517

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+
512525
wavestring = ''.join(wavelist)
513-
datastring = ', '.join(['"%s"' % repr_func(data) for data in datalist])
526+
datastring = ', '.join(['"%s"' % to_str(data, name) for data, name in datalist])
514527
if len(simtrace._wires[w]) == 1:
515528
vallens.append(1) # all are the same length
516529
return bool_signal_template % (w, wavestring)
517530
else:
518-
vallens.extend([len(repr_func(data)) for data in datalist])
531+
vallens.extend([len(to_str(data, name)) for data, name in datalist])
519532
return int_signal_template % (w, wavestring, datastring)
520533

521534
bool_signal_template = ' { name: "%s", wave: "%s" },'

tests/test_simulation.py

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,50 @@ def test_decimal_trace(self):
180180
self.check_rendered_trace(expected, repr_func=str, symbol_len=None)
181181

182182

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+
183227
class PrintTraceBase(unittest.TestCase):
184228
# note: doesn't include tests for compact=True because all the tests test that
185229
def setUp(self):

tests/test_visualization.py

Lines changed: 33 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -382,26 +382,50 @@ def test_trace_to_html_repr_func(self):
382382
)
383383
self.assertEqual(htmlstring, expected)
384384

385-
def test_trace_to_html_repr_func_2(self):
386-
i = pyrtl.Input(1, 'i')
387-
o = pyrtl.Output(2, 'o')
388-
o <<= i + 1
385+
def test_trace_to_html_repr_per_name(self):
386+
from enum import IntEnum
387+
388+
class Foo(IntEnum):
389+
A = 0
390+
B = 1
391+
C = 2
392+
D = 3
393+
394+
i = pyrtl.Input(4, 'i')
395+
state = pyrtl.Register(max(Foo).bit_length(), name='state')
396+
o = pyrtl.Output(name='o')
397+
o <<= state
398+
399+
with pyrtl.conditional_assignment:
400+
with i == 0b0001:
401+
state.next |= Foo.A
402+
with i == 0b0010:
403+
state.next |= Foo.B
404+
with i == 0b0100:
405+
state.next |= Foo.C
406+
with i == 0b1000:
407+
state.next |= Foo.D
389408

390409
sim = pyrtl.Simulation()
391-
sim.step_multiple({'i': '0100110'})
392-
htmlstring = pyrtl.trace_to_html(sim.tracer, repr_func=bin)
410+
sim.step_multiple({
411+
'i': [1, 2, 4, 8, 0]
412+
})
413+
414+
htmlstring = pyrtl.trace_to_html(sim.tracer, repr_per_name={'state': Foo})
393415
expected = (
394416
'<script type="WaveDrom">\n'
395417
'{\n'
396418
' signal : [\n'
397-
' { name: "i", wave: "010.1.0" },\n'
398-
' { name: "o", wave: "===.=.=", data: ["0b1", "0b10", "0b1", "0b10", "0b1"] },\n'
419+
' { name: "i", wave: "=====", data: ["0x1", "0x2", "0x4", "0x8", "0x0"] },\n'
420+
' { name: "o", wave: "=.===", data: ["0x0", "0x1", "0x2", "0x3"] },\n'
421+
' { name: "state", wave: "=.===", data: ["Foo.A", "Foo.B", "Foo.C", "Foo.D"] },\n'
399422
' ],\n'
400-
' config: { hscale: 1 }\n'
423+
' config: { hscale: 2 }\n'
401424
'}\n'
402425
'</script>\n'
403426
)
404427
self.assertEqual(htmlstring, expected)
405428

429+
406430
if __name__ == "__main__":
407431
unittest.main()

0 commit comments

Comments
 (0)