ec82764bef
RTL (GS rasterizer, EE core stub, platform bridge, LPDDR4B path), sim regression (272 TBs), docs, and tooling. Copyrighted PS2 content (BIOS, game code, GS dumps, and all dump-derived textures/traces) is excluded via .gitignore and stays local. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
235 lines
9.6 KiB
Verilog
235 lines
9.6 KiB
Verilog
// ============================================================================
|
|
// 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
|