speed up ==, hash, <, >, <=, and >= for plain_data
[nmutil.git] / src / nmutil / test / example_gtkwave.py
1 """Generation of GTKWave documents with nmutil.gtkw"""
2
3 from nmigen import Elaboratable, Signal, Module, Cat
4 from nmigen.back.pysim import Simulator
5 from nmigen.cli import rtlil
6 from math import log2
7
8 from vcd.gtkw import GTKWSave, GTKWColor
9 from nmutil.gtkw import write_gtkw
10
11
12 class Shifter(Elaboratable):
13 """Simple sequential shifter
14
15 * "Prev" port:
16
17 * ``p_i_data``: value to be shifted
18
19 * ``p_shift_i``: shift amount
20
21 * ``op__sdir``: shift direction (0 = left, 1 = right)
22
23 * ``p_i_valid`` and ``p_o_ready``: handshake
24
25 * "Next" port:
26
27 * ``n_o_data``: shifted value
28
29 * ``n_o_valid`` and ``n_i_ready``: handshake
30 """
31 def __init__(self, width):
32 self.width = width
33 """data width"""
34 self.p_i_data = Signal(width)
35 self.p_shift_i = Signal(width)
36 self.op__sdir = Signal()
37 self.p_i_valid = Signal()
38 self.p_o_ready = Signal()
39 self.n_o_data = Signal(width)
40 self.n_o_valid = Signal()
41 self.n_i_ready = Signal()
42
43 def elaborate(self, _):
44 m = Module()
45
46 # the control signals
47 load = Signal()
48 shift = Signal()
49 direction = Signal()
50 # the data flow
51 shift_in = Signal(self.width)
52 shift_left_by_1 = Signal(self.width)
53 shift_right_by_1 = Signal(self.width)
54 next_shift = Signal(self.width)
55 # the register
56 shift_reg = Signal(self.width, reset_less=True)
57 # build the data flow
58 m.d.comb += [
59 # connect input and output
60 shift_in.eq(self.p_i_data),
61 self.n_o_data.eq(shift_reg),
62 # generate shifted views of the register
63 shift_left_by_1.eq(Cat(0, shift_reg[:-1])),
64 shift_right_by_1.eq(Cat(shift_reg[1:], 0)),
65 ]
66 # choose the next value of the register according to the
67 # control signals
68 # default is no change
69 m.d.comb += next_shift.eq(shift_reg)
70 with m.If(load):
71 m.d.comb += next_shift.eq(shift_in)
72 with m.Elif(shift):
73 with m.If(direction):
74 m.d.comb += next_shift.eq(shift_right_by_1)
75 with m.Else():
76 m.d.comb += next_shift.eq(shift_left_by_1)
77
78 # register the next value
79 m.d.sync += shift_reg.eq(next_shift)
80
81 # Shift counter
82 shift_width = int(log2(self.width)) + 1
83 next_count = Signal(shift_width)
84 count = Signal(shift_width, reset_less=True)
85 m.d.sync += count.eq(next_count)
86
87 with m.FSM():
88 with m.State("IDLE"):
89 m.d.comb += [
90 # keep p.o_ready active on IDLE
91 self.p_o_ready.eq(1),
92 # keep loading the shift register and shift count
93 load.eq(1),
94 next_count.eq(self.p_shift_i),
95 ]
96 # capture the direction bit as well
97 m.d.sync += direction.eq(self.op__sdir)
98 with m.If(self.p_i_valid):
99 # Leave IDLE when data arrives
100 with m.If(next_count == 0):
101 # short-circuit for zero shift
102 m.next = "DONE"
103 with m.Else():
104 m.next = "SHIFT"
105 with m.State("SHIFT"):
106 m.d.comb += [
107 # keep shifting, while counter is not zero
108 shift.eq(1),
109 # decrement the shift counter
110 next_count.eq(count - 1),
111 ]
112 with m.If(next_count == 0):
113 # exit when shift counter goes to zero
114 m.next = "DONE"
115 with m.State("DONE"):
116 # keep n_o_valid active while the data is not accepted
117 m.d.comb += self.n_o_valid.eq(1)
118 with m.If(self.n_i_ready):
119 # go back to IDLE when the data is accepted
120 m.next = "IDLE"
121
122 return m
123
124 def __iter__(self):
125 yield self.op__sdir
126 yield self.p_i_data
127 yield self.p_shift_i
128 yield self.p_i_valid
129 yield self.p_o_ready
130 yield self.n_i_ready
131 yield self.n_o_valid
132 yield self.n_o_data
133
134 def ports(self):
135 return list(self)
136
137
138 def write_gtkw_direct():
139 """Write a formatted GTKWave "save" file, using vcd.gtkw directly"""
140 # hierarchy path, to prepend to signal names
141 dut = "top.shf."
142 # color styles
143 style_input = GTKWColor.orange
144 style_output = GTKWColor.yellow
145 style_debug = GTKWColor.red
146 with open("test_shifter_direct.gtkw", "wt") as gtkw_file:
147 gtkw = GTKWSave(gtkw_file)
148 gtkw.comment("Auto-generated by " + __file__)
149 gtkw.dumpfile("test_shifter.vcd")
150 # set a reasonable zoom level
151 # also, move the marker to an interesting place
152 gtkw.zoom_markers(-22.9, 10500000)
153 gtkw.trace(dut + "clk")
154 # place a comment in the signal names panel
155 gtkw.blank("Shifter Demonstration")
156 with gtkw.group("prev port"):
157 gtkw.trace(dut + "op__sdir", color=style_input)
158 # demonstrates using decimal base (default is hex)
159 gtkw.trace(dut + "p_i_data[7:0]", color=style_input,
160 datafmt='dec')
161 gtkw.trace(dut + "p_shift_i[7:0]", color=style_input,
162 datafmt='dec')
163 gtkw.trace(dut + "p_i_valid", color=style_input)
164 gtkw.trace(dut + "p_o_ready", color=style_output)
165 with gtkw.group("debug"):
166 gtkw.blank("Some debug statements")
167 # change the displayed name in the panel
168 gtkw.trace("top.zero", alias='zero delay shift',
169 color=style_debug)
170 gtkw.trace("top.interesting", color=style_debug)
171 gtkw.trace("top.test_case", alias="test case", color=style_debug)
172 gtkw.trace("top.msg", color=style_debug)
173 with gtkw.group("internal"):
174 gtkw.trace(dut + "fsm_state")
175 gtkw.trace(dut + "count[3:0]")
176 gtkw.trace(dut + "shift_reg[7:0]", datafmt='dec')
177 with gtkw.group("next port"):
178 gtkw.trace(dut + "n_o_data[7:0]", color=style_output,
179 datafmt='dec')
180 gtkw.trace(dut + "n_o_valid", color=style_output)
181 gtkw.trace(dut + "n_i_ready", color=style_input)
182
183
184 def test_shifter():
185 """Simulate the Shifter to generate some traces,
186 as well as the GTKWave documents"""
187 m = Module()
188 m.submodules.shf = dut = Shifter(8)
189 print("Shifter port names:")
190 for port in dut:
191 print("-", port.name)
192 # generate RTLIL
193 # try "proc; show" in yosys to check the data path
194 il = rtlil.convert(dut, ports=dut.ports())
195 with open("test_shifter.il", "w") as f:
196 f.write(il)
197
198 # write the GTKWave project file, directly
199 write_gtkw_direct()
200
201 # Describe a GTKWave document
202
203 # Style for signals, classes and groups
204 gtkwave_style = {
205 # Root selector. Gives default attributes for every signal.
206 '': {'base': 'dec'},
207 # color the traces, according to class
208 # class names are not hardcoded, they are just strings
209 'in': {'color': 'orange'},
210 'out': {'color': 'yellow'},
211 # signals in the debug group have a common color and module path
212 'debug': {'module': 'top', 'color': 'red'},
213 # display a different string replacing the signal name
214 'test_case': {'display': 'test case'},
215 }
216
217 # DOM style description for the trace pane
218 gtkwave_desc = [
219 # simple signal, without a class
220 # even so, it inherits the top-level root attributes
221 'clk',
222 # comment
223 {'comment': 'Shifter Demonstration'},
224 # collapsible signal group
225 ('prev port', [
226 # attach a class style for each signal
227 ('op__sdir', 'in'),
228 ('p_i_data[7:0]', 'in'),
229 ('p_shift_i[7:0]', 'in'),
230 ('p_i_valid', 'in'),
231 ('p_o_ready', 'out'),
232 ]),
233 # Signals in a signal group inherit the group attributes.
234 # In this case, a different module path and color.
235 ('debug', [
236 {'comment': 'Some debug statements'},
237 # inline attributes, instead of a class name
238 ('zero', {'display': 'zero delay shift'}),
239 'interesting',
240 'test_case',
241 'msg',
242 ]),
243 ('internal', [
244 'fsm_state',
245 'count[3:0]',
246 'shift_reg[7:0]',
247 ]),
248 ('next port', [
249 ('n_o_data[7:0]', 'out'),
250 ('n_o_valid', 'out'),
251 ('n_i_ready', 'in'),
252 ]),
253 ]
254
255 write_gtkw("test_shifter.gtkw", "test_shifter.vcd",
256 gtkwave_desc, gtkwave_style,
257 module="top.shf", loc=__file__, marker=10500000)
258
259 sim = Simulator(m)
260 sim.add_clock(1e-6)
261
262 # demonstrates adding extra debug signal traces
263 # they end up in the top module
264 #
265 zero = Signal() # mark an interesting place
266 #
267 # demonstrates string traces
268 #
269 # display a message when the signal is high
270 # the low level is just an horizontal line
271 interesting = Signal(decoder=lambda v: 'interesting!' if v else '')
272 # choose between alternate strings based on numerical value
273 test_cases = ['', '13>>2', '3<<4', '21<<0']
274 test_case = Signal(8, decoder=lambda v: test_cases[v])
275 # hack to display arbitrary strings, like debug statements
276 msg = Signal(decoder=lambda _: msg.str)
277 msg.str = ''
278
279 def send(data, shift, direction):
280 # present input data and assert i_valid
281 yield dut.p_i_data.eq(data)
282 yield dut.p_shift_i.eq(shift)
283 yield dut.op__sdir.eq(direction)
284 yield dut.p_i_valid.eq(1)
285 yield
286 # wait for p.o_ready to be asserted
287 while not (yield dut.p_o_ready):
288 yield
289 # show current operation operation
290 if direction:
291 msg.str = f'{data}>>{shift}'
292 else:
293 msg.str = f'{data}<<{shift}'
294 # force dump of the above message by toggling the
295 # underlying signal
296 yield msg.eq(0)
297 yield msg.eq(1)
298 # clear input data and negate p.i_valid
299 yield dut.p_i_valid.eq(0)
300 yield dut.p_i_data.eq(0)
301 yield dut.p_shift_i.eq(0)
302 yield dut.op__sdir.eq(0)
303
304 def receive(expected):
305 # signal readiness to receive data
306 yield dut.n_i_ready.eq(1)
307 yield
308 # wait for n.o_valid to be asserted
309 while not (yield dut.n_o_valid):
310 yield
311 # read result
312 result = yield dut.n_o_data
313 # negate n.i_ready
314 yield dut.n_i_ready.eq(0)
315 # check result
316 assert result == expected
317 # finish displaying the current operation
318 msg.str = ''
319 yield msg.eq(0)
320 yield msg.eq(1)
321
322 def producer():
323 # 13 >> 2
324 yield from send(13, 2, 1)
325 # 3 << 4
326 yield from send(3, 4, 0)
327 # 21 << 0
328 # use a debug signal to mark an interesting operation
329 # in this case, it is a shift by zero
330 yield interesting.eq(1)
331 yield from send(21, 0, 0)
332 yield interesting.eq(0)
333
334 def consumer():
335 # the consumer is not in step with the producer, but the
336 # order of the results are preserved
337 # 13 >> 2 = 3
338 yield test_case.eq(1)
339 yield from receive(3)
340 # 3 << 4 = 48
341 yield test_case.eq(2)
342 yield from receive(48)
343 # 21 << 0 = 21
344 yield test_case.eq(3)
345 # you can look for the rising edge of this signal to quickly
346 # locate this point in the traces
347 yield zero.eq(1)
348 yield from receive(21)
349 yield zero.eq(0)
350 yield test_case.eq(0)
351
352 sim.add_sync_process(producer)
353 sim.add_sync_process(consumer)
354 sim_writer = sim.write_vcd(
355 "test_shifter.vcd",
356 # include additional signals in the trace dump
357 traces=[zero, interesting, test_case, msg],
358 )
359 with sim_writer:
360 sim.run()
361
362
363 if __name__ == "__main__":
364 test_shifter()