Verilog Code for I2C Slave: Syntax, Example, and Tips
Verilog listens to the SCL clock and SDA data lines, responding to its address and handling read/write operations. The code typically includes state machines to detect start/stop conditions, address matching, and data transfer. You can implement it by monitoring signals and controlling SDA as an open-drain line.Syntax
An I2C slave module in Verilog usually has inputs for SCL (clock), SDA (data line, bidirectional), and a device address. It outputs data when addressed and acknowledges communication. The module uses a state machine to track start/stop conditions, address recognition, and data read/write phases.
Key parts include:
- Start/Stop detection: Detect edges on
SDAwhileSCLis high. - Address matching: Compare incoming bits to the device address.
- Data transfer: Read or write bytes bit by bit synchronized with
SCL. - Acknowledge: Drive
SDAlow to acknowledge receipt.
module i2c_slave(
input wire scl,
inout wire sda,
input wire rst_n,
input wire [6:0] slave_addr,
output reg [7:0] data_out,
input wire [7:0] data_in,
output reg data_ready
);
// Internal signals
reg sda_out;
reg sda_oe; // Output enable for SDA (open-drain)
reg [3:0] bit_cnt;
reg [7:0] shift_reg;
reg [6:0] addr;
reg rw_flag; // 0 = write, 1 = read
reg start_detected;
reg stop_detected;
reg [1:0] state;
// SDA line control
assign sda = sda_oe ? 1'b0 : 1'bz;
// States
localparam IDLE = 2'b00;
localparam ADDR = 2'b01;
localparam DATA = 2'b10;
// Start and stop detection logic
reg sda_d, scl_d;
always @(posedge scl or negedge rst_n) begin
if (!rst_n) begin
sda_d <= 1'b1;
scl_d <= 1'b1;
start_detected <= 1'b0;
stop_detected <= 1'b0;
end else begin
sda_d <= sda;
scl_d <= scl;
// Start condition: SDA falls while SCL is high
start_detected <= (sda_d == 1'b1 && sda == 1'b0 && scl == 1'b1);
// Stop condition: SDA rises while SCL is high
stop_detected <= (sda_d == 1'b0 && sda == 1'b1 && scl == 1'b1);
end
end
// Main state machine
always @(negedge scl or negedge rst_n) begin
if (!rst_n) begin
state <= IDLE;
bit_cnt <= 4'd0;
sda_oe <= 1'b0;
data_ready <= 1'b0;
end else begin
if (start_detected) begin
state <= ADDR;
bit_cnt <= 4'd7;
data_ready <= 1'b0;
end else if (stop_detected) begin
state <= IDLE;
sda_oe <= 1'b0;
data_ready <= 1'b0;
end else begin
case (state)
IDLE: begin
sda_oe <= 1'b0;
end
ADDR: begin
shift_reg[bit_cnt] <= sda;
if (bit_cnt == 0) begin
addr <= shift_reg[7:1];
rw_flag <= shift_reg[0];
if (shift_reg[7:1] == slave_addr) begin
sda_oe <= 1'b1; // ACK
end else begin
sda_oe <= 1'b0; // No ACK
state <= IDLE;
end
bit_cnt <= 4'd7;
if (shift_reg[7:1] == slave_addr) state <= DATA;
end else begin
bit_cnt <= bit_cnt - 1;
end
end
DATA: begin
if (rw_flag == 0) begin
// Master writes data to slave
shift_reg[bit_cnt] <= sda;
if (bit_cnt == 0) begin
data_out <= shift_reg;
data_ready <= 1'b1;
sda_oe <= 1'b1; // ACK
bit_cnt <= 4'd7;
end else begin
bit_cnt <= bit_cnt - 1;
end
end else begin
// Slave sends data to master
sda_oe <= 1'b1;
sda_out <= data_in[bit_cnt];
if (bit_cnt == 0) begin
bit_cnt <= 4'd7;
sda_oe <= 1'b0; // Release SDA for ACK from master
end else begin
bit_cnt <= bit_cnt - 1;
end
end
end
endcase
end
end
end
endmoduleExample
This example shows a simple I2C slave that listens for its 7-bit address 0x42. It detects start and stop conditions, acknowledges the address, and receives one byte of data from the master. When data is received, it sets data_ready high and stores the byte in data_out.
module i2c_slave_example(
input wire scl,
inout wire sda,
input wire rst_n,
output reg [7:0] data_out,
output reg data_ready
);
wire [6:0] slave_addr = 7'h42;
reg [7:0] data_in = 8'h00; // Not used in this example
i2c_slave slave_inst(
.scl(scl),
.sda(sda),
.rst_n(rst_n),
.slave_addr(slave_addr),
.data_out(data_out),
.data_in(data_in),
.data_ready(data_ready)
);
endmoduleCommon Pitfalls
Common mistakes when coding an I2C slave in Verilog include:
- Not detecting start and stop conditions correctly, which causes the slave to miss communication.
- Forgetting that
SDAis open-drain and must be driven low or released (high impedance), never driven high directly. - Incorrect bit counting or shifting, leading to wrong address or data reception.
- Not handling the acknowledge bit properly, causing the master to timeout.
Always synchronize SCL and SDA signals properly and test with real I2C traffic.
/* Wrong way: Driving SDA high directly */ assign sda = sda_oe ? 1'b1 : 1'bz; // Incorrect for open-drain /* Right way: Open-drain style */ assign sda = sda_oe ? 1'b0 : 1'bz; // Correct: drive low or release line
Quick Reference
Tips for writing an I2C slave in Verilog:
- Use edge detection on
SDAandSCLto find start/stop conditions. - Implement a state machine to handle address and data phases.
- Drive
SDAlow to acknowledge; otherwise, release it (high impedance). - Keep bit counters to track byte reception and transmission.
- Test with an I2C master simulator or hardware to verify timing and behavior.