一、IIC协议简介
1.IIC简介
IIC即Inter-Integrated Circuit(集成电路总线),它是一种同步串行半双工通信总线,使用多主从架构,由飞利浦公司在1980年代设计出来的一种简单、双向、二线制总线标准。多用于主机和从机在数据量不大且传输距离短的场合下的主从通信。主机启动总线,并产生时钟用于传送数据,此时任何接收数据的器件均被认为是从机。
IIC总线由数据线SDA和时钟线SCL构成通信线路,既可用于发送数据,也可接收数据。在主控与被控IC之间可进行双向数据传送,数据的传输速率在标准模式下可达100kbit/s,在快速模式下可达400kbit/s,在高速模式下可达3.4Mbit/s,各种被控器件均并联在总线上,通过器件地址(SLAVE ADDR,具体可查器件手册)识别。
注意:
1.IIC总线通过上拉电阻接高电平。即空闲状态为高,输出低电平时总线拉低。
2.主机通过识别器件地址ID建立多地址通信机制。
3.从机与从机之间不能进行数据交换。注意:IIC上的设备既可以是主机也能是从机,这取决于我们具体设备所需要实现的功能。
4.IIC支持多主多从。但同一时刻,只有一个主机能发起传输。若多个主机发起输,由于总线仲裁机制,只能给一个主机授权。IIC总线只能由总机发起传输。
总线仲裁机制
(1)仲裁过程:当主设备A准备占用I2C时,需要在SCL为高时将SDA拉高,再拉低,满足一个启动条件。当主设备A将SDA拉高后,需检查SDA的电平:
如果此时SDA电平为高,说明主设备可以占用总线,然后主设备A会将SDA拉低,一次满足启动条件,开始传输。
如果此时SDA电平为低,说明总线已经被其他设备占用,主设备A会退出。
(2)为什么SDA为低,就是被其他设备占用了呢?
因为线与逻辑的存在。只有总线上有其他的设备将SDA置为0,线与后,SDA线的电平为0。主设备A检查SDA线的电平时,会发现为低电平。所以仲裁时,哪个设备更早地将SDA线拉低,谁就抢占了优先权。
2.IIC通信原理
2.1 IIC传输时序
(1)起始信号,SCL高电平期间,SDA拉低;
(2)数据信号,SCL高电平期间,从机采样数据;SCL低电平期间,主机发送改变数据。
(3)应答信号,IIC总线传输数据时,每传输1个字节后都必须有一个SCL的有效应答信号。(可强制在第8周期低电平期间将SDA强制拉高,以保证能检测到有效应答)。
应答信号有两种状态:应答(ACK)、非应答 (NACK) ,接收端在应答位期间输出低电平则为应答,输出高电平则为非应答。
当由于某种原因,从机设备不产生应答时,如从机在进行其它处理而无法接收总线上的数据时,必须释放SDA总线,然后由主机产生一个停止信号以终止数据传输。
当主机在接收数据时,接收到最后一个字节后,必须给从机发送一个非应答信号,使从机释放总线以便主机可以发送停止信号,终止数据传输。
(4)停止信号,SCL高电平期间,SDA拉高;
2.2 IIC总线数据传输
IIC总线协议规定:起始信号表明一次传输开始,其后为寻址字节(高7位为从机地址、最低1位为方向位,方向位表明全机与从机之间的数据传输方向,0:主机发送数据给从机,1:从机发送数据给主机);在寻址字节后是对应的读、写操作的数据字节和应答位;在数据传输完成后主机必须发送停止信号
IIC总线的数据传输方式有许多读、写组合方式: 主机写操作、主机读操作、主机读写操作
图2.2 传输器件地址格式 |
二、工程实践
本次对IIC协议的功能探究采用的是“利用IIC控制EEPROM读写”,以此来加强对IIC协议的了解。详细参考:EEPROM手册解读
1.系统框图
2.代码设计
2.1 IIC接口模块设计
2.1.1 IIC状态转移图
2.1.2 IIC接口代码
module i2c_intf (
input clk ,
input rst_n ,
input [3:0] cmd ,
input req ,
input [7:0] wr_data ,
output [7:0] dout ,
output done ,
output reg scl ,
inout sda
);
parameter CMD_START = 4'b0001,
CMD_WITER = 4'b0010,
CMD_READ = 4'b0100,
CMD_STOP = 4'b1000;
parameter SCL_MAX = 50_000_000 / 100_000 ,// 500分频-->100k的时钟频率
SCL_LOW = 125,//低电平的中间时刻,发送 1/4
SCL_HIGHT = 375;//高电平的中间时刻,采样 3/4
//wr_data
reg [7:0] wr_data_r;
reg [7:0] rd_data ;
//cmd寄存
reg [3:0] cmd_r;
//ack响应
reg rx_ack;
//scl计数器
reg [8:0] cnt_scl ;
wire add_cnt_scl ;
wire end_cnt_scl ;
//bit计数器
reg [3:0] cnt_bit ;
wire add_cnt_bit ;
wire end_cnt_bit ;
reg [3:0] bit_max ;//bit最大计数复用
//状态转移条件
reg [3:0] state_c;
reg [3:0] state_n;
wire idle2start ;
wire idle2witer ;
wire idle2read ;
wire start2witer ;
wire witer2rack ;
wire rack2idle ;
wire rack2stop ;
wire read2sack ;
wire sack2idle ;
wire sack2stop ;
wire stop2idle ;
//三态门
reg sda_en ; // 设置SDA模式,1位输出,0为输入
reg sda_out ; // SDA寄存器
wire sda_in;
/**************************************************************
状态机
**************************************************************/
parameter IDLE = 0,
START = 1,
WITER = 2,
RACK = 3,
READ = 4,
SACK = 5,
STOP = 6;
//第一段状态机
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
state_c <= IDLE;
end
else begin
state_c <= state_n;
end
end
//第二段状态机
always @(*)begin
case(state_c)
IDLE : if(idle2start)
state_n = START;
else if(idle2witer)
state_n = WITER;
else if(idle2read)
state_n = READ;
else
state_n = state_c;
START : if(start2witer)
state_n = WITER;
else
state_n = state_c;
WITER : if(witer2rack)
state_n = RACK;
else
state_n = state_c;
RACK : if(rack2idle)
state_n = IDLE;
else if(rack2stop)
state_n = STOP;
else
state_n = state_c;
READ : if(read2sack)
state_n = SACK;
else
state_n = state_c;
SACK : if(sack2idle)
state_n = IDLE;
else if(sack2stop)
state_n = STOP;
else
state_n = state_c;
STOP : if(stop2idle)
state_n = IDLE;
else
state_n = state_c;
default : state_n = state_c;
endcase
end
//状态跳转条件
assign idle2start = state_c == IDLE && req && (cmd & CMD_START) ;
assign idle2witer = state_c == IDLE && req && (cmd & CMD_WITER) ;
assign idle2read = state_c == IDLE && req && (cmd & CMD_READ ) ;
assign start2witer = state_c == START && end_cnt_bit ;
assign witer2rack = state_c == WITER && end_cnt_bit ;
assign rack2idle = state_c == RACK && end_cnt_bit && (!(cmd_r & CMD_STOP));
assign rack2stop = state_c == RACK && end_cnt_bit && ((cmd_r & CMD_STOP) || rx_ack);
assign read2sack = state_c == READ && end_cnt_bit ;
assign sack2idle = state_c == SACK && end_cnt_bit && (!(cmd_r & CMD_STOP));
assign sack2stop = state_c == SACK && end_cnt_bit && (cmd_r & CMD_STOP);
assign stop2idle = state_c == STOP && end_cnt_bit ;
/**************************************************************
时序约束
**************************************************************/
//cmd寄存
always@(posedge clk or negedge rst_n)
if(!rst_n)
cmd_r <= 4'b0000;
else if(req)
cmd_r <= cmd;
//接收从机回应的ack
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
rx_ack <= 1'b1;
end
else if(state_c == RACK && cnt_scl == SCL_HIGHT)begin
rx_ack <= sda_in;
end
end
//写入的数据
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
wr_data_r <= 0;
end
else if(req)begin
wr_data_r <= wr_data;
end
end
//接收读取的数据
//rd_data 接收读入的数据
always @(posedge clk or negedge rst_n)begin
if(~rst_n)begin
rd_data <= 0;
end
else if(state_c == READ && cnt_scl == SCL_HIGHT)begin
rd_data[7-cnt_bit] <= sda_in; //将接收到的SDA信号串并转换发送到eeprom_rw模块
end
end
/**************************************************************
双向端口sda的使用方式
**************************************************************/
assign sda_in = sda; //高阻态的话,则把总线上的数据赋给sda_in
assign sda = sda_en ? sda_out : 1'bz;//使能1则输出,0则高阻态
//sda_en
always@(posedge clk or negedge rst_n)
if(!rst_n)
sda_en <= 1'b0;
else if(state_c == READ | state_c == RACK)
sda_en <= 1'b0;
else
sda_en <= 1'b1;
//sda_out
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
sda_out <= 1;
end
else begin
case (state_c)
START : if(cnt_scl == SCL_LOW)
sda_out <= 1'b1;
else if(cnt_scl == SCL_HIGHT)
sda_out <= 1'b0;
WITER : if(cnt_scl == SCL_LOW)
sda_out <= wr_data_r[7-cnt_bit];//MSB
STOP : if(cnt_scl == SCL_LOW)
sda_out <= 1'b0;
else if(cnt_scl == SCL_HIGHT)
sda_out <= 1'b1;
SACK : if(cnt_scl == SCL_LOW)
sda_out <= (cmd & CMD_STOP)?1'b1:1'b0;
default: sda_out <= 1'bz;
endcase
end
end
/**************************************************************
系统时钟降频模块
**************************************************************/
//cnt_scl
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt_scl <= 0;
end
else if(add_cnt_scl)begin
if(end_cnt_scl)begin
cnt_scl <= 0;
end
else begin
cnt_scl <= cnt_scl + 1;
end
end
else begin
cnt_scl <= cnt_scl;
end
end
assign add_cnt_scl = state_c != IDLE ;
assign end_cnt_scl = add_cnt_scl && cnt_scl == SCL_MAX - 1;
//scl
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
scl <= 1'b1;
end
else if(cnt_scl <= (SCL_MAX>>1))begin
scl <= 1'b0;
end
else begin
scl <= 1'b1;
end
end
/**************************************************************
bit计数器
**************************************************************/
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt_bit <= 0;
end
else if(add_cnt_bit)begin
if(end_cnt_bit)begin
cnt_bit <= 0;
end
else begin
cnt_bit <= cnt_bit + 1;
end
end
else begin
cnt_bit <= cnt_bit;
end
end
assign add_cnt_bit = end_cnt_scl ;
assign end_cnt_bit = add_cnt_bit && cnt_bit == bit_max - 1;
//bit_max
always@(*)
if(state_c == WITER || state_c == READ)
bit_max = 8;
else
bit_max = 1;
/**************************************************************
输出信号
**************************************************************/
assign dout = rd_data;
assign done = rack2idle | sack2idle | stop2idle;
endmodule
2.2 eeprom读写控制模块设计
2.2.1 EEPROM读写控制模块状态转移图
2.2.2 EEPROM读写控制模块代码
module eeprom_rw_ctrl #( parameter wr_byte_num = 3 ,
rd_byte_num = 4
)(
input clk ,
input rst_n ,
output [3:0] cmd ,
output req ,
output [7:0] wr_data , //写入eeprom的数据
input [7:0] dout , //从eeprom读取的数据
input done , //传输完成标识
input busy ,
//key
input rd_en ,
//rx
input rx_data_vld ,
input [7:0] rx_data ,
//tx
output [7:0] tx_din ,
output tx_din_vld
);
parameter CMD_START = 4'b0001,
CMD_WITER = 4'b0010,
CMD_READ = 4'b0100,
CMD_STOP = 4'b1000;
//byte计数器
reg [4:0] cnt_byte ;
wire add_cnt_byte ;
wire end_cnt_byte ;
reg [4:0] byte_max ;//byte最大计数复用
//wr_addr
reg [8:0] cnt_wr_addr; //地址计数器,最高位用于选择black
wire add_cnt_wr_addr;
wire end_cnt_wr_addr;
//rd_addr
reg [8:0] cnt_rd_addr;
wire add_cnt_rd_addr;
wire end_cnt_rd_addr;
//STATE
reg [3:0] state_c;
reg [3:0] state_n;
wire idle2witer ;
wire idle2read ;
wire witer2wait_wr;
wire wait_wr2done ;
wire wait_wr2witer;
wire read2wait_rd ;
wire wait_rd2done ;
wire wait_rd2read ;
wire done2idle ;
//FIFO
wire [7:0] wr_fifo_data ;
wire fifo_wr_rdreq;
wire fifo_wr_wrreq;
wire fifo_wr_empty;
wire fifo_wr_full ;
wire [7:0] wr_usedw ;
wire fifo_rd_rdreq;
wire fifo_rd_wrreq;
wire fifo_rd_empty;
wire fifo_rd_full ;
wire [7:0] rd_usedw ;
wire [7:0] tx_data ;
reg [7:0] tx_data_r ;
reg tx_data_vld ;
//
reg [7:0] wr_data_r ;
reg tx_req ;
reg [3:0] tx_cmd ;
/**************************************************************
状态机
**************************************************************/
parameter IDLE = 0,
WITER = 1,
WAIT_WR = 2,
READ = 3,
WAIT_RD = 4,
DONE = 5;
//第一段状态机
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
state_c <= IDLE;
end
else begin
state_c <= state_n;
end
end
//第二段状态机
always @(*)begin
case(state_c)
IDLE : if(idle2witer)
state_n = WITER;
else if(idle2read)
state_n = READ;
else
state_n = state_c;
WITER : if(witer2wait_wr)
state_n = WAIT_WR;
else
state_n = state_c;
WAIT_WR : if(wait_wr2done)
state_n = DONE;
else if(wait_wr2witer)
state_n = WITER;
else
state_n = state_c;
READ : if(read2wait_rd)
state_n = WAIT_RD;
else
state_n = state_c;
WAIT_RD : if(wait_rd2done)
state_n = DONE;
else if(wait_rd2read)
state_n = READ;
else
state_n = state_c;
DONE : if(done2idle)
state_n = IDLE;
else
state_n = state_c;
default : state_n = state_c;
endcase
end
//状态跳转条件
assign idle2witer = state_c == IDLE && (wr_usedw >= wr_byte_num - 2);
assign idle2read = state_c == IDLE && rd_en ;
assign witer2wait_wr = state_c == WITER && 1'b1 ;
assign wait_wr2done = state_c == WAIT_WR && end_cnt_byte ;
assign wait_wr2witer = state_c == WAIT_WR && (done & cnt_byte < wr_byte_num - 1) ;
assign read2wait_rd = state_c == READ && 1'b1 ;
assign wait_rd2done = state_c == WAIT_RD && end_cnt_byte ;
assign wait_rd2read = state_c == WAIT_RD && (done & cnt_byte < rd_byte_num - 1) ;
assign done2idle = state_c == DONE && 1'b1 ;
/**************************************************************
byte计数器
**************************************************************/
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt_byte <= 0;
end
else if(add_cnt_byte)begin
if(end_cnt_byte)begin
cnt_byte <= 0;
end
else begin
cnt_byte <= cnt_byte + 1;
end
end
else begin
cnt_byte <= cnt_byte;
end
end
assign add_cnt_byte = (state_c == WAIT_RD | state_c == WAIT_WR) & done ;
assign end_cnt_byte = add_cnt_byte && cnt_byte == byte_max - 1;
//byte_max
always@(*)
if(state_c == WAIT_WR)
byte_max = wr_byte_num;
else
byte_max = rd_byte_num;
always@(posedge clk or negedge rst_n)
if(!rst_n)
SEND(1'b0,4'b0,8'b0);
else if(state_c == WITER)
case (cnt_byte)
0 : SEND(1'b1,(CMD_START|CMD_WITER),{6'b101000,cnt_wr_addr[8],1'b0}); //START+WITER 写控制字(写设备地址)
1 : SEND(1'b1,CMD_WITER,cnt_wr_addr[7:0]); //WITER(写字节地址)
wr_byte_num - 1 : SEND(1'b1,(CMD_WITER|CMD_STOP),wr_fifo_data ); //WITER(写数据)
default : SEND(1'b1,CMD_WITER,wr_fifo_data );
endcase
else if(state_c == READ)
case (cnt_byte)
0 : SEND(1'b1,(CMD_START|CMD_WITER),{6'b101000,cnt_rd_addr[8],1'b0}); //START+WITER(虚写过程)
1 : SEND(1'b1,CMD_WITER,cnt_rd_addr[7:0]); //WITER(写字节地址)
2 : SEND(1'b1,(CMD_START|CMD_WITER),{6'b101000,cnt_rd_addr[8],1'b1}); //START+读控制字(写读设备地址)
rd_byte_num - 1 : SEND(1'b1,(CMD_READ |CMD_STOP),8'b0); //READ+STOP(读数据)
default : SEND(1'b1,CMD_READ,8'b0); //READ(如果数据没读完,继续读数据)
endcase
else
SEND(1'b0,cmd,wr_data_r);
//用task发送请求、命令、数据(地址+数据)
task SEND; //任务 任务名
input req ;//任务输入
input [3:0] command ;
input [7:0] data ;
begin //顺序执行
tx_req = req; //任务的内部逻辑
tx_cmd = command;
wr_data_r = data;
end
endtask //任务结束
/**************************************************************
读写地址计数器
**************************************************************/
//wr_addr
always@(posedge clk or negedge rst_n)
if(!rst_n)
cnt_wr_addr <= 'd0;
else if(add_cnt_wr_addr) begin
if(end_cnt_wr_addr)
cnt_wr_addr <= 'd0;
else
cnt_wr_addr <= cnt_wr_addr + (wr_byte_num - 2); //wr_byte_num - 2 写状态有一个设备地址,一个字节地址
end
assign add_cnt_wr_addr = wait_wr2done ;
assign end_cnt_wr_addr = add_cnt_wr_addr && cnt_wr_addr == 256 - 1 ;
//rd_addr
always@(posedge clk or negedge rst_n)
if(!rst_n)
cnt_rd_addr <= 'd0;
else if(add_cnt_rd_addr) begin
if(end_cnt_rd_addr)
cnt_rd_addr <= 'd0;
else
cnt_rd_addr <= cnt_rd_addr + (rd_byte_num - 3); //rd_byte_num - 3 随机读过程,有一个虚写状态(2byte),一个写读地址(1byte)
end
assign add_cnt_rd_addr = wait_rd2done ;
assign end_cnt_rd_addr = add_cnt_rd_addr && cnt_rd_addr == 256 - 1 ;
/**************************************************************
FIFO模块
**************************************************************/
//fifo_wr
fifo fifo_wr (
.aclr ( ~rst_n ),
.clock ( clk ),
.data ( rx_data ),
.rdreq ( fifo_wr_rdreq ),
.wrreq ( fifo_wr_wrreq ),
.empty ( fifo_wr_empty ),
.full ( fifo_wr_full ),
.q ( wr_fifo_data ),
.usedw ( wr_usedw )
);
assign fifo_wr_rdreq = state_c== WAIT_WR && done && cnt_byte > 1;
assign fifo_wr_wrreq = ~fifo_wr_full & rx_data_vld;
//fifo_rd
fifo fifo_rd (
.aclr ( ~rst_n ),
.clock ( clk ),
.data ( dout ),
.rdreq ( fifo_rd_rdreq ),
.wrreq ( fifo_rd_wrreq ),
.empty ( fifo_rd_empty ),
.full ( fifo_rd_full ),
.q ( tx_data ),
.usedw ( rd_usedw )
);
assign fifo_rd_rdreq = ~fifo_rd_empty && busy;
assign fifo_rd_wrreq = ~fifo_rd_full && state_c == WAIT_RD && cnt_byte > 2 && done;
//fifo数据缓存
always@(posedge clk or negedge rst_n)
if(!rst_n) begin
tx_data_r <= 8'b0;
tx_data_vld <= 1'b0;
end
else begin
tx_data_r <= tx_data;
tx_data_vld <= fifo_rd_rdreq;
end
//输出端口
assign req = tx_req;
assign cmd = tx_cmd;
assign wr_data = wr_data_r;
assign tx_din = tx_data_r;
assign tx_din_vld = tx_data_vld;
endmodule
2.3 顶层模块
module top (
input clk ,
input rst_n ,
input key_in ,
input rx_din ,
output tx_dout ,
output scl ,
inout sda
);
//模块连接
wire key_flag;
wire rx_data_vld;
wire [7:0] rx_data ;
wire [7:0] tx_din ;
wire tx_din_vld ;
wire [3:0] cmd ;
wire req ;
wire [7:0] wr_data;
wire [7:0] dout ;
wire done ;
key_filter#(
.CNT_MAX(20'd999)
)key_filter_inst(
/* input */ .clk (clk ) ,
/* input */ .rst_n (rst_n ) ,
/* input */ .key_in (key_in ) ,
/* output reg */ .key_flag (key_flag)
);
uart_rx #(
.CLK_FREQ(50_000_000),
.BPS (115200 ),
.CHECK ("NONE" )
)uart_rx_inst(
/* input */ .clk (clk ) ,
/* input */ .rst_n (rst_n ) ,
/* input */ .rx_din (rx_din ) ,
/* output */ .rx_data_vld(rx_data_vld) ,
/* output reg [7:0] */ .rx_data (rx_data )
);
uart_tx #(
.CLK_FREQ(50_000_000),
.BPS (115200 ),
.CHECK ("NONE" )
)uart_tx_inst(
/* input */ .clk (clk ) ,
/* input */ .rst_n (rst_n ) ,
/* input [7:0] */ .tx_din (tx_din ) ,
/* input */ .tx_din_vld(tx_din_vld) ,
/* output */ .tx_ready (tx_ready ) ,
/* output reg */ .tx_dout (tx_dout )
);
eeprom_rw_ctrl #(.wr_byte_num(18) ,
.rd_byte_num(19)
) eeprom_rw_ctrl_inst(
/* input */ .clk (clk ) ,
/* input */ .rst_n (rst_n ) ,
/* output reg [3:0] */ .cmd (cmd ) ,
/* output */ .req (req ) ,
/* output reg [7:0] */ .wr_data(wr_data) , //写入eeprom的数据
/* input [7:0] */ .dout (dout ) , //从eeprom读取的数据
/* input */ .done (done ) , //传输完成标识
/* input */ .busy (tx_ready) ,
/* //key */
/* input */ .rd_en (key_flag ) ,
/* //rx */
/* input */ .rx_data_vld (rx_data_vld),
/* input reg [7:0] */ .rx_data (rx_data ),
/* //tx */
/* output [7:0] */ .tx_din (tx_din ),
/* output */ .tx_din_vld (tx_din_vld )
);
i2c_intf i2c_intf_inst(
/* input */ .clk (clk ) ,
/* input */ .rst_n (rst_n ) ,
/* input [3:0] */ .cmd (cmd ) ,
/* input */ .req (req ) ,
/* input [7:0] */ .wr_data(wr_data) ,
/* output [7:0] */ .dout (dout ) ,
/* output */ .done (done ) ,
/* output reg */ .scl (scl ) ,
/* inout */ .sda (sda )
);
endmodule
2.4 其余模块
uart_rx、uart_tx、key_filter模块,略。详细见之前的博客。
3.仿真测试
3.1仿真测试代码
`timescale 1ns/1ps
module i2c_ctrl_rw_tb();
parameter CLK_CYCLE = 20;
reg sys_clk,sys_rst_n;
reg [3:0] cmd;
reg [7:0] wr_data;
reg req;
wire m_scl;
wire m_sda;
pullup(m_sda);//拉高sda,上拉
always #(CLK_CYCLE/2) sys_clk = ~sys_clk;
initial begin
sys_clk = 1'b1;
sys_rst_n = 1'b0;
#(CLK_CYCLE*2);
sys_rst_n = 1'b1;
end
i2c_intf i2c_intf(
/* input */ .clk (sys_clk) ,
/* input */ .rst_n (sys_rst_n) ,
/* input [3:0] */ .cmd (cmd) ,
/* input */ .req (req) ,
/* input [7:0] */ .wr_data(wr_data) ,
/* output [7:0] */ .dout () ,
/* output */ .done () ,
/* output reg */ .m_scl (m_scl) ,
/* inout */ .m_sda (m_sda)
);
M24LC04B M24LC04B(1'b0, 1'b0, 1'b0, 1'b0, m_sda, m_scl, ~sys_rst_n);
initial begin
req = 1'b0;
cmd = 4'b0;
wr_data = 8'b0;
#(CLK_CYCLE*100);
req = 1'b1;
my_cmd(4'b0011,8'ha0);//start+write
my_cmd(4'b0010,8'h02); //write
my_cmd(4'b0010,8'ha3);
my_cmd(4'b1000,8'h00); //stop
my_cmd(4'b0011,8'ha0);//start+write
my_cmd(4'b0010,8'h02);//write
my_cmd(4'b0011,8'ha1);//start+write
#(CLK_CYCLE*500);
my_cmd(4'b0100,8'ha3);//read
my_cmd(4'b1000,8'h00);//stop
req = 1'b0;
#(CLK_CYCLE*100);
$stop;
end
task my_cmd;
input [3:0] cmd_in;
input [7:0] data_in;
begin
cmd = cmd_in;
wr_data = data_in;
#(CLK_CYCLE*500); //延时包括应答周期
#(CLK_CYCLE*4000 +2);
end
endtask
endmodule
`timescale 1ns/1ps
module top_tb();
parameter CLK_CYCLE = 20;
reg sys_clk,sys_rst_n;
reg key_in,rx_din;
wire m_scl,m_sda;
pullup(m_sda);
always #(CLK_CYCLE/2) sys_clk = ~sys_clk;
initial begin
sys_clk = 1'b1;
sys_rst_n = 1'b0;
#(CLK_CYCLE*2);
sys_rst_n = 1'b1;
end
top top_tb(
/* input */ .clk (sys_clk) ,
/* input */ .rst_n (sys_rst_n) ,
/* input */ .key_in (key_in) ,
/* input */ .rx_din (rx_din) ,
/* output */ .tx_dout() ,
/* output */ .m_scl (m_scl) ,
/* inout */ .m_sda (m_sda)
);
M24LC04B M24LC04B(1'b0, 1'b0, 1'b0, 1'b0, m_sda, m_scl, ~sys_rst_n);
initial begin
rx_din = 1;
sys_clk = 1'b1;
key_in = 1'b1;
sys_rst_n = 1'b0;
#(CLK_CYCLE*2);
sys_rst_n = 1'b1;
#(CLK_CYCLE*20);
repeat(5) begin
my_uart_tx(8'ha0,1'b1);
#(CLK_CYCLE*10000);
key_in = 1'b0;
#(CLK_CYCLE*40000);
key_in = 1'b1;
end
repeat(5) begin
my_uart_tx(8'ha0,1'b1);
#(CLK_CYCLE*10000);
key_in = 1'b0;
#(CLK_CYCLE*40000);
key_in = 1'b1;
end
#(CLK_CYCLE*434);
$stop;
end
integer i;
task my_uart_tx;
input [7:0] data_in;
input key;
begin
rx_din = 0;
#(CLK_CYCLE*434);
//数据位
for(i=0;i<=7;i=i+1)begin
rx_din = data_in[i];
#(CLK_CYCLE*434);
end
//停止位
rx_din = 1;
#(CLK_CYCLE*434);
#(CLK_CYCLE*10000);
end
endtask
endmodule
3.2 仿真测试图
图3.1 IIC接口模块仿真测试图 |
图3.2 数据存入的memory |
图3.3 数据存入的block地址 |
仿真测试分析:
如图3.1,写数据时,当仿真输入cmd = 4’b0011时,执行写设备地址+写控制字功能,可以写入设备地址1010000;当仿真输入cmd = 4’b0010时,执行写功能,可以写入字节地址;当仿真输入cmd = 4’b0010时,执行写功能,可以写入数据;当仿真输入cmd = 4’b1000时,执行停止功能,停止写入;
读数据时,当仿真输入cmd = 4’b0011时,执行写设备地址功能,可以写入设备地址1010000;当仿真输入cmd = 4’b0010时,执行写功能,可以写入字节地址;当仿真输入cmd = 4’b0011时,执行写设备地址+读控制字功能,可以写入读的设备地址;当仿真输入cmd = 4’b0100时,执行读数据功能,读取数据并输出;最后,当仿真输入cmd = 4’b1000时,执行停止功能,停止读取