Rework cpuif to support transaction pipelining. Add more docs. update simulator

This commit is contained in:
Alex Mykyta
2022-02-13 17:25:31 -08:00
parent de5eecf0e7
commit d0ba488904
21 changed files with 493 additions and 68 deletions

View File

@@ -1,12 +1,14 @@
# Test Dependencies
## ModelSim
## Questa
Testcases require an installation of ModelSim/QuestaSim, and for `vlog` & `vsim`
Testcases require an installation of the Questa simulator, 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.
*Questa - Intel FPGA Starter Edition* can be downloaded for free from
https://fpgasoftware.intel.com/ and is sufficient to run unit tests. You will need
to generate a free license file to unlock the software: https://licensing.intel.com/psg/s/sales-signup-evaluationlicenses
## Python Packages
@@ -19,7 +21,7 @@ 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.
Use `pytest --workers auto` to run tests in parallel.
To run all tests:
```bash

View File

@@ -40,7 +40,7 @@ interface apb3_intf_driver #(
input PSLVERR;
endclocking
task reset();
task automatic reset();
cb.PSEL <= '0;
cb.PENABLE <= '0;
cb.PWRITE <= '0;
@@ -48,7 +48,10 @@ interface apb3_intf_driver #(
cb.PWDATA <= '0;
endtask
task write(logic [ADDR_WIDTH-1:0] addr, logic [DATA_WIDTH-1:0] data);
semaphore txn_mutex = new(1);
task automatic write(logic [ADDR_WIDTH-1:0] addr, logic [DATA_WIDTH-1:0] data);
txn_mutex.get();
##0;
// Initiate transfer
@@ -66,9 +69,11 @@ interface apb3_intf_driver #(
// Wait for response
while(cb.PREADY !== 1'b1) @(cb);
reset();
txn_mutex.put();
endtask
task read(logic [ADDR_WIDTH-1:0] addr, output logic [DATA_WIDTH-1:0] data);
task automatic read(logic [ADDR_WIDTH-1:0] addr, output logic [DATA_WIDTH-1:0] data);
txn_mutex.get();
##0;
// Initiate transfer
@@ -89,9 +94,10 @@ interface apb3_intf_driver #(
assert(!$isunknown(cb.PSLVERR)) else $error("Read from 0x%0x returned X's on PSLVERR", addr);
data = cb.PRDATA;
reset();
txn_mutex.put();
endtask
task assert_read(logic [ADDR_WIDTH-1:0] addr, logic [DATA_WIDTH-1:0] expected_data, logic [DATA_WIDTH-1:0] mask = '1);
task automatic assert_read(logic [ADDR_WIDTH-1:0] addr, logic [DATA_WIDTH-1:0] expected_data, logic [DATA_WIDTH-1:0] mask = '1);
logic [DATA_WIDTH-1:0] data;
read(addr, data);
data &= mask;

View File

@@ -77,7 +77,7 @@ interface axi4lite_intf_driver #(
input RRESP;
endclocking
task reset();
task automatic reset();
cb.AWVALID <= '0;
cb.AWADDR <= '0;
cb.AWPROT <= '0;
@@ -95,13 +95,20 @@ interface axi4lite_intf_driver #(
@cb;
end
task write(logic [ADDR_WIDTH-1:0] addr, logic [DATA_WIDTH-1:0] data);
semaphore txn_aw_mutex = new(1);
semaphore txn_w_mutex = new(1);
semaphore txn_b_mutex = new(1);
semaphore txn_ar_mutex = new(1);
semaphore txn_r_mutex = new(1);
task automatic write(logic [ADDR_WIDTH-1:0] addr, logic [DATA_WIDTH-1:0] data);
bit w_before_aw;
w_before_aw = $urandom_range(1,0);
##0;
fork
begin
txn_aw_mutex.get();
##0;
if(w_before_aw) repeat($urandom_range(2,0)) @cb;
cb.AWVALID <= '1;
cb.AWADDR <= addr;
@@ -109,9 +116,12 @@ interface axi4lite_intf_driver #(
@(cb);
while(cb.AWREADY !== 1'b1) @(cb);
cb.AWVALID <= '0;
txn_aw_mutex.put();
end
begin
txn_w_mutex.get();
##0;
if(!w_before_aw) repeat($urandom_range(2,0)) @cb;
cb.WVALID <= '1;
cb.WDATA <= data;
@@ -120,39 +130,47 @@ interface axi4lite_intf_driver #(
while(cb.WREADY !== 1'b1) @(cb);
cb.WVALID <= '0;
cb.WSTRB <= '0;
txn_w_mutex.put();
end
begin
txn_b_mutex.get();
@cb;
while(cb.BREADY !== 1'b1 && cb.BVALID !== 1'b1) @(cb);
assert(!$isunknown(cb.BRESP)) else $error("Read from 0x%0x returned X's on BRESP", addr);
txn_b_mutex.put();
end
join
endtask
task read(logic [ADDR_WIDTH-1:0] addr, output logic [DATA_WIDTH-1:0] data);
##0;
task automatic read(logic [ADDR_WIDTH-1:0] addr, output logic [DATA_WIDTH-1:0] data);
fork
begin
txn_ar_mutex.get();
##0;
cb.ARVALID <= '1;
cb.ARADDR <= addr;
cb.ARPROT <= '0;
@(cb);
while(cb.ARREADY !== 1'b1) @(cb);
cb.ARVALID <= '0;
txn_ar_mutex.put();
end
begin
txn_r_mutex.get();
@cb;
while(!(cb.RREADY === 1'b1 && cb.RVALID === 1'b1)) @(cb);
assert(!$isunknown(cb.RDATA)) else $error("Read from 0x%0x returned X's on RDATA", addr);
assert(!$isunknown(cb.RRESP)) else $error("Read from 0x%0x returned X's on RRESP", addr);
data = cb.RDATA;
txn_r_mutex.put();
end
join
endtask
task assert_read(logic [ADDR_WIDTH-1:0] addr, logic [DATA_WIDTH-1:0] expected_data, logic [DATA_WIDTH-1:0] mask = '1);
task automatic assert_read(logic [ADDR_WIDTH-1:0] addr, logic [DATA_WIDTH-1:0] expected_data, logic [DATA_WIDTH-1:0] mask = '1);
logic [DATA_WIDTH-1:0] data;
read(addr, data);
data &= mask;

View File

@@ -9,6 +9,8 @@ interface passthrough_driver #(
output logic m_cpuif_req_is_wr,
output logic [ADDR_WIDTH-1:0] m_cpuif_addr,
output logic [DATA_WIDTH-1:0] m_cpuif_wr_data,
input wire m_cpuif_req_stall_wr,
input wire m_cpuif_req_stall_rd,
input wire m_cpuif_rd_ack,
input wire m_cpuif_rd_err,
input wire [DATA_WIDTH-1:0] m_cpuif_rd_data,
@@ -25,6 +27,8 @@ interface passthrough_driver #(
output m_cpuif_req_is_wr;
output m_cpuif_addr;
output m_cpuif_wr_data;
input m_cpuif_req_stall_wr;
input m_cpuif_req_stall_rd;
input m_cpuif_rd_ack;
input m_cpuif_rd_err;
input m_cpuif_rd_data;
@@ -32,47 +36,70 @@ interface passthrough_driver #(
input m_cpuif_wr_err;
endclocking
task reset();
task automatic reset();
cb.m_cpuif_req <= '0;
cb.m_cpuif_req_is_wr <= '0;
cb.m_cpuif_addr <= '0;
cb.m_cpuif_wr_data <= '0;
endtask
task write(logic [ADDR_WIDTH-1:0] addr, logic [DATA_WIDTH-1:0] data);
##0;
semaphore txn_req_mutex = new(1);
semaphore txn_resp_mutex = new(1);
// Initiate transfer
cb.m_cpuif_req <= '1;
cb.m_cpuif_req_is_wr <= '1;
cb.m_cpuif_addr <= addr;
cb.m_cpuif_wr_data <= data;
@(cb);
reset();
task automatic write(logic [ADDR_WIDTH-1:0] addr, logic [DATA_WIDTH-1:0] data);
fork
begin
// Initiate transfer
txn_req_mutex.get();
##0;
cb.m_cpuif_req <= '1;
cb.m_cpuif_req_is_wr <= '1;
cb.m_cpuif_addr <= addr;
cb.m_cpuif_wr_data <= data;
@(cb);
while(cb.m_cpuif_req_stall_wr !== 1'b0) @(cb);
reset();
txn_req_mutex.put();
end
// Wait for response
while(cb.m_cpuif_wr_ack !== 1'b1) @(cb);
reset();
begin
// Wait for response
txn_resp_mutex.get();
@cb;
while(cb.m_cpuif_wr_ack !== 1'b1) @(cb);
txn_resp_mutex.put();
end
join
endtask
task read(logic [ADDR_WIDTH-1:0] addr, output logic [DATA_WIDTH-1:0] data);
##0;
task automatic read(logic [ADDR_WIDTH-1:0] addr, output logic [DATA_WIDTH-1:0] data);
fork
begin
// Initiate transfer
txn_req_mutex.get();
##0;
cb.m_cpuif_req <= '1;
cb.m_cpuif_req_is_wr <= '0;
cb.m_cpuif_addr <= addr;
@(cb);
while(cb.m_cpuif_req_stall_rd !== 1'b0) @(cb);
reset();
txn_req_mutex.put();
end
// Initiate transfer
cb.m_cpuif_req <= '1;
cb.m_cpuif_req_is_wr <= '0;
cb.m_cpuif_addr <= addr;
@(cb);
reset();
// Wait for response
while(cb.m_cpuif_rd_ack !== 1'b1) @(cb);
assert(!$isunknown(cb.m_cpuif_rd_data)) else $error("Read from 0x%0x returned X's on m_cpuif_rd_data", addr);
data = cb.m_cpuif_rd_data;
reset();
begin
// Wait for response
txn_resp_mutex.get();
@cb;
while(cb.m_cpuif_rd_ack !== 1'b1) @(cb);
assert(!$isunknown(cb.m_cpuif_rd_data)) else $error("Read from 0x%0x returned X's on m_cpuif_rd_data", addr);
data = cb.m_cpuif_rd_data;
txn_resp_mutex.put();
end
join
endtask
task assert_read(logic [ADDR_WIDTH-1:0] addr, logic [DATA_WIDTH-1:0] expected_data, logic [DATA_WIDTH-1:0] mask = '1);
task automatic assert_read(logic [ADDR_WIDTH-1:0] addr, logic [DATA_WIDTH-1:0] expected_data, logic [DATA_WIDTH-1:0] mask = '1);
logic [DATA_WIDTH-1:0] data;
read(addr, data);
data &= mask;

View File

@@ -3,6 +3,8 @@ wire s_cpuif_req;
wire s_cpuif_req_is_wr;
wire [{{exporter.cpuif.addr_width-1}}:0] s_cpuif_addr;
wire [{{exporter.cpuif.data_width-1}}:0] s_cpuif_wr_data;
wire s_cpuif_req_stall_wr;
wire s_cpuif_req_stall_rd;
wire s_cpuif_rd_ack;
wire s_cpuif_rd_err;
wire [{{exporter.cpuif.data_width-1}}:0] s_cpuif_rd_data;
@@ -18,6 +20,8 @@ passthrough_driver #(
.m_cpuif_req_is_wr(s_cpuif_req_is_wr),
.m_cpuif_addr(s_cpuif_addr),
.m_cpuif_wr_data(s_cpuif_wr_data),
.m_cpuif_req_stall_wr(s_cpuif_req_stall_wr),
.m_cpuif_req_stall_rd(s_cpuif_req_stall_rd),
.m_cpuif_rd_ack(s_cpuif_rd_ack),
.m_cpuif_rd_err(s_cpuif_rd_err),
.m_cpuif_rd_data(s_cpuif_rd_data),

View File

@@ -16,7 +16,7 @@ from peakrdl.regblock import RegblockExporter
from .cpuifs.base import CpuifTestMode
from .cpuifs.apb3 import APB3
from .simulators.modelsim import ModelSim
from .simulators.questa import Questa
class RegblockTestCase(unittest.TestCase):
@@ -40,9 +40,9 @@ class RegblockTestCase(unittest.TestCase):
retime_read_response = False
#: Abort test if it exceeds this number of clock cycles
timeout_clk_cycles = 1000
timeout_clk_cycles = 5000
simulator_cls = ModelSim
simulator_cls = Questa
#: this gets auto-loaded via the _load_request autouse fixture
request = None # type: pytest.FixtureRequest

View File

@@ -4,27 +4,21 @@ import os
from . import Simulator
class ModelSim(Simulator):
class Questa(Simulator):
def compile(self) -> None:
cmd = [
"vlog", "-sv", "-quiet", "-l", "build.log",
"+incdir+%s" % os.path.join(os.path.dirname(__file__), ".."),
# 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",
# Use strict LRM conformance
"-svinputport=net",
# all warnings are errors
"-warning", "error",
# except this one.. TODO: figure out if I can avoid this
"-suppress", "13314",
# Ignore noisy warning about vopt-time checking of always_comb/always_latch
"-suppress", "2583",
]
# Add source files
@@ -42,6 +36,7 @@ class ModelSim(Simulator):
# call vsim
cmd = [
"vsim", "-quiet",
"-voptargs=+acc",
"-msgmode", "both",
"-do", "set WildcardFilter [lsearch -not -all -inline $WildcardFilter Memory]",
"-do", "log -r /*;",

View File

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

View File

View File

@@ -0,0 +1,8 @@
addrmap regblock {
default sw=rw;
default hw=r;
reg {
field {} x[31:0] = 0;
} x[64] @ 0 += 4;
};

View File

@@ -0,0 +1,50 @@
{% extends "lib/tb_base.sv" %}
{% block seq %}
{% sv_line_anchor %}
##1;
cb.rst <= '0;
##1;
// Write all regs in parallel burst
for(int i=0; i<64; i++) begin
fork
automatic int i_fk = i;
begin
cpuif.write(i_fk*4, i_fk + 32'h12340000);
end
join_none
end
wait fork;
// Verify HW value
@cb;
for(int i=0; i<64; i++) begin
assert(cb.hwif_out.x[i].x.value == i + 32'h12340000)
else $error("hwif_out.x[i] == 0x%0x. Expected 0x%0x", cb.hwif_out.x[i].x.value, i + 32'h12340000);
end
// Read all regs in parallel burst
for(int i=0; i<64; i++) begin
fork
automatic int i_fk = i;
begin
cpuif.assert_read(i_fk*4, i_fk + 32'h12340000);
end
join_none
end
wait fork;
// Mix read/writes
for(int i=0; i<64; i++) begin
fork
automatic int i_fk = i;
begin
cpuif.write(i_fk*4, i_fk + 32'h56780000);
cpuif.assert_read(i_fk*4, i_fk + 32'h56780000);
end
join_none
end
wait fork;
{% 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()