在实现I2c PHY层设计后,可以实现4种PHY层的操作: 1)START + ADDR + WR/RD +ACK/NACK;2)WR_DATA + ACK/NACK;3)RD_DATA + ACK/NACK;4)STOP。之后就需要上层模块将PHY层组装起啦,根据用户指令,cmd模块分别发送这4种操作,完成I2c 协议。cmd 层设计结构如下:
1 cmd 层结构设计

图1
如图1所示, 在phy层基础上设计iic_cmd 层, 这层主要是用来将用户发来的数据解析为phy层所识别的4种操作,完成相应的i2c 总线协议。iic_cmd 层需要和上层应用进行数据交互(握手),同时将上层的数据翻译为phy层操作命令。 这里就需要定义和上层的接口协议了。

图2
如图2 所示, iic_cmd层和上层应用使用一个接口协议,用来确定上层数据的格式。分别为写I2C 数据格式和分别为读I2C 数据格式。
- 写I2C 数据格式:总包长(1byte) + 完成I2c协议后等待时间(ms 单位, 1byte) + 设备地址 + 写 (1 byte) + 数据 。。。。
- 读I2C 数据格式:总包长(1byte) + 完成I2c协议后等待时间(ms 单位, 1byte) + 设备地址 + 读 (1 byte) + 从设备种读取多收字节数据(1byte) + 设备存储地址(可选 ,1-2 bytes)
packet length : 数据包第1字节,标识当前包的总长度。
delay time : 数据包第2字节,表示当前I2c 操作后, 等待多少ms,再进行下次操作。
slave addr + op: 数据包第3字节,标识当前操作的外设地址,同时包括读写信息, OP = 0 ,表示写; OP = 1, 表示 读。
data/reg:写数据包第4字节,可以是设备存贮地址或者是写数据。
read data length:读数据包第4字节,表示准备从设备种读取多少个字节。 如果为0, 表示当前读操作无效。
data:写数据包第5 – n字节,表示有多少数据需要写入设备中。 写入数据总量可以通过 packet length – 3 计算出来。
reg0,reg1:写数据包第5 – 6字节,可选项,如果有,代表从设备的哪个存储地址开始读取数据,如果没有,表示从当前地址读取设备数据。
iic_cmd 层就是接收到以上包协议,根据协议,分析出控制器要对设备进行那些操作。
2 iic_cmd 模块解析
iic_cmd 模块主要是通过一个状态机解析数据包格式,得到相关的iic phy 所需要的信息,完成对phy的相关操作,最后在根据协议,是否需要延迟,延迟多长时间等。模块与上层应用的数据接口如下:
// app interface input [7:0] pkg_din, input pkg_din_vld, output reg pkg_din_ack = 0, output reg pkg_end = 0, output reg [7:0] pkg_dout = 0, output reg pkg_dout_vld = 0,
上层应用使用pkg_din_vld 发送完整数据包, pkg_din_vld 上升沿和pkg_din 同时由上层应用发出。
iic_cmd 得到pkg_din_vld 信号,抓取pkg_din 信息(数据包第1字节),返回

图3
pkg_din_vld: 数据输入包络线, 从第一个数据输入开始,知道cmd 层返回pkg_end 信号后,结束。
pkg_din: 应用层发给cmd层的数据。
pkg_din_ack:每当cmd层得到一个数据后, 就产生一个ack信号,通知上层应用程序,可以发送下一个数据了。
pkg_end:当前包结束标志,cmd层发出,通知上层应用程序,当前包处理结束。
pkg_dout: cmd模块输出,从phy层得到的数据被发送到应用层。
pkg_dout_vld: 指示pkg_dout 数据有效,只有一个时钟宽度。
状态机接口如下:

图4
黄色部分: 读取应用层数据,进行写操作
灰色部分:读操作,读取应用层数据, 确定读取设备中的memory 地址
绿色部分:读操作,读取设备中的数据。
iic_cmd代码部分:
`timescale 1ns / 1ps
module iic_cmd #
(
parameter CLK_F = 100_000_000
)
(
input sys_clk,
// phy interface
output reg [1:0] op_cmd,
output reg op_cmd_en,
input op_cmd_done,
output reg master_ack,
input dev_ack,
output reg [7:0] data_in,
input [7:0] data_out,
// app interface
input [7:0] pkg_din,
input pkg_din_vld,
output reg pkg_din_ack = 0,
output reg pkg_end = 0,
output reg [7:0] pkg_dout = 0,
output reg pkg_dout_vld = 0,
output reg i2c_error = 0,
input rst
);
localparam I2C_OP_START = 2'b00;
localparam I2C_OP_WR_DATA = 2'b01;
localparam I2C_OP_RD_DATA = 2'b10;
localparam I2C_OP_STOP = 2'b11;
//==============================================
localparam MS_CNT = CLK_F/1000;
reg ms_flag = 0;
reg [23:0] ms_cnt = 0;
always @ (posedge sys_clk)
if(pkg_end)
begin
ms_cnt <= 0;
ms_flag <= 0;
end
else
begin
ms_flag <= 0;
if(ms_cnt == MS_CNT - 1)
begin
ms_flag <= 1;
ms_cnt <= 0;
end
else ms_cnt <= ms_cnt + 1;
end
reg [7:0] reg0 = 0;
reg [7:0] reg1 = 0;
reg [7:0] pkg_len = 0;
reg [7:0] read_len = 0;
reg [7:0] pkg_delay = 0;
reg [6:0] dev_addr = 0;
reg [31:0] cnt;
reg [4:0] st = 0;
//======================
always@(posedge sys_clk or posedge rst)
if(rst)
begin
cnt <= 0;
op_cmd <= 0;
op_cmd_en <= 0;
master_ack <= 0;
pkg_din_ack <= 1'b0;
pkg_dout_vld <= 1'b0;
i2c_error <= 0;
st <= 0;
end
else case(st)
0:
begin
op_cmd <= 0;
op_cmd_en <= 1'b0;
i2c_error <= 0;
cnt <= 0;
pkg_dout_vld <= 1'b0;
pkg_din_ack <= 1'b0;
pkg_end <= 0;
if(pkg_din_vld) // waiting for app start
begin
pkg_len <= pkg_din; // get package length from app layer
pkg_din_ack <= 1;
st <= 1;
end
end
1:
begin
pkg_din_ack <= 0;
if(pkg_len < 4 )
begin
pkg_end <= 1;
st <= 21;
end
else st <= 2;
end
2:
begin
pkg_delay <= pkg_din; // get delay time after the transaction finished
pkg_din_ack <= 1;
st <= 3;
end
3:
begin
pkg_din_ack <= 0;
st <= 4;
end
4:
begin
cnt <= 3;
if(pkg_din[0]) // read
begin
dev_addr <= pkg_din[7:1]; // get device address
pkg_din_ack <= 1;
st <= 6;
end
else //write
begin
op_cmd_en <= 1'b1;
op_cmd <= I2C_OP_START;
data_in <= pkg_din; // device_addr + wr/rd
pkg_din_ack <= 1;
st <= 5;
end
end
//========================= write
5: // write data
begin
op_cmd_en <= 1'b0;
pkg_din_ack <= 1'b0;
if(op_cmd_done)
begin
if(!dev_ack)
begin
if(cnt == pkg_len) // jump to stop if the data had been sent
st <= 19;
else
begin
pkg_din_ack <= 1'b1;
op_cmd_en <= 1'b1;
op_cmd <= I2C_OP_WR_DATA;
data_in <= pkg_din; // send data to device
cnt <= cnt + 1;
end
end
else
begin
i2c_error <= 1'b1;
st <= 19; // jump to stop
end
end
end
//=========================read
6:
begin
pkg_din_ack <= 0;
st <= 7;
end
7:
begin
cnt <= 0;
read_len <= pkg_din; // get read device length
pkg_din_ack <= 1;
if(pkg_din == 0) // read device length = 0 means no read operation
begin
pkg_end <= 1;
st <= 21;
end
else st <= 8;
end
8:
begin
pkg_din_ack <= 0;
if(pkg_len == 4) //restart, random read device
st <= 16;
else st <= 9;
end
9:
begin
reg0 <= pkg_din; // device memory address 1
pkg_din_ack <= 1;
if(pkg_len == 5)
st <= 12;
else
st <= 10;
end
10:
begin
pkg_din_ack <= 0;
st <= 11;
end
11:
begin
reg1 <= pkg_din; // device memory address 2
pkg_din_ack <= 1;
st <= 12;
end
12: // start read transaction from memory address
begin
pkg_din_ack <= 0;
op_cmd_en <= 1'b1;
op_cmd <= I2C_OP_START;
data_in <= {dev_addr,1'b0};
st <= 13;
end
13:
begin
op_cmd_en <= 1'b0;
if(op_cmd_done)
begin
if(!dev_ack)
begin
op_cmd_en <= 1'b1;
op_cmd <= I2C_OP_WR_DATA;
data_in <= reg0;
if(pkg_len == 6)
st <= 14;
else
st <= 15;
end
else
begin
i2c_error <= 1'b1;
st <= 19; // 从设备ack/nack 错误跳转状态
end
end
end
14:
begin
op_cmd_en <= 1'b0;
if(op_cmd_done)
begin
if(!dev_ack)
begin
op_cmd_en <= 1'b1;
op_cmd <= I2C_OP_WR_DATA;
data_in <= reg1;
st <= 15;
end
else
begin
i2c_error <= 1'b1;
st <= 19; // 从设备ack/nack 错误跳转状态
end
end
end
15:
begin
op_cmd_en <= 1'b0;
if(op_cmd_done)
begin
if(!dev_ack)
st <= 16;
else
begin
i2c_error <= 1'b1;
st <= 19; // 从设备ack/nack 错误跳转状态
end
end
end
16: //restart
begin
master_ack <= 1'b0;
cnt <= 0;
op_cmd_en <= 1'b1;
op_cmd <= I2C_OP_START;
data_in <= {dev_addr,1'b1};
st <= 17;
end
17: // read data
begin
op_cmd_en <= 1'b0;
pkg_dout_vld <= 1'b0;
if(op_cmd_done)
begin
op_cmd_en <= 1'b1;
op_cmd <= I2C_OP_RD_DATA;
cnt <= cnt + 1;
if(cnt != 0) // I2C_OP_RD_DATA respond
begin
pkg_dout <= data_out;
pkg_dout_vld <= 1'b1;
end
if(cnt == (read_len - 1) ) // the last read transaction need a NACK
begin
cnt <= 0;
master_ack <= 1'b1;
st <= 18;
end
end
end
18:
begin
op_cmd_en <= 1'b0;
pkg_dout_vld <= 1'b0;
if(op_cmd_done)
begin
pkg_dout <= data_out;
pkg_dout_vld <= 1'b1;
st <= 19;
end
end
//=========== stop
19:
begin
pkg_dout_vld <= 1'b0;
op_cmd_en <= 1'b1;
op_cmd <= I2C_OP_STOP;
i2c_error <= 1'b0;
st <= 20;
end
20:
begin
op_cmd_en <= 1'b0;
cnt <= 0;
if(op_cmd_done)
begin
pkg_end <= 1;
st <= 21;
end
end
21:
begin
pkg_end <= 0;
if(cnt == pkg_delay)
st <= 0;
else if(ms_flag) cnt <= cnt + 1;
end
default:st <= 0;
endcase
endmodule
3 仿真
图5
如图5所示,当应用层发送 0c 05 a0 00 00 01 02 03 04 05 06 07 (写设备), 当cmd层接收到应用层的数据后,cmd层发送pkg_din_ack 给应用层;当整包结束后,cmd层发送pkg_end 给应用层,应用层撤销pkg_din_vld。
图6
当应用层发送:04 01 a1 02 (随机读设备),cmd层接收到应用层的数据后,读设备,读出的数据通过pkg_dout, pkg_dout_vld 返回应用层。