Add option to use xilinx simulator in tests. Not recommended - simulator is pretty awful
This commit is contained in:
2
.gitignore
vendored
2
.gitignore
vendored
@@ -7,6 +7,8 @@
|
|||||||
**/_build
|
**/_build
|
||||||
**/*.out
|
**/*.out
|
||||||
**/transcript
|
**/transcript
|
||||||
|
**/*.log
|
||||||
|
**/*.pb
|
||||||
|
|
||||||
build/
|
build/
|
||||||
dist/
|
dist/
|
||||||
|
|||||||
@@ -1,6 +1,2 @@
|
|||||||
begin
|
|
||||||
{%- filter indent %}
|
|
||||||
{%- block body %}
|
{%- block body %}
|
||||||
{%- endblock %}
|
{%- endblock %}
|
||||||
{%- endfilter %}
|
|
||||||
end
|
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ import unittest
|
|||||||
import os
|
import os
|
||||||
import glob
|
import glob
|
||||||
import shutil
|
import shutil
|
||||||
import subprocess
|
|
||||||
import inspect
|
import inspect
|
||||||
import pathlib
|
import pathlib
|
||||||
|
|
||||||
@@ -17,6 +16,8 @@ from peakrdl.regblock import RegblockExporter
|
|||||||
from .cpuifs.base import CpuifTestMode
|
from .cpuifs.base import CpuifTestMode
|
||||||
from .cpuifs.apb3 import APB3
|
from .cpuifs.apb3 import APB3
|
||||||
|
|
||||||
|
from .simulators.modelsim import ModelSim
|
||||||
|
|
||||||
|
|
||||||
class RegblockTestCase(unittest.TestCase):
|
class RegblockTestCase(unittest.TestCase):
|
||||||
#: Path to the testcase's RDL file.
|
#: 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
|
#: Abort test if it exceeds this number of clock cycles
|
||||||
timeout_clk_cycles = 1000
|
timeout_clk_cycles = 1000
|
||||||
|
|
||||||
|
simulator_cls = ModelSim
|
||||||
|
|
||||||
#: this gets auto-loaded via the _load_request autouse fixture
|
#: this gets auto-loaded via the _load_request autouse fixture
|
||||||
request = None # type: pytest.FixtureRequest
|
request = None # type: pytest.FixtureRequest
|
||||||
|
|
||||||
@@ -134,43 +137,6 @@ class RegblockTestCase(unittest.TestCase):
|
|||||||
stream.dump(output_path)
|
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
|
@classmethod
|
||||||
def setUpClass(cls):
|
def setUpClass(cls):
|
||||||
# Create fresh build dir
|
# Create fresh build dir
|
||||||
@@ -187,7 +153,16 @@ class RegblockTestCase(unittest.TestCase):
|
|||||||
# Create testbench from template
|
# Create testbench from template
|
||||||
cls._generate_tb(exporter)
|
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:
|
def setUp(self) -> None:
|
||||||
@@ -197,27 +172,8 @@ class RegblockTestCase(unittest.TestCase):
|
|||||||
|
|
||||||
|
|
||||||
def run_test(self, plusargs:List[str] = None) -> None:
|
def run_test(self, plusargs:List[str] = None) -> None:
|
||||||
plusargs = plusargs or []
|
simulator = self.simulator_cls(testcase_cls_inst=self)
|
||||||
|
simulator.run(plusargs)
|
||||||
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)
|
|
||||||
|
|
||||||
|
|
||||||
def tearDown(self) -> None:
|
def tearDown(self) -> None:
|
||||||
|
|||||||
26
test/lib/simulators/__init__.py
Normal file
26
test/lib/simulators/__init__.py
Normal file
@@ -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
|
||||||
61
test/lib/simulators/modelsim.py
Normal file
61
test/lib/simulators/modelsim.py
Normal file
@@ -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)
|
||||||
60
test/lib/simulators/xilinx.py
Normal file
60
test/lib/simulators/xilinx.py
Normal file
@@ -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)
|
||||||
@@ -57,8 +57,13 @@ module tb;
|
|||||||
|
|
||||||
{%- if exporter.hwif.has_output_struct %}
|
{%- if exporter.hwif.has_output_struct %}
|
||||||
{% sv_line_anchor %}
|
{% sv_line_anchor %}
|
||||||
initial forever begin
|
initial begin
|
||||||
##1; if(!rst) assert(!$isunknown({>>{hwif_out}})) else $error("hwif_out has X's!");
|
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
|
end
|
||||||
{%- endif %}
|
{%- endif %}
|
||||||
|
|
||||||
|
|||||||
@@ -88,7 +88,7 @@
|
|||||||
|
|
||||||
// r7 - sw=r; hw=w; // Wire/Bus - hardware assigns value
|
// r7 - sw=r; hw=w; // Wire/Bus - hardware assigns value
|
||||||
cpuif.assert_read('h6, 0);
|
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.assert_read('h6, 70);
|
||||||
cpuif.write('h6, 71);
|
cpuif.write('h6, 71);
|
||||||
cpuif.assert_read('h6, 70);
|
cpuif.assert_read('h6, 70);
|
||||||
|
|||||||
@@ -17,17 +17,17 @@
|
|||||||
end
|
end
|
||||||
|
|
||||||
// Assert via hwif
|
// Assert via hwif
|
||||||
assert(hwif_out.r0.a.value == 'h42);
|
assert(cb.hwif_out.r0.a.value == 'h42);
|
||||||
assert(hwif_out.r0.b.value == 'h0);
|
assert(cb.hwif_out.r0.b.value == 'h0);
|
||||||
assert(hwif_out.r0.c.value == 'h1);
|
assert(cb.hwif_out.r0.c.value == 'h1);
|
||||||
foreach(hwif_out.r1[x, y, z]) begin
|
foreach(cb.hwif_out.r1[x, y, z]) begin
|
||||||
assert(hwif_out.r1[x][y][z].a.value == 'h23);
|
assert(cb.hwif_out.r1[x][y][z].a.value == 'h23);
|
||||||
assert(hwif_out.r1[x][y][z].b.value == 'h0);
|
assert(cb.hwif_out.r1[x][y][z].b.value == 'h0);
|
||||||
assert(hwif_out.r1[x][y][z].c.value == 'h1);
|
assert(cb.hwif_out.r1[x][y][z].c.value == 'h1);
|
||||||
end
|
end
|
||||||
assert(hwif_out.r2.a.value == 'h11);
|
assert(cb.hwif_out.r2.a.value == 'h11);
|
||||||
assert(hwif_out.r2.b.value == 'h0);
|
assert(cb.hwif_out.r2.b.value == 'h0);
|
||||||
assert(hwif_out.r2.c.value == 'h1);
|
assert(cb.hwif_out.r2.c.value == 'h1);
|
||||||
|
|
||||||
// Write values
|
// Write values
|
||||||
cpuif.write(0, 32'h8000_0002);
|
cpuif.write(0, 32'h8000_0002);
|
||||||
@@ -50,15 +50,15 @@
|
|||||||
end
|
end
|
||||||
|
|
||||||
// Assert via hwif
|
// Assert via hwif
|
||||||
assert(hwif_out.r0.a.value == 'h02);
|
assert(cb.hwif_out.r0.a.value == 'h02);
|
||||||
assert(hwif_out.r0.b.value == 'h0);
|
assert(cb.hwif_out.r0.b.value == 'h0);
|
||||||
assert(hwif_out.r0.c.value == 'h1);
|
assert(cb.hwif_out.r0.c.value == 'h1);
|
||||||
foreach(hwif_out.r1[x, y, z]) begin
|
foreach(cb.hwif_out.r1[x, y, z]) begin
|
||||||
assert(hwif_out.r1[x][y][z].a.value == x*12+y*4+z+10);
|
assert(cb.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(cb.hwif_out.r1[x][y][z].b.value == 'h1);
|
||||||
assert(hwif_out.r1[x][y][z].c.value == 'h0);
|
assert(cb.hwif_out.r1[x][y][z].c.value == 'h0);
|
||||||
end
|
end
|
||||||
assert(hwif_out.r2.a.value == 'h0);
|
assert(cb.hwif_out.r2.a.value == 'h0);
|
||||||
assert(hwif_out.r2.b.value == 'h0);
|
assert(cb.hwif_out.r2.b.value == 'h0);
|
||||||
assert(hwif_out.r2.c.value == 'h0);
|
assert(cb.hwif_out.r2.c.value == 'h0);
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
{% block seq %}
|
{% block seq %}
|
||||||
{% sv_line_anchor %}
|
{% sv_line_anchor %}
|
||||||
|
logic [7:0] counter;
|
||||||
logic [7:0] rd_data;
|
logic [7:0] rd_data;
|
||||||
logic [7:0] latched_data;
|
logic [7:0] latched_data;
|
||||||
int event_count;
|
int event_count;
|
||||||
@@ -12,17 +13,19 @@
|
|||||||
##1;
|
##1;
|
||||||
|
|
||||||
// Verify that hwif gets sampled at the same cycle as swacc strobe
|
// 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;
|
@cb;
|
||||||
event_count = 0;
|
event_count = 0;
|
||||||
fork
|
fork
|
||||||
begin
|
begin
|
||||||
##0;
|
##0;
|
||||||
forever begin
|
forever begin
|
||||||
cb.hwif_in.r1.f.value <= cb.hwif_in.r1.f.value + 1;
|
counter++;
|
||||||
|
cb.hwif_in.r1.f.value <= counter;
|
||||||
@cb;
|
@cb;
|
||||||
if(cb.hwif_out.r1.f.swacc) begin
|
if(cb.hwif_out.r1.f.swacc) begin
|
||||||
latched_data = cb.hwif_in.r1.f.value;
|
latched_data = counter;
|
||||||
event_count++;
|
event_count++;
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -43,14 +46,14 @@
|
|||||||
begin
|
begin
|
||||||
##0;
|
##0;
|
||||||
forever begin
|
forever begin
|
||||||
assert(hwif_out.r2.f.value == 20);
|
assert(cb.hwif_out.r2.f.value == 20);
|
||||||
if(hwif_out.r2.f.swmod) break;
|
if(cb.hwif_out.r2.f.swmod) break;
|
||||||
@cb;
|
@cb;
|
||||||
end
|
end
|
||||||
@cb;
|
@cb;
|
||||||
forever begin
|
forever begin
|
||||||
assert(hwif_out.r2.f.value == 21);
|
assert(cb.hwif_out.r2.f.value == 21);
|
||||||
assert(hwif_out.r2.f.swmod == 0);
|
assert(cb.hwif_out.r2.f.swmod == 0);
|
||||||
@cb;
|
@cb;
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -67,14 +70,14 @@
|
|||||||
begin
|
begin
|
||||||
##0;
|
##0;
|
||||||
forever begin
|
forever begin
|
||||||
assert(hwif_out.r3.f.value == 30);
|
assert(cb.hwif_out.r3.f.value == 30);
|
||||||
if(hwif_out.r3.f.swmod) break;
|
if(cb.hwif_out.r3.f.swmod) break;
|
||||||
@cb;
|
@cb;
|
||||||
end
|
end
|
||||||
@cb;
|
@cb;
|
||||||
forever begin
|
forever begin
|
||||||
assert(hwif_out.r3.f.value == 0);
|
assert(cb.hwif_out.r3.f.value == 0);
|
||||||
assert(hwif_out.r3.f.swmod == 0);
|
assert(cb.hwif_out.r3.f.swmod == 0);
|
||||||
@cb;
|
@cb;
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
Reference in New Issue
Block a user