252 lines
7.2 KiB
Systemverilog
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
|