testcase framework

This commit is contained in:
Alex Mykyta
2021-11-21 19:00:47 -08:00
parent d3c876a491
commit f70bdf774c
69 changed files with 1730 additions and 403 deletions

3
test/.gitignore vendored
View File

@@ -1,3 +0,0 @@
work
transcript
*.wlf

17
test/README.md Normal file
View File

@@ -0,0 +1,17 @@
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
-------------
```
cd test/
python3 -m pip install requirements.txt
pytest -n auto
```

0
test/__init__.py Normal file
View File

0
test/lib/__init__.py Normal file
View File

View File

View File

@@ -0,0 +1,14 @@
from ..base import CpuifTestMode
from peakrdl.regblock.cpuif.apb3 import APB3_Cpuif, APB3_Cpuif_flattened
class APB3(CpuifTestMode):
cpuif_cls = APB3_Cpuif
tb_files = [
"apb3_intf.sv",
"apb3_intf_driver.sv",
]
tb_template = "tb_inst.sv"
class FlatAPB3(APB3):
cpuif_cls = APB3_Cpuif_flattened

View File

@@ -3,6 +3,7 @@ interface apb3_intf_driver #(
parameter ADDR_WIDTH = 32
)(
input wire clk,
input wire rst,
apb3_intf.master m_apb
);
@@ -84,12 +85,25 @@ interface apb3_intf_driver #(
// Wait for response
while(cb.PREADY !== 1'b1) @(cb);
assert(!$isunknown(cb.PRDATA)) else $error("Read from 0x%0x returned X's on PRDATA", addr);
assert(!$isunknown(cb.PSLVERR)) else $error("Read from 0x%0x returned X's on PSLVERR", addr);
data = cb.PRDATA;
reset();
endtask
task assert_read(logic [ADDR_WIDTH-1:0] addr, logic [DATA_WIDTH-1:0] expected_data);
logic [DATA_WIDTH-1:0] data;
read(addr, data);
assert(data == expected_data) else $error("Read from 0x%x returned 0x%x. Expected 0x%x", addr, data, expected_data);
endtask
initial begin
reset();
end
initial forever begin
@cb;
if(!rst) assert(!$isunknown(cb.PREADY)) else $error("Saw X on PREADY!");
end
endinterface

View File

@@ -0,0 +1,30 @@
apb3_intf #(
.DATA_WIDTH({{exporter.cpuif.data_width}}),
.ADDR_WIDTH({{exporter.cpuif.addr_width}})
) s_apb();
apb3_intf_driver #(
.DATA_WIDTH({{exporter.cpuif.data_width}}),
.ADDR_WIDTH({{exporter.cpuif.addr_width}})
) cpuif(
.clk(clk),
.rst(rst),
.m_apb(s_apb)
);
{% if type(cpuif).__name__.startswith("Flat") %}
wire s_apb_psel;
wire s_apb_penable;
wire s_apb_pwrite;
wire [{{exporter.cpuif.addr_width - 1}}:0] s_apb_paddr;
wire [{{exporter.cpuif.data_width - 1}}:0] s_apb_pwdata;
wire s_apb_pready;
wire [{{exporter.cpuif.data_width - 1}}:0] s_apb_prdata;
wire s_apb_pslverr;
assign s_apb_psel = s_apb.PSEL;
assign s_apb_penable = s_apb.PENABLE;
assign s_apb_pwrite = s_apb.PWRITE;
assign s_apb_paddr = s_apb.PADDR;
assign s_apb_pwdata = s_apb.PWDATA;
assign s_apb.PREADY = s_apb_pready;
assign s_apb.PRDATA = s_apb_prdata;
assign s_apb.PSLVERR = s_apb_pslverr;
{% endif %}

50
test/lib/cpuifs/base.py Normal file
View File

@@ -0,0 +1,50 @@
from typing import List, TYPE_CHECKING
import os
import inspect
import jinja2 as jj
from peakrdl.regblock.cpuif.base import CpuifBase
if TYPE_CHECKING:
from peakrdl.regblock import RegblockExporter
from ..regblock_testcase import RegblockTestCase
class CpuifTestMode:
cpuif_cls = None # type: CpuifBase
tb_files = [] # type: List[str]
tb_template = ""
def get_tb_files(self) -> List[str]:
class_dir = os.path.dirname(inspect.getfile(self.__class__))
cwd = os.getcwd()
tb_files = []
for file in self.tb_files:
relpath = os.path.relpath(
os.path.join(class_dir, file),
cwd
)
tb_files.append(relpath)
return tb_files
def get_tb_inst(self, tb_cls: 'RegblockTestCase', exporter: 'RegblockExporter') -> str:
class_dir = os.path.dirname(inspect.getfile(self.__class__))
loader = jj.FileSystemLoader(
os.path.join(class_dir)
)
jj_env = jj.Environment(
loader=loader,
undefined=jj.StrictUndefined,
)
context = {
"cpuif": self,
"cls": tb_cls,
"exporter": exporter,
"type": type,
}
template = jj_env.get_template(self.tb_template)
return template.render(context)

View File

@@ -0,0 +1,232 @@
from typing import Optional, List
import unittest
import os
import glob
import shutil
import subprocess
import inspect
import pytest
import jinja2 as jj
from systemrdl import RDLCompiler
from peakrdl.regblock import RegblockExporter
from .cpuifs.base import CpuifTestMode
from .cpuifs.apb3 import APB3
class RegblockTestCase(unittest.TestCase):
#: Path to the testcase's RDL file.
#: Relative to the testcase's dir. If unset, the first RDL file found in the
#: testcase dir will be used
rdl_file = None # type: Optional[str]
#: RDL type name to elaborate. If unset, compiler will automatically choose
#: the top.
rdl_elab_target = None # type: Optional[str]
#: Parameters to pass into RDL elaboration
rdl_elab_params = {}
#: Define what CPUIF to use for this testcase
cpuif = APB3() # type: CpuifTestMode
# Other exporter args:
retime_read_fanin = False
retime_read_response = False
#: Abort test if it exceeds this number of clock cycles
timeout_clk_cycles = 1000
#: this gets auto-loaded via the _load_request autouse fixture
request = None # type: pytest.FixtureRequest
@pytest.fixture(autouse=True)
def _load_request(self, request):
self.request = request
@classmethod
def get_testcase_dir(cls) -> str:
class_dir = os.path.dirname(inspect.getfile(cls))
return class_dir
@classmethod
def get_build_dir(cls) -> str:
this_dir = cls.get_testcase_dir()
build_dir = os.path.join(this_dir, cls.__name__ + ".out")
return build_dir
@classmethod
def _write_params(cls) -> None:
"""
Write out the class parameters to a file so that it is easier to debug
how a testcase was parameterized
"""
path = os.path.join(cls.get_build_dir(), "params.txt")
with open(path, 'w') as f:
for k, v in cls.__dict__.items():
if k.startswith("_") or callable(v):
continue
f.write(f"{k}: {repr(v)}\n")
@classmethod
def _export_regblock(cls) -> RegblockExporter:
"""
Call the peakrdl.regblock exporter to generate the DUT
"""
this_dir = cls.get_testcase_dir()
if cls.rdl_file:
rdl_file = cls.rdl_file
else:
# Find any *.rdl file in testcase dir
rdl_file = glob.glob(os.path.join(this_dir, "*.rdl"))[0]
rdlc = RDLCompiler()
rdlc.compile_file(rdl_file)
root = rdlc.elaborate(cls.rdl_elab_target, "regblock", cls.rdl_elab_params)
exporter = RegblockExporter()
exporter.export(
root,
cls.get_build_dir(),
module_name="regblock",
package_name="regblock_pkg",
cpuif_cls=cls.cpuif.cpuif_cls,
retime_read_fanin=cls.retime_read_fanin,
retime_read_response=cls.retime_read_response,
)
return exporter
@classmethod
def _generate_tb(cls, exporter: RegblockExporter):
"""
Render the testbench template into actual tb.sv
"""
template_root_path = os.path.join(os.path.dirname(__file__), "..")
loader = jj.FileSystemLoader(
template_root_path
)
jj_env = jj.Environment(
loader=loader,
undefined=jj.StrictUndefined,
)
context = {
"cls": cls,
"exporter": exporter,
}
# 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.relpath(template_path, template_root_path)
template = jj_env.get_template(template_path)
output_path = os.path.join(cls.get_build_dir(), "tb.sv")
stream = template.stream(context)
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
build_dir = cls.get_build_dir()
if os.path.exists(build_dir):
shutil.rmtree(build_dir)
os.mkdir(build_dir)
cls._write_params()
# Convert testcase RDL file --> SV
exporter = cls._export_regblock()
# Create testbench from template
cls._generate_tb(exporter)
cls._compile_tb()
def setUp(self) -> None:
# cd into the build directory
self.original_cwd = os.getcwd()
os.chdir(self.get_build_dir())
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)
def tearDown(self) -> None:
# cd back
os.chdir(self.original_cwd)
def assertSimLogPass(self, path: str):
self.assertTrue(os.path.isfile(path))
with open(path, encoding="utf-8") as f:
for line in f:
if line.startswith("# ** Error"):
self.fail(line)
elif line.startswith("# ** Fatal"):
self.fail(line)

View File

@@ -0,0 +1,90 @@
module tb;
timeunit 1ns;
timeprecision 1ps;
logic rst = '1;
logic clk = '0;
initial forever begin
#10ns;
clk = ~clk;
end
//--------------------------------------------------------------------------
// DUT Signal declarations
//--------------------------------------------------------------------------
{%- if exporter.hwif.has_input_struct %}
regblock_pkg::regblock__in_t hwif_in;
{%- endif %}
{%- if exporter.hwif.has_output_struct %}
regblock_pkg::regblock__out_t hwif_out;
{%- endif %}
{%- block declarations %}
{%- endblock %}
//--------------------------------------------------------------------------
// Clocking
//--------------------------------------------------------------------------
default clocking cb @(posedge clk);
default input #1step output #1;
output rst;
{%- if exporter.hwif.has_input_struct %}
output hwif_in;
{%- endif %}
{%- if exporter.hwif.has_output_struct %}
input hwif_out;
{%- endif %}
{%- filter indent %}
{%- block clocking_dirs %}
{%- endblock %}
{%- endfilter %}
endclocking
//--------------------------------------------------------------------------
// CPUIF
//--------------------------------------------------------------------------
{{cls.cpuif.get_tb_inst(cls, exporter)|indent}}
//--------------------------------------------------------------------------
// DUT
//--------------------------------------------------------------------------
regblock dut (.*);
{%- if exporter.hwif.has_output_struct %}
initial forever begin
##1; if(!rst) assert(!$isunknown({>>{hwif_out}})) else $error("hwif_out has X's!");
end
{%- endif %}
//--------------------------------------------------------------------------
// Test Sequence
//--------------------------------------------------------------------------
initial begin
cb.rst <= '1;
{%- if exporter.hwif.has_input_struct %}
cb.hwif_in <= '{default: '0};
{%- endif %}
begin
{%- filter indent(8) %}
{%- block seq %}
{%- endblock %}
{%- endfilter %}
end
##5;
$finish();
end
//--------------------------------------------------------------------------
// Monitor for timeout
//--------------------------------------------------------------------------
initial begin
##{{cls.timeout_clk_cycles}};
$fatal(1, "Test timed out after {{cls.timeout_clk_cycles}} clock cycles");
end
endmodule

23
test/lib/test_params.py Normal file
View File

@@ -0,0 +1,23 @@
from itertools import product
from .cpuifs.apb3 import APB3, FlatAPB3
all_cpuif = [
APB3(),
FlatAPB3(),
]
def get_permutations(spec):
param_list = []
for v in product(*spec.values()):
param_list.append(dict(zip(spec, v)))
return param_list
#-------------------------------------------------------------------------------
# TODO: this wont scale well. Create groups of permutatuions. not necessary to permute everything all the time.
TEST_PARAMS = get_permutations({
"cpuif": all_cpuif,
"retime_read_fanin": [True, False],
"retime_read_response": [True, False],
})

2
test/pytest.ini Normal file
View File

@@ -0,0 +1,2 @@
[pytest]
python_files = test_*.py testcase.py

3
test/requirements.txt Normal file
View File

@@ -0,0 +1,3 @@
pytest
parameterized
pytest-xdist

View File

@@ -1,8 +0,0 @@
#!/bin/bash
set -e
../export.py test_regblock.rdl
vlog -sv -f vlog_args.f -f src.f
vsim -c -quiet tb -do "log -r /*; run -all; exit;"

View File

@@ -1,5 +0,0 @@
interfaces/apb3_intf.sv
drivers/apb3_intf_driver.sv
test_regblock_pkg.sv
test_regblock.sv
tb.sv

View File

@@ -1,51 +0,0 @@
module tb;
timeunit 1ns;
timeprecision 1ps;
logic rst = '1;
logic clk = '0;
initial forever begin
#10ns;
clk = ~clk;
end
apb3_intf apb();
apb3_intf_driver driver(
.clk(clk),
.m_apb(apb)
);
test_regblock dut (
.clk(clk),
.rst(rst),
.s_apb(apb),
.hwif_out()
);
initial begin
logic [31:0] rd_data;
repeat(5) @(posedge clk);
rst = '0;
repeat(5) @(posedge clk);
driver.read('h000, rd_data);
driver.write('h000, 'h0);
driver.read('h000, rd_data);
driver.read('h100, rd_data);
driver.write('h100, 'h0);
driver.read('h100, rd_data);
driver.read('h000, rd_data);
driver.write('h000, 'hFFFF_FFFF);
driver.read('h000, rd_data);
repeat(5) @(posedge clk);
$finish();
end
endmodule

View File

View File

@@ -0,0 +1,64 @@
addrmap top {
default regwidth = 8;
// All the valid combinations from Table 12
reg {
field {
sw=rw; hw=rw; we; // Storage element
} f[8] = 10;
} r1;
reg {
field {
sw=rw; hw=r; // Storage element
} f[8] = 20;
} r2;
reg {
field {
sw=rw; hw=w; wel; // Storage element
} f[8] = 30;
} r3;
reg {
field {
sw=rw; hw=na; // Storage element
} f[8] = 40;
} r4;
reg {
field {
sw=r; hw=rw; we; // Storage element
} f[8] = 50;
} r5;
reg {
field {
sw=r; hw=r; // Wire/Bus - constant value
} f[8] = 60;
} r6;
reg {
field {
sw=r; hw=w; // Wire/Bus - hardware assigns value
} f[8];
} r7;
reg {
field {
sw=r; hw=na; // Wire/Bus - constant value
} f[8] = 80;
} r8;
reg {
field {
sw=w; hw=rw; we; // Storage element
} f[8] = 90;
} r9;
reg {
field {
sw=w; hw=r; // Storage element
} f[8] = 100;
} r10;
};

130
test/test_field_types/tb.sv Normal file
View File

@@ -0,0 +1,130 @@
{% extends "lib/templates/tb_base.sv" %}
{% block seq %}
cb.hwif_in.r3.f.wel <= 1;
##1;
cb.rst <= '0;
##1;
// r1 - sw=rw; hw=rw; we; // Storage element
cpuif.assert_read('h0, 10);
assert(cb.hwif_out.r1.f.value == 10);
cpuif.write('h0, 11);
cpuif.assert_read('h0, 11);
assert(cb.hwif_out.r1.f.value == 11);
cb.hwif_in.r1.f.value <= 9;
cpuif.assert_read('h0, 11);
assert(cb.hwif_out.r1.f.value == 11);
cb.hwif_in.r1.f.value <= 12;
cb.hwif_in.r1.f.we <= 1;
@cb;
cb.hwif_in.r1.f.value <= 0;
cb.hwif_in.r1.f.we <= 0;
cpuif.assert_read('h0, 12);
assert(cb.hwif_out.r1.f.value == 12);
// r2 - sw=rw; hw=r; // Storage element
cpuif.assert_read('h1, 20);
assert(cb.hwif_out.r2.f.value == 20);
cpuif.write('h1, 21);
cpuif.assert_read('h1, 21);
assert(cb.hwif_out.r2.f.value == 21);
// r3 - sw=rw; hw=w; wel; // Storage element
cpuif.assert_read('h2, 30);
cpuif.write('h2, 31);
cpuif.assert_read('h2, 31);
cb.hwif_in.r3.f.value <= 29;
cpuif.assert_read('h2, 31);
cb.hwif_in.r3.f.value <= 32;
cb.hwif_in.r3.f.wel <= 0;
@cb;
cb.hwif_in.r3.f.value <= 0;
cb.hwif_in.r3.f.wel <= 1;
cpuif.assert_read('h2, 32);
// r4 - sw=rw; hw=na; // Storage element
cpuif.assert_read('h3, 40);
cpuif.write('h3, 41);
cpuif.assert_read('h3, 41);
// r5 - sw=r; hw=rw; we; // Storage element
cpuif.assert_read('h4, 50);
assert(cb.hwif_out.r5.f.value == 50);
cpuif.write('h4, 51);
cpuif.assert_read('h4, 50);
assert(cb.hwif_out.r5.f.value == 50);
cb.hwif_in.r5.f.value <= 9;
cpuif.assert_read('h4, 50);
assert(cb.hwif_out.r5.f.value == 50);
cb.hwif_in.r5.f.value <= 52;
cb.hwif_in.r5.f.we <= 1;
@cb;
cb.hwif_in.r5.f.value <= 0;
cb.hwif_in.r5.f.we <= 0;
cpuif.assert_read('h4, 52);
assert(cb.hwif_out.r5.f.value == 52);
// r6 - sw=r; hw=r; // Wire/Bus - constant value
cpuif.assert_read('h5, 60);
assert(cb.hwif_out.r6.f.value == 60);
cpuif.write('h5, 61);
cpuif.assert_read('h5, 60);
assert(cb.hwif_out.r6.f.value == 60);
// r7 - sw=r; hw=w; // Wire/Bus - hardware assigns value
cpuif.assert_read('h6, 0);
cb.hwif_in.r7.f.value = 70;
cpuif.assert_read('h6, 70);
cpuif.write('h6, 71);
cpuif.assert_read('h6, 70);
// r8 - sw=r; hw=na; // Wire/Bus - constant value
cpuif.assert_read('h7, 80);
cpuif.write('h7, 81);
cpuif.assert_read('h7, 80);
// r9 - sw=w; hw=rw; we; // Storage element
cpuif.assert_read('h8, 0);
assert(cb.hwif_out.r9.f.value == 90);
cpuif.write('h8, 91);
cpuif.assert_read('h8, 0);
assert(cb.hwif_out.r9.f.value == 91);
cb.hwif_in.r9.f.value <= 89;
cpuif.assert_read('h8, 0);
assert(cb.hwif_out.r9.f.value == 91);
cb.hwif_in.r9.f.value <= 92;
cb.hwif_in.r9.f.we <= 1;
@cb;
cb.hwif_in.r9.f.value <= 0;
cb.hwif_in.r9.f.we <= 0;
cpuif.assert_read('h8, 0);
assert(cb.hwif_out.r9.f.value == 92);
// r10 - sw=w; hw=r; // Storage element
cpuif.assert_read('h9, 0);
assert(cb.hwif_out.r10.f.value == 100);
cpuif.write('h9, 101);
cpuif.assert_read('h9, 0);
assert(cb.hwif_out.r10.f.value == 101);
{% 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,10 @@
addrmap top #(
longint N_REGS = 1,
longint REGWIDTH = 32
) {
reg reg_t {
regwidth = REGWIDTH;
field {sw=rw; hw=na;} f[REGWIDTH] = 1;
};
reg_t regs[N_REGS];
};

View File

@@ -0,0 +1,31 @@
{% extends "lib/templates/tb_base.sv" %}
{%- block declarations %}
localparam REGWIDTH = {{cls.regwidth}};
localparam STRIDE = REGWIDTH/8;
localparam N_REGS = {{cls.n_regs}};
{%- endblock %}
{% block seq %}
bit [REGWIDTH-1:0] data[N_REGS];
##1;
cb.rst <= '0;
##1;
foreach(data[i]) data[i] = {$urandom(), $urandom(), $urandom(), $urandom()};
for(int i=0; i<N_REGS; i++) begin
cpuif.assert_read(i*STRIDE, 'h1);
end
for(int i=0; i<N_REGS; i++) begin
cpuif.write(i*STRIDE, data[i]);
end
for(int i=0; i<N_REGS; i++) begin
cpuif.assert_read(i*STRIDE, data[i]);
end
assert($bits(dut.cpuif_wr_data) == REGWIDTH);
{% endblock %}

View File

@@ -0,0 +1,37 @@
from parameterized import parameterized_class
from ..lib.regblock_testcase import RegblockTestCase
from ..lib.test_params import get_permutations
PARAMS = get_permutations({
"regwidth" : [8, 16, 32, 64],
})
@parameterized_class(PARAMS)
class TestFanin(RegblockTestCase):
retime_read_fanin = False
n_regs = 20
regwidth = 32
@classmethod
def setUpClass(cls):
cls.rdl_elab_params = {
"N_REGS": cls.n_regs,
"REGWIDTH": cls.regwidth,
}
super().setUpClass()
def test_dut(self):
self.run_test()
PARAMS = get_permutations({
"n_regs" : [1, 4, 7, 9, 11],
"regwidth" : [8, 16, 32, 64],
})
@parameterized_class(PARAMS)
class TestRetimedFanin(TestFanin):
retime_read_fanin = True
def test_dut(self):
self.run_test()

View File

@@ -1,12 +0,0 @@
addrmap test_regblock {
reg my_reg {
field { sw=rw; hw=r; anded;} a[8] = 0x10;
//field { sw=rw; hw=r; ored;} b[8] = 0x20;
//field { sw=rw; hw=r; swmod;} c[8] = 0x30;
};
//my_reg r0 @0x000;
//my_reg r1 @0x100;
my_reg r2[112] @0x200 += 8;
};

View File

@@ -1,170 +0,0 @@
// TODO: Add a banner
module test_regblock (
input wire clk,
input wire rst,
apb3_intf.slave s_apb,
output test_regblock_pkg::test_regblock__out_t hwif_out
);
//--------------------------------------------------------------------------
// CPU Bus interface logic
//--------------------------------------------------------------------------
logic cpuif_req;
logic cpuif_req_is_wr;
logic [10:0] cpuif_addr;
logic [31:0] cpuif_wr_data;
logic [31:0] cpuif_wr_biten;
logic cpuif_rd_ack;
logic [31:0] cpuif_rd_data;
logic cpuif_rd_err;
logic cpuif_wr_ack;
logic cpuif_wr_err;
begin
// Request
logic is_active;
always_ff @(posedge clk) begin
if(rst) begin
is_active <= '0;
cpuif_req <= '0;
cpuif_req_is_wr <= '0;
cpuif_addr <= '0;
cpuif_wr_data <= '0;
end else begin
if(~is_active) begin
if(s_apb.PSEL) begin
is_active <= '1;
cpuif_req <= '1;
cpuif_req_is_wr <= s_apb.PWRITE;
cpuif_addr <= {s_apb.PADDR[10:2], 2'b0};
cpuif_wr_data <= s_apb.PWDATA;
end
end else begin
cpuif_req <= '0;
if(cpuif_rd_ack || cpuif_wr_ack) begin
is_active <= '0;
end
end
end
end
assign cpuif_wr_biten = '1;
// Response
assign s_apb.PREADY = cpuif_rd_ack | cpuif_wr_ack;
assign s_apb.PRDATA = cpuif_rd_data;
assign s_apb.PSLVERR = cpuif_rd_err | cpuif_wr_err;
end
//--------------------------------------------------------------------------
// Address Decode
//--------------------------------------------------------------------------
typedef struct {
logic r2[112];
} decoded_reg_strb_t;
decoded_reg_strb_t decoded_reg_strb;
logic decoded_req;
logic decoded_req_is_wr;
logic [31:0] decoded_wr_data;
logic [31:0] decoded_wr_biten;
always_comb begin
for(int i0=0; i0<112; i0++) begin
decoded_reg_strb.r2[i0] = cpuif_req & (cpuif_addr == 'h200 + i0*'h8);
end
end
// Writes are always granted with no error response
assign cpuif_wr_ack = cpuif_req & cpuif_req_is_wr;
assign cpuif_wr_err = '0;
// Pass down signals to next stage
assign decoded_req = cpuif_req;
assign decoded_req_is_wr = cpuif_req_is_wr;
assign decoded_wr_data = cpuif_wr_data;
assign decoded_wr_biten = cpuif_wr_biten;
//--------------------------------------------------------------------------
// Field logic
//--------------------------------------------------------------------------
typedef struct {
struct {
struct {
logic [7:0] next;
logic load_next;
} a;
} r2[112];
} field_combo_t;
field_combo_t field_combo;
typedef struct {
struct {
logic [7:0] a;
} r2[112];
} field_storage_t;
field_storage_t field_storage;
for(genvar i0=0; i0<112; i0++) begin
// Field: test_regblock.r2[].a
always_comb begin
field_combo.r2[i0].a.next = field_storage.r2[i0].a;
field_combo.r2[i0].a.load_next = '0;
if(decoded_reg_strb.r2[i0] && decoded_req_is_wr) begin // SW write
field_combo.r2[i0].a.next = decoded_wr_data[7:0];
field_combo.r2[i0].a.load_next = '1;
end
end
always_ff @(posedge clk) begin
if(rst) begin
field_storage.r2[i0].a <= 'h10;
end else if(field_combo.r2[i0].a.load_next) begin
field_storage.r2[i0].a <= field_combo.r2[i0].a.next;
end
end
assign hwif_out.r2[i0].a.value = field_storage.r2[i0].a;
assign hwif_out.r2[i0].a.anded = &(field_storage.r2[i0].a);
end
//--------------------------------------------------------------------------
// Readback
//--------------------------------------------------------------------------
logic readback_err;
logic readback_done;
logic [31:0] readback_data;
// Assign readback values to a flattened array
logic [31:0] readback_array[112];
for(genvar i0=0; i0<112; i0++) begin
assign readback_array[i0*1 + 0][7:0] = (decoded_reg_strb.r2[i0] && !decoded_req_is_wr) ? field_storage.r2[i0].a : '0;
assign readback_array[i0*1 + 0][31:8] = '0;
end
// Reduce the array
always_comb begin
automatic logic [31:0] readback_data_var;
readback_done = decoded_req & ~decoded_req_is_wr;
readback_err = '0;
readback_data_var = '0;
for(int i=0; i<112; i++) readback_data_var |= readback_array[i];
readback_data = readback_data_var;
end
always_ff @(posedge clk) begin
if(rst) begin
cpuif_rd_ack <= '0;
cpuif_rd_data <= '0;
cpuif_rd_err <= '0;
end else begin
cpuif_rd_ack <= readback_done;
cpuif_rd_data <= readback_data;
cpuif_rd_err <= readback_err;
end
end
endmodule

View File

@@ -1,19 +0,0 @@
// TODO: Add a banner
package test_regblock_pkg;
// test_regblock.r2[].a
typedef struct {
logic [7:0] value;
logic anded;
} test_regblock__r2x__a__out_t;
// test_regblock.r2[]
typedef struct {
test_regblock__r2x__a__out_t a;
} test_regblock__r2x__out_t;
// test_regblock
typedef struct {
test_regblock__r2x__out_t r2[112];
} test_regblock__out_t;
endpackage

View File

View File

@@ -0,0 +1,34 @@
addrmap regblock {
default sw=rw;
default hw=r;
reg my_reg {
field {} a[8] = 0x23;
field {} b = 0;
field {} c[31:31] = 1;
};
my_reg r0 @0x000;
r0.a->reset = 0x42;
my_reg r1[2][3][4] @0x10 += 8;
my_reg r2 @0x1000;
r2.a->reset = 0x11;
reg subreg {
field {} x[7:4] = 1;
};
regfile subrf {
subreg r1[4] @ 0x0 += 4;
regfile {
subreg r1 @ 0x0;
subreg r2[2] @ 0x4 += 4;
subreg r3 @ 0xc;
} sub[2] @ 0x10 += 0x10;
subreg r2[4] @ 0x30 += 4;
};
subrf sub2[2] @ 0x2000 += 0x40;
subreg r3 @ 0x2080;
};

View File

@@ -0,0 +1,63 @@
{% extends "lib/templates/tb_base.sv" %}
{% block seq %}
##1;
cb.rst <= '0;
##1;
// Assert value via frontdoor
cpuif.assert_read(0, 32'h8000_0042);
for(int i=0; i<2*3*4; i++) begin
cpuif.assert_read('h10+i*8, 32'h8000_0023);
end
cpuif.assert_read('h1000, 32'h8000_0011);
for(int i=0; i<33; i++) begin
cpuif.assert_read('h2000 +i*4, 32'h0000_0010);
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);
end
assert(hwif_out.r2.a.value == 'h11);
assert(hwif_out.r2.b.value == 'h0);
assert(hwif_out.r2.c.value == 'h1);
// Write values
cpuif.write(0, 32'h8000_0002);
for(int i=0; i<2*3*4; i++) begin
cpuif.write('h10+i*8, i+'h110a);
end
cpuif.write('h1000, 32'h0000_0000);
for(int i=0; i<33; i++) begin
cpuif.write('h2000 +i*4, i << 4);
end
// Assert value via frontdoor
cpuif.assert_read(0, 32'h8000_0002);
for(int i=0; i<2*3*4; i++) begin
cpuif.assert_read('h10+i*8, i+'h10a);
end
cpuif.assert_read('h1000, 32'h0000_0000);
for(int i=0; i<33; i++) begin
cpuif.assert_read('h2000 +i*4, (i << 4) & 'hF0);
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);
end
assert(hwif_out.r2.a.value == 'h0);
assert(hwif_out.r2.b.value == 'h0);
assert(hwif_out.r2.c.value == 'h0);
{% endblock %}

View File

@@ -0,0 +1,9 @@
from parameterized import parameterized_class
from ..lib.regblock_testcase import RegblockTestCase
from ..lib.test_params import TEST_PARAMS
@parameterized_class(TEST_PARAMS)
class Test(RegblockTestCase):
def test_dut(self):
self.run_test()

View File

View File

@@ -0,0 +1,17 @@
addrmap top {
default regwidth = 8;
reg {
field {
sw=r; hw=w;
swacc;
} f[8];
} r1;
reg {
field {
sw=rw; hw=r;
swmod;
} f[8] = 20;
} r2;
};

View File

@@ -0,0 +1,66 @@
{% extends "lib/templates/tb_base.sv" %}
{% block seq %}
logic [7:0] rd_data;
logic [7:0] latched_data;
int event_count;
latched_data = 'x;
##1;
cb.rst <= '0;
##1;
// Verify that hwif gets sampled at the same cycle as swacc strobe
cb.hwif_in.r1.f.value <= 'h10;
@cb;
event_count = 0;
fork
begin
##0;
forever begin
cb.hwif_in.r1.f.value <= cb.hwif_in.r1.f.value + 1;
@cb;
if(cb.hwif_out.r1.f.swacc) begin
latched_data = cb.hwif_in.r1.f.value;
event_count++;
end
end
end
begin
cpuif.read('h0, rd_data);
@cb;
end
join_any
disable fork;
assert(rd_data == latched_data) else $error("Read returned 0x%0x but swacc strobed during 0x%0x", rd_data, latched_data);
assert(event_count == 1) else $error("Observed excess swacc events: %0d", event_count);
// Verify that hwif changes 1 cycle after swmod
fork
begin
##0;
forever begin
assert(hwif_out.r2.f.value == 20);
if(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);
@cb;
end
end
begin
cpuif.write('h1, 21);
@cb;
end
join_any
disable fork;
// TODO: verify some other atypical swmod (onread actions)
{% 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,9 +0,0 @@
-quiet
# Free version of ModelSim errors if generate statements are not used.
# These have been made optional long ago. Modern versions of SystemVerilog do
# not require them.
-suppress 2720
# Ignore warning about vopt-time checking of always_comb/always_latch
-suppress 2583

View File

@@ -1,45 +0,0 @@
onerror {resume}
quietly WaveActivateNextPane {} 0
add wave -noupdate /tb/rst
add wave -noupdate /tb/clk
add wave -noupdate /tb/apb/PSEL
add wave -noupdate /tb/apb/PENABLE
add wave -noupdate /tb/apb/PWRITE
add wave -noupdate /tb/apb/PADDR
add wave -noupdate /tb/apb/PWDATA
add wave -noupdate /tb/apb/PRDATA
add wave -noupdate /tb/apb/PREADY
add wave -noupdate /tb/apb/PSLVERR
add wave -noupdate -divider DUT
add wave -noupdate /tb/dut/cpuif_req
add wave -noupdate /tb/dut/cpuif_req_is_wr
add wave -noupdate /tb/dut/cpuif_addr
add wave -noupdate /tb/dut/cpuif_wr_data
add wave -noupdate /tb/dut/cpuif_wr_biten
add wave -noupdate /tb/dut/cpuif_rd_ack
add wave -noupdate /tb/dut/cpuif_rd_data
add wave -noupdate /tb/dut/cpuif_rd_err
add wave -noupdate /tb/dut/cpuif_wr_ack
add wave -noupdate /tb/dut/cpuif_wr_err
add wave -noupdate -divider Storage
add wave -noupdate -radix hexadecimal -childformat {{/tb/dut/field_storage.r0 -radix hexadecimal -childformat {{/tb/dut/field_storage.r0.a -radix hexadecimal} {/tb/dut/field_storage.r0.b -radix hexadecimal} {/tb/dut/field_storage.r0.c -radix hexadecimal}}} {/tb/dut/field_storage.r1 -radix hexadecimal -childformat {{/tb/dut/field_storage.r1.a -radix hexadecimal} {/tb/dut/field_storage.r1.b -radix hexadecimal} {/tb/dut/field_storage.r1.c -radix hexadecimal}}} {/tb/dut/field_storage.r2 -radix hexadecimal -childformat {{/tb/dut/field_storage.r2.a -radix hexadecimal} {/tb/dut/field_storage.r2.b -radix hexadecimal} {/tb/dut/field_storage.r2.c -radix hexadecimal}}}} -expand -subitemconfig {/tb/dut/field_storage.r0 {-height 17 -radix hexadecimal -childformat {{/tb/dut/field_storage.r0.a -radix hexadecimal} {/tb/dut/field_storage.r0.b -radix hexadecimal} {/tb/dut/field_storage.r0.c -radix hexadecimal}} -expand} /tb/dut/field_storage.r0.a {-height 17 -radix hexadecimal} /tb/dut/field_storage.r0.b {-height 17 -radix hexadecimal} /tb/dut/field_storage.r0.c {-height 17 -radix hexadecimal} /tb/dut/field_storage.r1 {-height 17 -radix hexadecimal -childformat {{/tb/dut/field_storage.r1.a -radix hexadecimal} {/tb/dut/field_storage.r1.b -radix hexadecimal} {/tb/dut/field_storage.r1.c -radix hexadecimal}} -expand} /tb/dut/field_storage.r1.a {-height 17 -radix hexadecimal} /tb/dut/field_storage.r1.b {-height 17 -radix hexadecimal} /tb/dut/field_storage.r1.c {-height 17 -radix hexadecimal} /tb/dut/field_storage.r2 {-height 17 -radix hexadecimal -childformat {{/tb/dut/field_storage.r2.a -radix hexadecimal} {/tb/dut/field_storage.r2.b -radix hexadecimal} {/tb/dut/field_storage.r2.c -radix hexadecimal}} -expand} /tb/dut/field_storage.r2.a {-height 17 -radix hexadecimal} /tb/dut/field_storage.r2.b {-height 17 -radix hexadecimal} /tb/dut/field_storage.r2.c {-height 17 -radix hexadecimal}} /tb/dut/field_storage
add wave -noupdate -divider HWIF
add wave -noupdate -radix hexadecimal -childformat {{/tb/dut/hwif_out.r0 -radix hexadecimal -childformat {{/tb/dut/hwif_out.r0.a -radix hexadecimal -childformat {{/tb/dut/hwif_out.r0.a.value -radix hexadecimal} {/tb/dut/hwif_out.r0.a.anded -radix hexadecimal}}} {/tb/dut/hwif_out.r0.b -radix hexadecimal -childformat {{/tb/dut/hwif_out.r0.b.value -radix hexadecimal} {/tb/dut/hwif_out.r0.b.ored -radix hexadecimal}}} {/tb/dut/hwif_out.r0.c -radix hexadecimal -childformat {{/tb/dut/hwif_out.r0.c.value -radix hexadecimal} {/tb/dut/hwif_out.r0.c.swmod -radix hexadecimal}}}}} {/tb/dut/hwif_out.r1 -radix hexadecimal -childformat {{/tb/dut/hwif_out.r1.a -radix hexadecimal} {/tb/dut/hwif_out.r1.b -radix hexadecimal} {/tb/dut/hwif_out.r1.c -radix hexadecimal}}} {/tb/dut/hwif_out.r2 -radix hexadecimal -childformat {{/tb/dut/hwif_out.r2.a -radix hexadecimal} {/tb/dut/hwif_out.r2.b -radix hexadecimal} {/tb/dut/hwif_out.r2.c -radix hexadecimal}}}} -expand -subitemconfig {/tb/dut/hwif_out.r0 {-height 17 -radix hexadecimal -childformat {{/tb/dut/hwif_out.r0.a -radix hexadecimal -childformat {{/tb/dut/hwif_out.r0.a.value -radix hexadecimal} {/tb/dut/hwif_out.r0.a.anded -radix hexadecimal}}} {/tb/dut/hwif_out.r0.b -radix hexadecimal -childformat {{/tb/dut/hwif_out.r0.b.value -radix hexadecimal} {/tb/dut/hwif_out.r0.b.ored -radix hexadecimal}}} {/tb/dut/hwif_out.r0.c -radix hexadecimal -childformat {{/tb/dut/hwif_out.r0.c.value -radix hexadecimal} {/tb/dut/hwif_out.r0.c.swmod -radix hexadecimal}}}} -expand} /tb/dut/hwif_out.r0.a {-height 17 -radix hexadecimal -childformat {{/tb/dut/hwif_out.r0.a.value -radix hexadecimal} {/tb/dut/hwif_out.r0.a.anded -radix hexadecimal}} -expand} /tb/dut/hwif_out.r0.a.value {-height 17 -radix hexadecimal} /tb/dut/hwif_out.r0.a.anded {-height 17 -radix hexadecimal} /tb/dut/hwif_out.r0.b {-height 17 -radix hexadecimal -childformat {{/tb/dut/hwif_out.r0.b.value -radix hexadecimal} {/tb/dut/hwif_out.r0.b.ored -radix hexadecimal}} -expand} /tb/dut/hwif_out.r0.b.value {-height 17 -radix hexadecimal} /tb/dut/hwif_out.r0.b.ored {-height 17 -radix hexadecimal} /tb/dut/hwif_out.r0.c {-height 17 -radix hexadecimal -childformat {{/tb/dut/hwif_out.r0.c.value -radix hexadecimal} {/tb/dut/hwif_out.r0.c.swmod -radix hexadecimal}} -expand} /tb/dut/hwif_out.r0.c.value {-height 17 -radix hexadecimal} /tb/dut/hwif_out.r0.c.swmod {-height 17 -radix hexadecimal} /tb/dut/hwif_out.r1 {-height 17 -radix hexadecimal -childformat {{/tb/dut/hwif_out.r1.a -radix hexadecimal} {/tb/dut/hwif_out.r1.b -radix hexadecimal} {/tb/dut/hwif_out.r1.c -radix hexadecimal}} -expand} /tb/dut/hwif_out.r1.a {-height 17 -radix hexadecimal} /tb/dut/hwif_out.r1.b {-height 17 -radix hexadecimal} /tb/dut/hwif_out.r1.c {-height 17 -radix hexadecimal} /tb/dut/hwif_out.r2 {-height 17 -radix hexadecimal -childformat {{/tb/dut/hwif_out.r2.a -radix hexadecimal} {/tb/dut/hwif_out.r2.b -radix hexadecimal} {/tb/dut/hwif_out.r2.c -radix hexadecimal}} -expand} /tb/dut/hwif_out.r2.a {-height 17 -radix hexadecimal} /tb/dut/hwif_out.r2.b {-height 17 -radix hexadecimal} /tb/dut/hwif_out.r2.c {-height 17 -radix hexadecimal}} /tb/dut/hwif_out
TreeUpdate [SetDefaultTree]
WaveRestoreCursors {{Cursor 1} {650000 ps} 0}
quietly wave cursor active 1
configure wave -namecolwidth 150
configure wave -valuecolwidth 100
configure wave -justifyvalue left
configure wave -signalnamewidth 0
configure wave -snapdistance 10
configure wave -datasetprefix 0
configure wave -rowmargin 4
configure wave -childrowmargin 2
configure wave -gridoffset 0
configure wave -gridperiod 1
configure wave -griddelta 40
configure wave -timeline 0
configure wave -timelineunits ps
update
WaveRestoreZoom {252900 ps} {755184 ps}