| # Copyright lowRISC contributors. | 
 | # Licensed under the Apache License, Version 2.0, see LICENSE for details. | 
 | # SPDX-License-Identifier: Apache-2.0 | 
 |  | 
 | # portions adapted from the javascript wavedrom.js | 
 | # https://github.com/drom/wavedrom/blob/master/wavedrom.js | 
 | # see LICENSE.wavedrom | 
 |  | 
 | import io | 
 | import logging as log | 
 |  | 
 | from wavegen import wavesvg_data | 
 |  | 
 | # Generate brick: follows wavedrom.js | 
 |  | 
 |  | 
 | def gen_brick(texts, extra, times): | 
 |     res = [] | 
 |  | 
 |     # length of four indicates 2 phases each with 2 bricks | 
 |     if (len(texts) == 4): | 
 |         if extra != int(extra): | 
 |             log.error("Clock must have an integer period") | 
 |         for j in range(times): | 
 |             res.append(texts[0]) | 
 |             for i in range(int(extra)): | 
 |                 res.append(texts[1]) | 
 |             res.append(texts[2]) | 
 |             for i in range(int(extra)): | 
 |                 res.append(texts[3]) | 
 |         return res | 
 |  | 
 |     if (len(texts) == 1): | 
 |         t1 = texts[0] | 
 |     else: | 
 |         t1 = texts[1] | 
 |  | 
 |     res.append(texts[0]) | 
 |     for i in range(times * int(2 * (extra + 1)) - 1): | 
 |         res.append(t1) | 
 |     return res | 
 |  | 
 |  | 
 | # Generate first brick in a line: follows wavedrom.js | 
 | def gen_first_wavebrick(text, extra, times): | 
 |  | 
 |     gentext = { | 
 |         'p': ['pclk', '111', 'nclk', '000'], | 
 |         'n': ['nclk', '000', 'pclk', '111'], | 
 |         'P': ['Pclk', '111', 'nclk', '000'], | 
 |         'N': ['Nclk', '000', 'pclk', '111'], | 
 |         'l': ['000'], | 
 |         'L': ['000'], | 
 |         '0': ['000'], | 
 |         'h': ['111'], | 
 |         'H': ['111'], | 
 |         '1': ['111'], | 
 |         '=': ['vvv-2'], | 
 |         '2': ['vvv-2'], | 
 |         '3': ['vvv-3'], | 
 |         '4': ['vvv-4'], | 
 |         '5': ['vvv-5'], | 
 |         'd': ['ddd'], | 
 |         'u': ['uuu'], | 
 |         'z': ['zzz'] | 
 |     } | 
 |     return gen_brick(gentext.get(text, ['xxx']), extra, times) | 
 |  | 
 |  | 
 | # Generate subsequent bricks: text contains before and after states | 
 | # Follows wavedrom | 
 | def gen_wavebrick(text, extra, times): | 
 |  | 
 |     # new states that have a hard edge going in to them | 
 |     new_hardedges = { | 
 |         'p': 'pclk', | 
 |         'n': 'nclk', | 
 |         'P': 'Pclk', | 
 |         'N': 'Nclk', | 
 |         'h': 'pclk', | 
 |         'l': 'nclk', | 
 |         'H': 'Pclk', | 
 |         'L': 'Nclk' | 
 |     } | 
 |  | 
 |     # new state with a soft edge | 
 |     new_softedges = { | 
 |         '0': '0', | 
 |         '1': '1', | 
 |         'x': 'x', | 
 |         'd': 'd', | 
 |         'u': 'u', | 
 |         'z': 'z', | 
 |         '=': 'v', | 
 |         '2': 'v', | 
 |         '3': 'v', | 
 |         '4': 'v', | 
 |         '5': 'v' | 
 |     } | 
 |  | 
 |     # state we are coming from | 
 |     old_edges = { | 
 |         'p': '0', | 
 |         'n': '1', | 
 |         'P': '0', | 
 |         'N': '1', | 
 |         'h': '1', | 
 |         'l': '0', | 
 |         'H': '1', | 
 |         'L': '0', | 
 |         '0': '0', | 
 |         '1': '1', | 
 |         'x': 'x', | 
 |         'd': 'd', | 
 |         'u': 'u', | 
 |         'z': 'z', | 
 |         '=': 'v', | 
 |         '2': 'v', | 
 |         '3': 'v', | 
 |         '4': 'v', | 
 |         '5': 'v' | 
 |     } | 
 |  | 
 |     # tags (basically the colour) -- js had two arrays for this | 
 |     tags = { | 
 |         'p': '', | 
 |         'n': '', | 
 |         'P': '', | 
 |         'N': '', | 
 |         'h': '', | 
 |         'l': '', | 
 |         'H': '', | 
 |         'L': '', | 
 |         '0': '', | 
 |         '1': '', | 
 |         'x': '', | 
 |         'd': '', | 
 |         'u': '', | 
 |         'z': '', | 
 |         '=': '-2', | 
 |         '2': '-2', | 
 |         '3': '-3', | 
 |         '4': '-4', | 
 |         '5': '-5' | 
 |     } | 
 |  | 
 |     # drawing for the second half of the new state | 
 |     new_secondbricks = { | 
 |         'p': '111', | 
 |         'n': '000', | 
 |         'P': '111', | 
 |         'N': '000', | 
 |         'h': '111', | 
 |         'l': '000', | 
 |         'H': '111', | 
 |         'L': '000', | 
 |         '0': '000', | 
 |         '1': '111', | 
 |         'x': 'xxx', | 
 |         'd': 'ddd', | 
 |         'u': 'uuu', | 
 |         'z': 'zzz', | 
 |         '=': 'vvv-2', | 
 |         '2': 'vvv-2', | 
 |         '3': 'vvv-3', | 
 |         '4': 'vvv-4', | 
 |         '5': 'vvv-5' | 
 |     } | 
 |  | 
 |     phase2_firstbricks = {'p': 'nclk', 'n': 'pclk', 'P': 'nclk', 'N': 'pclk'} | 
 |  | 
 |     phase2_secondbricks = {'p': '000', 'n': '111', 'P': '000', 'N': '111'} | 
 |  | 
 |     xclude = { | 
 |         'hp': '111', | 
 |         'Hp': '111', | 
 |         'ln': '000', | 
 |         'Ln': '000', | 
 |         'nh': '111', | 
 |         'Nh': '111', | 
 |         'pl': '000', | 
 |         'Pl': '000' | 
 |     } | 
 |  | 
 |     secondbrick = new_secondbricks.get(text[1]) | 
 |     hardbrick = new_hardedges.get(text[1]) | 
 |     if hardbrick == None: | 
 |         # a soft edge gets the brick type constructed from the | 
 |         # old state, m, new state. Old and new states may have | 
 |         # tags which basically represent the colour | 
 |         newch = new_softedges.get(text[1]) | 
 |         oldch = old_edges.get(text[0]) | 
 |         if newch == None or oldch == None: | 
 |             # unknown: can't find the characters to make an edge | 
 |             return gen_brick(['xxx'], extra, times) | 
 |         else: | 
 |             # soft curves | 
 |             return gen_brick([ | 
 |                 oldch + 'm' + newch + tags[text[0]] + tags[text[1]], | 
 |                 secondbrick | 
 |             ], extra, times) | 
 |     else: | 
 |         specialcase = xclude.get(text) | 
 |         if specialcase != None: | 
 |             hardbrick = specialcase | 
 |  | 
 |         # sharp curves | 
 |         twophase = phase2_firstbricks.get(text[1]) | 
 |         if twophase == None: | 
 |             # hlHL | 
 |             return gen_brick([hardbrick, secondbrick], extra, times) | 
 |         else: | 
 |             # pnPN | 
 |             return gen_brick([ | 
 |                 hardbrick, secondbrick, twophase, | 
 |                 phase2_secondbricks.get(text[1]) | 
 |             ], extra, times) | 
 |  | 
 |  | 
 | # text is the wave member of the signal object | 
 | # extra = hscale-1 ( padding ) | 
 |  | 
 |  | 
 | def parse_wavelane(text, extra): | 
 |     res = [] | 
 |     pos = 1 | 
 |     tlen = len(text) | 
 |     subCycle = False | 
 |     if tlen == 0: | 
 |         return res; | 
 |     next = text[0] | 
 |  | 
 |     repeats = 1 | 
 |     while pos < tlen and (text[pos] == '.' or text[pos] == '|'): | 
 |         pos += 1 | 
 |         repeats += 1 | 
 |  | 
 |     res = gen_first_wavebrick(next, extra, repeats) | 
 |  | 
 |     while pos < tlen: | 
 |         top = next | 
 |         next = text[pos] | 
 |         pos += 1 | 
 |         if next == '<':  # sub-cycles on | 
 |             subCycle = True | 
 |             next = text[pos] | 
 |             pos += 1 | 
 |  | 
 |         if next == '>':  # sub-cycles off | 
 |             subCycle = False | 
 |             next = text[pos] | 
 |             pos += 1 | 
 |  | 
 |         repeats = 1 | 
 |         while pos < tlen and (text[pos] == '.' or text[pos] == '|'): | 
 |             pos += 1 | 
 |             repeats += 1 | 
 |         if subCycle: | 
 |             res.extend(gen_wavebrick(top + next, 0, repeats - lane.period)) | 
 |         else: | 
 |             res.extend(gen_wavebrick(top + next, extra, repeats)) | 
 |  | 
 |     # res is array of half brick types, each is item is string | 
 |     return res | 
 |  | 
 |  | 
 | def render_svghead(out, width, height, bricksused, svgn): | 
 |     out.write('  <svg id="svgcontent_' + str(svgn) + '"\n') | 
 |     out.write(wavesvg_data.head1) | 
 |     out.write('height="' + str(height) + '" width="' + str(width) + '"\n') | 
 |     out.write('viewBox="0 0 ' + str(width) + ' ' + str(height) + '">\n') | 
 |     out.write(wavesvg_data.head2) | 
 |     if len(bricksused) > 0: | 
 |         out.write(wavesvg_data.defs_head) | 
 |         for br in bricksused: | 
 |             out.write(wavesvg_data.use_defs[br]) | 
 |         out.write(wavesvg_data.defs_tail) | 
 |  | 
 |  | 
 | def render_svgtail(out): | 
 |     out.write(wavesvg_data.tail) | 
 |  | 
 |  | 
 | def render_lanes_head(out, xoffset, yoffset, svgn): | 
 |     out.write('    <g id="lanes_' + str(svgn) + '" transform="translate(' + | 
 |               str(xoffset) + ', ' + str(yoffset) + ')">\n') | 
 |  | 
 |  | 
 | def render_events(out, svgn, events, edges): | 
 |     out.write('      <g id="wavearcs_' + str(svgn) + '">\n') | 
 |     for edge in edges: | 
 |         sp_edge = edge.split(None, 1) | 
 |         log.info("Edge " + str(edge) + " splits " + str(sp_edge)) | 
 |         ev_from = events.get(sp_edge[0][0]) | 
 |         ev_to = events.get(sp_edge[0][-1]) | 
 |         shape = sp_edge[0][1:-1] | 
 |         if (len(sp_edge) > 1): | 
 |             label = sp_edge[1] | 
 |         else: | 
 |             label = '' | 
 |  | 
 |         if ev_from == None or ev_to == None or len(shape) < 1: | 
 |             log.warn("Could not find events for " + sp_edge[0]) | 
 |             continue | 
 |  | 
 |         dx = ev_to[0] - ev_from[0] | 
 |         dy = ev_to[1] - ev_from[1] | 
 |         # lx,ly is the center of the label, it may be adjusted | 
 |         lx = (ev_to[0] + ev_from[0]) // 2 | 
 |         ly = (ev_to[1] + ev_from[1]) // 2 | 
 |  | 
 |         if shape[0] == '<' and shape[-1] == '>': | 
 |             path_s = 'marker-end:url(#arrowhead);' \ | 
 |                      'marker-start:url(#arrowtail);stroke:#0041c4;' \ | 
 |                      'stroke-width:1;fill:none' | 
 |             shape = shape[1:-1] | 
 |         elif shape[-1] == '>': | 
 |             path_s = 'marker-end:url(#arrowhead);stroke:#0041c4;' \ | 
 |                      'stroke-width:1;fill:none' | 
 |             shape = shape[:-1] | 
 |         elif shape[0] == '<': | 
 |             path_s = 'marker-start:url(#arrowtail);stroke:#0041c4;' \ | 
 |                      'stroke-width:1;fill:none' | 
 |             shape = shape[1:] | 
 |         else: | 
 |             path_s = 'fill:none;stroke:#00F;stroke-width:1' | 
 |  | 
 |         # SVG uses the case to indicate abs or relative | 
 |         path_type = 'M' | 
 |         # always start at the from point | 
 |         path_d = ' ' + str(ev_from)[1:-1] | 
 |  | 
 |         if shape == '~': | 
 |             path_d += (' c ' + str(0.7 * dx) + ', 0 ' + str(0.3 * dx) + ', ' + | 
 |                        str(dy) + ' ' + str(dx) + ', ' + str(dy)) | 
 |         elif shape == '-~': | 
 |             path_d += (' c ' + str(0.7 * dx) + ', 0 ' + str(dx) + ', ' + | 
 |                        str(dy) + ' ' + str(dx) + ', ' + str(dy)) | 
 |             lx = ev_from[0] + dx * 0.75 | 
 |         elif shape == '~-': | 
 |             path_d += (' c 0, 0 ' + str(0.3 * dx) + ', ' + str(dy) + ' ' + | 
 |                        str(dx) + ', ' + str(dy)) | 
 |             lx = ev_from[0] + dx * 0.25 | 
 |         elif shape == '-|': | 
 |             path_d += ' ' + str(dx) + ',0 0,' + str(dy) | 
 |             path_type = 'm' | 
 |             lx = ev_to[0] | 
 |         elif shape == '|-': | 
 |             path_d += ' 0,' + str(dy) + ' ' + str(dx) + ',0' | 
 |             path_type = 'm' | 
 |             lx = ev_from[0] | 
 |         elif shape == '-|-': | 
 |             path_d += (' ' + str(dx / 2) + ',0 0,' + str(dy) + ' ' + str( | 
 |                 dx / 2) + ',0') | 
 |             path_type = 'm' | 
 |         else:  # catch - here (and anything else) | 
 |             path_d += ' ' + str(ev_to)[1:-1] | 
 |  | 
 |         out.write('      <path id="gmark_' + sp_edge[0][0] + '_' + | 
 |                   sp_edge[0][-1] + '" d="' + path_type + path_d + '" style="' + | 
 |                   path_s + '"></path>\n') | 
 |         if len(label) != 0: | 
 |             out.write('        <rect height="9" ' | 
 |                       'style="fill: rgb(255, 255, 255);" ' | 
 |                       'width="' + str(5 * len(label)) + '" ' | 
 |                       'x="' + str(lx - 2.5 * len(label)) + '" ' | 
 |                       'y="' + str(ly - 8) + '"></rect>\n') | 
 |             out.write('      <text style="font-size: 10px;" ' | 
 |                       'text-anchor="middle" xml:space="preserve" ' | 
 |                       'x="' + str(lx) + '" y="' + str(ly) + '">' | 
 |                       '<tspan>' + label + '</tspan></text>\n') | 
 |  | 
 |     # Do events last so they are on top | 
 |     for e in events: | 
 |         log.info("Event " + e) | 
 |         if e.islower(): | 
 |             (evx, evy) = events[e] | 
 |             # rectangles are taller than in js because it looks better to me | 
 |             out.write('        <rect y="' + str(evy - 8) + '" height="12" ' | 
 |                       'x="' + str(evx - 3) + '" width="6" ' | 
 |                       'style="fill: rgb(255, 255, 255);"></rect>\n') | 
 |             out.write('        <text style="font-size: 8px;" ' | 
 |                       'x="' + str(evx) + '" y="' + str(evy) + '" ' | 
 |                       'text-anchor="middle">' + e + '</text>\n') | 
 |     out.write('      </g>\n') | 
 |  | 
 |  | 
 | def render_wavelanes(out, xscale, yscale, lanes, svgn, events): | 
 |     lnum = 0 | 
 |     x_edgeoff = 6 | 
 |     for lane in lanes: | 
 |         phase = lane[5] | 
 |  | 
 |         out.write('      <g id="wavelane_' + str(svgn) + '_' + str(lnum) + '"' | 
 |                   ' transform="translate(0,' + str(lnum * yscale + 5) + | 
 |                   ')">\n') | 
 |         if len(lane[0]) != 0: | 
 |             out.write('        <text x="-15" y="15" class="info" ' | 
 |                       'text-anchor="end" xml:space="preserve">' | 
 |                       '<tspan>' + lane[0] + '</tspan></text>\n') | 
 |         out.write('        <g id="wavelane_draw_' + str(svgn) + '_' + | 
 |                   str(lnum) + '" ' | 
 |                   'transform="translate(0,0)">\n') | 
 |         if phase < 0: | 
 |             bnum = abs(phase) | 
 |             lstart = 0 | 
 |         else: | 
 |             bnum = 0 | 
 |             lstart = phase | 
 |  | 
 |         for x in lane[1][lstart:]: | 
 |             out.write('          <use xlink:href="#' + x + '" ' | 
 |                       'transform="translate(' + str(bnum * xscale) + | 
 |                       ')"></use>\n') | 
 |             bnum += 1 | 
 |         dpos = 0 | 
 |         dend = len(lane[3]) | 
 |         period = lane[4] | 
 |         if phase < 0: | 
 |             i = 0 | 
 |         else: | 
 |             # start point ensures bnum below is never less than -1 | 
 |             i = phase // 2 | 
 |  | 
 |         if dend > 0 and lane[3][-1] == '!cdata!': | 
 |             lane[3].pop() | 
 |             dend -= 1; | 
 |             labelif = '01=2345ud' | 
 |         else: | 
 |             labelif = '=2345' | 
 |  | 
 |         scan_max = max(len(lane[2]), len(lane[6])) | 
 |         while (i < scan_max): | 
 |             bnum = i * 2 - phase | 
 |  | 
 |             if (i < len(lane[2])): | 
 |                 x = lane[2][i] | 
 |                 if dpos < dend and x in labelif: | 
 |                     nslot = 1 | 
 |                     while (i + nslot) < len( | 
 |                             lane[2]) and lane[2][i + nslot] == '.': | 
 |                         nslot += 1 | 
 |                     xcenter = period * xscale * nslot + bnum * period * xscale | 
 |                     # the center needs to be offset by the width of the | 
 |                     # edge because that lives in the first brick | 
 |                     xcenter += x_edgeoff | 
 |                     out.write('        <text x="' + str(xcenter)) | 
 |                     out.write('" y="' + str(yscale / 2) + '" ' | 
 |                               'text-anchor="middle" xml:space="preserve">') | 
 |                     tspan_or_text(out, lane[3][dpos], True) | 
 |                     out.write('</text>') | 
 |                     dpos = dpos + 1 | 
 |                 if x == '|': | 
 |                     # render a gap (this diverges from how the js does it | 
 |                     # where it is in a different g | 
 |                     out.write('        <use xlink:href="#gap" transform="' | 
 |                               'translate(' + | 
 |                               str(period * (bnum * xscale + xscale)) + ')"' | 
 |                               '></use>\n') | 
 |  | 
 |             if i < len(lane[6]): | 
 |                 ev = lane[6][i] | 
 |                 if ev != '.': | 
 |                     events[ev] = (period * bnum * xscale + x_edgeoff, | 
 |                                   lnum * yscale + 2 + yscale // 2) | 
 |             i += 1 | 
 |  | 
 |         out.write('        </g>\n      </g>\n') | 
 |         lnum += 1 | 
 |  | 
 |  | 
 | def render_marks(out, nbricks, xscale, ylen, svgn): | 
 |     out.write('      <g id="gmarks_' + str(svgn) + '">\n') | 
 |     mnum = 0 | 
 |     for i in range(nbricks // 2): | 
 |         out.write('        <path id="gmark_' + str(svgn) + '_' + str(mnum) + | 
 |                   '" ' | 
 |                   'd="m ' + str(i * 2 * xscale) + ',0 0,' + str(ylen) + '" ' | 
 |                   'style="stroke: rgb(136, 136, 136); ' | 
 |                   'stroke-width:0.5; stroke-dasharray:1,3"' | 
 |                   '></path>\n') | 
 |         mnum += 1 | 
 |     out.write('      </g>\n') | 
 |  | 
 |  | 
 | def tspan_or_text(out, text, outerspan): | 
 |     if isinstance(text, str): | 
 |         if outerspan: out.write('<tspan>') | 
 |         out.write(text) | 
 |         if (outerspan): out.write('</tspan>') | 
 |     else: | 
 |         if text[0] != 'tspan': | 
 |             log.warn('Expecting tspan, got ' + str(text[0])) | 
 |             return | 
 |         if len(text) == 3 and isinstance(text[1], dict): | 
 |             out.write('<tspan ') | 
 |             for x in text[1]: | 
 |                 out.write(x + '="' + text[1][x] + '" ') | 
 |             out.write('>') | 
 |             tspan_or_text(out, text[2], False) | 
 |         else: | 
 |             out.write('<tspan>') | 
 |             for x in text[1:]: | 
 |                 tspan_or_text(out, x, False) | 
 |         out.write('</tspan>') | 
 |  | 
 |  | 
 | def render_caption(out, text, nbricks, xscale, yoffset): | 
 |     out.write('        <text x="' + str(nbricks * xscale // 2) + '" ' | 
 |               'y="' + str(yoffset) + '" ' | 
 |               ' text-anchor="middle" fill="#000" xml:space="preserve">') | 
 |     tspan_or_text(out, text, True) | 
 |     out.write('</text>\n') | 
 |  | 
 |  | 
 | def render_ticktock(out, info, xoff, xsep, yoff, num): | 
 |     # info could be a number/string representing the lowest tick number | 
 |     # or a string containing a list of space separated labels | 
 |     # or a list containing separate strings or a single string | 
 |  | 
 |     if isinstance(info, int) or (isinstance(info, str) and info.isdecimal()): | 
 |         base = int(info) | 
 |         for i in range(num): | 
 |             out.write('<text x="' + str(xoff + i * xsep) + '" y="' + | 
 |                       str(yoff) + '" ' | 
 |                       'text-anchor="middle" class="muted" xml:space="preserve"' | 
 |                       '>' + str(i + base) + '</text>\n') | 
 |     else: | 
 |         if isinstance(info, list): | 
 |             if len(info) == 1: | 
 |                 labels = info[0].split() | 
 |             else: | 
 |                 labels = info | 
 |         else: | 
 |             labels = info.split() | 
 |         if num > len(labels): | 
 |             num = len(labels) | 
 |         for i in range(num): | 
 |             out.write('<text x="' + str(xoff + i * xsep) + '" y="' + | 
 |                       str(yoff) + '" ' | 
 |                       'text-anchor="middle" class="muted" xml:space="preserve"' | 
 |                       '>' + labels[i] + '</text>\n') | 
 |  | 
 |  | 
 | def render_lanes_tail(out): | 
 |     out.write('    </g>\n') | 
 |  | 
 |  | 
 | # group array is [0=Name, 1=startlane, 2=endlane+1, 3=depth] | 
 | def render_groups(out, groups, yhead, yoff, snum): | 
 |     gdepth = 0 | 
 |     if len(groups) == 0: | 
 |         return | 
 |  | 
 |     for gr in groups: | 
 |         if gr[3] > gdepth: | 
 |             gdepth = gr[3] | 
 |     xgoff = 80 - gdepth * 25 | 
 |  | 
 |     out.write('    <g id="groups_' + str(snum) + '">\n') | 
 |  | 
 |     gnum = 0 | 
 |     for gr in groups: | 
 |         ymin = yhead + gr[1] * yoff | 
 |         ylen = (gr[2] - gr[1]) * yoff | 
 |  | 
 |         out.write('      <path id="group_' + str(snum) + '_' + str(gnum) + | 
 |                   '" ') | 
 |         out.write('d="m ' + str(xgoff + 25 * gr[3]) + ',' + str(ymin + 3.5) + | 
 |                   ' c -3,0 -5,2 -5,5 l 0,' + str(ylen - 16) + ' c 0,3 2,5 5,5"' | 
 |                   'style="stroke: rgb(0, 65, 196); stroke-width: 1; ' | 
 |                   'fill: none;"></path>\n') | 
 |         if len(gr[0]) > 0: | 
 |             out.write('      <g transform="translate(' + | 
 |                       str(xgoff - 10 + 25 * gr[3]) + ',' + | 
 |                       str(ymin + ylen // 2) + ')">\n') | 
 |             out.write('        <g transform="rotate(270)">\n') | 
 |             out.write('          <text text-anchor="middle" class="info" ' | 
 |                       'xml:space="preserve"><tspan>' + gr[0] + | 
 |                       '</tspan></text>\n        </g>\n      </g>') | 
 |         gnum += 1 | 
 |     out.write('    </g>\n') | 
 |  | 
 |  | 
 | def parse_wave(x, hscale, lanes, groups, gdepth, bricksused): | 
 |     sname = "" | 
 |     wave = '' | 
 |     node = '' | 
 |     labels = [] | 
 |     bricks = [] | 
 |     extra = hscale - 1 | 
 |     phase = 0 | 
 |     xmax = 0 | 
 |     global prevdefs | 
 |  | 
 |     if isinstance(x, list): | 
 |         gname = x[0] | 
 |         startlane = len(lanes) | 
 |         for y in x[1:]: | 
 |             ymax = parse_wave(y, hscale, lanes, groups, gdepth + 1, bricksused) | 
 |             if ymax > xmax: | 
 |                 xmax = ymax | 
 |         groups.append([gname, startlane, len(lanes), gdepth]) | 
 |         return xmax | 
 |  | 
 |     if 'name' in x: | 
 |         sname = x['name'] | 
 |  | 
 |     # period must be before wave because it changes extra | 
 |     if 'period' in x: | 
 |         fp = float(x['period']) | 
 |         if fp < 0 or fp * 2 != int(fp * 2): | 
 |             log.error("Period must be integer or 0.5") | 
 |         extra = hscale * fp - 1 | 
 |     if 'phase' in x: | 
 |         phase = int(x['phase'] * 2) | 
 |     if 'wave' in x: | 
 |         wave = x['wave'] | 
 |         bricks = parse_wavelane(wave, extra) | 
 |         for br in bricks: | 
 |             if not br in bricksused and not br in prevdefs: | 
 |                 bricksused.append(br) | 
 |         if 'data' in x: | 
 |             labels = x['data'] | 
 |             if isinstance(labels, str): | 
 |                 labels = labels.split() | 
 |         if 'cdata' in x: | 
 |             labels = x['cdata'] | 
 |             if isinstance(labels, str): | 
 |                 labels = labels.split() | 
 |             labels.append('!cdata!') | 
 |  | 
 |     if 'node' in x: | 
 |         node = x['node'] | 
 |  | 
 |     lanes.append([sname, bricks, wave, labels, extra + 1, phase, node]) | 
 |     return len(bricks) | 
 |  | 
 |  | 
 | # obj is Hjson parsed object with wavejson | 
 | # svg_num is a number that makes this svg unique. First one must be 0 | 
 |  | 
 |  | 
 | def convert(obj, svg_num): | 
 |     xs = 20  # x scale = width of cycle | 
 |     ys = 20  # y scale = height of wave | 
 |     yo = int(ys * 1.5)  # y offest between lines of waves | 
 |     xg = 150  # xoffset of waves (space for names and groups) | 
 |     yh0 = 0  # height allocated for header tick/tock labels | 
 |     yh1 = 0  # height allocated for header string | 
 |     headtext = ''  # header string | 
 |     headticktock = 0  # does header have tick=1/tock=2 | 
 |     yf0 = 0  # height allocated for footer tick/tock labels | 
 |     yf1 = 0  # height allocated for footer string | 
 |     foottext = ''  # footer string | 
 |     footticktock = 0  # does footer have tick=1/tock=2 | 
 |     global prevdefs  # holds bricks previously defined | 
 |     events = {} | 
 |  | 
 |     if svg_num == 0: | 
 |         bricksused = ['gap'] | 
 |         prevdefs = [] | 
 |     else: | 
 |         bricksused = [] | 
 |  | 
 |     # section was parseConfig in js | 
 |     if 'config' in obj and 'hscale' in obj['config']: | 
 |         hscale = int(obj['config']['hscale']) | 
 |         log.info("Set hscale to " + str(hscale)) | 
 |     else: | 
 |         hscale = 1 | 
 |  | 
 |     if 'head' in obj: | 
 |         head = obj['head'] | 
 |         if 'tick' in head: | 
 |             yh0 = 20 | 
 |             headtt = head['tick'] | 
 |             headticktock = 1 | 
 |         elif 'tock' in head: | 
 |             yh0 = 20 | 
 |             headtt = head['tock'] | 
 |             headticktock = 2 | 
 |  | 
 |         if 'text' in head: | 
 |             yh1 = 46 | 
 |             headtext = head['text'] | 
 |  | 
 |     if 'foot' in obj: | 
 |         foot = obj['foot'] | 
 |         if 'tick' in foot: | 
 |             yf0 = 20 | 
 |             foottt = foot['tick'] | 
 |             footticktock = 1 | 
 |         elif 'tock' in foot: | 
 |             yf0 = 20 | 
 |             foottt = foot['tock'] | 
 |             footticktock = 2 | 
 |  | 
 |         if 'text' in foot: | 
 |             yf1 = 46 | 
 |             foottext = foot['text'] | 
 |  | 
 |     if 'edge' in obj: | 
 |         if 'arrows' not in prevdefs: | 
 |             bricksused.append('arrows') | 
 |         edges = obj['edge'] | 
 |         log.info("Got edge: " + str(edges)) | 
 |     else: | 
 |         edges = [] | 
 |  | 
 |     # build the signal bricks array | 
 |  | 
 |     lanes = [] | 
 |     groups = [] | 
 |     xmax = 0 | 
 |     if 'signal' in obj: | 
 |         for x in obj['signal']: | 
 |             xlen = parse_wave(x, hscale, lanes, groups, 0, bricksused) | 
 |             if xlen > xmax: | 
 |                 xmax = xlen | 
 |  | 
 |     log.info("Got " + str(len(lanes)) + " lanes. xmax is " + str(xmax)) | 
 |     log.info(str(lanes)) | 
 |  | 
 |     outbuf = io.StringIO() | 
 |  | 
 |     height = len(lanes) * yo + yh0 + yh1 + yf0 + yf1 | 
 |     width = xg + xmax * xs + xs | 
 |     wheight = len(lanes) * yo | 
 |  | 
 |     render_svghead(outbuf, width, height, bricksused, svg_num) | 
 |     render_lanes_head(outbuf, xg, yh0 + yh1, svg_num) | 
 |     render_marks(outbuf, xmax, xs, wheight, svg_num) | 
 |     if yh1 != 0: | 
 |         render_caption(outbuf, headtext, xmax, xs, -33 if yh0 != 0 else -13) | 
 |  | 
 |     if yf1 != 0: | 
 |         render_caption(outbuf, foottext, xmax, xs, | 
 |                        wheight + (45 if yf0 != 0 else 25)) | 
 |  | 
 |     if headticktock == 1: | 
 |         render_ticktock(outbuf, headtt, 0, 2 * xs, -5, xmax // 2) | 
 |     if headticktock == 2: | 
 |         render_ticktock(outbuf, headtt, xs, 2 * xs, -5, xmax // 2) | 
 |  | 
 |     if footticktock == 1: | 
 |         render_ticktock(outbuf, foottt, 0, 2 * xs, wheight + 15, xmax // 2) | 
 |     if footticktock == 2: | 
 |         render_ticktock(outbuf, foottt, xs, 2 * xs, wheight + 15, xmax // 2) | 
 |  | 
 |     render_wavelanes(outbuf, xs, yo, lanes, svg_num, events) | 
 |     if (len(events) > 0): | 
 |         render_events(outbuf, svg_num, events, edges) | 
 |     render_lanes_tail(outbuf) | 
 |     render_groups(outbuf, groups, yh0 + yh1, yo, svg_num) | 
 |     render_svgtail(outbuf) | 
 |     prevdefs.extend(bricksused) | 
 |  | 
 |     generated = outbuf.getvalue() | 
 |     outbuf.close() | 
 |     return generated |