opencos-eda 0.2.57__py3-none-any.whl → 0.3.0__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/eda_base.py CHANGED
@@ -506,12 +506,11 @@ class Command: # pylint: disable=too-many-public-methods
506
506
  self.args[key].update(value)
507
507
 
508
508
  elif isinstance(cur_value, list):
509
- # if list, append (no duplicates)
509
+ # if list, append (allow duplicates)
510
510
  if isinstance(value, list):
511
511
  # new value also a list
512
512
  for x in value:
513
- if x not in self.args[key]:
514
- self.args[key].append(x)
513
+ self.args[key].append(x)
515
514
  elif value not in cur_value:
516
515
  self.args[key].append(value)
517
516
 
@@ -970,13 +969,16 @@ class CommandDesign(Command): # pylint: disable=too-many-instance-attributes
970
969
  self.targets_dict = {} # key = targets that we've already processed in DEPS files
971
970
  self.last_added_source_file_inferred_top = ''
972
971
 
973
- self.has_dep_shell_commands = False
972
+ self.has_pre_compile_dep_shell_commands = False
973
+ self.has_post_tool_dep_shell_commands = False
974
974
 
975
975
 
976
976
  def run_dep_commands(self) -> None:
977
- '''Run shell/peakrdl style commands from DEPS files
977
+ '''Run shell/peakrdl style commands from DEPS files, this is peformed before
978
978
 
979
- These are deferred to maintain the deps ordering, and run in that order.
979
+ any tool compile step. These are deferred to maintain the deps ordering, and
980
+ run in that order. Note this will NOT run any DEPS command marked with
981
+ run-after-tool=True.
980
982
  '''
981
983
  self.run_dep_shell_commands()
982
984
  # Update any work_dir_add_srcs@ in our self.files, self.files_v, etc, b/c
@@ -986,17 +988,41 @@ class CommandDesign(Command): # pylint: disable=too-many-instance-attributes
986
988
  self.update_non_source_files_in_work_dir()
987
989
 
988
990
 
989
- def run_dep_shell_commands(self) -> None:
990
- '''Specifically runs shell command from DEPS files'''
991
+ def run_post_tool_dep_commands(self) -> None:
992
+ '''Run shell style commands from DEPS files that have been marked with
993
+
994
+ run-after-tool=True. Note these are skipped if any args like
995
+ stop-before- or stop-after- are set.
996
+ '''
997
+
998
+ self.run_dep_shell_commands(filter_run_after_tool=True)
999
+
1000
+
1001
+ def run_dep_shell_commands( # pylint: disable=too-many-branches,too-many-locals
1002
+ self, filter_run_after_tool: bool = False
1003
+ ) -> None:
1004
+ '''Runs collected shell command from DEPS files.
1005
+
1006
+ There are two flavors of shell commands: with or without 'run-after-tool'
1007
+ set. The default is to run shell command before the compile step of any tool,
1008
+ by calling this method with default pre_compile=True before any tool runs
1009
+ (for generating code, etc). However, it may be useful to run shell commands
1010
+ after a tool is complete (check timing, coverage, etc).
1011
+ '''
991
1012
 
992
1013
  # Runs from self.args['work-dir']
993
1014
  all_cmds_lists = []
994
1015
 
995
1016
  log_fnames_count = {} # count per target_node.
996
1017
 
997
- for i, d in enumerate(self.dep_shell_commands):
1018
+ filtered_dep_shell_commands = []
1019
+ for value in self.dep_shell_commands:
1020
+ if value['attributes']['run-after-tool'] == filter_run_after_tool:
1021
+ filtered_dep_shell_commands.append(value)
1022
+
1023
+
1024
+ for i, d in enumerate(filtered_dep_shell_commands):
998
1025
  clist = util.ShellCommandList(d['exec_list'])
999
- run_from_work_dir = d['run_from_work_dir'] # default True
1000
1026
  log = clist.tee_fpath
1001
1027
  target_node = d["target_node"]
1002
1028
  if clist.tee_fpath is None:
@@ -1011,29 +1037,48 @@ class CommandDesign(Command): # pylint: disable=too-many-instance-attributes
1011
1037
  # (or tee name from DEPS.yml)
1012
1038
  [f'# command {i}: target: {d["target_path"]} : {target_node} --> {log}'],
1013
1039
  ]
1014
- if not run_from_work_dir:
1040
+ if not d['attributes']['run-from-work-dir']:
1015
1041
  all_cmds_lists.append([f'cd {d["target_path"]}'])
1016
1042
 
1017
1043
  # actual command (list or util.ShellCommandList)
1018
1044
  all_cmds_lists.append(clist)
1019
1045
 
1020
- if not run_from_work_dir:
1046
+ if not d['attributes']['run-from-work-dir']:
1021
1047
  all_cmds_lists.append([f'cd {os.path.abspath(self.args["work-dir"])}'])
1022
1048
 
1023
1049
  d['exec_list'] = clist # update to tee_fpath is set.
1024
1050
 
1025
1051
  if all_cmds_lists:
1052
+ if filter_run_after_tool:
1053
+ filename='post_tool_dep_shell_commands.sh'
1054
+ self.has_post_tool_dep_shell_commands = True
1055
+ else:
1056
+ filename='pre_compile_dep_shell_commands.sh'
1057
+ self.has_pre_compile_dep_shell_commands = True
1058
+
1026
1059
  util.write_shell_command_file(
1027
- dirpath=self.args['work-dir'], filename='pre_compile_dep_shell_commands.sh',
1060
+ dirpath=self.args['work-dir'], filename=filename,
1028
1061
  command_lists=all_cmds_lists
1029
1062
  )
1030
- self.has_dep_shell_commands = True
1031
1063
 
1032
- for i, d in enumerate(self.dep_shell_commands):
1064
+
1065
+ if all_cmds_lists and filter_run_after_tool and \
1066
+ any(self.args.get(x, False) for x in (
1067
+ "stop-before-compile",
1068
+ "stop-after-compile",
1069
+ "stop-after-elaborate"
1070
+ )):
1071
+ args_set = [key for key,value in self.args.items() if \
1072
+ key.startswith('stop-') and value]
1073
+ util.info(f'Skipping DEPS run-after-tool commands due to args {args_set}')
1074
+ util.debug(f'Skipped commands: {filtered_dep_shell_commands=}')
1075
+ return
1076
+
1077
+ for i, d in enumerate(filtered_dep_shell_commands):
1033
1078
  util.info(f'run_dep_shell_commands {i=}: {d=}')
1034
1079
  clist = util.ShellCommandList(d['exec_list'])
1035
1080
  tee_fpath=clist.tee_fpath
1036
- if d['run_from_work_dir']:
1081
+ if d['attributes']['run-from-work-dir']:
1037
1082
  run_from_dir = self.args['work-dir']
1038
1083
  else:
1039
1084
  # Run from the target's directory (not the `eda` caller $PWD)
@@ -229,6 +229,7 @@ tools:
229
229
  riviera:
230
230
  defines:
231
231
  OC_TOOL_RIVIERA: 1
232
+ RIVIERA: 1
232
233
  log-bad-strings:
233
234
  - "Error:"
234
235
  log-must-strings:
@@ -292,7 +293,7 @@ tools:
292
293
  - 3009 # 3009: [TSCALE] - Module 'myname' does not have a timeunit/timeprecision
293
294
  # specification in effect, but other modules do.
294
295
  simulate-waves-args: |
295
- -voptargs=+acc=bcgnprst
296
+ -voptargs=+acc=bcnprst
296
297
 
297
298
 
298
299
  iverilog:
@@ -18,3 +18,14 @@ target_echo_hi_bye:
18
18
  target_test:
19
19
  deps: target_echo_hi_bye
20
20
  top: foo
21
+
22
+ target_test_with_post_tool_commands:
23
+ deps:
24
+ # In this test, we want to put a new command in the front of the ordered "deps" list,
25
+ # but with run-after-tool=true, so it should run after any of the normal pre-compile
26
+ # shell commands.
27
+ - commands:
28
+ - shell: echo "final goodbye"
29
+ run-after-tool: true
30
+ - target_echo_hi_bye
31
+ top: foo
opencos/tests/helpers.py CHANGED
@@ -13,6 +13,7 @@ from opencos import eda
13
13
  from opencos import deps_schema
14
14
  from opencos.utils.markup_helpers import yaml_safe_load
15
15
  from opencos.utils import status_constants
16
+ from opencos.utils.subprocess_helpers import subprocess_run_background
16
17
 
17
18
 
18
19
  def eda_wrap_is_sim_fail(rc: int, quiet: bool = False) -> bool:
@@ -157,6 +158,15 @@ class Helpers:
157
158
  DEFAULT_DIR = ''
158
159
  DEFAULT_LOG_DIR = os.getcwd()
159
160
  DEFAULT_LOG = os.path.join(DEFAULT_LOG_DIR, '.pytest.eda.log')
161
+
162
+ # How should the job run? subprocess? eda_wrap? eda.main?
163
+ # Note - if using eda.main, args like --debug will persist in opencos.util.args,
164
+ # so if you need those to be re-loaded, set RUN_IN_SUBPROCESS=True.
165
+ # Note - if you mess with os.enviorn, it may persist through subprocess.
166
+ RUN_IN_SUBPROCESS = True
167
+ USE_EDA_WRAP = True
168
+ PRESERVE_ENV = False
169
+
160
170
  def chdir(self):
161
171
  '''Changes directory to self.DEFAULT_DIR and removes eda.work, eda.export paths'''
162
172
  chdir_remove_work_dir('', self.DEFAULT_DIR)
@@ -173,7 +183,11 @@ class Helpers:
173
183
  ret = os.path.join(self.DEFAULT_LOG_DIR, right)
174
184
  return ret
175
185
 
176
- def log_it(self, command_str:str, logfile=None, use_eda_wrap=True) -> int:
186
+ def log_it(
187
+ self, command_str: str, logfile=None, use_eda_wrap: bool = True,
188
+ run_in_subprocess: bool = False,
189
+ preserve_env: bool = False
190
+ ) -> int:
177
191
  '''Replacement for calling eda.main or eda_wrap, when you want an internal logfile
178
192
 
179
193
  Usage:
@@ -183,6 +197,10 @@ class Helpers:
183
197
  Note this will run with --no-default-log to avoid a Windows problem with stomping
184
198
  on a log file.
185
199
  '''
200
+
201
+ if self.PRESERVE_ENV or preserve_env:
202
+ saved_env = os.environ.copy()
203
+
186
204
  logfile = self._resolve_logfile(logfile)
187
205
  rc = 50
188
206
 
@@ -191,13 +209,26 @@ class Helpers:
191
209
  # look at eda.work/{target}.sim/sim.log or xsim.log.
192
210
  print(f'{os.getcwd()=}')
193
211
  print(f'{command_str=}')
194
- with open(logfile, 'w', encoding='utf-8') as f:
195
- with redirect_stdout(f), redirect_stderr(f):
196
- if use_eda_wrap:
197
- rc = eda_wrap('--no-default-log', *(command_str.split()))
198
- else:
199
- rc = eda.main('--no-default-log', *(command_str.split()))
200
- print(f'Wrote: {os.path.abspath(logfile)=}')
212
+ if run_in_subprocess or self.RUN_IN_SUBPROCESS:
213
+ command_list = ['eda', '--no-default-log'] + command_str.split()
214
+ _, _, rc = subprocess_run_background(
215
+ work_dir=self.DEFAULT_DIR,
216
+ command_list=command_list,
217
+ background=True,
218
+ tee_fpath=logfile
219
+ )
220
+ else:
221
+ with open(logfile, 'w', encoding='utf-8') as f:
222
+ with redirect_stdout(f), redirect_stderr(f):
223
+ if use_eda_wrap or self.USE_EDA_WRAP:
224
+ rc = eda_wrap('--no-default-log', *(command_str.split()))
225
+ else:
226
+ rc = eda.main('--no-default-log', *(command_str.split()))
227
+ print(f'Wrote: {os.path.abspath(logfile)=}')
228
+
229
+ if self.PRESERVE_ENV or preserve_env:
230
+ os.environ = saved_env
231
+
201
232
  return rc
202
233
 
203
234
  def is_in_log(self, *want_str, logfile=None, windows_path_support=False):
@@ -216,33 +247,31 @@ class Helpers:
216
247
  '''gets all log lines with any of want_str args are in the logfile, or self.DEFAULT_LOG'''
217
248
  logfile = self._resolve_logfile(logfile)
218
249
  ret_list = []
219
- want_str0 = ' '.join(list(want_str))
220
- want_str1 = want_str0.replace('/', '\\')
221
250
  with open(logfile, encoding='utf-8') as f:
222
251
  for line in f.readlines():
223
- if want_str0 in line:
252
+ if any(x in line for x in list(want_str)):
224
253
  ret_list.append(line)
225
- elif windows_path_support and want_str1 in line:
254
+ elif windows_path_support and \
255
+ any(x.replace('/', '\\') in line for x in list(want_str)):
226
256
  ret_list.append(line)
227
257
  return ret_list
228
258
 
229
259
  def get_log_words_with(self, *want_str, logfile=None, windows_path_support=False):
230
- '''gets all log lines with any of *want_str within a single word
260
+ '''gets all log words with any of *want_str within a single word
231
261
  in the logfile or self.DEFAULT_LOG
232
262
  '''
233
263
  logfile = self._resolve_logfile(logfile)
234
264
  ret_list = []
235
- want_str0 = ' '.join(list(want_str))
236
- want_str1 = want_str0.replace('/', '\\')
237
265
  with open(logfile, encoding='utf-8') as f:
238
266
  for line in f.readlines():
239
- if want_str0 in line:
267
+ if any(x in line for x in list(want_str)):
240
268
  for word in line.split():
241
- if want_str0 in word:
269
+ if any(x in word for x in list(want_str)):
242
270
  ret_list.append(word)
243
- elif windows_path_support and want_str1 in line:
271
+ elif windows_path_support and \
272
+ any(x.replace('/', '\\') in line for x in list(want_str)):
244
273
  for word in line.split():
245
- if want_str1 in word:
274
+ if any(x.replace('/', '\\') in word for x in list(want_str)):
246
275
  ret_list.append(word)
247
276
 
248
277
  return ret_list
@@ -59,26 +59,26 @@ def test_get_all_targets_eda_multi():
59
59
 
60
60
  def test_parse_deps_shell_str__no_parse():
61
61
  line = 'some_file.sv'
62
- d = deps_commands.parse_deps_shell_str(line, '', '')
62
+ d = deps_commands.parse_deps_shell_str(line, '', '', attributes={})
63
63
  assert not d, f'{d=}'
64
64
 
65
65
  line = 'some_target:'
66
- d = deps_commands.parse_deps_shell_str(line, '', '')
66
+ d = deps_commands.parse_deps_shell_str(line, '', '', attributes={})
67
67
  assert not d, f'{d=}'
68
68
 
69
69
  line = ' csr@some_file.sv'
70
- d = deps_commands.parse_deps_shell_str(line, '', '')
70
+ d = deps_commands.parse_deps_shell_str(line, '', '', attributes={})
71
71
  assert not d, f'{d=}'
72
72
 
73
73
  def test_parse_deps_shell_str__cp():
74
74
  line = ' shell@ cp ./oclib_fifo_test.sv oclib_fifo_test_COPY.sv ;'
75
- d = deps_commands.parse_deps_shell_str(line, '', '')
75
+ d = deps_commands.parse_deps_shell_str(line, '', '', attributes={})
76
76
  assert d, f'{d=}'
77
77
  assert d['exec_list'] == ['cp', './oclib_fifo_test.sv', 'oclib_fifo_test_COPY.sv', ';'], f'{d=}'
78
78
 
79
79
  def test_parse_deps_shell_str__echo():
80
80
  line = ' shell@echo "hello world"'
81
- d = deps_commands.parse_deps_shell_str(line, '', '')
81
+ d = deps_commands.parse_deps_shell_str(line, '', '', attributes={})
82
82
  assert d, f'{d=}'
83
83
  assert d['exec_list'] == ['echo', '"hello', 'world"'], f'{d=}'
84
84
 
@@ -88,7 +88,9 @@ def test_parse_deps_shell_str__enable_filepath_replacement():
88
88
  module_dir = os.path.dirname(os.path.abspath(__file__))
89
89
  os.chdir(module_dir)
90
90
  line = 'shell@cp ../deps/deps_commands.py .pytest.copied.py'
91
- d = deps_commands.parse_deps_shell_str(line, target_path='./', target_node='foo_target')
91
+ d = deps_commands.parse_deps_shell_str(
92
+ line, target_path='./', target_node='foo_target', attributes={}
93
+ )
92
94
  assert d, f'{d=}'
93
95
  spath = os.path.abspath(os.path.join('..', 'deps', 'deps_commands.py'))
94
96
  assert d['exec_list'] == ['cp', spath, '.pytest.copied.py'], f'{d=}'
@@ -100,8 +102,10 @@ def test_parse_deps_shell_str__disable_filepath_replacement():
100
102
  module_dir = os.path.dirname(os.path.abspath(__file__))
101
103
  os.chdir(module_dir)
102
104
  line = 'shell@cp ../deps/deps_commands.py .pytest.copied.py'
103
- d = deps_commands.parse_deps_shell_str(line, target_path='./', target_node='foo_target',
104
- enable_filepath_subst_target_dir=False)
105
+ d = deps_commands.parse_deps_shell_str(
106
+ line, target_path='./', target_node='foo_target',
107
+ attributes={'filepath-subst-target-dir': False}
108
+ )
105
109
  assert d, f'{d=}'
106
110
  assert d['exec_list'] == ['cp', '../deps/deps_commands.py', '.pytest.copied.py'], f'{d=}'
107
111
  assert d['target_node'] == 'foo_target'
@@ -112,8 +116,10 @@ def test_parse_deps_shell_str__enable_dirpath_replacement():
112
116
  module_dir = os.path.dirname(os.path.abspath(__file__))
113
117
  os.chdir(module_dir)
114
118
  line = 'shell@ls -ltr ./'
115
- d = deps_commands.parse_deps_shell_str(line, target_path='./', target_node='foo_target',
116
- enable_dirpath_subst_target_dir=True)
119
+ d = deps_commands.parse_deps_shell_str(
120
+ line, target_path='./', target_node='foo_target',
121
+ attributes={'dirpath-subst-target-dir': True}
122
+ )
117
123
  assert d, f'{d=}'
118
124
  assert d['exec_list'] == ['ls', '-ltr', os.path.abspath('./')], f'{d=}'
119
125
  assert d['target_node'] == 'foo_target'
@@ -125,7 +131,10 @@ def test_parse_deps_shell_str__disable_dirpath_replacement():
125
131
  module_dir = os.path.dirname(os.path.abspath(__file__))
126
132
  os.chdir(module_dir)
127
133
  line = 'shell@ls -ltr ./'
128
- d = deps_commands.parse_deps_shell_str(line, target_path='./', target_node='foo_target')
134
+ d = deps_commands.parse_deps_shell_str(
135
+ line, target_path='./', target_node='foo_target',
136
+ attributes={}
137
+ )
129
138
  assert d, f'{d=}'
130
139
  assert d['exec_list'] == ['ls', '-ltr', './'], f'{d=}'
131
140
  assert d['target_node'] == 'foo_target'
@@ -134,26 +143,26 @@ def test_parse_deps_shell_str__disable_dirpath_replacement():
134
143
 
135
144
  def test_parse_deps_work_dir_add_srcs__no_parse():
136
145
  line = 'some_file.sv'
137
- d = deps_commands.parse_deps_work_dir_add_srcs(line, '', '')
146
+ d = deps_commands.parse_deps_work_dir_add_srcs(line, '', '', {})
138
147
  assert not d, f'{d=}'
139
148
 
140
149
  line = 'some_target:'
141
- d = deps_commands.parse_deps_work_dir_add_srcs(line, '', '')
150
+ d = deps_commands.parse_deps_work_dir_add_srcs(line, '', '', {})
142
151
  assert not d, f'{d=}'
143
152
 
144
153
  line = ' csr@some_file.sv'
145
- d = deps_commands.parse_deps_work_dir_add_srcs(line, '', '')
154
+ d = deps_commands.parse_deps_work_dir_add_srcs(line, '', '', {})
146
155
  assert not d, f'{d=}'
147
156
 
148
157
  def test_parse_deps_work_dir_add_srcs__single_file():
149
158
  line = ' work_dir_add_srcs@ single_file.txt'
150
- d = deps_commands.parse_deps_work_dir_add_srcs(line, '', '')
159
+ d = deps_commands.parse_deps_work_dir_add_srcs(line, '', '', {})
151
160
  assert d, f'{d=}'
152
161
  assert d['file_list'] == ['single_file.txt']
153
162
 
154
163
  def test_parse_deps_work_dir_add_srcs__several_file():
155
164
  line = ' work_dir_add_srcs@ single_file.txt another.sv gen-verilog/mine.v ./gen-vhdl/wordy.vhdl'
156
- d = deps_commands.parse_deps_work_dir_add_srcs(line, '', '')
165
+ d = deps_commands.parse_deps_work_dir_add_srcs(line, '', '', {})
157
166
  assert d, f'{d=}'
158
167
  assert d['file_list'] == [
159
168
  'single_file.txt', 'another.sv', 'gen-verilog/mine.v', './gen-vhdl/wordy.vhdl'
@@ -162,34 +171,37 @@ def test_parse_deps_work_dir_add_srcs__several_file():
162
171
 
163
172
  def test_parse_deps_peakrdl__no_parse():
164
173
  line = 'some_file.sv'
165
- d = deps_commands.parse_deps_peakrdl(line, '', '')
174
+ d = deps_commands.parse_deps_peakrdl(line, '', '', {})
166
175
  assert not d, f'{d=}'
167
176
 
168
177
  line = 'some_target:'
169
- d = deps_commands.parse_deps_peakrdl(line, '', '')
178
+ d = deps_commands.parse_deps_peakrdl(line, '', '', {})
170
179
  assert not d, f'{d=}'
171
180
 
172
181
  line = ' csr@some_file.sv'
173
- d = deps_commands.parse_deps_peakrdl(line, '', '')
182
+ d = deps_commands.parse_deps_peakrdl(line, '', '', {})
174
183
  assert not d, f'{d=}'
175
184
 
176
185
  def test_parse_deps_peakrdl__with_top():
177
186
  line = ' peakrdl@ --cpuif axi4-lite-flat --top my_fancy_csrs ./my_csrs.rdl'
178
- d = deps_commands.parse_deps_peakrdl(line, '', '')
187
+ d = deps_commands.parse_deps_peakrdl(line, '', '', {})
179
188
  assert d, f'{d=}'
180
189
  assert len(d['shell_commands_list']) > 0
181
- assert d['work_dir_add_srcs']['file_list'] == ['peakrdl/my_fancy_csrs_pkg.sv', 'peakrdl/my_fancy_csrs.sv']
190
+ assert d['work_dir_add_srcs']['file_list'] == ['peakrdl/my_fancy_csrs_pkg.sv',
191
+ 'peakrdl/my_fancy_csrs.sv']
182
192
 
183
193
  def test_parse_deps_peakrdl__with_top2():
184
194
  line = ' peakrdl@ --cpuif axi4-lite-flat --top=my_fancy_csrs ./my_csrs.rdl'
185
- d = deps_commands.parse_deps_peakrdl(line, '', '')
195
+ d = deps_commands.parse_deps_peakrdl(line, '', '', {})
186
196
  assert d, f'{d=}'
187
197
  assert len(d['shell_commands_list']) > 0
188
- assert d['work_dir_add_srcs']['file_list'] == ['peakrdl/my_fancy_csrs_pkg.sv', 'peakrdl/my_fancy_csrs.sv']
198
+ assert d['work_dir_add_srcs']['file_list'] == ['peakrdl/my_fancy_csrs_pkg.sv',
199
+ 'peakrdl/my_fancy_csrs.sv']
189
200
 
190
201
  def test_parse_deps_peakrdl__infer_top():
191
202
  line = ' peakrdl@ --cpuif axi4-lite-flat ./my_csrs.rdl'
192
- d = deps_commands.parse_deps_peakrdl(line, '', '')
203
+ d = deps_commands.parse_deps_peakrdl(line, '', '', {})
193
204
  assert d, f'{d=}'
194
205
  assert len(d['shell_commands_list']) > 0
195
- assert d['work_dir_add_srcs']['file_list'] == ['peakrdl/my_csrs_pkg.sv', 'peakrdl/my_csrs.sv']
206
+ assert d['work_dir_add_srcs']['file_list'] == ['peakrdl/my_csrs_pkg.sv',
207
+ 'peakrdl/my_csrs.sv']
opencos/tests/test_eda.py CHANGED
@@ -20,7 +20,6 @@ and should be more gracefully handled.
20
20
  import os
21
21
  import shutil
22
22
  import subprocess
23
- from contextlib import redirect_stdout, redirect_stderr
24
23
 
25
24
  import pytest
26
25
 
@@ -107,6 +106,7 @@ class TestsRequiresVerilator( # pylint: disable=too-many-public-methods
107
106
  assert rc == 0
108
107
 
109
108
 
109
+
110
110
  def test_args_sim_tool_with_path(self):
111
111
  '''Test for calling a tool as --tool=<tool>=</path/to/tool-exe>'''
112
112
  verilator_fullpath = shutil.which('verilator')
@@ -659,6 +659,31 @@ class TestsRequiresIVerilog(Helpers):
659
659
  assert rc == 0
660
660
 
661
661
 
662
+ @pytest.mark.skipif(not can_run_eda_sim(), reason='no tool found to handle command: sim')
663
+ class TestArgs(Helpers):
664
+ '''Test some args features, needs a sim tool'''
665
+ DEFAULT_DIR = os.path.join(THISPATH, '..', '..', 'lib', 'tests')
666
+
667
+ def test_duplicate_args(self):
668
+ '''Use oclib_fifo_test to make sure we don't lose (do NOT uniquify) duplicate
669
+ list-style args'''
670
+ self.chdir()
671
+ rc = self.log_it(
672
+ 'sim --stop-before-compile oclib_fifo_test --compile-args=-hi --compile-args=-hi',
673
+ use_eda_wrap=False
674
+ )
675
+ assert rc == 0
676
+ # Confirm we have two args in self.args['compile-args'] for: -hi
677
+ eda_config_yml_path = os.path.join(
678
+ os.getcwd(), 'eda.work', 'oclib_fifo_test.sim', 'eda_output_config.yml'
679
+ )
680
+ data = yaml_safe_load(eda_config_yml_path)
681
+ assert 'args' in data
682
+ assert 'compile-args' in data['args']
683
+ assert len(data['args']['compile-args']) == 2
684
+ assert data['args']['compile-args'] == ['-hi', '-hi']
685
+
686
+
662
687
  @pytest.mark.skipif(not can_run_eda_sim(), reason='no tool found to handle command: sim')
663
688
  class TestDepsReqs:
664
689
  '''Tests for 'reqs' in the DEPS files. 'reqs' are requirements, like a .pcap or file
@@ -708,65 +733,6 @@ class TestDepsReqs:
708
733
  assert rc > 1
709
734
 
710
735
 
711
- @pytest.mark.parametrize("command", ['sim', 'shell'])
712
- def test_deps_command_order(command):
713
- '''Test for various "commands" within a DEPS target. This test checks that command
714
- order is preserved in the top-to-bottom deps order, meaning that eda.py has to collect
715
- all commands deps order, and then execute them in that order.'''
716
-
717
- chdir_remove_work_dir('deps_files/command_order')
718
- if command == 'sim' and not can_run_eda_sim():
719
- pytest.skip(f'sim skipped, {can_run_eda_sim()=}')
720
- return # skip/pass
721
-
722
- if command == 'shell':
723
- cmd_list = 'shell target_test'.split()
724
- else:
725
- cmd_list = 'sim --stop-before-compile target_test'.split()
726
-
727
- with open('eda.log', 'w', encoding='utf-8') as f:
728
- with redirect_stdout(f):
729
- with redirect_stderr(f):
730
- rc = eda.main(*cmd_list)
731
-
732
- print(f'{rc=}')
733
- assert rc == 0
734
-
735
- # We should see "hi" before "bye" to confirm deps + command order is correct.
736
- # see ./deps_files/command_order/DEPS.yml - target = target_test
737
- found_str_list = [
738
- 'exec: echo "hi"',
739
- 'exec: echo "bye"',
740
- ]
741
- found_lines_list = [None, None]
742
-
743
- with open('eda.log', encoding='utf-8') as f:
744
- for lineno, line in enumerate(f.readlines()):
745
- line = line.rstrip()
746
- for idx,key in enumerate(found_str_list):
747
- if key in line:
748
- found_lines_list[idx] = lineno
749
-
750
- assert found_lines_list[0] # found hi
751
- assert found_lines_list[1] # found bye
752
- assert found_lines_list[0] < found_lines_list[1] # hi before bye
753
-
754
- # Added check, we redirected to create eda.log earlier to confirm the targets worked,
755
- # but as a general eda.py check, all shell commands should create their own
756
- # {target}__shell_0.log file:
757
- work_dir = os.path.join(
758
- THISPATH, 'deps_files', 'command_order', 'eda.work', f'target_test.{command}'
759
- )
760
- # Note that eda will write out the returncode INFO line to tee'd log files, so
761
- # there is more in the log file than "hi" or "bye".
762
- with open(os.path.join(work_dir, 'target_echo_hi__shell_0.log'), encoding='utf-8') as f:
763
- text = ' '.join(f.readlines()).strip()
764
- assert any(text.startswith(x) for x in ['hi', '"hi"', '\\"hi\\"'])
765
- # Added check, one of the targets uses a custom 'tee' file name, instead of the default log.
766
- with open(os.path.join(work_dir, 'custom_tee_echo_bye.log'), encoding='utf-8') as f:
767
- text = ''.join(f.readlines()).strip()
768
- assert any(text.startswith(x) for x in ['bye', '"bye"', '\\"bye\\"'])
769
-
770
736
 
771
737
  @pytest.mark.skipif('verilator' not in tools_loaded, reason="requires verilator")
772
738
  class TestDepsOtherMarkup:
@@ -32,6 +32,14 @@ class CommandSimModelsimAse(CommandSim, ToolModelsimAse):
32
32
  self.args.update({
33
33
  'tool': self._TOOL, # override
34
34
  'gui': False,
35
+ 'vopt': self.use_vopt,
36
+ })
37
+ self.args_help.update({
38
+ 'vopt': (
39
+ 'Boolean to enable/disable use of vopt step prior to vsim step'
40
+ ' Note that vopt args can be controlled with --elab-args=<value1>'
41
+ ' --elab-args=<value2> ...'
42
+ )
35
43
  })
36
44
 
37
45
  def set_tool_defines(self):
@@ -158,7 +166,7 @@ class CommandSimModelsimAse(CommandSim, ToolModelsimAse):
158
166
  'compile-waivers',
159
167
  [ #defaults:
160
168
  '2275', # 2275 - Existing package 'foo_pkg' will be overwritten.
161
- ]):
169
+ ]) + self.args['compile-waivers']:
162
170
  vlog_dot_f_lines += ['-suppress', str(waiver)]
163
171
 
164
172
  if self.args['gui'] or self.args['waves']:
@@ -252,15 +260,18 @@ class CommandSimModelsimAse(CommandSim, ToolModelsimAse):
252
260
 
253
261
  sim_plusargs_str = self._get_sim_plusargs_str()
254
262
  vsim_suppress_list_str = self._get_vsim_suppress_list_str()
263
+ vsim_ext_args = ' '.join(self.args.get('sim-args', []))
255
264
 
256
- voptargs_str = ""
265
+ voptargs_str = self.tool_config.get('elab-args', '')
266
+ voptargs_str += ' '.join(self.args.get('elab-args', []))
257
267
  if self.args['gui'] or self.args['waves']:
258
- voptargs_str = self.tool_config.get('simulate-waves-args', '+acc')
268
+ voptargs_str += ' ' + self.tool_config.get('simulate-waves-args', '+acc')
259
269
  util.artifacts.add_extension(
260
270
  search_paths=self.args['work-dir'], file_extension='wlf',
261
271
  typ='waveform', description='Modelsim/Questa Waveform WLF (Wave Log Format) file'
262
272
  )
263
273
 
274
+ # TODO(drew): support self.args['sim_libary'] (1 lists)
264
275
  vlog_do_lines = []
265
276
  vsim_do_lines = []
266
277
 
@@ -269,9 +280,6 @@ class CommandSimModelsimAse(CommandSim, ToolModelsimAse):
269
280
  voptargs_str += more_voptargs_str
270
281
 
271
282
 
272
- # TODO(drew): support self.args['sim_libary', 'elab-args', sim-args'] (3 lists)
273
- # to add to vsim_one_liner.
274
-
275
283
  vopt_one_liner = ""
276
284
  if self.use_vopt:
277
285
  vopt_one_liner = (
@@ -283,12 +291,12 @@ class CommandSimModelsimAse(CommandSim, ToolModelsimAse):
283
291
 
284
292
  vsim_one_liner = "vsim -onfinish stop" \
285
293
  + f" -sv_seed {self.args['seed']} {sim_plusargs_str} {vsim_suppress_list_str}" \
286
- + f" opt__{self.args['top']}"
294
+ + f" {vsim_ext_args} opt__{self.args['top']}"
287
295
  else:
288
296
  # vopt doesn't exist, use single vsim call after vlog call:
289
297
  vsim_one_liner = "vsim -onfinish stop" \
290
298
  + f" -sv_seed {self.args['seed']} {sim_plusargs_str} {vsim_suppress_list_str}" \
291
- + f" {voptargs_str} work.{self.args['top']}"
299
+ + f" {voptargs_str} {vsim_ext_args} work.{self.args['top']}"
292
300
 
293
301
 
294
302
  vsim_one_liner = vsim_one_liner.replace('\n', ' ')
@@ -393,7 +401,7 @@ class CommandSimModelsimAse(CommandSim, ToolModelsimAse):
393
401
  #defaults:
394
402
  '3009', # 3009: [TSCALE] - Module 'foo' does not have a timeunit/timeprecision
395
403
  # specification in effect, but other modules do.
396
- ]):
404
+ ]) + self.args['sim-waivers']:
397
405
  vsim_suppress_list += ['-suppress', str(waiver)]
398
406
 
399
407
  return ' '.join(vsim_suppress_list)