0
0
VerilogHow-ToIntermediate · 4 min read

Verilog Code for I2C Master: Syntax and Example

An I2C master in Verilog controls the clock and data lines to communicate with I2C devices. The code typically includes generating SCL clock, managing SDA data line, and implementing start, stop, read, and write sequences. Below is a simple example showing these key parts in Verilog.
📐

Syntax

The basic syntax for an I2C master in Verilog includes defining inputs and outputs for clock (clk), reset (rst), serial clock line (scl), and serial data line (sda). You implement a state machine to handle start, stop, read, and write operations. The sda line is bidirectional and usually modeled as an inout port with tri-state control.

  • clk: System clock input.
  • rst: Reset signal.
  • scl: I2C clock line output.
  • sda: I2C data line, bidirectional.
verilog
module i2c_master(
    input wire clk,
    input wire rst,
    output reg scl,
    inout wire sda
);

// Internal signals
reg sda_out;
reg sda_oe; // Output enable for sda

assign sda = sda_oe ? sda_out : 1'bz; // Tri-state control

// State machine states
localparam IDLE = 0, START = 1, WRITE = 2, STOP = 3;
reg [1:0] state;

always @(posedge clk or posedge rst) begin
    if (rst) begin
        state <= IDLE;
        scl <= 1;
        sda_out <= 1;
        sda_oe <= 0;
    end else begin
        case(state)
            IDLE: begin
                scl <= 1;
                sda_out <= 1;
                sda_oe <= 0;
                // Next state logic here
            end
            // Additional states to be implemented
        endcase
    end
end

endmodule
💻

Example

This example shows a simple I2C master module that generates a start condition, writes a byte, and then generates a stop condition. It uses a state machine to control the scl and sda lines.

verilog
module i2c_master_example(
    input wire clk,
    input wire rst,
    output reg scl,
    inout wire sda
);

reg sda_out;
reg sda_oe;
assign sda = sda_oe ? sda_out : 1'bz;

localparam IDLE=0, START=1, WRITE=2, STOP=3;
reg [1:0] state = IDLE;
reg [3:0] bit_cnt = 0;
reg [7:0] data = 8'hA5; // Example data byte

always @(posedge clk or posedge rst) begin
    if (rst) begin
        state <= IDLE;
        scl <= 1;
        sda_out <= 1;
        sda_oe <= 0;
        bit_cnt <= 0;
    end else begin
        case(state)
            IDLE: begin
                scl <= 1;
                sda_out <= 1;
                sda_oe <= 0;
                state <= START;
            end
            START: begin
                sda_out <= 0; // Start condition: SDA goes low while SCL is high
                sda_oe <= 1;
                scl <= 1;
                state <= WRITE;
                bit_cnt <= 7;
            end
            WRITE: begin
                scl <= 0;
                sda_out <= data[bit_cnt];
                sda_oe <= 1;
                scl <= 1; // Clock high to latch bit
                if (bit_cnt == 0) begin
                    state <= STOP;
                end else begin
                    bit_cnt <= bit_cnt - 1;
                end
            end
            STOP: begin
                scl <= 1;
                sda_out <= 0;
                sda_oe <= 1;
                sda_out <= 1; // Stop condition: SDA goes high while SCL is high
                state <= IDLE;
            end
        endcase
    end
end

endmodule
⚠️

Common Pitfalls

Common mistakes when writing an I2C master in Verilog include:

  • Not properly handling the bidirectional sda line, causing bus contention.
  • Incorrect timing of scl and sda signals violating I2C protocol rules.
  • Forgetting to generate start and stop conditions correctly.
  • Not waiting for acknowledgment bits from the slave device.

Always use tri-state control for sda and carefully sequence the clock and data lines.

verilog
/* Wrong way: Driving sda as output without tri-state */
module wrong_i2c_master(
    input wire clk,
    output reg sda
);

// This will cause bus contention if slave tries to drive sda
always @(posedge clk) begin
    sda <= 1; // Always driving high, no tri-state
end

endmodule

/* Right way: Use tri-state for sda */
module right_i2c_master(
    input wire clk,
    inout wire sda
);

reg sda_out;
reg sda_oe;
assign sda = sda_oe ? sda_out : 1'bz;

always @(posedge clk) begin
    sda_out <= 1;
    sda_oe <= 1; // Enable output only when driving
end

endmodule
📊

Quick Reference

Key points for writing an I2C master in Verilog:

  • Start condition: SDA goes low while SCL is high.
  • Stop condition: SDA goes high while SCL is high.
  • Data bits: Change SDA when SCL is low, sample SDA when SCL is high.
  • Acknowledge bit: Master releases SDA, slave pulls low to acknowledge.
  • Use tri-state buffer: Model SDA as inout with output enable.

Key Takeaways

Use tri-state control for the bidirectional SDA line to avoid bus conflicts.
Generate proper start and stop conditions by controlling SDA while SCL is high.
Change SDA data only when SCL is low and sample data when SCL is high.
Implement a state machine to sequence I2C operations cleanly.
Always wait for acknowledgment bits from the slave device after sending data.