Commit 14c91fb7 authored by Martin Jeřábek's avatar Martin Jeřábek

Merge branch 'testfw-update' into 'master'

testfw: wave-opt, ...

Closes #284

See merge request !243
parents 27d09104 8f52cbb5
Pipeline #6967 passed with stage
in 48 seconds
......@@ -24,6 +24,9 @@ if [ -d /build ]; then
else
d="$(realpath "$(dirname "$0")")"
#"
docker run --rm -ti -e DISPLAY=$DISPLAY -v /tmp/.X11-unix:/tmp/.X11-unix -v "$d:/build" --user $(id -u):$(id -g) registry.gitlab.com/canfd/server-tools/ghdl:gtkwave /build/run-docker-test "$@"
docker run --rm -ti -e DISPLAY=$DISPLAY -v /tmp/.X11-unix:/tmp/.X11-unix \
-v "$d:/build" --user $(id -u):$(id -g) --memory 6G \
registry.gitlab.com/canfd/server-tools/ghdl:gtkwave \
/build/run-docker-test "$@"
fi
......@@ -16,6 +16,12 @@ Features:
* `gtkw`: a path to GTKW file with waveform layout definition for gtkwave; if
set together with `wave`, this takes precedence. The specified gtkw
file is not modified.
* `dump_all_signals`: If true, dump all signals in GUI mode, not only these
included in the layout file. May be overriden by
`--dumpall` commandline option. By default, it is set to
true, but for long-lasting tests with lots of signals it
may be necessary to set it to false to prevent `gtkwave`
to run out of memory while loading waveforms.
* many more
## Using waveform layout files
......@@ -23,8 +29,12 @@ Features:
* Specify the file in YML config, either as `gtkw` or `wave` (tcl). Later, this
might be extended to native gtkw-generating python files.
* Run the tests with `--create-ghws`. This generates signal and type hierarchy.
You should run this each time you modify a signal in the layout (or add a signal both to code and to layout).
You should run this each time you modify a signal in the layout (or add a
signal both to code and to layout).
* Run in gui mode, using the VUnit `-g` flag.
* If a layout file is specified and `dump_all_signals` is false (and
`--dumpall` is not used), only the signals specified in the layout file are
dumped.
# How it works
......
This diff is collapsed.
......@@ -27,7 +27,7 @@ setup_logging()
from . import vunit_ifc
from . import test_unit, test_sanity, test_feature, test_reference
from vunit.ui import VUnit
from .test_common import add_common_sources, add_flags
from .test_common import add_common_sources, get_compile_options
#-------------------------------------------------------------------------------
......@@ -80,10 +80,12 @@ def create():
@click.option('--strict', 'strict', flag_value=1,
help='Return non-zero if an unconfigured test was found.')
@click.option('--no-strict', 'strict', flag_value=0)
@click.option('--dumpall', is_flag=True, flag_value=True, default=False,
help='In GUI mode, dump all signals, not only these included in layout file.')
@click.option('--create-ghws/--no-create-ghws', default=False,
help='Only elaborate and create basic GHW files necessary for converting TCL layout files to GTKW files for gtkwave..')
@click.pass_obj
def test(obj, *, config, strict, create_ghws, vunit_args):
def test(obj, *, config, strict, create_ghws, dumpall, vunit_args):
"""Run the tests. Configuration is passed in YAML config file.
You mas pass arguments directly to VUnit by appending them at the command end.
......@@ -143,14 +145,20 @@ def test(obj, *, config, strict, create_ghws, vunit_args):
tests = []
for cfg_key, factory in tests_classes:
if cfg_key in config:
tests.append(factory(ui, lib, config[cfg_key], build, base, create_ghws=create_ghws))
tests.append(factory(ui, lib, config[cfg_key], build, base,
create_ghws=create_ghws,
force_unrestricted_dump_signals=dumpall))
(func_cov_dir / "html").mkdir(parents=True, exist_ok=True)
(func_cov_dir / "coverage_data").mkdir(parents=True, exist_ok=True)
for t in tests:
t.add_sources()
add_flags(ui, lib, build)
c = get_compile_options()
for k, v in c.items():
lib.set_compile_option(k, v)
conf_ok = [t.configure() for t in tests]
# check for unknown tests
......@@ -198,27 +206,3 @@ def vunit_run(ui, build, out_basename) -> int:
f.write(c)
out.unlink()
return res
"""
+ vunit configurations
+ pass modelsim gui file via ui.set_sim_option("modelsim.init_file.gui", ...)
+ include the standard library files in ui.set_sim_option("modelsim.init_files.after_load", [...])
+ set TCOMP global variable
- allow preprocessed calls to log()
- use some log from vunit?
- use random from unit?
+ use per-test default configurations (with set tcl files etc.), different sanity configurations
x pass encoded composite generics (sanity test)
+ use watchdog - pass the time in config: test_runner_watchdog(runner, 10 ms);
- bash completion for files & tests:
- click._bashcompletion.get_choices -> extend the if to check if the given argument is an instance of XXX
and implement completion method for that instance. Complete test names.
- feature tests
- sanity - optimize bus delay shift registers
"""
from vcd.gtkw import GTKWSave
import tkinter
from typing import List
from typing import List, Set
import logging
import traceback
import functools
......@@ -25,6 +25,7 @@ def logexc(f):
class TclFuncs:
def __init__(self, gtkw: str, hierarchy):
self.gtkw = gtkw
self.used_signals = set() # type: Set[str]
self.hierarchy = hierarchy
# set up TCL
......@@ -65,6 +66,13 @@ class TclFuncs:
fqn = 'top.' + fqn
return fqn.replace('(', '[').replace(')', ']').lower()
def convsig_wave_opt(self, sig: str) -> str:
sig = re.sub(r'__([0-9]+)', r'(\1)', sig)
sig = re.sub(r'\([^)]+\)', '', sig)
if sig[0] != '/':
sig = '/'+sig
return sig
def _add_trace(self, signal, type, *, label: str, datafmt: str, expand: bool, **kwds):
if ghw_parse.is_record(type):
with self.gtkw.group(label, closed=not expand):
......@@ -72,6 +80,7 @@ class TclFuncs:
# do not pass label
self._add_trace(signal+'/'+iname, itype, datafmt=datafmt, expand=False, label=None, **kwds)
else:
self.used_signals.add(self.convsig_wave_opt(signal))
signal = self.convsig(signal)
self.gtkw.trace(signal, alias=label, datafmt=datafmt, **kwds)
......@@ -170,7 +179,7 @@ class TclFuncs:
self.gtkw.end_group(o.group)
def tcl2gtkw(tcl_wave, tcl_init_files: List[str], gtkw, ghw: Path):
def tcl2gtkw(tcl_wave, tcl_init_files: List[str], gtkw, ghw: Path) -> List[str]:
hierarchy = ghw_parse.parse(ghw)
with open(gtkw, 'wt') as f:
gtkw = GTKWSave(f)
......@@ -183,3 +192,5 @@ def tcl2gtkw(tcl_wave, tcl_init_files: List[str], gtkw, ghw: Path):
c.tcl.createcommand('run_simulation', lambda: None)
c.source(tcl_wave)
c.finalize()
used_signals = sorted(c.used_signals)
return used_signals
vunit_hdl
#vunit_hdl
git+git://github.com/mjerabek/vunit@ghdl-gtkwave#egg=vunit_hdl
pyvcd
attrs
jinja2
parsy
pyyaml
click
yattag
json2html
This diff is collapsed.
import logging
from pathlib import Path
from .test_common import add_sources, TestsBase, dict_merge, \
get_common_modelsim_init_files, get_seed
get_seed, OptionsDict
from textwrap import dedent
import re
......@@ -26,25 +26,14 @@ class FeatureTests(TestsBase):
tb = self.lib.get_test_benches('*tb_feature')[0]
tb.scan_tests_from_file(str(wrname))
def create_psl_cov_file_opt(self, name):
psl_path = "functional_coverage/coverage_data/psl_cov_feature_{}.json".format(name)
psl_flag = "--psl-report={}".format(psl_path)
return {"ghdl.sim_flags" : [psl_flag]}
def configure(self) -> bool:
tb = self.lib.get_test_benches('*tb_feature')[0]
default = self.config['default']
sim_options = self.get_default_sim_options()
# generate & set per-test modelsim tcl file
tcl = self.build / 'modelsim_init_feature.tcl'
with tcl.open('wt', encoding='utf-8') as f:
print(dedent('''\
global TCOMP
set TCOMP tb_feature/test_comp
'''), file=f)
init_files = get_common_modelsim_init_files()
init_files += [str(tcl)]
tb.set_sim_option("modelsim.init_files.after_load", init_files)
sim_options += self.generate_init_tcl('modelsim_init_feature.tcl', 'tb_feature/test_comp')
sim_options += self.add_modelsim_gui_file(tb, default, 'feature', sim_options['modelsim.init_files.after_load'])
for name, cfg in self.config['tests'].items():
if cfg is None:
......@@ -64,12 +53,13 @@ class FeatureTests(TestsBase):
'seed' : get_seed(cfg)
}
if (cfg['psl_coverage']):
psl_opts = self.create_psl_cov_file_opt(name)
tb.add_config(name, generics=generics, sim_options=psl_opts)
else:
tb.add_config(name, generics=generics)
self.add_modelsim_gui_file(tb, default, 'feature', init_files)
local_sim_options = OptionsDict()
if cfg['psl_coverage']:
local_sim_options += self.add_psl_cov('{}.{}'.format(tb.name, name))
local_sim_options = sim_options + local_sim_options
tb.add_config(name, generics=generics, sim_options=local_sim_options)
return self._check_for_unconfigured()
def _check_for_unconfigured(self) -> bool:
......
This diff is collapsed.
import logging
from pathlib import Path
from .test_common import add_sources, TestsBase, dict_merge, \
get_common_modelsim_init_files, get_seed
from textwrap import dedent
get_seed, OptionsDict
log = logging.getLogger(__name__)
......@@ -18,28 +17,20 @@ class ReferenceTests(TestsBase):
sources.append('reference/vunit_reference_wrapper.vhd')
add_sources(self.lib, sources)
def create_psl_cov_file_opt(self, name):
psl_path = "functional_coverage/coverage_data/psl_cov_reference_{}.json".format(name)
psl_flag = "--psl-report={}".format(psl_path)
return {"ghdl.sim_flags" : [psl_flag]}
def configure(self) -> bool:
tb = self.lib.get_test_benches('*reference*')[0]
default = self.config['default']
tcl = self.build / 'modelsim_init_reference.tcl'
with tcl.open('wt', encoding='utf-8') as f:
print(dedent('''\
global TCOMP
set TCOMP tb_reference_wrapper/i_test
'''), file=f)
# TODO: is this necessary?
tb.scan_tests_from_file(str(self.base / "reference/vunit_reference_wrapper.vhd"))
init_files = get_common_modelsim_init_files()
init_files += [str(tcl)]
sim_options = self.get_default_sim_options()
# generate & set per-test modelsim tcl file
sim_options += self.generate_init_tcl('modelsim_init_reference.tcl', 'tb_reference_wrapper/i_test')
sim_options += self.add_modelsim_gui_file(tb, default, 'reference', sim_options['modelsim.init_files.after_load'])
for data_set, cfg in self.config['tests'].items():
dict_merge(cfg, default)
# bm = len_to_matrix(cfg['topology'], cfg['bus_len_v'])
generics = {
'timeout' : cfg['timeout'],
'iterations' : cfg['iterations'],
......@@ -48,13 +39,10 @@ class ReferenceTests(TestsBase):
'seed' : get_seed(cfg),
'data_path' : str(self.build) + '/../' + cfg['data_path'],
}
local_sim_options = OptionsDict()
if cfg['psl_coverage']:
local_sim_options += self.add_psl_cov('{}.{}'.format(tb.name, data_set))
local_sim_options = sim_options + local_sim_options
tb.add_config(data_set, generics=generics, sim_options=local_sim_options)
if (cfg['psl_coverage']):
psl_opts = self.create_psl_cov_file_opt(data_set)
tb.add_config(data_set, generics=generics, sim_options=psl_opts)
else:
tb.add_config(data_set, generics=generics)
tb.set_sim_option("modelsim.init_files.after_load", init_files)
self.add_modelsim_gui_file(tb, default, 'reference', init_files)
return True
import logging
from textwrap import dedent
from .test_common import TestsBase, add_sources, dict_merge, vhdl_serialize, \
get_seed, get_common_modelsim_init_files
get_seed, OptionsDict
log = logging.getLogger(__name__)
def len_to_matrix(topology, bus_len):
l = bus_len
if topology == 'bus':
bm = [[0.0, l[1], l[1]+l[2], l[1]+l[2]+l[3]],
[l[1], 0.0, l[2], l[2]+l[3]],
[l[1]+l[2], l[2], 0.0, l[3]],
[l[1]+l[2]+l[3], l[2]+l[3], l[3], 0.0]]
elif topology == 'star':
bm = [[0.0, l[1]+l[2], l[1]+l[3], l[1]+l[4]],
[l[1]+l[2], 0.0, l[2]+l[3], l[2]+l[4]],
[l[1]+l[3], l[2]+l[3], 0.0, l[3]+l[4]],
[l[1]+l[4], l[2]+l[4], l[3]+l[4], 0.0]]
elif topology == 'tree':
bm = [[0.0, l[1]+l[2], l[1]+l[3]+l[5], l[1]+l[4]+l[5]],
[l[1]+l[2], 0.0, l[2]+l[3]+l[5], l[2]+l[4]+l[5]],
[l[1]+l[3]+l[5], l[2]+l[3]+l[5], 0.0, l[3]+l[4]],
[l[1]+l[4]+l[5], l[2]+l[4]+l[5], l[3]+l[4], 0.0]]
elif topology == 'ring':
bm = [[0.0, min(l[1], l[2]+l[3]+l[4]), min(l[1]+l[2],l[3]+l[4]), min(l[4],l[1]+l[2]+l[3])],
[min(l[1], l[2]+l[3]+l[4]), 0.0, min(l[2], l[1]+l[3]+l[4]), min(l[2]+l[3],l[1]+l[4])],
[min(l[1]+l[2],l[3]+l[4]), min(l[2], l[1]+l[3]+l[4]), 0.0, min(l[3],l[1]+l[2]+l[4])],
[min(l[4],l[1]+l[2]+l[3]), min(l[2]+l[3],l[1]+l[4]), min(l[3],l[1]+l[2]+l[4]), 0.0]]
elif topology == 'custom':
bm = [[0.0, l[1], l[2], l[3]],
[l[1], 0.0, l[4], l[5]],
[l[2], l[4], 0.0, l[6]],
[l[3], l[6], l[6], 0.0]]
else:
raise ValueError("Invalid bus topology.")
return bm
class SanityTests(TestsBase):
def add_sources(self):
add_sources(self.lib, ['sanity/**/*.vhd'])
......@@ -48,25 +15,14 @@ class SanityTests(TestsBase):
valid_name = valid_name.replace("/", "_")
return valid_name
def create_psl_cov_file_opt(self, name):
test_name = "psl_cov_sanity_{}.json".format(self.format_valid_test_name(name))
psl_path = "functional_coverage/coverage_data/{}".format(test_name)
psl_flag = "--psl-report={}".format(psl_path)
return {"ghdl.sim_flags" : [psl_flag]}
def configure(self):
tb = self.lib.get_test_benches('*tb_sanity')[0]
default = self.config['default']
tcl = self.build / 'modelsim_init_sanity.tcl'
with tcl.open('wt', encoding='utf-8') as f:
print(dedent('''\
global TCOMP
set TCOMP tb_sanity
'''), file=f)
init_files = get_common_modelsim_init_files()
init_files += [str(tcl)]
sim_options = self.get_default_sim_options()
# generate & set per-test modelsim tcl file
sim_options += self.generate_init_tcl('modelsim_init_sanity.tcl', 'tb_sanity')
sim_options += self.add_modelsim_gui_file(tb, default, 'sanity', sim_options['modelsim.init_files.after_load'])
for name, cfg in self.config['tests'].items():
if 'wave' in cfg:
......@@ -74,7 +30,6 @@ class SanityTests(TestsBase):
' (set it in default instead)'.format(name))
dict_merge(cfg, default)
# bm = len_to_matrix(cfg['topology'], cfg['bus_len_v'])
generics = {
'timeout' : cfg['timeout'],
'iterations' : cfg['iterations'],
......@@ -94,13 +49,9 @@ class SanityTests(TestsBase):
'gauss_iter' : vhdl_serialize(cfg['gauss_iter']),
}
sanity_cfg_name = name.replace(" ", "_").replace("/", "_").strip('"')
local_sim_options = OptionsDict()
if cfg['psl_coverage']:
psl_opts = self.create_psl_cov_file_opt(name)
tb.add_config(name, generics=generics, sim_options=psl_opts)
else:
tb.add_config(name, generics=generics)
self.add_modelsim_gui_file(tb, default, 'sanity', init_files)
local_sim_options += self.add_psl_cov('{}.{}'.format(tb.name, name))
local_sim_options = sim_options + local_sim_options
tb.add_config(name, generics=generics, sim_options=local_sim_options)
return True
......@@ -2,7 +2,7 @@ import re
import logging
from textwrap import dedent
from .test_common import add_sources, dict_merge, TestsBase, \
get_common_modelsim_init_files, get_seed
get_seed, OptionsDict
from pprint import pprint
log = logging.getLogger(__name__)
......@@ -13,15 +13,14 @@ class UnitTests(TestsBase):
add_sources(self.lib, ['unit/**/*.vhd'])
self._create_wrapper(self.build / "tb_wrappers.vhd")
def add_psl_cov_file(self, tb, name):
psl_path = "functional_coverage/coverage_data/psl_cov_unit_{}.json".format(name)
psl_flag = "--psl-report={}".format(psl_path)
tb.set_sim_option("ghdl.sim_flags", [psl_flag])
def configure(self) -> bool:
lib, config, build = self.lib, self.config, self.build
default = config['default']
unit_tests = lib.get_test_benches('*_unit_test')
for ut in unit_tests:
ut.scan_tests_from_file(str(build / "../unit/vunittb_wrapper.vhd"))
for name, cfg in config['tests'].items():
dict_merge(cfg, default)
tb = lib.get_test_benches('*tb_{}_unit_test'.format(name),
......@@ -39,19 +38,15 @@ class UnitTests(TestsBase):
tb.set_generic('error_tol', cfg['error_tolerance'])
tb.set_generic('seed', get_seed(cfg))
sim_options = self.get_default_sim_options()
# generate & set per-test modelsim tcl file
tcl = build / 'modelsim_init_{}.tcl'.format(name)
with tcl.open('wt', encoding='utf-8') as f:
print(dedent('''\
global TCOMP
set TCOMP tb_{}_unit_test/tb/i_test
'''.format(name)), file=f)
init_files = get_common_modelsim_init_files()
init_files += [str(tcl)]
tb.set_sim_option("modelsim.init_files.after_load", init_files)
if (cfg['psl_coverage']):
self.add_psl_cov_file(tb, name)
self.add_modelsim_gui_file(tb, cfg, name, tcl_init_files=init_files)
sim_options += self.generate_init_tcl('modelsim_init_{}.tcl'.format(name), 'tb_{}_unit_test/tb/i_test'.format(name))
sim_options += self.add_modelsim_gui_file(tb, cfg, name, sim_options['modelsim.init_files.after_load'])
if cfg['psl_coverage']:
sim_options += self.add_psl_cov(tb.name)
self.set_sim_options(tb, sim_options)
return self._check_for_unconfigured()
def _check_for_unconfigured(self) -> bool:
......
......@@ -5,6 +5,8 @@ _default: &default
psl_coverage: false
# seed: 0 # optional; use to reconstruct results from randomized runs
# randomize: false
# In GUI mode, dump all signals, not only these included in layout file.
dump_all_signals: true
feature:
default:
<<: *default
......
......@@ -4,6 +4,8 @@ _default: &default
error_tolerance: 0
# seed: 0 # optional; use to reconstruct results from randomized runs
# randomize: false
# In GUI mode, dump all signals, not only these included in layout file.
dump_all_signals: true
unit:
default:
<<: *default
......@@ -109,6 +111,7 @@ sanity:
timeout: 2 sec
gauss_iter: 40
wave: sanity/sanity_env_setup.tcl
dump_all_signals: false
tests:
"1Mb/10Mb 20 m Star":
topology: star
......
......@@ -3,6 +3,8 @@ _default: &default
psl_coverage: true
error_tolerance: 0
randomize: true
# In GUI mode, dump all signals, not only these included in layout file.
dump_all_signals: true
unit:
default:
<<: *default
......@@ -109,6 +111,7 @@ sanity:
timeout: 2 sec
gauss_iter: 40
wave: sanity/sanity_env_setup.tcl
dump_all_signals: false
tests:
"1Mb/10Mb 20 m Star":
topology: star
......
......@@ -3,6 +3,8 @@ _default: &default
error_tolerance: 0
# seed: 0 # optional; use to reconstruct results from randomized runs
# randomize: false
# In GUI mode, dump all signals, not only these included in layout file.
dump_all_signals: true
reference:
default:
<<: *default
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment