blob: c7096d26286decee60aceef7d22757f7cb5aec3c [file] [log] [blame]
Srikrishna Iyer09a81e92019-12-30 10:47:57 -08001#!/usr/bin/env python3
2# Copyright lowRISC contributors.
3# Licensed under the Apache License, Version 2.0, see LICENSE for details.
4# SPDX-License-Identifier: Apache-2.0
5r"""
6Classes
7"""
8
9import logging as log
10import pprint
11import re
12import sys
13
14import hjson
15
16from .utils import *
17
18
19class Modes():
20 """
21 Abstraction for specifying collection of options called as 'modes'. This is
22 the base class which is extended for run_modes, build_modes, tests and regressions.
23 """
24 def self_str(self):
25 '''
26 This is used to construct the string representation of the entire class object.
27 '''
28 tname = ""
29 if self.type != "": tname = self.type + "_"
30 if self.mname != "": tname += self.mname
31 if log.getLogger().isEnabledFor(VERBOSE):
32 return "\n<---" + tname + ":\n" + pprint.pformat(self.__dict__) + \
33 "\n--->\n"
34 else:
35 return tname + ":" + self.name
36
37 def __str__(self):
38 return self.self_str()
39
40 def __repr__(self):
41 return self.self_str()
42
43 def __init__(self, mdict):
44 keys = mdict.keys()
45 attrs = self.__dict__.keys()
46
47 if not 'name' in keys:
48 log.error("Key \"name\" missing in mode %s", mdict)
49 sys.exit(1)
50
51 if not hasattr(self, "type"):
52 log.fatal("Key \"type\" is missing or invalid")
53 sys.exit(1)
54
55 if not hasattr(self, "mname"): self.mname = ""
56
57 for key in keys:
58 if key not in attrs:
59 log.error("Key %s in %s is invalid", key, mdict)
60 sys.exit(1)
61 setattr(self, key, mdict[key])
62
63 def get_sub_modes(self):
64 sub_modes = []
65 if hasattr(self, "en_" + self.type + "_modes"):
66 sub_modes = getattr(self, "en_" + self.type + "_modes")
67 return sub_modes
68
69 def set_sub_modes(self, sub_modes):
70 setattr(self, "en_" + self.type + "_modes", sub_modes)
71
72 def merge_mode(self, mode):
73 '''
74 Merge a new mode with self.
75 Merge sub mode specified with 'en_*_modes with self.
76 '''
77
78 sub_modes = self.get_sub_modes()
79 is_sub_mode = mode.name in sub_modes
80
81 if not mode.name == self.name and not is_sub_mode:
82 return False
83
84 # only merge the lists; if strs are different, then throw an error
85 attrs = self.__dict__.keys()
86 for attr in attrs:
87 # merge lists together
88 self_attr_val = getattr(self, attr)
89 mode_attr_val = getattr(mode, attr)
90
91 if type(self_attr_val) is list:
92 self_attr_val.extend(mode_attr_val)
93 setattr(self, attr, self_attr_val)
94
95 elif not is_sub_mode or attr not in ["name", "mname"]:
96 self.check_conflict(mode.name, attr, mode_attr_val)
97
98 # Check newly appended sub_modes, remove 'self' and duplicates
99 sub_modes = self.get_sub_modes()
100
101 if sub_modes != []:
102 new_sub_modes = []
103 for sub_mode in sub_modes:
104 if not self.name == sub_mode and not sub_mode in new_sub_modes:
105 new_sub_modes.append(sub_mode)
106 self.set_sub_modes(new_sub_modes)
107 return True
108
109 def check_conflict(self, name, attr, mode_attr_val):
110 self_attr_val = getattr(self, attr)
111 if self_attr_val == mode_attr_val: return
112
113 default_val = None
114 if type(self_attr_val) is int:
115 default_val = -1
116 elif type(self_attr_val) is str:
117 default_val = ""
118
119 if self_attr_val != default_val and mode_attr_val != default_val:
120 log.error(
121 "mode %s cannot be merged into %s due to conflicting %s {%s, %s}",
122 name, self.name, attr, str(self_attr_val), str(mode_attr_val))
123 sys.exit(1)
124 elif self_attr_val == default_val:
125 self_attr_val = mode_attr_val
126 setattr(self, attr, self_attr_val)
127
128 @staticmethod
129 def create_modes(ModeType, mdicts):
130 '''
131 Create modes of type ModeType from a given list of raw dicts
132 Process dependencies.
133 Return a list of modes objects.
134 '''
135 def merge_sub_modes(mode, parent, objs):
136 # Check if there are modes available to merge
137 sub_modes = mode.get_sub_modes()
138 if sub_modes == []: return
139
140 # Set parent if it is None. If not, check cyclic dependency
141 if parent is None:
142 parent = mode
143 else:
144 if mode.name == parent.name:
145 log.error("Cyclic dependency when processing mode \"%s\"",
146 mode.name)
147 sys.exit(1)
148
149 for sub_mode in sub_modes:
150 # Find the sub_mode obj from str
151 found = False
152 for obj in objs:
153 if sub_mode == obj.name:
154 # First recursively merge the sub_modes
155 merge_sub_modes(obj, parent, objs)
156
157 # Now merge the sub mode with mode
158 mode.merge_mode(obj)
159 found = True
160 break
161 if not found:
162 log.error(
163 "Sub mode \"%s\" added to mode \"%s\" was not found!",
164 sub_mode, mode.name)
165 sys.exit(1)
166
167 modes_objs = []
168 # create a default mode if available
169 default_mode = ModeType.get_default_mode()
170 if default_mode is not None: modes_objs.append(default_mode)
171
172 # Process list of raw dicts that represent the modes
173 # Pass 1: Create unique set of modes by merging modes with the same name
174 for mdict in mdicts:
175 # Create a new item
176 new_mode_merged = False
177 new_mode = ModeType(mdict)
178 for mode in modes_objs:
179 # Merge new one with existing if available
180 if mode.name == new_mode.name:
181 mode.merge_mode(new_mode)
182 new_mode_merged = True
183 break
184
185 # Add the new mode to the list if not already appended
186 if not new_mode_merged:
187 modes_objs.append(new_mode)
188 ModeType.item_names.append(new_mode.name)
189
190 # Pass 2: Recursively expand sub modes within parent modes
191 for mode in modes_objs:
192 merge_sub_modes(mode, None, modes_objs)
193
194 # Return the list of objects
195 return modes_objs
196
197 @staticmethod
198 def get_default_mode(ModeType):
199 return None
200
201 @staticmethod
202 def find_mode(mode_name, modes):
203 '''
204 Given a mode_name in string, go through list of modes and return the mode with
205 the string that matches. Thrown an error and return None if nothing was found.
206 '''
207 found = False
208 for mode in modes:
209 if mode_name == mode.name:
210 return mode
211 return None
212
213 @staticmethod
214 def find_and_merge_modes(mode, mode_names, modes, merge_modes=True):
215 '''
216 '''
217 found_mode_objs = []
218 for mode_name in mode_names:
219 sub_mode = Modes.find_mode(mode_name, modes)
220 if sub_mode is not None:
221 found_mode_objs.append(sub_mode)
222 if merge_modes is True: mode.merge_mode(sub_mode)
223 else:
224 log.error("Mode \"%s\" enabled within mode \"%s\" not found!",
225 mode_name, mode.name)
226 sys.exit(1)
227 return found_mode_objs
228
229
230class BuildModes(Modes):
231 """
232 Build modes.
233 """
234
235 # Maintain a list of build_modes str
236 item_names = []
237
238 def __init__(self, bdict):
239 self.name = ""
240 self.type = "build"
241 if not hasattr(self, "mname"): self.mname = "mode"
242 self.is_sim_mode = 0
243 self.build_opts = []
244 self.run_opts = []
245 self.en_build_modes = []
246
247 super().__init__(bdict)
248 self.en_build_modes = list(set(self.en_build_modes))
249
250 @staticmethod
251 def get_default_mode():
252 return BuildModes({"name": "default"})
253
254
255class RunModes(Modes):
256 """
257 Run modes.
258 """
259
260 # Maintain a list of run_modes str
261 item_names = []
262
263 def __init__(self, rdict):
264 self.name = ""
265 self.type = "run"
266 if not hasattr(self, "mname"): self.mname = "mode"
267 self.reseed = -1
268 self.run_opts = []
269 self.uvm_test = ""
270 self.uvm_test_seq = ""
271 self.build_mode = ""
272 self.en_run_modes = []
273 self.sw_dir = ""
274 self.sw_name = ""
275
276 super().__init__(rdict)
277 self.en_run_modes = list(set(self.en_run_modes))
278
279 @staticmethod
280 def get_default_mode():
281 return None
282
283
284class Tests(RunModes):
285 """
286 Abstraction for tests. The RunModes abstraction can be reused here with a few
287 modifications.
288 """
289
290 # Maintain a list of tests str
291 item_names = []
292
293 # TODO: This info should be passed via hjson
294 defaults = {
295 "reseed": -1,
296 "uvm_test": "",
297 "uvm_test_seq": "",
298 "build_mode": ""
299 }
300
301 def __init__(self, tdict):
302 if not hasattr(self, "mname"): self.mname = "test"
303 super().__init__(tdict)
304
305 @staticmethod
306 def create_tests(tdicts, sim_cfg):
307 '''
308 Create Tests from a given list of raw dicts.
309 TODO: enhance the raw dict to include file scoped defaults.
310 Process enabled run modes and the set build mode.
311 Return a list of test objects.
312 '''
313 def get_pruned_en_run_modes(test_en_run_modes, global_en_run_modes):
314 pruned_en_run_modes = []
315 for test_en_run_mode in test_en_run_modes:
316 if test_en_run_mode not in global_en_run_modes:
317 pruned_en_run_modes.append(test_en_run_mode)
318 return pruned_en_run_modes
319
320 tests_objs = []
321 # Pass 1: Create unique set of tests by merging tests with the same name
322 for tdict in tdicts:
323 # Create a new item
324 new_test_merged = False
325 new_test = Tests(tdict)
326 for test in tests_objs:
327 # Merge new one with existing if available
328 if test.name == new_test.name:
329 test.merge_mode(new_test)
330 new_test_merged = True
331 break
332
333 # Add the new test to the list if not already appended
334 if not new_test_merged:
335 tests_objs.append(new_test)
336 Tests.item_names.append(new_test.name)
337
338 # Pass 2: Process dependencies
339 build_modes = []
340 if hasattr(sim_cfg, "build_modes"):
341 build_modes = getattr(sim_cfg, "build_modes")
342
343 run_modes = []
344 if hasattr(sim_cfg, "run_modes"):
345 run_modes = getattr(sim_cfg, "run_modes")
346
347 attrs = Tests.defaults
348 for test_obj in tests_objs:
349 # Unpack run_modes first
350 en_run_modes = get_pruned_en_run_modes(test_obj.en_run_modes,
351 sim_cfg.en_run_modes)
352 Modes.find_and_merge_modes(test_obj, en_run_modes, run_modes)
353
354 # Find and set the missing attributes from sim_cfg
355 # If not found in sim_cfg either, then throw a warning
356 # TODO: These should be file-scoped
357 for attr in attrs.keys():
358 # Check if attr value is default
359 val = getattr(test_obj, attr)
360 default_val = attrs[attr]
361 if val == default_val:
362 global_val = None
363 # Check if we can find a default in sim_cfg
364 if hasattr(sim_cfg, attr):
365 global_val = getattr(sim_cfg, attr)
366
367 if global_val is not None and global_val != default_val:
368 setattr(test_obj, attr, global_val)
369
370 else:
371 # TODO: should we even enforce this?
372 log.error(
373 "Required value \"%s\" for the test \"%s\" is missing",
374 attr, test_obj.name)
375 sys, exit(1)
376
377 # Unpack the build mode for this test
378 build_mode_objs = Modes.find_and_merge_modes(test_obj,
379 [test_obj.build_mode],
380 build_modes,
381 merge_modes=False)
382 test_obj.build_mode = build_mode_objs[0]
383
384 # Error if set build mode is actually a sim mode
385 if test_obj.build_mode.is_sim_mode is True:
386 log.error(
387 "Test \"%s\" uses build_mode %s which is actually a sim mode",
388 test_obj.name, test_obj.build_mode.name)
389 sys.exit(1)
390
391 # Merge build_mode's run_opts with self
392 test_obj.run_opts.extend(test_obj.build_mode.run_opts)
393
394 # Return the list of tests
395 return tests_objs
396
397 @staticmethod
398 def merge_global_opts(tests, global_build_opts, global_run_opts):
399 processed_build_modes = []
400 for test in tests:
401 if test.build_mode.name not in processed_build_modes:
402 test.build_mode.build_opts.extend(global_build_opts)
403 processed_build_modes.append(test.build_mode.name)
404 test.run_opts.extend(global_run_opts)
405
406
407class Regressions(Modes):
408 """
409 Abstraction for test sets / regression sets.
410 """
411
412 # Maintain a list of tests str
413 item_names = []
414
415 # TODO: define __repr__ and __str__ to print list of tests if VERBOSE
416
417 def __init__(self, regdict):
418 self.name = ""
419 self.type = ""
420 if not hasattr(self, "mname"): self.mname = "regression"
421 self.tests = []
422 self.reseed = -1
423 self.test_names = []
424 self.excl_tests = [] # TODO: add support for this
425 self.en_sim_modes = []
426 self.en_run_modes = []
427 self.build_opts = []
428 self.run_opts = []
429 super().__init__(regdict)
430
431 @staticmethod
432 def create_regressions(regdicts, sim_cfg, tests):
433 '''
434 Create Test sets from a given list of raw dicts.
435 Return a list of test set objects.
436 '''
437
438 regressions_objs = []
439 # Pass 1: Create unique set of test sets by merging test sets with the same name
440 for regdict in regdicts:
441 # Create a new item
442 new_regression_merged = False
443 new_regression = Regressions(regdict)
444
445 # Check for name conflicts with tests before merging
446 if new_regression.name in Tests.item_names:
447 log.error("Test names and regression names are required to be unique. " + \
448 "The regression \"%s\" bears the same name with an existing test. ",
449 new_regression.name)
450 sys.exit(1)
451
452 for regression in regressions_objs:
453 # Merge new one with existing if available
454 if regression.name == new_regression.name:
455 regression.merge_mode(new_regression)
456 new_regression_merged = True
457 break
458
459 # Add the new test to the list if not already appended
460 if not new_regression_merged:
461 regressions_objs.append(new_regression)
462 Regressions.item_names.append(new_regression.name)
463
464 # Pass 2: Process dependencies
465 build_modes = []
466 if hasattr(sim_cfg, "build_modes"):
467 build_modes = getattr(sim_cfg, "build_modes")
468
469 run_modes = []
470 if hasattr(sim_cfg, "run_modes"):
471 run_modes = getattr(sim_cfg, "run_modes")
472
473 for regression_obj in regressions_objs:
474 # Unpack the sim modes
475 found_sim_mode_objs = Modes.find_and_merge_modes(
476 regression_obj, regression_obj.en_sim_modes, build_modes,
477 False)
478
479 for sim_mode_obj in found_sim_mode_objs:
480 if sim_mode_obj.is_sim_mode == 0:
481 log.error(
482 "Enabled mode \"%s\" within the regression \"%s\" is not a sim mode",
483 sim_mode_obj.name, regression_obj.name)
484 sys.exit(1)
485
486 # Check if sim_mode_obj's sub-modes are a part of regressions's
487 # sim modes- if yes, then it will cause duplication of opts
488 # Throw an error and exit.
489 for sim_mode_obj_sub in sim_mode_obj.en_build_modes:
490 if sim_mode_obj_sub in regression_obj.en_sim_modes:
491 log.error("Regression \"%s\" enables sim_modes \"%s\" and \"%s\". " + \
492 "The former is already a sub_mode of the latter.",
493 regression_obj.name, sim_mode_obj_sub, sim_mode_obj.name)
494 sys.exit(1)
495
496 # Check if sim_mode_obj is also passed on the command line, in
497 # which case, skip
498 if sim_mode_obj.name in sim_cfg.en_build_modes:
499 continue
500
501 # Merge the build and run opts from the sim modes
502 regression_obj.build_opts.extend(sim_mode_obj.build_opts)
503 regression_obj.run_opts.extend(sim_mode_obj.run_opts)
504
505 # Unpack the run_modes
506 # TODO: If there are other params other than run_opts throw an error and exit
507 found_run_mode_objs = Modes.find_and_merge_modes(
508 regression_obj, regression_obj.en_run_modes, run_modes, False)
509
510 # Only merge the run_opts from the run_modes enabled
511 for run_mode_obj in found_run_mode_objs:
512 # Check if run_mode_obj is also passed on the command line, in
513 # which case, skip
514 if run_mode_obj.name in sim_cfg.en_run_modes:
515 continue
516 self.run_opts.extend(run_mode_obj.run_opts)
517
518 # Unpack tests
519 if regression_obj.tests == []:
520 log.log(VERBOSE,
521 "Unpacking all tests in scope for regression \"%s\"",
522 regression_obj.name)
523 regression_obj.tests = sim_cfg.tests
524 regression_obj.test_names = Tests.item_names
525
526 else:
527 tests_objs = []
528 regression_obj.test_names = regression_obj.tests
529 for test in regression_obj.tests:
530 test_obj = Modes.find_mode(test, sim_cfg.tests)
531 if test_obj is None:
532 log.error(
533 "Test \"%s\" added to regression \"%s\" not found!",
534 test, regression_obj.name)
535 sys.exit(1)
536 tests_objs.append(test_obj)
537 regression_obj.tests = tests_objs
538
539 # Return the list of tests
540 return regressions_objs
541
542 def merge_regression_opts(self):
543 processed_build_modes = []
544 for test in self.tests:
545 if test.build_mode.name not in processed_build_modes:
546 test.build_mode.build_opts.extend(self.build_opts)
547 processed_build_modes.append(test.build_mode.name)
548 test.run_opts.extend(self.run_opts)
549
550 # Override reseed if available.
551 if self.reseed != -1:
552 test.reseed = self.reseed