协议本身就不做介绍了,只介绍一下编写思路,首先这个iic模块具有两个功能,对一个地址进行一个字节读,对一个地址进行一个字节写。由于iic的地址包含设备地址(7位)和寄存器地址(8或16位),因此模块对外提供参数来让用户选择采用哪种位宽的寄存器地址。即REGADDR_2BYTES=1时候采用16位,0时采用8位,而SCLK_FREQ则表示iic的时钟多少频率,而输入的i_clk是50mhz的时钟,这里写了sim是为了仿真用,可以删除


其中本模块核心的寄存器变量如上图,其中状态有如下这几个,除了IDLE,START,STOP,每个状态都会读写完一个字节后进入下一个状态

当start信号拉高,模块开始工作,此时bytecnt开始计数,这个变量和当前读写的字节有关,同时采用sclcnt开始计数,这个计数器是用来给iic做分频时钟用的,本设计采用4分频,其逻辑如下:检验sclcnt的最后两位,那么最后两位有4种数值在反复循环00,01,10,11,那么在11和01处进行SCL时钟的翻转来实现四分频,同时iic协议要求scl低电平时候变化数据,因此在00也就是图上绿色的那一时刻让bytecnt++,并根据当前bytecnt来更新sda信号线。对应代码如下:
bytecnt++:

设备地址信号更新:

寄存器地址信号更新:

要写的字节数据更新和要读的字节数据更新,同时三态线io_sda逻辑如下,后面会说:

从上面时序图可以看到还有一个scl_vld信号,这个是说明只有该信号高电平时候scl才能执行,是为了防止在IDLE,START,STOP这几个状态的时候scl开始变化而设立的,也就是说只有该信号高电平,数据传输才真正开始。
然后ack信号会在bytecnt=0的时候读取,并在bytecnt==0且sclcnt=11的时候进行状态机跳转


由于为了适应多个时钟域,因此额外定义了CLK_DIV信号让其作为四分频标志位,当其拉高时scl_cnt++


总体代码如下:
`timescale 1ns / 1ps
// 采用字节写,随机地址读
// 字节写: 起始位|器件地址|读ACK|寄存器地址|读ACK|数据|读ACK|停止位
// 随机地址读: 起始位|器件地址|读ACK|寄存器地址|读ACK|停止位|起始位|器件地址|读ACK|数据|写ACK|停止位
`define sim
module I2C_Master#(
parameter SCLK_FREQ = 400_000,
parameter [0:0] REGADDR_2BYTES = 0
)(
input i_clk ,
input i_rst ,
input i_start ,
input i_rw_flag ,
input [6:0] i_dev_addr ,
input [15:0] i_reg_addr ,
input [7:0] i_wr_data ,
inout io_sda ,
`ifdef sim
output o_rden ,
`endif
output reg o_scl ,
output reg [7:0] o_rd_data ,
output reg o_vld ,
output reg o_err ,
output o_rdy
);
localparam S_IDLE = 8'b00000001,
S_START = 8'b00000010,
S_DADDR = 8'b00000100,
S_RADDR_16 = 8'b00001000,
S_RADDR_8 = 8'b00010000,
S_WR_DATA = 8'b00100000,
S_RD_DATA = 8'b01000000,
S_STOP = 8'b10000000;
reg [7:0] r_cstate ;
reg [7:0] r_nstate ;
localparam CLK_DIV = 50_000_000 / SCLK_FREQ / 4;
//时钟计数器
reg [15:0] r_clk_cnt ;
wire w_div_flag ;
assign w_div_flag = r_clk_cnt >= CLK_DIV; //分频标志
always@(posedge i_clk or posedge i_rst) begin
if(i_rst)
r_clk_cnt <= 'd0;
else if(r_cstate == S_IDLE)
r_clk_cnt <= 'd0;
else if(r_clk_cnt < CLK_DIV)
r_clk_cnt <= r_clk_cnt + 1;
else
r_clk_cnt <= 'd0;
end
reg [6:0] r_dev_addr ;
reg [7:0] r_reg_addr16;
reg [7:0] r_reg_addr8 ;
reg [7:0] r_wr_data ;
reg r_rw_flag ;
assign o_rdy = r_cstate == S_IDLE;
always@(posedge i_clk or posedge i_rst) begin
if(i_rst) begin
r_dev_addr <= 'd0;
r_reg_addr16<= 'd0;
r_reg_addr8 <= 'd0;
r_wr_data <= 'd0;
r_rw_flag <= 'b0;
end
else if(r_cstate == S_IDLE & i_start) begin
r_dev_addr <= i_dev_addr ;
r_reg_addr16<= i_reg_addr[15:8] ;
r_reg_addr8 <= i_reg_addr[7:0] ;
r_wr_data <= i_wr_data ;
r_rw_flag <= i_rw_flag ;
end
else begin
r_dev_addr <= r_dev_addr ;
r_reg_addr16<= r_reg_addr16 ;
r_reg_addr8 <= r_reg_addr8 ;
r_wr_data <= r_wr_data ;
r_rw_flag <= r_rw_flag ;
end
end
//时钟四分频计数器
reg [15:0] r_scl_cnt ;
reg r_scl_vld ;
always@(posedge i_clk or posedge i_rst) begin
if(i_rst)
r_scl_cnt <= 'd0;
else if(r_cstate == S_IDLE)
r_scl_cnt <= 'd0;
else if(r_cstate == S_STOP & (&r_scl_cnt[1:0]) & w_div_flag)//当计数到2'b11且状态为stop便清零
r_scl_cnt <= 'd0;
else if(w_div_flag)
r_scl_cnt <= r_scl_cnt + 1;//四分频一次加一
else
r_scl_cnt <= r_scl_cnt;
end
//iic时钟只有在scl_vld拉高期间才能变化
always@(posedge i_clk or posedge i_rst) begin
if(i_rst)
r_scl_vld <= 'b0;
else if(r_cstate == S_STOP & r_scl_cnt[1:0] >= 2)
r_scl_vld <= 'b0;
else if(r_cstate == S_START & r_scl_cnt[1:0] >= 2)
r_scl_vld <= 'b1;
else
r_scl_vld <= r_scl_vld;
end
always@(posedge i_clk or posedge i_rst) begin
if(i_rst)
o_scl <= 'b1;
else if(r_scl_vld & r_scl_cnt[0] & r_clk_cnt == 0)//iic时钟
o_scl <= ~o_scl;
else
o_scl <= o_scl;
end
reg r_dummy_wr ;
wire w_rw_bit ;
assign w_rw_bit = r_dummy_wr ^ r_rw_flag;
always@(posedge i_clk or posedge i_rst) begin
if(i_rst)
r_dummy_wr <= 'b0;
else if(r_cstate == S_IDLE & i_start)
r_dummy_wr <= i_rw_flag;
else if(r_cstate == S_STOP & (&r_scl_cnt[1:0]) & w_div_flag)
r_dummy_wr <= 'b0;
else
r_dummy_wr <= r_dummy_wr;
end
reg [4:0] r_byte_cnt ;//根据该计数器去变化sda
reg r_read_en ;
`ifdef sim
assign o_rden = r_read_en;
`endif
always@(posedge i_clk or posedge i_rst) begin
if(i_rst)
r_byte_cnt <= 'd0;
else if(r_scl_vld & r_cstate != S_STOP)
if(&r_scl_cnt[1:0] & w_div_flag)//当scl cnt后两位为2'b11 同时分频标志拉高时,计数器++
r_byte_cnt <= r_byte_cnt < 8 ? r_byte_cnt + 1 : 'd0;
else
r_byte_cnt <= r_byte_cnt;
else
r_byte_cnt <= 'd0;
end
always@(*) begin
if(i_rst)
r_read_en = 'b0;
else if(r_cstate == S_RD_DATA)
r_read_en = |r_byte_cnt;
else if(|(r_cstate & 8'b10000011))
r_read_en = 'b0;
else
r_read_en <= !r_byte_cnt;
end
reg r_daddr_bit ;
always@(posedge i_clk or posedge i_rst) begin
if(i_rst)
r_daddr_bit <= 'b0;
else if(r_cstate == S_DADDR)
case(r_byte_cnt)
1: r_daddr_bit <= r_dev_addr[6];
2: r_daddr_bit <= r_dev_addr[5];
3: r_daddr_bit <= r_dev_addr[4];
4: r_daddr_bit <= r_dev_addr[3];
5: r_daddr_bit <= r_dev_addr[2];
6: r_daddr_bit <= r_dev_addr[1];
7: r_daddr_bit <= r_dev_addr[0];
8: r_daddr_bit <= w_rw_bit;
default: r_daddr_bit <= 'b0;
endcase
else
r_daddr_bit <= 'b0;
end
reg r_raddr_bit ;
always@(posedge i_clk or posedge i_rst) begin
if(i_rst)
r_raddr_bit <= 'b0;
else if(r_cstate == S_RADDR_16)
case(r_byte_cnt)
1: r_raddr_bit <= r_reg_addr16[7];
2: r_raddr_bit <= r_reg_addr16[6];
3: r_raddr_bit <= r_reg_addr16[5];
4: r_raddr_bit <= r_reg_addr16[4];
5: r_raddr_bit <= r_reg_addr16[3];
6: r_raddr_bit <= r_reg_addr16[2];
7: r_raddr_bit <= r_reg_addr16[1];
8: r_raddr_bit <= r_reg_addr16[0];
default: r_raddr_bit <= 'b0;
endcase
else if(r_cstate == S_RADDR_8)
case(r_byte_cnt)
1: r_raddr_bit <= r_reg_addr8[7];
2: r_raddr_bit <= r_reg_addr8[6];
3: r_raddr_bit <= r_reg_addr8[5];
4: r_raddr_bit <= r_reg_addr8[4];
5: r_raddr_bit <= r_reg_addr8[3];
6: r_raddr_bit <= r_reg_addr8[2];
7: r_raddr_bit <= r_reg_addr8[1];
8: r_raddr_bit <= r_reg_addr8[0];
default: r_raddr_bit <= 'b0;
endcase
else
r_raddr_bit <= 'b0;
end
reg r_wrdata_bit ;
always@(posedge i_clk or posedge i_rst) begin
if(i_rst)
r_wrdata_bit <= 'b0;
else if(r_cstate == S_WR_DATA)
case(r_byte_cnt)
1: r_wrdata_bit <= r_wr_data[7];
2: r_wrdata_bit <= r_wr_data[6];
3: r_wrdata_bit <= r_wr_data[5];
4: r_wrdata_bit <= r_wr_data[4];
5: r_wrdata_bit <= r_wr_data[3];
6: r_wrdata_bit <= r_wr_data[2];
7: r_wrdata_bit <= r_wr_data[1];
8: r_wrdata_bit <= r_wr_data[0];
default: r_wrdata_bit <= 'b0;
endcase
else
r_wrdata_bit <= 'b0;
end
assign io_sda = ~r_read_en ? |{~r_scl_vld, r_daddr_bit, r_raddr_bit, r_wrdata_bit} : 1'bz;
always@(posedge i_clk or posedge i_rst) begin
if(i_rst)
o_rd_data <= 'd0;
else if(r_cstate == S_RD_DATA && r_scl_cnt[1:0] == 2'b01 && w_div_flag)
case(r_byte_cnt)
1: o_rd_data[7] <= io_sda;
2: o_rd_data[6] <= io_sda;
3: o_rd_data[5] <= io_sda;
4: o_rd_data[4] <= io_sda;
5: o_rd_data[3] <= io_sda;
6: o_rd_data[2] <= io_sda;
7: o_rd_data[1] <= io_sda;
8: o_rd_data[0] <= io_sda;
default: o_rd_data <= o_rd_data;
endcase
else
o_rd_data <= o_rd_data;
end
wire w_ack_flag ;
assign w_ack_flag = |(r_cstate & 8'b00111100) & r_byte_cnt == 'd0 & r_scl_cnt[1:0] == 2'b01 & w_div_flag;
always@(posedge i_clk or posedge i_rst) begin
if(i_rst)
o_err <= 'b0;
else if(r_cstate == S_IDLE)
o_err <= 'b0;
else if(w_ack_flag)
o_err <= io_sda | o_err;
else
o_err <= o_err;
end
reg [7:0] r_raddr_st ;
always@(posedge i_clk) begin
if(r_cstate == S_RADDR_16)
r_raddr_st <= S_RADDR_8;
else
r_raddr_st <= REGADDR_2BYTES ? S_RADDR_16 : S_RADDR_8;
end
wire w_state_chg ;
assign w_state_chg = (i_start & r_cstate == S_IDLE) | (&(r_scl_cnt[1:0]) && !r_byte_cnt && w_div_flag);
always@(posedge i_clk or posedge i_rst) begin
if(i_rst)
o_vld <= 'b0;
else if(r_cstate == S_STOP & r_nstate == S_IDLE)
o_vld <= 'b1;
else
o_vld <= 'b0;
end
always@(posedge i_clk or posedge i_rst) begin
if(i_rst)
r_cstate <= S_IDLE;
else
r_cstate <= r_nstate;
end
always@(*) begin
if(i_rst)
r_nstate = S_IDLE;
else
case(r_cstate)
S_IDLE: r_nstate = w_state_chg ? S_START : S_IDLE;
S_START: r_nstate = w_state_chg ? S_DADDR : S_START;
S_DADDR: r_nstate = w_state_chg ? w_rw_bit ? S_RD_DATA : r_raddr_st : S_DADDR;
S_RADDR_16: r_nstate = w_state_chg ? S_RADDR_8 : S_RADDR_16;
S_RADDR_8: r_nstate = w_state_chg ? r_dummy_wr ? S_STOP : S_WR_DATA : S_RADDR_8;
S_WR_DATA: r_nstate = w_state_chg ? S_STOP : S_WR_DATA;
S_RD_DATA: r_nstate = w_state_chg ? S_STOP : S_RD_DATA;
S_STOP: r_nstate = w_state_chg ? r_dummy_wr ? S_START : S_IDLE : S_STOP;
default: r_nstate = S_IDLE;
endcase
end
endmodule
tb文件如下:
`timescale 1ns / 1ps
//////////////////////////////////////////////////////////////////////////////////
// Company:
// Engineer:
//
// Create Date: 2025/05/22 11:15:31
// Design Name:
// Module Name: IIC_tb
// Project Name:
// Target Devices:
// Tool Versions:
// Description:
//
// Dependencies:
//
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
//
//////////////////////////////////////////////////////////////////////////////////
module IIC_tb();
localparam REGADDR_2BYTES = 0;
reg i_clk50m ;
reg i_rst50m ;
reg i_start ;
reg i_rw_flag ;
reg [6:0] i_dev_addr ;
reg [15:0] i_reg_addr ;
reg [7:0] i_wr_data ;
wire o_rden ;
wire io_sda ;
wire o_scl ;
wire [7:0] o_rd_data ;
wire o_vld ;
wire o_err ;
wire o_rdy ;
always#10 i_clk50m = ~i_clk50m;
reg [15:0] r_random;
always#20 r_random = $random()%16'hffff;
assign io_sda = o_rden ? r_random[0] : 1'bz;
initial begin
i_start = 0;
i_clk50m = 1;
i_rst50m = 1;
i_rw_flag = 0;
i_dev_addr = 7'b1010101;
i_reg_addr = {8'h00, 8'h34};
i_wr_data = 8'he3;
#100
i_rst50m = 0;
#10
i_start = 1;
#30
i_start = 0;
@(posedge o_vld)
#5000
i_rw_flag = 1;
i_dev_addr = 7'b1110001;
i_reg_addr = {8'h00,8'hab};
i_start = 1;
#30
i_start = 0;
end
I2C_Master#(
.REGADDR_2BYTES(REGADDR_2BYTES))
I2C_Master_U (
i_clk50m ,
i_rst50m ,
i_start ,
i_rw_flag ,
i_dev_addr ,
i_reg_addr ,
i_wr_data ,
io_sda ,
o_rden ,
o_scl ,
o_rd_data ,
o_vld ,
o_err ,
o_rdy
);
endmodule
讲解视频: