Rework cpuif to support transaction pipelining. Add more docs. update simulator
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 /*;",
|
||||
@@ -1,4 +1,4 @@
|
||||
pytest
|
||||
parameterized
|
||||
pytest-xdist
|
||||
pytest-parallel
|
||||
jinja2-simple-tags
|
||||
|
||||
0
test/test_pipelined_cpuif/__init__.py
Normal file
0
test/test_pipelined_cpuif/__init__.py
Normal file
8
test/test_pipelined_cpuif/regblock.rdl
Normal file
8
test/test_pipelined_cpuif/regblock.rdl
Normal file
@@ -0,0 +1,8 @@
|
||||
addrmap regblock {
|
||||
default sw=rw;
|
||||
default hw=r;
|
||||
|
||||
reg {
|
||||
field {} x[31:0] = 0;
|
||||
} x[64] @ 0 += 4;
|
||||
};
|
||||
50
test/test_pipelined_cpuif/tb_template.sv
Normal file
50
test/test_pipelined_cpuif/tb_template.sv
Normal 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 %}
|
||||
9
test/test_pipelined_cpuif/testcase.py
Normal file
9
test/test_pipelined_cpuif/testcase.py
Normal 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()
|
||||
Reference in New Issue
Block a user