module i2c_single_reg #(
    parameter FILTER_LEN = 4,
    parameter DEV_ADDR = 7'h70
)
(
    input  wire       clk,
    input  wire       rst,

    /*
     * I2C interface
     */
    input  wire       scl_i,
    output wire       scl_o,
    output wire       scl_t,
    input  wire       sda_i,
    output wire       sda_o,
    output wire       sda_t,

    /*
     * Data register
     */
    input  wire [7:0] data_in,
    input  wire       data_latch,
    output wire [7:0] data_out
);

localparam [4:0]
    STATE_IDLE = 4'd0,
    STATE_ADDRESS = 4'd1,
    STATE_ACK = 4'd2,
    STATE_WRITE_1 = 4'd3,
    STATE_WRITE_2 = 4'd4,
    STATE_READ_1 = 4'd5,
    STATE_READ_2 = 4'd6,
    STATE_READ_3 = 4'd7;

reg [4:0] state_reg = STATE_IDLE;

reg [7:0] data_reg = 8'd0;
reg [7:0] shift_reg = 8'd0;

reg mode_read_reg = 1'b0;

reg [3:0] bit_count_reg = 4'd0;

reg [FILTER_LEN-1:0] scl_i_filter_reg = {FILTER_LEN{1'b1}};
reg [FILTER_LEN-1:0] sda_i_filter_reg = {FILTER_LEN{1'b1}};

reg scl_i_reg = 1'b1;
reg sda_i_reg = 1'b1;

reg sda_o_reg = 1'b1;

reg last_scl_i_reg = 1'b1;
reg last_sda_i_reg = 1'b1;

assign scl_o = 1'b1;
assign scl_t = 1'b1;
assign sda_o = sda_o_reg;
assign sda_t = sda_o_reg;

assign data_out = data_reg;

wire scl_posedge = scl_i_reg && !last_scl_i_reg;
wire scl_negedge = !scl_i_reg && last_scl_i_reg;
wire sda_posedge = sda_i_reg && !last_sda_i_reg;
wire sda_negedge = !sda_i_reg && last_sda_i_reg;

wire start_bit = sda_negedge && scl_i_reg;
wire stop_bit = sda_posedge && scl_i_reg;

always @(posedge clk) begin

    if (start_bit) begin
        sda_o_reg <= 1'b1;

        bit_count_reg = 4'd7;
        state_reg <= STATE_ADDRESS;
    end else if (stop_bit) begin
        sda_o_reg <= 1'b1;

        state_reg <= STATE_IDLE;
    end else begin
        case (state_reg)
            STATE_IDLE: begin
                // line idle
                sda_o_reg <= 1'b1;

                state_reg <= STATE_IDLE;
            end
            STATE_ADDRESS: begin
                // read address
                sda_o_reg <= 1'b1;

                if (scl_posedge) begin
                    if (bit_count_reg > 0) begin
                        // shift in address
                        bit_count_reg <= bit_count_reg-1;
                        shift_reg <= {shift_reg[6:0], sda_i_reg};
                        state_reg <= STATE_ADDRESS;
                    end else begin
                        // check address
                        mode_read_reg <= sda_i_reg;
                        if (shift_reg[6:0] == DEV_ADDR) begin
                            // it's a match, send ACK
                            state_reg <= STATE_ACK;
                        end else begin
                            // no match, return to idle
                            state_reg <= STATE_IDLE;
                        end
                    end
                end else begin
                    state_reg <= STATE_ADDRESS;
                end
            end
            STATE_ACK: begin
                // send ACK bit
                if (scl_negedge) begin
                    sda_o_reg <= 1'b0;
                    bit_count_reg <= 4'd7;
                    if (mode_read_reg) begin
                        // reading
                        shift_reg <= data_reg;
                        state_reg <= STATE_READ_1;
                    end else begin
                        // writing
                        state_reg <= STATE_WRITE_1;
                    end
                end else begin
                    state_reg <= STATE_ACK;
                end
            end
            STATE_WRITE_1: begin
                // write data byte
                if (scl_negedge) begin
                    sda_o_reg <= 1'b1;
                    state_reg <= STATE_WRITE_2;
                end else begin
                    state_reg <= STATE_WRITE_1;
                end
            end
            STATE_WRITE_2: begin
                // write data byte
                sda_o_reg <= 1'b1;
                if (scl_posedge) begin
                    // shift in data bit
                    shift_reg <= {shift_reg[6:0], sda_i_reg};
                    if (bit_count_reg > 0) begin
                        bit_count_reg <= bit_count_reg-1;
                        state_reg <= STATE_WRITE_2;
                    end else begin
                        data_reg <= {shift_reg[6:0], sda_i_reg};
                        state_reg <= STATE_ACK;
                    end
                end else begin
                    state_reg <= STATE_WRITE_2;
                end
            end
            STATE_READ_1: begin
                // read data byte
                if (scl_negedge) begin
                    // shift out data bit
                    {sda_o_reg, shift_reg} = {shift_reg, sda_i_reg};

                    if (bit_count_reg > 0) begin
                        bit_count_reg = bit_count_reg-1;
                        state_reg = STATE_READ_1;
                    end else begin
                        state_reg = STATE_READ_2;
                    end
                end else begin
                    state_reg = STATE_READ_1;
                end
            end
            STATE_READ_2: begin
                // read ACK bit
                if (scl_negedge) begin
                    // release SDA
                    sda_o_reg <= 1'b1;
                    state_reg <= STATE_READ_3;
                end else begin
                    state_reg <= STATE_READ_2;
                end
            end
            STATE_READ_3: begin
                // read ACK bit
                if (scl_posedge) begin
                    if (sda_i_reg) begin
                        // NACK, return to idle
                        state_reg <= STATE_IDLE;
                    end else begin
                        // ACK, read another byte
                        bit_count_reg <= 4'd7;
                        shift_reg <= data_reg;
                        state_reg <= STATE_READ_1;
                    end
                end else begin
                    state_reg <= STATE_READ_3;
                end
            end
        endcase
    end

    if (data_latch) begin
        data_reg <= data_in;
    end

    scl_i_filter_reg <= (scl_i_filter_reg << 1) | scl_i;
    sda_i_filter_reg <= (sda_i_filter_reg << 1) | sda_i;

    if (scl_i_filter_reg == {FILTER_LEN{1'b1}}) begin
        scl_i_reg <= 1'b1;
    end else if (scl_i_filter_reg == {FILTER_LEN{1'b0}}) begin
        scl_i_reg <= 1'b0;
    end

    if (sda_i_filter_reg == {FILTER_LEN{1'b1}}) begin
        sda_i_reg <= 1'b1;
    end else if (sda_i_filter_reg == {FILTER_LEN{1'b0}}) begin
        sda_i_reg <= 1'b0;
    end

    last_scl_i_reg <= scl_i_reg;
    last_sda_i_reg <= sda_i_reg;

    if (rst) begin
        state_reg <= STATE_IDLE;
        sda_o_reg <= 1'b1;
    end
end

endmodule