opencos-eda 0.2.51__py3-none-any.whl → 0.2.53__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- opencos/commands/__init__.py +2 -0
- opencos/commands/build.py +1 -1
- opencos/commands/deps_help.py +259 -0
- opencos/commands/export.py +1 -1
- opencos/commands/flist.py +3 -0
- opencos/commands/lec.py +1 -1
- opencos/commands/open.py +2 -0
- opencos/commands/proj.py +1 -1
- opencos/commands/shell.py +1 -1
- opencos/commands/sim.py +32 -8
- opencos/commands/synth.py +1 -1
- opencos/commands/upload.py +3 -0
- opencos/commands/waves.py +13 -2
- opencos/deps/defaults.py +1 -0
- opencos/deps/deps_file.py +30 -4
- opencos/deps/deps_processor.py +72 -2
- opencos/deps_schema.py +3 -0
- opencos/eda.py +66 -29
- opencos/eda_base.py +159 -20
- opencos/eda_config.py +2 -2
- opencos/eda_config_defaults.yml +83 -3
- opencos/eda_extract_targets.py +1 -58
- opencos/tests/helpers.py +16 -0
- opencos/tests/test_eda.py +13 -2
- opencos/tests/test_tools.py +227 -6
- opencos/tools/cocotb.py +492 -0
- opencos/tools/modelsim_ase.py +67 -51
- opencos/tools/quartus.py +638 -0
- opencos/tools/questa.py +167 -88
- opencos/tools/questa_fse.py +10 -0
- opencos/tools/riviera.py +1 -0
- opencos/tools/verilator.py +31 -0
- opencos/tools/vivado.py +3 -3
- opencos/util.py +22 -3
- opencos/utils/str_helpers.py +85 -0
- opencos/utils/vscode_helper.py +47 -0
- {opencos_eda-0.2.51.dist-info → opencos_eda-0.2.53.dist-info}/METADATA +2 -1
- {opencos_eda-0.2.51.dist-info → opencos_eda-0.2.53.dist-info}/RECORD +43 -39
- {opencos_eda-0.2.51.dist-info → opencos_eda-0.2.53.dist-info}/WHEEL +0 -0
- {opencos_eda-0.2.51.dist-info → opencos_eda-0.2.53.dist-info}/entry_points.txt +0 -0
- {opencos_eda-0.2.51.dist-info → opencos_eda-0.2.53.dist-info}/licenses/LICENSE +0 -0
- {opencos_eda-0.2.51.dist-info → opencos_eda-0.2.53.dist-info}/licenses/LICENSE.spdx +0 -0
- {opencos_eda-0.2.51.dist-info → opencos_eda-0.2.53.dist-info}/top_level.txt +0 -0
opencos/tools/cocotb.py
ADDED
|
@@ -0,0 +1,492 @@
|
|
|
1
|
+
''' opencos.tools.cocotb - Used by opencos.eda for sim commands with --tool=cocotb.
|
|
2
|
+
|
|
3
|
+
Contains classes for ToolCocotb, CommandSimCocotb.
|
|
4
|
+
'''
|
|
5
|
+
|
|
6
|
+
import os
|
|
7
|
+
import shutil
|
|
8
|
+
import subprocess
|
|
9
|
+
|
|
10
|
+
from opencos import util
|
|
11
|
+
from opencos.eda_base import Tool
|
|
12
|
+
from opencos.commands import CommandSim
|
|
13
|
+
from opencos.utils.str_helpers import sanitize_defines_for_sh
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class ToolCocotb(Tool):
|
|
17
|
+
'''ToolCocotb used by opencos.eda for --tool=cocotb'''
|
|
18
|
+
|
|
19
|
+
_TOOL = 'cocotb'
|
|
20
|
+
_EXE = 'python'
|
|
21
|
+
_URL = 'https://github.com/cocotb/cocotb'
|
|
22
|
+
|
|
23
|
+
cocotb_version = ''
|
|
24
|
+
python_exe = ''
|
|
25
|
+
|
|
26
|
+
def get_versions(self) -> str:
|
|
27
|
+
if self._VERSION:
|
|
28
|
+
return self._VERSION
|
|
29
|
+
|
|
30
|
+
# Check if python is available
|
|
31
|
+
python_path = shutil.which('python') or shutil.which('python3')
|
|
32
|
+
if not python_path:
|
|
33
|
+
self.error('"python" or "python3" not in path, required for cocotb')
|
|
34
|
+
else:
|
|
35
|
+
self.python_exe = python_path
|
|
36
|
+
|
|
37
|
+
# Check if cocotb is installed
|
|
38
|
+
try:
|
|
39
|
+
version_ret = subprocess.run(
|
|
40
|
+
[self.python_exe, '-c', 'import cocotb; print(cocotb.__version__)'],
|
|
41
|
+
capture_output=True,
|
|
42
|
+
check=True,
|
|
43
|
+
text=True
|
|
44
|
+
)
|
|
45
|
+
version = version_ret.stdout.strip()
|
|
46
|
+
self.cocotb_version = version
|
|
47
|
+
self._VERSION = version
|
|
48
|
+
util.debug(f'Found cocotb version: {version}')
|
|
49
|
+
return self._VERSION
|
|
50
|
+
except subprocess.CalledProcessError:
|
|
51
|
+
self.error('cocotb package not installed in python environment. '
|
|
52
|
+
'Install with: pip install cocotb')
|
|
53
|
+
except Exception as e:
|
|
54
|
+
self.error(f'Failed to check cocotb version: {e}')
|
|
55
|
+
|
|
56
|
+
return ''
|
|
57
|
+
|
|
58
|
+
def set_tool_defines(self):
|
|
59
|
+
super().set_tool_defines()
|
|
60
|
+
self.defines.update({
|
|
61
|
+
'SIMULATION': 1,
|
|
62
|
+
'COCOTB': 1,
|
|
63
|
+
'OC_TOOL_COCOTB': None,
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
class CommandSimCocotb(CommandSim, ToolCocotb):
|
|
68
|
+
'''CommandSimCocotb is a command handler for: eda sim --tool=cocotb'''
|
|
69
|
+
|
|
70
|
+
def __init__(self, config: dict):
|
|
71
|
+
CommandSim.__init__(self, config)
|
|
72
|
+
ToolCocotb.__init__(self, config=self.config)
|
|
73
|
+
|
|
74
|
+
self.args.update({
|
|
75
|
+
'gui': False,
|
|
76
|
+
'tcl-file': None,
|
|
77
|
+
'cocotb-test-module': None,
|
|
78
|
+
'cocotb-test-runner': 'python',
|
|
79
|
+
'cocotb-simulator': 'verilator',
|
|
80
|
+
'cocotb-makefile': False,
|
|
81
|
+
'cocotb-python-runner': True,
|
|
82
|
+
'cocotb-standalone-makefile': False,
|
|
83
|
+
})
|
|
84
|
+
|
|
85
|
+
self.args_help.update({
|
|
86
|
+
'waves': 'Include waveforms by setting COCOTB_ENABLE_WAVES=1',
|
|
87
|
+
'cocotb-test-module': 'Python test module name (e.g., test_my_design)',
|
|
88
|
+
'cocotb-test-runner': 'Test runner to use: python (default) or pytest',
|
|
89
|
+
'cocotb-simulator': ('Simulator backend: verilator (default), icarus, etc.'
|
|
90
|
+
' Note that iverilog will convert to icarus here'),
|
|
91
|
+
'cocotb-makefile': 'Use traditional Makefile system instead of Python runner',
|
|
92
|
+
'cocotb-python-runner': 'Use Python-based runner system (default, cocotb 1.8+)',
|
|
93
|
+
'cocotb-standalone-makefile': ('Use provided Makefile as-is, '
|
|
94
|
+
'run make in source directory'),
|
|
95
|
+
})
|
|
96
|
+
|
|
97
|
+
self.cocotb_command_lists = []
|
|
98
|
+
self.cocotb_test_files = []
|
|
99
|
+
|
|
100
|
+
def set_tool_defines(self):
|
|
101
|
+
ToolCocotb.set_tool_defines(self)
|
|
102
|
+
|
|
103
|
+
def prepare_compile(self):
|
|
104
|
+
self.set_tool_defines()
|
|
105
|
+
|
|
106
|
+
# Fix iverilog -> icarus
|
|
107
|
+
if self.args['cocotb-simulator'] == 'iverilog':
|
|
108
|
+
self.args['cocotb-simulator'] = 'icarus'
|
|
109
|
+
|
|
110
|
+
# Find cocotb test files
|
|
111
|
+
self._find_cocotb_test_files()
|
|
112
|
+
|
|
113
|
+
if self.args['cocotb-standalone-makefile']:
|
|
114
|
+
self._prepare_standalone_makefile_system()
|
|
115
|
+
elif self.args['cocotb-makefile']:
|
|
116
|
+
self._prepare_makefile_system()
|
|
117
|
+
else:
|
|
118
|
+
self._prepare_python_runner_system()
|
|
119
|
+
|
|
120
|
+
# Create directories
|
|
121
|
+
paths = ['logs', 'sim_build']
|
|
122
|
+
util.safe_mkdirs(base=self.args['work-dir'], new_dirs=paths)
|
|
123
|
+
|
|
124
|
+
# Write shell scripts
|
|
125
|
+
util.write_shell_command_file(
|
|
126
|
+
dirpath=self.args['work-dir'],
|
|
127
|
+
filename='cocotb_test.sh',
|
|
128
|
+
command_lists=self.cocotb_command_lists,
|
|
129
|
+
line_breaks=True
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
util.write_shell_command_file(
|
|
133
|
+
dirpath=self.args['work-dir'],
|
|
134
|
+
filename='all.sh',
|
|
135
|
+
command_lists=[
|
|
136
|
+
['./pre_compile_dep_shell_commands.sh'],
|
|
137
|
+
['./cocotb_test.sh'],
|
|
138
|
+
]
|
|
139
|
+
)
|
|
140
|
+
|
|
141
|
+
def _find_cocotb_test_files(self):
|
|
142
|
+
'''Find Python test files that contain cocotb tests'''
|
|
143
|
+
self.cocotb_test_files = []
|
|
144
|
+
|
|
145
|
+
# Look for test files in the current directory and deps
|
|
146
|
+
for file_path in self.files_non_source:
|
|
147
|
+
if (file_path.endswith('.py') and
|
|
148
|
+
('test' in file_path.lower() or 'tb' in file_path.lower())):
|
|
149
|
+
# Check if it's a cocotb test file
|
|
150
|
+
try:
|
|
151
|
+
with open(file_path, 'r', encoding='utf-8') as f:
|
|
152
|
+
content = f.read()
|
|
153
|
+
if 'import cocotb' in content or 'from cocotb' in content:
|
|
154
|
+
self.cocotb_test_files.append(file_path)
|
|
155
|
+
util.debug(f'Found cocotb test file: {file_path}')
|
|
156
|
+
except Exception as e:
|
|
157
|
+
util.debug(f'Could not read {file_path}: {e}')
|
|
158
|
+
|
|
159
|
+
# If no test files found, look for explicit test module
|
|
160
|
+
if not self.cocotb_test_files and self.args.get('cocotb-test-module'):
|
|
161
|
+
test_module = self.args['cocotb-test-module']
|
|
162
|
+
if not test_module.endswith('.py'):
|
|
163
|
+
test_module += '.py'
|
|
164
|
+
|
|
165
|
+
# Look in work directory and target directory
|
|
166
|
+
for search_dir in [self.args['work-dir'], os.path.dirname(self.target or '.')]:
|
|
167
|
+
test_path = os.path.join(search_dir, test_module)
|
|
168
|
+
if os.path.exists(test_path):
|
|
169
|
+
self.cocotb_test_files.append(test_path)
|
|
170
|
+
break
|
|
171
|
+
|
|
172
|
+
if not self.cocotb_test_files:
|
|
173
|
+
self.error('No cocotb test files found. Either include .py files with '
|
|
174
|
+
'cocotb imports in your deps, or specify --cocotb-test-module')
|
|
175
|
+
|
|
176
|
+
def _prepare_python_runner_system(self):
|
|
177
|
+
'''Prepare cocotb using the Python-based runner system (cocotb 1.8+)'''
|
|
178
|
+
|
|
179
|
+
# Create a Python runner script
|
|
180
|
+
runner_script = self._create_python_runner_script()
|
|
181
|
+
|
|
182
|
+
if self.args['cocotb-test-runner'] == 'pytest':
|
|
183
|
+
# Use pytest to run the tests
|
|
184
|
+
runner_script_name = os.path.basename(runner_script)
|
|
185
|
+
cmd_list = [
|
|
186
|
+
self.python_exe, '-m', 'pytest',
|
|
187
|
+
runner_script_name,
|
|
188
|
+
'-v', '-s'
|
|
189
|
+
]
|
|
190
|
+
else:
|
|
191
|
+
# Run the Python script directly
|
|
192
|
+
runner_script_name = os.path.basename(runner_script)
|
|
193
|
+
cmd_list = [self.python_exe, runner_script_name]
|
|
194
|
+
|
|
195
|
+
# Set environment variables
|
|
196
|
+
env_vars = self._get_cocotb_env_vars()
|
|
197
|
+
|
|
198
|
+
# Create command with environment variables
|
|
199
|
+
command_list = self._create_env_command(env_vars) + cmd_list
|
|
200
|
+
self.cocotb_command_lists = [util.ShellCommandList(command_list,
|
|
201
|
+
tee_fpath='cocotb_test.log')]
|
|
202
|
+
|
|
203
|
+
def _prepare_makefile_system(self):
|
|
204
|
+
'''Prepare cocotb using the traditional Makefile system'''
|
|
205
|
+
|
|
206
|
+
makefile_path = os.path.join(self.args['work-dir'], 'Makefile')
|
|
207
|
+
with open(makefile_path, 'w', encoding='utf-8') as f:
|
|
208
|
+
f.write(self._create_makefile_content())
|
|
209
|
+
|
|
210
|
+
cmd_list = self._create_shell_command_with_success('make -f Makefile')
|
|
211
|
+
self.cocotb_command_lists = [util.ShellCommandList(cmd_list,
|
|
212
|
+
tee_fpath='cocotb_makefile.log')]
|
|
213
|
+
|
|
214
|
+
def _prepare_standalone_makefile_system(self):
|
|
215
|
+
'''Use provided Makefile as-is, run make in source directory'''
|
|
216
|
+
|
|
217
|
+
# Find the Makefile in our dependencies
|
|
218
|
+
makefile_path = None
|
|
219
|
+
for file_path in self.files_non_source:
|
|
220
|
+
if os.path.basename(file_path).lower() == 'makefile':
|
|
221
|
+
makefile_path = file_path
|
|
222
|
+
break
|
|
223
|
+
|
|
224
|
+
if not makefile_path:
|
|
225
|
+
self.error('No Makefile found in deps for --cocotb-standalone-makefile')
|
|
226
|
+
|
|
227
|
+
makefile_dir = os.path.dirname(os.path.abspath(makefile_path))
|
|
228
|
+
cmd_list = self._create_shell_command_with_success(f'cd {makefile_dir} && make')
|
|
229
|
+
self.cocotb_command_lists = [util.ShellCommandList(
|
|
230
|
+
cmd_list, tee_fpath='cocotb_standalone.log')]
|
|
231
|
+
|
|
232
|
+
def _get_test_module_name(self) -> str:
|
|
233
|
+
'''Get the test module name from args or detected files'''
|
|
234
|
+
if self.args.get('cocotb-test-module'):
|
|
235
|
+
return self.args['cocotb-test-module']
|
|
236
|
+
if self.cocotb_test_files:
|
|
237
|
+
# Use the first test file found, strip .py extension
|
|
238
|
+
test_file = os.path.basename(self.cocotb_test_files[0])
|
|
239
|
+
return test_file.replace('.py', '')
|
|
240
|
+
return 'test_design'
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
def _get_hdl_sources(self) -> dict:
|
|
244
|
+
'''Get HDL source files organized by type'''
|
|
245
|
+
return {
|
|
246
|
+
'verilog': (self.files_sv or []) + (self.files_v or []),
|
|
247
|
+
'vhdl': self.files_vhd or [],
|
|
248
|
+
'all': (self.files_sv or []) + (self.files_v or []) + (self.files_vhd or [])
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
def _create_python_runner_script(self) -> str:
|
|
252
|
+
'''Create the Python runner script and return its path'''
|
|
253
|
+
test_module = self._get_test_module_name()
|
|
254
|
+
hdl_sources = self._get_hdl_sources()['all']
|
|
255
|
+
|
|
256
|
+
script_content = self._generate_runner_script_content(test_module, hdl_sources)
|
|
257
|
+
script_path = os.path.join(self.args['work-dir'], 'cocotb_runner.py')
|
|
258
|
+
|
|
259
|
+
with open(script_path, 'w', encoding='utf-8') as f:
|
|
260
|
+
f.write(script_content)
|
|
261
|
+
|
|
262
|
+
os.chmod(script_path, 0o755)
|
|
263
|
+
|
|
264
|
+
return script_path
|
|
265
|
+
|
|
266
|
+
def _generate_runner_script_content(self, test_module: str, hdl_sources: list) -> str:
|
|
267
|
+
'''Generate the content for the Python runner script'''
|
|
268
|
+
return f'''#!/usr/bin/env python3
|
|
269
|
+
"""
|
|
270
|
+
Cocotb test runner script generated by opencos
|
|
271
|
+
"""
|
|
272
|
+
|
|
273
|
+
import os
|
|
274
|
+
import sys
|
|
275
|
+
from pathlib import Path
|
|
276
|
+
|
|
277
|
+
# Add current directory to Python path for test imports
|
|
278
|
+
sys.path.insert(0, os.getcwd())
|
|
279
|
+
|
|
280
|
+
try:
|
|
281
|
+
from cocotb_tools.runner import get_runner
|
|
282
|
+
except ImportError:
|
|
283
|
+
print("ERROR: cocotb not found. Install with: pip install cocotb")
|
|
284
|
+
sys.exit(1)
|
|
285
|
+
|
|
286
|
+
def run_cocotb_test():
|
|
287
|
+
"""Run cocotb test using the Python runner system"""
|
|
288
|
+
|
|
289
|
+
# Configuration
|
|
290
|
+
simulator = "{self.args['cocotb-simulator']}"
|
|
291
|
+
hdl_toplevel = "{self.args.get('top', 'top')}"
|
|
292
|
+
test_module = "{test_module}"
|
|
293
|
+
|
|
294
|
+
# HDL source files
|
|
295
|
+
hdl_sources = {hdl_sources!r}
|
|
296
|
+
|
|
297
|
+
# Convert to Path objects and make absolute
|
|
298
|
+
sources = []
|
|
299
|
+
for src in hdl_sources:
|
|
300
|
+
src_path = Path(src)
|
|
301
|
+
if not src_path.is_absolute():
|
|
302
|
+
src_path = Path.cwd() / src_path
|
|
303
|
+
sources.append(src_path)
|
|
304
|
+
|
|
305
|
+
# Include directories
|
|
306
|
+
include_dirs = {list(self.incdirs)!r}
|
|
307
|
+
|
|
308
|
+
# Defines (filter out None values for cocotb compatibility)
|
|
309
|
+
all_defines = {dict(self.defines)!r}
|
|
310
|
+
defines = {{k: v for k, v in all_defines.items() if v is not None}}
|
|
311
|
+
|
|
312
|
+
# Parameters (empty for simple modules without parameters)
|
|
313
|
+
parameters = {{}}
|
|
314
|
+
|
|
315
|
+
try:
|
|
316
|
+
# Get the runner for the specified simulator
|
|
317
|
+
runner = get_runner(simulator)
|
|
318
|
+
|
|
319
|
+
build_args = []
|
|
320
|
+
|
|
321
|
+
if simulator == "verilator":
|
|
322
|
+
build_args.extend({list(self.args.get('verilate-args', []))!r})
|
|
323
|
+
|
|
324
|
+
# Build the design
|
|
325
|
+
runner.build(
|
|
326
|
+
sources=sources,
|
|
327
|
+
hdl_toplevel=hdl_toplevel,
|
|
328
|
+
includes=include_dirs,
|
|
329
|
+
defines=defines,
|
|
330
|
+
parameters=parameters,
|
|
331
|
+
build_dir="sim_build",
|
|
332
|
+
build_args=build_args,
|
|
333
|
+
)
|
|
334
|
+
|
|
335
|
+
# Run the test
|
|
336
|
+
runner.test(
|
|
337
|
+
hdl_toplevel=hdl_toplevel,
|
|
338
|
+
test_module=test_module,
|
|
339
|
+
test_dir=".",
|
|
340
|
+
)
|
|
341
|
+
|
|
342
|
+
print("{self._get_success_message()}")
|
|
343
|
+
|
|
344
|
+
except Exception as e:
|
|
345
|
+
print(f"ERROR: Cocotb test failed: {{e}}")
|
|
346
|
+
sys.exit(1)
|
|
347
|
+
|
|
348
|
+
if __name__ == "__main__":
|
|
349
|
+
run_cocotb_test()
|
|
350
|
+
'''
|
|
351
|
+
|
|
352
|
+
def _create_makefile_content(self) -> str:
|
|
353
|
+
'''Create Makefile content for cocotb traditional system'''
|
|
354
|
+
|
|
355
|
+
# Determine test module
|
|
356
|
+
test_module = self._get_test_module_name()
|
|
357
|
+
|
|
358
|
+
# Get HDL sources and determine language
|
|
359
|
+
hdl_sources = self._get_hdl_sources()
|
|
360
|
+
verilog_sources = hdl_sources['verilog']
|
|
361
|
+
vhdl_sources = hdl_sources['vhdl']
|
|
362
|
+
|
|
363
|
+
# Determine HDL language
|
|
364
|
+
if vhdl_sources and not verilog_sources:
|
|
365
|
+
hdl_lang = 'vhdl'
|
|
366
|
+
sources_var = 'VHDL_SOURCES'
|
|
367
|
+
sources_list = ' '.join(vhdl_sources)
|
|
368
|
+
else:
|
|
369
|
+
hdl_lang = 'verilog'
|
|
370
|
+
sources_var = 'VERILOG_SOURCES'
|
|
371
|
+
sources_list = ' '.join(verilog_sources)
|
|
372
|
+
|
|
373
|
+
makefile_content = f'''# Cocotb Makefile generated by opencos
|
|
374
|
+
|
|
375
|
+
# Simulator selection
|
|
376
|
+
SIM ?= {self.args['cocotb-simulator']}
|
|
377
|
+
|
|
378
|
+
# HDL language
|
|
379
|
+
TOPLEVEL_LANG = {hdl_lang}
|
|
380
|
+
|
|
381
|
+
# HDL sources
|
|
382
|
+
{sources_var} = {sources_list}
|
|
383
|
+
|
|
384
|
+
# Top level module
|
|
385
|
+
TOPLEVEL = {self.args.get('top', 'top')}
|
|
386
|
+
|
|
387
|
+
# Test module
|
|
388
|
+
MODULE = {test_module}
|
|
389
|
+
|
|
390
|
+
# Include directories
|
|
391
|
+
'''
|
|
392
|
+
|
|
393
|
+
if self.incdirs:
|
|
394
|
+
makefile_content += ('COMPILE_ARGS += ' +
|
|
395
|
+
' '.join(f'-I{inc}' for inc in self.incdirs) + '\n')
|
|
396
|
+
|
|
397
|
+
# Add defines
|
|
398
|
+
if self.defines:
|
|
399
|
+
define_args = []
|
|
400
|
+
for k, v in self.defines.items():
|
|
401
|
+
if v is None:
|
|
402
|
+
define_args.append(f'-D{k}')
|
|
403
|
+
else:
|
|
404
|
+
define_args.append(f'-D{k}={sanitize_defines_for_sh(v)}')
|
|
405
|
+
makefile_content += 'COMPILE_ARGS += ' + ' '.join(define_args) + '\n'
|
|
406
|
+
|
|
407
|
+
if self.args['cocotb-simulator'] == 'verilator' and self.args.get('verilate-args'):
|
|
408
|
+
makefile_content += 'COMPILE_ARGS += ' + ' '.join(self.args['verilate-args']) + '\n'
|
|
409
|
+
|
|
410
|
+
makefile_content += '''
|
|
411
|
+
# Waves support
|
|
412
|
+
ifeq ($(WAVES),1)
|
|
413
|
+
COCOTB_ENABLE_WAVES = 1
|
|
414
|
+
export COCOTB_ENABLE_WAVES
|
|
415
|
+
endif
|
|
416
|
+
|
|
417
|
+
# Include cocotb's Makefile
|
|
418
|
+
include $(shell cocotb-config --makefiles)/Makefile.sim
|
|
419
|
+
'''
|
|
420
|
+
|
|
421
|
+
return makefile_content
|
|
422
|
+
|
|
423
|
+
def _get_success_message(self) -> str:
|
|
424
|
+
'''Get standardized success message'''
|
|
425
|
+
return "Cocotb test completed successfully!"
|
|
426
|
+
|
|
427
|
+
def _create_shell_command_with_success(self, base_command: str) -> list:
|
|
428
|
+
'''Create a shell command list with success message'''
|
|
429
|
+
return ['sh', '-c', f'{base_command} && echo "{self._get_success_message()}"']
|
|
430
|
+
|
|
431
|
+
def _get_cocotb_env_vars(self) -> dict:
|
|
432
|
+
'''Get environment variables for cocotb execution'''
|
|
433
|
+
env_vars = {}
|
|
434
|
+
|
|
435
|
+
# Basic cocotb configuration
|
|
436
|
+
env_vars['SIM'] = self.args['cocotb-simulator']
|
|
437
|
+
env_vars['TOPLEVEL'] = self.args.get('top', 'top')
|
|
438
|
+
|
|
439
|
+
# Enable waves if requested
|
|
440
|
+
if self.args.get('waves', False):
|
|
441
|
+
env_vars['COCOTB_ENABLE_WAVES'] = '1'
|
|
442
|
+
|
|
443
|
+
# Set log level based on verbosity
|
|
444
|
+
if util.args.get('verbose', False):
|
|
445
|
+
env_vars['COCOTB_LOG_LEVEL'] = 'DEBUG'
|
|
446
|
+
else:
|
|
447
|
+
env_vars['COCOTB_LOG_LEVEL'] = 'INFO'
|
|
448
|
+
|
|
449
|
+
# Random seed
|
|
450
|
+
if self.args.get('seed'):
|
|
451
|
+
env_vars['COCOTB_RANDOM_SEED'] = str(self.args['seed'])
|
|
452
|
+
|
|
453
|
+
return env_vars
|
|
454
|
+
|
|
455
|
+
def _create_env_command(self, env_vars: dict) -> list:
|
|
456
|
+
'''Create environment variable command prefix'''
|
|
457
|
+
env_cmd = []
|
|
458
|
+
for key, value in env_vars.items():
|
|
459
|
+
env_cmd.extend(['env', f'{key}={value}'])
|
|
460
|
+
return env_cmd
|
|
461
|
+
|
|
462
|
+
def compile(self):
|
|
463
|
+
# For cocotb, compilation happens as part of the test run
|
|
464
|
+
if self.args['stop-before-compile']:
|
|
465
|
+
return
|
|
466
|
+
util.info('Cocotb: compilation will happen during test execution')
|
|
467
|
+
|
|
468
|
+
def elaborate(self):
|
|
469
|
+
# For cocotb, elaboration happens as part of the test run
|
|
470
|
+
pass
|
|
471
|
+
|
|
472
|
+
def simulate(self):
|
|
473
|
+
if self.args['stop-before-compile'] or self.args['stop-after-compile'] or \
|
|
474
|
+
self.args['stop-after-elaborate']:
|
|
475
|
+
return
|
|
476
|
+
|
|
477
|
+
# Run the cocotb tests
|
|
478
|
+
self.run_commands_check_logs(self.cocotb_command_lists)
|
|
479
|
+
|
|
480
|
+
def get_compile_command_lists(self, **kwargs) -> list:
|
|
481
|
+
# Cocotb handles compilation internally
|
|
482
|
+
return []
|
|
483
|
+
|
|
484
|
+
def get_elaborate_command_lists(self, **kwargs) -> list:
|
|
485
|
+
# Cocotb handles elaboration internally
|
|
486
|
+
return []
|
|
487
|
+
|
|
488
|
+
def get_simulate_command_lists(self, **kwargs) -> list:
|
|
489
|
+
return self.cocotb_command_lists
|
|
490
|
+
|
|
491
|
+
def get_post_simulate_command_lists(self, **kwargs) -> list:
|
|
492
|
+
return []
|
opencos/tools/modelsim_ase.py
CHANGED
|
@@ -19,6 +19,7 @@ class ToolModelsimAse(ToolQuesta):
|
|
|
19
19
|
|
|
20
20
|
_TOOL = 'modelsim_ase' # otherwise it's 'questa' from base class.
|
|
21
21
|
_EXE = 'vsim'
|
|
22
|
+
use_vopt = False
|
|
22
23
|
|
|
23
24
|
class CommandSimModelsimAse(CommandSim, ToolModelsimAse):
|
|
24
25
|
'''CommandSimModelsimAse is a command handler for: eda sim --tool=modelsim_ase'''
|
|
@@ -44,6 +45,15 @@ class CommandSimModelsimAse(CommandSim, ToolModelsimAse):
|
|
|
44
45
|
)
|
|
45
46
|
)
|
|
46
47
|
|
|
48
|
+
def run_in_batch_mode(self) -> bool:
|
|
49
|
+
'''Returns bool if we should run in batch mode (-c) from command line'''
|
|
50
|
+
# TODO(drew): make CommandSimQuesta a parent and inherit this method instead.
|
|
51
|
+
if self.args['test-mode']:
|
|
52
|
+
return True
|
|
53
|
+
if self.args['gui']:
|
|
54
|
+
return False
|
|
55
|
+
return True
|
|
56
|
+
|
|
47
57
|
# We do override do_it() to avoid using CommandSimQuesta.do_it()
|
|
48
58
|
def do_it(self):
|
|
49
59
|
CommandSim.do_it(self)
|
|
@@ -111,7 +121,7 @@ class CommandSimModelsimAse(CommandSim, ToolModelsimAse):
|
|
|
111
121
|
# This will also set up a compile.
|
|
112
122
|
vsim_command_list = [
|
|
113
123
|
self.sim_exe,
|
|
114
|
-
'' if self.
|
|
124
|
+
'-c' if self.run_in_batch_mode() else '',
|
|
115
125
|
'-do', 'vsim_vlogonly.do', '-logfile', 'sim.log',
|
|
116
126
|
]
|
|
117
127
|
return [vsim_command_list]
|
|
@@ -120,7 +130,7 @@ class CommandSimModelsimAse(CommandSim, ToolModelsimAse):
|
|
|
120
130
|
# This will also set up a compile, for vlog + vsim (0 time)
|
|
121
131
|
vsim_command_list = [
|
|
122
132
|
self.sim_exe,
|
|
123
|
-
'' if self.
|
|
133
|
+
'-c' if self.run_in_batch_mode() else '',
|
|
124
134
|
'-do', 'vsim_lintonly.do', '-logfile', 'sim.log',
|
|
125
135
|
]
|
|
126
136
|
return [vsim_command_list]
|
|
@@ -129,7 +139,7 @@ class CommandSimModelsimAse(CommandSim, ToolModelsimAse):
|
|
|
129
139
|
# This will also set up a compile, for vlog + vsim (with run -a)
|
|
130
140
|
vsim_command_list = [
|
|
131
141
|
self.sim_exe,
|
|
132
|
-
'' if self.
|
|
142
|
+
'-c' if self.run_in_batch_mode() else '',
|
|
133
143
|
'-do', 'vsim.do', '-logfile', 'sim.log',
|
|
134
144
|
]
|
|
135
145
|
return [vsim_command_list]
|
|
@@ -193,7 +203,9 @@ class CommandSimModelsimAse(CommandSim, ToolModelsimAse):
|
|
|
193
203
|
with open(vlog_dot_f_fpath, 'w', encoding='utf-8') as f:
|
|
194
204
|
f.writelines(line + "\n" for line in vlog_dot_f_lines)
|
|
195
205
|
|
|
196
|
-
def write_vsim_dot_do(
|
|
206
|
+
def write_vsim_dot_do( # pylint: disable=too-many-locals
|
|
207
|
+
self, dot_do_to_write: list
|
|
208
|
+
) -> None:
|
|
197
209
|
'''Writes files(s) based on dot_do_to_write(list of str)
|
|
198
210
|
|
|
199
211
|
list arg values can be empty (all) or have items 'all', 'sim', 'lint', 'vlog'.'''
|
|
@@ -216,27 +228,28 @@ class CommandSimModelsimAse(CommandSim, ToolModelsimAse):
|
|
|
216
228
|
# TODO(drew): support self.args['sim_libary', 'elab-args', sim-args'] (3 lists)
|
|
217
229
|
# to add to vsim_one_liner.
|
|
218
230
|
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
231
|
+
vopt_one_liner = ""
|
|
232
|
+
if self.use_vopt:
|
|
233
|
+
vopt_one_liner = (
|
|
234
|
+
f"vopt {voptargs_str} work.{self.args['top']} -o opt__{self.args['top']}"
|
|
235
|
+
)
|
|
236
|
+
vopt_one_liner = vopt_one_liner.replace('\n', ' ') # needs to be a one-liner
|
|
237
|
+
# vopt doesn't need -voptargs=(value) like vsim does, simply use (value).
|
|
238
|
+
vopt_one_liner = vopt_one_liner.replace('-voptargs=', '')
|
|
222
239
|
|
|
223
|
-
|
|
240
|
+
vsim_one_liner = "vsim -onfinish stop" \
|
|
241
|
+
+ f" -sv_seed {self.args['seed']} {sim_plusargs_str} {vsim_suppress_list_str}" \
|
|
242
|
+
+ f" opt__{self.args['top']}"
|
|
243
|
+
else:
|
|
244
|
+
# vopt doesn't exist, use single vsim call after vlog call:
|
|
245
|
+
vsim_one_liner = "vsim -onfinish stop" \
|
|
246
|
+
+ f" -sv_seed {self.args['seed']} {sim_plusargs_str} {vsim_suppress_list_str}" \
|
|
247
|
+
+ f" {voptargs_str} work.{self.args['top']}"
|
|
224
248
|
|
|
225
|
-
vsim_vlogonly_dot_do_lines = [
|
|
226
|
-
"if {[file exists work]} { vdel -all work; }",
|
|
227
|
-
"vlib work;",
|
|
228
|
-
"if {[catch {vlog -f vlog.f} result]} {",
|
|
229
|
-
" echo \"Caught $result \";",
|
|
230
|
-
" if {[batch_mode]} {",
|
|
231
|
-
" quit -f -code 20;",
|
|
232
|
-
" }",
|
|
233
|
-
"}",
|
|
234
|
-
"if {[batch_mode]} {",
|
|
235
|
-
" quit -f -code 0;",
|
|
236
|
-
"}",
|
|
237
|
-
]
|
|
238
249
|
|
|
239
|
-
|
|
250
|
+
vsim_one_liner = vsim_one_liner.replace('\n', ' ')
|
|
251
|
+
|
|
252
|
+
vlog_do_lines = [
|
|
240
253
|
"if {[file exists work]} { vdel -all work; }",
|
|
241
254
|
"vlib work;",
|
|
242
255
|
"quietly set qc 30;",
|
|
@@ -246,44 +259,35 @@ class CommandSimModelsimAse(CommandSim, ToolModelsimAse):
|
|
|
246
259
|
" quit -f -code 20;",
|
|
247
260
|
" }",
|
|
248
261
|
"}",
|
|
262
|
+
]
|
|
263
|
+
|
|
264
|
+
vopt_do_lines = []
|
|
265
|
+
if self.use_vopt:
|
|
266
|
+
vopt_do_lines = [
|
|
267
|
+
"if {[catch { " + vopt_one_liner + " } result] } {",
|
|
268
|
+
" echo \"Caught $result\";",
|
|
269
|
+
" if {[batch_mode]} {",
|
|
270
|
+
" quit -f -code 19;",
|
|
271
|
+
" }",
|
|
272
|
+
"}",
|
|
273
|
+
]
|
|
274
|
+
|
|
275
|
+
vsim_do_lines = [
|
|
249
276
|
"if {[catch { " + vsim_one_liner + " } result] } {",
|
|
250
277
|
" echo \"Caught $result\";",
|
|
251
278
|
" if {[batch_mode]} {",
|
|
252
|
-
" quit -f -code
|
|
279
|
+
" quit -f -code 18;",
|
|
253
280
|
" }",
|
|
254
281
|
"}",
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
"} elseif {[regexp \"TESTSTATUS += 1\" $TestStatus]} {",
|
|
259
|
-
" quietly set qc 0;",
|
|
260
|
-
"} else {",
|
|
261
|
-
" quietly set qc 2;",
|
|
262
|
-
"}",
|
|
282
|
+
]
|
|
283
|
+
|
|
284
|
+
vsim_vlogonly_dot_do_lines = vlog_do_lines + [
|
|
263
285
|
"if {[batch_mode]} {",
|
|
264
|
-
" quit -f -code
|
|
286
|
+
" quit -f -code 0;",
|
|
265
287
|
"}",
|
|
266
288
|
]
|
|
267
289
|
|
|
268
|
-
|
|
269
|
-
"if {[file exists work]} { vdel -all work; }",
|
|
270
|
-
"vlib work;",
|
|
271
|
-
"quietly set qc 30;",
|
|
272
|
-
"if {[catch {vlog -f vlog.f} result]} {",
|
|
273
|
-
" echo \"Caught $result \";",
|
|
274
|
-
" if {[batch_mode]} {",
|
|
275
|
-
" quit -f -code 20;",
|
|
276
|
-
" }",
|
|
277
|
-
"}",
|
|
278
|
-
"if {[catch { " + vsim_one_liner + " } result] } {",
|
|
279
|
-
" echo \"Caught $result\";",
|
|
280
|
-
" if {[batch_mode]} {",
|
|
281
|
-
" quit -f -code 19;",
|
|
282
|
-
" }",
|
|
283
|
-
"}",
|
|
284
|
-
"onbreak { resume; };",
|
|
285
|
-
"catch {log -r *};",
|
|
286
|
-
"run -a;",
|
|
290
|
+
final_check_teststatus_do_lines = [
|
|
287
291
|
"set TestStatus [coverage attribute -name SEED -name TESTSTATUS];",
|
|
288
292
|
"if {[regexp \"TESTSTATUS += 0\" $TestStatus]} {",
|
|
289
293
|
" quietly set qc 0;",
|
|
@@ -297,6 +301,18 @@ class CommandSimModelsimAse(CommandSim, ToolModelsimAse):
|
|
|
297
301
|
"}",
|
|
298
302
|
]
|
|
299
303
|
|
|
304
|
+
# final vlog/vopt/vsim lint-only .do command (want to make sure it can completely
|
|
305
|
+
# build for 'elab' style eda job), runs for 0ns, logs nothing for a waveform, quits
|
|
306
|
+
vsim_lintonly_dot_do_lines = vlog_do_lines + vopt_do_lines + vsim_do_lines \
|
|
307
|
+
+ final_check_teststatus_do_lines
|
|
308
|
+
|
|
309
|
+
# final vlog/opt/vsim full simulation .do command.
|
|
310
|
+
vsim_dot_do_lines = vlog_do_lines + vopt_do_lines + vsim_do_lines + [
|
|
311
|
+
"onbreak { resume; };",
|
|
312
|
+
"catch {log -r *};",
|
|
313
|
+
"run -a;",
|
|
314
|
+
] + final_check_teststatus_do_lines
|
|
315
|
+
|
|
300
316
|
write_all = len(dot_do_to_write) == 0 or 'all' in dot_do_to_write
|
|
301
317
|
if write_all or 'sim' in dot_do_to_write:
|
|
302
318
|
with open(vsim_dot_do_fpath, 'w', encoding='utf-8') as f:
|