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

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 

8 

9# GTKWave related utils. 

10 

11"""GTKWave related utilities for the Apio Scons sub process.""" 

12 

13import re 

14from pathlib import Path 

15from vcdvcd import VCDVCD 

16from apio.common.apio_console import fatal_error 

17 

18GTKW_AUTO_FILE_MARKER = "THIS FILE WAS GENERATED AUTOMATICALLY BY APIO" 

19 

20 

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.""" 

24 

25 # -- Normalized path with '/', even on windows. 

26 tb_path_posix = Path(testbench_path).as_posix() 

27 

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 ] 

39 

40 return "\n".join(lines) + "\n" 

41 

42 

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. 

47 

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 """ 

53 

54 # -- Pattern for top levels signals. E.g. 'testbench.CLK'. 

55 pattern = re.compile(r"^[^.]+[.][^.]+$") 

56 

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) 

60 

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) 

73 

74 # -- Sort, case insensitive. 

75 signals.sort(key=str.casefold) 

76 

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") 

82 

83 

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.""" 

87 

88 # pylint: disable=broad-exception-caught 

89 

90 assert gtkw_path.endswith(".gtkw") 

91 

92 # -- If doesn't exist than now. 

93 if not Path(gtkw_path).exists(): 

94 return False 

95 

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 

106 

107 except Exception as e: 

108 fatal_error(f"Failed to scan existing .gtkw file {gtkw_path}", cause=e)