blob: da2f3c6fa66d92d3d8b25e79537828ee6dd89bee [file] [log] [blame]
// Copyright lowRISC contributors.
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0
//
// Description: I2C finite state machine
module i2c_fsm (
input clk_i, // clock
input rst_ni, // active low reset
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 host_enable_i, // enable host functionality
input target_enable_i, // enable target functionality
input fmt_fifo_rvalid_i, // indicates there is valid data in fmt_fifo
input fmt_fifo_wvalid_i, // indicates data is being put into fmt_fifo
input [5:0] fmt_fifo_depth_i, // fmt_fifo_depth
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, // indicates byte is an number of reads
input fmt_flag_read_continue_i,// host to send Ack to final byte read
input fmt_flag_nak_ok_i, // no Ack is expected
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
input tx_fifo_rvalid_i, // indicates there is valid data in tx_fifo
input tx_fifo_wvalid_i, // indicates data is being put into tx_fifo
input [5:0] tx_fifo_depth_i, // tx_fifo_depth
output logic tx_fifo_rready_o, // populates tx_fifo
input [7:0] tx_fifo_rdata_i, // byte in tx_fifo to be sent to host
input logic acq_fifo_wready_i, // low if acq_fifo is full
output logic acq_fifo_wvalid_o, // high if there is valid data in acq_fifo
output logic [9:0] acq_fifo_wdata_o, // byte and signal in acq_fifo read from target
output logic host_idle_o, // indicates the host is idle
output logic target_idle_o, // indicates the target is idle
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, // assert if target stretches clock past max
input stretch_en_addr_i, // enable target stretching clock after address matching
input stretch_en_tx_i, // enable target stretching clock after transmit transaction
input stretch_en_acq_i, // enable target stretching clock after acquire transaction
input stretch_stop_i, // stop stretching clock and resume normal operation
input [31:0] host_timeout_i, // max time target waits for host to pull clock down
input logic [6:0] target_address0_i,
input logic [6:0] target_mask0_i,
input logic [6:0] target_address1_i,
input logic [6:0] target_mask1_i,
output logic event_nak_o, // target didn't Ack when expected
output logic event_scl_interference_o, // other device forcing SCL low
output logic event_sda_interference_o, // other device forcing SDA low
output logic event_stretch_timeout_o, // target stretches clock past max time
output logic event_sda_unstable_o, // SDA is not constant during SCL pulse
output logic event_trans_complete_o, // Transaction is complete
output logic event_tx_empty_o, // tx_fifo is empty but data is needed
output logic event_tx_nonempty_o, // tx_fifo is nonempty after stop
output logic event_ack_stop_o, // target received stop after ack
output logic event_host_timeout_o // host ceased sending SCL pulses during ongoing transactn
);
// I2C bus clock timing variables
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 being stretched by target
// Bit and byte counter variables
logic [2:0] bit_index; // bit being transmitted to or read from the bus
logic bit_decr; // indicates bit_index must be decremented by 1
logic bit_clr; // indicates bit_index must be reset to 7
logic [8:0] byte_num; // number of bytes to read
logic [8:0] byte_index; // byte being read from the bus
logic byte_decr; // indicates byte_index must be decremented by 1
logic byte_clr; // indicates byte_index must be reset to byte_num
// Other internal variables
logic scl_temp; // scl internal
logic sda_temp; // data internal
logic scl_i_q; // scl_i delayed by one clock
logic sda_i_q; // sda_i delayed by one clock
logic [7:0] read_byte; // register for reads from target
logic read_byte_clr; // clear read_byte contents
logic shift_data_en; // indicates data must be shifted in from the bus
logic no_stop; // indicates no stop has been issued before start
logic log_start; // indicates start is been issued
logic log_stop; // indicates stop is been issued
logic restart; // indicates repeated start state is entered into
// Target specific variables
logic start_det; // indicates start or repeated start is detected on the bus
logic stop_det; // indicates stop is detected on the bus
logic address0_match;// indicates target's address0 matches the one sent by host
logic address1_match;// indicates target's address1 matches the one sent by host
logic address_match; // indicates one of target's addresses matches the one sent by host
logic [7:0] input_byte; // register for reads from host
logic input_byte_clr;// clear input_byte contents
logic [31:0] scl_high_cnt; // counter for continuously released scl_i
// Target bit counter variables
logic [3:0] bit_idx; // bit index including ack/nack
logic bit_ack; // indicates ACK bit been sent or received
logic rw_bit; // indicates host wants to read (1) or write (0)
logic host_ack; // indicates host acqnowledged transmitted byte
// Clock counter implementation
typedef enum logic [3:0] {
tSetupStart, tHoldStart, tClockLow, tSetupBit, tClockPulse, tHoldBit,
tClockStart, tClockStop, 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 = 20'(t_r_i) + 20'(tsu_sta_i);
tHoldStart : tcount_d = 20'(t_f_i) + 20'(thd_sta_i);
tClockStart : tcount_d = 20'(thd_dat_i);
tClockLow : tcount_d = 20'(tlow_i) - 20'(t_r_i) - 20'(tsu_dat_i) - 20'(thd_dat_i);
tSetupBit : tcount_d = 20'(t_r_i) + 20'(tsu_dat_i);
tClockPulse : tcount_d = 20'(t_r_i) + 20'(thigh_i) + 20'(t_f_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 == '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 <= '1;
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'b1 && 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_clr) 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
// Deserializer for a byte read from the bus
always_ff @ (posedge clk_i or negedge rst_ni) begin : read_register
if (!rst_ni) begin
read_byte <= 8'h00;
end else if (read_byte_clr) begin
read_byte <= 8'h00;
end else if (shift_data_en) begin
read_byte[7:0] <= {read_byte[6:0], sda_i}; // MSB goes in first
end
end
// Number of bytes to read
always_comb begin : byte_number
if (!fmt_flag_read_bytes_i) byte_num = 9'd0;
else if (fmt_byte_i == '0) byte_num = 9'd256;
else byte_num = 9'(fmt_byte_i);
end
// Byte index implementation
always_ff @ (posedge clk_i or negedge rst_ni) begin : byte_counter
if (!rst_ni) begin
byte_index <= '0;
end else if (byte_clr) begin
byte_index <= byte_num;
end else if (byte_decr) begin
byte_index <= byte_index - 1'b1;
end else begin
byte_index <= byte_index;
end
end
// SDA and SCL at the previous clock edge
always_ff @ (posedge clk_i or negedge rst_ni) begin : bus_prev
if (!rst_ni) begin
scl_i_q <= 1'b1;
sda_i_q <= 1'b1;
end else begin
scl_i_q <= scl_i;
sda_i_q <= sda_i;
end
end
// Stop issued before
always_ff @ (posedge clk_i or negedge rst_ni) begin : stop_state
if (!rst_ni) begin
no_stop <= 1'b0;
end else if (log_stop) begin
no_stop <= 1'b0;
end else if (log_start) begin
no_stop <= 1'b1;
end else begin
no_stop <= no_stop;
end
end
// (Repeated) Start condition detection by target
always_ff @ (posedge clk_i or negedge rst_ni) begin : s_detect
if (!rst_ni) begin
start_det <= 1'b0;
end else if (scl_i_q == 1'b1 && scl_i == 1'b1) begin
if (sda_i_q == 1'b1 && sda_i == '0) start_det <= 1'b1;
else start_det <= 1'b0;
end else begin
start_det <= 1'b0;
end
end
// Stop condition detection by target
always_ff @ (posedge clk_i or negedge rst_ni) begin : p_detect
if (!rst_ni) begin
stop_det <= 1'b0;
end else if (scl_i_q == 1'b1 && scl_i == 1'b1) begin
if (sda_i_q == '0 && sda_i == 1'b1) stop_det <= 1'b1;
else stop_det <= 1'b0;
end else begin
stop_det <= 1'b0;
end
end
// Bit counter on the target side
assign bit_ack = (bit_idx == 4'd8) && !start_det; // ack
// Increment counter on negative SCL edge
always_ff @ (posedge clk_i or negedge rst_ni) begin : tgt_bit_counter
if (!rst_ni) begin
bit_idx <= 4'd0;
end else if (start_det || bit_ack) begin
bit_idx <= 4'd0;
end else if (scl_i_q == 1'b1 && scl_i == '0) begin
bit_idx <= bit_idx + 1'b1;
end else begin
bit_idx <= bit_idx;
end
end
// Counter for continuously released SCL state
always_ff @ (posedge clk_i or negedge rst_ni) begin : scl_high_counter
if (!rst_ni) begin
scl_high_cnt <= 32'd0;
end else if (scl_i) begin
scl_high_cnt <= scl_high_cnt + 1'b1;
end else begin
scl_high_cnt <= 32'd0;
end
end
// Deserializer for a byte read from the bus on the target side
assign address0_match = ((input_byte[7:1] & target_mask0_i) == target_address0_i);
assign address1_match = ((input_byte[7:1] & target_mask1_i) == target_address1_i);
assign address_match = (address0_match || address1_match);
assign rw_bit = input_byte[0];
// Shift data in on positive SCL edge
always_ff @ (posedge clk_i or negedge rst_ni) begin : tgt_input_register
if (!rst_ni) begin
input_byte <= 8'h00;
end else if (input_byte_clr) begin
input_byte <= 8'h00;
end else if (scl_i_q == '0 && scl_i == 1'b1) begin
if (!bit_ack) input_byte[7:0] <= {input_byte[6:0], sda_i}; // MSB goes in first
end
end
// Detection by the target of ACK bit send by the host
always_ff @ (posedge clk_i or negedge rst_ni) begin : host_ack_register
if (!rst_ni) begin
host_ack <= 1'b0;
end else if (scl_i_q == '0 && scl_i == 1'b1) begin
if (bit_ack) host_ack <= ~sda_i;
end
end
// State definitions
typedef enum logic [5:0] {
Idle, PopFmtFifo, SetupStart, HoldStart, SetupStop, HoldStop,
ClockLow, SetupBit, ClockPulse, HoldBit,
ClockLowAck, SetupDevAck, ClockPulseAck, HoldDevAck,
ReadClockLow, ReadSetupBit, ReadClockPulse, ReadHoldBit,
HostClockLowAck, HostSetupBitAck, HostClockPulseAck, HostHoldBitAck,
Active, ClockStart, ClockStop,
AddrRead, AddrAckWait, AddrAckSetup, AddrAckPulse, AddrAckHold,
TransmitWait, TransmitSetup, TransmitPulse, TransmitHold, TransmitAck,
AcquireByte, AcquireAckWait, AcquireAckSetup, AcquireAckPulse, AcquireAckHold,
PopTxFifo, AcquireSrP, StretchTxEmpty, StretchAcqFull, StretchAddr,
StretchAcquire, StretchTransmit
} state_e;
state_e state_q, state_d;
// Outputs for each state
always_comb begin : state_outputs
host_idle_o = 1'b1;
target_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;
tx_fifo_rready_o = 1'b0;
acq_fifo_wvalid_o = 1'b0;
acq_fifo_wdata_o = 10'b0;
event_nak_o = 1'b0;
event_scl_interference_o = 1'b0;
event_sda_interference_o = 1'b0;
event_sda_unstable_o = 1'b0;
event_stretch_timeout_o = 1'b0;
event_trans_complete_o = 1'b0;
event_tx_empty_o = 1'b0;
event_tx_nonempty_o = 1'b0;
event_ack_stop_o = 1'b0;
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;
if (host_enable_i && sda_i == '0) event_sda_interference_o = 1'b1;
if (!target_address0_i && !target_mask0_i && !target_address1_i && !target_mask1_i) begin
acq_fifo_wvalid_o = 1'b0;
end
end
// SetupStart: SDA and SCL are released
SetupStart : begin
host_idle_o = 1'b0;
sda_temp = 1'b1;
scl_temp = 1'b1;
if (sda_i == '0) event_sda_interference_o = 1'b1;
if (restart == 1'b1) event_trans_complete_o = 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
// ClockStart: SCL is pulled low, SDA stays low
ClockStart : begin
host_idle_o = 1'b0;
sda_temp = 1'b0;
scl_temp = 1'b0;
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;
if (sda_temp == 1'b1 && sda_i == '0) event_sda_interference_o = 1'b1;
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;
if ((stretch > stretch_timeout_i) && timeout_enable_i) begin
event_stretch_timeout_o = 1'b1;
end
if (scl_i_q == 1'b1 && scl_i == '0) event_scl_interference_o = 1'b1;
if (sda_temp == 1'b1 && sda_i == '0) event_sda_interference_o = 1'b1;
if (sda_i_q != sda_i) event_sda_unstable_o = 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;
if (sda_temp == 1'b1 && sda_i == '0) event_sda_interference_o = 1'b1;
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;
if (sda_i == '0 && !fmt_flag_nak_ok_i) event_nak_o = 1'b1;
if ((stretch > stretch_timeout_i) && timeout_enable_i) begin
event_stretch_timeout_o = 1'b1;
end
if (scl_i_q == 1'b1 && scl_i == '0) event_scl_interference_o = 1'b1;
if (sda_i_q != sda_i) event_sda_unstable_o = 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 is released
ReadClockLow : begin
host_idle_o = 1'b0;
sda_temp = 1'b1;
scl_temp = 1'b0;
end
// ReadSetupBit: Read indexed bit off SDA, SCL stays low
ReadSetupBit : begin
host_idle_o = 1'b0;
scl_temp = 1'b0;
end
// ReadClockPulse: SCL is released, the indexed bit value is read off SDA
ReadClockPulse : begin
host_idle_o = 1'b0;
scl_temp = 1'b1;
if ((stretch > stretch_timeout_i) && timeout_enable_i) begin
event_stretch_timeout_o = 1'b1;
end
if (scl_i_q == 1'b1 && scl_i == '0) event_scl_interference_o = 1'b1;
if (sda_i_q != sda_i) event_sda_unstable_o = 1'b1;
end
// ReadHoldBit: SCL is pulled low
ReadHoldBit : begin
host_idle_o = 1'b0;
scl_temp = 1'b0;
if (bit_index == '0 && tcount_q == 20'd1) begin
rx_fifo_wdata_o = read_byte; // transfer read data to rx_fifo
rx_fifo_wvalid_o = 1'b1; // assert that rx_fifo has valid data
end
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'b0;
else if (byte_index == 9'd1) sda_temp = 1'b1;
else sda_temp = 1'b0;
scl_temp = 1'b0;
if (sda_temp == 1'b1 && sda_i == '0) event_sda_interference_o = 1'b1;
end
// HostClockPulseAck: SCL is released
HostClockPulseAck : begin
host_idle_o = 1'b0;
if (fmt_flag_read_continue_i) sda_temp = 1'b0;
else if (byte_index == 9'd1) sda_temp = 1'b1;
else sda_temp = 1'b0;
scl_temp = 1'b1;
if ((stretch > stretch_timeout_i) && timeout_enable_i) begin
event_stretch_timeout_o = 1'b1;
end
if (scl_i_q == 1'b1 && scl_i == '0) event_scl_interference_o = 1'b1;
if (sda_temp == 1'b1 && sda_i == '0) event_sda_interference_o = 1'b1;
if (sda_i_q != sda_i) event_sda_unstable_o = 1'b1;
end
// HostHoldBitAck: SCL is pulled low
HostHoldBitAck : begin
host_idle_o = 1'b0;
if (fmt_flag_read_continue_i) sda_temp = 1'b0;
else if (byte_index == 9'd1) sda_temp = 1'b1;
else sda_temp = 1'b0;
scl_temp = 1'b0;
if (sda_temp == 1'b1 && sda_i == '0) event_sda_interference_o = 1'b1;
end
// ClockStop: SCL is pulled low, SDA stays low
ClockStop : begin
host_idle_o = 1'b0;
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;
if (sda_i == '0) event_sda_interference_o = 1'b1;
event_trans_complete_o = 1'b1;
end
// Active: continue while keeping SCL low
Active : begin
host_idle_o = 1'b0;
scl_temp = 1'b0;
end
// PopFmtFifo: populate fmt_fifo
PopFmtFifo : begin
host_idle_o = 1'b0;
if (fmt_flag_stop_after_i) scl_temp = 1'b1;
else scl_temp = 1'b0;
fmt_fifo_rready_o = 1'b1;
end
// AddrRead: read and compare target address
AddrRead : begin
target_idle_o = 1'b0;
if (bit_ack && address_match) begin
acq_fifo_wdata_o = {1'b0, 1'b1, input_byte}; // transfer data to acq_fifo
acq_fifo_wvalid_o = 1'b1; // assert that acq_fifo has valid data
if (tx_fifo_depth_i == '0 && rw_bit) event_tx_empty_o = 1'b1;
end
end
// AddrAckWait: pause before acknowledging
AddrAckWait : begin
target_idle_o = 1'b0;
end
// AddrAckSetup: target pulls SDA low while SCL is low
AddrAckSetup : begin
target_idle_o = 1'b0;
sda_temp = 1'b0;
end
// AddrAckPulse: target pulls SDA low while SCL is released
AddrAckPulse : begin
target_idle_o = 1'b0;
sda_temp = 1'b0;
end
// AddrAckHold: target pulls SDA low while SCL is pulled low
AddrAckHold : begin
target_idle_o = 1'b0;
sda_temp = 1'b0;
end
// TransmitWait: pause before sending a bit
TransmitWait : begin
target_idle_o = 1'b0;
end
// TransmitSetup: target shifts indexed bit onto SDA while SCL is low
TransmitSetup : begin
target_idle_o = 1'b0;
sda_temp = tx_fifo_rdata_i[3'(bit_idx)];
end
// TransmitPulse: target shifts indexed bit onto SDA while SCL is released
TransmitPulse : begin
target_idle_o = 1'b0;
sda_temp = tx_fifo_rdata_i[3'(bit_idx)];
end
// TransmitHold: target shifts indexed bit onto SDA while SCL is pulled low
TransmitHold : begin
target_idle_o = 1'b0;
sda_temp = tx_fifo_rdata_i[3'(bit_idx)];
end
// TransmitAck: target waits for host to ACK transmission
TransmitAck : begin
target_idle_o = 1'b0;
if (tx_fifo_depth_i == 6'd1 && !tx_fifo_wvalid_i && host_ack) event_tx_empty_o = 1'b1;
if (host_ack && (start_det || stop_det)) event_ack_stop_o = 1'b1;
end
// PopTxFifo: populate tx_fifo
PopTxFifo : begin
target_idle_o = 1'b0;
tx_fifo_rready_o = 1'b1;
end
// AcquireByte: target acquires a byte
AcquireByte : begin
target_idle_o = 1'b0;
if (bit_ack) begin
acq_fifo_wdata_o = {1'b0, 1'b0, input_byte}; // transfer data to acq_fifo
acq_fifo_wvalid_o = 1'b1; // assert that acq_fifo has valid data
end
end
// AcquireAckWait: pause before acknowledging
AcquireAckWait : begin
target_idle_o = 1'b0;
end
// AcquireAckSetup: target pulls SDA low while SCL is low
AcquireAckSetup : begin
target_idle_o = 1'b0;
sda_temp = 1'b0;
end
// AcquireAckPulse: target pulls SDA low while SCL is released
AcquireAckPulse : begin
target_idle_o = 1'b0;
sda_temp = 1'b0;
end
// AcquireAckHold: target pulls SDA low while SCL is pulled low
AcquireAckHold : begin
target_idle_o = 1'b0;
sda_temp = 1'b0;
end
// AcquireSrP: target acquires repeated Start or Stop
AcquireSrP : begin
if (start_det) acq_fifo_wdata_o = {1'b1, 1'b1, input_byte};
else acq_fifo_wdata_o = {1'b1, 1'b0, input_byte};
acq_fifo_wvalid_o = 1'b1;
if (tx_fifo_depth_i != '0) event_tx_nonempty_o = 1'b1;
end
// StretchAddr: target stretches the clock after matching an address
StretchAddr : begin
target_idle_o = 1'b0;
scl_temp = 1'b0;
end
// StretchAcquire: target stretches the clock after acquiring a byte
StretchAcquire : begin
target_idle_o = 1'b0;
scl_temp = 1'b0;
end
// StretchTransmit: target stretches the clock after transmitting a byte
StretchTransmit : begin
target_idle_o = 1'b0;
scl_temp = 1'b0;
end
// StretchTxEmpty: target stretches the clock when tx_fifo is empty
StretchTxEmpty : begin
target_idle_o = 1'b0;
tx_fifo_rready_o = 1'b1;
scl_temp = 1'b0;
if (tx_fifo_depth_i == '0) event_tx_empty_o = 1'b1;
end
// StretchAcqFull: target stretches the clock when acq_fifo is full
StretchAcqFull : begin
target_idle_o = 1'b0;
scl_temp = 1'b0;
end
// default
default : begin
host_idle_o = 1'b1;
target_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;
tx_fifo_rready_o = 1'b0;
acq_fifo_wvalid_o = 1'b0;
acq_fifo_wdata_o = 10'b0;
event_nak_o = 1'b0;
event_scl_interference_o = 1'b0;
event_sda_interference_o = 1'b0;
event_sda_unstable_o = 1'b0;
event_stretch_timeout_o = 1'b0;
event_trans_complete_o = 1'b0;
event_tx_empty_o = 1'b0;
event_tx_nonempty_o = 1'b0;
event_ack_stop_o = 1'b0;
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_clr = 1'b0;
byte_decr = 1'b0;
byte_clr = 1'b0;
read_byte_clr = 1'b0;
shift_data_en = 1'b0;
log_start = 1'b0;
log_stop = 1'b0;
restart = 1'b0;
input_byte_clr = 1'b0;
unique case (state_q)
// Idle: initial state, SDA and SCL are released (high)
Idle : begin
if (!host_enable_i && !target_enable_i) state_d = Idle; // Idle unless host is enabled
else if (host_enable_i) begin
if (!fmt_fifo_rvalid_i) state_d = Idle;
else state_d = Active;
end else if (target_enable_i) begin
if (!start_det) state_d = Idle;
else begin
state_d = AddrRead;
input_byte_clr = 1'b1;
end
end
end
// SetupStart: SDA and SCL are released
SetupStart : begin
if (no_stop) restart = 1'b1;
if (tcount_q == 20'd1) begin
state_d = HoldStart;
load_tcount = 1'b1;
tcount_sel = tHoldStart;
log_start = 1'b1;
end
end
// HoldStart: SDA is pulled low, SCL is released
HoldStart : begin
if (tcount_q == 20'd1) begin
state_d = ClockStart;
load_tcount = 1'b1;
tcount_sel = tClockStart;
end
end
// ClockStart: SCL is pulled low, SDA stays low
ClockStart : begin
if (tcount_q == 20'd1) 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 == 20'd1) 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 == 20'd1) 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 == 20'd1) begin
state_d = HoldBit;
load_tcount = 1'b1;
tcount_sel = tHoldBit;
end
end
// HoldBit: SCL is pulled low
HoldBit : begin
if (tcount_q == 20'd1) begin
load_tcount = 1'b1;
tcount_sel = tClockLow;
if (bit_index == '0) begin
state_d = ClockLowAck;
bit_clr = 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 == 20'd1) 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 == 20'd1) begin
state_d = ClockPulseAck;
load_tcount = 1'b1;
tcount_sel = tClockPulse;
end
end
// ClockPulseAck: SCL is released
ClockPulseAck : begin
if (tcount_q == 20'd1) begin
state_d = HoldDevAck;
load_tcount = 1'b1;
tcount_sel = tHoldBit;
end
end
// HoldDevAck: SCL is pulled low
HoldDevAck : begin
if (tcount_q == 20'd1) begin
if (fmt_flag_stop_after_i) begin
state_d = ClockStop;
load_tcount = 1'b1;
tcount_sel = tClockStop;
end else begin
state_d = PopFmtFifo;
load_tcount = 1'b1;
tcount_sel = tNoDelay;
end
end
end
// ReadClockLow: SCL is pulled low, SDA is released
ReadClockLow : begin
if (tcount_q == 20'd1) 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 == 20'd1) begin
state_d = ReadClockPulse;
load_tcount = 1'b1;
tcount_sel = tClockPulse;
end
end
// ReadClockPulse: SCL is released, the indexed bit value is read off SDA
ReadClockPulse : begin
if (tcount_q == 20'd1) begin
state_d = ReadHoldBit;
load_tcount = 1'b1;
tcount_sel = tHoldBit;
shift_data_en = 1'b1;
end
end
// ReadHoldBit: SCL is pulled low
ReadHoldBit : begin
if (tcount_q == 20'd1) begin
load_tcount = 1'b1;
tcount_sel = tClockLow;
if (bit_index == '0) begin
state_d = HostClockLowAck;
bit_clr = 1'b1;
read_byte_clr = 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 == 20'd1) begin
state_d = HostSetupBitAck;
load_tcount = 1'b1;
tcount_sel = tSetupBit;
end
end
// HostSetupBitAck: Shift Ack/Nack bit onto SDA
HostSetupBitAck : begin
if (tcount_q == 20'd1) begin
state_d = HostClockPulseAck;
load_tcount = 1'b1;
tcount_sel = tClockPulse;
end
end
// HostClockPulseAck: SCL is released
HostClockPulseAck : begin
if (tcount_q == 20'd1) begin
state_d = HostHoldBitAck;
load_tcount = 1'b1;
tcount_sel = tHoldBit;
end
end
// HostHoldBitAck: SCL is pulled low
HostHoldBitAck : begin
if (tcount_q == 20'd1) begin
if (byte_index == 9'd1) begin
if (fmt_flag_stop_after_i) begin
state_d = ClockStop;
load_tcount = 1'b1;
tcount_sel = tClockStop;
end else begin
state_d = PopFmtFifo;
load_tcount = 1'b1;
tcount_sel = tNoDelay;
end
end else begin
state_d = ReadClockLow;
load_tcount = 1'b1;
tcount_sel = tClockLow;
byte_decr = 1'b1;
end
end
end
// ClockStop: SCL is pulled low, SDA stays low
ClockStop : begin
if (tcount_q == 20'd1) begin
state_d = SetupStop;
load_tcount = 1'b1;
tcount_sel = tSetupStop;
end
end
// SetupStop: SDA is pulled low, SCL is released
SetupStop : begin
if (tcount_q == 20'd1) begin
state_d = HoldStop;
load_tcount = 1'b1;
tcount_sel = tHoldStop;
log_stop = 1'b1;
end
end
// HoldStop: SDA and SCL are released
HoldStop : begin
if (tcount_q == 20'd1) begin
if (!host_enable_i) begin
state_d = Idle;
load_tcount = 1'b1;
tcount_sel = tNoDelay;
end else begin
state_d = PopFmtFifo;
load_tcount = 1'b1;
tcount_sel = tNoDelay;
end
end
end
// Active: continue while keeping SCL low
Active : begin
if (fmt_flag_read_bytes_i) begin
byte_clr = 1'b1;
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
// PopFmtFifo: populate fmt_fifo
PopFmtFifo : begin
if (!host_enable_i) begin
state_d = ClockStop;
load_tcount = 1'b1;
tcount_sel = tClockStop;
end else if (fmt_fifo_depth_i == 6'd1 && !fmt_fifo_wvalid_i) begin
state_d = Idle;
load_tcount = 1'b1;
tcount_sel = tNoDelay;
end else begin
state_d = Active;
load_tcount = 1'b1;
tcount_sel = tNoDelay;
end
end
// AddrRead: read and compare target address
AddrRead : begin
if (bit_ack) begin
if (address_match) begin
state_d = AddrAckWait;
load_tcount = 1'b1;
tcount_sel = tClockStart;
end else state_d = Idle;
end
end
// AddrAckWait: pause before acknowledging
AddrAckWait : begin
if (tcount_q == 20'd1) begin
if (stretch_en_addr_i) state_d = StretchAddr;
else state_d = AddrAckSetup;
end
end
// AddrAckSetup: target pulls SDA low while SCL is low
AddrAckSetup : begin
if (scl_i) state_d = AddrAckPulse;
end
// AddrAckPulse: target pulls SDA low while SCL is released
AddrAckPulse : begin
if (!scl_i) begin
state_d = AddrAckHold;
load_tcount = 1'b1;
tcount_sel = tClockStart;
end
end
// AddrAckHold: target pulls SDA low while SCL is pulled low
AddrAckHold : begin
if (tcount_q == 20'd1) begin
if (rw_bit) begin
if (tx_fifo_rvalid_i) begin
state_d = TransmitWait;
load_tcount = 1'b1;
tcount_sel = tClockLow;
end else state_d = StretchTxEmpty;
end else begin
if (acq_fifo_wready_i) state_d = AcquireByte;
else state_d = StretchAcqFull;
end
end
end
// TransmitWait: pause before sending a bit
TransmitWait : begin
if (tcount_q == 20'd1) begin
state_d = TransmitSetup;
end
end
// TransmitSetup: target shifts indexed bit onto SDA while SCL is low
TransmitSetup : begin
if (scl_i) state_d = TransmitPulse;
end
// TransmitPulse: target shifts indexed bit onto SDA while SCL is released
TransmitPulse : begin
if (!scl_i) begin
state_d = TransmitHold;
load_tcount = 1'b1;
tcount_sel = tClockStart;
end
end
// TransmitHold: target shifts indexed bit onto SDA while SCL is pulled low
TransmitHold : begin
if (tcount_q == 20'd1) begin
load_tcount = 1'b1;
tcount_sel = tClockLow;
if (bit_ack) begin
state_d = TransmitAck;
end else begin
state_d = TransmitWait;
end
end
end
// TransmitAck: target waits for host to ACK transmission
TransmitAck : begin
if (scl_i) begin
if (host_ack) begin
if (stretch_en_tx_i) state_d = StretchTransmit;
else state_d = PopTxFifo;
end else begin
if (start_det || stop_det) state_d = AcquireSrP;
end
end
end
// PopTxFifo: populate tx_fifo
PopTxFifo : begin
if (!target_enable_i) begin
state_d = Idle;
end else if (tx_fifo_depth_i == 6'd1 && !tx_fifo_wvalid_i) begin
state_d = StretchTxEmpty;
end else begin
state_d = TransmitWait;
load_tcount = 1'b1;
tcount_sel = tClockLow;
end
end
// AcquireByte: target acquires a byte
AcquireByte : begin
if (bit_ack) begin
state_d = AcquireAckWait;
load_tcount = 1'b1;
tcount_sel = tClockStart;
end
end
// AcquireAckWait: pause before acknowledging
AcquireAckWait : begin
if (tcount_q == 20'd1) begin
if (stretch_en_acq_i) state_d = StretchAcquire;
else state_d = AcquireAckSetup;
end
end
// AcquireAckSetup: target pulls SDA low while SCL is low
AcquireAckSetup : begin
if (scl_i) state_d = AcquireAckPulse;
end
// AcquireAckPulse: target pulls SDA low while SCL is released
AcquireAckPulse : begin
if (!scl_i) begin
state_d = AcquireAckHold;
load_tcount = 1'b1;
tcount_sel = tClockStart;
end
end
// AcquireAckHold: target pulls SDA low while SCL is pulled low
AcquireAckHold : begin
if (tcount_q == 20'd1) begin
if (bit_ack) begin
if (start_det || stop_det) state_d = AcquireSrP;
else state_d = AcquireByte;
end
end
end
// AcquireSrP: target acquires repeated Start or Stop
AcquireSrP : begin
state_d = Idle;
end
// StretchAddr: target stretches the clock after matching an address
StretchAddr : begin
if (!stretch_stop_i) state_d = StretchAddr;
else state_d = AddrAckSetup;
end
// StretchAcquire: target stretches the clock after acquiring a byte
StretchAcquire : begin
if (!stretch_stop_i) state_d = StretchAcquire;
else state_d = AcquireAckSetup;
end
// StretchTransmit: target stretches the clock after transmitting a byte
StretchTransmit : begin
if (!stretch_stop_i) state_d = StretchTransmit;
else state_d = PopTxFifo;
end
// StretchTxEmpty: target stretches the clock when tx_fifo is empty
StretchTxEmpty : begin
if (tx_fifo_depth_i == '0) begin
state_d = StretchTxEmpty;
end else begin
state_d = TransmitWait;
load_tcount = 1'b1;
tcount_sel = tClockLow;
end
end
// StretchAcqFull: target stretches the clock when acq_fifo is full
StretchAcqFull : begin
if (acq_fifo_wready_i) state_d = AcquireByte;
else state_d = StretchAcqFull;
end
// default
default : begin
state_d = Idle;
load_tcount = 1'b0;
tcount_sel = tNoDelay;
bit_decr = 1'b0;
bit_clr = 1'b0;
byte_decr = 1'b0;
byte_clr = 1'b0;
read_byte_clr = 1'b0;
shift_data_en = 1'b0;
log_start = 1'b0;
log_stop = 1'b0;
restart = 1'b0;
input_byte_clr = 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;
// Host ceased sending SCL pulses during ongoing transaction
assign event_host_timeout_o = (!target_idle_o & (scl_high_cnt > host_timeout_i)) ? 1'b1 : 1'b0;
endmodule