[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