209 lines
5.5 KiB
Coq
209 lines
5.5 KiB
Coq
|
|
`timescale 1ns/1ps
|
||
|
|
|
||
|
|
module tb_i2c_master;
|
||
|
|
reg clk;
|
||
|
|
reg rst_n;
|
||
|
|
reg [6:0] addr;
|
||
|
|
reg rw;
|
||
|
|
reg [7:0] data_in;
|
||
|
|
reg start_tx;
|
||
|
|
|
||
|
|
tri1 scl;
|
||
|
|
tri1 sda;
|
||
|
|
|
||
|
|
reg slave_scl_low;
|
||
|
|
reg slave_sda_low;
|
||
|
|
reg nack_first_ack;
|
||
|
|
reg arbitration_request;
|
||
|
|
reg [7:0] read_value;
|
||
|
|
reg saw_arbitration_lost;
|
||
|
|
|
||
|
|
wire [7:0] data_out;
|
||
|
|
wire busy;
|
||
|
|
wire ack_err;
|
||
|
|
|
||
|
|
integer errors;
|
||
|
|
integer checks;
|
||
|
|
|
||
|
|
localparam IDLE = 4'd0;
|
||
|
|
localparam ADDR_DRIVE = 4'd3;
|
||
|
|
localparam ADDR_SAMPLE = 4'd4;
|
||
|
|
localparam ACK1 = 4'd5;
|
||
|
|
localparam READ = 4'd8;
|
||
|
|
localparam ACK2 = 4'd9;
|
||
|
|
localparam STOP_A = 4'd10;
|
||
|
|
localparam CLOCK_STRETCH = 4'd12;
|
||
|
|
localparam ARBITRATION_LOST = 4'd13;
|
||
|
|
|
||
|
|
assign scl = slave_scl_low ? 1'b0 : 1'bz;
|
||
|
|
assign sda = slave_sda_low ? 1'b0 : 1'bz;
|
||
|
|
|
||
|
|
i2c_master dut (
|
||
|
|
.clk(clk),
|
||
|
|
.rst_n(rst_n),
|
||
|
|
.addr(addr),
|
||
|
|
.rw(rw),
|
||
|
|
.data_in(data_in),
|
||
|
|
.start_tx(start_tx),
|
||
|
|
.data_out(data_out),
|
||
|
|
.busy(busy),
|
||
|
|
.ack_err(ack_err),
|
||
|
|
.scl(scl),
|
||
|
|
.sda(sda)
|
||
|
|
);
|
||
|
|
|
||
|
|
always #5 clk = ~clk;
|
||
|
|
|
||
|
|
always @(*) begin
|
||
|
|
slave_sda_low = 1'b0;
|
||
|
|
|
||
|
|
if (dut.state == ACK1) begin
|
||
|
|
if (!nack_first_ack) begin
|
||
|
|
slave_sda_low = 1'b1;
|
||
|
|
end
|
||
|
|
end else if (dut.state == ACK2 && !dut.latched_rw) begin
|
||
|
|
slave_sda_low = 1'b1;
|
||
|
|
end else if (dut.state == READ) begin
|
||
|
|
slave_sda_low = ~read_value[dut.bit_count];
|
||
|
|
end
|
||
|
|
|
||
|
|
if (arbitration_request &&
|
||
|
|
(dut.state == ADDR_DRIVE || dut.state == ADDR_SAMPLE) &&
|
||
|
|
dut.tx_shift[7]) begin
|
||
|
|
slave_sda_low = 1'b1;
|
||
|
|
end
|
||
|
|
end
|
||
|
|
|
||
|
|
always @(posedge clk) begin
|
||
|
|
if (dut.state == ARBITRATION_LOST) begin
|
||
|
|
saw_arbitration_lost <= 1'b1;
|
||
|
|
end
|
||
|
|
end
|
||
|
|
|
||
|
|
task check;
|
||
|
|
input cond;
|
||
|
|
input string message;
|
||
|
|
begin
|
||
|
|
checks = checks + 1;
|
||
|
|
if (!cond) begin
|
||
|
|
errors = errors + 1;
|
||
|
|
$display("CHECK FAILED: %0s", message);
|
||
|
|
end
|
||
|
|
end
|
||
|
|
endtask
|
||
|
|
|
||
|
|
task reset_dut;
|
||
|
|
begin
|
||
|
|
rst_n = 1'b0;
|
||
|
|
start_tx = 1'b0;
|
||
|
|
addr = 7'h00;
|
||
|
|
rw = 1'b0;
|
||
|
|
data_in = 8'h00;
|
||
|
|
nack_first_ack = 1'b0;
|
||
|
|
arbitration_request = 1'b0;
|
||
|
|
slave_scl_low = 1'b0;
|
||
|
|
read_value = 8'h00;
|
||
|
|
repeat (3) @(posedge clk);
|
||
|
|
rst_n = 1'b1;
|
||
|
|
@(posedge clk);
|
||
|
|
end
|
||
|
|
endtask
|
||
|
|
|
||
|
|
task launch_transfer;
|
||
|
|
input [6:0] tx_addr;
|
||
|
|
input tx_rw;
|
||
|
|
input [7:0] tx_data;
|
||
|
|
begin
|
||
|
|
addr = tx_addr;
|
||
|
|
rw = tx_rw;
|
||
|
|
data_in = tx_data;
|
||
|
|
start_tx = 1'b1;
|
||
|
|
@(posedge clk);
|
||
|
|
start_tx = 1'b0;
|
||
|
|
end
|
||
|
|
endtask
|
||
|
|
|
||
|
|
task wait_for_idle;
|
||
|
|
integer timeout;
|
||
|
|
begin
|
||
|
|
timeout = 0;
|
||
|
|
while ((busy || dut.state != IDLE) && timeout < 200) begin
|
||
|
|
@(posedge clk);
|
||
|
|
timeout = timeout + 1;
|
||
|
|
end
|
||
|
|
check(timeout < 200, "I2C transfer timed out");
|
||
|
|
end
|
||
|
|
endtask
|
||
|
|
|
||
|
|
initial begin
|
||
|
|
clk = 1'b0;
|
||
|
|
rst_n = 1'b0;
|
||
|
|
addr = 7'h00;
|
||
|
|
rw = 1'b0;
|
||
|
|
data_in = 8'h00;
|
||
|
|
start_tx = 1'b0;
|
||
|
|
slave_scl_low = 1'b0;
|
||
|
|
nack_first_ack = 1'b0;
|
||
|
|
arbitration_request = 1'b0;
|
||
|
|
read_value = 8'h00;
|
||
|
|
saw_arbitration_lost = 1'b0;
|
||
|
|
errors = 0;
|
||
|
|
checks = 0;
|
||
|
|
|
||
|
|
reset_dut();
|
||
|
|
|
||
|
|
launch_transfer(7'h42, 1'b0, 8'hA5);
|
||
|
|
wait_for_idle();
|
||
|
|
check(ack_err == 1'b0, "write transaction should complete without ack_err");
|
||
|
|
check(busy == 1'b0, "busy should deassert after write");
|
||
|
|
check(scl === 1'b1 && sda === 1'b1, "bus should return high after STOP");
|
||
|
|
|
||
|
|
read_value = 8'h3C;
|
||
|
|
launch_transfer(7'h42, 1'b1, 8'h00);
|
||
|
|
wait_for_idle();
|
||
|
|
check(data_out == 8'h3C, "read transaction should capture slave data");
|
||
|
|
check(ack_err == 1'b0, "read transaction should complete without ack_err");
|
||
|
|
|
||
|
|
nack_first_ack = 1'b1;
|
||
|
|
launch_transfer(7'h42, 1'b0, 8'h55);
|
||
|
|
wait_for_idle();
|
||
|
|
check(ack_err == 1'b1, "NACK on first ACK should set ack_err");
|
||
|
|
nack_first_ack = 1'b0;
|
||
|
|
|
||
|
|
fork
|
||
|
|
begin
|
||
|
|
wait (dut.state == STOP_A);
|
||
|
|
slave_scl_low = 1'b1;
|
||
|
|
repeat (3) @(posedge clk);
|
||
|
|
slave_scl_low = 1'b0;
|
||
|
|
end
|
||
|
|
join_none
|
||
|
|
launch_transfer(7'h42, 1'b0, 8'hC3);
|
||
|
|
wait_for_idle();
|
||
|
|
check(ack_err == 1'b0, "clock stretch should not force ack_err");
|
||
|
|
|
||
|
|
saw_arbitration_lost = 1'b0;
|
||
|
|
fork
|
||
|
|
begin
|
||
|
|
wait (dut.state == ADDR_SAMPLE && dut.tx_shift[7]);
|
||
|
|
arbitration_request = 1'b1;
|
||
|
|
repeat (2) @(posedge clk);
|
||
|
|
arbitration_request = 1'b0;
|
||
|
|
end
|
||
|
|
join_none
|
||
|
|
launch_transfer(7'h60, 1'b0, 8'hF0);
|
||
|
|
wait_for_idle();
|
||
|
|
check(saw_arbitration_lost, "arbitration loss path should be reachable");
|
||
|
|
check(ack_err == 1'b1, "arbitration loss should set ack_err");
|
||
|
|
|
||
|
|
if (errors == 0) begin
|
||
|
|
$display("Hint: Total mismatched samples is 0 out of %0d samples", checks);
|
||
|
|
end else begin
|
||
|
|
$display("Hint: Total mismatched samples is %0d out of %0d samples", errors, checks);
|
||
|
|
end
|
||
|
|
$display("Mismatches: %0d in %0d samples", errors, checks);
|
||
|
|
$finish;
|
||
|
|
end
|
||
|
|
|
||
|
|
endmodule
|