Last active
October 28, 2022 08:56
-
-
Save Towdium/794b778125434c2334102e1510868a90 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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