Added gtkw doc generation to simple gpio block test
[pinmux.git] / src / spec / simple_gpio.py
1 """Simple GPIO peripheral on wishbone
2
3 This is an extremely simple GPIO peripheral intended for use in XICS
4 testing, however it could also be used as an actual GPIO peripheral
5
6 Modified for use with pinmux, will probably change the class name later.
7 """
8 from random import randint
9 from math import ceil, floor
10 from nmigen import Elaboratable, Module, Signal, Record, Array, Cat
11 from nmigen.hdl.rec import Layout
12 from nmigen.utils import log2_int
13 from nmigen.cli import rtlil
14 from soc.minerva.wishbone import make_wb_layout
15 from nmutil.util import wrap
16 from soc.bus.test.wb_rw import wb_read, wb_write
17
18 from nmutil.gtkw import write_gtkw
19
20 cxxsim = False
21 if cxxsim:
22 from nmigen.sim.cxxsim import Simulator, Settle
23 else:
24 from nmigen.sim import Simulator, Settle
25
26 # Layout of 8-bit configuration word:
27 # bank[2:0] i/o | pden puen ien oe
28 NUMBANKBITS = 3 # max 3 bits, only supporting 4 banks (0-3)
29 csrbus_layout = (("oe", 1),
30 ("ie", 1),
31 ("puen", 1),
32 ("pden", 1),
33 ("io", 1),
34 ("bank", NUMBANKBITS)
35 )
36
37 gpio_layout = (("i", 1),
38 ("oe", 1),
39 ("o", 1),
40 ("puen", 1),
41 ("pden", 1),
42 ("bank", NUMBANKBITS)
43 )
44
45 class SimpleGPIO(Elaboratable):
46
47 def __init__(self, wordsize=4, n_gpio=16):
48 print("SimpleGPIO: WB Data # of bytes: {0}, # of GPIOs: {1}"
49 .format(wordsize, n_gpio))
50 self.wordsize = wordsize
51 self.n_gpio = n_gpio
52 class Spec: pass
53 spec = Spec()
54 spec.addr_wid = 30
55 spec.mask_wid = 4
56 spec.reg_wid = wordsize*8 # 32
57 self.bus = Record(make_wb_layout(spec), name="gpio_wb")
58
59 print("CSRBUS layout: ", csrbus_layout)
60 # create array - probably a cleaner way to do this...
61 temp = []
62 for i in range(self.wordsize):
63 temp_str = "word{}".format(i)
64 temp.append(Record(name=temp_str, layout=csrbus_layout))
65 self.multicsrbus = Array(temp)
66
67 temp = []
68 for i in range(self.n_gpio):
69 temp_str = "gpio{}".format(i)
70 temp.append(Record(name=temp_str, layout=gpio_layout))
71 self.gpio_ports = Array(temp)
72
73 def elaborate(self, platform):
74 m = Module()
75 comb, sync = m.d.comb, m.d.sync
76
77 bus = self.bus
78 wb_rd_data = bus.dat_r
79 wb_wr_data = bus.dat_w
80 wb_ack = bus.ack
81
82 gpio_ports = self.gpio_ports
83 multi = self.multicsrbus
84
85 comb += wb_ack.eq(0)
86
87 row_start = Signal(log2_int(self.n_gpio))
88 # Flag for indicating rd/wr transactions
89 new_transaction = Signal(1)
90
91 #print("Types:")
92 #print("gpio_addr: ", type(gpio_addr))
93
94 # One address used to configure CSR, set output, read input
95 with m.If(bus.cyc & bus.stb):
96 comb += wb_ack.eq(1) # always ack
97 # Probably wasteful
98 sync += row_start.eq(bus.adr * self.wordsize)
99 sync += new_transaction.eq(1)
100 with m.If(bus.we): # write
101 # Configure CSR
102 for byte in range(0, self.wordsize):
103 sync += multi[byte].eq(wb_wr_data[byte*8:8+byte*8])
104 with m.Else(): # read
105 # Concatinate the GPIO configs that are on the same "row" or
106 # address and send
107 multi_cat = []
108 for i in range(0, self.wordsize):
109 multi_cat.append(multi[i])
110 comb += wb_rd_data.eq(Cat(multi_cat))
111 with m.Else():
112 sync += new_transaction.eq(0)
113 # Update the state of "io" while no WB transactions
114 for byte in range(0, self.wordsize):
115 with m.If(gpio_ports[row_start+byte].oe):
116 sync += multi[byte].io.eq(gpio_ports[row_start+byte].o)
117 with m.Else():
118 sync += multi[byte].io.eq(gpio_ports[row_start+byte].i)
119 # Only update GPIOs config if a new transaction happened last cycle
120 # (read or write). Always lags from multi csrbus by 1 clk cycle, most
121 # sane way I could think of while using Record().
122 with m.If(new_transaction):
123 for byte in range(0, self.wordsize):
124 sync += gpio_ports[row_start+byte].oe.eq(multi[byte].oe)
125 sync += gpio_ports[row_start+byte].puen.eq(multi[byte].puen)
126 sync += gpio_ports[row_start+byte].pden.eq(multi[byte].pden)
127 # Check to prevent output being set if GPIO configured as input
128 # TODO: No checking is done if ie/oe high together
129 with m.If(gpio_ports[row_start+byte].oe):
130 sync += gpio_ports[row_start+byte].o.eq(multi[byte].io)
131 sync += gpio_ports[row_start+byte].bank.eq(multi[byte].bank)
132 return m
133
134 def __iter__(self):
135 for field in self.bus.fields.values():
136 yield field
137 #yield self.gpio_o
138
139 def ports(self):
140 return list(self)
141
142 def gpio_test_in_pattern(dut, pattern):
143 num_gpios = len(dut.gpio_ports)
144 print("Test pattern:")
145 print(pattern)
146 for pat in range(0, len(pattern)):
147 for gpio in range(0, num_gpios):
148 yield from gpio_set_in_pad(dut, gpio, pattern[pat])
149 yield
150 temp = yield from gpio_rd_input(dut, gpio)
151 print("Pattern: {0}, Reading {1}".format(pattern[pat], temp))
152 assert (temp == pattern[pat])
153 pat += 1
154 if pat == len(pattern):
155 break
156
157 def test_gpio_single(dut, gpio, use_random=True):
158 oe = 1
159 ie = 0
160 output = 0
161 puen = 0
162 pden = 0
163 if use_random:
164 bank = randint(0, (2**NUMBANKBITS)-1)
165 print("Random bank select: {0:b}".format(bank))
166 else:
167 bank = 3 # not special, chose for testing
168
169 gpio_csr = yield from gpio_config(dut, gpio, oe, ie, puen, pden, output,
170 bank, check=True)
171 # Enable output
172 output = 1
173 gpio_csr = yield from gpio_config(dut, gpio, oe, ie, puen, pden, output,
174 bank, check=True)
175
176 # Shadow reg container class
177 class GPIOConfigReg():
178 def __init__(self):
179 self.oe=0
180 self.ie=0
181 self.puen=0
182 self.pden=0
183 self.io=0
184 self.bank=0
185
186 def set(self, oe=0, ie=0, puen=0, pden=0, outval=0, bank=0):
187 self.oe=oe
188 self.ie=ie
189 self.puen=puen
190 self.pden=pden
191 self.io=outval
192 self.bank=bank
193
194 def set_out(self, outval):
195 self.io=outval
196
197 # Object for storing each gpio's config state
198
199 class GPIOManager():
200 def __init__(self, dut, layout):
201 self.dut = dut
202 # arrangement of config bits making up csr word
203 self.csr_layout = layout
204 self.shift_dict = self._create_shift_dict()
205 self.n_gpios = len(self.dut.gpio_ports)
206 print(dir(self.dut))
207 # Since GPIO HDL block already has wordsize parameter, use directly
208 # Alternatively, can derive from WB data r/w buses (div by 8 for bytes)
209 #self.wordsize = len(self.dut.gpio_wb__dat_w) / 8
210 self.wordsize = self.dut.wordsize
211 self.n_rows = ceil(self.n_gpios / self.wordsize)
212 self.shadow_csr = []
213 for i in range(self.n_gpios):
214 self.shadow_csr.append(GPIOConfigReg())
215
216 # The shifting of control bits in the configuration word is dependent on the
217 # defined layout. To prevent maintaining the shift constants in a separate
218 # location, the same layout is used to generate a dictionary of bit shifts
219 # with which the configuration word can be produced!
220 def _create_shift_dict(self):
221 shift = 0
222 shift_dict = {}
223 for i in range(0, len(self.csr_layout)):
224 shift_dict[self.csr_layout[i][0]] = shift
225 shift += self.csr_layout[i][1]
226 print(shift_dict)
227 return shift_dict
228
229 def _parse_gpio_arg(self, gpio_str):
230 # TODO: No input checking!
231 print("Given GPIO/range string: {}".format(gpio_str))
232 if gpio_str == "all":
233 start = 0
234 end = self.n_gpios
235 elif '-' in gpio_str:
236 start, end = gpio_str.split('-')
237 start = int(start)
238 end = int(end) + 1
239 if (end < start) or (end > self.n_gpios):
240 raise Exception("Second GPIO must be higher than first and"
241 + " must be lower or equal to last available GPIO.")
242 else:
243 start = int(gpio_str)
244 if start >= self.n_gpios:
245 raise Exception("GPIO must be less/equal to last GPIO.")
246 end = start + 1
247 print("Setting config for GPIOs {0} until {1}".format(start, end))
248 return start, end
249
250 # Take config parameters of specified GPIOs, and combine them to produce
251 # bytes for sending via WB bus
252 def _pack_csr(self, start, end):
253 #start, end = self._parse_gpio_arg(gpio_str)
254 num_csr = end-start
255 csr = [0] * num_csr
256 for i in range(0, num_csr):
257 gpio = i + start
258 print("Pack: gpio{}".format(gpio))
259 csr[i] = ((self.shadow_csr[gpio].oe << self.shift_dict['oe'])
260 | (self.shadow_csr[gpio].ie << self.shift_dict['ie'])
261 | (self.shadow_csr[gpio].puen << self.shift_dict['puen'])
262 | (self.shadow_csr[gpio].pden << self.shift_dict['pden'])
263 | (self.shadow_csr[gpio].io << self.shift_dict['io'])
264 | (self.shadow_csr[gpio].bank << self.shift_dict['bank']))
265
266 print("GPIO{0} Packed CSR: {1:x}".format(gpio, csr[i]))
267
268 return csr # return the config byte list
269
270 def rd_csr(self, row_start):
271 row_word = yield from wb_read(self.dut.bus, row_start)
272 print("Returned CSR: {0:x}".format(row_word))
273 return row_word
274
275 def rd_input(self, row_start):
276 in_val = yield from wb_read(dut.bus, gpio)
277 in_val = (in_val >> IOSHIFT) & 1
278 print("GPIO{0} | Input: {1:b}".format(gpio, in_val))
279 return in_val
280
281 def print_info(self):
282 print("----------")
283 print("GPIO Block Info:")
284 print("Number of GPIOs: {}".format(self.n_gpios))
285 print("WB Data bus width (in bytes): {}".format(self.wordsize))
286 print("Number of rows: {}".format(self.n_rows))
287 print("----------")
288
289 # Write all shadow registers to GPIO block
290 def wr_all(self):
291 # UPDATE using ALL shadow registers
292 csr = self._pack_csr(0, self.n_gpios)
293 #start_addr = floor(start / self.wordsize)
294 start_addr = 0
295 curr_gpio = 0
296 for row in range(0, self.n_rows):
297 row_word = 0
298 start_byte = curr_gpio % self.wordsize
299 for byte in range(start_byte, self.wordsize):
300 if curr_gpio > self.n_gpios:
301 break
302 #row_word += csr[byte] << (8 * byte)
303 row_word += csr[curr_gpio] << (8 * byte)
304 curr_gpio += 1
305 print("Configuring CSR to {0:x} to addr: {1:x}"
306 .format(row_word, start_addr+row))
307 yield from wb_write(self.dut.bus, start_addr+row, row_word)
308 yield # Allow one clk cycle to propagate
309
310 if(True): #check):
311 test_row = yield from self.rd_csr(start_addr+row)
312 assert row_word == test_row
313
314
315 def config(self, gpio_str, oe, ie, puen, pden, outval, bank, check=False):
316 start, end = self._parse_gpio_arg(gpio_str)
317 # Update the shadow configuration
318 for gpio in range(start, end):
319 print(oe, ie, puen, pden, outval, bank)
320 self.shadow_csr[gpio].set(oe, ie, puen, pden, outval, bank)
321
322 yield from self.wr_all()
323
324 # Set/Clear the output bit for single or group of GPIOs
325 def set_out(self, gpio_str, outval):
326 start, end = self._parse_gpio_arg(gpio_str)
327 for gpio in range(start, end):
328 self.shadow_csr[gpio].set_out(outval)
329
330 if start == end:
331 print("Setting GPIO{0} output to {1}".format(start, outval))
332 else:
333 print("Setting GPIOs {0}-{1} output to {2}"
334 .format(start, end-1, outval))
335
336 yield from self.wr_all()
337 """
338 # Not used normally - only for debug
339 def reg_write(dut, gpio, reg_val):
340 print("Configuring CSR to {0:x}".format(reg_val))
341 yield from wb_write(dut.bus, gpio, reg_val)
342
343 # TODO: There's probably a cleaner way to clear the bit...
344 def gpio_set_in_pad(dut, gpio, in_val):
345 old_in_val = yield dut.gpio_i
346 if in_val:
347 new_in_val = old_in_val | (in_val << gpio)
348 else:
349 temp = (old_in_val >> gpio) & 1
350 if temp:
351 mask = ~(1 << gpio)
352 new_in_val = old_in_val & mask
353 else:
354 new_in_val = old_in_val
355 print("Previous GPIO i: {0:b} | New GPIO i: {1:b}"
356 .format(old_in_val, new_in_val))
357 yield dut.gpio_i.eq(new_in_val)
358 yield # Allow one clk cycle to propagate
359 """
360
361 def sim_gpio(dut, use_random=True):
362 #print(dut)
363 #print(dir(dut.gpio_ports))
364 #print(len(dut.gpio_ports))
365
366 gpios = GPIOManager(dut, csrbus_layout)
367 gpios.print_info()
368 # TODO: not working yet
369 #test_pattern = []
370 #for i in range(0, (num_gpios * 2)):
371 # test_pattern.append(randint(0,1))
372 #yield from gpio_test_in_pattern(dut, test_pattern)
373
374 #yield from gpio_config(dut, start_gpio, oe, ie, puen, pden, outval, bank, end_gpio, check=False, wordsize=4)
375 #reg_val = 0xC56271A2
376 #reg_val = 0xFFFFFFFF
377 #yield from reg_write(dut, 0, reg_val)
378 #yield from reg_write(dut, 0, reg_val)
379 #yield
380
381 #csr_val = yield from wb_read(dut.bus, 0)
382 #print("CSR Val: {0:x}".format(csr_val))
383 print("Finished the simple GPIO block test!")
384
385 def gen_gtkw_doc(n_gpios, wordsize, filename):
386 # GTKWave doc generation
387 wb_data_width = wordsize*8
388 n_rows = ceil(n_gpios/wordsize)
389 style = {
390 '': {'base': 'hex'},
391 'in': {'color': 'orange'},
392 'out': {'color': 'yellow'},
393 'debug': {'module': 'top', 'color': 'red'}
394 }
395
396 # Create a trace list, each block expected to be a tuple()
397 traces = []
398 wb_traces = ('Wishbone Bus', [
399 ('gpio_wb__cyc', 'in'),
400 ('gpio_wb__stb', 'in'),
401 ('gpio_wb__we', 'in'),
402 ('gpio_wb__adr[27:0]', 'in'),
403 ('gpio_wb__dat_w[{}:0]'.format(wb_data_width-1), 'in'),
404 ('gpio_wb__dat_r[{}:0]'.format(wb_data_width-1), 'out'),
405 ('gpio_wb__ack', 'out'),
406 ])
407 traces.append(wb_traces)
408
409 gpio_internal_traces = ('Internal', [
410 ('clk', 'in'),
411 ('new_transaction'),
412 ('row_start[2:0]'),
413 ('rst', 'in')
414 ])
415 traces.append(gpio_internal_traces)
416
417 traces.append({'comment': 'Multi-byte GPIO config bus'})
418 for word in range(0, wordsize):
419 prefix = "word{}__".format(word)
420 single_word = []
421 word_signals = []
422 single_word.append('Word{}'.format(word))
423 word_signals.append((prefix+'bank[{}:0]'.format(NUMBANKBITS-1)))
424 word_signals.append((prefix+'ie'))
425 word_signals.append((prefix+'io'))
426 word_signals.append((prefix+'oe'))
427 word_signals.append((prefix+'pden'))
428 word_signals.append((prefix+'puen'))
429 single_word.append(word_signals)
430 traces.append(tuple(single_word))
431
432 for gpio in range(0, n_gpios):
433 prefix = "gpio{}__".format(gpio)
434 single_gpio = []
435 gpio_signals = []
436 single_gpio.append('GPIO{} Port'.format(gpio))
437 gpio_signals.append((prefix+'bank[{}:0]'.format(NUMBANKBITS-1), 'out'))
438 gpio_signals.append( (prefix+'i', 'in') )
439 gpio_signals.append( (prefix+'o', 'out') )
440 gpio_signals.append( (prefix+'oe', 'out') )
441 gpio_signals.append( (prefix+'pden', 'out') )
442 gpio_signals.append( (prefix+'puen', 'out') )
443 single_gpio.append(gpio_signals)
444 traces.append(tuple(single_gpio))
445
446 print(traces)
447
448 write_gtkw(filename+".gtkw", filename+".vcd", traces, style,
449 module="top.xics_icp")
450
451 def test_gpio():
452 filename = "test_gpio"
453 n_gpios = 8
454 wordsize = 4 # Number of bytes in the WB data word
455 dut = SimpleGPIO(wordsize, n_gpios)
456 vl = rtlil.convert(dut, ports=dut.ports())
457 with open("test_gpio.il", "w") as f:
458 f.write(vl)
459
460 m = Module()
461 m.submodules.xics_icp = dut
462
463 sim = Simulator(m)
464 sim.add_clock(1e-6)
465
466 #sim.add_sync_process(wrap(sim_gpio(dut, use_random=False)))
467 sim.add_sync_process(wrap(test_gpioman(dut)))
468 sim_writer = sim.write_vcd('test_gpio.vcd')
469 with sim_writer:
470 sim.run()
471
472 gen_gtkw_doc(n_gpios, wordsize, filename)
473
474 def test_gpioman(dut):
475 gpios = GPIOManager(dut, csrbus_layout)
476 gpios.print_info()
477 #gpios._parse_gpio_arg("all")
478 #gpios._parse_gpio_arg("0")
479 #gpios._parse_gpio_arg("1-3")
480 #gpios._parse_gpio_arg("20")
481
482 oe = 1
483 ie = 0
484 puen = 0
485 pden = 1
486 outval = 0
487 bank = 3
488 yield from gpios.config("0-3", oe=1, ie=0, puen=0, pden=1, outval=0, bank=2)
489 ie = 1
490 yield from gpios.config("4-7", oe=0, ie=1, puen=0, pden=1, outval=0, bank=2)
491 yield from gpios.set_out("0-3", outval=1)
492
493
494 if __name__ == '__main__':
495 test_gpio()
496