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