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

161 statements  

« prev     ^ index     » next       coverage.py v7.11.0, created at 2025-10-28 23:17 +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 

48from pyEDAA.OSVVM.Project.Procedures import ConstraintFile, ScopeToRef, ScopeToCell 

49 

50 

51@export 

52class TclEnvironment: 

53 _tcl: Tk 

54 _procedures: Dict[str, Callable] 

55 _context: Context 

56 

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

58 self._context = context 

59 context._processor = self 

60 

61 self._tcl = Tcl() 

62 self._procedures = {} 

63 

64 @readonly 

65 def TCL(self) -> Tk: 

66 return self._tcl 

67 

68 @readonly 

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

70 return self._procedures 

71 

72 @readonly 

73 def Context(self) -> Context: 

74 return self._context 

75 

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

77 if tclProcedureName is None: 

78 tclProcedureName = pythonFunction.__name__ 

79 

80 self._tcl.createcommand(tclProcedureName, pythonFunction) 

81 self._procedures[tclProcedureName] = pythonFunction 

82 

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

84 try: 

85 self._tcl.eval(tclCode) 

86 except TclError as e: 

87 e = getException(e, self._context) 

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

89 ex.add_note(tclCode) 

90 raise ex from e 

91 

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

93 try: 

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

95 except TclError as e: 

96 ex = getException(e, self._context) 

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

98 

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

100 self._tcl.setvar(tclVariableName, value) 

101 

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

103 return self._tcl.getvar(tclVariableName) 

104 

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

106 self._tcl.unsetvar(tclVariableName) 

107 

108 

109@export 

110class OsvvmVariables: 

111 _vhdlVersion: VHDLVersion 

112 _toolVendor: str 

113 _toolName: str 

114 _toolVersion: str 

115 

116 def __init__( 

117 self, 

118 vhdlVersion: Nullable[VHDLVersion] = None, 

119 toolVendor: Nullable[str] = None, 

120 toolName: Nullable[str] = None, 

121 toolVersion: Nullable[str] = None 

122 ) -> None: 

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

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

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

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

127 

128 @readonly 

129 def VHDlversion(self) -> VHDLVersion: 

130 return self._vhdlVersion 

131 

132 @readonly 

133 def ToolVendor(self) -> str: 

134 return self._toolVendor 

135 

136 @readonly 

137 def ToolName(self) -> str: 

138 return self._toolName 

139 

140 @readonly 

141 def ToolVersion(self) -> str: 

142 return self._toolVersion 

143 

144 

145@export 

146class OsvvmProFileProcessor(TclEnvironment): 

147 def __init__( 

148 self, 

149 context: Nullable[Context] = None, 

150 osvvmVariables: Nullable[OsvvmVariables] = None 

151 ) -> None: 

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

153 context = osvvmContext 

154 

155 super().__init__(context) 

156 

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

158 osvvmVariables = OsvvmVariables() 

159 

160 self.LoadOsvvmDefaults(osvvmVariables) 

161 self.OverwriteTclProcedures() 

162 self.RegisterTclProcedures() 

163 

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

165 match osvvmVariables.VHDlversion: 

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

167 version = "2002" 

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

169 version = "2008" 

170 case VHDLVersion.VHDL2019: 

171 version = "2019" 

172 case _: 

173 version = "unsupported" 

174 

175 code = dedent(f"""\ 

176 namespace eval ::osvvm {{ 

177 variable VhdlVersion {version} 

178 variable ToolVendor "{osvvmVariables.ToolVendor}" 

179 variable ToolName "{osvvmVariables.ToolName}" 

180 variable ToolNameVersion "{osvvmVariables.ToolVersion}" 

181 variable ToolSupportsDeferredConstants 1 

182 variable ToolSupportsGenericPackages 1 

183 variable FunctionalCoverageIntegratedInSimulator "default" 

184 variable Support2019FilePath 1 

185 

186 variable ClockResetVersion 0 

187 }} 

188 """) 

189 

190 try: 

191 self._tcl.eval(code) 

192 except TclError as ex: 

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

194 

195 def OverwriteTclProcedures(self) -> None: 

196 self.RegisterPythonFunctionAsTclProcedure(noop, "puts") 

197 

198 def RegisterTclProcedures(self) -> None: 

199 self.RegisterPythonFunctionAsTclProcedure(build) 

200 self.RegisterPythonFunctionAsTclProcedure(include) 

201 self.RegisterPythonFunctionAsTclProcedure(library) 

202 self.RegisterPythonFunctionAsTclProcedure(analyze) 

203 self.RegisterPythonFunctionAsTclProcedure(simulate) 

204 self.RegisterPythonFunctionAsTclProcedure(generic) 

205 

206 self.RegisterPythonFunctionAsTclProcedure(BuildName) 

207 self.RegisterPythonFunctionAsTclProcedure(NoNullRangeWarning) 

208 

209 self.RegisterPythonFunctionAsTclProcedure(TestSuite) 

210 self.RegisterPythonFunctionAsTclProcedure(TestName) 

211 self.RegisterPythonFunctionAsTclProcedure(RunTest) 

212 

213 self.RegisterPythonFunctionAsTclProcedure(SetVHDLVersion) 

214 self.RegisterPythonFunctionAsTclProcedure(GetVHDLVersion) 

215 self.RegisterPythonFunctionAsTclProcedure(SetCoverageAnalyzeEnable) 

216 self.RegisterPythonFunctionAsTclProcedure(SetCoverageSimulateEnable) 

217 

218 self.RegisterPythonFunctionAsTclProcedure(FileExists) 

219 self.RegisterPythonFunctionAsTclProcedure(DirectoryExists) 

220 self.RegisterPythonFunctionAsTclProcedure(ChangeWorkingDirectory) 

221 

222 self.RegisterPythonFunctionAsTclProcedure(FindOsvvmSettingsDirectory) 

223 self.RegisterPythonFunctionAsTclProcedure(CreateOsvvmScriptSettingsPkg) 

224 

225 self.RegisterPythonFunctionAsTclProcedure(ConstraintFile) 

226 self.RegisterPythonFunctionAsTclProcedure(ScopeToRef) 

227 self.RegisterPythonFunctionAsTclProcedure(ScopeToCell) 

228 

229 self.RegisterPythonFunctionAsTclProcedure(noop, "OpenBuildHtml") 

230 self.RegisterPythonFunctionAsTclProcedure(noop, "SetTranscriptType") 

231 self.RegisterPythonFunctionAsTclProcedure(noop, "GetTranscriptType") 

232 self.RegisterPythonFunctionAsTclProcedure(noop, "SetSimulatorResolution") 

233 self.RegisterPythonFunctionAsTclProcedure(noop, "GetSimulatorResolution") 

234 

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

236 # TODO: should a context be used with _context to restore _currentDirectory? 

237 includeFile = self._context.IncludeFile(path) 

238 self.EvaluateProFile(includeFile) 

239 

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

241 if buildName is None: 

242 buildName = buildFile.stem 

243 

244 self._context.BeginBuild(buildName) 

245 includeFile = self._context.IncludeFile(buildFile) 

246 self.EvaluateProFile(includeFile) 

247 

248 # TODO: should a context be used with _context to restore _currentDirectory? 

249 return self._context.EndBuild() 

250 

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

252 if projectName is None: 

253 projectName = regressionFile.stem 

254 

255 self.EvaluateProFile(regressionFile) 

256 

257 return self._context.ToProject(projectName) 

258 

259 

260@export 

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

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

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

264 return lastException 

265 

266 return ex