// ============================================================================
// I2C_Controller.v — Fixed-frame I2C master (3-byte write transactions)
// ============================================================================
//
// SPDX-License-Identifier: MIT
// Copyright (c) 2026 retroDE contributors
//
// Clean-room implementation, not derived from any GPL upstream. Released
// under the MIT license to allow reuse outside the retroDE project. The
// retroDE project as a whole is distributed under GPLv3 — see ../LICENSE
// for the combined-work terms. See ../LICENSES/MIT.txt for the full MIT
// text.
//
// ----------------------------------------------------------------------------
// Purpose
//   Simple I2C master that sends a fixed 24-bit frame per transaction:
//       I2C_DATA = { slave_addr[6:0], rw, reg_addr[7:0], data[7:0] }
//   Slave ACK is sampled after each of the three bytes. A STOP condition is
//   generated at the end of the frame.
//
// Timing contract (compatible with legacy I2C_HDMI_Config parent)
//   CLK       Fabric clock; state is registered on its rising edge.
//   CLK_EN    One-cycle pulse at the desired I2C bit rate. State only advances
//             when CLK_EN is asserted.
//   CLK_PHASE Square wave at the SCL rate. During actively clocked bit cells,
//             I2C_SCLK is driven as ~CLK_PHASE. Dedicated START/STOP hold
//             phases force SCL high or low for a full cell.
//   I2C_SDAT  Open-drain: driven low via 1'b0 or released to 1'bz. An
//             external or FPGA internal pull-up is required on this line.
//   I2C_SCLK  Actively driven (not open-drain). This matches the known-good
//             DE25-Nano HDMI path and avoids relying on board-side pull-ups.
//
// Interface
//   Port list is preserved verbatim from the legacy module so this file is
//   a drop-in replacement. W_R is retained as a no-op input for source-
//   compatibility; direction is encoded in I2C_DATA[16] by convention.
//   SD_COUNTER and SDO are exposed for debug/observation only.
//
// Implementation note
//   The transaction is modeled as explicit phases rather than implicit state
//   updates. This keeps the START, bit, ACK, and STOP cells easy to inspect
//   while preserving the known-good bus waveform used by retroDE_splash.
// ============================================================================

`timescale 1ns/1ps

module I2C_Controller (
    input  wire        CLK,
    input  wire        CLK_EN,
    input  wire        CLK_PHASE,
    output wire        I2C_SCLK,
    inout  wire        I2C_SDAT,
    input  wire [23:0] I2C_DATA,
    input  wire        GO,
    output reg         END,
    input  wire        W_R,           // retained for interface compat; unused
    output wire        ACK,
    input  wire        RESET,
    output wire [5:0]  SD_COUNTER,    // debug: current transaction phase
    output wire        SDO            // debug: current SDA release state
);

    localparam [5:0] PH_IDLE       = 6'd0,
                     PH_START_HOLD = 6'd1,
                     PH_START_LOW  = 6'd2,
                     PH_B0_7       = 6'd3,
                     PH_B0_6       = 6'd4,
                     PH_B0_5       = 6'd5,
                     PH_B0_4       = 6'd6,
                     PH_B0_3       = 6'd7,
                     PH_B0_2       = 6'd8,
                     PH_B0_1       = 6'd9,
                     PH_B0_0       = 6'd10,
                     PH_ACK0       = 6'd11,
                     PH_B1_7       = 6'd12,
                     PH_B1_6       = 6'd13,
                     PH_B1_5       = 6'd14,
                     PH_B1_4       = 6'd15,
                     PH_B1_3       = 6'd16,
                     PH_B1_2       = 6'd17,
                     PH_B1_1       = 6'd18,
                     PH_B1_0       = 6'd19,
                     PH_ACK1       = 6'd20,
                     PH_B2_7       = 6'd21,
                     PH_B2_6       = 6'd22,
                     PH_B2_5       = 6'd23,
                     PH_B2_4       = 6'd24,
                     PH_B2_3       = 6'd25,
                     PH_B2_2       = 6'd26,
                     PH_B2_1       = 6'd27,
                     PH_B2_0       = 6'd28,
                     PH_ACK2       = 6'd29,
                     PH_STOP_LOW   = 6'd30,
                     PH_STOP_HIGH  = 6'd31,
                     PH_DONE       = 6'd32;

    reg  [5:0]  phase;
    reg  [23:0] frame_data;
    reg  [2:0]  ack_bits;
    reg         sda_release;

    reg  [5:0]  phase_next;
    reg  [23:0] frame_data_next;
    reg  [2:0]  ack_bits_next;
    reg         sda_release_next;
    reg         end_next;

    assign I2C_SCLK =
        (phase == PH_IDLE || phase == PH_START_HOLD || phase == PH_STOP_HIGH || phase == PH_DONE) ? 1'b1 :
        (phase == PH_START_LOW || phase == PH_STOP_LOW) ? 1'b0 :
                                                          ~CLK_PHASE;

    assign I2C_SDAT   = sda_release ? 1'bz : 1'b0;
    assign ACK        = |ack_bits;
    assign SDO        = sda_release;
    assign SD_COUNTER = phase;

    always @(*) begin
        phase_next       = phase;
        frame_data_next  = frame_data;
        ack_bits_next    = ack_bits;
        sda_release_next = sda_release;
        end_next         = END;

        case (phase)
            PH_IDLE: begin
                end_next         = 1'b0;
                sda_release_next = 1'b1;
                if (GO) begin
                    phase_next       = PH_START_HOLD;
                    frame_data_next  = I2C_DATA;
                    ack_bits_next    = 3'd0;
                    sda_release_next = 1'b0;
                end
            end

            PH_START_HOLD: begin
                phase_next       = PH_START_LOW;
                sda_release_next = 1'b0;
            end

            PH_START_LOW: begin
                phase_next       = PH_B0_7;
                sda_release_next = frame_data[23];
            end

            PH_B0_7: begin phase_next = PH_B0_6; sda_release_next = frame_data[22]; end
            PH_B0_6: begin phase_next = PH_B0_5; sda_release_next = frame_data[21]; end
            PH_B0_5: begin phase_next = PH_B0_4; sda_release_next = frame_data[20]; end
            PH_B0_4: begin phase_next = PH_B0_3; sda_release_next = frame_data[19]; end
            PH_B0_3: begin phase_next = PH_B0_2; sda_release_next = frame_data[18]; end
            PH_B0_2: begin phase_next = PH_B0_1; sda_release_next = frame_data[17]; end
            PH_B0_1: begin phase_next = PH_B0_0; sda_release_next = frame_data[16]; end
            PH_B0_0: begin phase_next = PH_ACK0; sda_release_next = 1'b1;           end

            PH_ACK0: begin
                phase_next       = PH_B1_7;
                ack_bits_next[0] = I2C_SDAT;
                sda_release_next = frame_data[15];
            end

            PH_B1_7: begin phase_next = PH_B1_6; sda_release_next = frame_data[14]; end
            PH_B1_6: begin phase_next = PH_B1_5; sda_release_next = frame_data[13]; end
            PH_B1_5: begin phase_next = PH_B1_4; sda_release_next = frame_data[12]; end
            PH_B1_4: begin phase_next = PH_B1_3; sda_release_next = frame_data[11]; end
            PH_B1_3: begin phase_next = PH_B1_2; sda_release_next = frame_data[10]; end
            PH_B1_2: begin phase_next = PH_B1_1; sda_release_next = frame_data[9];  end
            PH_B1_1: begin phase_next = PH_B1_0; sda_release_next = frame_data[8];  end
            PH_B1_0: begin phase_next = PH_ACK1; sda_release_next = 1'b1;           end

            PH_ACK1: begin
                phase_next       = PH_B2_7;
                ack_bits_next[1] = I2C_SDAT;
                sda_release_next = frame_data[7];
            end

            PH_B2_7: begin phase_next = PH_B2_6; sda_release_next = frame_data[6]; end
            PH_B2_6: begin phase_next = PH_B2_5; sda_release_next = frame_data[5]; end
            PH_B2_5: begin phase_next = PH_B2_4; sda_release_next = frame_data[4]; end
            PH_B2_4: begin phase_next = PH_B2_3; sda_release_next = frame_data[3]; end
            PH_B2_3: begin phase_next = PH_B2_2; sda_release_next = frame_data[2]; end
            PH_B2_2: begin phase_next = PH_B2_1; sda_release_next = frame_data[1]; end
            PH_B2_1: begin phase_next = PH_B2_0; sda_release_next = frame_data[0]; end
            PH_B2_0: begin phase_next = PH_ACK2; sda_release_next = 1'b1;          end

            PH_ACK2: begin
                phase_next       = PH_STOP_LOW;
                ack_bits_next[2] = I2C_SDAT;
                sda_release_next = 1'b0;
            end

            PH_STOP_LOW: begin
                phase_next       = PH_STOP_HIGH;
                sda_release_next = 1'b0;
            end

            PH_STOP_HIGH: begin
                phase_next       = PH_DONE;
                sda_release_next = 1'b1;
            end

            PH_DONE: begin
                end_next         = 1'b1;
                sda_release_next = 1'b1;
                if (!GO)
                    phase_next = PH_IDLE;
            end

            default: begin
                phase_next       = PH_IDLE;
                sda_release_next = 1'b1;
                end_next         = 1'b0;
            end
        endcase
    end

    always @(posedge CLK or negedge RESET) begin
        if (!RESET) begin
            phase       <= PH_IDLE;
            frame_data  <= 24'd0;
            ack_bits    <= 3'd0;
            sda_release <= 1'b1;
            END         <= 1'b0;
        end
        else if (CLK_EN) begin
            phase       <= phase_next;
            frame_data  <= frame_data_next;
            ack_bits    <= ack_bits_next;
            sda_release <= sda_release_next;
            END         <= end_next;
        end
    end

endmodule
