| # Copyright lowRISC contributors. | |
| # Licensed under the Apache License, Version 2.0, see LICENSE for details. | |
| # SPDX-License-Identifier: Apache-2.0 | |
| # | |
| # Convert I2C host format to SVG | |
| import logging as log | |
| from collections import namedtuple | |
| import i2csvg.i2csvg_data as sdata | |
| # validating version of int(x, 0) | |
| # returns int value, error flag | |
| # if error flag is True value will be zero | |
| def check_int(x, err_prefix): | |
| if isinstance(x, int): | |
| return x, False | |
| if len(x) == 0: | |
| log.error(err_prefix + " Zero length string") | |
| return 0, True | |
| if x[0] == '0' and len(x) > 2: | |
| if x[1] in 'bB': | |
| validch = '01' | |
| elif x[1] in 'oO': | |
| validch = '01234567' | |
| elif x[1] in 'xX': | |
| validch = '0123456789abcdefABCDEF' | |
| else: | |
| log.error(err_prefix + | |
| ": int must start digit, 0b, 0B, 0o, 0O, 0x or 0X") | |
| return 0, True | |
| for c in x[2:]: | |
| if not c in validch: | |
| log.error(err_prefix + ": Bad character " + c + " in " + x) | |
| return 0, True | |
| else: | |
| if not x.isdecimal(): | |
| log.error(err_prefix + ": Number not valid int " + x) | |
| return 0, 1 | |
| return int(x, 0), False | |
| def check_single(line, char, fullline, err): | |
| ''' Check for character char in input line | |
| Return True if there is one (or more) | |
| Propagate err or force to err True if more than one char | |
| ''' | |
| res = False | |
| if char in line: | |
| res = True | |
| if line.count(char) > 1: | |
| log.warning('Multiple ' + char + ' in line ' + fullline) | |
| err = True | |
| return res, err | |
| I2cOp = namedtuple('I2cOp', | |
| 'read rcont start stop nackok mvalue adr size fbyte tag') | |
| def check_and_size(iic, line): | |
| ''' Check I2C Op for validity and return size in bits | |
| ''' | |
| err = False | |
| if iic.start and iic.read: | |
| log.error('Start and Read found in ' + line) | |
| err = True | |
| if iic.rcont and not iic.read: | |
| log.error('RCont without Read found in ' + line) | |
| err = True | |
| # documentation says R+C and P is not permitted, but I think it | |
| # is needed for protocols where the last read data is ACKed? | |
| # (these don't match I2C or SMBus spec but exist in the wild) | |
| size = 0 | |
| if iic.start: | |
| size += 1 | |
| if iic.stop: | |
| size += 1 | |
| if iic.read: | |
| # multi is D0, D1, ..., Dn-1 so 3 byte/acks and a 1 bit squiggle | |
| # regular read is one byte/ack per | |
| size += 9 * 3 + 1 if iic.mvalue else 9 * iic.fbyte | |
| else: | |
| # write data is one byte/ack | |
| size += 9 | |
| # rcont, nackok just affect the polarity of the final ack bit | |
| # adr just affects how the write data is drawn | |
| return size, err | |
| def parse_i2c_fifodata(line): | |
| ''' Parse input line of I2C FDATA fifo and convert to internal type | |
| Line is usually 0x + the hex value written to the register | |
| But could be binary (0b), octal (0o) or decimal | |
| ''' | |
| fifodata, err = check_int(line, 'FIFO value') | |
| # bit values here must match the register definition! | |
| ress = (fifodata & 0x0100) != 0 | |
| resp = (fifodata & 0x0200) != 0 | |
| resr = (fifodata & 0x0400) != 0 | |
| resc = (fifodata & 0x0800) != 0 | |
| resn = (fifodata & 0x1000) != 0 | |
| resb = fifodata & 0xff | |
| resm = False # only used in descriptive case | |
| resa = False # only used in descriptive case | |
| tmpr = I2cOp(resr, resc, ress, resp, resn, resm, resa, 0, resb, None) | |
| size, serr = check_and_size(tmpr, line) | |
| if serr: | |
| err = True | |
| return I2cOp(resr, resc, ress, resp, resn, resm, resa, size, resb, | |
| None), err | |
| def parse_i2c_code(line): | |
| ''' Parse input line of I2C FDATA fifo and convert to internal type | |
| Line is coded with flags and an 8-bit data value | |
| S - Start flag, P - stop flag, | |
| R - read flag, C - continue read flag, N - NackOk flag | |
| followed by the data byte | |
| Special cases: | |
| M - indicates multiple bytes instead of data byte | |
| A - followed by 0 or 1 address/direction or 2 address/data | |
| Data value in quotes is a tag | |
| ''' | |
| resr, resc, ress, resp, resn = False, False, False, False, False | |
| resm, resa, resb = False, False, 0 | |
| err = False | |
| firstval = 0 | |
| for i in line: | |
| if i.isdigit() or i == "'": | |
| break | |
| firstval += 1 | |
| # Will only check the flags section, so no concern about hex digits | |
| ress, err = check_single(line[:firstval], 'S', line, err) | |
| resp, err = check_single(line[:firstval], 'P', line, err) | |
| resr, err = check_single(line[:firstval], 'R', line, err) | |
| resc, err = check_single(line[:firstval], 'C', line, err) | |
| resn, err = check_single(line[:firstval], 'N', line, err) | |
| # these two are formally part of the value but parse like flags | |
| resm, err = check_single(line[:firstval], 'M', line, err) | |
| resa, err = check_single(line[:firstval], 'A', line, err) | |
| if firstval == len(line): | |
| if not resm: | |
| err = True | |
| log.error('No value found in ' + line) | |
| rest = None | |
| else: | |
| if resm: | |
| err = True | |
| log.error('Found M and value in ' + line) | |
| rest = None | |
| resb = 0 | |
| elif line[firstval] == "'": | |
| rest = line[firstval + 1:-1] | |
| resb = 0 | |
| else: | |
| rest = None | |
| resb, verr = check_int(line[firstval:], | |
| 'Value in ' + line + ' ' + str(firstval)) | |
| if verr: | |
| err = True | |
| if resb < 0 or resb > 255 or (resa and resb > 2): | |
| log.error('Value out of range in ' + line) | |
| resb = 0 | |
| err = True | |
| tmpr = I2cOp(resr, resc, ress, resp, resn, resm, resa, 0, resb, rest) | |
| size, serr = check_and_size(tmpr, line) | |
| if serr: | |
| err = True | |
| return I2cOp(resr, resc, ress, resp, resn, resm, resa, size, resb, | |
| rest), err | |
| def parse_file(infile, fifodata=False, prefix=None): | |
| ''' Parse a file of I2C data | |
| fifodata indicates if the data is a dump from writes to FDATA fifo | |
| prefix is a prefix on valid lines and will be stripped | |
| lines without the prefix are ignored | |
| Returns list of I2cOps or str (for titles) | |
| ''' | |
| transaction = [] | |
| errors = 0 | |
| firstline = True | |
| for line in infile: | |
| if prefix: | |
| if not line.startswith(prefix): | |
| continue | |
| line = line[len(prefix):] | |
| if len(line) == 0 or line.isspace() or line[0] == '#': | |
| continue | |
| line = line.lstrip().rstrip() | |
| if line[0] == 'T': | |
| transaction.append(line[1:].lstrip()) | |
| continue | |
| schar = ',' | |
| if fifodata and not ',' in line: | |
| # fifodata could also be whitespace spearated | |
| schar = None | |
| for sline in line.split(sep=schar): | |
| if fifodata: | |
| t, err = parse_i2c_fifodata(sline) | |
| else: | |
| t, err = parse_i2c_code(sline) | |
| if err: | |
| errors += 1 | |
| else: | |
| transaction.append(t) | |
| if errors > 0: | |
| log.error('Found ' + str(errors) + ' errors in input') | |
| return transaction | |
| def output_debug(outfile, t, term): | |
| for tr in t: | |
| outfile.write(str(tr) + term) | |
| def text_element(tr, term, titles): | |
| if isinstance(tr, str): | |
| if titles: | |
| return 'T ' + tr + term | |
| return '' | |
| flags = 'S' if tr.start else '.' | |
| flags += 'P' if tr.stop else '.' | |
| flags += 'R' if tr.read else '.' | |
| flags += 'C' if tr.rcont else '.' | |
| flags += 'N' if tr.nackok else '.' | |
| # mvalue and adr are only for drawing, but can propagate in value | |
| if tr.adr: | |
| val = 'A' + str(tr.fbyte) | |
| else: | |
| if tr.tag: | |
| val = "'" + tr.tag + "'" | |
| else: | |
| val = 'M' if tr.mvalue else hex(tr.fbyte) | |
| return flags + ' ' + val + term | |
| def output_text(outfile, transactions, term, titles=True): | |
| for tr in transactions: | |
| text = text_element(tr, term, titles) | |
| if text: | |
| outfile.write(text) | |
| # use will place a defined group at the given x,y | |
| def svg_use(item, x, y): | |
| return ' <use href="#' + item + '" x="' + str(x) + \ | |
| '" y="' + str(y) + '" />\n' | |
| # a byte write is a byte of data from the host and an ack from the device | |
| def svg_wrbyte(xpos, ypos, nok, label): | |
| rtext = svg_use('hbyte', xpos, ypos) | |
| rtext += ' <text x="' + str(xpos + (sdata.bytew / 2)) | |
| rtext += '" y="' + str(ypos + sdata.txty) + '">\n' | |
| rtext += label | |
| rtext += '</text>\n' | |
| xpos += sdata.bytew | |
| if nok: | |
| rtext += svg_use('norackd', xpos, ypos) | |
| else: | |
| rtext += svg_use('ackd', xpos, ypos) | |
| xpos += sdata.bitw | |
| return rtext, xpos | |
| # a byte read is a byte of data from the device and an ack/nack from the host | |
| def svg_rdbyte(xpos, ypos, ack, label): | |
| rtext = svg_use('dbyte', xpos, ypos) | |
| rtext += ' <text x="' + str(xpos + (sdata.bytew / 2)) | |
| rtext += '" y="' + str(ypos + sdata.txty) + '">\n' | |
| rtext += label | |
| rtext += '</text>\n' | |
| xpos += sdata.bytew | |
| rtext += svg_use(ack, xpos, ypos) | |
| xpos += sdata.bitw | |
| return rtext, xpos | |
| def svg_element(tr, xpos, ypos): | |
| etext = '' | |
| if tr.start: | |
| etext += svg_use('start', xpos, ypos) | |
| xpos += sdata.bitw | |
| if tr.read and not tr.mvalue: | |
| for n in range(0, 1 if tr.tag else tr.fbyte): | |
| acktype = 'ackh' if (n < tr.fbyte - 1) or tr.rcont else 'nackh' | |
| t, xpos = svg_rdbyte(xpos, ypos, acktype, | |
| tr.tag if tr.tag else 'D' + str(n + 1)) | |
| etext += t | |
| if xpos > sdata.wrap and (n < tr.fbyte - 1): | |
| xpos = sdata.cindent | |
| ypos += sdata.linesep | |
| elif tr.read and tr.mvalue: | |
| # need space to draw three byte+ack and a break squiggle | |
| if (xpos + (sdata.bytew + sdata.bitw) * 3 + sdata.bitw) > sdata.wrap: | |
| xpos = sdata.cindent | |
| ypos += sdata.linesep | |
| t, xpos = svg_rdbyte(xpos, ypos, 'ackh', 'Data1') | |
| etext += t | |
| t, xpos = svg_rdbyte(xpos, ypos, 'ackh', 'Data2') | |
| etext += t | |
| etext += svg_use('skip', xpos, ypos) | |
| xpos += sdata.bitw | |
| t, xpos = svg_rdbyte(xpos, ypos, 'nackh', 'DataN') | |
| etext += t | |
| elif tr.adr: | |
| etext += svg_use('adr' + str(tr.fbyte), xpos, ypos) | |
| xpos += sdata.bytew | |
| etext += svg_use('ackd', xpos, ypos) | |
| xpos += sdata.bitw | |
| elif tr.mvalue: | |
| # need space to draw three byte+ack and a break squiggle | |
| if (xpos + (sdata.bytew + sdata.bitw) * 3 + sdata.bitw) > sdata.wrap: | |
| xpos = sdata.cindent | |
| ypos += sdata.linesep | |
| t, xpos = svg_wrbyte(xpos, ypos, tr.nackok, 'Data1') | |
| etext += t | |
| t, xpos = svg_wrbyte(xpos, ypos, tr.nackok, 'Data2') | |
| etext += t | |
| etext += svg_use('skip', xpos, ypos) | |
| xpos += sdata.bitw | |
| t, xpos = svg_wrbyte(xpos, ypos, tr.nackok, 'DataN') | |
| etext += t | |
| elif tr.start: # and not tr.adr by position in elif | |
| etext += svg_use('adr' + str(tr.fbyte & 1), xpos, ypos) | |
| etext += ' <text x="' + str(xpos + 115) | |
| etext += '" y="' + str(ypos + sdata.txty) + '">' + hex(tr.fbyte >> 1) | |
| etext += '</text>\n' | |
| xpos += sdata.bytew | |
| etext += svg_use('ackd', xpos, ypos) | |
| xpos += sdata.bitw | |
| else: | |
| t, xpos = svg_wrbyte(xpos, ypos, tr.nackok, | |
| tr.tag if tr.tag else hex(tr.fbyte)) | |
| etext += t | |
| if tr.stop: | |
| etext += svg_use('pstop', xpos, ypos) | |
| xpos += sdata.bitw | |
| return etext, xpos, ypos | |
| # since they are referenced by href name the style and defs only | |
| # go in the first svg in a file | |
| first_svg = True | |
| def out_svg(outfile, svg, ypos, svgtext): | |
| global first_svg | |
| outfile.write('<svg\n' + sdata.svgtag_consts) | |
| outfile.write('viewBox="0 0 ' + str(sdata.svgw) + ' ' + | |
| str(ypos + sdata.linesep + 8) + '">\n') | |
| if (first_svg): | |
| outfile.write(sdata.svgstyle + sdata.svg_defs) | |
| first_svg = False | |
| outfile.write(svg) | |
| if svgtext: | |
| outfile.write('<text x="10" y="' + str(ypos + sdata.linesep + 3)) | |
| outfile.write('" class="tt">' + svgtext[:-2] + '</text>\n') | |
| outfile.write('</svg>\n') | |
| def output_svg(outfile, transactions, title): | |
| xpos = 0 | |
| ypos = 0 | |
| svg = '' | |
| svgtext = '' | |
| for tr in transactions: | |
| if isinstance(tr, str): | |
| if svg: | |
| out_svg(outfile, svg, ypos, svgtext) | |
| if title: | |
| outfile.write('<h2>' + tr + '</h2>\n') | |
| xpos = 0 | |
| ypos = 0 | |
| svg = '' | |
| svgtext = '' | |
| continue | |
| if xpos > sdata.wrap: | |
| xpos = sdata.cindent | |
| ypos += sdata.linesep | |
| trsvg, xpos, ypos = svg_element(tr, xpos, ypos) | |
| svgtext += text_element(tr, ', ', False) | |
| svg += trsvg | |
| out_svg(outfile, svg, ypos, svgtext) |