Coverage for apio/scons/gtkwave_util.py: 96%
36 statements
« prev ^ index » next coverage.py v7.17.2, created at 2026-11-07 05:08 +0000
« prev ^ index » next coverage.py v7.17.2, created at 2026-11-07 05:08 +0000
1# This file is part of the Apio project
2# (C) 2016-present FPGAwars
3# Project authors:
4# * Jesús Arroyo (Jesus89) (2016-2019)
5# * Juan Gonzalez (obijuan) (2019-2024)
6# * The Apio Team (2024-present)
7# License: GPLv2
9# GTKWave related utils.
11"""GTKWave related utilities for the Apio Scons sub process."""
13import re
14from pathlib import Path
15from vcdvcd import VCDVCD
16from apio.common.apio_console import fatal_error
18GTKW_AUTO_FILE_MARKER = "THIS FILE WAS GENERATED AUTOMATICALLY BY APIO"
21def _get_gtkw_file_header(testbench_path: str) -> str:
22 """Return a header string for the auto generated .gtkw file. 'testbench'
23 is the relative path of the testbench file."""
25 # -- Normalized path with '/', even on windows.
26 tb_path_posix = Path(testbench_path).as_posix()
28 lines = [
29 f"[*] GTKWave display configuration for 'apio sim {tb_path_posix}'",
30 f"[*] {GTKW_AUTO_FILE_MARKER}.",
31 "[*] DO NOT EDIT IT MANUALLY!",
32 f"[*] To customize this file, run 'apio sim {tb_path_posix}'",
33 "[*] and save the file from GTKWave.",
34 "",
35 "[*] GTKWave Analyzer v3.4.0 (w)1999-2022 BSI",
36 "",
37 "[*]",
38 ]
40 return "\n".join(lines) + "\n"
43def create_gtkwave_file(
44 testbench_path: str, vcd_path: str, gtkw_path: str
45) -> None:
46 """Generates a GTKWave configuration file from a VCD file.
48 Args:
49 testbench_path (str): Path to the simulated testbench.
50 vcd_path (str): Path to the input VCD file.
51 gtkw_path (str): Path to the output GTKWave configuration file.
52 """
54 # -- Pattern for top levels signals. E.g. 'testbench.CLK'.
55 pattern = re.compile(r"^[^.]+[.][^.]+$")
57 # -- Parse the vcd file and load the signals that match the pattern.
58 # -- Do not load the actual signals values, just the metadata.
59 vcd = VCDVCD(vcd_path, signal_res=[pattern], store_tvs=False)
61 # -- Get a list of the matching signals names. GTKWave names signals
62 # -- of these VCD types without the bit range, e.g. 'main_tb.i' rather
63 # -- than 'main_tb.i[31:0]', so we strip it. Otherwise GTKWave 4 fails
64 # -- to find them and prints warnings.
65 no_range_types = {"integer", "parameter", "real", "string"}
66 range_pattern = re.compile(r"\s*\[[^\[\]]*\]$")
67 signals = []
68 for name, signal_id in vcd.references_to_ids.items():
69 if vcd.data[signal_id].var_type in no_range_types:
70 # -- E.g. 'main_tb.i[31:0]' -> 'main_tb.i'.
71 name = range_pattern.sub("", name)
72 signals.append(name)
74 # -- Sort, case insensitive.
75 signals.sort(key=str.casefold)
77 # -- Write the output file.
78 with open(gtkw_path, "w", encoding="utf-8") as f:
79 f.write(_get_gtkw_file_header(testbench_path))
80 for signal in signals:
81 f.write(signal + "\n")
84def is_user_gtkw_file(gtkw_path: str) -> bool:
85 """Test if the given .gtkw file exists and contains user's saved
86 GTKWave display configuration."""
88 # pylint: disable=broad-exception-caught
90 assert gtkw_path.endswith(".gtkw")
92 # -- If doesn't exist than now.
93 if not Path(gtkw_path).exists():
94 return False
96 # -- File exists, test the content.
97 try:
98 # with gtkw_path.open("r", encoding="utf-8", errors="replace") as f:
99 with open(gtkw_path, "r", encoding="utf-8", errors="replace") as f:
100 for line in f:
101 if GTKW_AUTO_FILE_MARKER in line:
102 # -- File contains the apio auto marker. Not a user file.
103 return False
104 # -- Marker not found. Must be a user file.
105 return True
107 except Exception as e:
108 fatal_error(f"Failed to scan existing .gtkw file {gtkw_path}", cause=e)