Files
gps/hardware/ip/max2771_spi/tb/max2771_spi_engine_tb.sv
T
2026-08-06 22:14:52 +03:00

252 lines
7.2 KiB
Systemverilog

`timescale 1ns/1ps
module max2771_spi_engine_case #(
parameter integer CLOCK_HZ = 50_000_000,
parameter integer CLOCK_PERIOD_NS = 20,
parameter integer EXPECTED_HALF_CYCLES = 7
) (
output reg done,
output reg failed
);
localparam [31:0] READ_VALUE = 32'hA596_3CC3;
localparam [31:0] WRITE_VALUE = 32'hBEA4_1603;
localparam realtime EXPECTED_HALF_NS = CLOCK_PERIOD_NS * EXPECTED_HALF_CYCLES;
reg clk = 1'b0;
reg rst_n = 1'b0;
reg start = 1'b0;
reg read_not_write = 1'b0;
reg [3:0] address = 4'd0;
reg [31:0] write_data = 32'd0;
wire [31:0] read_data;
wire busy;
wire ready;
wire sclk;
wire csn;
wire sdata_i;
wire sdata_o;
wire sdata_oe;
tri sdata;
reg slave_oe = 1'b0;
reg slave_data = 1'b0;
realtime previous_sclk_edge;
realtime csn_assert_time;
realtime last_rising_time;
realtime previous_csn_release;
integer edge_count;
assign sdata = sdata_oe ? sdata_o : 1'bz;
assign sdata = slave_oe ? slave_data : 1'bz;
assign sdata_i = sdata;
max2771_spi_engine #(
.CLOCK_HZ(CLOCK_HZ),
.MAX_SCLK_HZ(4_000_000)
) dut (
.clk(clk),
.rst_n(rst_n),
.start(start),
.read_not_write(read_not_write),
.address(address),
.write_data(write_data),
.read_data(read_data),
.busy(busy),
.ready(ready),
.sclk(sclk),
.csn(csn),
.sdata_i(sdata_i),
.sdata_o(sdata_o),
.sdata_oe(sdata_oe)
);
always #(CLOCK_PERIOD_NS / 2.0) clk = ~clk;
task automatic fail(input string message);
begin
$error("CLOCK_HZ=%0d: %s", CLOCK_HZ, message);
failed = 1'b1;
end
endtask
task automatic check(input bit condition, input string message);
begin
if (!condition)
fail(message);
end
endtask
task automatic launch(
input bit is_read,
input [3:0] register_address,
input [31:0] value
);
begin
wait (ready);
@(negedge clk);
read_not_write = is_read;
address = register_address;
write_data = value;
start = 1'b1;
@(negedge clk);
start = 1'b0;
wait (!csn);
end
endtask
task automatic check_edge_timing(input realtime now_time);
realtime delta;
begin
if (edge_count != 0) begin
delta = now_time - previous_sclk_edge;
if (delta != EXPECTED_HALF_NS)
fail($sformatf("SCLK half-period %0.3f ns, expected %0.3f ns",
delta, EXPECTED_HALF_NS));
end
previous_sclk_edge = now_time;
edge_count = edge_count + 1;
end
endtask
task automatic run_write;
reg [47:0] expected_frame;
integer bit_number;
begin
expected_frame = {8'd0, 4'h0, 1'b0, 3'd0, WRITE_VALUE};
edge_count = 0;
launch(1'b0, 4'h0, WRITE_VALUE);
csn_assert_time = $realtime;
if (previous_csn_release != 0.0)
check((csn_assert_time - previous_csn_release) >= 100.0,
"CSN inactive interval is shorter than 100 ns");
for (bit_number = 0; bit_number < 48; bit_number = bit_number + 1) begin
@(posedge sclk);
check_edge_timing($realtime);
if (bit_number == 0)
check(($realtime - csn_assert_time) >= 10.0,
"CSN setup is shorter than 10 ns");
check(sdata_oe === 1'b1, "host released SDATA during write frame");
check(sdata === expected_frame[47-bit_number],
$sformatf("write frame mismatch at bit %0d", bit_number));
check(sdata !== 1'bx, "SDATA contention detected during write");
last_rising_time = $realtime;
@(negedge sclk);
check_edge_timing($realtime);
end
#1;
check(csn === 1'b1, "CSN did not deassert after 48 clocks");
check(sdata_oe === 1'b0, "host did not release SDATA after write");
check(($realtime - 1.0 - last_rising_time) >= 10.0,
"CSN hold is shorter than 10 ns");
previous_csn_release = $realtime - 1.0;
end
endtask
task automatic run_read;
reg [47:0] expected_header;
integer bit_number;
begin
expected_header = {8'd0, 4'h9, 1'b1, 3'd0, 32'd0};
edge_count = 0;
launch(1'b1, 4'h9, 32'd0);
csn_assert_time = $realtime;
check((csn_assert_time - previous_csn_release) >= 100.0,
"CSN inactive interval is shorter than 100 ns");
for (bit_number = 0; bit_number < 48; bit_number = bit_number + 1) begin
@(posedge sclk);
check_edge_timing($realtime);
if (bit_number <= 12) begin
check(sdata_oe === 1'b1,
$sformatf("host released SDATA in read header at bit %0d", bit_number));
check(sdata === expected_header[47-bit_number],
$sformatf("read header mismatch at bit %0d", bit_number));
end else if (bit_number <= 15) begin
check(sdata_oe === 1'b0, "host drove SDATA during turnaround");
check(slave_oe === 1'b0, "slave drove SDATA during turnaround");
check(sdata === 1'bz, "SDATA was not high impedance during turnaround");
end else begin
check(sdata_oe === 1'b0, "host drove SDATA during read data");
check(slave_oe === 1'b1, "slave did not drive read data");
check(sdata === READ_VALUE[47-bit_number],
$sformatf("read data mismatch at bit %0d", bit_number));
check(sdata !== 1'bx, "SDATA contention detected during read");
end
last_rising_time = $realtime;
@(negedge sclk);
check_edge_timing($realtime);
if ((bit_number >= 15) && (bit_number < 47)) begin
slave_oe = 1'b1;
slave_data = READ_VALUE[46-bit_number];
end
end
slave_oe = 1'b0;
#1;
check(csn === 1'b1, "CSN did not deassert after read frame");
check(read_data === READ_VALUE,
$sformatf("read result 0x%08x, expected 0x%08x", read_data, READ_VALUE));
check(sdata_oe === 1'b0, "host did not keep SDATA as input after read");
previous_csn_release = $realtime - 1.0;
end
endtask
initial begin
done = 1'b0;
failed = 1'b0;
previous_csn_release = 0.0;
repeat (5) @(posedge clk);
rst_n = 1'b1;
repeat (2) @(posedge clk);
check(ready === 1'b1, "engine was not ready after reset");
check(csn === 1'b1, "CSN was not idle high after reset");
check(sclk === 1'b0, "SCLK was not idle low after reset");
check(sdata_oe === 1'b0, "SDATA was not an input after reset");
run_write();
run_read();
done = 1'b1;
end
endmodule
module max2771_spi_engine_tb;
wire done_50mhz;
wire failed_50mhz;
wire done_100mhz;
wire failed_100mhz;
max2771_spi_engine_case #(
.CLOCK_HZ(50_000_000),
.CLOCK_PERIOD_NS(20),
.EXPECTED_HALF_CYCLES(7)
) case_50mhz (
.done(done_50mhz),
.failed(failed_50mhz)
);
max2771_spi_engine_case #(
.CLOCK_HZ(100_000_000),
.CLOCK_PERIOD_NS(10),
.EXPECTED_HALF_CYCLES(13)
) case_100mhz (
.done(done_100mhz),
.failed(failed_100mhz)
);
initial begin
wait (done_50mhz && done_100mhz);
if (failed_50mhz || failed_100mhz)
$fatal(1, "MAX2771 SPI engine test FAILED");
$display("MAX2771 SPI engine test PASSED: 50 MHz -> 3.571 MHz, 100 MHz -> 3.846 MHz");
$finish;
end
initial begin
#100_000;
$fatal(1, "MAX2771 SPI engine test timed out");
end
endmodule