opencos-eda 0.3.5__py3-none-any.whl → 0.3.6__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/sim.py +16 -6
- opencos/deps/deps_processor.py +13 -2
- opencos/eda.py +6 -5
- opencos/eda_base.py +77 -29
- opencos/eda_config.py +2 -1
- opencos/eda_config_defaults.yml +5 -1
- opencos/export_helper.py +89 -31
- opencos/files.py +3 -1
- opencos/tests/helpers.py +23 -17
- opencos/tools/cocotb.py +94 -21
- opencos/tools/verilator.py +5 -2
- opencos/util.py +73 -50
- {opencos_eda-0.3.5.dist-info → opencos_eda-0.3.6.dist-info}/METADATA +4 -3
- {opencos_eda-0.3.5.dist-info → opencos_eda-0.3.6.dist-info}/RECORD +19 -19
- {opencos_eda-0.3.5.dist-info → opencos_eda-0.3.6.dist-info}/WHEEL +0 -0
- {opencos_eda-0.3.5.dist-info → opencos_eda-0.3.6.dist-info}/entry_points.txt +0 -0
- {opencos_eda-0.3.5.dist-info → opencos_eda-0.3.6.dist-info}/licenses/LICENSE +0 -0
- {opencos_eda-0.3.5.dist-info → opencos_eda-0.3.6.dist-info}/licenses/LICENSE.spdx +0 -0
- {opencos_eda-0.3.5.dist-info → opencos_eda-0.3.6.dist-info}/top_level.txt +0 -0
opencos/tests/helpers.py
CHANGED
|
@@ -164,38 +164,43 @@ def assert_gen_deps_yml_good(filepath:str, want_target:str='') -> dict:
|
|
|
164
164
|
|
|
165
165
|
def assert_export_json_good(filepath:str) -> dict:
|
|
166
166
|
'''Checks that an exported JSON (from eda export, or eda <command> --export) has known keys'''
|
|
167
|
-
assert os.path.
|
|
167
|
+
assert os.path.isfile(filepath), f'{filepath=} does not exist'
|
|
168
168
|
with open(filepath, encoding='utf-8') as f:
|
|
169
169
|
data = json.load(f)
|
|
170
|
-
assert '
|
|
171
|
-
assert '
|
|
172
|
-
|
|
170
|
+
assert 'tests' in data
|
|
171
|
+
assert len(data.get('tests', [])) >= 1
|
|
172
|
+
for test in data.get('tests', []):
|
|
173
|
+
check_test_runner_schema(test)
|
|
173
174
|
return data
|
|
174
175
|
|
|
176
|
+
def check_test_runner_schema(test: dict) -> None:
|
|
177
|
+
'''Confirm that a single test's JSON/JSONL schema is OK.'''
|
|
178
|
+
assert 'correlationId' in test
|
|
179
|
+
assert 'jobType' in test
|
|
180
|
+
assert 'cmd' in test
|
|
181
|
+
assert 'filesList' in test # 0 files is OK.
|
|
182
|
+
|
|
175
183
|
|
|
176
184
|
def assert_export_jsonl_good(filepath:str, jsonl:bool=True) -> list:
|
|
177
185
|
'''Checks that an exported JSONL (from eda multi --export) has known keys'''
|
|
178
|
-
assert os.path.
|
|
186
|
+
assert os.path.isfile(filepath), f'{filepath=} does not exist'
|
|
179
187
|
ret = []
|
|
180
188
|
with open(filepath, encoding='utf-8') as f:
|
|
181
189
|
if jsonl:
|
|
190
|
+
print(f'Using JSONL for {filepath=}')
|
|
182
191
|
for line in f.readlines():
|
|
183
192
|
line = line.strip()
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
assert any(x in data for x in ['files', 'tb'])
|
|
188
|
-
ret.append(data)
|
|
193
|
+
test = json.loads(line)
|
|
194
|
+
check_test_runner_schema(test)
|
|
195
|
+
ret.append(test)
|
|
189
196
|
else:
|
|
197
|
+
print(f'Using JSON for {filepath=}')
|
|
190
198
|
data = json.load(f)
|
|
191
199
|
assert 'tests' in data
|
|
192
|
-
assert
|
|
193
|
-
for
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
assert any(x in entry for x in ['files', 'tb'])
|
|
197
|
-
ret.append(entry)
|
|
198
|
-
|
|
200
|
+
assert len(data.get('tests', [])) >= 1
|
|
201
|
+
for test in data.get('tests', []):
|
|
202
|
+
check_test_runner_schema(test)
|
|
203
|
+
ret.append(test)
|
|
199
204
|
|
|
200
205
|
return ret
|
|
201
206
|
|
|
@@ -265,6 +270,7 @@ class Helpers:
|
|
|
265
270
|
background=True,
|
|
266
271
|
tee_fpath=logfile
|
|
267
272
|
)
|
|
273
|
+
print(f'Wrote: {os.path.abspath(logfile)=}')
|
|
268
274
|
else:
|
|
269
275
|
with open(logfile, 'w', encoding='utf-8') as f:
|
|
270
276
|
with redirect_stdout(f), redirect_stderr(f):
|
opencos/tools/cocotb.py
CHANGED
|
@@ -10,7 +10,9 @@ import subprocess
|
|
|
10
10
|
from opencos import util
|
|
11
11
|
from opencos.eda_base import Tool
|
|
12
12
|
from opencos.commands import CommandSim
|
|
13
|
+
from opencos.utils import status_constants
|
|
13
14
|
from opencos.utils.str_helpers import sanitize_defines_for_sh
|
|
15
|
+
from opencos.tools import verilator # For default waivers.
|
|
14
16
|
|
|
15
17
|
|
|
16
18
|
class ToolCocotb(Tool):
|
|
@@ -76,6 +78,8 @@ class CommandSimCocotb(CommandSim, ToolCocotb):
|
|
|
76
78
|
'tcl-file': None,
|
|
77
79
|
'cocotb-test-module': None,
|
|
78
80
|
'cocotb-test-runner': 'python',
|
|
81
|
+
'cocotb-test-runner-file': '',
|
|
82
|
+
'cocotb-test-run-dir': None, # If None, use self.args['work-dir']
|
|
79
83
|
'cocotb-simulator': 'verilator',
|
|
80
84
|
'cocotb-makefile': False,
|
|
81
85
|
'cocotb-python-runner': True,
|
|
@@ -86,17 +90,25 @@ class CommandSimCocotb(CommandSim, ToolCocotb):
|
|
|
86
90
|
'waves': 'Include waveforms by setting COCOTB_ENABLE_WAVES=1',
|
|
87
91
|
'cocotb-test-module': 'Python test module name (e.g., test_my_design)',
|
|
88
92
|
'cocotb-test-runner': 'Test runner to use: python (default) or pytest',
|
|
89
|
-
'cocotb-
|
|
90
|
-
|
|
93
|
+
'cocotb-test-runner-file': 'Bring your own test_runner.py file',
|
|
94
|
+
'cocotb-test-run-dir': (
|
|
95
|
+
'Directory where cocotb-test-runner will run, if unset uses default eda.work/(name)'
|
|
96
|
+
),
|
|
97
|
+
'cocotb-simulator': (
|
|
98
|
+
'Simulator backend: verilator (default), icarus, etc.'
|
|
99
|
+
' Note that iverilog will convert to icarus here'
|
|
100
|
+
),
|
|
91
101
|
'cocotb-makefile': 'Use traditional Makefile system instead of Python runner',
|
|
92
102
|
'cocotb-python-runner': 'Use Python-based runner system (default, cocotb 1.8+)',
|
|
93
|
-
'cocotb-standalone-makefile': (
|
|
94
|
-
|
|
103
|
+
'cocotb-standalone-makefile': (
|
|
104
|
+
'Use provided Makefile as-is, run make in source directory'
|
|
105
|
+
),
|
|
95
106
|
})
|
|
96
107
|
|
|
97
108
|
self.cocotb_command_lists = []
|
|
98
109
|
self.cocotb_test_files = []
|
|
99
110
|
|
|
111
|
+
|
|
100
112
|
def set_tool_defines(self):
|
|
101
113
|
ToolCocotb.set_tool_defines(self)
|
|
102
114
|
|
|
@@ -155,12 +167,13 @@ class CommandSimCocotb(CommandSim, ToolCocotb):
|
|
|
155
167
|
simulate_sh_fname='cocotb_test.sh'
|
|
156
168
|
)
|
|
157
169
|
|
|
170
|
+
|
|
158
171
|
def _find_cocotb_test_files(self):
|
|
159
172
|
'''Find Python test files that contain cocotb tests'''
|
|
160
173
|
self.cocotb_test_files = []
|
|
161
174
|
|
|
162
175
|
# Look for test files in the current directory and deps
|
|
163
|
-
for file_path in self.files_non_source:
|
|
176
|
+
for file_path in self.files_non_source + self.files_py:
|
|
164
177
|
if (file_path.endswith('.py') and
|
|
165
178
|
('test' in file_path.lower() or 'tb' in file_path.lower())):
|
|
166
179
|
# Check if it's a cocotb test file
|
|
@@ -193,8 +206,19 @@ class CommandSimCocotb(CommandSim, ToolCocotb):
|
|
|
193
206
|
def _prepare_python_runner_system(self):
|
|
194
207
|
'''Prepare cocotb using the Python-based runner system (cocotb 1.8+)'''
|
|
195
208
|
|
|
196
|
-
# Create a Python runner script
|
|
197
|
-
|
|
209
|
+
# Create a Python runner script, or use from --cocotb-test-runner-file
|
|
210
|
+
if self.args['cocotb-test-runner-file']:
|
|
211
|
+
|
|
212
|
+
runner_script = self.args['cocotb-test-runner-file']
|
|
213
|
+
if not os.path.isfile(self.args['cocotb-test-runner-file']):
|
|
214
|
+
self.error(
|
|
215
|
+
"File does not exist, for: --cocotb-test-runner-file=",
|
|
216
|
+
f"{self.args['cocotb-test-runner-file']}",
|
|
217
|
+
error_code=status_constants.EDA_GENERAL_FILE_NOT_FOUND
|
|
218
|
+
)
|
|
219
|
+
return
|
|
220
|
+
else:
|
|
221
|
+
runner_script = self._create_python_runner_script()
|
|
198
222
|
|
|
199
223
|
if self.args['cocotb-test-runner'] == 'pytest':
|
|
200
224
|
# Use pytest to run the tests
|
|
@@ -212,28 +236,41 @@ class CommandSimCocotb(CommandSim, ToolCocotb):
|
|
|
212
236
|
# Set environment variables
|
|
213
237
|
env_vars = self._get_cocotb_env_vars()
|
|
214
238
|
|
|
215
|
-
# Create command with environment variables
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
239
|
+
# Create command with environment variables, needs to be list-of-(cmd_list)
|
|
240
|
+
self.cocotb_command_lists = [
|
|
241
|
+
util.ShellCommandList(
|
|
242
|
+
self._create_env_command(env_vars) + cmd_list,
|
|
243
|
+
tee_fpath='cocotb_test.log',
|
|
244
|
+
# If someone wanted to run this from a specific dir, then
|
|
245
|
+
# run it from there.
|
|
246
|
+
work_dir=self.args['cocotb-test-run-dir']
|
|
247
|
+
)
|
|
248
|
+
]
|
|
219
249
|
|
|
220
250
|
def _prepare_makefile_system(self):
|
|
221
251
|
'''Prepare cocotb using the traditional Makefile system'''
|
|
222
252
|
|
|
223
253
|
makefile_path = os.path.join(self.args['work-dir'], 'Makefile')
|
|
254
|
+
self.files_makefile.append(makefile_path)
|
|
224
255
|
with open(makefile_path, 'w', encoding='utf-8') as f:
|
|
225
256
|
f.write(self._create_makefile_content())
|
|
226
257
|
|
|
227
258
|
cmd_list = self._create_shell_command_with_success('make -f Makefile')
|
|
228
|
-
self.cocotb_command_lists = [
|
|
229
|
-
|
|
259
|
+
self.cocotb_command_lists = [
|
|
260
|
+
util.ShellCommandList(
|
|
261
|
+
cmd_list, tee_fpath='cocotb_makefile.log',
|
|
262
|
+
# If someone wanted to run this from a specific dir, then
|
|
263
|
+
# run it from there.
|
|
264
|
+
work_dir=self.args['cocotb-test-run-dir']
|
|
265
|
+
)
|
|
266
|
+
]
|
|
230
267
|
|
|
231
268
|
def _prepare_standalone_makefile_system(self):
|
|
232
269
|
'''Use provided Makefile as-is, run make in source directory'''
|
|
233
270
|
|
|
234
271
|
# Find the Makefile in our dependencies
|
|
235
272
|
makefile_path = None
|
|
236
|
-
for file_path in self.files_non_source:
|
|
273
|
+
for file_path in self.files_non_source + self.files_makefile:
|
|
237
274
|
if os.path.basename(file_path).lower() == 'makefile':
|
|
238
275
|
makefile_path = file_path
|
|
239
276
|
break
|
|
@@ -241,10 +278,19 @@ class CommandSimCocotb(CommandSim, ToolCocotb):
|
|
|
241
278
|
if not makefile_path:
|
|
242
279
|
self.error('No Makefile found in deps for --cocotb-standalone-makefile')
|
|
243
280
|
|
|
281
|
+
if any(value for key,value in self.args.items() if key.startswith('export')):
|
|
282
|
+
util.warning(f'Using an --export arg with Cocotb standalong Makefile {makefile_path}',
|
|
283
|
+
'is not fully supported')
|
|
284
|
+
|
|
244
285
|
makefile_dir = os.path.dirname(os.path.abspath(makefile_path))
|
|
245
|
-
cmd_list = self._create_shell_command_with_success(
|
|
246
|
-
self.cocotb_command_lists = [
|
|
247
|
-
|
|
286
|
+
cmd_list = self._create_shell_command_with_success('make')
|
|
287
|
+
self.cocotb_command_lists = [
|
|
288
|
+
util.ShellCommandList(
|
|
289
|
+
cmd_list,
|
|
290
|
+
tee_fpath='cocotb_standalone.log',
|
|
291
|
+
work_dir=makefile_dir
|
|
292
|
+
)
|
|
293
|
+
]
|
|
248
294
|
|
|
249
295
|
def _get_test_module_name(self) -> str:
|
|
250
296
|
'''Get the test module name from args or detected files'''
|
|
@@ -280,8 +326,22 @@ class CommandSimCocotb(CommandSim, ToolCocotb):
|
|
|
280
326
|
|
|
281
327
|
return script_path
|
|
282
328
|
|
|
329
|
+
|
|
283
330
|
def _generate_runner_script_content(self, test_module: str, hdl_sources: list) -> str:
|
|
284
331
|
'''Generate the content for the Python runner script'''
|
|
332
|
+
|
|
333
|
+
if shutil.which('verilator'):
|
|
334
|
+
# TODO(drew): this shortcuts if verilator is truly usable,
|
|
335
|
+
# consider using eda_tool_helper to get "tools_loaded", which
|
|
336
|
+
# is not set in self.config['tools_loaded'] when --tool=cocotb.
|
|
337
|
+
# Would need minor refactor for eda.py methods auto_tools_order,
|
|
338
|
+
# tool_setup to go into eda_tool_helper.py, and those methods would
|
|
339
|
+
# need hooks to be non-destructive to config.
|
|
340
|
+
tmp_verilator_obj = verilator.VerilatorSim(config=self.config)
|
|
341
|
+
verilator_waivers = tmp_verilator_obj.get_verilator_tool_config_waivers()
|
|
342
|
+
else:
|
|
343
|
+
verilator_waivers = []
|
|
344
|
+
|
|
285
345
|
return f'''#!/usr/bin/env python3
|
|
286
346
|
"""
|
|
287
347
|
Cocotb test runner script generated by opencos
|
|
@@ -337,6 +397,8 @@ def run_cocotb_test():
|
|
|
337
397
|
|
|
338
398
|
if simulator == "verilator":
|
|
339
399
|
build_args.extend({list(self.args.get('verilate-args', []))!r})
|
|
400
|
+
build_args.extend({list(self.args.get('compile-args', []))!r})
|
|
401
|
+
build_args.extend({list(verilator_waivers)!r})
|
|
340
402
|
|
|
341
403
|
# Build the design
|
|
342
404
|
runner.build(
|
|
@@ -449,9 +511,20 @@ include $(shell cocotb-config --makefiles)/Makefile.sim
|
|
|
449
511
|
'''Get environment variables for cocotb execution'''
|
|
450
512
|
env_vars = {}
|
|
451
513
|
|
|
514
|
+
top = self.args.get('top', 'top')
|
|
515
|
+
|
|
452
516
|
# Basic cocotb configuration
|
|
453
|
-
env_vars
|
|
454
|
-
|
|
517
|
+
env_vars.update({
|
|
518
|
+
'SIM': self.args['cocotb-simulator'],
|
|
519
|
+
'TOPLEVEL': top,
|
|
520
|
+
'COCOTB_TOPLEVEL': top,
|
|
521
|
+
})
|
|
522
|
+
|
|
523
|
+
if test_module := self.args['cocotb-test-module']:
|
|
524
|
+
env_vars.update({
|
|
525
|
+
'MODULE': test_module,
|
|
526
|
+
'COCOTB_TEST_MODULES': test_module,
|
|
527
|
+
})
|
|
455
528
|
|
|
456
529
|
# Enable waves if requested
|
|
457
530
|
if self.args.get('waves', False):
|
|
@@ -464,8 +537,8 @@ include $(shell cocotb-config --makefiles)/Makefile.sim
|
|
|
464
537
|
env_vars['COCOTB_LOG_LEVEL'] = 'INFO'
|
|
465
538
|
|
|
466
539
|
# Random seed
|
|
467
|
-
if self.args.get('seed'):
|
|
468
|
-
env_vars['COCOTB_RANDOM_SEED'] = str(
|
|
540
|
+
if seed := self.args.get('seed'):
|
|
541
|
+
env_vars['COCOTB_RANDOM_SEED'] = str(seed)
|
|
469
542
|
|
|
470
543
|
return env_vars
|
|
471
544
|
|
opencos/tools/verilator.py
CHANGED
|
@@ -228,7 +228,7 @@ class VerilatorSim(CommandSim, ToolVerilator):
|
|
|
228
228
|
warnings=(not lint_only), add_uvm_pkg_if_found=True
|
|
229
229
|
)
|
|
230
230
|
|
|
231
|
-
verilate_command_list += self.
|
|
231
|
+
verilate_command_list += self.get_verilator_tool_config_waivers()
|
|
232
232
|
|
|
233
233
|
verilate_command_list += self._verilator_args_defaults_cflags_nproc()
|
|
234
234
|
|
|
@@ -408,7 +408,10 @@ class VerilatorSim(CommandSim, ToolVerilator):
|
|
|
408
408
|
|
|
409
409
|
return verilate_command_list
|
|
410
410
|
|
|
411
|
-
def
|
|
411
|
+
def get_verilator_tool_config_waivers(self) -> list:
|
|
412
|
+
'''Returns list of args to verilator for waviers, from --compile-waivers and
|
|
413
|
+
|
|
414
|
+
--config-yml for tool: (config)'''
|
|
412
415
|
|
|
413
416
|
# Add compile waivers from self.config (tools.verilator.compile-waivers list):
|
|
414
417
|
# list(set(mylist)) to get unique.
|
opencos/util.py
CHANGED
|
@@ -32,6 +32,7 @@ env_files_loaded = set() # pylint: disable=invalid-name
|
|
|
32
32
|
|
|
33
33
|
args = { # pylint: disable=invalid-name
|
|
34
34
|
'color' : bool(supportsColor.stdout),
|
|
35
|
+
'emoji' : bool(getattr(supportsColor.stdout, 'level', 0) >= 2),
|
|
35
36
|
'quiet' : False,
|
|
36
37
|
'verbose' : False,
|
|
37
38
|
'debug' : False,
|
|
@@ -44,16 +45,21 @@ args = { # pylint: disable=invalid-name
|
|
|
44
45
|
max_error_code = 0 # pylint: disable=invalid-name
|
|
45
46
|
|
|
46
47
|
class Colors:
|
|
47
|
-
'''Namespace class for color printing help
|
|
48
|
+
'''Namespace class for color printing help
|
|
49
|
+
|
|
50
|
+
Avoid calling these directly, other than perhapas calling info(*txt, color=Colors.red)
|
|
51
|
+
with 'color' set. It is preferred for outside callers to use one of the print_<color>(..)
|
|
52
|
+
functions, or one of info|warning|error|debug
|
|
53
|
+
'''
|
|
48
54
|
red = "\x1B[31m"
|
|
49
55
|
green = "\x1B[32m"
|
|
50
|
-
|
|
51
|
-
|
|
56
|
+
yellow = "\x1B[33m" # This looks orange, but it's techincally yellow
|
|
57
|
+
foreground = "\x1B[39m"
|
|
52
58
|
normal = "\x1B[0m"
|
|
53
59
|
|
|
54
60
|
@staticmethod
|
|
55
61
|
def color_text(text: str, color: str) -> str:
|
|
56
|
-
'''Wraps 'text' (str) with color (one of red|green|
|
|
62
|
+
'''Wraps 'text' (str) with color (one of red|green|yellow|foreground) prefix and
|
|
57
63
|
|
|
58
64
|
color (normal) suffix. Disables color prefix/suffix wrapping if args['color']=False
|
|
59
65
|
'''
|
|
@@ -61,29 +67,13 @@ class Colors:
|
|
|
61
67
|
return color + text + "\x1B[0m" # (normal)
|
|
62
68
|
return text
|
|
63
69
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
if args['
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
'''Wraps text for printing as green, disabled if global args['color']=False'''
|
|
72
|
-
if args['color']:
|
|
73
|
-
return Colors.green + text + Colors.normal
|
|
74
|
-
return text
|
|
75
|
-
|
|
76
|
-
def orange_text(text: str) -> str:
|
|
77
|
-
'''Wraps text for printing as orange, disabled if global args['color']=False'''
|
|
78
|
-
if args['color']:
|
|
79
|
-
return Colors.orange + text + Colors.normal
|
|
80
|
-
return text
|
|
81
|
-
|
|
82
|
-
def yellow_text(text: str) -> str:
|
|
83
|
-
'''Wraps text for printing as yellow, disabled if global args['color']=False'''
|
|
84
|
-
if args['color']:
|
|
85
|
-
return Colors.yellow + text + Colors.normal
|
|
86
|
-
return text
|
|
70
|
+
|
|
71
|
+
def safe_emoji(emoji: str, default: str = '') -> str:
|
|
72
|
+
'''Returns emoji character if args['emoji'] is True'''
|
|
73
|
+
if args['emoji']:
|
|
74
|
+
return emoji
|
|
75
|
+
return default
|
|
76
|
+
|
|
87
77
|
|
|
88
78
|
class ArtifactTypes(Enum):
|
|
89
79
|
'''Types that are allow-listed for artifacts.add* methods. If you don't use one of
|
|
@@ -343,7 +333,7 @@ def get_argparse_bool_action_kwargs() -> dict:
|
|
|
343
333
|
def get_argparser() -> argparse.ArgumentParser:
|
|
344
334
|
'''Returns the opencos.util ArgumentParser'''
|
|
345
335
|
parser = argparse.ArgumentParser(
|
|
346
|
-
prog='opencos common options', add_help=False, allow_abbrev=False
|
|
336
|
+
prog=f'{safe_emoji("🔎 ")}opencos common options', add_help=False, allow_abbrev=False
|
|
347
337
|
)
|
|
348
338
|
# We set allow_abbrev=False so --force-logfile won't try to attempt parsing shorter similarly
|
|
349
339
|
# named args like --force, we want those to go to unparsed list.
|
|
@@ -355,6 +345,8 @@ def get_argparser() -> argparse.ArgumentParser:
|
|
|
355
345
|
parser.add_argument('--version', default=False, action='store_true')
|
|
356
346
|
parser.add_argument('--color', **bool_action_kwargs, default=bool(supportsColor.stdout),
|
|
357
347
|
help='Use shell colors for info/warning/error messaging')
|
|
348
|
+
parser.add_argument('--emoji', **bool_action_kwargs, default=args['emoji'],
|
|
349
|
+
help=f'Support emojis in terminal{safe_emoji(" 💪")}')
|
|
358
350
|
parser.add_argument('--quiet', **bool_action_kwargs, default=args['quiet'],
|
|
359
351
|
help='Do not display info messaging')
|
|
360
352
|
parser.add_argument('--verbose', **bool_action_kwargs, default=args['verbose'],
|
|
@@ -410,12 +402,25 @@ def get_argparser_short_help(parser: object = None) -> str:
|
|
|
410
402
|
'''Returns short help for our ArgumentParser'''
|
|
411
403
|
if not parser:
|
|
412
404
|
parser = get_argparser()
|
|
413
|
-
|
|
405
|
+
|
|
406
|
+
if not args['color']:
|
|
407
|
+
# Since python3.14 doesn't care about our custom color settings,
|
|
408
|
+
# need to remove any ANSI colors from argparse help formatter:
|
|
409
|
+
full_lines = strip_ansi_color(parser.format_help()).split('\n')
|
|
410
|
+
else:
|
|
411
|
+
full_lines = parser.format_help().split('\n')
|
|
412
|
+
|
|
414
413
|
lineno = 0
|
|
415
414
|
for lineno, line in enumerate(full_lines):
|
|
415
|
+
# Again, strip any ANSI colors when searching for starting text:
|
|
416
|
+
# - options:
|
|
417
|
+
# - optional arguments:
|
|
418
|
+
if args['color']:
|
|
419
|
+
line = strip_ansi_color(line)
|
|
416
420
|
if any(line.startswith(x) for x in ('options:', 'optional arguments:')):
|
|
417
421
|
break
|
|
418
|
-
|
|
422
|
+
|
|
423
|
+
# skip the line that says 'options:', replace with the progname:
|
|
419
424
|
return f'{parser.prog}:\n' + '\n'.join(full_lines[lineno + 1:])
|
|
420
425
|
|
|
421
426
|
|
|
@@ -724,7 +729,7 @@ def print_post(text: str, end: str) -> None:
|
|
|
724
729
|
|
|
725
730
|
|
|
726
731
|
def print_color(text: str, color: str, end: str = '\n') -> None:
|
|
727
|
-
'''Note that color(str) must be one of Colors.[red|green|
|
|
732
|
+
'''Note that color(str) must be one of Colors.[red|green|yellow|normal]'''
|
|
728
733
|
print_pre()
|
|
729
734
|
print(Colors.color_text(text, color), end=end, flush=True)
|
|
730
735
|
print_post(text, end)
|
|
@@ -732,25 +737,25 @@ def print_color(text: str, color: str, end: str = '\n') -> None:
|
|
|
732
737
|
def print_red(text: str, end: str = '\n') -> None:
|
|
733
738
|
'''Print text as red, goes back to normal color'''
|
|
734
739
|
print_pre()
|
|
735
|
-
print(
|
|
740
|
+
print(Colors.color_text(text, color=Colors.red), end=end, flush=True)
|
|
736
741
|
print_post(text, end)
|
|
737
742
|
|
|
738
743
|
def print_green(text: str, end: str = '\n') -> None:
|
|
739
744
|
'''Print text as green, goes back to normal color'''
|
|
740
745
|
print_pre()
|
|
741
|
-
print(
|
|
746
|
+
print(Colors.color_text(text, color=Colors.green), end=end, flush=True)
|
|
742
747
|
print_post(text, end)
|
|
743
748
|
|
|
744
|
-
def
|
|
745
|
-
'''Print text as
|
|
749
|
+
def print_yellow(text: str, end: str = '\n') -> None:
|
|
750
|
+
'''Print text as yellow, goes back to normal color'''
|
|
746
751
|
print_pre()
|
|
747
|
-
print(
|
|
752
|
+
print(Colors.color_text(text, color=Colors.yellow), end=end, flush=True)
|
|
748
753
|
print_post(text, end)
|
|
749
754
|
|
|
750
|
-
def
|
|
751
|
-
'''Print text as
|
|
755
|
+
def print_foreground_color(text: str, end: str = '\n') -> None:
|
|
756
|
+
'''Print text as foreground color, goes back to normal color'''
|
|
752
757
|
print_pre()
|
|
753
|
-
print(
|
|
758
|
+
print(Colors.color_text(text, color=Colors.foreground), end=end, flush=True)
|
|
754
759
|
print_post(text, end)
|
|
755
760
|
|
|
756
761
|
|
|
@@ -763,8 +768,11 @@ def set_debug_level(level) -> None:
|
|
|
763
768
|
info(f"Set debug level to {debug_level}")
|
|
764
769
|
|
|
765
770
|
|
|
766
|
-
def debug(
|
|
767
|
-
|
|
771
|
+
def debug(
|
|
772
|
+
*text, level: int = 1, start: object = None, end: str = '\n', color=Colors.foreground
|
|
773
|
+
) -> None:
|
|
774
|
+
'''Print debug messaging (in foreground color if possible). If args['debug'] is false,
|
|
775
|
+
prints nothing.
|
|
768
776
|
|
|
769
777
|
*text: (positional str args) to be printed
|
|
770
778
|
level: (int) debug level to decide if printed or not.
|
|
@@ -777,10 +785,10 @@ def debug(*text, level: int = 1, start: object = None, end: str = '\n') -> None:
|
|
|
777
785
|
start = "DEBUG: " + (f"[{progname}] " if progname_in_message else "")
|
|
778
786
|
if args['debug'] and \
|
|
779
787
|
(((level==1) and args['verbose']) or (debug_level >= level)):
|
|
780
|
-
|
|
788
|
+
print_color(f"{start}{' '.join(list(text))}", color=color, end=end)
|
|
781
789
|
|
|
782
790
|
|
|
783
|
-
def info(*text, start: object = None, end='\n') -> None:
|
|
791
|
+
def info(*text, start: object = None, end='\n', color=Colors.green) -> None:
|
|
784
792
|
'''Print information messaging (in green if possible). If args['quiet'], prints nothing.
|
|
785
793
|
|
|
786
794
|
*text: (positional str args) to be printed
|
|
@@ -792,10 +800,10 @@ def info(*text, start: object = None, end='\n') -> None:
|
|
|
792
800
|
if start is None:
|
|
793
801
|
start = "INFO: " + (f"[{progname}] " if progname_in_message else "")
|
|
794
802
|
if not args['quiet']:
|
|
795
|
-
|
|
803
|
+
print_color(f"{start}{' '.join(list(text))}", color=color, end=end)
|
|
796
804
|
|
|
797
805
|
def warning(*text, start: object = None, end: str = '\n') -> None:
|
|
798
|
-
'''Print warning messaging (in
|
|
806
|
+
'''Print warning messaging (in yellow if possible).
|
|
799
807
|
|
|
800
808
|
*text: (positional str args) to be printed
|
|
801
809
|
start: (optional str) prefix to message; if None: chooses default start str
|
|
@@ -806,7 +814,7 @@ def warning(*text, start: object = None, end: str = '\n') -> None:
|
|
|
806
814
|
if start is None:
|
|
807
815
|
start = "WARNING: " + (f"[{progname}] " if progname_in_message else "")
|
|
808
816
|
args['warnings'] += 1
|
|
809
|
-
|
|
817
|
+
print_yellow(f"{start}{' '.join(list(text))}", end=end)
|
|
810
818
|
|
|
811
819
|
|
|
812
820
|
def error(
|
|
@@ -829,6 +837,7 @@ def error(
|
|
|
829
837
|
|
|
830
838
|
if start is None:
|
|
831
839
|
start = "ERROR: " + (f"[{progname}] " if progname_in_message else "")
|
|
840
|
+
start += safe_emoji("❌ ")
|
|
832
841
|
args['errors'] += 1
|
|
833
842
|
max_error_code = max(max_error_code, error_code)
|
|
834
843
|
print_red(f"{start}{' '.join(list(text))}", end=end)
|
|
@@ -862,7 +871,17 @@ def exit( # pylint: disable=redefined-builtin
|
|
|
862
871
|
|
|
863
872
|
if global_exit_allowed:
|
|
864
873
|
if not quiet:
|
|
865
|
-
|
|
874
|
+
info_color = Colors.green
|
|
875
|
+
start = safe_emoji('🔚 ')
|
|
876
|
+
if args['errors']:
|
|
877
|
+
info_color = Colors.red
|
|
878
|
+
start = safe_emoji('❗ ')
|
|
879
|
+
elif args['warnings']:
|
|
880
|
+
info_color = Colors.yellow
|
|
881
|
+
info(
|
|
882
|
+
f"{start}Exiting with {args['warnings']} warnings, {args['errors']} errors",
|
|
883
|
+
color=info_color
|
|
884
|
+
)
|
|
866
885
|
sys.exit(error_code)
|
|
867
886
|
|
|
868
887
|
if error_code is None:
|
|
@@ -998,14 +1017,18 @@ def import_class_from_string(full_class_name: str) -> None:
|
|
|
998
1017
|
class ShellCommandList(list):
|
|
999
1018
|
'''Wrapper around a list, of str that we'll run as a subprocess command
|
|
1000
1019
|
|
|
1001
|
-
included member
|
|
1020
|
+
included member vars for:
|
|
1021
|
+
- tee_path, to save a log from this subprocess commands list
|
|
1022
|
+
- work_dir - in case we want to run this from non-default location.
|
|
1002
1023
|
'''
|
|
1003
|
-
def __init__(self, obj: object = None, tee_fpath: str = ''):
|
|
1024
|
+
def __init__(self, obj: object = None, tee_fpath: str = '', work_dir: str = ''):
|
|
1004
1025
|
super().__init__(obj)
|
|
1005
|
-
for k in
|
|
1026
|
+
for k in ('tee_fpath', 'work_dir'):
|
|
1006
1027
|
setattr(self, k, getattr(obj, k, None))
|
|
1007
1028
|
if tee_fpath:
|
|
1008
1029
|
self.tee_fpath = tee_fpath
|
|
1030
|
+
if work_dir:
|
|
1031
|
+
self.work_dir = work_dir
|
|
1009
1032
|
|
|
1010
1033
|
|
|
1011
1034
|
def write_shell_command_file(
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: opencos-eda
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.6
|
|
4
4
|
Summary: A simple Python package for wrapping RTL simuliatons and synthesis
|
|
5
5
|
Author-email: Simon Sabato <simon@cognichip.ai>, Drew Ranck <drew@cognichip.ai>
|
|
6
6
|
Project-URL: Homepage, https://github.com/cognichip/opencos
|
|
@@ -16,11 +16,12 @@ Requires-Dist: schema>=0.7.7
|
|
|
16
16
|
Requires-Dist: toml>=0.10.2
|
|
17
17
|
Requires-Dist: yamllint>=1.35.1
|
|
18
18
|
Requires-Dist: PySerial>=3.5
|
|
19
|
-
Requires-Dist: cocotb>=2.0
|
|
20
19
|
Requires-Dist: supports_color>=0.2.0
|
|
20
|
+
Requires-Dist: cocotb>=2.0
|
|
21
|
+
Requires-Dist: pytest>=8.3.5
|
|
22
|
+
Requires-Dist: coverage>=7.6.1
|
|
21
23
|
Provides-Extra: dev
|
|
22
24
|
Requires-Dist: pylint>=3.0.0; extra == "dev"
|
|
23
|
-
Requires-Dist: pytest>=8.3.5; extra == "dev"
|
|
24
25
|
Provides-Extra: docs
|
|
25
26
|
Requires-Dist: mkdocs; extra == "docs"
|
|
26
27
|
Requires-Dist: mkdocs-material; extra == "docs"
|