Reorganize how tb infrstructure selects toolchains

This commit is contained in:
Alex Mykyta
2023-10-22 11:04:43 -07:00
parent 683fc4d0ac
commit d689bb7077
24 changed files with 323 additions and 179 deletions

View File

@@ -52,9 +52,7 @@ jobs:
- name: Test - name: Test
run: | run: |
cd tests cd tests
export SKIP_SYNTH_TESTS=1 pytest --cov=peakrdl_regblock --synth-tool skip --sim-tool stub
export STUB_SIMULATOR=1
pytest --cov=peakrdl_regblock
- name: Coveralls - name: Coveralls
env: env:

1
.gitignore vendored
View File

@@ -7,6 +7,7 @@
**/_build **/_build
**/*.out **/*.out
**/transcript **/transcript
**/htmlcov
**/*.log **/*.log
**/*.pb **/*.pb
**/.Xil **/.Xil

View File

@@ -30,11 +30,6 @@ To run synthesis tests, Vivado needs to be installed and visible via the PATH en
Vivado can be downloaded for free from: https://www.xilinx.com/support/download.html Vivado can be downloaded for free from: https://www.xilinx.com/support/download.html
To skip synthesis tests, export the following environment variable:
```bash
export SKIP_SYNTH_TESTS=1
```
## Python Packages ## Python Packages
@@ -62,6 +57,13 @@ You can also run a specific testcase. For example:
pytest tests/test_hw_access pytest tests/test_hw_access
``` ```
Command-line arguments can be used to explicitly select which simulator/synthesis tools are used
If unspecified, the tool will be selected automatically based on what you have installed.
```bash
pytest --sim-tool questa --synth-tool vivado
```
Alternatively, launch tests using the helper script. This handles installing Alternatively, launch tests using the helper script. This handles installing
dependencies into a virtual environment automatically. dependencies into a virtual environment automatically.
```bash ```bash

25
tests/conftest.py Normal file
View File

@@ -0,0 +1,25 @@
def pytest_addoption(parser):
parser.addoption(
"--sim-tool",
choices=["questa", "xilinx", "stub", "skip", "auto"],
default="auto",
help="""
Select the simulator to use.
stub: run the testcase using a no-op simulator stub
skip: skip all the simulation tests
auto: choose the best simulator based on what is installed
"""
)
parser.addoption(
"--synth-tool",
choices=["vivado", "skip", "auto"],
default="auto",
help="""
Select the synthesis tool to use.
skip: skip all the simulation tests
auto: choose the best tool based on what is installed
"""
)

View File

@@ -1,4 +1,4 @@
from typing import Optional, List from typing import Optional
import unittest import unittest
import os import os
import glob import glob
@@ -49,41 +49,37 @@ class BaseTestCase(unittest.TestCase):
def _load_request(self, request): def _load_request(self, request):
self.request = request self.request = request
@classmethod def get_testcase_dir(self) -> str:
def get_testcase_dir(cls) -> str: class_dir = os.path.dirname(inspect.getfile(self.__class__))
class_dir = os.path.dirname(inspect.getfile(cls))
return class_dir return class_dir
@classmethod def get_run_dir(self) -> str:
def get_run_dir(cls) -> str: this_dir = self.get_testcase_dir()
this_dir = cls.get_testcase_dir() run_dir = os.path.join(this_dir, "run.out", self.__class__.__name__)
run_dir = os.path.join(this_dir, "run.out", cls.__name__)
return run_dir return run_dir
@classmethod def _write_params(self) -> None:
def _write_params(cls) -> None:
""" """
Write out the class parameters to a file so that it is easier to debug Write out the class parameters to a file so that it is easier to debug
how a testcase was parameterized how a testcase was parameterized
""" """
path = os.path.join(cls.get_run_dir(), "params.txt") path = os.path.join(self.get_run_dir(), "params.txt")
with open(path, 'w') as f: with open(path, 'w') as f:
for k, v in cls.__dict__.items(): for k, v in self.__class__.__dict__.items():
if k.startswith("_") or callable(v): if k.startswith("_") or callable(v):
continue continue
f.write(f"{k}: {repr(v)}\n") f.write(f"{k}: {repr(v)}\n")
@classmethod def _export_regblock(self):
def _export_regblock(cls):
""" """
Call the peakrdl_regblock exporter to generate the DUT Call the peakrdl_regblock exporter to generate the DUT
""" """
this_dir = cls.get_testcase_dir() this_dir = self.get_testcase_dir()
if cls.rdl_file: if self.rdl_file:
rdl_file = cls.rdl_file rdl_file = self.rdl_file
else: else:
# Find any *.rdl file in testcase dir # Find any *.rdl file in testcase dir
rdl_file = glob.glob(os.path.join(this_dir, "*.rdl"))[0] rdl_file = glob.glob(os.path.join(this_dir, "*.rdl"))[0]
@@ -98,45 +94,33 @@ class BaseTestCase(unittest.TestCase):
rdlc.compile_file(udp_file) rdlc.compile_file(udp_file)
rdlc.compile_file(rdl_file) rdlc.compile_file(rdl_file)
root = rdlc.elaborate(cls.rdl_elab_target, "regblock", cls.rdl_elab_params) root = rdlc.elaborate(self.rdl_elab_target, "regblock", self.rdl_elab_params)
cls.exporter.export( self.exporter.export(
root, root,
cls.get_run_dir(), self.get_run_dir(),
module_name="regblock", module_name="regblock",
package_name="regblock_pkg", package_name="regblock_pkg",
cpuif_cls=cls.cpuif.cpuif_cls, cpuif_cls=self.cpuif.cpuif_cls,
retime_read_fanin=cls.retime_read_fanin, retime_read_fanin=self.retime_read_fanin,
retime_read_response=cls.retime_read_response, retime_read_response=self.retime_read_response,
reuse_hwif_typedefs=cls.reuse_hwif_typedefs, reuse_hwif_typedefs=self.reuse_hwif_typedefs,
retime_external_reg=cls.retime_external, retime_external_reg=self.retime_external,
retime_external_regfile=cls.retime_external, retime_external_regfile=self.retime_external,
retime_external_mem=cls.retime_external, retime_external_mem=self.retime_external,
retime_external_addrmap=cls.retime_external, retime_external_addrmap=self.retime_external,
default_reset_activelow=cls.default_reset_activelow, default_reset_activelow=self.default_reset_activelow,
default_reset_async=cls.default_reset_async, default_reset_async=self.default_reset_async,
) )
@classmethod def setUp(self) -> None:
def setUpClass(cls):
# Create fresh build dir # Create fresh build dir
run_dir = cls.get_run_dir() run_dir = self.get_run_dir()
if os.path.exists(run_dir): if os.path.exists(run_dir):
shutil.rmtree(run_dir) shutil.rmtree(run_dir)
pathlib.Path(run_dir).mkdir(parents=True, exist_ok=True) pathlib.Path(run_dir).mkdir(parents=True, exist_ok=True)
cls._write_params() self._write_params()
# Convert testcase RDL file --> SV # Convert testcase RDL file --> SV
cls._export_regblock() self._export_regblock()
def setUp(self) -> None:
# cd into the run directory
self.original_cwd = os.getcwd()
os.chdir(self.get_run_dir())
def run_test(self, plusargs:List[str] = None) -> None:
simulator = self.simulator_cls(testcase_cls_inst=self)
simulator.run(plusargs)

View File

@@ -66,7 +66,7 @@ class CpuifTestMode:
return self._get_file_paths("rtl_files") return self._get_file_paths("rtl_files")
def get_tb_inst(self, tb_cls: 'SimTestCase', exporter: 'RegblockExporter') -> str: def get_tb_inst(self, testcase: 'SimTestCase', exporter: 'RegblockExporter') -> str:
class_dir = self._get_class_dir_of_variable("tb_template") class_dir = self._get_class_dir_of_variable("tb_template")
loader = jj.FileSystemLoader(class_dir) loader = jj.FileSystemLoader(class_dir)
jj_env = jj.Environment( jj_env = jj.Environment(
@@ -77,7 +77,7 @@ class CpuifTestMode:
context = { context = {
"cpuif": self, "cpuif": self,
"cls": tb_cls, "testcase": testcase,
"exporter": exporter, "exporter": exporter,
"type": type, "type": type,
} }

View File

@@ -1,24 +1,21 @@
from typing import List from typing import List
import os import os
import jinja2 as jj import jinja2 as jj
import pytest
from .sv_line_anchor import SVLineAnchor from .sv_line_anchor import SVLineAnchor
from .simulators.questa import Questa from .simulators import get_simulator_cls
from .simulators import StubSimulator
from .base_testcase import BaseTestCase from .base_testcase import BaseTestCase
SIM_CLS = Questa
if os.environ.get("STUB_SIMULATOR", False):
SIM_CLS = StubSimulator
class SimTestCase(BaseTestCase): class SimTestCase(BaseTestCase):
#: Abort test if it exceeds this number of clock cycles #: Abort test if it exceeds this number of clock cycles
timeout_clk_cycles = 5000 timeout_clk_cycles = 5000
simulator_cls = SIM_CLS incompatible_sim_tools = set()
tb_template_file = "tb_template.sv" tb_template_file = "tb_template.sv"
@@ -32,17 +29,14 @@ class SimTestCase(BaseTestCase):
clocking_hwif_in = True clocking_hwif_in = True
clocking_hwif_out = True clocking_hwif_out = True
def get_extra_tb_files(self) -> List[str]:
@classmethod
def get_extra_tb_files(cls) -> List[str]:
paths = [] paths = []
for path in cls.extra_tb_files: for path in self.extra_tb_files:
path = os.path.join(cls.get_testcase_dir(), path) path = os.path.join(self.get_testcase_dir(), path)
paths.append(path) paths.append(path)
return paths return paths
@classmethod def _generate_tb(self):
def _generate_tb(cls):
""" """
Render the testbench template into actual tb.sv Render the testbench template into actual tb.sv
""" """
@@ -57,32 +51,38 @@ class SimTestCase(BaseTestCase):
) )
context = { context = {
"cls": cls, "testcase": self,
"exporter": cls.exporter, "exporter": self.exporter,
} }
# template path needs to be relative to the Jinja loader root # template path needs to be relative to the Jinja loader root
template_path = os.path.join(cls.get_testcase_dir(), cls.tb_template_file) template_path = os.path.join(self.get_testcase_dir(), self.tb_template_file)
template_path = os.path.relpath(template_path, template_root_path) template_path = os.path.relpath(template_path, template_root_path)
template = jj_env.get_template(template_path) template = jj_env.get_template(template_path)
output_path = os.path.join(cls.get_run_dir(), "tb.sv") output_path = os.path.join(self.get_run_dir(), "tb.sv")
stream = template.stream(context) stream = template.stream(context)
stream.dump(output_path) stream.dump(output_path)
@classmethod def setUp(self):
def setUpClass(cls): name = self.request.config.getoption("--sim-tool")
super().setUpClass() if name in self.incompatible_sim_tools:
pytest.skip()
simulator_cls = get_simulator_cls(name)
if simulator_cls is None:
pytest.skip()
super().setUp()
# Create testbench from template # Create testbench from template
cls._generate_tb() self._generate_tb()
simulator = cls.simulator_cls(testcase_cls=cls) simulator = simulator_cls(self)
# cd into the build directory # cd into the build directory
cwd = os.getcwd() cwd = os.getcwd()
os.chdir(cls.get_run_dir()) os.chdir(self.get_run_dir())
try: try:
simulator.compile() simulator.compile()
finally: finally:
@@ -91,5 +91,16 @@ class SimTestCase(BaseTestCase):
def run_test(self, plusargs:List[str] = None) -> None: def run_test(self, plusargs:List[str] = None) -> None:
simulator = self.simulator_cls(testcase_cls_inst=self) name = self.request.config.getoption("--sim-tool")
simulator.run(plusargs) simulator_cls = get_simulator_cls(name)
simulator = simulator_cls(self)
# cd into the build directory
cwd = os.getcwd()
os.chdir(self.get_run_dir())
try:
simulator.run(plusargs)
finally:
# cd back
os.chdir(cwd)

View File

@@ -1,34 +1,37 @@
from typing import Type, TYPE_CHECKING, List from typing import Type, Optional, List
import functools
if TYPE_CHECKING: from .base import Simulator
from ..sim_testcase import SimTestCase from .questa import Questa
from .xilinx import Xilinx
from .stub import StubSimulator
class Simulator: ALL_SIMULATORS: List[Simulator]
ALL_SIMULATORS = [
Questa,
Xilinx,
StubSimulator,
]
def __init__(self, testcase_cls: 'Type[SimTestCase]' = None, testcase_cls_inst: 'SimTestCase' = None) -> None: @functools.lru_cache()
self.testcase_cls = testcase_cls def get_simulator_cls(name: str) -> Optional[Type[Simulator]]:
self.testcase_cls_inst = testcase_cls_inst if name == "skip":
return None
@property if name == "auto":
def tb_files(self) -> List[str]: # Find the first simulator that is installed
files = [] for sim_cls in ALL_SIMULATORS:
files.extend(self.testcase_cls.cpuif.get_sim_files()) if sim_cls is StubSimulator:
files.extend(self.testcase_cls.get_extra_tb_files()) # Never offer the stub as an automatic option
files.append("regblock_pkg.sv") continue
files.append("regblock.sv") if sim_cls.is_installed():
files.append("tb.sv") return sim_cls
raise ValueError("Could not find any installed simulators")
return files # Look up which explicit simulator name was specified
for sim_cls in ALL_SIMULATORS:
def compile(self) -> None: if sim_cls.name == name:
raise NotImplementedError if not sim_cls.is_installed():
raise ValueError("Simulator '%s' is not installed" % sim_cls.name)
def run(self, plusargs:List[str] = None) -> None: return sim_cls
raise NotImplementedError raise RuntimeError
class StubSimulator(Simulator):
def compile(self) -> None:
pass
def run(self, plusargs:List[str] = None) -> None:
pass

View File

@@ -0,0 +1,31 @@
from typing import TYPE_CHECKING, List
if TYPE_CHECKING:
from ..sim_testcase import SimTestCase
class Simulator:
name = ""
@classmethod
def is_installed(cls) -> bool:
raise NotImplementedError
def __init__(self, testcase: 'SimTestCase' = None) -> None:
self.testcase = testcase
@property
def tb_files(self) -> List[str]:
files = []
files.extend(self.testcase.cpuif.get_sim_files())
files.extend(self.testcase.get_extra_tb_files())
files.append("regblock_pkg.sv")
files.append("regblock.sv")
files.append("tb.sv")
return files
def compile(self) -> None:
raise NotImplementedError
def run(self, plusargs:List[str] = None) -> None:
raise NotImplementedError

View File

@@ -1,10 +1,20 @@
from typing import List from typing import List
import subprocess import subprocess
import os import os
import shutil
from . import Simulator from .base import Simulator
class Questa(Simulator): class Questa(Simulator):
name = "questa"
@classmethod
def is_installed(cls) -> bool:
return (
shutil.which("vlog") is not None
and shutil.which("vsim") is not None
)
def compile(self) -> None: def compile(self) -> None:
cmd = [ cmd = [
"vlog", "-sv", "-quiet", "-l", "build.log", "vlog", "-sv", "-quiet", "-l", "build.log",
@@ -31,7 +41,7 @@ class Questa(Simulator):
def run(self, plusargs:List[str] = None) -> None: def run(self, plusargs:List[str] = None) -> None:
plusargs = plusargs or [] plusargs = plusargs or []
test_name = self.testcase_cls_inst.request.node.name test_name = self.testcase.request.node.name
# call vsim # call vsim
cmd = [ cmd = [
@@ -54,11 +64,11 @@ class Questa(Simulator):
def assertSimLogPass(self, path: str): def assertSimLogPass(self, path: str):
self.testcase_cls_inst.assertTrue(os.path.isfile(path)) self.testcase.assertTrue(os.path.isfile(path))
with open(path, encoding="utf-8") as f: with open(path, encoding="utf-8") as f:
for line in f: for line in f:
if line.startswith("# ** Error"): if line.startswith("# ** Error"):
self.testcase_cls_inst.fail(line) self.testcase.fail(line)
elif line.startswith("# ** Fatal"): elif line.startswith("# ** Fatal"):
self.testcase_cls_inst.fail(line) self.testcase.fail(line)

View File

@@ -0,0 +1,17 @@
from typing import List
from .base import Simulator
class StubSimulator(Simulator):
name = "stub"
@classmethod
def is_installed(cls) -> bool:
# Always available!
return True
def compile(self) -> None:
pass
def run(self, plusargs: List[str] = None) -> None:
pass

View File

@@ -1,22 +1,34 @@
from typing import List from typing import List
import subprocess import subprocess
import os import os
import shutil
from . import Simulator from .base import Simulator
class Xilinx(Simulator): class Xilinx(Simulator):
""" """
Don't bother using the Xilinx simulator... Its buggy and extraordinarily slow. Don't bother using the Xilinx simulator... Its buggy and extraordinarily slow.
As observed in v2021.1: As observed in v2023.2:
- clocking block assignments do not seem to actually simulate correctly. - clocking block assignments do not seem to actually simulate correctly.
assignment statements get ignored or the values get mangled. assignment statements get ignored or the values get mangled.
- Streaming operators have all sorts of limitations. - Streaming operators have all sorts of limitations.
Keeping this here in case someday it works better... Keeping this here in case someday it works better...
""" """
name = "xilinx"
@classmethod
def is_installed(cls) -> bool:
return (
shutil.which("xvlog") is not None
and shutil.which("xelab") is not None
and shutil.which("xsim") is not None
)
def compile(self) -> None: def compile(self) -> None:
cmd = [ cmd = [
"xvlog", "--sv", "xvlog", "--sv",
"--log", "compile.log",
"--include", os.path.join(os.path.dirname(__file__), ".."), "--include", os.path.join(os.path.dirname(__file__), ".."),
"--define", "XSIM", "--define", "XSIM",
] ]
@@ -25,6 +37,7 @@ class Xilinx(Simulator):
cmd = [ cmd = [
"xelab", "xelab",
"--log", "elaborate.log",
"--timescale", "1ns/1ps", "--timescale", "1ns/1ps",
"--debug", "all", "--debug", "all",
"tb", "tb",
@@ -35,7 +48,7 @@ class Xilinx(Simulator):
def run(self, plusargs:List[str] = None) -> None: def run(self, plusargs:List[str] = None) -> None:
plusargs = plusargs or [] plusargs = plusargs or []
test_name = self.testcase_cls_inst.request.node.name test_name = self.testcase.request.node.name
# call vsim # call vsim
cmd = [ cmd = [
@@ -54,13 +67,13 @@ class Xilinx(Simulator):
def assertSimLogPass(self, path: str): def assertSimLogPass(self, path: str):
self.testcase_cls_inst.assertTrue(os.path.isfile(path)) self.testcase.assertTrue(os.path.isfile(path))
with open(path, encoding="utf-8") as f: with open(path, encoding="utf-8") as f:
for line in f: for line in f:
if line.startswith("Error:"): if line.startswith("Error:"):
self.testcase_cls_inst.fail(line) self.testcase.fail(line)
elif line.startswith("Fatal:"): elif line.startswith("Fatal:"):
self.testcase_cls_inst.fail(line) self.testcase.fail(line)
elif line.startswith("FATAL_ERROR:"): elif line.startswith("FATAL_ERROR:"):
self.testcase_cls_inst.fail(line) self.testcase.fail(line)

View File

@@ -1,12 +1,11 @@
from typing import List from typing import List
import subprocess
import os import os
import pytest import pytest
from .base_testcase import BaseTestCase from .base_testcase import BaseTestCase
from .synthesizers import get_synthesizer_cls
@pytest.mark.skipif(os.environ.get("SKIP_SYNTH_TESTS", False), reason="user skipped")
class SynthTestCase(BaseTestCase): class SynthTestCase(BaseTestCase):
def _get_synth_files(self) -> List[str]: def _get_synth_files(self) -> List[str]:
@@ -17,20 +16,24 @@ class SynthTestCase(BaseTestCase):
return files return files
def setUp(self) -> None:
name = self.request.config.getoption("--synth-tool")
synth_cls = get_synthesizer_cls(name)
if synth_cls is None:
pytest.skip()
super().setUp()
def run_synth(self) -> None: def run_synth(self) -> None:
script = os.path.join( name = self.request.config.getoption("--synth-tool")
os.path.dirname(__file__), synth_cls = get_synthesizer_cls(name)
"synthesis/vivado/run.tcl" synth = synth_cls(self)
)
cmd = [ # cd into the build directory
"vivado", "-nojournal", "-notrace", cwd = os.getcwd()
"-mode", "batch", os.chdir(self.get_run_dir())
"-log", "out.log",
"-source", script,
"-tclargs"
]
cmd.extend(self._get_synth_files())
subprocess.run(cmd, check=True) try:
synth.run()
finally:
# cd back
os.chdir(cwd)

View File

@@ -0,0 +1,30 @@
from typing import List, Optional, Type
import functools
from .base import Synthesizer
from .vivado import Vivado
ALL_SYNTHESIZERS: List[Synthesizer]
ALL_SYNTHESIZERS = [
Vivado,
]
@functools.lru_cache()
def get_synthesizer_cls(name: str) -> Optional[Type[Synthesizer]]:
if name == "skip":
return None
if name == "auto":
# Find the first tool that is installed
for synth_cls in ALL_SYNTHESIZERS:
if synth_cls.is_installed():
return synth_cls
raise ValueError("Could not find any installed synthesis tools")
# Look up which explicit synth tool name was specified
for synth_cls in ALL_SYNTHESIZERS:
if synth_cls.name == name:
if not synth_cls.is_installed():
raise ValueError("Synthesis tool '%s' is not installed" % synth_cls.name)
return synth_cls
raise RuntimeError

View File

@@ -0,0 +1,17 @@
from typing import TYPE_CHECKING, List
if TYPE_CHECKING:
from ..synth_testcase import SynthTestCase
class Synthesizer:
name = ""
@classmethod
def is_installed(cls) -> bool:
raise NotImplementedError
def __init__(self, testcase: 'SynthTestCase' = None) -> None:
self.testcase = testcase
def run(self) -> None:
raise NotImplementedError

View File

@@ -0,0 +1,29 @@
import os
import subprocess
import shutil
from .base import Synthesizer
class Vivado(Synthesizer):
name = "vivado"
@classmethod
def is_installed(cls) -> bool:
return shutil.which("vivado") is not None
def run(self) -> None:
script = os.path.join(
os.path.dirname(__file__),
"vivado_scripts/run.tcl"
)
cmd = [
"vivado", "-nojournal", "-notrace",
"-mode", "batch",
"-log", "out.log",
"-source", script,
"-tclargs"
]
cmd.extend(self.testcase._get_synth_files())
subprocess.run(cmd, check=True)

View File

@@ -40,11 +40,11 @@ module tb;
default clocking cb @(posedge clk); default clocking cb @(posedge clk);
default input #1step output #1; default input #1step output #1;
output rst; output rst;
{%- if exporter.hwif.has_input_struct and cls.clocking_hwif_in %} {%- if exporter.hwif.has_input_struct and testcase.clocking_hwif_in %}
output hwif_in; output hwif_in;
{%- endif %} {%- endif %}
{%- if exporter.hwif.has_output_struct and cls.clocking_hwif_out %} {%- if exporter.hwif.has_output_struct and testcase.clocking_hwif_out %}
input hwif_out; input hwif_out;
{%- endif %} {%- endif %}
@@ -61,7 +61,7 @@ module tb;
//-------------------------------------------------------------------------- //--------------------------------------------------------------------------
// CPUIF // CPUIF
//-------------------------------------------------------------------------- //--------------------------------------------------------------------------
{{cls.cpuif.get_tb_inst(cls, exporter)|indent}} {{testcase.cpuif.get_tb_inst(testcase, exporter)|indent}}
//-------------------------------------------------------------------------- //--------------------------------------------------------------------------
// DUT // DUT
@@ -93,7 +93,7 @@ module tb;
//-------------------------------------------------------------------------- //--------------------------------------------------------------------------
initial begin initial begin
cb.rst <= '1; cb.rst <= '1;
{%- if exporter.hwif.has_input_struct and cls.init_hwif_in %} {%- if exporter.hwif.has_input_struct and testcase.init_hwif_in %}
cb.hwif_in <= '{default: '0}; cb.hwif_in <= '{default: '0};
{%- endif %} {%- endif %}
@@ -112,8 +112,8 @@ module tb;
// Monitor for timeout // Monitor for timeout
//-------------------------------------------------------------------------- //--------------------------------------------------------------------------
initial begin initial begin
##{{cls.timeout_clk_cycles}}; ##{{testcase.timeout_clk_cycles}};
$fatal(1, "Test timed out after {{cls.timeout_clk_cycles}} clock cycles"); $fatal(1, "Test timed out after {{testcase.timeout_clk_cycles}} clock cycles");
end end
endmodule endmodule

View File

@@ -19,10 +19,7 @@ pip install -U .
cd $this_dir cd $this_dir
# Run unit tests # Run unit tests
export SKIP_SYNTH_TESTS=1 pytest --workers auto --cov=peakrdl_regblock --synth-tool skip
#export STUB_SIMULATOR=1
export NO_XSIM=1
pytest --workers auto --cov=peakrdl_regblock
# Generate coverage report # Generate coverage report
coverage html -i -d $this_dir/htmlcov coverage html -i -d $this_dir/htmlcov

View File

@@ -1,5 +1,6 @@
from ..lib.sim_testcase import SimTestCase from ..lib.sim_testcase import SimTestCase
class Test(SimTestCase): class Test(SimTestCase):
incompatible_sim_tools = {"xilinx"}
def test_dut(self): def test_dut(self):
self.run_test() self.run_test()

View File

@@ -1,5 +1,6 @@
from ..lib.sim_testcase import SimTestCase from ..lib.sim_testcase import SimTestCase
class Test(SimTestCase): class Test(SimTestCase):
incompatible_sim_tools = {"xilinx"}
def test_dut(self): def test_dut(self):
self.run_test() self.run_test()

View File

@@ -2,9 +2,9 @@
{%- block declarations %} {%- block declarations %}
{% sv_line_anchor %} {% sv_line_anchor %}
localparam REGWIDTH = {{cls.regwidth}}; localparam REGWIDTH = {{testcase.regwidth}};
localparam STRIDE = REGWIDTH/8; localparam STRIDE = REGWIDTH/8;
localparam N_REGS = {{cls.n_regs}}; localparam N_REGS = {{testcase.n_regs}};
{%- endblock %} {%- endblock %}

View File

@@ -1,13 +1,11 @@
import os import os
from parameterized import parameterized_class from parameterized import parameterized_class
import pytest
from ..lib.sim_testcase import SimTestCase from ..lib.sim_testcase import SimTestCase
from ..lib.synth_testcase import SynthTestCase from ..lib.synth_testcase import SynthTestCase
from ..lib.test_params import get_permutations from ..lib.test_params import get_permutations
from ..lib.cpuifs import ALL_CPUIF from ..lib.cpuifs import ALL_CPUIF
from ..lib.simulators.xilinx import Xilinx
@@ -51,30 +49,3 @@ class TestDefaultResets(SimTestCase):
class TestSynth(SynthTestCase): class TestSynth(SynthTestCase):
def test_dut(self): def test_dut(self):
self.run_synth() self.run_synth()
@pytest.mark.skipif(os.environ.get("STUB_SIMULATOR", False) or os.environ.get("NO_XSIM", False), reason="user skipped")
@parameterized_class(get_permutations({
"cpuif": ALL_CPUIF,
"retime_read_fanin": [True, False],
"retime_read_response": [True, False],
"reuse_hwif_typedefs": [True, False],
}))
class TestVivado(SimTestCase):
"""
Vivado XSIM's implementation of clocking blocks is broken, which is heavily used
by the testbench infrastructure in most testcases.
Since this group of tests does not rely on writing HWIF values, the bugs in
xsim are avoided.
Run this testcase using xsim to get some cross-simulator coverage.
Goal is to validate the generated RTL doesn't use constructs that offend xsim.
This is skipped in CI stub tests as it doesn't add value
"""
simulator_cls = Xilinx
def test_dut(self):
self.run_test()