blob: bf93e172d5a53e391704ec438c9ad35ddb4fa1e2 [file] [log] [blame]
Mark Hayter14336e72019-12-29 16:21:34 -08001# Copyright lowRISC contributors.
2# Licensed under the Apache License, Version 2.0, see LICENSE for details.
3# SPDX-License-Identifier: Apache-2.0
4#
Scott Johnsonfe79c4b2020-07-08 10:31:08 -07005# Convert I2C host format to SVG
Mark Hayter14336e72019-12-29 16:21:34 -08006
7import logging as log
8from collections import namedtuple
9
10import i2csvg.i2csvg_data as sdata
11
12
13# validating version of int(x, 0)
14# returns int value, error flag
15# if error flag is True value will be zero
16def check_int(x, err_prefix):
17 if isinstance(x, int):
18 return x, False
19 if len(x) == 0:
20 log.error(err_prefix + " Zero length string")
21 return 0, True
22 if x[0] == '0' and len(x) > 2:
23 if x[1] in 'bB':
24 validch = '01'
25 elif x[1] in 'oO':
26 validch = '01234567'
27 elif x[1] in 'xX':
28 validch = '0123456789abcdefABCDEF'
29 else:
30 log.error(err_prefix +
31 ": int must start digit, 0b, 0B, 0o, 0O, 0x or 0X")
32 return 0, True
33 for c in x[2:]:
34 if not c in validch:
35 log.error(err_prefix + ": Bad character " + c + " in " + x)
36 return 0, True
37 else:
38 if not x.isdecimal():
39 log.error(err_prefix + ": Number not valid int " + x)
40 return 0, 1
41 return int(x, 0), False
42
43
44def check_single(line, char, fullline, err):
45 ''' Check for character char in input line
46 Return True if there is one (or more)
47 Propagate err or force to err True if more than one char
48 '''
49 res = False
50 if char in line:
51 res = True
52 if line.count(char) > 1:
53 log.warning('Multiple ' + char + ' in line ' + fullline)
54 err = True
55 return res, err
56
57
58I2cOp = namedtuple('I2cOp',
59 'read rcont start stop nackok mvalue adr size fbyte tag')
60
61
62def check_and_size(iic, line):
63 ''' Check I2C Op for validity and return size in bits
64 '''
65 err = False
66 if iic.start and iic.read:
67 log.error('Start and Read found in ' + line)
68 err = True
69 if iic.rcont and not iic.read:
70 log.error('RCont without Read found in ' + line)
71 err = True
72
73 # documentation says R+C and P is not permitted, but I think it
74 # is needed for protocols where the last read data is ACKed?
75 # (these don't match I2C or SMBus spec but exist in the wild)
76
77 size = 0
78 if iic.start:
79 size += 1
80 if iic.stop:
81 size += 1
82 if iic.read:
83 # multi is D0, D1, ..., Dn-1 so 3 byte/acks and a 1 bit squiggle
84 # regular read is one byte/ack per
85 size += 9 * 3 + 1 if iic.mvalue else 9 * iic.fbyte
86 else:
87 # write data is one byte/ack
88 size += 9
89 # rcont, nackok just affect the polarity of the final ack bit
90 # adr just affects how the write data is drawn
91 return size, err
92
93
94def parse_i2c_fifodata(line):
95 ''' Parse input line of I2C FDATA fifo and convert to internal type
96 Line is usually 0x + the hex value written to the register
97 But could be binary (0b), octal (0o) or decimal
98 '''
99 fifodata, err = check_int(line, 'FIFO value')
100 # bit values here must match the register definition!
101 ress = (fifodata & 0x0100) != 0
102 resp = (fifodata & 0x0200) != 0
103 resr = (fifodata & 0x0400) != 0
104 resc = (fifodata & 0x0800) != 0
105 resn = (fifodata & 0x1000) != 0
106 resb = fifodata & 0xff
107 resm = False # only used in descriptive case
108 resa = False # only used in descriptive case
109
110 tmpr = I2cOp(resr, resc, ress, resp, resn, resm, resa, 0, resb, None)
111 size, serr = check_and_size(tmpr, line)
112 if serr:
113 err = True
114 return I2cOp(resr, resc, ress, resp, resn, resm, resa, size, resb,
115 None), err
116
117
118def parse_i2c_code(line):
119 ''' Parse input line of I2C FDATA fifo and convert to internal type
120 Line is coded with flags and an 8-bit data value
121 S - Start flag, P - stop flag,
122 R - read flag, C - continue read flag, N - NackOk flag
123 followed by the data byte
Scott Johnsonfe79c4b2020-07-08 10:31:08 -0700124 Special cases:
Mark Hayter14336e72019-12-29 16:21:34 -0800125 M - indicates multiple bytes instead of data byte
126 A - followed by 0 or 1 address/direction or 2 address/data
127 Data value in quotes is a tag
128 '''
129 resr, resc, ress, resp, resn = False, False, False, False, False
130 resm, resa, resb = False, False, 0
131
132 err = False
133 firstval = 0
134 for i in line:
135 if i.isdigit() or i == "'":
136 break
137 firstval += 1
138 # Will only check the flags section, so no concern about hex digits
139 ress, err = check_single(line[:firstval], 'S', line, err)
140 resp, err = check_single(line[:firstval], 'P', line, err)
141 resr, err = check_single(line[:firstval], 'R', line, err)
142 resc, err = check_single(line[:firstval], 'C', line, err)
143 resn, err = check_single(line[:firstval], 'N', line, err)
144 # these two are formally part of the value but parse like flags
145 resm, err = check_single(line[:firstval], 'M', line, err)
146 resa, err = check_single(line[:firstval], 'A', line, err)
147
148 if firstval == len(line):
149 if not resm:
150 err = True
151 log.error('No value found in ' + line)
152 rest = None
153 else:
154 if resm:
155 err = True
156 log.error('Found M and value in ' + line)
157 rest = None
158 resb = 0
159 elif line[firstval] == "'":
160 rest = line[firstval + 1:-1]
161 resb = 0
162 else:
163 rest = None
164 resb, verr = check_int(line[firstval:],
165 'Value in ' + line + ' ' + str(firstval))
166 if verr:
167 err = True
168 if resb < 0 or resb > 255 or (resa and resb > 2):
169 log.error('Value out of range in ' + line)
170 resb = 0
171 err = True
172
173 tmpr = I2cOp(resr, resc, ress, resp, resn, resm, resa, 0, resb, rest)
174 size, serr = check_and_size(tmpr, line)
175 if serr:
176 err = True
177 return I2cOp(resr, resc, ress, resp, resn, resm, resa, size, resb,
178 rest), err
179
180
181def parse_file(infile, fifodata=False, prefix=None):
182 ''' Parse a file of I2C data
183 fifodata indicates if the data is a dump from writes to FDATA fifo
184 prefix is a prefix on valid lines and will be stripped
185 lines without the prefix are ignored
186 Returns list of I2cOps or str (for titles)
187 '''
188 transaction = []
189 errors = 0
190 firstline = True
191
192 for line in infile:
193 if prefix:
194 if not line.startswith(prefix):
195 continue
196 line = line[len(prefix):]
197
198 if len(line) == 0 or line.isspace() or line[0] == '#':
199 continue
200 line = line.lstrip().rstrip()
201 if line[0] == 'T':
202 transaction.append(line[1:].lstrip())
203 continue
204 schar = ','
205 if fifodata and not ',' in line:
206 # fifodata could also be whitespace spearated
207 schar = None
208
209 for sline in line.split(sep=schar):
210 if fifodata:
211 t, err = parse_i2c_fifodata(sline)
212 else:
213 t, err = parse_i2c_code(sline)
214 if err:
215 errors += 1
216 else:
217 transaction.append(t)
218 if errors > 0:
219 log.error('Found ' + str(errors) + ' errors in input')
220 return transaction
221
222
223def output_debug(outfile, t, term):
224 for tr in t:
225 outfile.write(str(tr) + term)
226
227
228def text_element(tr, term, titles):
229 if isinstance(tr, str):
230 if titles:
231 return 'T ' + tr + term
232 return ''
233 flags = 'S' if tr.start else '.'
234 flags += 'P' if tr.stop else '.'
235 flags += 'R' if tr.read else '.'
236 flags += 'C' if tr.rcont else '.'
237 flags += 'N' if tr.nackok else '.'
238
239 # mvalue and adr are only for drawing, but can propagate in value
240 if tr.adr:
241 val = 'A' + str(tr.fbyte)
242 else:
243 if tr.tag:
244 val = "'" + tr.tag + "'"
245 else:
246 val = 'M' if tr.mvalue else hex(tr.fbyte)
247 return flags + ' ' + val + term
248
249
250def output_text(outfile, transactions, term, titles=True):
251 for tr in transactions:
252 text = text_element(tr, term, titles)
253 if text:
254 outfile.write(text)
255
256
257# use will place a defined group at the given x,y
258def svg_use(item, x, y):
259 return ' <use href="#' + item + '" x="' + str(x) + \
260 '" y="' + str(y) + '" />\n'
261
262
263# a byte write is a byte of data from the host and an ack from the device
264def svg_wrbyte(xpos, ypos, nok, label):
265 rtext = svg_use('hbyte', xpos, ypos)
266 rtext += ' <text x="' + str(xpos + (sdata.bytew / 2))
267 rtext += '" y="' + str(ypos + sdata.txty) + '">\n'
268 rtext += label
269 rtext += '</text>\n'
270 xpos += sdata.bytew
271 if nok:
272 rtext += svg_use('norackd', xpos, ypos)
273 else:
274 rtext += svg_use('ackd', xpos, ypos)
275 xpos += sdata.bitw
276 return rtext, xpos
277
278
279# a byte read is a byte of data from the device and an ack/nack from the host
280def svg_rdbyte(xpos, ypos, ack, label):
281 rtext = svg_use('dbyte', xpos, ypos)
282 rtext += ' <text x="' + str(xpos + (sdata.bytew / 2))
283 rtext += '" y="' + str(ypos + sdata.txty) + '">\n'
284 rtext += label
285 rtext += '</text>\n'
286 xpos += sdata.bytew
287 rtext += svg_use(ack, xpos, ypos)
288 xpos += sdata.bitw
289 return rtext, xpos
290
291
292def svg_element(tr, xpos, ypos):
293 etext = ''
294 if tr.start:
295 etext += svg_use('start', xpos, ypos)
296 xpos += sdata.bitw
297
298 if tr.read and not tr.mvalue:
299 for n in range(0, 1 if tr.tag else tr.fbyte):
300 acktype = 'ackh' if (n < tr.fbyte - 1) or tr.rcont else 'nackh'
301 t, xpos = svg_rdbyte(xpos, ypos, acktype,
302 tr.tag if tr.tag else 'D' + str(n + 1))
303 etext += t
304 if xpos > sdata.wrap and (n < tr.fbyte - 1):
305 xpos = sdata.cindent
306 ypos += sdata.linesep
307 elif tr.read and tr.mvalue:
308 # need space to draw three byte+ack and a break squiggle
309 if (xpos + (sdata.bytew + sdata.bitw) * 3 + sdata.bitw) > sdata.wrap:
310 xpos = sdata.cindent
311 ypos += sdata.linesep
312 t, xpos = svg_rdbyte(xpos, ypos, 'ackh', 'Data1')
313 etext += t
314 t, xpos = svg_rdbyte(xpos, ypos, 'ackh', 'Data2')
315 etext += t
316 etext += svg_use('skip', xpos, ypos)
317 xpos += sdata.bitw
318 t, xpos = svg_rdbyte(xpos, ypos, 'nackh', 'DataN')
319 etext += t
320
321 elif tr.adr:
322 etext += svg_use('adr' + str(tr.fbyte), xpos, ypos)
323 xpos += sdata.bytew
324 etext += svg_use('ackd', xpos, ypos)
325 xpos += sdata.bitw
326 elif tr.mvalue:
327 # need space to draw three byte+ack and a break squiggle
328 if (xpos + (sdata.bytew + sdata.bitw) * 3 + sdata.bitw) > sdata.wrap:
329 xpos = sdata.cindent
330 ypos += sdata.linesep
331 t, xpos = svg_wrbyte(xpos, ypos, tr.nackok, 'Data1')
332 etext += t
333 t, xpos = svg_wrbyte(xpos, ypos, tr.nackok, 'Data2')
334 etext += t
335 etext += svg_use('skip', xpos, ypos)
336 xpos += sdata.bitw
337 t, xpos = svg_wrbyte(xpos, ypos, tr.nackok, 'DataN')
338 etext += t
339
340 elif tr.start: # and not tr.adr by position in elif
341 etext += svg_use('adr' + str(tr.fbyte & 1), xpos, ypos)
342 etext += ' <text x="' + str(xpos + 115)
343 etext += '" y="' + str(ypos + sdata.txty) + '">' + hex(tr.fbyte >> 1)
344 etext += '</text>\n'
345 xpos += sdata.bytew
346 etext += svg_use('ackd', xpos, ypos)
347 xpos += sdata.bitw
348
349 else:
350 t, xpos = svg_wrbyte(xpos, ypos, tr.nackok,
351 tr.tag if tr.tag else hex(tr.fbyte))
352 etext += t
353
354 if tr.stop:
355 etext += svg_use('pstop', xpos, ypos)
356 xpos += sdata.bitw
357
358 return etext, xpos, ypos
359
360
361# since they are referenced by href name the style and defs only
362# go in the first svg in a file
363first_svg = True
364
365
366def out_svg(outfile, svg, ypos, svgtext):
367 global first_svg
368 outfile.write('<svg\n' + sdata.svgtag_consts)
369 outfile.write('viewBox="0 0 ' + str(sdata.svgw) + ' ' +
370 str(ypos + sdata.linesep + 8) + '">\n')
371 if (first_svg):
372 outfile.write(sdata.svgstyle + sdata.svg_defs)
373 first_svg = False
374 outfile.write(svg)
375 if svgtext:
376 outfile.write('<text x="10" y="' + str(ypos + sdata.linesep + 3))
377 outfile.write('" class="tt">' + svgtext[:-2] + '</text>\n')
378 outfile.write('</svg>\n')
379
380
381def output_svg(outfile, transactions, title):
382 xpos = 0
383 ypos = 0
384 svg = ''
385 svgtext = ''
386 for tr in transactions:
387 if isinstance(tr, str):
388 if svg:
389 out_svg(outfile, svg, ypos, svgtext)
390 if title:
391 outfile.write('<h2>' + tr + '</h2>\n')
392 xpos = 0
393 ypos = 0
394 svg = ''
395 svgtext = ''
396 continue
397 if xpos > sdata.wrap:
398 xpos = sdata.cindent
399 ypos += sdata.linesep
400 trsvg, xpos, ypos = svg_element(tr, xpos, ypos)
401 svgtext += text_element(tr, ', ', False)
402 svg += trsvg
403
404 out_svg(outfile, svg, ypos, svgtext)