Navigation Menu

Skip to content

Instantly share code, notes, and snippets.

@Towdium
Last active October 28, 2022 08:56
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Towdium/794b778125434c2334102e1510868a90 to your computer and use it in GitHub Desktop.
Save Towdium/794b778125434c2334102e1510868a90 to your computer and use it in GitHub Desktop.
import svgwrite
import re
import sys
'''
A little script to convert Altera waveform (.vwf) to SVG image
Make sure you have pip package svgwrite installed by 'pip install svgwrite'
Usage 'python vwf2svg.py file_name.vwf'
By the way, python 3 please
'''
class Bit:
def __init__(self, data):
self.data = data
@staticmethod
def clock(time, end, duty=0.5, phase=0):
ret = []
time1 = time * duty
time0 = time - time1
time_remaining = end
if phase != 0:
phase_d = -phase % time
if phase_d > time1:
ret.append((phase_d - time1, False))
ret.append((time1, True))
else:
ret.append((phase_d, True))
time_remaining -= phase_d
group = [(time0, False), (time1, True)]
amount = int(time_remaining / time)
ret += amount * group
time_remaining -= amount * time
if time_remaining > 0:
if time_remaining < time0:
ret.append((time_remaining, False))
else:
ret.append((time0, False))
ret.append((time_remaining - time0, True))
return Bit(ret)
@staticmethod
def sequence(init, times):
ret = []
for time in times:
ret.append((time, init))
init = not init
return Bit(ret)
class Port:
def __init__(self, name, out, bits):
self.out = out
self.bits = bits
self.name = name
class Wave:
def __init__(self, end, data=None):
self.ports = [] if data is None else data
self.end = end
def add_port(self, port):
self.ports.append(port)
def add_ports(self, ports):
self.ports += ports
def draw(self, filename):
dwg = svgwrite.Drawing(filename, (620, len(self.ports) * 20 + 20))
time = 0
bit = self.ports[0].bits[0]
for t, v in bit.data:
time += t
width = 0
for port in self.ports:
w = len(port.name) * 6
width = max(w, width)
reserved = 32 + width
Wave._draw_frame(dwg, len(self.ports), time, reserved)
for i, p in enumerate(self.ports):
if len(p.bits) == 1:
Wave._draw_single(dwg, p.name, p.bits[0], i, (600 - reserved) / self.end, reserved, p.out)
else:
Wave._draw_bus(dwg, p.name, p.bits, i, (600 - reserved) / self.end, reserved, p.out)
dwg.save()
@staticmethod
def _draw_frame(drawing, total, end, x_off):
width = 10
while width * 100 < end:
width *= 2
width = width / end * (600 - x_off)
pos = x_off + width
while pos < 601:
drawing.add(drawing.line((pos, 20), (pos, 20 * total + 20),
stroke='silver', stroke_width=0.5, stroke_dasharray='2 0.2'))
pos += width
drawing.add(drawing.line((0, 20), (600, 20), stroke='grey', stroke_width=0.5))
drawing.add(drawing.line((x_off, 20), (x_off, 20 * total + 20), stroke='grey', stroke_width=0.5))
width = 10
while width * 15 < end:
width *= 2
width_scaled = width / end * (600 - x_off)
pos = x_off
time = 0
while pos < 601:
drawing.add(drawing.text(Wave._to_time(time), (pos, 13), font_size='10px',
font_family='consolas, monospace', text_anchor='middle'))
drawing.add(drawing.line((pos, 17), (pos, 20), stroke='grey', stroke_width=0.5))
pos += width_scaled
time += width
@staticmethod
def _to_time(time):
if time > 1000:
return '%.2fus' % (time / 1000)
else:
return '%dns' % time
@staticmethod
def _draw_single(drawing, name, bit, index, scale, off_x, out):
y0, y1 = index * 20 + 37, index * 20 + 23
Wave._draw_name(drawing, out, False, name, index)
time, value = 0, bit.data[0][1]
p = drawing.add(drawing.path(fill='none', stroke='black', stroke_width=0.4))
p.push('m', (off_x + time * scale, y1 if value else y0))
for t, v in bit.data:
if v != value:
p.push('v', -14 if v else 14)
if v:
x = off_x + scale * time
drawing.add(drawing.line((x, y1 + 0.5), (x + t * scale, y1 + 0.4), stroke='black', stroke_width=0.8))
p.push('h', t * scale)
time += t
value = v
@staticmethod
def _draw_bus(drawing, name, bits, index, scale, off_x, out):
y = index * 20 + 30
Wave._draw_name(drawing, out, True, name, index)
data = Wave._mix_bit(bits)
time = 0
for t, v in data:
Wave._draw_poly(drawing, v, off_x + time * scale, y, t * scale)
time += t
@staticmethod
def _draw_poly(drawing, value, x, y, dx):
p = drawing.add(drawing.path(fill='none', stroke='black', stroke_width=0.4))
width = dx / 2 if dx < 6 else 3
horizontal = dx - 2 * width
p.push('m', (x, y))
p.push('l', (width, -6))
p.push('h', horizontal)
p.push('l', (width, 6))
p.push('l', (-width, 6))
p.push('h', -horizontal)
p.push('l', (-width, -6))
if len(value) * 6 > dx:
value = value[:int(dx/6)]
drawing.add(drawing.text(value[::-1], (x + dx / 2 - len(value) * 6 / 2, y + 4),
font_size='10px', font_family='consolas, monospace'))
@staticmethod
def _mix_bit(bits: [Bit]):
time_change = set()
bits_abs = []
time_total = 0
sorted(time_change)
for bit in bits:
data = {}
time = 0
for t, v in bit.data:
data[time] = v
time_change.add(time)
time += t
time_total = max(time_total, time)
bits_abs.append(data)
time_change = sorted(time_change)
bits_data = {}
for time in time_change:
s = ''
for bit in bits_abs:
time_cache = 0
for t, v in bit.items():
if t > time:
break
else:
time_cache = t
s += '1' if bit[time_cache] else '0'
bits_data[time] = s[::-1]
ret = []
time_cache = 0
value_cache = ''
for t, v in bits_data.items():
if t == time_cache:
value_cache = v
else:
ret.append((t - time_cache, value_cache))
time_cache = t
value_cache = v
ret.append((time_total - time_cache, value_cache))
return ret
@staticmethod
def _draw_name(drawing, out, bus, name, index):
drawing.add(drawing.text(name, (22, index * 20 + 33), font_size='10px', font_family='consolas, monospace'))
Wave._draw_icon(drawing, out, bus, index)
@staticmethod
def _draw_icon(drawing, out, bus, index):
x1 = 2 if out else 0
x2 = -9 if out else 0
if bus:
drawing.add(drawing.line((13 + x2, index * 20 + 29), (16 + x2, index * 20 + 29),
stroke='black', stroke_width=0.5))
drawing.add(drawing.line((15 + x2, index * 20 + 31), (18 + x2, index * 20 + 31),
stroke='black', stroke_width=0.5))
if out:
Wave._draw_port(drawing, 15 + x1, index * 20 + 31)
Wave._draw_port(drawing, 13 + x1, index * 20 + 29)
else:
Wave._draw_port(drawing, 13 + x1, index * 20 + 29)
Wave._draw_port(drawing, 15 + x1, index * 20 + 31)
else:
Wave._draw_port(drawing, 14 + x1, index * 20 + 30)
drawing.add(drawing.line((14 + x2, index * 20 + 30), (17 + x2, index * 20 + 30),
stroke='black', stroke_width=0.5))
@staticmethod
def _draw_port(drawing, x, y):
p = drawing.add(drawing.path(fill='white', stroke='black', stroke_width=0.5))
p.push('m', (x, y))
p.push('l', (-3, 3))
p.push('h', -5)
p.push('v', -6)
p.push('h', 5)
p.push('l', (3, 3))
@staticmethod
def from_file(filename):
ports = dict()
bits = dict()
with open(filename, 'r') as f:
s = f.read()
s = re.sub(Wave._reg_cmt, '', s)
s = re.sub('\n+', '\n', s)
ss = []
p = re.compile(r'\n[\nA-Z]')
m = p.search(s)
while m:
ss.append(s[0:m.start(0)])
s = s[m.end(0)-1:]
m = p.search(s)
# ss = s.split('\n\n')
for i in range(len(ss)):
ss[i] = ss[i].replace('\n', '').replace('\t', '').replace(' ', '')
ss = [i for i in ss if i]
length = 0
for i in ss:
if i.startswith('TRANSITION_LIST'):
name, sect = Wave._to_bit(i)
bits[name] = sect
elif i.startswith('SIGNAL'):
parent = Wave._reg_sgn_pnt.search(i).group(1)
name = Wave._reg_sgn_name.search(i).group(1)
bus = Wave._reg_sgn_bus.search(i).group(1) == 'BUS'
out = Wave._reg_sgn_dir.search(i).group(1) == 'OUTPUT'
if parent:
ports[parent][0].append(name)
elif not bus:
ports[name] = ([name], out)
else:
ports[name] = ([], out)
elif i.startswith('HEADER'):
length = float(Wave._reg_len.search(i).group(1))
ret = []
for k, v in ports.items():
ret.append(Port(k, v[1], [bits[i] for i in v[0]]))
return Wave(length, ret)
_reg_cmt = re.compile('/\*.*?\*/', re.DOTALL)
_reg_bit = re.compile(r'TRANSITION_LIST\("(.*?)"\){(.*)}')
_reg_node = re.compile(r'REPEAT=(\d*);(.*)')
_reg_stat = re.compile(r'LEVEL(\d)FOR(.*);')
_reg_sgn_pnt = re.compile(r'PARENT="(.*?)"')
_reg_sgn_name = re.compile(r'SIGNAL\("(.*?)"\)')
_reg_sgn_bus = re.compile(r'SIGNAL_TYPE=(.*?);')
_reg_sgn_dir = re.compile(r'DIRECTION=(.*?);')
_reg_len = re.compile(r'DATA_DURATION=(.*?);')
@staticmethod
def _to_bit(s):
m = Wave._reg_bit.fullmatch(s)
return m.group(1), Bit(Wave._to_section(m.group(2))[0])
@staticmethod
def _to_section(s: str):
start = s.index('{')
s = s[start + 1:]
level = 1
remaining = ''
for i in range(len(s)):
if s[i] == '{':
level += 1
elif s[i] == '}':
level -= 1
if level == 0:
remaining = s[i + 1:]
s = s[:i]
break
ret = []
m = Wave._reg_node.fullmatch(s)
repeat = m.group(1)
s = m.group(2)
while s:
if s.startswith('NODE'):
sect, s = Wave._to_section(s)
ret += sect
else:
i = s.index(';')
stat = s[:i + 1]
s = s[i + 1:]
ms = Wave._reg_stat.fullmatch(stat)
ret.append((float(ms.group(2)), ms.group(1) == '1'))
return ret * int(repeat), remaining
def main():
name = sys.argv[1]
if name.endswith('.vwf'):
print('Converting file: ' + name)
w = Wave.from_file(name)
w.draw(name[:-4] + '.svg')
else:
print('Invalid file: ' + name)
if __name__ == '__main__':
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment