More testcases & documentation

This commit is contained in:
Alex Mykyta
2021-12-04 17:24:19 -08:00
parent f70bdf774c
commit 3adf7e1328
44 changed files with 827 additions and 63 deletions

View File

@@ -1,17 +1,80 @@
ModelSim
--------
# Test Dependencies
## ModelSim
Testcases require an installation of ModelSim/QuestaSim, and for `vlog` & `vsim`
commands to be visible via the PATH environment variable.
ModelSim - Intel FPGA Edition can be downloaded for free from https://fpgasoftware.intel.com/ and is sufficient to run unit tests.
Running tests
-------------
## Python Packages
Install dependencies required for running tests
```bash
python3 -m pip install test/requirements.txt
```
# Running tests
Tests can be launched from the test directory using `pytest``.
Use ``pytest -n auto`` to run tests in parallel.
To run all tests:
```bash
cd test/
python3 -m pip install requirements.txt
pytest -n auto
pytest
```
You can also run a specific testcase. For example:
```bash
cd test/test_hw_access
pytest
```
# Test organization
The goal for this test infrastructre is to make it easy to add small-standalone
testcases, with minimal repetition/boilerplate code that is usually present in
SystemVerilog testbenches.
To accomplish this, Jinja templates are used extensively to generate the
resulting ``tb.sv`` file, as well as assist in dynamic testcase parameterization.
## CPU Interfaces
Each CPU interface type is described in its own folder as follows:
`lib/cpuifs/<type>/__init__.py`
: Definitions for CPU Interface test mode classes.
`lib/cpuifs/<type>/tb_inst.sv`
: Jinja template that defines how the CPU interface is declared & instantiated in the testbench file.
`lib/cpuifs/<type>/*.sv`
: Any other files required for compilation.
## Testcase
Each testcase group has its own folder and contains the following:
`test_*/__init__.py`
: Empty file required for test discovery.
`test_*/regblock.rdl`
: Testcase RDL file. Testcase infrastructure will automatically compile this and generate the regblock output SystemVerilog.
`test_*/tb_template.sv`
: Jinja template that defines the testcase-specific sequence.
`test_*/testcase.py`
: Defines Python unittest testcase entry point.
## Parameterization
Testcase classes can be parameterized using the [parameterized](https://github.com/wolever/parameterized) extension. This allows the same testcase to be run against multiple permutations of regblock export modes such as CPU interfaces, retiming flop stages, or even RDL parameterizations.

View File

@@ -1,3 +1,4 @@
{% sv_line_anchor %}
apb3_intf #(
.DATA_WIDTH({{exporter.cpuif.data_width}}),
.ADDR_WIDTH({{exporter.cpuif.addr_width}})
@@ -11,6 +12,7 @@ apb3_intf_driver #(
.m_apb(s_apb)
);
{% if type(cpuif).__name__.startswith("Flat") %}
{% sv_line_anchor %}
wire s_apb_psel;
wire s_apb_penable;
wire s_apb_pwrite;

View File

@@ -6,6 +6,8 @@ import jinja2 as jj
from peakrdl.regblock.cpuif.base import CpuifBase
from ..sv_line_anchor import SVLineAnchor
if TYPE_CHECKING:
from peakrdl.regblock import RegblockExporter
from ..regblock_testcase import RegblockTestCase
@@ -30,13 +32,17 @@ class CpuifTestMode:
def get_tb_inst(self, tb_cls: 'RegblockTestCase', exporter: 'RegblockExporter') -> str:
class_dir = os.path.dirname(inspect.getfile(self.__class__))
# For consistency, make the template root path relative to the test dir
template_root_path = os.path.join(os.path.dirname(__file__), "../..")
loader = jj.FileSystemLoader(
os.path.join(class_dir)
template_root_path
)
jj_env = jj.Environment(
loader=loader,
undefined=jj.StrictUndefined,
extensions=[SVLineAnchor],
)
context = {
@@ -46,5 +52,11 @@ class CpuifTestMode:
"type": type,
}
template = jj_env.get_template(self.tb_template)
# template paths are relative to their class.
# transform to be relative to the root path
class_dir = os.path.dirname(inspect.getfile(self.__class__))
template_local_path = os.path.join(class_dir, self.tb_template)
template_path = os.path.relpath(template_local_path, template_root_path)
template = jj_env.get_template(template_path)
return template.render(context)

View File

@@ -5,11 +5,14 @@ import glob
import shutil
import subprocess
import inspect
import pathlib
import pytest
import jinja2 as jj
from systemrdl import RDLCompiler
from .sv_line_anchor import SVLineAnchor
from peakrdl.regblock import RegblockExporter
from .cpuifs.base import CpuifTestMode
from .cpuifs.apb3 import APB3
@@ -53,7 +56,7 @@ class RegblockTestCase(unittest.TestCase):
@classmethod
def get_build_dir(cls) -> str:
this_dir = cls.get_testcase_dir()
build_dir = os.path.join(this_dir, cls.__name__ + ".out")
build_dir = os.path.join(this_dir, "run.out", cls.__name__)
return build_dir
@classmethod
@@ -113,6 +116,7 @@ class RegblockTestCase(unittest.TestCase):
jj_env = jj.Environment(
loader=loader,
undefined=jj.StrictUndefined,
extensions=[SVLineAnchor],
)
context = {
@@ -121,7 +125,7 @@ class RegblockTestCase(unittest.TestCase):
}
# template path needs to be relative to the Jinja loader root
template_path = os.path.join(cls.get_testcase_dir(), "tb.sv")
template_path = os.path.join(cls.get_testcase_dir(), "tb_template.sv")
template_path = os.path.relpath(template_path, template_root_path)
template = jj_env.get_template(template_path)
@@ -173,7 +177,7 @@ class RegblockTestCase(unittest.TestCase):
build_dir = cls.get_build_dir()
if os.path.exists(build_dir):
shutil.rmtree(build_dir)
os.mkdir(build_dir)
pathlib.Path(build_dir).mkdir(parents=True, exist_ok=True)
cls._write_params()

View File

@@ -0,0 +1,10 @@
from jinja2_simple_tags import StandaloneTag
class SVLineAnchor(StandaloneTag):
"""
Define a custom Jinja tag that emits a SystemVerilog `line directive so that
assertion messages can get properly back-annotated
"""
tags = {"sv_line_anchor"}
def render(self):
return f'`line {self.lineno + 1} "{self.template}" 0'

View File

@@ -1,3 +1,4 @@
{% sv_line_anchor %}
module tb;
timeunit 1ns;
timeprecision 1ps;
@@ -51,9 +52,11 @@ module tb;
//--------------------------------------------------------------------------
// DUT
//--------------------------------------------------------------------------
{% sv_line_anchor %}
regblock dut (.*);
{%- 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!");
end
@@ -82,6 +85,7 @@ module tb;
//--------------------------------------------------------------------------
// Monitor for timeout
//--------------------------------------------------------------------------
{% sv_line_anchor %}
initial begin
##{{cls.timeout_clk_cycles}};
$fatal(1, "Test timed out after {{cls.timeout_clk_cycles}} clock cycles");

View File

@@ -1,3 +1,4 @@
pytest
parameterized
pytest-xdist
jinja2-simple-tags

View File

View File

@@ -0,0 +1,8 @@
addrmap top {
reg {
field {
sw=rw; hw=r;
anded; ored; xored;
} f[7:0] = 0;
} r1;
};

View File

@@ -0,0 +1,44 @@
{% extends "lib/templates/tb_base.sv" %}
{% block seq %}
{% sv_line_anchor %}
##1;
cb.rst <= '0;
##1;
cpuif.write('h0, 'h00);
@cb;
assert(cb.hwif_out.r1.f.anded == 1'b0);
assert(cb.hwif_out.r1.f.ored == 1'b0);
assert(cb.hwif_out.r1.f.xored == 1'b0);
cpuif.write('h0, 'h01);
@cb;
assert(cb.hwif_out.r1.f.anded == 1'b0);
assert(cb.hwif_out.r1.f.ored == 1'b1);
assert(cb.hwif_out.r1.f.xored == 1'b1);
cpuif.write('h0, 'h02);
@cb;
assert(cb.hwif_out.r1.f.anded == 1'b0);
assert(cb.hwif_out.r1.f.ored == 1'b1);
assert(cb.hwif_out.r1.f.xored == 1'b1);
cpuif.write('h0, 'h03);
@cb;
assert(cb.hwif_out.r1.f.anded == 1'b0);
assert(cb.hwif_out.r1.f.ored == 1'b1);
assert(cb.hwif_out.r1.f.xored == 1'b0);
cpuif.write('h0, 'hFE);
@cb;
assert(cb.hwif_out.r1.f.anded == 1'b0);
assert(cb.hwif_out.r1.f.ored == 1'b1);
assert(cb.hwif_out.r1.f.xored == 1'b1);
cpuif.write('h0, 'hFF);
@cb;
assert(cb.hwif_out.r1.f.anded == 1'b1);
assert(cb.hwif_out.r1.f.ored == 1'b1);
assert(cb.hwif_out.r1.f.xored == 1'b0);
{% endblock %}

View File

@@ -0,0 +1,5 @@
from ..lib.regblock_testcase import RegblockTestCase
class Test(RegblockTestCase):
def test_dut(self):
self.run_test()

View File

@@ -1,6 +1,7 @@
{% extends "lib/templates/tb_base.sv" %}
{% block seq %}
{% sv_line_anchor %}
cb.hwif_in.r3.f.wel <= 1;
##1;
cb.rst <= '0;

View File

View File

@@ -0,0 +1,63 @@
addrmap top {
reg {
field {
sw=rw; hw=na;
} hw_enable[7:0] = 0xFF;
field {
sw=rw; hw=na;
} hw_mask[15:8] = 0x00;
field {
sw=rw; hw=na;
} hw_clr[16:16] = 0;
field {
sw=rw; hw=na;
} hw_set[17:17] = 0;
field {
sw=rw; hw=na;
} hw_we[18:18] = 0;
field {
sw=rw; hw=na;
} hw_wel[20:20] = 1;
} hw_ctrl;
reg {
field {
sw=r; hw=w;
we; hwclr; hwset;
} f[7:0] = 0x11;
} r1;
r1.f->hwenable = hw_ctrl.hw_enable;
reg {
field {
sw=r; hw=w;
we; hwclr; hwset;
} f[7:0] = 0x22;
} r2;
r2.f->hwmask = hw_ctrl.hw_mask;
reg {
field {
sw=rw; hw=w;
} f[7:0] = 0x33;
} r3;
r3.f->hwenable = hw_ctrl.hw_enable;
r3.f->hwclr = hw_ctrl.hw_clr;
r3.f->hwset = hw_ctrl.hw_set;
r3.f->we = hw_ctrl.hw_we;
reg {
field {
sw=rw; hw=w;
} f[7:0] = 0x44;
} r4;
r4.f->wel = hw_ctrl.hw_wel;
};

View File

@@ -0,0 +1,94 @@
{% extends "lib/templates/tb_base.sv" %}
{% block seq %}
{% sv_line_anchor %}
##1;
cb.rst <= '0;
##1;
// check initial conditions
cpuif.assert_read('h4, 'h11);
cpuif.assert_read('h8, 'h22);
cpuif.assert_read('hC, 'h33);
//---------------------------------
// set hwenable = F0
cpuif.write('h0, 'h00_F0);
// test hwenable + we
cb.hwif_in.r1.f.value <= 'hAB;
cb.hwif_in.r1.f.we <= '1;
@cb;
cb.hwif_in.r1.f.we <= '0;
cpuif.assert_read('h4, 'hA1);
// test hwenable + hwclr
cb.hwif_in.r1.f.hwclr <= '1;
@cb;
cb.hwif_in.r1.f.hwclr <= '0;
cpuif.assert_read('h4, 'h01);
// test hwenable + hwset
cb.hwif_in.r1.f.hwset <= '1;
@cb;
cb.hwif_in.r1.f.hwset <= '0;
cpuif.assert_read('h4, 'hF1);
//---------------------------------
// set hwmask = F0
cpuif.write('h0, 'hF0_00);
// test hwmask + we
cb.hwif_in.r2.f.value <= 'hAB;
cb.hwif_in.r2.f.we <= '1;
@cb;
cb.hwif_in.r2.f.we <= '0;
cpuif.assert_read('h8, 'h2B);
// test hwmask + hwclr
cb.hwif_in.r2.f.hwclr <= '1;
@cb;
cb.hwif_in.r2.f.hwclr <= '0;
cpuif.assert_read('h8, 'h20);
// test hwmask + hwset
cb.hwif_in.r2.f.hwset <= '1;
@cb;
cb.hwif_in.r2.f.hwset <= '0;
cpuif.assert_read('h8, 'h2F);
//---------------------------------
// test hwenable + hwclr via reference
// toggle hwenable = F0, hwclr=1
cpuif.write('h0, 'h1_00_F0);
cpuif.write('h0, 'h0_00_00);
cpuif.assert_read('hC, 'h03);
// test hwenable + hwset via reference
// toggle hwenable = 0F, hwset=1
cpuif.write('h0, 'h2_00_0F);
cpuif.write('h0, 'h0_00_00);
cpuif.assert_read('hC, 'h0F);
// test hwenable + we via reference
cb.hwif_in.r3.f.value <= 'hAA;
// toggle hwenable = 0F, we=1
cpuif.write('h0, 'h4_00_0F);
cpuif.write('h0, 'h0_00_00);
cpuif.assert_read('hC, 'h0A);
//---------------------------------
// test wel via reference
cb.hwif_in.r4.f.value <= 'hBB;
// toggle wel
cpuif.write('h0, 'h10_00_00);
cpuif.write('h0, 'h00_00_00);
cpuif.assert_read('h10, 'hBB);
cb.hwif_in.r4.f.value <= 'hCC;
// toggle wel
cpuif.write('h0, 'h10_00_00);
cpuif.write('h0, 'h00_00_00);
cpuif.assert_read('h10, 'hCC);
{% endblock %}

View File

@@ -0,0 +1,5 @@
from ..lib.regblock_testcase import RegblockTestCase
class Test(RegblockTestCase):
def test_dut(self):
self.run_test()

View File

View File

@@ -0,0 +1,61 @@
addrmap top {
reg {
field {
sw=rw; hw=na;
onread = rclr;
} f1[7:0] = 0xF0;
field {
sw=rw; hw=na;
onread = rset;
} f2[15:8] = 0x0F;
} r1;
reg {
field {
sw=rw; hw=na;
onwrite = woset;
} f1[3:0] = 0x0;
field {
sw=rw; hw=na;
onwrite = woclr;
} f2[7:4] = 0xF;
field {
sw=rw; hw=na;
onwrite = wot;
} f3[11:8] = 0x0;
} r2;
reg {
field {
sw=rw; hw=na;
onwrite = wzs;
} f1[3:0] = 0x0;
field {
sw=rw; hw=na;
onwrite = wzc;
} f2[7:4] = 0xF;
field {
sw=rw; hw=na;
onwrite = wzt;
} f3[11:8] = 0x0;
} r3;
reg {
field {
sw=rw; hw=na;
onwrite = wclr;
} f1[7:0] = 0xF0;
field {
sw=rw; hw=na;
onwrite = wset;
} f2[15:8] = 0x0F;
} r4;
};

View File

@@ -0,0 +1,30 @@
{% extends "lib/templates/tb_base.sv" %}
{% block seq %}
{% sv_line_anchor %}
##1;
cb.rst <= '0;
##1;
cpuif.assert_read('h0, 'h0F_F0);
cpuif.assert_read('h0, 'hFF_00);
cpuif.write ('h0, 'h00_FF);
cpuif.assert_read('h0, 'h00_FF);
cpuif.assert_read('h0, 'hFF_00);
cpuif.assert_read('h4, 'h0_F_0);
cpuif.write ('h4, 'h1_1_1);
cpuif.assert_read('h4, 'h1_E_1);
cpuif.write ('h4, 'h1_2_2);
cpuif.assert_read('h4, 'h0_C_3);
cpuif.assert_read('h8, 'h0_F_0);
cpuif.write ('h8, 'hE_E_E);
cpuif.assert_read('h8, 'h1_E_1);
cpuif.write ('h8, 'hE_D_D);
cpuif.assert_read('h8, 'h0_C_3);
cpuif.assert_read('hC, 'h0F_F0);
cpuif.write ('hC, 'h12_34);
cpuif.assert_read('hC, 'hFF_00);
{% endblock %}

View File

@@ -0,0 +1,5 @@
from ..lib.regblock_testcase import RegblockTestCase
class Test(RegblockTestCase):
def test_dut(self):
self.run_test()

View File

@@ -1,12 +1,15 @@
{% extends "lib/templates/tb_base.sv" %}
{%- block declarations %}
{% sv_line_anchor %}
localparam REGWIDTH = {{cls.regwidth}};
localparam STRIDE = REGWIDTH/8;
localparam N_REGS = {{cls.n_regs}};
{%- endblock %}
{% block seq %}
{% sv_line_anchor %}
bit [REGWIDTH-1:0] data[N_REGS];
##1;

View File

@@ -1,6 +1,7 @@
{% extends "lib/templates/tb_base.sv" %}
{% block seq %}
{% sv_line_anchor %}
##1;
cb.rst <= '0;
##1;

View File

@@ -14,4 +14,12 @@ addrmap top {
swmod;
} f[8] = 20;
} r2;
reg {
field {
sw=rw; hw=r;
swmod;
rclr;
} f[8] = 30;
} r3;
};

View File

@@ -1,6 +1,7 @@
{% extends "lib/templates/tb_base.sv" %}
{% block seq %}
{% sv_line_anchor %}
logic [7:0] rd_data;
logic [7:0] latched_data;
int event_count;
@@ -61,6 +62,28 @@
join_any
disable fork;
// TODO: verify some other atypical swmod (onread actions)
// Verify that hwif changes 1 cycle after swmod
fork
begin
##0;
forever begin
assert(hwif_out.r3.f.value == 30);
if(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);
@cb;
end
end
begin
cpuif.assert_read('h2, 30);
@cb;
end
join_any
disable fork;
{% endblock %}

View File

View File

@@ -0,0 +1,66 @@
addrmap top {
default regwidth = 8;
reg {
field {
sw=rw; hw=na;
} r3_swwe[0:0] = 1;
field {
sw=rw; hw=na;
} r4_swwel[1:1] = 0;
} lock;
//---------------------------------
// via inferred signal
//---------------------------------
reg {
field {
sw=rw; hw=na;
swwe;
} f[8] = 0x11;
} r1;
reg {
field {
sw=rw; hw=na;
swwel;
} f[8] = 0x22;
} r2;
//---------------------------------
// via lock register
//---------------------------------
reg {
field {
sw=rw; hw=na;
} f[8] = 0x33;
} r3;
r3.f->swwe = lock.r3_swwe;
reg {
field {
sw=rw; hw=na;
} f[8] = 0x44;
} r4;
r4.f->swwel = lock.r4_swwel;
//---------------------------------
// via prop ref chaining
//---------------------------------
reg {
field {
sw=rw; hw=na;
} f[8] = 0x55;
} r5;
r5.f->swwe = r3.f->swwe;
reg {
field {
sw=rw; hw=na;
} f[8] = 0x66;
} r6;
r6.f->swwe = r4.f->swwel; // intentionally opposite!
};

View File

@@ -0,0 +1,63 @@
{% extends "lib/templates/tb_base.sv" %}
{% block seq %}
{% sv_line_anchor %}
##1;
cb.rst <= '0;
##1;
// r1 swwe = true
cpuif.assert_read('h1, 'h11);
cb.hwif_in.r1.f.swwe <= '0;
cpuif.write ('h1, 'h12);
cpuif.assert_read('h1, 'h11);
cb.hwif_in.r1.f.swwe <= '1;
cpuif.write ('h1, 'h13);
cpuif.assert_read('h1, 'h13);
// r2 swwel = true
cpuif.assert_read('h2, 'h22);
cb.hwif_in.r2.f.swwel <= '1;
cpuif.write ('h2, 'h23);
cpuif.assert_read('h2, 'h22);
cb.hwif_in.r2.f.swwel <= '0;
cpuif.write ('h2, 'h24);
cpuif.assert_read('h2, 'h24);
// r3 swwe = lock.r3_swwe
cpuif.assert_read('h3, 'h33);
cpuif.write ('h0, 'h0);
cpuif.write ('h3, 'h32);
cpuif.assert_read('h3, 'h33);
cpuif.write ('h0, 'h1);
cpuif.write ('h3, 'h34);
cpuif.assert_read('h3, 'h34);
// r4 swwel = lock.r4_swwel
cpuif.assert_read('h4, 'h44);
cpuif.write ('h0, 'h2);
cpuif.write ('h4, 'h40);
cpuif.assert_read('h4, 'h44);
cpuif.write ('h0, 'h0);
cpuif.write ('h4, 'h45);
cpuif.assert_read('h4, 'h45);
// r5 swwe = r3->swwe = lock.r3_swwe
cpuif.assert_read('h5, 'h55);
cpuif.write ('h0, 'h0);
cpuif.write ('h5, 'h52);
cpuif.assert_read('h5, 'h55);
cpuif.write ('h0, 'h1);
cpuif.write ('h5, 'h54);
cpuif.assert_read('h5, 'h54);
// r6 swwe = r4->swwel = lock.r4_swwel
cpuif.assert_read('h6, 'h66);
cpuif.write ('h0, 'h0);
cpuif.write ('h6, 'h60);
cpuif.assert_read('h6, 'h66);
cpuif.write ('h0, 'h2);
cpuif.write ('h6, 'h65);
cpuif.assert_read('h6, 'h65);
{% endblock %}

View File

@@ -0,0 +1,5 @@
from ..lib.regblock_testcase import RegblockTestCase
class Test(RegblockTestCase):
def test_dut(self):
self.run_test()