blob: bf93e172d5a53e391704ec438c9ad35ddb4fa1e2 [file] [log] [blame]
# 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)