[i2c] Update FSM
Initial FSM framework, updated based on the feedback
- Added comments for code
- Added _i and _o sufixes to inputs and outputs, respectively
- States and transitions:
- Idle state
- Issue start (SetupStart and HoldStart)
- Transmit byte (four substates)
- Read byte (four substates)
- Issue stop (SetupStop and HoldStop)
- Target acknowledge (four substates)
- Host (not)acknowledge (four substates)
- PopFmtFifo
- Event flags are not implemented yet
diff --git a/hw/ip/i2c/rtl/i2c_core.sv b/hw/ip/i2c/rtl/i2c_core.sv
index 8f99521..cccc5d5 100644
--- a/hw/ip/i2c/rtl/i2c_core.sv
+++ b/hw/ip/i2c/rtl/i2c_core.sv
@@ -222,43 +222,43 @@
.rst_ni,
.scl_i,
- .scl_o(scl_out_fsm),
+ .scl_o (scl_out_fsm),
.sda_i,
- .sda_o(sda_out_fsm),
+ .sda_o (sda_out_fsm),
- .fmt_fifo_rvalid,
- .fmt_fifo_rready,
+ .fmt_fifo_rvalid_i (fmt_fifo_rvalid),
+ .fmt_fifo_rready_o (fmt_fifo_rready),
- .fmt_byte,
- .fmt_flag_start_before,
- .fmt_flag_stop_after,
- .fmt_flag_read_bytes,
- .fmt_flag_read_continue,
- .fmt_flag_nak_ok,
+ .fmt_byte_i (fmt_byte),
+ .fmt_flag_start_before_i (fmt_flag_start_before),
+ .fmt_flag_stop_after_i (fmt_flag_stop_after),
+ .fmt_flag_read_bytes_i (fmt_flag_read_bytes),
+ .fmt_flag_read_continue_i(fmt_flag_read_continue),
+ .fmt_flag_nak_ok_i (fmt_flag_nak_ok),
- .rx_fifo_wvalid,
- .rx_fifo_wdata,
+ .rx_fifo_wvalid_o (rx_fifo_wvalid),
+ .rx_fifo_wdata_o (rx_fifo_wdata),
- .host_idle,
+ .host_idle_o (host_idle),
- .thigh,
- .tlow,
- .t_r,
- .t_f,
- .thd_sta,
- .tsu_sta,
- .tsu_sto,
- .tsu_dat,
- .thd_dat,
- .t_buf,
- .stretch_timeout,
- .timeout_enable,
+ .thigh_i (thigh),
+ .tlow_i (tlow),
+ .t_r_i (t_r),
+ .t_f_i (t_f),
+ .thd_sta_i (thd_sta),
+ .tsu_sta_i (tsu_sta),
+ .tsu_sto_i (tsu_sto),
+ .tsu_dat_i (tsu_dat),
+ .thd_dat_i (thd_dat),
+ .t_buf_i (t_buf),
+ .stretch_timeout_i (stretch_timeout),
+ .timeout_enable_i (timeout_enable),
- .event_nak,
- .event_scl_interference,
- .event_sda_interference,
- .event_stretch_timeout,
- .event_sda_unstable
+ .event_nak_o (event_nak),
+ .event_scl_interference_o(event_scl_interference),
+ .event_sda_interference_o(event_sda_interference),
+ .event_stretch_timeout_o (event_stretch_timeout),
+ .event_sda_unstable_o (event_sda_unstable)
);
prim_intr_hw #(.Width(1)) intr_hw_fmt_watermark (
diff --git a/hw/ip/i2c/rtl/i2c_fsm.sv b/hw/ip/i2c/rtl/i2c_fsm.sv
index ca0f438..ddf2a83 100644
--- a/hw/ip/i2c/rtl/i2c_fsm.sv
+++ b/hw/ip/i2c/rtl/i2c_fsm.sv
@@ -5,59 +5,545 @@
// Description: I2C finite state machine
module i2c_fsm (
- input clk_i,
- input rst_ni,
+ input clk_i, // clock
+ input rst_ni, // active low reset
- input scl_i,
- output scl_o,
- input sda_i,
- output sda_o,
+ input scl_i, // serial clock input from i2c bus
+ output scl_o, // serial clock output to i2c bus
+ input sda_i, // serial data input from i2c bus
+ output sda_o, // serial data output to i2c bus
- input fmt_fifo_rvalid,
- output fmt_fifo_rready,
- input [7:0] fmt_byte,
- input fmt_flag_start_before,
- input fmt_flag_stop_after,
- input fmt_flag_read_bytes,
- input fmt_flag_read_continue,
- input fmt_flag_nak_ok,
+ input fmt_fifo_rvalid_i, // indicates there is valid data in fmt_fifo
+ output logic fmt_fifo_rready_o, // populates fmt_fifo
+ input [7:0] fmt_byte_i, // byte in fmt_fifo to be sent to target
+ input fmt_flag_start_before_i, // issue start before sending byte
+ input fmt_flag_stop_after_i, // issue stop after sending byte
+ input fmt_flag_read_bytes_i,
+ input fmt_flag_read_continue_i,
+ input fmt_flag_nak_ok_i,
- output rx_fifo_wvalid,
- output [7:0] rx_fifo_wdata,
+ output logic rx_fifo_wvalid_o, // high if there is valid data in rx_fifo
+ output logic [7:0] rx_fifo_wdata_o, // byte in rx_fifo read from target
- output host_idle,
+ output logic host_idle_o, // indicates the host is idle
- input [15:0] thigh,
- input [15:0] tlow,
- input [15:0] t_r,
- input [15:0] t_f,
- input [15:0] thd_sta,
- input [15:0] tsu_sta,
- input [15:0] tsu_sto,
- input [15:0] tsu_dat,
- input [15:0] thd_dat,
- input [15:0] t_buf,
- input [30:0] stretch_timeout,
- input timeout_enable,
+ input [15:0] thigh_i, // high period of the SCL in clock units
+ input [15:0] tlow_i, // low period of the SCL in clock units
+ input [15:0] t_r_i, // rise time of both SDA and SCL in clock units
+ input [15:0] t_f_i, // fall time of both SDA and SCL in clock units
+ input [15:0] thd_sta_i, // hold time for (repeated) START in clock units
+ input [15:0] tsu_sta_i, // setup time for repeated START in clock units
+ input [15:0] tsu_sto_i, // setup time for STOP in clock units
+ input [15:0] tsu_dat_i, // data setup time in clock units
+ input [15:0] thd_dat_i, // data hold time in clock units
+ input [15:0] t_buf_i, // bus free time between STOP and START in clock units
+ input [30:0] stretch_timeout_i, // max time target may stretch the clock
+ input timeout_enable_i, // enable target to stretch clock past timeout
- output event_nak,
- output event_scl_interference,
- output event_sda_interference,
- output event_stretch_timeout,
- output event_sda_unstable
+ output event_nak_o,
+ output event_scl_interference_o,
+ output event_sda_interference_o,
+ output event_stretch_timeout_o,
+ output event_sda_unstable_o
);
// PLACEHOLDER IO
- assign scl_o = 1'b0;
- assign sda_o = 1'b0;
- assign fmt_fifo_rready = 1'b0;
- assign rx_fifo_wvalid = 1'b0;
- assign rx_fifo_wdata = 8'h00;
+ assign event_nak_o = 1'b0;
+ assign event_scl_interference_o = 1'b0;
+ assign event_sda_interference_o = 1'b0;
+ assign event_stretch_timeout_o = 1'b0;
+ assign event_sda_unstable_o = 1'b0;
- assign event_nak = 1'b0;
- assign event_scl_interference = 1'b0;
- assign event_sda_interference = 1'b0;
- assign event_stretch_timeout = 1'b0;
- assign event_sda_unstable = 1'b0;
+ // Timing generation for I2C bus
+ logic [19:0] tcount_q; // current counter for setting delays
+ logic [19:0] tcount_d; // next counter for setting delays
+ logic load_tcount; // indicates counter must be loaded
+ logic [30:0] stretch; // counter for clock stretching by target
+ logic [2:0] bit_index;
+ logic bit_decr;
+ logic bit_rst;
+ logic scl_temp; // scl internal
+ logic sda_temp; // data internal
-endmodule;
+ // Clock counter implementation
+ typedef enum logic [3:0] {
+ tSetupStart, tHoldStart, tClockLow, tSetupBit, tClockPulse, tHoldBit,
+ tSetupStop, tHoldStop, tNoDelay
+ } tcount_sel_e;
+
+ tcount_sel_e tcount_sel;
+
+ always_comb begin : counter_functions
+ tcount_d = tcount_q;
+ if (load_tcount) begin
+ unique case (tcount_sel)
+ tSetupStart : tcount_d = t_r_i + tsu_sta_i;
+ tHoldStart : tcount_d = t_f_i + thd_sta_i;
+ tClockLow : tcount_d = t_f_i + tlow_i - t_r_i - tsu_dat_i;
+ tSetupBit : tcount_d = t_r_i + tsu_dat_i;
+ tClockPulse : tcount_d = t_r_i + thigh_i;
+ tHoldBit : tcount_d = t_f_i + thd_dat_i;
+ tSetupStop : tcount_d = t_r_i + tsu_sto_i;
+ tHoldStop : tcount_d = t_r_i + t_buf_i;
+ tNoDelay : tcount_d = '0;
+ default : tcount_d = '0;
+ endcase
+ end else if (stretch == 0) begin
+ tcount_d = tcount_q - 1'b1;
+ end else begin
+ tcount_d = tcount_q; // pause timer if clock is stretched
+ end
+ end
+
+ always_ff @ (posedge clk_i or negedge rst_ni) begin : clk_counter
+ if (!rst_ni) begin
+ tcount_q <= '0;
+ end else begin
+ tcount_q <= tcount_d;
+ end
+ end
+
+ // Clock stretching detection
+ always_ff @ (posedge clk_i or negedge rst_ni) begin : clk_stretch
+ if (!rst_ni) begin
+ stretch <= '0;
+ end else if (scl_temp == 1 && scl_i == 0) begin
+ stretch <= stretch + 1'b1;
+ end else begin
+ stretch <= '0;
+ end
+ end
+
+ // Bit index implementation
+ always_ff @ (posedge clk_i or negedge rst_ni) begin : bit_counter
+ if (!rst_ni) begin
+ bit_index <= 3'd7;
+ end else if (bit_rst) begin
+ bit_index <= 3'd7;
+ end else if (bit_decr) begin
+ bit_index <= bit_index - 1'b1;
+ end else begin
+ bit_index <= bit_index;
+ end
+ end
+
+ // State definitions
+ typedef enum logic [4:0] {
+ Idle, PopFmtFifo, SetupStart, HoldStart, SetupStop, HoldStop,
+ ClockLow, SetupBit, ClockPulse, HoldBit,
+ ClockLowAck, SetupDevAck, ClockPulseAck, HoldDevAck,
+ ReadClockLow, ReadSetupBit, ReadClockPulse, ReadHoldBit,
+ HostClockLowAck, HostSetupBitAck, HostClockPulseAck, HostHoldBitAck
+ } state_e;
+
+ state_e state_q, state_d;
+
+ // Outputs for each state
+ always_comb begin : state_outputs
+ host_idle_o = 1'b1;
+ sda_temp = 1'b1;
+ scl_temp = 1'b1;
+ fmt_fifo_rready_o = 1'b0;
+ rx_fifo_wvalid_o = 1'b0;
+ rx_fifo_wdata_o = 8'h00;
+ unique case (state_q)
+ // Idle: initial state, SDA and SCL are released (high)
+ Idle : begin
+ host_idle_o = 1'b1;
+ sda_temp = 1'b1;
+ scl_temp = 1'b1;
+ end
+ // SetupStart: SDA and SCL are released
+ SetupStart : begin
+ host_idle_o = 1'b0;
+ sda_temp = 1'b1;
+ scl_temp = 1'b1;
+ end
+ // HoldStart: SDA is pulled low, SCL is released
+ HoldStart : begin
+ host_idle_o = 1'b0;
+ sda_temp = 1'b0;
+ scl_temp = 1'b1;
+ end
+ // ClockLow: SCL is pulled low, SDA stays low
+ ClockLow : begin
+ host_idle_o = 1'b0;
+ sda_temp = 1'b0;
+ scl_temp = 1'b0;
+ end
+ // SetupBit: Shift indexed bit onto SDA, SCL stays low
+ SetupBit : begin
+ host_idle_o = 1'b0;
+ sda_temp = fmt_byte_i[bit_index];
+ scl_temp = 1'b0;
+ end
+ // ClockPulse: SCL is released, SDA keeps the indexed bit value
+ ClockPulse : begin
+ host_idle_o = 1'b0;
+ sda_temp = fmt_byte_i[bit_index];
+ scl_temp = 1'b1;
+ end
+ // HoldBit: SCL is pulled low
+ HoldBit : begin
+ host_idle_o = 1'b0;
+ sda_temp = fmt_byte_i[bit_index];
+ scl_temp = 1'b0;
+ end
+ // ClockLowAck: SCL and SDA are pulled low
+ ClockLowAck : begin
+ host_idle_o = 1'b0;
+ sda_temp = 1'b0;
+ scl_temp = 1'b0;
+ end
+ // SetupDevAck: SDA is released, waiting for target to pull it low
+ SetupDevAck : begin
+ host_idle_o = 1'b0;
+ sda_temp = 1'b1;
+ scl_temp = 1'b0;
+ end
+ // ClockPulseAck: SCL is released
+ ClockPulseAck : begin
+ host_idle_o = 1'b0;
+ sda_temp = 1'b1;
+ scl_temp = 1'b1;
+ end
+ // HoldDevAck: SCL is pulled low
+ HoldDevAck : begin
+ host_idle_o = 1'b0;
+ sda_temp = 1'b1;
+ scl_temp = 1'b0;
+ end
+ // ReadClockLow: SCL is pulled low, SDA stays low
+ ReadClockLow : begin
+ host_idle_o = 1'b0;
+ sda_temp = 1'b0;
+ scl_temp = 1'b0;
+ end
+ // ReadSetupBit: Read indexed bit off SDA, SCL stays low
+ ReadSetupBit : begin
+ host_idle_o = 1'b0;
+ rx_fifo_wdata_o[bit_index] = sda_i;
+ scl_temp = 1'b0;
+ end
+ // ReadClockPulse: SCL is released, SDA keeps the indexed bit value
+ ReadClockPulse : begin
+ host_idle_o = 1'b0;
+ rx_fifo_wdata_o[bit_index] = sda_i;
+ scl_temp = 1'b1;
+ end
+ // ReadHoldBit: SCL is pulled low
+ ReadHoldBit : begin
+ host_idle_o = 1'b0;
+ rx_fifo_wdata_o[bit_index] = sda_i;
+ scl_temp = 1'b0;
+ if (bit_index == 0) rx_fifo_wvalid_o = 1'b1; // Byte is read
+ end
+ // HostClockLowAck: SCL and SDA are pulled low
+ HostClockLowAck : begin
+ host_idle_o = 1'b0;
+ sda_temp = 1'b0;
+ scl_temp = 1'b0;
+ end
+ // HostSetupBitAck: Shift Ack/Nack bit onto SDA
+ HostSetupBitAck : begin
+ host_idle_o = 1'b0;
+ if (fmt_flag_read_continue_i) sda_temp = 1'b1;
+ else sda_temp = 1'b0;
+ scl_temp = 1'b0;
+ end
+ // HostClockPulseAck: SCL is released
+ HostClockPulseAck : begin
+ host_idle_o = 1'b0;
+ if (fmt_flag_read_continue_i) sda_temp = 1'b1;
+ else sda_temp = 1'b0;
+ scl_temp = 1'b1;
+ end
+ // HostHoldBitAck: SCL is pulled low
+ HostHoldBitAck : begin
+ host_idle_o = 1'b0;
+ if (fmt_flag_read_continue_i) sda_temp = 1'b1;
+ else sda_temp = 1'b0;
+ scl_temp = 1'b0;
+ end
+ // SetupStop: SDA is pulled low, SCL is released
+ SetupStop : begin
+ host_idle_o = 1'b0;
+ sda_temp = 1'b0;
+ scl_temp = 1'b1;
+ end
+ // HoldStop: SDA and SCL are released
+ HoldStop : begin
+ host_idle_o = 1'b0;
+ sda_temp = 1'b1;
+ scl_temp = 1'b1;
+ end
+ // PopFmtFifo: populates fmt_fifo
+ PopFmtFifo : begin
+ host_idle_o = 1'b0;
+ fmt_fifo_rready_o = 1'b1;
+ end
+ // default
+ default : begin
+ host_idle_o = 1'b1;
+ sda_temp = 1'b1;
+ scl_temp = 1'b1;
+ fmt_fifo_rready_o = 1'b0;
+ rx_fifo_wvalid_o = 1'b0;
+ rx_fifo_wdata_o = 8'h00;
+ end
+ endcase
+ end
+
+ // Conditional state transition
+ always_comb begin : state_functions
+ state_d = state_q;
+ load_tcount = 1'b0;
+ tcount_sel = tNoDelay;
+ bit_decr = 1'b0;
+ bit_rst = 1'b0;
+
+ unique case (state_q)
+ // Idle: initial state, SDA and SCL are released (high)
+ Idle : begin
+ if (!fmt_fifo_rvalid_i) state_d = Idle;
+ else if (fmt_flag_read_bytes_i) begin
+ state_d = ReadClockLow;
+ load_tcount = 1'b1;
+ tcount_sel = tClockLow;
+ end else if (fmt_flag_start_before_i) begin
+ state_d = SetupStart;
+ load_tcount = 1'b1;
+ tcount_sel = tSetupStart;
+ end else begin
+ state_d = ClockLow;
+ load_tcount = 1'b1;
+ tcount_sel = tClockLow;
+ end
+ end
+
+ // SetupStart: SDA and SCL are released
+ SetupStart : begin
+ if (tcount_q == 0) begin
+ state_d = HoldStart;
+ load_tcount = 1'b1;
+ tcount_sel = tHoldStart;
+ end
+ end
+ // HoldStart: SDA is pulled low, SCL is released
+ HoldStart : begin
+ if (tcount_q == 0) begin
+ state_d = ClockLow;
+ load_tcount = 1'b1;
+ tcount_sel = tClockLow;
+ end
+ end
+
+ // ClockLow: SCL is pulled low, SDA stays low
+ ClockLow : begin
+ if (tcount_q == 0) begin
+ state_d = SetupBit;
+ load_tcount = 1'b1;
+ tcount_sel = tSetupBit;
+ end
+ end
+ // SetupBit: Shift indexed bit onto SDA, SCL stays low
+ SetupBit : begin
+ if (tcount_q == 0) begin
+ state_d = ClockPulse;
+ load_tcount = 1'b1;
+ tcount_sel = tClockPulse;
+ end
+ end
+ // ClockPulse: SCL is released, SDA keeps the indexed bit value
+ ClockPulse : begin
+ if (tcount_q == 0) begin
+ state_d = HoldBit;
+ load_tcount = 1'b1;
+ tcount_sel = tHoldBit;
+ end
+ end
+ // HoldBit: SCL is pulled low
+ HoldBit : begin
+ if (tcount_q == 0) begin
+ load_tcount = 1'b1;
+ tcount_sel = tClockLow;
+ if (bit_index == 0) begin
+ state_d = ClockLowAck;
+ bit_rst = 1'b1;
+ end else begin
+ state_d = ClockLow;
+ bit_decr = 1'b1;
+ end
+ end
+ end
+
+ // ClockLowAck: SCL and SDA are pulled low
+ ClockLowAck : begin
+ if (tcount_q == 0) begin
+ state_d = SetupDevAck;
+ load_tcount = 1'b1;
+ tcount_sel = tSetupBit;
+ end
+ end
+ // SetupDevAck: SDA is released, waiting for target to pull it low
+ SetupDevAck : begin
+ if (tcount_q == 0) begin
+ state_d = ClockPulseAck;
+ load_tcount = 1'b1;
+ tcount_sel = tClockPulse;
+ end
+ end
+ // ClockPulseAck: SCL is released
+ ClockPulseAck : begin
+ if (tcount_q == 0) begin
+ state_d = HoldDevAck;
+ load_tcount = 1'b1;
+ tcount_sel = tHoldBit;
+ end
+ end
+ // HoldDevAck: SCL is pulled low
+ HoldDevAck : begin
+ if (tcount_q == 0) begin
+ if (fmt_flag_stop_after_i) begin
+ state_d = SetupStop;
+ load_tcount = 1'b1;
+ tcount_sel = tSetupStop;
+ end else begin
+ state_d = PopFmtFifo;
+ load_tcount = 1'b1;
+ tcount_sel = tNoDelay;
+ end
+ end
+ end
+
+ // ReadClockLow: SCL is pulled low, SDA stays low
+ ReadClockLow : begin
+ if (tcount_q == 0) begin
+ state_d = ReadSetupBit;
+ load_tcount = 1'b1;
+ tcount_sel = tSetupBit;
+ end
+ end
+ // ReadSetupBit: Shift indexed bit onto SDA, SCL stays low
+ ReadSetupBit : begin
+ if (tcount_q == 0) begin
+ state_d = ReadClockPulse;
+ load_tcount = 1'b1;
+ tcount_sel = tClockPulse;
+ end
+ end
+ // ReadClockPulse: SCL is released, SDA keeps the indexed bit value
+ ReadClockPulse : begin
+ if (tcount_q == 0) begin
+ state_d = ReadHoldBit;
+ load_tcount = 1'b1;
+ tcount_sel = tHoldBit;
+ end
+ end
+ // ReadHoldBit: SCL is pulled low
+ ReadHoldBit : begin
+ if (tcount_q == 0) begin
+ load_tcount = 1'b1;
+ tcount_sel = tClockLow;
+ if (bit_index == 0) begin
+ state_d = HostClockLowAck;
+ bit_rst = 1'b1;
+ end else begin
+ state_d = ReadClockLow;
+ bit_decr = 1'b1;
+ end
+ end
+ end
+
+ // HostClockLowAck: SCL and SDA are pulled low
+ HostClockLowAck : begin
+ if (tcount_q == 0) begin
+ state_d = HostSetupBitAck;
+ load_tcount = 1'b1;
+ tcount_sel = tSetupBit;
+ end
+ end
+ // HostSetupBitAck: SDA is released, waiting for target to pull it low
+ HostSetupBitAck : begin
+ if (tcount_q == 0) begin
+ state_d = HostClockPulseAck;
+ load_tcount = 1'b1;
+ tcount_sel = tClockPulse;
+ end
+ end
+ // HostClockPulseAck: SCL is released
+ HostClockPulseAck : begin
+ if (tcount_q == 0) begin
+ state_d = HostHoldBitAck;
+ load_tcount = 1'b1;
+ tcount_sel = tHoldBit;
+ end
+ end
+ // HostHoldBitAck: SCL is pulled low
+ HostHoldBitAck : begin
+ if (tcount_q == 0) begin
+ if (fmt_flag_read_continue_i) begin
+ state_d = ReadClockLow;
+ load_tcount = 1'b1;
+ tcount_sel = tClockLow;
+ end else if (fmt_flag_stop_after_i) begin
+ state_d = SetupStop;
+ load_tcount = 1'b1;
+ tcount_sel = tSetupStop;
+ end else begin
+ state_d = PopFmtFifo;
+ load_tcount = 1'b1;
+ tcount_sel = tNoDelay;
+ end
+ end
+ end
+
+ // SetupStop: SDA is pulled low, SCL is released
+ SetupStop : begin
+ if (tcount_q == 0) begin
+ state_d = HoldStop;
+ load_tcount = 1'b1;
+ tcount_sel = tHoldStop;
+ end
+ end
+ // HoldStop: SDA and SCL are released
+ HoldStop : begin
+ if (tcount_q == 0) begin
+ state_d = PopFmtFifo;
+ load_tcount = 1'b1;
+ tcount_sel = tNoDelay;
+ end
+ end
+
+ // PopFmtFifo: populates fmt_fifo
+ PopFmtFifo : begin
+ state_d = Idle;
+ load_tcount = 1'b1;
+ tcount_sel = tNoDelay;
+ end
+
+ // default
+ default : begin
+ state_d = Idle;
+ load_tcount = 1'b0;
+ tcount_sel = tNoDelay;
+ bit_decr = 1'b0;
+ bit_rst = 1'b0;
+ end
+ endcase
+ end
+
+ // Synchronous state transition
+ always_ff @ (posedge clk_i or negedge rst_ni) begin : state_transition
+ if (!rst_ni) begin
+ state_q <= Idle;
+ end else begin
+ state_q <= state_d;
+ end
+ end
+
+ // I2C bus outputs
+ assign scl_o = scl_temp;
+ assign sda_o = sda_temp;
+
+endmodule