opencos-eda 0.3.6__py3-none-any.whl → 0.3.8__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/flist.py +2 -2
- opencos/commands/multi.py +4 -3
- opencos/commands/sim.py +73 -14
- opencos/eda.py +22 -20
- opencos/eda_base.py +227 -82
- opencos/eda_config.py +1 -0
- opencos/eda_config_defaults.yml +28 -3
- opencos/tests/deps_files/command_order/DEPS.yml +13 -0
- opencos/tests/helpers.py +9 -5
- opencos/tests/test_eda.py +1 -1
- opencos/tests/test_tools.py +62 -33
- opencos/tools/iverilog.py +2 -2
- opencos/tools/riviera.py +33 -2
- opencos/tools/slang.py +24 -0
- opencos/tools/surelog.py +22 -0
- opencos/tools/verilator.py +12 -7
- opencos/tools/vivado.py +4 -0
- opencos/tools/yosys.py +3 -3
- opencos/util.py +29 -6
- opencos/utils/str_helpers.py +4 -4
- opencos/utils/subprocess_helpers.py +23 -6
- {opencos_eda-0.3.6.dist-info → opencos_eda-0.3.8.dist-info}/METADATA +6 -4
- {opencos_eda-0.3.6.dist-info → opencos_eda-0.3.8.dist-info}/RECORD +28 -28
- {opencos_eda-0.3.6.dist-info → opencos_eda-0.3.8.dist-info}/WHEEL +0 -0
- {opencos_eda-0.3.6.dist-info → opencos_eda-0.3.8.dist-info}/entry_points.txt +0 -0
- {opencos_eda-0.3.6.dist-info → opencos_eda-0.3.8.dist-info}/licenses/LICENSE +0 -0
- {opencos_eda-0.3.6.dist-info → opencos_eda-0.3.8.dist-info}/licenses/LICENSE.spdx +0 -0
- {opencos_eda-0.3.6.dist-info → opencos_eda-0.3.8.dist-info}/top_level.txt +0 -0
opencos/tests/test_tools.py
CHANGED
|
@@ -3,16 +3,16 @@
|
|
|
3
3
|
# pylint: disable=R0801 # (similar lines in 2+ files)
|
|
4
4
|
|
|
5
5
|
import os
|
|
6
|
+
import shutil
|
|
6
7
|
import sys
|
|
7
8
|
import pytest
|
|
8
9
|
|
|
9
|
-
from opencos import
|
|
10
|
-
|
|
10
|
+
from opencos import eda_base
|
|
11
11
|
from opencos.tools.verilator import ToolVerilator
|
|
12
12
|
from opencos.tools.vivado import ToolVivado
|
|
13
13
|
from opencos.tools.cocotb import ToolCocotb
|
|
14
14
|
from opencos.tests import helpers
|
|
15
|
-
from opencos.tests.helpers import eda_wrap, eda_wrap_is_sim_fail, config, tools_loaded
|
|
15
|
+
from opencos.tests.helpers import Helpers, eda_wrap, eda_wrap_is_sim_fail, config, tools_loaded
|
|
16
16
|
from opencos.utils.markup_helpers import yaml_safe_load
|
|
17
17
|
from opencos.utils import status_constants
|
|
18
18
|
|
|
@@ -23,6 +23,10 @@ def chdir_remove_work_dir(relpath):
|
|
|
23
23
|
'''Changes dir to relpath, removes the work directories (eda.work, eda.export*)'''
|
|
24
24
|
return helpers.chdir_remove_work_dir(THISPATH, relpath)
|
|
25
25
|
|
|
26
|
+
def filter_tools(tools: list) -> list:
|
|
27
|
+
'''Given a list of tool l, filters to return a list of tools that are loaded'''
|
|
28
|
+
return [x for x in tools if x in tools_loaded]
|
|
29
|
+
|
|
26
30
|
|
|
27
31
|
def test_tools_loaded():
|
|
28
32
|
'''Does not directly call 'eda.main' instead create a few Tool
|
|
@@ -32,14 +36,19 @@ def test_tools_loaded():
|
|
|
32
36
|
assert config
|
|
33
37
|
assert len(config.keys()) > 0
|
|
34
38
|
|
|
39
|
+
|
|
35
40
|
# It's possible we're running in some container or install that has no tools, for example,
|
|
36
41
|
# Windows.
|
|
37
42
|
if sys.platform.startswith('win') and \
|
|
38
43
|
not helpers.can_run_eda_command('elab', 'sim', cfg=config):
|
|
39
|
-
# Windows,
|
|
44
|
+
# Windows, no handlers for elab or sim:
|
|
40
45
|
pass
|
|
41
46
|
else:
|
|
42
|
-
|
|
47
|
+
basic_tools_available_via_path = any(shutil.which(x) for x in (
|
|
48
|
+
'verilator', 'iverilog', 'xsim', 'vsim', 'slang', 'yosys'
|
|
49
|
+
))
|
|
50
|
+
if basic_tools_available_via_path:
|
|
51
|
+
assert len(tools_loaded) > 0
|
|
43
52
|
|
|
44
53
|
def version_checker(
|
|
45
54
|
obj: eda_base.Tool, chk_str: str
|
|
@@ -49,7 +58,7 @@ def test_tools_loaded():
|
|
|
49
58
|
assert chk_str in full_ver, f'{chk_str=} not in {full_ver=}'
|
|
50
59
|
ver_num = full_ver.rsplit(':', maxsplit=1)[-1]
|
|
51
60
|
if 'b' in ver_num:
|
|
52
|
-
ver_num = ver_num.split('b')[0]
|
|
61
|
+
ver_num = ver_num.split('b')[0]
|
|
53
62
|
if '.' in ver_num:
|
|
54
63
|
major_ver = ver_num.split('.')[0]
|
|
55
64
|
assert major_ver.isdigit(), (
|
|
@@ -101,40 +110,60 @@ list_of_added_sim_args = [
|
|
|
101
110
|
'--gui --test-mode',
|
|
102
111
|
]
|
|
103
112
|
|
|
113
|
+
list_of_loaded_tools = filter_tools(list_of_tools)
|
|
114
|
+
|
|
104
115
|
cannot_use_cocotb = 'cocotb' not in tools_loaded or \
|
|
105
116
|
('iverilog' not in tools_loaded and \
|
|
106
117
|
'verilator' not in tools_loaded)
|
|
107
118
|
CANNOT_USE_COCOTB_REASON = 'requires cocotb in tools_loaded, and one of (iverilog, verilator) too'
|
|
108
119
|
|
|
109
|
-
@pytest.mark.parametrize("command", list_of_commands)
|
|
110
|
-
@pytest.mark.parametrize("tool", list_of_tools)
|
|
111
|
-
@pytest.mark.parametrize("target,sim_expect_pass", list_of_deps_targets)
|
|
112
|
-
@pytest.mark.parametrize("added_sim_args_str", list_of_added_sim_args)
|
|
113
|
-
def test_sim_elab_tools_pass_or_fail(command, tool, target, sim_expect_pass, added_sim_args_str):
|
|
114
|
-
'''tests that: eda <sim|elab> --tool <parameter-tool> <parameter-args> <parameter-target>
|
|
115
120
|
|
|
116
|
-
|
|
121
|
+
class TestSimElabTools(Helpers):
|
|
122
|
+
'''Tests for eda sim|elab for various tools with various args'''
|
|
117
123
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
124
|
+
DEFAULT_DIR = os.path.join(THISPATH, 'deps_files', 'test_err_fatal')
|
|
125
|
+
|
|
126
|
+
@pytest.mark.parametrize("command", list_of_commands)
|
|
127
|
+
@pytest.mark.parametrize("tool", list_of_loaded_tools)
|
|
128
|
+
@pytest.mark.parametrize("target,sim_expect_pass", list_of_deps_targets)
|
|
129
|
+
@pytest.mark.parametrize("added_sim_args_str", list_of_added_sim_args)
|
|
130
|
+
def test_pass_or_fail(
|
|
131
|
+
self, command, tool, target, sim_expect_pass, added_sim_args_str
|
|
132
|
+
):
|
|
133
|
+
'''tests that: eda <sim|elab> --tool <parameter-tool> <parameter-args> <parameter-target>
|
|
134
|
+
|
|
135
|
+
will correctly pass or fail depending on if it is supported or not.
|
|
136
|
+
|
|
137
|
+
Also tests for: non-gui, or --gui --test-mode (runs non-gui, but most python args will
|
|
138
|
+
be for --gui mode, signal logging, etc).
|
|
139
|
+
'''
|
|
140
|
+
added_args_str = ''
|
|
141
|
+
if command == 'sim':
|
|
142
|
+
added_args_str = added_sim_args_str
|
|
143
|
+
|
|
144
|
+
rc = self.log_it(f'{command} --tool {tool} {added_args_str} {target}')
|
|
145
|
+
print(f'{rc=}')
|
|
146
|
+
tool_error_count_lines = self.get_log_lines_with('tool errors')
|
|
147
|
+
if command != 'sim' or sim_expect_pass:
|
|
148
|
+
# command='elab' should pass.
|
|
149
|
+
assert rc == 0
|
|
150
|
+
assert tool_error_count_lines
|
|
151
|
+
assert all('tool warnings' in line for line in tool_error_count_lines)
|
|
152
|
+
assert all(' 0 tool errors' in line for line in tool_error_count_lines)
|
|
153
|
+
|
|
154
|
+
else:
|
|
155
|
+
assert eda_wrap_is_sim_fail(rc)
|
|
156
|
+
assert tool_error_count_lines
|
|
157
|
+
assert all('tool warnings' in line for line in tool_error_count_lines)
|
|
158
|
+
# May or may not have reported tool errors.
|
|
159
|
+
assert all('tool errors' in line for line in tool_error_count_lines)
|
|
160
|
+
# The final line of tool warnings/errors should have > 0 tool errors,
|
|
161
|
+
# since we were checking SV $error and $fatal
|
|
162
|
+
assert ' 0 tool errors' not in tool_error_count_lines[-1]
|
|
163
|
+
# The line should end with ' X tool errors'
|
|
164
|
+
parts = tool_error_count_lines[-1].split()
|
|
165
|
+
assert parts[-3].isdigit()
|
|
166
|
+
assert int(parts[-3]) in (1, 2, 3) # we should have at least 1, but some tools dup.
|
|
138
167
|
|
|
139
168
|
|
|
140
169
|
@pytest.mark.skipif('vivado' not in tools_loaded, reason="requires vivado")
|
opencos/tools/iverilog.py
CHANGED
|
@@ -93,7 +93,7 @@ class CommandSimIverilog(CommandSim, ToolIverilog):
|
|
|
93
93
|
def compile(self):
|
|
94
94
|
if self.args['stop-before-compile']:
|
|
95
95
|
return
|
|
96
|
-
self.run_commands_check_logs(self.iverilog_command_lists
|
|
96
|
+
self.run_commands_check_logs(self.iverilog_command_lists)
|
|
97
97
|
|
|
98
98
|
def elaborate(self):
|
|
99
99
|
pass
|
|
@@ -148,7 +148,7 @@ class CommandSimIverilog(CommandSim, ToolIverilog):
|
|
|
148
148
|
|
|
149
149
|
command_list += list(self.files_sv) + list(self.files_v)
|
|
150
150
|
|
|
151
|
-
return [ util.ShellCommandList(command_list) ]
|
|
151
|
+
return [ util.ShellCommandList(command_list, tee_fpath='compile.log') ]
|
|
152
152
|
|
|
153
153
|
def get_elaborate_command_lists(self, **kwargs) -> list:
|
|
154
154
|
return []
|
opencos/tools/riviera.py
CHANGED
|
@@ -21,6 +21,7 @@ class ToolRiviera(ToolModelsimAse):
|
|
|
21
21
|
_TOOL = 'riviera'
|
|
22
22
|
_EXE = 'vsim'
|
|
23
23
|
use_vopt = False
|
|
24
|
+
uvm_versions = set()
|
|
24
25
|
|
|
25
26
|
def get_versions(self) -> str:
|
|
26
27
|
if self._VERSION:
|
|
@@ -40,7 +41,17 @@ class ToolRiviera(ToolModelsimAse):
|
|
|
40
41
|
)
|
|
41
42
|
stdout = version_ret.stdout.decode('utf-8', errors='replace').rstrip()
|
|
42
43
|
|
|
43
|
-
#
|
|
44
|
+
# Get the UVM versions in the install directory. Note this may run
|
|
45
|
+
# more than once, so only do this if self.uvm_versions not yet set:
|
|
46
|
+
riviera_path, _ = os.path.split(self.sim_exe_base_path)
|
|
47
|
+
vlib_path = os.path.join(riviera_path, 'vlib')
|
|
48
|
+
if not self.uvm_versions and os.path.isdir(vlib_path):
|
|
49
|
+
for item in os.listdir(vlib_path):
|
|
50
|
+
# uvm-1.1, uvm-1.1d - so don't pick anything > 9 chars (uvm-M.mRr)
|
|
51
|
+
if item.startswith('uvm-') and '1800' not in item and len(item) <= 9:
|
|
52
|
+
self.uvm_versions.add(item[4:])
|
|
53
|
+
|
|
54
|
+
# For Version, expect:
|
|
44
55
|
# Aldec, Inc. Riviera-PRO version 2025.04.139.9738 built for Linux64 on May 30, 2025
|
|
45
56
|
left, right = stdout.split('version')
|
|
46
57
|
if 'Riviera' not in left:
|
|
@@ -76,6 +87,18 @@ class CommandSimRiviera(CommandSimModelsimAse, ToolRiviera):
|
|
|
76
87
|
'bring your own .tcl file to run in Riviera (vsim) for coverage. The default'
|
|
77
88
|
' tcl steps are (from tool config in --config-yml): '
|
|
78
89
|
) + '; '.join(self.tool_config.get('simulate-coverage-tcl', [])),
|
|
90
|
+
'uvm': (
|
|
91
|
+
'Attempts to support UVM. Adds to vlog: -l uvm +incdir+PATH for the PATH to'
|
|
92
|
+
' uvm_macros.svh for the installed version of Riviera used.'
|
|
93
|
+
),
|
|
94
|
+
})
|
|
95
|
+
|
|
96
|
+
if self.uvm_versions:
|
|
97
|
+
# set default to latest version:
|
|
98
|
+
self.args['uvm-version'] = sorted(self.uvm_versions)[-1]
|
|
99
|
+
|
|
100
|
+
self.args_kwargs.update({
|
|
101
|
+
'uvm-version': { 'choices': list(self.uvm_versions) }
|
|
79
102
|
})
|
|
80
103
|
|
|
81
104
|
def set_tool_defines(self):
|
|
@@ -136,7 +159,9 @@ class CommandSimRiviera(CommandSimModelsimAse, ToolRiviera):
|
|
|
136
159
|
return []
|
|
137
160
|
|
|
138
161
|
|
|
139
|
-
def write_vlog_dot_f(
|
|
162
|
+
def write_vlog_dot_f( # pylint: disable=too-many-branches
|
|
163
|
+
self, filename: str = 'vlog.f'
|
|
164
|
+
) -> None:
|
|
140
165
|
'''Returns none, creates filename (str) for a vlog.f'''
|
|
141
166
|
vlog_dot_f_lines = []
|
|
142
167
|
|
|
@@ -153,6 +178,12 @@ class CommandSimRiviera(CommandSimModelsimAse, ToolRiviera):
|
|
|
153
178
|
vlog_dot_f_fname = filename
|
|
154
179
|
vlog_dot_f_fpath = os.path.join(self.args['work-dir'], vlog_dot_f_fname)
|
|
155
180
|
|
|
181
|
+
if self.args['uvm']:
|
|
182
|
+
vlog_dot_f_lines.extend([
|
|
183
|
+
f'-uvmver {self.args["uvm-version"]}',
|
|
184
|
+
'-dbg'
|
|
185
|
+
])
|
|
186
|
+
|
|
156
187
|
for value in self.incdirs:
|
|
157
188
|
vlog_dot_f_lines += [ f"+incdir+{value}" ]
|
|
158
189
|
|
opencos/tools/slang.py
CHANGED
|
@@ -176,6 +176,30 @@ class CommandElabSlang(CommandElab, ToolSlang):
|
|
|
176
176
|
def get_post_simulate_command_lists(self, **kwargs) -> list:
|
|
177
177
|
return []
|
|
178
178
|
|
|
179
|
+
def update_tool_warn_err_counts_from_log_lines(
|
|
180
|
+
self, log_lines: list, bad_strings: list, warning_strings: list
|
|
181
|
+
) -> None:
|
|
182
|
+
'''
|
|
183
|
+
Overriden from Command, we ignore bad_strings/warning_strings and use a custom
|
|
184
|
+
checker.
|
|
185
|
+
'''
|
|
186
|
+
for line in log_lines:
|
|
187
|
+
if not line.startswith('Build failed: '):
|
|
188
|
+
continue
|
|
189
|
+
if not all(x in line for x in ('errors', 'warnings')):
|
|
190
|
+
continue
|
|
191
|
+
|
|
192
|
+
parts = line.strip().split()
|
|
193
|
+
if len(parts) < 6:
|
|
194
|
+
continue
|
|
195
|
+
|
|
196
|
+
errs = parts[2]
|
|
197
|
+
warns = parts[4]
|
|
198
|
+
if errs.isdigit():
|
|
199
|
+
self.tool_error_count += int(errs)
|
|
200
|
+
if warns.isdigit():
|
|
201
|
+
self.tool_warning_count += int(warns)
|
|
202
|
+
|
|
179
203
|
def _get_slang_command_list_start(self) -> list:
|
|
180
204
|
command_list = [self.slang_exe]
|
|
181
205
|
|
opencos/tools/surelog.py
CHANGED
|
@@ -147,6 +147,28 @@ class CommandElabSurelog(CommandElab, ToolSurelog):
|
|
|
147
147
|
command_lists=self.surelog_command_lists, line_breaks=True
|
|
148
148
|
)
|
|
149
149
|
|
|
150
|
+
def update_tool_warn_err_counts_from_log_lines(
|
|
151
|
+
self, log_lines: list, bad_strings: list, warning_strings: list
|
|
152
|
+
) -> None:
|
|
153
|
+
'''
|
|
154
|
+
Overriden from Command, we ignore bad_strings/warning_strings and use a custom
|
|
155
|
+
checker.
|
|
156
|
+
'''
|
|
157
|
+
for line in log_lines:
|
|
158
|
+
line = line.strip()
|
|
159
|
+
if line.endswith(' 0'):
|
|
160
|
+
continue
|
|
161
|
+
if line.startswith('[ FATAL] : ') or \
|
|
162
|
+
line.startswith('[ SYNTAX] : ') or \
|
|
163
|
+
line.startswith('[ ERROR] : '):
|
|
164
|
+
parts = line.split()
|
|
165
|
+
if parts[-1].isdigit():
|
|
166
|
+
self.tool_error_count += int(parts[-1])
|
|
167
|
+
if line.startswith('[WARNING] : '):
|
|
168
|
+
parts = line.split()
|
|
169
|
+
if parts[-1].isdigit():
|
|
170
|
+
self.tool_warning_count += int(parts[-1])
|
|
171
|
+
|
|
150
172
|
|
|
151
173
|
class CommandLintSurelog(CommandElabSurelog):
|
|
152
174
|
'''CommandLintSurelog is a command handler for: eda lint --tool=surelog.'''
|
opencos/tools/verilator.py
CHANGED
|
@@ -76,14 +76,12 @@ class VerilatorSim(CommandSim, ToolVerilator):
|
|
|
76
76
|
ToolVerilator.__init__(self, config=self.config)
|
|
77
77
|
self.args.update({
|
|
78
78
|
'gui': False,
|
|
79
|
-
'tcl-file': None,
|
|
80
79
|
'dump-vcd': False,
|
|
81
80
|
'waves-fst': True,
|
|
82
81
|
'waves-vcd': False,
|
|
83
82
|
'lint-only': False,
|
|
84
83
|
'cc-mode': False,
|
|
85
84
|
'verilator-coverage-args': [],
|
|
86
|
-
'uvm': False,
|
|
87
85
|
'x-assign': '',
|
|
88
86
|
'x-initial': '',
|
|
89
87
|
})
|
|
@@ -95,7 +93,7 @@ class VerilatorSim(CommandSim, ToolVerilator):
|
|
|
95
93
|
' plusarg and apply $dumpfile("dump.fst").'
|
|
96
94
|
),
|
|
97
95
|
'waves-fst': (
|
|
98
|
-
'
|
|
96
|
+
'If using --waves, apply simulation runtime arg +trace.'
|
|
99
97
|
' Note that if you do not have SV code using $dumpfile, eda will add'
|
|
100
98
|
' _waves_pkg.sv to handle this for you with +trace runtime plusarg.'
|
|
101
99
|
),
|
|
@@ -118,9 +116,9 @@ class VerilatorSim(CommandSim, ToolVerilator):
|
|
|
118
116
|
' Also conditinally adds to verilated exe call:'
|
|
119
117
|
' +verilator+rand+reset+[0,2] for arg values 0, unique|fast'),
|
|
120
118
|
'uvm': (
|
|
121
|
-
'Warns on Verilator < 5.042, or missing $UVM_HOME environment
|
|
122
|
-
' .env, $UVM_HOME/uvm_pkg.sv should exist), and will run verilator
|
|
123
|
-
' -Wno-fatal +define+UVM_NO_DPI'
|
|
119
|
+
'Enables UVM. Warns on Verilator < 5.042, or missing $UVM_HOME environment'
|
|
120
|
+
' var set (or in .env, $UVM_HOME/uvm_pkg.sv should exist), and will run verilator'
|
|
121
|
+
' with args: -Wno-fatal +define+UVM_NO_DPI'
|
|
124
122
|
),
|
|
125
123
|
'verilator-coverage-args': (
|
|
126
124
|
'Requires --coverage, args to be applied to verilator_coverage, which runs'
|
|
@@ -128,6 +126,12 @@ class VerilatorSim(CommandSim, ToolVerilator):
|
|
|
128
126
|
),
|
|
129
127
|
})
|
|
130
128
|
|
|
129
|
+
|
|
130
|
+
self.args_kwargs.update({
|
|
131
|
+
'x-assign': { 'choices': ['0', '1', 'unique', 'fast'] },
|
|
132
|
+
'x-initial': { 'choices': ['0', 'unique', 'fast'] },
|
|
133
|
+
})
|
|
134
|
+
|
|
131
135
|
self.verilate_command_lists = []
|
|
132
136
|
self.lint_only_command_lists = []
|
|
133
137
|
self.verilated_exec_command_lists = []
|
|
@@ -563,7 +567,8 @@ class VerilatorSim(CommandSim, ToolVerilator):
|
|
|
563
567
|
'version > v5.042')
|
|
564
568
|
|
|
565
569
|
if not os.environ.get('UVM_HOME', ''):
|
|
566
|
-
util.warning('--uvm set, however env (or .env or --env-file)
|
|
570
|
+
util.warning('--uvm set, however env (or .env or --env-file)',
|
|
571
|
+
'$UVM_HOME is not set')
|
|
567
572
|
|
|
568
573
|
uvm_pkg_found = self._verilator_support_uvm_pkg_fpath(add_if_found=add_uvm_pkg_if_found)
|
|
569
574
|
if warnings and not uvm_pkg_found:
|
opencos/tools/vivado.py
CHANGED
|
@@ -225,6 +225,8 @@ class CommandSimVivado(CommandSim, ToolVivado):
|
|
|
225
225
|
|
|
226
226
|
if self.tool_config.get('elab-waves-args', ''):
|
|
227
227
|
command_list += self.tool_config.get('elab-waves-args', '').split()
|
|
228
|
+
if self.args['uvm']:
|
|
229
|
+
command_list.extend(['-L', 'uvm'])
|
|
228
230
|
elif self.args['gui'] and self.args['waves']:
|
|
229
231
|
command_list += ['-debug', 'all']
|
|
230
232
|
elif self.args['gui']:
|
|
@@ -305,6 +307,8 @@ class CommandSimVivado(CommandSim, ToolVivado):
|
|
|
305
307
|
command_list[0] += ".bat"
|
|
306
308
|
if typ == 'sv':
|
|
307
309
|
command_list.append('-sv')
|
|
310
|
+
if self.args['uvm']:
|
|
311
|
+
command_list.extend(['-L', 'uvm'])
|
|
308
312
|
command_list += self.tool_config.get('compile-args', '').split()
|
|
309
313
|
if util.args['verbose']:
|
|
310
314
|
command_list += ['-v', '2']
|
opencos/tools/yosys.py
CHANGED
|
@@ -70,9 +70,9 @@ class ToolYosys(Tool):
|
|
|
70
70
|
[self.sta_exe, '-version'], capture_output=True, check=False
|
|
71
71
|
)
|
|
72
72
|
util.debug(f'{self.yosys_exe} {sta_version_ret=}')
|
|
73
|
-
sta_ver = sta_version_ret.stdout.decode('utf-8', errors='replace').split()
|
|
74
|
-
if sta_ver:
|
|
75
|
-
self.sta_version = sta_ver
|
|
73
|
+
sta_ver = sta_version_ret.stdout.decode('utf-8', errors='replace').split()
|
|
74
|
+
if sta_ver and isinstance(sta_ver, list):
|
|
75
|
+
self.sta_version = sta_ver[0]
|
|
76
76
|
|
|
77
77
|
version_ret = subprocess.run(
|
|
78
78
|
[self.yosys_exe, '--version'], capture_output=True, check=False
|
opencos/util.py
CHANGED
|
@@ -54,7 +54,11 @@ class Colors:
|
|
|
54
54
|
red = "\x1B[31m"
|
|
55
55
|
green = "\x1B[32m"
|
|
56
56
|
yellow = "\x1B[33m" # This looks orange, but it's techincally yellow
|
|
57
|
+
cyan = "\x1b[36m"
|
|
57
58
|
foreground = "\x1B[39m"
|
|
59
|
+
bold = "\x1B[1m"
|
|
60
|
+
byellow = "\x1B[1m\x1B[33m"
|
|
61
|
+
bcyan = "\x1B[1m\x1b[36m"
|
|
58
62
|
normal = "\x1B[0m"
|
|
59
63
|
|
|
60
64
|
@staticmethod
|
|
@@ -67,6 +71,14 @@ class Colors:
|
|
|
67
71
|
return color + text + "\x1B[0m" # (normal)
|
|
68
72
|
return text
|
|
69
73
|
|
|
74
|
+
def disable(self) -> None:
|
|
75
|
+
'''Clears all color str in class Colors, to prevent print() methods that use
|
|
76
|
+
util.Colors from printing color'''
|
|
77
|
+
for x in dir(self):
|
|
78
|
+
if not callable(getattr(self, x, None)) and isinstance(x, str) and \
|
|
79
|
+
not x.startswith('_'):
|
|
80
|
+
setattr(self, x, '')
|
|
81
|
+
|
|
70
82
|
|
|
71
83
|
def safe_emoji(emoji: str, default: str = '') -> str:
|
|
72
84
|
'''Returns emoji character if args['emoji'] is True'''
|
|
@@ -421,7 +433,7 @@ def get_argparser_short_help(parser: object = None) -> str:
|
|
|
421
433
|
break
|
|
422
434
|
|
|
423
435
|
# skip the line that says 'options:', replace with the progname:
|
|
424
|
-
return f'{parser.prog}
|
|
436
|
+
return f'{Colors.cyan}{parser.prog}:{Colors.normal}\n' + '\n'.join(full_lines[lineno + 1:])
|
|
425
437
|
|
|
426
438
|
|
|
427
439
|
def process_token(arg: list) -> bool:
|
|
@@ -533,13 +545,14 @@ def process_tokens( # pylint: disable=too-many-branches
|
|
|
533
545
|
global debug_level # pylint: disable=global-statement
|
|
534
546
|
debug_level = 0
|
|
535
547
|
|
|
536
|
-
# Deal with --debug, --debug-level, --env-file, -f/--input-file(s)
|
|
537
|
-
# for now put dot-f file contents in front of of tokens, do this first
|
|
538
|
-
# separate custom argparser:
|
|
548
|
+
# Deal with --version, --debug, --debug-level, --env-file, -f/--input-file(s)
|
|
549
|
+
# tokens first, for now put dot-f file contents in front of of tokens, do this first
|
|
550
|
+
# in a separate custom argparser:
|
|
539
551
|
bool_action_kwargs = get_argparse_bool_action_kwargs()
|
|
540
552
|
parser = argparse.ArgumentParser(
|
|
541
553
|
prog='opencos -f/--input-file', add_help=False, allow_abbrev=False
|
|
542
554
|
)
|
|
555
|
+
parser.add_argument('--version', default=False, action='store_true')
|
|
543
556
|
parser.add_argument('--debug', **bool_action_kwargs,
|
|
544
557
|
help='Display additional debug messaging level 1 or higher')
|
|
545
558
|
parser.add_argument('--debug-level', type=int, default=0,
|
|
@@ -560,6 +573,10 @@ def process_tokens( # pylint: disable=too-many-branches
|
|
|
560
573
|
except argparse.ArgumentError:
|
|
561
574
|
error(f'util -f/--input-file, problem attempting to parse_known_args for: {tokens}')
|
|
562
575
|
|
|
576
|
+
if parsed.version:
|
|
577
|
+
# Stop processing, return nothing, caller can print version and exit.
|
|
578
|
+
return parsed, []
|
|
579
|
+
|
|
563
580
|
process_debug_args(parsed=parsed)
|
|
564
581
|
debug(f'util.process_tokens: {parsed=} {unparsed=} from: {tokens}')
|
|
565
582
|
|
|
@@ -599,6 +616,9 @@ def process_tokens( # pylint: disable=too-many-branches
|
|
|
599
616
|
warning(f'python error, nested -f/--input-file(s) {parsed.input_file} should',
|
|
600
617
|
'have already been resolved')
|
|
601
618
|
|
|
619
|
+
if not parsed.color:
|
|
620
|
+
Colors.disable(Colors) # strip strings in Colors class
|
|
621
|
+
|
|
602
622
|
# clear existing artifacts dicts (mostly for pytests repeatedly calling eda.main),
|
|
603
623
|
# set artifacts.enabled based on args['artifacts-json']
|
|
604
624
|
artifacts.reset(enable=parsed.artifacts_json)
|
|
@@ -836,7 +856,9 @@ def error(
|
|
|
836
856
|
global max_error_code # pylint: disable=global-statement
|
|
837
857
|
|
|
838
858
|
if start is None:
|
|
839
|
-
start = "ERROR:
|
|
859
|
+
start = f"{Colors.bold}ERROR:{Colors.normal}{Colors.red} "
|
|
860
|
+
if progname_in_message:
|
|
861
|
+
start += f"[{progname}] "
|
|
840
862
|
start += safe_emoji("❌ ")
|
|
841
863
|
args['errors'] += 1
|
|
842
864
|
max_error_code = max(max_error_code, error_code)
|
|
@@ -879,7 +901,8 @@ def exit( # pylint: disable=redefined-builtin
|
|
|
879
901
|
elif args['warnings']:
|
|
880
902
|
info_color = Colors.yellow
|
|
881
903
|
info(
|
|
882
|
-
f"{start}Exiting with {args['warnings']} warnings
|
|
904
|
+
f"{start}Exiting with {Colors.bold}{args['warnings']} warnings{info_color},",
|
|
905
|
+
f"{Colors.bold}{args['errors']} errors",
|
|
883
906
|
color=info_color
|
|
884
907
|
)
|
|
885
908
|
sys.exit(error_code)
|
opencos/utils/str_helpers.py
CHANGED
|
@@ -132,11 +132,11 @@ def get_terminal_columns():
|
|
|
132
132
|
|
|
133
133
|
Returns:
|
|
134
134
|
int: The number of columns in the terminal, or a default value (e.g., 80)
|
|
135
|
-
if the terminal size cannot be determined.
|
|
135
|
+
if the terminal size cannot be determined. Min value of 40 is returned.
|
|
136
136
|
"""
|
|
137
137
|
try:
|
|
138
138
|
size = os.get_terminal_size()
|
|
139
|
-
return size.columns
|
|
139
|
+
return max(40, size.columns)
|
|
140
140
|
except OSError:
|
|
141
141
|
# Handle cases where the terminal size cannot be determined (e.g., not in a TTY)
|
|
142
142
|
return 80 # Default to 80 columns
|
|
@@ -166,13 +166,13 @@ def pretty_list_columns_manual(data: list, num_columns: int = 4, auto_columns: b
|
|
|
166
166
|
max_line_len = 0
|
|
167
167
|
for x in max_lengths:
|
|
168
168
|
max_line_len += x + _spacing
|
|
169
|
-
if max_line_len
|
|
169
|
+
if max_line_len >= window_cols:
|
|
170
170
|
# subtract a column (already >= 2):
|
|
171
171
|
ret_lines.extend(
|
|
172
172
|
pretty_list_columns_manual(data=data, num_columns=num_columns-1, auto_columns=True)
|
|
173
173
|
)
|
|
174
174
|
return ret_lines
|
|
175
|
-
if max_line_len + max_item_len + _spacing
|
|
175
|
+
if max_line_len + max_item_len + _spacing < window_cols:
|
|
176
176
|
# add 1 more column if we're guaranteed to have room.
|
|
177
177
|
ret_lines.extend(
|
|
178
178
|
pretty_list_columns_manual(data=data, num_columns=num_columns+1, auto_columns=True)
|
|
@@ -6,7 +6,9 @@ import subprocess
|
|
|
6
6
|
import sys
|
|
7
7
|
|
|
8
8
|
import psutil
|
|
9
|
+
from opencos import util
|
|
9
10
|
from opencos.util import debug, error, info, warning, progname, global_log
|
|
11
|
+
from opencos.utils.str_helpers import strip_ansi_color
|
|
10
12
|
|
|
11
13
|
IS_WINDOWS = sys.platform.startswith('win')
|
|
12
14
|
|
|
@@ -68,7 +70,7 @@ def subprocess_run_background( # pylint: disable=too-many-branches
|
|
|
68
70
|
|
|
69
71
|
proc_kwargs = {'shell': shell,
|
|
70
72
|
'stdout': subprocess.PIPE,
|
|
71
|
-
'stderr': subprocess.STDOUT
|
|
73
|
+
'stderr': subprocess.STDOUT
|
|
72
74
|
}
|
|
73
75
|
if work_dir:
|
|
74
76
|
proc_kwargs['cwd'] = work_dir
|
|
@@ -101,14 +103,29 @@ def subprocess_run_background( # pylint: disable=too-many-branches
|
|
|
101
103
|
error(f'Unable to open file "{tee_fpath}" for writing, {e}')
|
|
102
104
|
|
|
103
105
|
for line in iter(proc.stdout.readline, b''):
|
|
104
|
-
line = line.
|
|
106
|
+
line = line.decode("utf-8", errors="replace") # leave \n intact
|
|
107
|
+
|
|
108
|
+
# Since we don't control what the subprocess command did, if it
|
|
109
|
+
# thinks we support color, but user ran with --no-color, we need to strip ANSI colors:
|
|
110
|
+
if not util.args['color']:
|
|
111
|
+
line = strip_ansi_color(line)
|
|
112
|
+
|
|
113
|
+
# Print the line with color, if --color:
|
|
105
114
|
if not background:
|
|
106
|
-
print(line)
|
|
115
|
+
print(line, end='')
|
|
116
|
+
|
|
117
|
+
# for all logs, and the returned stdout str, if we haven't stripped color yet,
|
|
118
|
+
# we need to now, before writing to tee_fpath_f, or to global_log:
|
|
119
|
+
if util.args['color']:
|
|
120
|
+
line = strip_ansi_color(line)
|
|
121
|
+
line = line.replace('\r', '') # remove CR
|
|
122
|
+
|
|
107
123
|
if tee_fpath_f:
|
|
108
|
-
tee_fpath_f.write(line
|
|
124
|
+
tee_fpath_f.write(line)
|
|
109
125
|
if global_log.file:
|
|
110
|
-
|
|
111
|
-
|
|
126
|
+
# directly write to file handle, avoid util.UtilLogger.write(line, end='')
|
|
127
|
+
global_log.file.write(line)
|
|
128
|
+
stdout += line
|
|
112
129
|
|
|
113
130
|
proc.communicate()
|
|
114
131
|
remove_completed_parent_pid(proc.pid)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: opencos-eda
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.8
|
|
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
|
|
@@ -17,11 +17,13 @@ Requires-Dist: toml>=0.10.2
|
|
|
17
17
|
Requires-Dist: yamllint>=1.35.1
|
|
18
18
|
Requires-Dist: PySerial>=3.5
|
|
19
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
|
|
23
20
|
Provides-Extra: dev
|
|
24
21
|
Requires-Dist: pylint>=3.0.0; extra == "dev"
|
|
22
|
+
Requires-Dist: pytest>=8.3.5; extra == "dev"
|
|
23
|
+
Provides-Extra: cocotb
|
|
24
|
+
Requires-Dist: cocotb>=2.0; extra == "cocotb"
|
|
25
|
+
Requires-Dist: pytest>=8.3.5; extra == "cocotb"
|
|
26
|
+
Requires-Dist: coverage>=7.6.1; extra == "cocotb"
|
|
25
27
|
Provides-Extra: docs
|
|
26
28
|
Requires-Dist: mkdocs; extra == "docs"
|
|
27
29
|
Requires-Dist: mkdocs-material; extra == "docs"
|