Skip to content

Commit

Permalink
I2C: A couple of fixes around SCL stretching is handled
Browse files Browse the repository at this point in the history
  • Loading branch information
Aaron-Hartwig committed Dec 6, 2024
1 parent e5f3844 commit 3c8d1c3
Show file tree
Hide file tree
Showing 7 changed files with 368 additions and 111 deletions.
13 changes: 10 additions & 3 deletions hdl/ip/bsv/I2C/I2CBitController.bsv
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,9 @@ interface I2CBitController;
interface Put#(Event) send;
interface Get#(Event) receive;

// way of indicating when there is no bus activity
method Bool busy();

// clock stretching information sent sideband from the state machine events
method Bool scl_stretch_seen();
method Bool scl_stretch_timeout();
Expand Down Expand Up @@ -155,14 +158,14 @@ module mkI2CBitController #(
// After the delay we know SCL is being stretched if we aren't the ones
// holding it low.
(* fire_when_enabled *)
rule do_sample_scl_stretch(scl_stretch_sample_strobe && scl_in == 0);
scl_stretching <= scl_out_en == 0;
rule do_sample_scl_stretch(scl_stretch_sample_strobe);
scl_stretching <= scl_out_en == 0 && scl_in == 0;
scl_stretch_sample_delay <= False;
endrule

// If SCL is high then no one is holding it
(* fire_when_enabled *)
rule do_clear_scl_stretch(scl_in == 1);
rule do_clear_scl_stretch(scl_in == 1 && !scl_stretch_sample_strobe);
scl_stretching <= False;
endrule

Expand Down Expand Up @@ -251,6 +254,8 @@ module mkI2CBitController #(
(* fire_when_enabled *)
rule do_scl_stretch_timeout(scl_stretch_timeout_cntr);
state <= AwaitStart;
scl_active <= False;
sda_out_en <= 0;
scl_stretch_timeout_r <= True;
incoming_events.clear();
endrule
Expand Down Expand Up @@ -416,6 +421,8 @@ module mkI2CBitController #(
endinterface
interface Get receive = toGet(outgoing_events);

method busy = state != AwaitStart;

method scl_stretch_seen = scl_stretch_seen_r;
method scl_stretch_timeout = scl_stretch_timeout_r;
endmodule
Expand Down
71 changes: 42 additions & 29 deletions hdl/ip/bsv/I2C/I2CCore.bsv
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ export Pins(..);
export I2CCore(..);
export mkI2CCore;

import ConfigReg::*;
import DefaultValue::*;
import DReg::*;
import FIFO::*;
Expand Down Expand Up @@ -105,40 +106,57 @@ module mkI2CCore#(Integer core_clk_freq,
Reg#(Bool) in_random_read <- mkReg(False);
Reg#(Bool) in_write_ack_poll <- mkReg(False);
Reg#(Bool) write_acked <- mkReg(False);

ConfigReg#(Bool) state_cleared <- mkConfigReg(False);
Reg#(Bool) clearing_state <- mkReg(False);
PulseWire clear_state <- mkPulseWire;
PulseWire next_send_data <- mkPulseWire;

// relabel this net for brevity
let timed_out = bit_ctrl.scl_stretch_timeout;

(* fire_when_enabled, no_implicit_conditions *)
rule do_valid_command;
valid_command <= isValid(cur_command);
endrule

// when the bit controller has timed out, clear core state
(* fire_when_enabled *)
rule do_handle_stretch_timeout(timed_out);
next_command.clear();
error_r <= tagged Invalid;
state_r <= Idle;
rule do_clearing_state_reg;
clearing_state <= (bit_ctrl.scl_stretch_timeout && !state_cleared) || clear_state;
endrule

(* fire_when_enabled *)
rule do_register_command(state_r == Idle && !valid_command && !timed_out);
cur_command <= tagged Valid next_command.first;
error_r <= tagged Invalid;
state_r <= SendStart;
rule do_handle_stretch_timeout(clearing_state);
next_command.deq();
bytes_done <= 0;
in_random_read <= False;
in_write_ack_poll <= False;
write_acked <= False;
cur_command <= tagged Invalid;
state_r <= Idle;
endrule

(* fire_when_enabled *)
rule do_send_start (state_r == SendStart && valid_command && !timed_out);
rule do_state_cleared_reg;
if (clearing_state) begin
state_cleared <= True;
end else if (valid_command && bit_ctrl.busy()) begin
state_cleared <= False;
end
endrule

(* fire_when_enabled *)
rule do_register_command(state_r == Idle && !valid_command && !clearing_state);
cur_command <= tagged Valid next_command.first;
error_r <= tagged Invalid;
state_r <= SendStart;
endrule

(* fire_when_enabled *)
rule do_send_start (state_r == SendStart && valid_command && !clearing_state);
bit_ctrl.send.put(tagged Start);
state_r <= SendAddr;
endrule

(* fire_when_enabled *)
rule do_send_addr (state_r == SendAddr && valid_command && !timed_out);
rule do_send_addr (state_r == SendAddr && valid_command && !clearing_state);
let cmd = fromMaybe(?, cur_command);
let is_read = (cmd.op == Read) || in_random_read;
let addr_byte = {cmd.i2c_addr, pack(is_read)};
Expand All @@ -149,21 +167,22 @@ module mkI2CCore#(Integer core_clk_freq,
(* fire_when_enabled *)
rule do_await_addr_ack (state_r == AwaitAddrAck
&& valid_command
&& !timed_out);
&& !clearing_state);
let ack_nack <- bit_ctrl.receive.get();
let cmd = fromMaybe(?, cur_command);

case (ack_nack) matches
tagged Ack: begin
// Begin a Read
if (cmd.op == Read || in_random_read) begin
// begin a Read
bytes_done <= 1;
bit_ctrl.send.put(tagged Read (cmd.num_bytes == 1));
state_r <= Reading;
end else if (in_write_ack_poll) begin
write_acked <= True;
state_r <= Stop;
end else begin
// begin a Write
bit_ctrl.send.put(tagged Write cmd.reg_addr);
state_r <= AwaitWriteAck;
end
Expand All @@ -180,7 +199,7 @@ module mkI2CCore#(Integer core_clk_freq,
endrule

(* fire_when_enabled *)
rule do_writing (state_r == Writing && valid_command && !timed_out);
rule do_writing (state_r == Writing && valid_command && !clearing_state);
bit_ctrl.send.put(tagged Write tx_data);
next_send_data.send();
state_r <= AwaitWriteAck;
Expand All @@ -189,7 +208,7 @@ module mkI2CCore#(Integer core_clk_freq,
(* fire_when_enabled *)
rule do_await_writing_ack (state_r == AwaitWriteAck
&& valid_command
&& !timed_out);
&& !clearing_state);
let ack_nack <- bit_ctrl.receive.get();
let cmd = fromMaybe(?, cur_command);

Expand All @@ -215,7 +234,7 @@ module mkI2CCore#(Integer core_clk_freq,
endrule

(* fire_when_enabled *)
rule do_reading (state_r == Reading && valid_command && !timed_out);
rule do_reading (state_r == Reading && valid_command && !clearing_state);
let rdata <- bit_ctrl.receive.get();
let cmd = fromMaybe(?, cur_command);

Expand All @@ -233,14 +252,14 @@ module mkI2CCore#(Integer core_clk_freq,
endcase
endrule

rule do_next_read (state_r == NextRead && valid_command && !timed_out);
rule do_next_read (state_r == NextRead && valid_command && !clearing_state);
let cmd = fromMaybe(?, cur_command);
bit_ctrl.send.put(tagged Read (cmd.num_bytes == bytes_done));
state_r <= Reading;
endrule

(* fire_when_enabled *)
rule do_stop (state_r == Stop && valid_command && !timed_out);
rule do_stop (state_r == Stop && valid_command && !clearing_state);
bit_ctrl.send.put(tagged Stop);

let cmd = fromMaybe(?, cur_command);
Expand All @@ -253,14 +272,8 @@ module mkI2CCore#(Integer core_clk_freq,
endrule

(* fire_when_enabled *)
rule do_done (state_r == Done && valid_command && !timed_out);
next_command.deq();
bytes_done <= 0;
in_random_read <= False;
in_write_ack_poll <= False;
write_acked <= False;
cur_command <= tagged Invalid;
state_r <= Idle;
rule do_done (state_r == Done && valid_command && !clearing_state && !bit_ctrl.busy());
clear_state.send();
endrule

interface pins = bit_ctrl.pins;
Expand Down
5 changes: 2 additions & 3 deletions hdl/ip/bsv/I2C/test/I2CBitControllerTests.bsv
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,6 @@ module mkBench (Bench);
Reg#(Vector#(3,Bit#(8))) prev_written_bytes <- mkReg(replicate(0));
Reg#(UInt#(2)) bytes_done <- mkReg(0);
Reg#(Bool) is_last_byte <- mkReg(False);
Reg#(Bool) stretch_timeout <- mkReg(False);

FSM write_seq <- mkFSMWithPred(seq
dut.send.put(tagged Start);
Expand Down Expand Up @@ -120,7 +119,7 @@ module mkBench (Bench);

dut.send.put(tagged Stop);
check_peripheral_event(periph, tagged ReceivedStop, "Expected to receive STOP");
endseq, command_r.op == Write && !stretch_timeout);
endseq, command_r.op == Write && !dut.scl_stretch_timeout);

FSM read_seq <- mkFSMWithPred(seq
dut.send.put(tagged Start);
Expand Down Expand Up @@ -153,7 +152,7 @@ module mkBench (Bench);

dut.send.put(tagged Stop);
check_peripheral_event(periph, tagged ReceivedStop, "Expected to receive STOP");
endseq, command_r.op == Read && !stretch_timeout);
endseq, command_r.op == Read && !dut.scl_stretch_timeout);

FSM rnd_read_seq <- mkFSMWithPred(seq
dut.send.put(tagged Start);
Expand Down
6 changes: 3 additions & 3 deletions hdl/ip/bsv/I2C/test/I2CCoreTests.bsv
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ module mkBench (Bench);
endaction
endseq
check_peripheral_event(periph, tagged ReceivedStop, "Expected to receive STOP");
endseq, command_r.op == Write);
endseq, command_r.op == Write && !dut.scl_stretch_timeout);

FSM read_seq <- mkFSMWithPred(seq
dut.send_command.put(command_r);
Expand All @@ -156,7 +156,7 @@ module mkBench (Bench);
check_peripheral_event(periph, tagged ReceivedNack, "Expected to receive NACK to end the Read");
check_peripheral_event(periph, tagged ReceivedStop, "Expected to receive STOP");
bytes_done <= 0;
endseq, command_r.op == Read);
endseq, command_r.op == Read && !dut.scl_stretch_timeout);

FSM rand_read_seq <- mkFSMWithPred(seq
dut.send_command.put(command_r);
Expand All @@ -181,7 +181,7 @@ module mkBench (Bench);
check_peripheral_event(periph, tagged ReceivedNack, "Expected to receive NACK to end the Read");
check_peripheral_event(periph, tagged ReceivedStop, "Expected to receive STOP");
bytes_done <= 0;
endseq, command_r.op == RandomRead);
endseq, command_r.op == RandomRead && !dut.scl_stretch_timeout);

rule do_handle_stretch_timeout(dut.scl_stretch_timeout);
write_seq.abort();
Expand Down
Loading

0 comments on commit 3c8d1c3

Please sign in to comment.