blob: ad7246bf4d1d1c271e4daeae6013ac17bd683291 [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 import i2c_pkg::*;
#(
parameter int FifoDepth = 64,
localparam int FifoDepthWidth = $clog2(FifoDepth+1)
) (
input clk_i, // clock
input rst_ni, // active low reset
input scl_i, // serial clock input from i2c bus
output logic scl_o, // serial clock output to i2c bus
input sda_i, // serial data input from i2c bus
output logic 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 [6: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 [FifoDepthWidth-1:0] tx_fifo_depth_i, // tx_fifo_depth
output logic tx_fifo_rready_o, // pop entry from tx_fifo
input [7:0] tx_fifo_rdata_i, // byte in tx_fifo to be sent to host
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
input [FifoDepthWidth-1:0] acq_fifo_depth_i,
output logic acq_fifo_wready_o, // local version of ready
input [9:0] acq_fifo_rdata_i, // only used for assertion
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 [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_cmd_complete_o, // Command is complete
output logic event_tx_stretch_o, // tx transaction is being stretched
output logic event_unexp_stop_o, // target received an unexpected stop
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 [31:0] stretch_idle_cnt; // counter for clock being stretched by target
// or clock idle by host.
logic stretch_en;
// 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_q; // scl internal flopped
logic sda_q; // data internal flopped
logic scl_d; // scl internal
logic sda_d; // 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 trans_started; // indicates a transaction has started
logic pend_restart; // there is a pending restart waiting to be processed
logic req_restart; // request restart
logic log_start; // indicates start is been issued
logic log_stop; // indicates stop is been issued
// 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 acq_fifo_wready;
logic stretch_addr;
logic stretch_tx;
logic expect_stop;
// 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 acknowledged transmitted byte
// Clock counter implementation
typedef enum logic [3:0] {
tSetupStart,
tHoldStart,
tSetupData,
tClockStart,
tClockLow,
tClockPulse,
tHoldBit,
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);
tSetupData : tcount_d = 20'(t_r_i) + 20'(tsu_dat_i);
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) + 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_idle_cnt == '0 || 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
logic unused_fifo_outputs;
assign unused_fifo_outputs = |{tx_fifo_depth_i, tx_fifo_wvalid_i, fmt_fifo_wvalid_i};
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/idle detection when i2c_ctrl.
// When in host mode, this is a stretch count for how long an external target
// has stretched the clock.
// When in target mode, this is an idle count for how long an external host
// has kept the clock idle after a START indication.
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
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
stretch_idle_cnt <= '0;
end else if (!target_idle_o && scl_i) begin
stretch_idle_cnt <= stretch_idle_cnt + 1'b1;
end else begin
stretch_idle_cnt <= '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
// Registers whether a transaction start has been observed.
// A transaction start does not include a "restart", but rather
// the first start after enabling i2c, or a start observed after a
// stop.
always_ff @(posedge clk_i or negedge rst_ni) begin
if (!rst_ni) begin
trans_started <= '0;
end else if (trans_started && !host_enable_i) begin
trans_started <= '0;
end else if (log_start) begin
trans_started <= 1'b1;
end else if (log_stop) begin
trans_started <= 1'b0;
end
end
always_ff @(posedge clk_i or negedge rst_ni) begin
if (!rst_ni) begin
pend_restart <= '0;
end else if (pend_restart && !host_enable_i) begin
pend_restart <= '0;
end else if (req_restart) begin
pend_restart <= 1'b1;
end else if (log_start) begin
pend_restart <= '0;
end
end
// (Repeated) Start condition detection by target
assign start_det = target_enable_i && (scl_i_q && scl_i) & (sda_i_q && !sda_i);
// Stop condition detection by target
assign stop_det = target_enable_i && (scl_i_q && scl_i) & (!sda_i_q && sda_i);
// Bit counter on the target side
assign bit_ack = (bit_idx == 4'd8); // 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) begin
bit_idx <= 4'd0;
end else if (scl_i_q && !scl_i) begin
// input byte clear is always asserted on a "start"
// condition.
if (input_byte_clr || bit_ack) bit_idx <= 4'd0;
else bit_idx <= bit_idx + 1'b1;
end else begin
bit_idx <= bit_idx;
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);
// 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 && scl_i) 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 && scl_i) begin
if (bit_ack) host_ack <= ~sda_i;
end
end
// An artificial acq_fifo_wready is used here to ensure we always have
// space to asborb a stop / repeat start format byte. Without guaranteeing
// space for this entry, the target module would need to stretch the
// repeat start / stop indication. If a system does not support stretching,
// there's no good way for a stop to be NACK'd.
logic [FifoDepthWidth-1:0] acq_fifo_remainder;
assign acq_fifo_remainder = FifoDepth - acq_fifo_depth_i;
assign acq_fifo_wready = acq_fifo_remainder > FifoDepthWidth'(1'b1);
// State definitions
typedef enum logic [5:0] {
Idle,
///////////////////////
// Host function states
///////////////////////
Active, PopFmtFifo,
// Host function starts a transaction
SetupStart, HoldStart, ClockStart,
// Host function stops a transaction
SetupStop, HoldStop, ClockStop,
// Host function transmits a bit to the external target
ClockLow, ClockPulse, HoldBit,
// Host function recevies an ack from the external target
ClockLowAck, ClockPulseAck, HoldDevAck,
// Host function reads a bit from the external target
ReadClockLow, ReadClockPulse, ReadHoldBit,
// Host function transmits an ack to the external target
HostClockLowAck, HostClockPulseAck, HostHoldBitAck,
/////////////////////////
// Target function states
/////////////////////////
// Target function receives start and address from external host
AcquireStart, AddrRead,
// Target function acknowledges the address and returns an ack to external host
AddrAckWait, AddrAckSetup, AddrAckPulse, AddrAckHold,
// Target function sends read data to external host-receiver
TransmitWait, TransmitSetup, TransmitPulse, TransmitHold,
// Target function receives ack from external host
TransmitAck, TransmitAckPulse, WaitForStop,
// Target function receives write data from the external host
AcquireByte,
// Target function sends ack to external host
AcquireAckWait, AcquireAckSetup, AcquireAckPulse, AcquireAckHold,
// Target function clock stretch handling.
StretchAddr,
StretchTx, StretchTxSetup,
StretchAcqFull
} state_e;
state_e state_q, state_d;
// enable sda interference detection
// Detects when the controller releases sda to be pulled high, but the line
// is unexpectedly held low by another driver.
logic en_sda_interf_det;
logic [16:0] sda_rise_cnt;
// sda_rise_latency refers to the time between
// changing sda_o to 1 and sampling sda_i as 1.
// This value is a combination of the bus rise time and the
// input sychronization delay
logic [16:0] sda_rise_latency;
assign sda_rise_latency = t_r_i + 16'h2;
// When detection is enabled, count through the rise time.
// Once rise time count is reached, hold in place until disabled.
always_ff @(posedge clk_i or negedge rst_ni) begin
if (!rst_ni) begin
sda_rise_cnt <= '0;
end else if (!en_sda_interf_det && |sda_rise_cnt) begin
// When detection is disabled, 0 the count.
// Only 0 the count if the count is currently non-zero to avoid
// unnecessary toggling.
sda_rise_cnt <= '0;
end else if (en_sda_interf_det && sda_rise_cnt < sda_rise_latency) begin
sda_rise_cnt <= sda_rise_cnt + 1'b1;
end
end
// There are two conditions of sda interference:
// 1. When the host function is first enabled, but for some reason sda_i is already 0.
// 2. Any time the host function is trying to drive a 1 but it observes a 0 instead.
//
// When the count is reached, we are pass the rise time period.
// Now check for any inconsistency in the sda value.
assign event_sda_interference_o = (host_idle_o & host_enable_i & !sda_i) |
((sda_rise_cnt == sda_rise_latency) & (sda_o & !sda_i));
logic rw_bit_q;
always_ff @(posedge clk_i or negedge rst_ni) begin
if (!rst_ni) begin
rw_bit_q <= '0;
end else if (bit_ack && address_match) begin
rw_bit_q <= rw_bit;
end
end
// Reverse the bit order since data should be sent out MSB first
logic [7:0] tx_fifo_rdata;
assign tx_fifo_rdata = {<<1{tx_fifo_rdata_i}};
// The usage of target_idle_o directly confuses xcelium and leads the
// the simulator to a combinational loop. While it may be a tool recognized
// loop, it is not an actual physical loop, since target_idle affects only
// state_d, which is not used directly by any logic in this module.
// This is a work around for a known tool limitation.
logic target_idle;
assign target_idle = target_idle_o;
// During a host issued read, a stop was received without first seeing a nack.
// This may be harmless but is technically illegal behavior, notify software.
assign event_unexp_stop_o = !target_idle & rw_bit_q & stop_det & !expect_stop;
// Outputs for each state
always_comb begin : state_outputs
host_idle_o = 1'b1;
target_idle_o = 1'b1;
sda_d = 1'b1;
scl_d = 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_unstable_o = 1'b0;
event_cmd_complete_o = 1'b0;
rw_bit = rw_bit_q;
stretch_en = 1'b0;
expect_stop = 1'b0;
unique case (state_q)
// Idle: initial state, SDA and SCL are released (high)
Idle : begin
host_idle_o = 1'b1;
sda_d = 1'b1;
scl_d = 1'b1;
end
// SetupStart: SDA and SCL are released
SetupStart : begin
host_idle_o = 1'b0;
sda_d = 1'b1;
scl_d = 1'b1;
if (log_start) event_cmd_complete_o = pend_restart;
end
// HoldStart: SDA is pulled low, SCL is released
HoldStart : begin
host_idle_o = 1'b0;
sda_d = 1'b0;
scl_d = 1'b1;
end
// ClockStart: SCL is pulled low, SDA stays low
ClockStart : begin
host_idle_o = 1'b0;
sda_d = 1'b0;
scl_d = 1'b0;
end
ClockLow : begin
host_idle_o = 1'b0;
sda_d = fmt_byte_i[bit_index];
scl_d = 1'b0;
end
// ClockPulse: SCL is released, SDA keeps the indexed bit value
ClockPulse : begin
host_idle_o = 1'b0;
sda_d = fmt_byte_i[bit_index];
scl_d = 1'b1;
stretch_en = 1'b1;
if (scl_i_q && !scl_i) event_scl_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_d = fmt_byte_i[bit_index];
scl_d = 1'b0;
end
// ClockLowAck: SCL pulled low, SDA is released
ClockLowAck : begin
host_idle_o = 1'b0;
sda_d = 1'b1;
scl_d = 1'b0;
end
// ClockPulseAck: SCL is released
ClockPulseAck : begin
host_idle_o = 1'b0;
sda_d = 1'b1;
scl_d = 1'b1;
if (sda_i && !fmt_flag_nak_ok_i) event_nak_o = 1'b1;
stretch_en = 1'b1;
if (scl_i_q && !scl_i) 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_d = 1'b1;
scl_d = 1'b0;
end
// ReadClockLow: SCL is pulled low, SDA is released
ReadClockLow : begin
host_idle_o = 1'b0;
sda_d = 1'b1;
scl_d = 1'b0;
end
// ReadClockPulse: SCL is released, the indexed bit value is read off SDA
ReadClockPulse : begin
host_idle_o = 1'b0;
scl_d = 1'b1;
stretch_en = 1'b1;
if (scl_i_q && !scl_i) 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_d = 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 pulled low, SDA is conditional
HostClockLowAck : begin
host_idle_o = 1'b0;
scl_d = 1'b0;
// If it is the last byte of a read, send a NAK before the stop.
// Otherwise send the ack.
if (fmt_flag_read_continue_i) sda_d = 1'b0;
else if (byte_index == 9'd1) sda_d = 1'b1;
else sda_d = 1'b0;
end
// HostClockPulseAck: SCL is released
HostClockPulseAck : begin
host_idle_o = 1'b0;
if (fmt_flag_read_continue_i) sda_d = 1'b0;
else if (byte_index == 9'd1) sda_d = 1'b1;
else sda_d = 1'b0;
scl_d = 1'b1;
stretch_en = 1'b1;
if (scl_i_q && !scl_i) event_scl_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_d = 1'b0;
else if (byte_index == 9'd1) sda_d = 1'b1;
else sda_d = 1'b0;
scl_d = 1'b0;
end
// ClockStop: SCL is pulled low, SDA stays low
ClockStop : begin
host_idle_o = 1'b0;
sda_d = 1'b0;
scl_d = 1'b0;
end
// SetupStop: SDA is pulled low, SCL is released
SetupStop : begin
host_idle_o = 1'b0;
sda_d = 1'b0;
scl_d = 1'b1;
end
// HoldStop: SDA and SCL are released
HoldStop : begin
host_idle_o = 1'b0;
sda_d = 1'b1;
scl_d = 1'b1;
event_cmd_complete_o = 1'b1;
end
// Active: continue while keeping SCL low
Active : begin
host_idle_o = 1'b0;
// If this is a transaction start, do not drive scl low
// since in the next state we will drive it high to initiate
// the start bit.
// If this is a restart, continue driving the clock low.
scl_d = fmt_flag_start_before_i && !trans_started;
end
// PopFmtFifo: populate fmt_fifo
PopFmtFifo : begin
host_idle_o = 1'b0;
if (fmt_flag_stop_after_i) scl_d = 1'b1;
else scl_d = 1'b0;
fmt_fifo_rready_o = 1'b1;
end
// AcquireStart: hold start condition
AcquireStart : begin
target_idle_o = 1'b0;
end
// AddrRead: read and compare target address
AddrRead : begin
target_idle_o = 1'b0;
rw_bit = input_byte[0];
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_d = 1'b0;
end
// AddrAckPulse: target pulls SDA low while SCL is released
AddrAckPulse : begin
target_idle_o = 1'b0;
sda_d = 1'b0;
end
// AddrAckHold: target pulls SDA low while SCL is pulled low
AddrAckHold : begin
target_idle_o = 1'b0;
sda_d = 1'b0;
// Upon transition to next state, populate the acquisition fifo
if (tcount_q == 20'd1) begin
// only write to fifo if stretching conditions are not met
acq_fifo_wvalid_o = ~stretch_addr;
acq_fifo_wdata_o = {AcqStart, input_byte};
end
end
// TransmitWait: Check if data is available prior to transmit
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_d = tx_fifo_rdata[3'(bit_idx)];
end
// TransmitPulse: target holds indexed bit onto SDA while SCL is released
TransmitPulse : begin
target_idle_o = 1'b0;
// Hold value
sda_d = sda_q;
end
// TransmitHold: target holds indexed bit onto SDA while SCL is pulled low, for the hold time
TransmitHold : begin
target_idle_o = 1'b0;
// Hold value
sda_d = sda_q;
end
// TransmitAck: target waits for host to ACK transmission
TransmitAck : begin
target_idle_o = 1'b0;
end
TransmitAckPulse : begin
target_idle_o = 1'b0;
if (!scl_i) begin
// Pop Fifo regardless of ack/nack
tx_fifo_rready_o = 1'b1;
end
end
// WaitForStop just waiting for host to trigger a stop after nack
WaitForStop : begin
target_idle_o = 1'b0;
expect_stop = 1'b1;
sda_d = 1'b1;
end
// AcquireByte: target acquires a byte
AcquireByte : begin
target_idle_o = 1'b0;
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_d = 1'b0;
end
// AcquireAckPulse: target pulls SDA low while SCL is released
AcquireAckPulse : begin
target_idle_o = 1'b0;
sda_d = 1'b0;
end
// AcquireAckHold: target pulls SDA low while SCL is pulled low
AcquireAckHold : begin
target_idle_o = 1'b0;
sda_d = 1'b0;
if (tcount_q == 20'd1) begin
acq_fifo_wdata_o = {AcqData, input_byte}; // transfer data to acq_fifo
acq_fifo_wvalid_o = acq_fifo_wready; // assert that acq_fifo has valid data
end
end
// StretchAddr: target stretches the clock if matching address cannot be
// despoited yet.
StretchAddr : begin
target_idle_o = 1'b0;
scl_d = 1'b0;
acq_fifo_wvalid_o = ~stretch_addr;
acq_fifo_wdata_o = {AcqStart, input_byte};
end
// StretchTx: target stretches the clock when tx_fifo is empty
StretchTx : begin
target_idle_o = 1'b0;
scl_d = 1'b0;
end
// StretchTxSetup: drive the return data
StretchTxSetup: begin
target_idle_o = 1'b0;
scl_d = 1'b0;
sda_d = tx_fifo_rdata[3'(bit_idx)];
end
// StretchAcqFull: target stretches the clock when acq_fifo is full
StretchAcqFull : begin
target_idle_o = 1'b0;
scl_d = 1'b0;
// When space becomes available, deposit entry
acq_fifo_wdata_o = {AcqData, input_byte}; // transfer data to acq_fifo
acq_fifo_wvalid_o = acq_fifo_wready; // assert that acq_fifo has valid data
end
// default
default : begin
host_idle_o = 1'b1;
target_idle_o = 1'b1;
sda_d = 1'b1;
scl_d = 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_unstable_o = 1'b0;
event_cmd_complete_o = 1'b0;
end
endcase // unique case (state_q)
// start / stop override
if (start_det || stop_det) begin
// Only add an entry if this is a repeated start or stop.
acq_fifo_wvalid_o = !target_idle_o;
acq_fifo_wdata_o = start_det ? {AcqRestart, input_byte} :
{AcqStop, input_byte};
event_cmd_complete_o = !target_idle_o;
end
end
assign stretch_addr = !acq_fifo_wready;
// Stretch Tx phase when:
// 1. When there is no data to return to host
// 2. When the acq_fifo contains any entry other than a singular start condition
// read command.
//
// Only the fifo depth is checked here, because stretch_tx is only evaluated by the
// fsm on the read path. This means a read start byte has already been deposited.
assign stretch_tx = ~tx_fifo_rvalid_i |
(acq_fifo_depth_i > FifoDepthWidth'(1'b1));
// Only used for assertion
logic unused_acq_rdata;
assign unused_acq_rdata = |acq_fifo_rdata_i;
// 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;
req_restart = 1'b0;
input_byte_clr = 1'b0;
en_sda_interf_det = 1'b0;
event_tx_stretch_o = 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 = Active;
end
end
// SetupStart: SDA and SCL are released
SetupStart : begin
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 stays low, shift indexed bit onto SDA
ClockLow : begin
en_sda_interf_det = 1'b1;
if (tcount_q == 20'd1) begin
load_tcount = 1'b1;
if (pend_restart) begin
state_d = SetupStart;
tcount_sel = tSetupStart;
end else begin
state_d = ClockPulse;
tcount_sel = tClockPulse;
end
end
end
// ClockPulse: SCL is released, SDA keeps the indexed bit value
ClockPulse : begin
en_sda_interf_det = 1'b1;
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
en_sda_interf_det = 1'b1;
if (tcount_q == 20'd1) begin
en_sda_interf_det = 1'b0;
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: Target is allowed to drive ack back
// to host (dut)
ClockLowAck : 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 = 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 is pulled low, SDA is conditional based on
// byte position
HostClockLowAck : begin
en_sda_interf_det = 1'b1;
if (tcount_q == 20'd1) begin
state_d = HostClockPulseAck;
load_tcount = 1'b1;
tcount_sel = tClockPulse;
end
end
// HostClockPulseAck: SCL is released
HostClockPulseAck : begin
en_sda_interf_det = 1'b1;
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
en_sda_interf_det = 1'b1;
if (tcount_q == 20'd1) begin
en_sda_interf_det = 1'b0;
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
en_sda_interf_det = 1'b1;
if (tcount_q == 20'd1) begin
en_sda_interf_det = 1'b0;
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 && !trans_started) begin
state_d = SetupStart;
load_tcount = 1'b1;
tcount_sel = tSetupStart;
end else begin
state_d = ClockLow;
load_tcount = 1'b1;
req_restart = fmt_flag_start_before_i;
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 == 7'h1) 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
// AcquireStart: hold start condition
AcquireStart : begin
if (scl_i_q && !scl_i) begin
// If we are not able to accept the start bit, stretch or nak
state_d = AddrRead;
input_byte_clr = 1'b1;
end
end
// AddrRead: read and compare target address
AddrRead : begin
if (bit_ack && address_match) begin
state_d = AddrAckWait;
load_tcount = 1'b1;
tcount_sel = tClockStart;
end else if (bit_ack && !address_match) begin
state_d = Idle;
end
end
// AddrAckWait: pause before acknowledging
AddrAckWait : begin
if (tcount_q == 20'd1 && !scl_i) begin
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
// Stretch when requested by software or when there is insufficient
// space to hold the start / address format byte.
// If there is sufficient space, the format byte is written into the acquisition fifo.
if (stretch_addr) begin
state_d = StretchAddr;
end else if (rw_bit_q) begin
state_d = TransmitWait;
end else if (!rw_bit_q) begin
state_d = AcquireByte;
end
end
end
// TransmitWait: Evaluate whether there are entries to send first
TransmitWait: begin
if (stretch_tx) begin
state_d = StretchTx;
end else begin
state_d = TransmitSetup;
load_tcount = 1'b1;
tcount_sel = tClockStart;
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
if (bit_ack) begin
state_d = TransmitAck;
end else begin
load_tcount = 1'b1;
tcount_sel = tClockStart;
state_d = TransmitSetup;
end
end
end
// Wait for clock to become positive.
TransmitAck : begin
if (scl_i) begin
state_d = TransmitAckPulse;
end
end
// TransmitAckPulse: target waits for host to ACK transmission
// If a nak is received, that means a stop is incoming.
TransmitAckPulse : begin
if (!scl_i) begin
// If host acknowledged, that means we must continue
if (host_ack) begin
state_d = TransmitWait;
end else begin
// If host nak'd then the transaction is about to terminate, go to a wait state
state_d = WaitForStop;
end
end
end
// An inert state just waiting for host to issue a stop
// Cannot cycle back to idle directly as other events depend on the system being
// non-idle.
WaitForStop: begin
state_d = WaitForStop;
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 && !scl_i) begin
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 whilREe SCL is pulled low
AcquireAckHold : begin
if (tcount_q == 20'd1) begin
// If there is no space for the current entry, stretch clocks and
// wait for software to make space.
state_d = acq_fifo_wready ? AcquireByte : StretchAcqFull;
end
end
// StretchAddr: The address phase can not yet be completed, stretch
// clock and wait.
StretchAddr : begin
if (!stretch_addr) begin
// When transmitting after an address stretch, we need to assume
// that is looks like a Tx stretch. This is because if we try
// to follow the normal path, the logic will release the clock
// too early relative to driving the data. This will cause a
// setup violation. This is the same case to needing StretchTxSetup.
state_d = rw_bit_q ? StretchTx : AcquireByte;
end
end
// StretchTx: target stretches the clock when tx conditions are not satisfied.
StretchTx : begin
// When in stretch state, always notify software that help is required.
event_tx_stretch_o = 1'b1;
if (!stretch_tx) begin
// When data becomes available, we must first drive it onto the line
// for at least the "setup" period. If we do not, once the clock is released, the
// pull-up in the system will likely immediately trigger a rising clock
// edge (since the stretch likely pushed us way beyond the original intended
// rise). If we do not artificially create the setup period here, it will
// likely create a timing violation.
state_d = StretchTxSetup;
load_tcount = 1'b1;
tcount_sel = tSetupData;
// When leaving stretch state, de-assert software notification
event_tx_stretch_o = 1'b0;
end
end
StretchTxSetup : begin
if (tcount_q == 20'd1) begin
state_d = TransmitSetup;
end
end
// StretchAcqFull: target stretches the clock when acq_fifo is full
// When space becomes available, deposit the entry into the acqusition fifo
// and move on to receive the next byte.
StretchAcqFull : begin
if (acq_fifo_wready) state_d = AcquireByte;
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;
input_byte_clr = 1'b0;
event_tx_stretch_o = 1'b0;
end
endcase // unique case (state_q)
// When a start is detected, always go to the acquire start state.
// Differences in repeated start / start handling are done in the
// other fsm.
if (!target_idle && !target_enable_i) begin
// If the target function is currently not idle but target_enable is suddenly dropped,
// (maybe because the host locked up and we want to cycle back to an initial state),
// transition immediately.
// The same treatment is not given to the host mode because it already attempts to
// gracefully terminate. If the host cannot gracefully terminate for whatever reason,
// (the other side is holding SCL low), we may need to forcefully reset the module.
// TODO: It may be worth having a force stop condition to force the host back to
// Idle in case graceful termination is not possible.
state_d = Idle;
end else if (start_det) begin
state_d = AcquireStart;
end else if (stop_det) begin
state_d = Idle;
end
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
always_ff @(posedge clk_i or negedge rst_ni) begin
if (!rst_ni) begin
scl_q <= 1'b1;
sda_q <= 1'b1;
end else begin
scl_q <= scl_d;
sda_q <= sda_d;
end
end
assign scl_o = scl_q;
assign sda_o = sda_q;
// Host ceased sending SCL pulses during ongoing transaction
assign event_host_timeout_o = !target_idle_o & (stretch_idle_cnt > host_timeout_i);
// Target stretched clock beyond timeout
assign event_stretch_timeout_o = stretch_en &&
(stretch_idle_cnt[30:0] > stretch_timeout_i) && timeout_enable_i;
// Fed out for interrupt purposes
assign acq_fifo_wready_o = acq_fifo_wready;
// Check to make sure scl_i is never a single cycle glitch
`ASSERT(SclInputGlitch_A, $rose(scl_i) |-> ##1 scl_i)
// Make sure we never attempt to send a single cycle glitch
`ASSERT(SclOutputGlitch_A, $rose(scl_o) |-> ##1 scl_o)
// If we are actively transmitting, that must mean that there are no
// unhandled write commands and if there is a command present it must be
// a read.
`ASSERT(AcqDepthRdCheck_A, ((state_q == TransmitSetup) && (acq_fifo_depth_i > '0)) |->
(acq_fifo_depth_i == 1) && acq_fifo_rdata_i[0])
endmodule