preloader

MASTER

`timescale 1ns / 1ps

// Main module declaration
module i2c_master(
    input wire clk,                // System clock
    input wire rst,                // Reset signal
    input wire [6:0] addr,         // 7-bit I2C slave address
    input wire [7:0] data_in,      // Data to send to slave in write mode
    input wire enable,             // Start signal for I2C communication
    input wire rw,                 // Read/Write control (0 for write, 1 for read)
    output reg [7:0] data_out,     // Data received from slave in read mode
    output wire ready,             // Indicates when the master is ready for a new transaction
    inout i2c_sda,                 // I2C data line (SDA) - bidirectional
    inout wire i2c_scl             // I2C clock line (SCL) - bidirectional
);

    // Define states for I2C master FSM
    localparam IDLE = 0;
    localparam START = 1;
    localparam ADDRESS = 2;
    localparam READ_ACK = 3;
    localparam WRITE_DATA = 4;
    localparam WRITE_ACK = 5;
    localparam READ_DATA = 6;
    localparam READ_ACK2 = 7;
    localparam STOP = 8;

    localparam DIVIDE_BY = 4;      // Clock divider to generate I2C clock from system clock

    reg [7:0] state;               // Current state of the FSM
    reg [7:0] saved_addr;          // Stores the 7-bit address and RW bit for the current transaction
    reg [7:0] saved_data;          // Data to be sent in write transactions
    reg [7:0] counter;             // Bit counter for data/address transmission
    reg [7:0] counter2 = 0;        // Divider counter for generating i2c_clk
    reg write_enable;              // Controls whether the master drives SDA line
    reg sda_out;                   // Data to output on SDA line when write_enable is 1
    reg i2c_scl_enable = 0;        // Controls the state of the i2c_scl line (enabled or high)
    reg i2c_clk = 1;               // Internal I2C clock signal

    // Ready signal is high when the master is idle and not in reset
    assign ready = ((rst == 0) && (state == IDLE)) ? 1 : 0;

    // I2C SCL signal: High when i2c_scl_enable is low; otherwise, driven by i2c_clk
    assign i2c_scl = (i2c_scl_enable == 0) ? 1 : i2c_clk;

    // SDA line is driven by sda_out when write_enable is high; otherwise, it's in high-impedance
    assign i2c_sda = (write_enable == 1) ? sda_out : 'bz;

    // I2C clock divider: Divides system clock to generate i2c_clk
    always @(posedge clk) begin
        if (counter2 == (DIVIDE_BY / 2) - 1) begin
            i2c_clk <= ~i2c_clk;    // Toggle i2c_clk when half period is reached
            counter2 <= 0;          // Reset the divider counter
        end else begin
            counter2 <= counter2 + 1; // Increment the divider counter
        end
    end

    // Enable/disable I2C clock based on current state
    always @(negedge i2c_clk or posedge rst) begin
        if (rst == 1) begin
            i2c_scl_enable <= 0;    // Disable SCL on reset
        end else begin
            if ((state == IDLE) || (state == START) || (state == STOP)) begin
                i2c_scl_enable <= 0; // SCL is disabled in IDLE, START, and STOP states
            end else begin
                i2c_scl_enable <= 1; // Enable SCL in other states
            end
        end
    end

    // State machine for controlling the I2C master operation
    always @(posedge i2c_clk or posedge rst) begin
        if (rst == 1) begin
            state <= IDLE;          // Reset state to IDLE on reset
        end else begin
            case (state)
                
                IDLE: begin
                    if (enable) begin
                        state <= START;  // Start I2C transaction when enable is high
                        saved_addr <= {addr, rw};  // Save the 7-bit address and RW bit
                        saved_data <= data_in;     // Save the data to be sent (in write mode)
                    end
                end

                START: begin
                    counter <= 7;          // Initialize bit counter to 7 for 8-bit transmission
                    state <= ADDRESS;      // Move to ADDRESS state
                end

                ADDRESS: begin
                    if (counter == 0) begin 
                        state <= READ_ACK;  // Move to ACK check after sending address and RW bit
                    end else begin
                        counter <= counter - 1;  // Transmit address bits, count down
                    end
                end

                READ_ACK: begin
                    if (i2c_sda == 0) begin  // ACK received (SDA pulled low by slave)
                        counter <= 7;       // Reset bit counter
                        if (saved_addr[0] == 0) state <= WRITE_DATA; // If RW=0, go to write mode
                        else state <= READ_DATA;                     // If RW=1, go to read mode
                    end else begin
                        state <= STOP;      // NACK received, move to STOP state
                    end
                end

                WRITE_DATA: begin
                    if (counter == 0) begin
                        state <= READ_ACK2; // Move to second ACK check after data transmission
                    end else begin
                        counter <= counter - 1; // Transmit data bits, count down
                    end
                end

                READ_ACK2: begin
                    if ((i2c_sda == 0) && (enable == 1)) state <= IDLE; // Return to IDLE on ACK
                    else state <= STOP;  // If NACK received or enable low, go to STOP
                end

                READ_DATA: begin
                    data_out[counter] <= i2c_sda;  // Capture data bit from SDA line
                    if (counter == 0) state <= WRITE_ACK; // After last bit, go to WRITE_ACK
                    else counter <= counter - 1; // Count down for each bit received
                end

                WRITE_ACK: begin
                    state <= STOP;  // Go to STOP after sending ACK
                end

                STOP: begin
                    state <= IDLE;  // Go back to IDLE after STOP condition
                end
            endcase
        end
    end

    // SDA output logic based on the current state
    always @(negedge i2c_clk or posedge rst) begin
        if (rst == 1) begin
            write_enable <= 1;       // Drive SDA high on reset
            sda_out <= 1;
        end else begin
            case (state)
                
                START: begin
                    write_enable <= 1;  // Enable SDA for start condition
                    sda_out <= 0;       // Pull SDA low for start condition
                end
                
                ADDRESS: begin
                    sda_out <= saved_addr[counter]; // Send each bit of the address and RW bit
                end
                
                READ_ACK: begin
                    write_enable <= 0;  // Release SDA to allow slave to drive ACK/NACK
                end
                
                WRITE_DATA: begin 
                    write_enable <= 1;  // Enable SDA for data transmission
                    sda_out <= saved_data[counter]; // Output each bit of data to SDA
                end
                
                WRITE_ACK: begin
                    write_enable <= 1;  // Enable SDA for ACK transmission
                    sda_out <= 0;       // Send ACK by pulling SDA low
                end
                
                READ_DATA: begin
                    write_enable <= 0;  // Release SDA to read data from slave
                end
                
                STOP: begin
                    write_enable <= 1;  // Enable SDA for stop condition
                    sda_out <= 1;       // Release SDA to indicate stop
                end
            endcase
        end
    end

endmodule
Explanation –>
SLAVE
module i2c_slave(
    input [6:0] addr_in,   // Slave address to respond to (dynamic address input)
    inout sda,             // I2C data line (SDA) - bidirectional
    inout scl              // I2C clock line (SCL)
);

    // Define states for the I2C slave FSM
    localparam READ_ADDR = 0;   // State for reading the address from the master
    localparam SEND_ACK = 1;    // State for sending ACK after receiving a matching address
    localparam READ_DATA = 2;   // State for reading data from the master
    localparam WRITE_DATA = 3;  // State for sending data to the master
    localparam SEND_ACK2 = 4;   // State for sending ACK after receiving data from the master

    reg [7:0] addr;             // Register to store the address received from the master
    reg [7:0] counter;          // Bit counter for data/address transmission
    reg [7:0] state = 0;        // Current state of the FSM
    reg [7:0] data_in = 0;      // Register to store data received from the master
    reg [7:0] data_out = 8'b11001100;  // Data to be sent to the master in read mode
    reg sda_out = 0;            // Data to drive onto SDA when write_enable is high
    reg sda_in = 0;             // Register to capture SDA input data
    reg start = 0;              // Flag to indicate the start condition (SDA goes low while SCL is high)
    reg write_enable = 0;       // Controls whether the slave drives the SDA line

    // Tri-state SDA line: driven by sda_out when write_enable is high, otherwise high-impedance
    assign sda = (write_enable == 1) ? sda_out : 'bz;

    // Detect start condition on SDA falling edge when SCL is high
    always @(negedge sda) begin
        if ((start == 0) && (scl == 1)) begin
            start <= 1;           // Set start flag
            counter <= 7;         // Initialize counter to read 8 bits (address or data)
        end
    end

    // Detect stop condition on SDA rising edge when SCL is high
    always @(posedge sda) begin
        if ((start == 1) && (scl == 1)) begin
            state <= READ_ADDR;   // Go to READ_ADDR state to read the address from master
            start <= 0;           // Clear start flag
            write_enable <= 0;    // Release SDA line
        end
    end

    // State machine for I2C slave behavior, triggered on rising edge of SCL
    always @(posedge scl) begin
        if (start == 1) begin     // Only proceed if start condition was detected
            case(state)
                
                READ_ADDR: begin
                    addr[counter] <= sda;      // Capture address bit from SDA
                    if(counter == 0) begin
                        state <= SEND_ACK;     // Move to SEND_ACK after receiving full address
                    end else begin
                        counter <= counter - 1; // Count down to receive 8 bits
                    end
                end
                
                SEND_ACK: begin
                    // Check if received address matches slave address (addr_in)
                    if(addr[7:1] == addr_in) begin
                        counter <= 7;          // Reset bit counter for next data frame
                        // Determine next state based on R/W bit (addr[0])
                        if(addr[0] == 0) begin 
                            state <= READ_DATA; // If R/W=0, master wants to write, go to READ_DATA
                        end else begin
                            state <= WRITE_DATA; // If R/W=1, master wants to read, go to WRITE_DATA
                        end
                    end else begin
                        state <= READ_ADDR;    // Address mismatch, go back to READ_ADDR
                    end
                end
                
                READ_DATA: begin
                    data_in[counter] <= sda;   // Capture data bit from SDA
                    if(counter == 0) begin
                        state <= SEND_ACK2;    // Move to SEND_ACK2 after receiving full byte
                    end else begin
                        counter <= counter - 1; // Count down to receive 8 bits
                    end
                end
                
                SEND_ACK2: begin
                    state <= READ_ADDR;        // Go back to READ_ADDR to listen for next address
                end
                
                WRITE_DATA: begin
                    // Transmit data_out to master one bit at a time
                    if(counter == 0) begin
                        state <= READ_ADDR;    // After last bit, go back to READ_ADDR
                    end else begin
                        counter <= counter - 1; // Count down for each bit sent
                    end
                end
                
            endcase
        end
    end

    // Control SDA output behavior on falling edge of SCL, depending on the state
    always @(negedge scl) begin
        case(state)
            
            READ_ADDR: begin
                write_enable <= 0;           // Release SDA while reading address
            end
            
            SEND_ACK: begin
                sda_out <= (addr[7:1] == addr_in) ? 0 : 1; // Send ACK (low) if address matches, else NACK (high)
                write_enable <= 1;           // Enable SDA to drive ACK/NACK
            end
            
            READ_DATA: begin
                write_enable <= 0;           // Release SDA while reading data
            end
            
            WRITE_DATA: begin
                sda_out <= data_out[counter]; // Send each bit of data_out on SDA
                write_enable <= 1;           // Enable SDA to drive data
            end
            
            SEND_ACK2: begin
                sda_out <= 0;                // Send ACK (low) after receiving data
                write_enable <= 1;           // Enable SDA to drive ACK
            end
        endcase
    end
endmodule
Explanation