diff --git a/.gitignore b/.gitignore index d8c037d..6c730b9 100644 --- a/.gitignore +++ b/.gitignore @@ -7,6 +7,8 @@ **/_build **/*.out **/transcript +**/*.log +**/*.pb build/ dist/ diff --git a/peakrdl/regblock/cpuif/base_tmpl.sv b/peakrdl/regblock/cpuif/base_tmpl.sv index 330d321..0fe2e30 100644 --- a/peakrdl/regblock/cpuif/base_tmpl.sv +++ b/peakrdl/regblock/cpuif/base_tmpl.sv @@ -1,6 +1,2 @@ -begin - {%- filter indent %} - {%- block body %} - {%- endblock %} - {%- endfilter %} -end +{%- block body %} +{%- endblock %} diff --git a/test/lib/regblock_testcase.py b/test/lib/regblock_testcase.py index b426fed..ce9a7a5 100644 --- a/test/lib/regblock_testcase.py +++ b/test/lib/regblock_testcase.py @@ -3,7 +3,6 @@ import unittest import os import glob import shutil -import subprocess import inspect import pathlib @@ -17,6 +16,8 @@ from peakrdl.regblock import RegblockExporter from .cpuifs.base import CpuifTestMode from .cpuifs.apb3 import APB3 +from .simulators.modelsim import ModelSim + class RegblockTestCase(unittest.TestCase): #: Path to the testcase's RDL file. @@ -41,6 +42,8 @@ class RegblockTestCase(unittest.TestCase): #: Abort test if it exceeds this number of clock cycles timeout_clk_cycles = 1000 + simulator_cls = ModelSim + #: this gets auto-loaded via the _load_request autouse fixture request = None # type: pytest.FixtureRequest @@ -134,43 +137,6 @@ class RegblockTestCase(unittest.TestCase): stream.dump(output_path) - @classmethod - def _compile_tb(cls): - # CD into the build directory - cwd = os.getcwd() - os.chdir(cls.get_build_dir()) - - cmd = [ - "vlog", "-sv", "-quiet", "-l", "build.log", - - # Free version of ModelSim throws errors if generate/endgenerate - # blocks are not used. - # These have been made optional long ago. Modern versions of SystemVerilog do - # not require them and I prefer not to add them. - "-suppress", "2720", - - # Ignore noisy warning about vopt-time checking of always_comb/always_latch - "-suppress", "2583", - ] - - # Add CPUIF sources - cmd.extend(cls.cpuif.get_tb_files()) - - # Add DUT sources - cmd.append("regblock_pkg.sv") - cmd.append("regblock.sv") - - # Add TB - cmd.append("tb.sv") - - # Run command! - try: - subprocess.run(cmd, check=True) - finally: - # cd back - os.chdir(cwd) - - @classmethod def setUpClass(cls): # Create fresh build dir @@ -187,7 +153,16 @@ class RegblockTestCase(unittest.TestCase): # Create testbench from template cls._generate_tb(exporter) - cls._compile_tb() + simulator = cls.simulator_cls(testcase_cls=cls) + + # cd into the build directory + cwd = os.getcwd() + os.chdir(cls.get_build_dir()) + try: + simulator.compile() + finally: + # cd back + os.chdir(cwd) def setUp(self) -> None: @@ -197,27 +172,8 @@ class RegblockTestCase(unittest.TestCase): def run_test(self, plusargs:List[str] = None) -> None: - plusargs = plusargs or [] - - test_name = self.request.node.name - - # call vsim - cmd = [ - "vsim", "-quiet", - "-msgmode", "both", - "-do", "set WildcardFilter [lsearch -not -all -inline $WildcardFilter Memory]", - "-do", "log -r /*;", - "-do", "run -all; exit;", - "-c", - "-l", "%s.log" % test_name, - "-wlf", "%s.wlf" % test_name, - "tb", - ] - for plusarg in plusargs: - cmd.append("+" + plusarg) - subprocess.run(cmd, check=True) - - self.assertSimLogPass("%s.log" % test_name) + simulator = self.simulator_cls(testcase_cls_inst=self) + simulator.run(plusargs) def tearDown(self) -> None: diff --git a/test/lib/simulators/__init__.py b/test/lib/simulators/__init__.py new file mode 100644 index 0000000..fd4014d --- /dev/null +++ b/test/lib/simulators/__init__.py @@ -0,0 +1,26 @@ +from typing import Type, TYPE_CHECKING, List + +if TYPE_CHECKING: + from ..regblock_testcase import RegblockTestCase + +class Simulator: + + def __init__(self, testcase_cls: 'Type[RegblockTestCase]' = None, testcase_cls_inst: 'RegblockTestCase' = None) -> None: + self.testcase_cls = testcase_cls + self.testcase_cls_inst = testcase_cls_inst + + @property + def tb_files(self) -> List[str]: + files = [] + files.extend(self.testcase_cls.cpuif.get_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 diff --git a/test/lib/simulators/modelsim.py b/test/lib/simulators/modelsim.py new file mode 100644 index 0000000..51aced0 --- /dev/null +++ b/test/lib/simulators/modelsim.py @@ -0,0 +1,61 @@ +from typing import List +import subprocess +import os + +from . import Simulator + +class ModelSim(Simulator): + def compile(self) -> None: + cmd = [ + "vlog", "-sv", "-quiet", "-l", "build.log", + + # Free version of ModelSim throws errors if generate/endgenerate + # blocks are not used. + # These have been made optional long ago. Modern versions of SystemVerilog do + # not require them and I prefer not to add them. + "-suppress", "2720", + + # Ignore noisy warning about vopt-time checking of always_comb/always_latch + "-suppress", "2583", + ] + + # Add source files + cmd.extend(self.tb_files) + + # Run command! + subprocess.run(cmd, check=True) + + + def run(self, plusargs:List[str] = None) -> None: + plusargs = plusargs or [] + + test_name = self.testcase_cls_inst.request.node.name + + # call vsim + cmd = [ + "vsim", "-quiet", + "-msgmode", "both", + "-do", "set WildcardFilter [lsearch -not -all -inline $WildcardFilter Memory]", + "-do", "log -r /*;", + "-do", "run -all; exit;", + "-c", + "-l", "%s.log" % test_name, + "-wlf", "%s.wlf" % test_name, + "tb", + ] + for plusarg in plusargs: + cmd.append("+" + plusarg) + subprocess.run(cmd, check=True) + + self.assertSimLogPass("%s.log" % test_name) + + + def assertSimLogPass(self, path: str): + self.testcase_cls_inst.assertTrue(os.path.isfile(path)) + + with open(path, encoding="utf-8") as f: + for line in f: + if line.startswith("# ** Error"): + self.testcase_cls_inst.fail(line) + elif line.startswith("# ** Fatal"): + self.testcase_cls_inst.fail(line) diff --git a/test/lib/simulators/xilinx.py b/test/lib/simulators/xilinx.py new file mode 100644 index 0000000..793f31b --- /dev/null +++ b/test/lib/simulators/xilinx.py @@ -0,0 +1,60 @@ +from typing import List +import subprocess +import os + +from . import Simulator + +class Xilinx(Simulator): + """ + Don't bother using the Xilinx simulator... Its buggy and extraordinarily slow. + As observed in v2021.1, clocking block assignments do not seem to actually simulate + correctly - assignemnt statements get ignored or the values get mangled. + + Keeping this here in case someday it works better... + """ + def compile(self) -> None: + cmd = [ + "xvlog", "--sv" + ] + cmd.extend(self.tb_files) + subprocess.run(cmd, check=True) + + cmd = [ + "xelab", + "--timescale", "1ns/1ps", + "--debug", "all", + "tb", + ] + subprocess.run(cmd, check=True) + + + def run(self, plusargs:List[str] = None) -> None: + plusargs = plusargs or [] + + test_name = self.testcase_cls_inst.request.node.name + + # call vsim + cmd = [ + "xsim", + "--R", + "--log", "%s.log" % test_name, + "tb", + ] + + for plusarg in plusargs: + cmd.append("--testplusarg") + cmd.append(plusarg) + subprocess.run(cmd, check=True) + + self.assertSimLogPass("%s.log" % test_name) + + + def assertSimLogPass(self, path: str): + self.testcase_cls_inst.assertTrue(os.path.isfile(path)) + + with open(path, encoding="utf-8") as f: + for line in f: + if line.startswith("Error:"): + self.testcase_cls_inst.fail(line) + elif line.startswith("Fatal:"): + self.testcase_cls_inst.fail(line) diff --git a/test/lib/templates/tb_base.sv b/test/lib/templates/tb_base.sv index 69d8363..31464f9 100644 --- a/test/lib/templates/tb_base.sv +++ b/test/lib/templates/tb_base.sv @@ -57,8 +57,13 @@ module tb; {%- if exporter.hwif.has_output_struct %} {% sv_line_anchor %} - initial forever begin - ##1; if(!rst) assert(!$isunknown({>>{hwif_out}})) else $error("hwif_out has X's!"); + initial begin + logic [$bits(hwif_out)-1:0] tmp; + forever begin + ##1; + tmp = {>>{hwif_out}}; // Workaround for Xilinx's xsim - assign to tmp variable + if(!rst) assert(!$isunknown(tmp)) else $error("hwif_out has X's!"); + end end {%- endif %} diff --git a/test/test_field_types/tb_template.sv b/test/test_field_types/tb_template.sv index 3a5c728..d096ae4 100644 --- a/test/test_field_types/tb_template.sv +++ b/test/test_field_types/tb_template.sv @@ -88,7 +88,7 @@ // r7 - sw=r; hw=w; // Wire/Bus - hardware assigns value cpuif.assert_read('h6, 0); - cb.hwif_in.r7.f.value = 70; + cb.hwif_in.r7.f.value <= 70; cpuif.assert_read('h6, 70); cpuif.write('h6, 71); cpuif.assert_read('h6, 70); diff --git a/test/test_structural_sw_rw/tb_template.sv b/test/test_structural_sw_rw/tb_template.sv index f494278..fd03814 100644 --- a/test/test_structural_sw_rw/tb_template.sv +++ b/test/test_structural_sw_rw/tb_template.sv @@ -17,17 +17,17 @@ end // Assert via hwif - assert(hwif_out.r0.a.value == 'h42); - assert(hwif_out.r0.b.value == 'h0); - assert(hwif_out.r0.c.value == 'h1); - foreach(hwif_out.r1[x, y, z]) begin - assert(hwif_out.r1[x][y][z].a.value == 'h23); - assert(hwif_out.r1[x][y][z].b.value == 'h0); - assert(hwif_out.r1[x][y][z].c.value == 'h1); + assert(cb.hwif_out.r0.a.value == 'h42); + assert(cb.hwif_out.r0.b.value == 'h0); + assert(cb.hwif_out.r0.c.value == 'h1); + foreach(cb.hwif_out.r1[x, y, z]) begin + assert(cb.hwif_out.r1[x][y][z].a.value == 'h23); + assert(cb.hwif_out.r1[x][y][z].b.value == 'h0); + assert(cb.hwif_out.r1[x][y][z].c.value == 'h1); end - assert(hwif_out.r2.a.value == 'h11); - assert(hwif_out.r2.b.value == 'h0); - assert(hwif_out.r2.c.value == 'h1); + assert(cb.hwif_out.r2.a.value == 'h11); + assert(cb.hwif_out.r2.b.value == 'h0); + assert(cb.hwif_out.r2.c.value == 'h1); // Write values cpuif.write(0, 32'h8000_0002); @@ -50,15 +50,15 @@ end // Assert via hwif - assert(hwif_out.r0.a.value == 'h02); - assert(hwif_out.r0.b.value == 'h0); - assert(hwif_out.r0.c.value == 'h1); - foreach(hwif_out.r1[x, y, z]) begin - assert(hwif_out.r1[x][y][z].a.value == x*12+y*4+z+10); - assert(hwif_out.r1[x][y][z].b.value == 'h1); - assert(hwif_out.r1[x][y][z].c.value == 'h0); + assert(cb.hwif_out.r0.a.value == 'h02); + assert(cb.hwif_out.r0.b.value == 'h0); + assert(cb.hwif_out.r0.c.value == 'h1); + foreach(cb.hwif_out.r1[x, y, z]) begin + assert(cb.hwif_out.r1[x][y][z].a.value == x*12+y*4+z+10); + assert(cb.hwif_out.r1[x][y][z].b.value == 'h1); + assert(cb.hwif_out.r1[x][y][z].c.value == 'h0); end - assert(hwif_out.r2.a.value == 'h0); - assert(hwif_out.r2.b.value == 'h0); - assert(hwif_out.r2.c.value == 'h0); + assert(cb.hwif_out.r2.a.value == 'h0); + assert(cb.hwif_out.r2.b.value == 'h0); + assert(cb.hwif_out.r2.c.value == 'h0); {% endblock %} diff --git a/test/test_swacc_swmod/tb_template.sv b/test/test_swacc_swmod/tb_template.sv index 6b73d61..98bfe39 100644 --- a/test/test_swacc_swmod/tb_template.sv +++ b/test/test_swacc_swmod/tb_template.sv @@ -2,6 +2,7 @@ {% block seq %} {% sv_line_anchor %} + logic [7:0] counter; logic [7:0] rd_data; logic [7:0] latched_data; int event_count; @@ -12,17 +13,19 @@ ##1; // Verify that hwif gets sampled at the same cycle as swacc strobe - cb.hwif_in.r1.f.value <= 'h10; + counter = 'h10; + cb.hwif_in.r1.f.value <= counter; @cb; event_count = 0; fork begin ##0; forever begin - cb.hwif_in.r1.f.value <= cb.hwif_in.r1.f.value + 1; + counter++; + cb.hwif_in.r1.f.value <= counter; @cb; if(cb.hwif_out.r1.f.swacc) begin - latched_data = cb.hwif_in.r1.f.value; + latched_data = counter; event_count++; end end @@ -43,14 +46,14 @@ begin ##0; forever begin - assert(hwif_out.r2.f.value == 20); - if(hwif_out.r2.f.swmod) break; + assert(cb.hwif_out.r2.f.value == 20); + if(cb.hwif_out.r2.f.swmod) break; @cb; end @cb; forever begin - assert(hwif_out.r2.f.value == 21); - assert(hwif_out.r2.f.swmod == 0); + assert(cb.hwif_out.r2.f.value == 21); + assert(cb.hwif_out.r2.f.swmod == 0); @cb; end end @@ -67,14 +70,14 @@ begin ##0; forever begin - assert(hwif_out.r3.f.value == 30); - if(hwif_out.r3.f.swmod) break; + assert(cb.hwif_out.r3.f.value == 30); + if(cb.hwif_out.r3.f.swmod) break; @cb; end @cb; forever begin - assert(hwif_out.r3.f.value == 0); - assert(hwif_out.r3.f.swmod == 0); + assert(cb.hwif_out.r3.f.value == 0); + assert(cb.hwif_out.r3.f.swmod == 0); @cb; end end