From 41b117b63d19b4462c276ec381ed3b87811f38e9 Mon Sep 17 00:00:00 2001 From: Harry Callahan Date: Thu, 29 Feb 2024 17:41:08 +0000 Subject: [PATCH] [i2c,rtl] i2c_fsm changes for prediction of target clock-stretching 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 --- hw/ip/i2c/rtl/i2c_fsm.sv | 81 +++++++++++++++++++++++++++++++++++----- 1 file changed, 72 insertions(+), 9 deletions(-) diff --git a/hw/ip/i2c/rtl/i2c_fsm.sv b/hw/ip/i2c/rtl/i2c_fsm.sv index e30fd9e53a518c..95ebbf6d6fcb08 100644 --- a/hw/ip/i2c/rtl/i2c_fsm.sv +++ b/hw/ip/i2c/rtl/i2c_fsm.sv @@ -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 @@ -140,6 +143,7 @@ module i2c_fsm import i2c_pkg::*; tClockStart, tClockLow, tClockPulse, + tClockHigh, tHoldBit, tClockStop, tSetupStop, @@ -159,6 +163,7 @@ 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); @@ -166,10 +171,8 @@ module i2c_fsm import i2c_pkg::*; 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 @@ -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 @@ -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 @@ -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; @@ -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; @@ -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 @@ -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; @@ -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; @@ -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 @@ -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; @@ -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