Skip to content

Commit

Permalink
[i2c,rtl] i2c_fsm changes for prediction of target clock-stretching
Browse files Browse the repository at this point in the history
Previously, the cycle counter for any 'thigh' state would only decrement once
the FSM had observed scl_i = 1'b1 on it's input. When the FSM releases SCL to
create the clock pulse, a minimum of 3 cycles is required to observe this effect
on it's input. (1-cycle output flop, 2-cycle input synchronizer)

This change allows the HOST-mode state machine to proceed if it does not
observe SCL being stretched 4 cycles after releasing SCL to try and create the
next clock pulse. This removes the 3-cycle delay from every 'thigh' state, and
in the absence of clock-stretching, brings the performance of the block in-line
with a user's calculations based on the given timing parameters.

This imposes a minimum 'thigh' of 4 cycles, which may limit the possible
performance of the block when the frequency of clk_i is not significantly
greater than scl.

Signed-off-by: Harry Callahan <[email protected]>
  • Loading branch information
hcallahan-lowrisc committed Mar 7, 2024
1 parent 3275d66 commit 41b117b
Showing 1 changed file with 72 additions and 9 deletions.
81 changes: 72 additions & 9 deletions hw/ip/i2c/rtl/i2c_fsm.sv
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,9 @@ module i2c_fsm import i2c_pkg::*;
logic load_tcount; // indicates counter must be loaded
logic [31:0] stretch_idle_cnt; // counter for clock being stretched by target
// or clock idle by host.

// (IP in HOST-Mode) This bit is active when the FSM is in a state where a TARGET might
// be trying to stretch the clock, preventing the controller FSM from continuing.
logic stretch_en;

// Bit and byte counter variables
Expand Down Expand Up @@ -140,6 +143,7 @@ module i2c_fsm import i2c_pkg::*;
tClockStart,
tClockLow,
tClockPulse,
tClockHigh,
tHoldBit,
tClockStop,
tSetupStop,
Expand All @@ -159,17 +163,16 @@ module i2c_fsm import i2c_pkg::*;
tClockStart : tcount_d = 20'(thd_dat_i);
tClockLow : tcount_d = 20'(tlow_i) - 20'(thd_dat_i);
tClockPulse : tcount_d = 20'(t_r_i) + 20'(thigh_i);
tClockHigh : tcount_d = 20'(thigh_i);
tHoldBit : tcount_d = 20'(t_f_i) + 20'(thd_dat_i);
tClockStop : tcount_d = 20'(t_f_i) + 20'(tlow_i) - 20'(thd_dat_i);
tSetupStop : tcount_d = 20'(t_r_i) + 20'(tsu_sto_i);
tHoldStop : tcount_d = 20'(t_r_i) + 20'(t_buf_i) - 20'(tsu_sta_i);
tNoDelay : tcount_d = 20'h00001;
default : tcount_d = 20'h00001;
endcase
end else if (stretch_idle_cnt == '0 || target_enable_i) begin
end else if (host_enable_i || target_enable_i) begin
tcount_d = tcount_q - 1'b1;
end else begin
tcount_d = tcount_q; // pause timer if clock is stretched
end
end

Expand All @@ -192,7 +195,8 @@ module i2c_fsm import i2c_pkg::*;
always_ff @ (posedge clk_i or negedge rst_ni) begin : clk_stretch
if (!rst_ni) begin
stretch_idle_cnt <= '0;
end else if (stretch_en && scl_d && !scl_i) begin
end else if (stretch_en) begin
// HOST-mode count of clock stretching
stretch_idle_cnt <= stretch_idle_cnt + 1'b1;
end else if (!target_idle_o && event_host_timeout_o) begin
// If the host has timed out, reset the counter and try again
Expand All @@ -204,6 +208,30 @@ module i2c_fsm import i2c_pkg::*;
end
end

// The TARGET can stretch the clock during any time that the host drives SCL to 0.
// However, we (the HOST) cannot know it is being stretched until we release SCL,
// usually trying to create the next clock pulse.
// Due to the 3-cycle round trip (1-cycle output flop, 2-cycle input synchronizer),
// even if the TARGET is not stretching the clock, we cannot observe the effect of
// releasing the clock until 3 cycles later.
//
// 'stretch_predict_cnt_expired' becomes active once we have observed 4 cycles of
// delay, and if scl_i = 1'b0 at this point we know that the Target is stretching the
// clock. This requires 'thigh >= 4' to guarantee we don't miss stretching, which
// is the 3-cycle round trip +1 for potential metastability on the input.
logic stretch_predict_cnt_expired;
always_ff @ (posedge clk_i or negedge rst_ni) begin
if (!rst_ni) begin
stretch_predict_cnt_expired <= 1'b0;
end else begin
if (stretch_idle_cnt == 32'(32'd3 + t_r_i)) begin
stretch_predict_cnt_expired <= 1'b1;
end else if (!stretch_en) begin
stretch_predict_cnt_expired <= 1'b0;
end
end
end

// Bit index implementation
always_ff @ (posedge clk_i or negedge rst_ni) begin : bit_counter
if (!rst_ni) begin
Expand Down Expand Up @@ -486,6 +514,11 @@ module i2c_fsm import i2c_pkg::*;
scl_d = 1'b1;
end
end

///////////////
// HOST MODE //
///////////////

// SetupStart: SDA and SCL are released
SetupStart : begin
host_idle_o = 1'b0;
Expand Down Expand Up @@ -640,6 +673,11 @@ module i2c_fsm import i2c_pkg::*;
else scl_d = 1'b0;
fmt_fifo_rready_o = 1'b1;
end

/////////////////
// TARGET MODE //
/////////////////

// AcquireStart: hold start condition
AcquireStart : begin
target_idle_o = 1'b0;
Expand Down Expand Up @@ -844,6 +882,10 @@ module i2c_fsm import i2c_pkg::*;
end
end

///////////////
// HOST MODE //
///////////////

// SetupStart: SDA and SCL are released
SetupStart : begin
if (tcount_q == 20'd1) begin
Expand Down Expand Up @@ -887,7 +929,11 @@ module i2c_fsm import i2c_pkg::*;
// ClockPulse: SCL is released, SDA keeps the indexed bit value
ClockPulse : begin
en_sda_interf_det = 1'b1;
if (tcount_q == 20'd1) begin
if (!scl_i && stretch_predict_cnt_expired) begin
// Saw stretching. Remain in this state and don't count down until we see SCL high.
load_tcount = 1'b1;
tcount_sel = tClockHigh;
end else if (tcount_q == 20'd1) begin
state_d = HoldBit;
load_tcount = 1'b1;
tcount_sel = tHoldBit;
Expand Down Expand Up @@ -920,7 +966,11 @@ module i2c_fsm import i2c_pkg::*;
end
// ClockPulseAck: SCL is released
ClockPulseAck : begin
if (tcount_q == 20'd1) begin
if (!scl_i && stretch_predict_cnt_expired) begin
// Saw stretching. Remain in this state and don't count down until we see SCL high.
load_tcount = 1'b1;
tcount_sel = tClockHigh;
end else if (tcount_q == 20'd1) begin
state_d = HoldDevAck;
load_tcount = 1'b1;
tcount_sel = tHoldBit;
Expand Down Expand Up @@ -950,11 +1000,15 @@ module i2c_fsm import i2c_pkg::*;
end
// ReadClockPulse: SCL is released, the indexed bit value is read off SDA
ReadClockPulse : begin
if (tcount_q == 20'd1) begin
if (!scl_i && stretch_predict_cnt_expired) begin
// Saw stretching. Remain in this state and don't count down until we see SCL high.
load_tcount = 1'b1;
tcount_sel = tClockHigh;
end else if (tcount_q == 20'd1) begin
state_d = ReadHoldBit;
load_tcount = 1'b1;
tcount_sel = tHoldBit;
shift_data_en = 1'b1;
shift_data_en = 1'b1; // SDA is sampled on the final clk_i cycle of the SCL pulse.
end
end
// ReadHoldBit: SCL is pulled low
Expand Down Expand Up @@ -985,7 +1039,11 @@ module i2c_fsm import i2c_pkg::*;
// HostClockPulseAck: SCL is released
HostClockPulseAck : begin
en_sda_interf_det = 1'b1;
if (tcount_q == 20'd1) begin
if (!scl_i && stretch_predict_cnt_expired) begin
// Saw stretching. Remain in this state and don't count down until we see SCL high.
load_tcount = 1'b1;
tcount_sel = tClockHigh;
end else if (tcount_q == 20'd1) begin
state_d = HostHoldBitAck;
load_tcount = 1'b1;
tcount_sel = tHoldBit;
Expand Down Expand Up @@ -1085,6 +1143,11 @@ module i2c_fsm import i2c_pkg::*;
end
end


/////////////////
// TARGET MODE //
/////////////////

// AcquireStart: hold start condition
AcquireStart : begin
if (scl_i_q && !scl_i) begin
Expand Down

0 comments on commit 41b117b

Please sign in to comment.