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