5327bc0493b170b3b4fc9621d991db6b6bd580bc
[nmutil.git] / src / nmutil / gtkw.py
1 from vcd.gtkw import GTKWSave, GTKWColor
2
3
4 def write_gtkw(gtkw_name, vcd_name, gtkw_dom, gtkw_style=None,
5 module=None, loc=None, color=None, base=None,
6 zoom=-22.9, marker=-1):
7 """ Write a GTKWave document according to the supplied style and DOM.
8
9 :param gtkw_name: name of the generated GTKWave document
10 :param vcd_name: name of the waveform file
11 :param gtkw_dom: DOM style description for the trace pane
12 :param gtkw_style: style for signals, classes and groups
13 :param module: default module
14 :param color: default trace color
15 :param base: default numerical base
16 :param loc: source code location to include as a comment
17 :param zoom: initial zoom level, in GTKWave format
18 :param marker: initial location of a marker
19
20 **gtkw_style format**
21
22 Syntax: ``{selector: {attribute: value, ...}, ...}``
23
24 "selector" can be a signal, class or group
25
26 Signal groups propagate most attributes to their children
27
28 Attribute choices:
29
30 * module: instance path, for prepending to the signal name
31 * color: trace color
32 * base: numerical base for value display
33 * display: alternate text to display in the signal pane
34 * comment: comment to display in the signal pane
35
36 **gtkw_dom format**
37
38 Syntax: ``[signal, (signal, class), (group, [children]), comment, ...]``
39
40 The DOM is a list of nodes.
41
42 Nodes are signals, signal groups or comments.
43
44 * signals are strings, or tuples: ``(signal name, class, class, ...)``
45 * signal groups are tuples: ``(group name, class, class, ..., [nodes])``
46 * comments are: ``{'comment': 'comment string'}``
47
48 In place of a class name, an inline class description can be used.
49 ``(signal, {attribute: value, ...}, ...)``
50 """
51 colors = {
52 'blue': GTKWColor.blue,
53 'cycle': GTKWColor.cycle,
54 'green': GTKWColor.green,
55 'indigo': GTKWColor.indigo,
56 'normal': GTKWColor.normal,
57 'orange': GTKWColor.orange,
58 'red': GTKWColor.red,
59 'violet': GTKWColor.violet,
60 'yellow': GTKWColor.yellow,
61 }
62
63 with open(gtkw_name, "wt") as gtkw_file:
64 gtkw = GTKWSave(gtkw_file)
65 if loc is not None:
66 gtkw.comment("Auto-generated by " + loc)
67 gtkw.dumpfile(vcd_name)
68 # set a reasonable zoom level
69 # also, move the marker to an interesting place
70 gtkw.zoom_markers(zoom, marker)
71
72 # create an empty style, if needed
73 if gtkw_style is None:
74 gtkw_style = dict()
75
76 # create an empty root selector, if needed
77 root_style = gtkw_style.get('', dict())
78
79 # apply styles to the root selector, if provided
80 if module is not None:
81 root_style['module'] = module
82 if color is not None:
83 root_style['color'] = color
84 if base is not None:
85 root_style['base'] = base
86 # base cannot be None, use 'hex' by default
87 if root_style.get('base') is None:
88 root_style['base'] = 'hex'
89
90 # recursively walk the DOM
91 def walk(dom, style):
92 for node in dom:
93 node_name = None
94 children = None
95 # copy the style from the parent
96 node_style = style.copy()
97 # node is a signal name string
98 if isinstance(node, str):
99 node_name = node
100 # apply style from node name, if specified
101 if node_name in gtkw_style:
102 node_style.update(gtkw_style[node_name])
103 # node is a tuple
104 # could be a signal or a group
105 elif isinstance(node, tuple):
106 node_name = node[0]
107 # collect styles from the selectors
108 # order goes from the most specific to most generic
109 # which means earlier selectors override later ones
110 for selector in reversed(node):
111 # update the node style from the selector
112 if isinstance(selector, str):
113 if selector in gtkw_style:
114 node_style.update(gtkw_style[selector])
115 # apply an inline style description
116 elif isinstance(selector, dict):
117 node_style.update(selector)
118 # node is a group if it has a child list
119 if isinstance(node[-1], list):
120 children = node[-1]
121 # comment
122 elif isinstance(node, dict):
123 if 'comment' in node:
124 gtkw.blank(node['comment'])
125 # emit the group delimiters and walk over the child list
126 if children is not None:
127 gtkw.begin_group(node_name)
128 # pass on the group style to its children
129 walk(children, node_style)
130 gtkw.end_group(node_name)
131 # emit a trace, if the node is a signal
132 elif node_name is not None:
133 signal_name = node_name
134 # prepend module name to signal
135 if 'module' in node_style:
136 node_module = node_style['module']
137 if node_module is not None:
138 signal_name = node_module + '.' + signal_name
139 node_color = colors.get(node_style.get('color'))
140 node_base = node_style.get('base')
141 display = node_style.get('display')
142 gtkw.trace(signal_name, color=node_color,
143 datafmt=node_base, alias=display)
144
145 walk(gtkw_dom, root_style)