Add support for external components. (#4 & #36)

This commit is contained in:
Alex Mykyta
2023-05-03 21:57:25 -07:00
parent f1a75f8d38
commit ca9185dac7
35 changed files with 1341 additions and 78 deletions

View File

@@ -36,6 +36,7 @@ class BaseTestCase(unittest.TestCase):
retime_read_fanin = False
retime_read_response = False
reuse_hwif_typedefs = True
retime_external = False
#: this gets auto-loaded via the _load_request autouse fixture
request = None # type: pytest.FixtureRequest
@@ -105,7 +106,11 @@ class BaseTestCase(unittest.TestCase):
cpuif_cls=cls.cpuif.cpuif_cls,
retime_read_fanin=cls.retime_read_fanin,
retime_read_response=cls.retime_read_response,
reuse_hwif_typedefs=cls.reuse_hwif_typedefs
reuse_hwif_typedefs=cls.reuse_hwif_typedefs,
retime_external_reg=cls.retime_external,
retime_external_regfile=cls.retime_external,
retime_external_mem=cls.retime_external,
retime_external_addrmap=cls.retime_external,
)
@classmethod

View File

@@ -0,0 +1,73 @@
module external_block #(
parameter WIDTH = 32,
parameter ADDR_WIDTH = 8
)(
input wire clk,
input wire rst,
input wire req,
input wire req_is_wr,
input wire [ADDR_WIDTH-1:0] addr,
input wire [WIDTH-1:0] wr_data,
input wire [WIDTH-1:0] wr_biten,
output logic rd_ack,
output logic [WIDTH-1:0] rd_data,
output logic wr_ack
);
timeunit 1ns;
timeprecision 1ps;
localparam ADDR_SHIFT = $clog2(WIDTH/8);
localparam N_ENTRIES = 2**(ADDR_WIDTH - ADDR_SHIFT);
logic [WIDTH-1:0] mem[N_ENTRIES];
task do_write(int idx, logic [WIDTH-1:0] data, logic [WIDTH-1:0] biten);
automatic int delay;
// Random delay
delay = $urandom_range(3,0);
repeat(delay) @(posedge clk)
$info("Write delay: %d", delay);
for(int b=0; b<WIDTH; b++) begin
if(biten[b]) mem[idx][b] <= data[b];
end
wr_ack <= '1;
endtask
task do_read(int idx);
automatic int delay;
// Random delay
delay = $urandom_range(3,0);
repeat(delay) @(posedge clk)
$info("Read delay: %d", delay);
rd_data <= mem[idx];
rd_ack <= '1;
endtask;
initial begin
rd_ack <= '0;
rd_data <= '0;
wr_ack <= '0;
for(int i=0; i<N_ENTRIES; i++) mem[i] <= '0;
forever begin
// Wait for next clock edge
@(posedge clk);
rd_ack <= '0;
rd_data <= '0;
wr_ack <= '0;
// wait slightly longer to "peek" at the current cycle's state
#1ns;
if(!rst && req) begin
if(req_is_wr) do_write(addr >> ADDR_SHIFT, wr_data, wr_biten);
else do_read(addr >> ADDR_SHIFT);
end
end
end
endmodule

78
tests/lib/external_reg.sv Normal file
View File

@@ -0,0 +1,78 @@
module external_reg #(
parameter WIDTH = 32,
parameter SUBWORDS = 1
)(
input wire clk,
input wire rst,
input wire [SUBWORDS-1:0] req,
input wire req_is_wr,
input wire [WIDTH-1:0] wr_data,
input wire [WIDTH-1:0] wr_biten,
output logic rd_ack,
output logic [WIDTH-1:0] rd_data,
output logic wr_ack
);
timeunit 1ns;
timeprecision 1ps;
logic [SUBWORDS-1:0][WIDTH-1:0] value;
task do_write(logic [SUBWORDS-1:0] strb, logic [WIDTH-1:0] data, logic [WIDTH-1:0] biten);
automatic int delay;
// Random delay
delay = $urandom_range(3,0);
repeat(delay) @(posedge clk)
$info("Write delay: %d", delay);
for(int i=0; i<SUBWORDS; i++) begin
if(strb[i]) begin
for(int b=0; b<WIDTH; b++) begin
if(biten[b]) value[i][b] <= data[b];
end
end
end
wr_ack <= '1;
endtask
task do_read(logic [SUBWORDS-1:0] strb);
automatic int delay;
// Random delay
delay = $urandom_range(3,0);
repeat(delay) @(posedge clk)
$info("Read delay: %d", delay);
for(int i=0; i<SUBWORDS; i++) begin
if(strb[i]) begin
rd_data <= value[i];
end
end
rd_ack <= '1;
endtask;
initial begin
rd_ack <= '0;
rd_data <= '0;
wr_ack <= '0;
value <= '0;
forever begin
// Wait for next clock edge
@(posedge clk);
rd_ack <= '0;
rd_data <= '0;
wr_ack <= '0;
// wait slightly longer to "peek" at the current cycle's state
#1ns;
if(!rst && req) begin
if(req_is_wr) do_write(req, wr_data, wr_biten);
else do_read(req);
end
end
end
endmodule

View File

@@ -22,6 +22,25 @@ class SimTestCase(BaseTestCase):
tb_template_file = "tb_template.sv"
# Paths are relative to the testcase dir
extra_tb_files = [] # type: List[str]
# Whether to initialize the hwif_in struct at test startup
init_hwif_in = True
# Control whether to include in clocking block
clocking_hwif_in = True
clocking_hwif_out = True
@classmethod
def get_extra_tb_files(cls) -> List[str]:
paths = []
for path in cls.extra_tb_files:
path = os.path.join(cls.get_testcase_dir(), path)
paths.append(path)
return paths
@classmethod
def _generate_tb(cls):
"""

View File

@@ -13,6 +13,7 @@ class Simulator:
def tb_files(self) -> List[str]:
files = []
files.extend(self.testcase_cls.cpuif.get_sim_files())
files.extend(self.testcase_cls.get_extra_tb_files())
files.append("regblock_pkg.sv")
files.append("regblock.sv")
files.append("tb.sv")

View File

@@ -30,11 +30,11 @@ module tb;
default clocking cb @(posedge clk);
default input #1step output #1;
output rst;
{%- if exporter.hwif.has_input_struct %}
{%- if exporter.hwif.has_input_struct and cls.clocking_hwif_in %}
output hwif_in;
{%- endif %}
{%- if exporter.hwif.has_output_struct %}
{%- if exporter.hwif.has_output_struct and cls.clocking_hwif_out %}
input hwif_out;
{%- endif %}
@@ -68,12 +68,15 @@ module tb;
{%- endif %}
{% sv_line_anchor %}
{%- block dut_support %}
{%- endblock %}
//--------------------------------------------------------------------------
// Test Sequence
//--------------------------------------------------------------------------
initial begin
cb.rst <= '1;
{%- if exporter.hwif.has_input_struct %}
{%- if exporter.hwif.has_input_struct and cls.init_hwif_in %}
cb.hwif_in <= '{default: '0};
{%- endif %}

View File

@@ -492,7 +492,7 @@ valid-metaclass-classmethod-first-arg=cls
ignored-parents=
# Maximum number of arguments for function / method.
max-args=8
max-args=16
# Maximum number of attributes for a class (see R0902).
max-attributes=7

View File

View File

@@ -0,0 +1,29 @@
addrmap top {
reg my_reg {
field {sw=rw; hw=r;} whatever[32] = 0;
};
reg my_wide_reg {
regwidth = 64;
accesswidth = 32;
field {sw=rw; hw=r;} whatever = 0;
};
external my_reg ext_reg @ 0x00;
my_reg int_reg @ 0x04;
external my_wide_reg wide_ext_reg @ 0x10;
external my_reg ext_reg_array[32] @ 0x100 += 4;
external regfile {
my_reg placeholder @ 8*4-4;
} rf @ 0x1000;
addrmap {
my_reg placeholder @ 8*4-4;
} am @ 0x2000;
external mem {
memwidth = 32;
mementries = 8;
} mm @ 0x3000;
};

View File

@@ -0,0 +1,207 @@
{% extends "lib/tb_base.sv" %}
{%- block dut_support %}
{% sv_line_anchor %}
external_reg ext_reg_inst (
.clk(clk),
.rst(rst),
.req(hwif_out.ext_reg.req),
.req_is_wr(hwif_out.ext_reg.req_is_wr),
.wr_data(hwif_out.ext_reg.wr_data),
.wr_biten(hwif_out.ext_reg.wr_biten),
.rd_ack(hwif_in.ext_reg.rd_ack),
.rd_data(hwif_in.ext_reg.rd_data),
.wr_ack(hwif_in.ext_reg.wr_ack)
);
external_reg #(
.SUBWORDS(2)
) wide_ext_reg_inst (
.clk(clk),
.rst(rst),
.req(hwif_out.wide_ext_reg.req),
.req_is_wr(hwif_out.wide_ext_reg.req_is_wr),
.wr_data(hwif_out.wide_ext_reg.wr_data),
.wr_biten(hwif_out.wide_ext_reg.wr_biten),
.rd_ack(hwif_in.wide_ext_reg.rd_ack),
.rd_data(hwif_in.wide_ext_reg.rd_data),
.wr_ack(hwif_in.wide_ext_reg.wr_ack)
);
for(genvar i=0; i<32; i++) begin : array
external_reg ext_reg_inst (
.clk(clk),
.rst(rst),
.req(hwif_out.ext_reg_array[i].req),
.req_is_wr(hwif_out.ext_reg_array[i].req_is_wr),
.wr_data(hwif_out.ext_reg_array[i].wr_data),
.wr_biten(hwif_out.ext_reg_array[i].wr_biten),
.rd_ack(hwif_in.ext_reg_array[i].rd_ack),
.rd_data(hwif_in.ext_reg_array[i].rd_data),
.wr_ack(hwif_in.ext_reg_array[i].wr_ack)
);
end
external_block #(
.ADDR_WIDTH(5)
) rf_inst (
.clk(clk),
.rst(rst),
.req(hwif_out.rf.req),
.req_is_wr(hwif_out.rf.req_is_wr),
.addr(hwif_out.rf.addr),
.wr_data(hwif_out.rf.wr_data),
.wr_biten(hwif_out.rf.wr_biten),
.rd_ack(hwif_in.rf.rd_ack),
.rd_data(hwif_in.rf.rd_data),
.wr_ack(hwif_in.rf.wr_ack)
);
external_block #(
.ADDR_WIDTH(5)
) am_inst (
.clk(clk),
.rst(rst),
.req(hwif_out.am.req),
.req_is_wr(hwif_out.am.req_is_wr),
.addr(hwif_out.am.addr),
.wr_data(hwif_out.am.wr_data),
.wr_biten(hwif_out.am.wr_biten),
.rd_ack(hwif_in.am.rd_ack),
.rd_data(hwif_in.am.rd_data),
.wr_ack(hwif_in.am.wr_ack)
);
external_block #(
.ADDR_WIDTH(5)
) mm_inst (
.clk(clk),
.rst(rst),
.req(hwif_out.mm.req),
.req_is_wr(hwif_out.mm.req_is_wr),
.addr(hwif_out.mm.addr),
.wr_data(hwif_out.mm.wr_data),
.wr_biten(hwif_out.mm.wr_biten),
.rd_ack(hwif_in.mm.rd_ack),
.rd_data(hwif_in.mm.rd_data),
.wr_ack(hwif_in.mm.wr_ack)
);
{%- endblock %}
{% block seq %}
logic [31:0] x;
{% sv_line_anchor %}
##1;
cb.rst <= '0;
##1;
//--------------------------------------------------------------------------
// Simple read/write tests
//--------------------------------------------------------------------------
repeat(20) begin
x = $urandom();
cpuif.write('h00, x);
cpuif.assert_read('h00, x);
assert(ext_reg_inst.value == x);
end
for(int i=0; i<2; i++) begin
repeat(20) begin
x = $urandom();
cpuif.write('h10 + i*4, x);
cpuif.assert_read('h10 + i*4, x);
assert(wide_ext_reg_inst.value[i] == x);
end
end
for(int i=0; i<32; i++) begin
repeat(20) begin
x = $urandom();
cpuif.write('h100 + i*4, x);
cpuif.assert_read('h100 + i*4, x);
end
end
for(int i=0; i<8; i++) begin
repeat(20) begin
x = $urandom();
cpuif.write('h1000 + i*4, x);
cpuif.assert_read('h1000 + i*4, x);
assert(rf_inst.mem[i] == x);
end
end
for(int i=0; i<8; i++) begin
repeat(20) begin
x = $urandom();
cpuif.write('h2000 + i*4, x);
cpuif.assert_read('h2000 + i*4, x);
assert(am_inst.mem[i] == x);
end
end
for(int i=0; i<8; i++) begin
repeat(20) begin
x = $urandom();
cpuif.write('h3000 + i*4, x);
cpuif.assert_read('h3000 + i*4, x);
assert(mm_inst.mem[i] == x);
end
end
//--------------------------------------------------------------------------
// Pipelined access
//--------------------------------------------------------------------------
// init array with unique known value
cpuif.write('h4, 'h1234);
for(int i=0; i<32; i++) begin
cpuif.write('h100 + i*4, 'h100 + i);
end
for(int i=0; i<8; i++) begin
cpuif.write('h1000 + i*4, 'h1000 + i);
cpuif.write('h2000 + i*4, 'h2000 + i);
cpuif.write('h3000 + i*4, 'h3000 + i);
end
// random pipelined read/writes
repeat(256) begin
fork
begin
automatic int i, j;
i = $urandom_range(31, 0);
j = $urandom_range(7, 0);
case($urandom_range(9,0))
// external reg
0: cpuif.write('h100 + i*4, 'h100 + i);
1: cpuif.assert_read('h100 + i*4, 'h100 + i);
// internal reg
2: cpuif.write('h4, 'h1234);
3: cpuif.assert_read('h4, 'h1234);
// external regfile
4: cpuif.write('h1000 + j*4, 'h1000 + j);
5: cpuif.assert_read('h1000 + j*4, 'h1000 + j);
// external addrmap
6: cpuif.write('h2000 + j*4, 'h2000 + j);
7: cpuif.assert_read('h2000 + j*4, 'h2000 + j);
// external mem
8: cpuif.write('h3000 + j*4, 'h3000 + j);
9: cpuif.assert_read('h3000 + j*4, 'h3000 + j);
endcase
end
join_none
end
wait fork;
{% endblock %}

View File

@@ -0,0 +1,31 @@
from parameterized import parameterized_class
from ..lib.sim_testcase import SimTestCase
from ..lib.test_params import get_permutations
from ..lib.cpuifs.apb4 import APB4
from ..lib.cpuifs.axi4lite import AXI4Lite
from ..lib.cpuifs.passthrough import Passthrough
TEST_PARAMS = get_permutations({
"cpuif": [
APB4(),
AXI4Lite(),
Passthrough(),
],
"retime_read_fanin": [True, False],
"retime_read_response": [True, False],
"retime_external": [True, False],
})
@parameterized_class(TEST_PARAMS)
class Test(SimTestCase):
extra_tb_files = [
"../lib/external_reg.sv",
"../lib/external_block.sv",
]
init_hwif_in = False
clocking_hwif_in = False
timeout_clk_cycles = 30000
def test_dut(self):
self.run_test()