Coverage for pyEDAA/OSVVM/Project/TCL.py: 75%

157 statements  

« prev     ^ index     » next       coverage.py v7.9.1, created at 2025-06-27 22:24 +0000

1# ==================================================================================================================== # 

2# _____ ____ _ _ ___ ______ ____ ____ __ # 

3# _ __ _ _| ____| _ \ / \ / \ / _ \/ ___\ \ / /\ \ / / \/ | # 

4# | '_ \| | | | _| | | | |/ _ \ / _ \ | | | \___ \\ \ / / \ \ / /| |\/| | # 

5# | |_) | |_| | |___| |_| / ___ \ / ___ \ | |_| |___) |\ V / \ V / | | | | # 

6# | .__/ \__, |_____|____/_/ \_\/_/ \_(_)___/|____/ \_/ \_/ |_| |_| # 

7# |_| |___/ # 

8# ==================================================================================================================== # 

9# Authors: # 

10# Patrick Lehmann # 

11# # 

12# License: # 

13# ==================================================================================================================== # 

14# Copyright 2025-2025 Patrick Lehmann - Boetzingen, Germany # 

15# # 

16# Licensed under the Apache License, Version 2.0 (the "License"); # 

17# you may not use this file except in compliance with the License. # 

18# You may obtain a copy of the License at # 

19# # 

20# http://www.apache.org/licenses/LICENSE-2.0 # 

21# # 

22# Unless required by applicable law or agreed to in writing, software # 

23# distributed under the License is distributed on an "AS IS" BASIS, # 

24# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # 

25# See the License for the specific language governing permissions and # 

26# limitations under the License. # 

27# # 

28# SPDX-License-Identifier: Apache-2.0 # 

29# ==================================================================================================================== # 

30# 

31from pathlib import Path 

32from textwrap import dedent 

33from tkinter import Tk, Tcl, TclError 

34from typing import Any, Dict, Callable, Optional as Nullable 

35 

36from pyTooling.Decorators import readonly, export 

37from pyVHDLModel import VHDLVersion 

38 

39from pyEDAA.OSVVM import OSVVMException 

40from pyEDAA.OSVVM.Project import Context, osvvmContext, Build, Project 

41from pyEDAA.OSVVM.Project.Procedures import noop, NoNullRangeWarning 

42from pyEDAA.OSVVM.Project.Procedures import FileExists, DirectoryExists, FindOsvvmSettingsDirectory 

43from pyEDAA.OSVVM.Project.Procedures import build, BuildName, include, library, analyze, simulate, generic 

44from pyEDAA.OSVVM.Project.Procedures import TestSuite, TestName, RunTest 

45from pyEDAA.OSVVM.Project.Procedures import ChangeWorkingDirectory, CreateOsvvmScriptSettingsPkg 

46from pyEDAA.OSVVM.Project.Procedures import SetVHDLVersion, GetVHDLVersion 

47from pyEDAA.OSVVM.Project.Procedures import SetCoverageAnalyzeEnable, SetCoverageSimulateEnable 

48 

49 

50@export 

51class TclEnvironment: 

52 _tcl: Tk 

53 _procedures: Dict[str, Callable] 

54 _context: Context 

55 

56 def __init__(self, context: Context) -> None: 

57 self._context = context 

58 context._processor = self 

59 

60 self._tcl = Tcl() 

61 self._procedures = {} 

62 

63 @readonly 

64 def TCL(self) -> Tk: 

65 return self._tcl 

66 

67 @readonly 

68 def Procedures(self) -> Dict[str, Callable]: 

69 return self._procedures 

70 

71 @readonly 

72 def Context(self) -> Context: 

73 return self._context 

74 

75 def RegisterPythonFunctionAsTclProcedure(self, pythonFunction: Callable, tclProcedureName: Nullable[str] = None): 

76 if tclProcedureName is None: 

77 tclProcedureName = pythonFunction.__name__ 

78 

79 self._tcl.createcommand(tclProcedureName, pythonFunction) 

80 self._procedures[tclProcedureName] = pythonFunction 

81 

82 def EvaluateTclCode(self, tclCode: str) -> None: 

83 try: 

84 self._tcl.eval(tclCode) 

85 except TclError as e: 

86 e = getException(e, self._context) 

87 ex = OSVVMException(f"Caught TclError while evaluating TCL code.") 

88 ex.add_note(tclCode) 

89 raise ex from e 

90 

91 def EvaluateProFile(self, path: Path) -> None: 

92 try: 

93 self._tcl.evalfile(str(path)) 

94 except TclError as e: 

95 ex = getException(e, self._context) 

96 raise OSVVMException(f"Caught TclError while processing '{path}'.") from ex 

97 

98 def __setitem__(self, tclVariableName: str, value: Any) -> None: 

99 self._tcl.setvar(tclVariableName, value) 

100 

101 def __getitem__(self, tclVariableName: str) -> None: 

102 return self._tcl.getvar(tclVariableName) 

103 

104 def __delitem__(self, tclVariableName: str) -> None: 

105 self._tcl.unsetvar(tclVariableName) 

106 

107 

108@export 

109class OsvvmVariables: 

110 _vhdlVersion: VHDLVersion 

111 _toolVendor: str 

112 _toolName: str 

113 _toolVersion: str 

114 

115 def __init__( 

116 self, 

117 vhdlVersion: Nullable[VHDLVersion] = None, 

118 toolVendor: Nullable[str] = None, 

119 toolName: Nullable[str] = None, 

120 toolVersion: Nullable[str] = None 

121 ) -> None: 

122 self._vhdlVersion = vhdlVersion if vhdlVersion is not None else VHDLVersion.VHDL2008 

123 self._toolVendor = toolVendor if toolVendor is not None else "EDA²" 

124 self._toolName = toolName if toolName is not None else "pyEDAA.ProjectModel" 

125 self._toolVersion = toolVersion if toolVersion is not None else "0.1" 

126 

127 @readonly 

128 def VHDlversion(self) -> VHDLVersion: 

129 return self._vhdlVersion 

130 

131 @readonly 

132 def ToolVendor(self) -> str: 

133 return self._toolVendor 

134 

135 @readonly 

136 def ToolName(self) -> str: 

137 return self._toolName 

138 

139 @readonly 

140 def ToolVersion(self) -> str: 

141 return self._toolVersion 

142 

143 

144@export 

145class OsvvmProFileProcessor(TclEnvironment): 

146 def __init__( 

147 self, 

148 context: Nullable[Context] = None, 

149 osvvmVariables: Nullable[OsvvmVariables] = None 

150 ) -> None: 

151 if context is None: 151 ↛ 154line 151 didn't jump to line 154 because the condition on line 151 was always true

152 context = osvvmContext 

153 

154 super().__init__(context) 

155 

156 if osvvmVariables is None: 156 ↛ 159line 156 didn't jump to line 159 because the condition on line 156 was always true

157 osvvmVariables = OsvvmVariables() 

158 

159 self.LoadOsvvmDefaults(osvvmVariables) 

160 self.OverwriteTclProcedures() 

161 self.RegisterTclProcedures() 

162 

163 def LoadOsvvmDefaults(self, osvvmVariables: OsvvmVariables) -> None: 

164 match osvvmVariables.VHDlversion: 

165 case VHDLVersion.VHDL2002: 165 ↛ 166line 165 didn't jump to line 166 because the pattern on line 165 never matched

166 version = "2002" 

167 case VHDLVersion.VHDL2008: 167 ↛ 169line 167 didn't jump to line 169 because the pattern on line 167 always matched

168 version = "2008" 

169 case VHDLVersion.VHDL2019: 

170 version = "2019" 

171 case _: 

172 version = "unsupported" 

173 

174 code = dedent(f"""\ 

175 namespace eval ::osvvm {{ 

176 variable VhdlVersion {version} 

177 variable ToolVendor "{osvvmVariables.ToolVendor}" 

178 variable ToolName "{osvvmVariables.ToolName}" 

179 variable ToolNameVersion "{osvvmVariables.ToolVersion}" 

180 variable ToolSupportsDeferredConstants 1 

181 variable ToolSupportsGenericPackages 1 

182 variable FunctionalCoverageIntegratedInSimulator "default" 

183 variable Support2019FilePath 1 

184 

185 variable ClockResetVersion 0 

186 }} 

187 """) 

188 

189 try: 

190 self._tcl.eval(code) 

191 except TclError as ex: 

192 raise OSVVMException(f"TCL error occurred, when initializing OSVVM variables.") from ex 

193 

194 def OverwriteTclProcedures(self) -> None: 

195 self.RegisterPythonFunctionAsTclProcedure(noop, "puts") 

196 

197 def RegisterTclProcedures(self) -> None: 

198 self.RegisterPythonFunctionAsTclProcedure(build) 

199 self.RegisterPythonFunctionAsTclProcedure(include) 

200 self.RegisterPythonFunctionAsTclProcedure(library) 

201 self.RegisterPythonFunctionAsTclProcedure(analyze) 

202 self.RegisterPythonFunctionAsTclProcedure(simulate) 

203 self.RegisterPythonFunctionAsTclProcedure(generic) 

204 

205 self.RegisterPythonFunctionAsTclProcedure(BuildName) 

206 self.RegisterPythonFunctionAsTclProcedure(NoNullRangeWarning) 

207 

208 self.RegisterPythonFunctionAsTclProcedure(TestSuite) 

209 self.RegisterPythonFunctionAsTclProcedure(TestName) 

210 self.RegisterPythonFunctionAsTclProcedure(RunTest) 

211 

212 self.RegisterPythonFunctionAsTclProcedure(SetVHDLVersion) 

213 self.RegisterPythonFunctionAsTclProcedure(GetVHDLVersion) 

214 self.RegisterPythonFunctionAsTclProcedure(SetCoverageAnalyzeEnable) 

215 self.RegisterPythonFunctionAsTclProcedure(SetCoverageSimulateEnable) 

216 

217 self.RegisterPythonFunctionAsTclProcedure(FileExists) 

218 self.RegisterPythonFunctionAsTclProcedure(DirectoryExists) 

219 self.RegisterPythonFunctionAsTclProcedure(ChangeWorkingDirectory) 

220 

221 self.RegisterPythonFunctionAsTclProcedure(FindOsvvmSettingsDirectory) 

222 self.RegisterPythonFunctionAsTclProcedure(CreateOsvvmScriptSettingsPkg) 

223 

224 self.RegisterPythonFunctionAsTclProcedure(noop, "OpenBuildHtml") 

225 self.RegisterPythonFunctionAsTclProcedure(noop, "SetTranscriptType") 

226 self.RegisterPythonFunctionAsTclProcedure(noop, "GetTranscriptType") 

227 self.RegisterPythonFunctionAsTclProcedure(noop, "SetSimulatorResolution") 

228 self.RegisterPythonFunctionAsTclProcedure(noop, "GetSimulatorResolution") 

229 

230 def LoadIncludeFile(self, path: Path) -> None: 

231 includeFile = self._context.IncludeFile(path) 

232 

233 self.EvaluateProFile(includeFile) 

234 

235 def LoadBuildFile(self, buildFile: Path, buildName: Nullable[str] = None) -> Build: 

236 if buildName is None: 

237 buildName = buildFile.stem 

238 

239 self._context.BeginBuild(buildName) 

240 includeFile = self._context.IncludeFile(buildFile) 

241 self.EvaluateProFile(includeFile) 

242 

243 return self._context.EndBuild() 

244 

245 def LoadRegressionFile(self, regressionFile: Path, projectName: Nullable[str] = None) -> Project: 

246 if projectName is None: 

247 projectName = regressionFile.stem 

248 

249 self.EvaluateProFile(regressionFile) 

250 

251 return self._context.ToProject(projectName) 

252 

253 

254@export 

255def getException(ex: Exception, context: Context) -> Exception: 

256 if str(ex) == "": 256 ↛ 260line 256 didn't jump to line 260 because the condition on line 256 was always true

257 if (lastException := context.LastException) is not None: 257 ↛ 260line 257 didn't jump to line 260 because the condition on line 257 was always true

258 return lastException 

259 

260 return ex