IIC接口协议是一种比较简单、常用的一种接口协议,使用它的场景很广泛(最常见的如EEPROM读写、一些寄存器的配置等),是我们学习FPGA过程中常用的接口协议,本篇使用EEPROM(AT24C512B)器件作为目标器件进行IIC接口实现,其他器件读写方式略有差别,IIC协议不变。
IIC (Inter-Integrated Circuit(集成电路总线)),百度百科解释如下:IIC-百度百科。
这里简单概括一下协议内容:IIC(IIC,inter-Integrated circuit),两线式串行总线,用于MCU和外设间的通信;IIC只需两根线进行数据传输(数据线SDA和时钟线SCL),以半双工方式实现MCU和外设之间数据传输,常用的传输速率为100kbps、400kbps。
注意:SDA和SCL两根总线需要上拉,使总线处于空闲状态。
IIC协议如下:
1.空闲状态
协议规定,SDA和SCL同时为高电平时,总线处于空闲状态。上拉电阻保证电平处于高电平。
2.起始信号和结束信号
起始信号:SCL为高电平时,SDA电平发生高到低的跳变。
停止信号:SCL为高电平时,SDA电平发生低到高的跳变。
3.应答信号
发送器每发送完一个字节(8个脉冲),在第9个脉冲间释放总线,接收器返回一个ACK信号,协议规定,低电平为有效应答,高电平为无效应答。
4.数据有效性
协议对有效数据进行了规定:即时钟信号为高电平期间,数据必须保持稳定,时钟信号低电平期间,数据线上的电平才允许变化。也就是说,数据在时钟信号到来前必须准备好,并保持到时钟信号的下降沿之后。
5.数据传输
I2C为同步传输,时钟控制数据位的传输,边沿触发。
这里只实现“字节写”和“随机读”读写方式,其他读写方式根据实际情况略作更改即可
1.字节写
在起始信号后第一个字节写入7bit设备地址和1bit读写信号,等待从机回复一个ACK信号,第二字节写入高字节寄存器地址,等待从机回复一个ACK信号,第三字节写入低字节寄存器地址(若地址只有八位,写入一个寄存器地址即可),等待从机回复一个ACK信号,第四个字节写入要写入的数据,等待从机回复一个ACK信号,最后是一个停止信号。
2.随机读
在起始信号后第一个字节写入7bit设备地址和1bit读写信号,等待从机回复一个ACK信号,第二字节写入高字节寄存器地址,等待从机回复一个ACK信号,第三字节写入低字节寄存器地址(若地址只有八位,写入一个寄存器地址即可),等待从机回复一个ACK信号(前面的3个字节是一个虚写操作);这里之后需要增加一个起始信号,第四个字节写入7bit设备地址和1bit读信号,等待从机回复一个ACK信号,第五个字节从机回复当前地址的数据,之后从机再回复一个NOACK信号,最后是一个停止信号。
1.宏定义文件
宏定义文件定义了3个宏,“SIM”用于仿真和综合的切换,综合时将“SIM”注释即可;“SIM_MODEL”用于添加仿真模型,默认即可;“FIRST_SIM”用于第一次仿真和非第一次仿真的切换,如果不是第一次仿真注释即可。
`define SIM
`define SIM_MODEL
// `define FIRST_SIM
2.IIC主机实现
IIC主机用于读写主控,这里使用了两个parameter,“IIC_RATE ”用于切换IIC速率(50K、100K、400K),“REGADDR_WIDTH ”用于切换读写地址位宽(8bit、16bit)
`timescale 1ns/1ns
//------------------------------------------------------------------------------------------
`include "macro_define.v"
`define SCL_POS (cnt_delay==scl_pos_cnt) //cnt=0:scl posedge
`define SCL_HIG (cnt_delay==scl_hig_cnt) //cnt=1:scl high level :data stable
`define SCL_NEG (cnt_delay==scl_neg_cnt) //cnt=2:scl negedge
`define SCL_LOW (cnt_delay==scl_low_cnt) //cnt=3:scl low level :data change
module iic_master
#(
parameter IIC_RATE = "100k",
parameter REGADDR_WIDTH = 16
)
(
input wire clk , //50M
input wire rst_n , //
output wire scl , //IIC clock
inout wire sda , //IIC data
input wire iic_start , //IIC start
input wire rd_or_wr , //1:read 0:write
input wire[6:0] iic_device_id , //IIC device address
input wire[REGADDR_WIDTH-1:0] iic_reg_addr , //register address
input wire[7:0] iic_wr_data , //write register data
output reg iic_rd_data_en , //IIC done flag
output reg [7:0] iic_rd_data , //read register data
output reg iic_done , //IIC done flag
output reg iic_busy=0 //IIC busy flag
);
//-------------------reg/wire--------------------------//
reg iic_start_t1 ;
reg iic_start_rise ;
reg[3:0] cnt ;
reg[15:0] cnt_delay ;
wire scl_valid ;
reg [15:0] delay_counter ;
wire[15:0] scl_pos_cnt ;
wire[15:0] scl_hig_cnt ;
wire[15:0] scl_neg_cnt ;
wire[15:0] scl_low_cnt ;
wire[8*4-1:0] iic_rate ;
reg scl_temp ;
reg[7:0] db_r ; //
reg[3:0] cstate ; //
reg sda_r ; //sda output reg
reg sda_link ; //0:output 1:input
reg[3:0] num ; //
reg ack_reg=0 ;
//----------------------------------------------------//
assign iic_rate = IIC_RATE ;
assign scl_valid = iic_busy ;
always @ (*)
begin
if(iic_rate=="50k")
delay_counter <= 2000;
else if(iic_rate=="100k")
delay_counter <= 1000;
else if(iic_rate=="400k")
delay_counter <= 500;
else
delay_counter <= 250;
end
assign scl_pos_cnt = (delay_counter>>2)-1;
assign scl_hig_cnt = (delay_counter>>1)-1;
assign scl_neg_cnt = (delay_counter>>1)+(delay_counter>>2)-1;
assign scl_low_cnt = delay_counter-1;
//iic_start_rise
always @ (posedge clk or negedge rst_n)
if( !rst_n )begin
iic_start_t1 <= 1'b0;
iic_start_rise <= 1'b0;
end
else begin
iic_start_t1 <= iic_start;
iic_start_rise <= iic_start & ~iic_start_t1;
end
//cnt_delay
always @ (posedge clk or negedge rst_n)
if( !rst_n )
cnt_delay <= 10'd0;
else if( scl_valid && cnt_delay == delay_counter-1 )
cnt_delay <= 10'd0;
else if(scl_valid==1)
cnt_delay <= cnt_delay+1'b1;
else
cnt_delay <= 10'd0;
always @ (posedge clk or negedge rst_n) begin
if( !rst_n ) cnt <= 3'd0;
else begin
case ( cnt_delay )
scl_neg_cnt: cnt <= 3'd0;
scl_low_cnt: cnt <= 3'd1;
scl_pos_cnt: cnt <= 3'd2;
scl_hig_cnt: cnt <= 3'd3;
default: ;
endcase
end
end
//产生iic所需要的时钟
always @ (posedge clk or negedge rst_n)
if(!rst_n)
scl_temp <= 1'b1;
else if(cnt_delay==scl_pos_cnt)
scl_temp <= 1'b1; //scl rise edge
else if(cnt_delay==scl_neg_cnt)
scl_temp <= 1'b0; //scl falling edge
else
scl_temp <= scl_temp;
assign scl = (scl_valid==1)?scl_temp:1;
reg iic_done_t;
//
always @ (posedge clk or negedge rst_n)
if(!rst_n)begin
iic_done_t <= 1'b0;
end
else begin
iic_done_t <= iic_done;
end
always @ (posedge clk or negedge rst_n)
if(!rst_n)
iic_rd_data_en<= 1'b0;
else if(rd_or_wr)
iic_rd_data_en <= iic_done & ~iic_done_t;
else
iic_rd_data_en<= 1'b0;
//---------------------------------------------//
localparam IDLE = 4'd0 ,
START1 = 4'd1 ,
ADD1 = 4'd2 ,
ACK1 = 4'd3 ,
ADD2 = 4'd4 ,
ACK2 = 4'd5 ,
ADD2_1 = 4'd6 ,
ACK2_1 = 4'd7 ,
START2 = 4'd8 ,
ADD3 = 4'd9 ,
ACK3 = 4'd10 ,
DATA = 4'd11 ,
ACK4 = 4'd12 ,
STOP1 = 4'd13 ,
STOP2 = 4'd14 ;
always @ (posedge clk or negedge rst_n) begin
if(!rst_n) begin
cstate <= IDLE;
sda_r <= 1'b1;
sda_link <= 1'b1;
num <= 4'd0;
db_r <= 8'd0;
iic_rd_data <= 8'b0000_0000;
iic_done <= 1'b0;
end
else
case (cstate)
IDLE: begin
sda_link <= 1'b1; //0:input;1:output
sda_r <= 1'b1;
iic_done <= 1'b0;
if(iic_start_rise) begin //iic start
`ifdef SIM
db_r <= {iic_device_id,rd_or_wr}; //write device address rd_or_wr=1:read;rd_or_wr=0:write;
// $display("simulation");
`else
db_r <= {iic_device_id,1'b0}; //write device address rd_or_wr=1:read;rd_or_wr=0:write;
// $display("synth");
`endif
cstate <= START1;
iic_busy <= 1;
end
else cstate <= IDLE;
end
START1: begin //iic start singal
if(`SCL_HIG) begin //scl = 1;
sda_link <= 1'b1; //0:input;1:output
sda_r <= 1'b0; //iic start singal
cstate <= ADD1;
num <= 4'd0; //num clear
end
else cstate <= START1;
end
ADD1: begin //iic receive device id
if(`SCL_LOW) begin
if(num == 4'd8) begin
num <= 4'd0; //num clear
sda_r <= 1'b1;
sda_link <= 1'b0; //0:input;1:output wait ack
cstate <= ACK1;
end
else begin
cstate <= ADD1;
num <= num+1'b1;
case (num)
4'd0: sda_r <= db_r[7];
4'd1: sda_r <= db_r[6];
4'd2: sda_r <= db_r[5];
4'd3: sda_r <= db_r[4];
4'd4: sda_r <= db_r[3];
4'd5: sda_r <= db_r[2];
4'd6: sda_r <= db_r[1];
4'd7: sda_r <= db_r[0]; //read or warte
default: ;
endcase
end
end
else cstate <= ADD1;
end
ACK1: begin //iic after transmit device id,receive slave ack singal
if(`SCL_HIG)
ack_reg <= sda;
else if(`SCL_NEG) begin
sda_r <= 1'b0;
sda_link <= 1'b1; //0:input;1:output wait ack
if(ack_reg==0 && REGADDR_WIDTH == 16)begin
cstate <= ADD2; // if register address width is 16bit,go to status ADD2
db_r <= iic_reg_addr[15:8]; // Transmit High 8-bit Address first
end
else if(ack_reg==0 && REGADDR_WIDTH == 8)begin
cstate <= ADD2_1; // if register address width is 8bit,go to status ADD2_1
db_r <= iic_reg_addr[7:0]; // Transmit 8-bit Address
end
else if(ack_reg!=0)
cstate <= IDLE;
end
else cstate <= ACK1; //wait slave respond
end
ADD2: begin // Transmit High 8-bit Address first
if(`SCL_LOW) begin
if(num==4'd8) begin
num <= 4'd0; //num计数清零
sda_r <= 1'b1;
sda_link <= 1'b0; //sda置为高阻态(input)
cstate <= ACK2;
end
else begin
sda_link <= 1'b1; //sda作为output
num <= num+1'b1;
case (num)
4'd0: sda_r <= db_r[7];
4'd1: sda_r <= db_r[6];
4'd2: sda_r <= db_r[5];
4'd3: sda_r <= db_r[4];
4'd4: sda_r <= db_r[3];
4'd5: sda_r <= db_r[2];
4'd6: sda_r <= db_r[1];
4'd7: sda_r <= db_r[0];
default: ;
endcase
cstate <= ADD2;
end
end
else cstate <= ADD2;
end
ACK2: begin //iic transmit high 8-bit address finish,receive slave ack singal
if(`SCL_HIG)
ack_reg <= sda;
else if(`SCL_NEG)begin
sda_r <= 1'b0;
sda_link <= 1'b1; //0:input;1:output wait ack
if(ack_reg==0)begin
cstate <= ADD2_1; //从机响应信号
db_r <= iic_reg_addr[7:0]; // 1地址
end
else
cstate <= IDLE;
end
else cstate <= ACK2; //等待从机响应
end
ADD2_1: begin // if register address width is 16bit Transmit Low 8-bit Address
if(`SCL_LOW) begin // if register address width is 8bit Transmit register Address
if(num==4'd8) begin
num <= 4'd0; //num计数清零
sda_r <= 1'b1;
sda_link <= 1'b0; //sda置为高阻态(input)
cstate <= ACK2_1;
end
else begin
sda_link <= 1'b1; //sda作为output
num <= num+1'b1;
case (num)
4'd0: sda_r <= db_r[7];
4'd1: sda_r <= db_r[6];
4'd2: sda_r <= db_r[5];
4'd3: sda_r <= db_r[4];
4'd4: sda_r <= db_r[3];
4'd5: sda_r <= db_r[2];
4'd6: sda_r <= db_r[1];
4'd7: sda_r <= db_r[0];
default: ;
endcase
cstate <= ADD2_1;
end
end
else cstate <= ADD2_1;
end
ACK2_1: begin //iic transmit register address finish,receive slave ack singal
if(`SCL_HIG)
ack_reg <= sda;
else if(`SCL_NEG)begin
sda_r <= 1'b0;
sda_link <= 1'b1; //0:input;1:output wait ack
if(ack_reg==0)begin
if(rd_or_wr) begin
cstate <= START2; //读操作
db_r <= {iic_device_id,1'b1}; //送器件地址(读操作),特定地址读需要执行该步骤以下操作
end
else begin
cstate <= DATA; //写操作
db_r <= iic_wr_data; //写入的数据
end
end
else
cstate <= IDLE;
end
else cstate <= ACK2_1; //等待从机响应
end
START2: begin //读操作起始位
if(`SCL_LOW) begin
sda_link <= 1'b1; //sda作为output
sda_r <= 1'b1; //拉高数据线sda
cstate <= START2;
end
else if(`SCL_HIG) begin //scl为高电平中间
sda_r <= 1'b0; //拉低数据线sda,产生起始位信号
cstate <= ADD3;
end
else cstate <= START2;
end
ADD3: begin //送读操作地址
if(`SCL_LOW) begin
if(num==4'd8) begin
num <= 4'd0; //num计数清零
sda_r <= 1'b1;
sda_link <= 1'b0; //sda置为高阻态(input)
cstate <= ACK3;
end
else begin
num <= num+1'b1;
case (num)
4'd0: sda_r <= db_r[7];
4'd1: sda_r <= db_r[6];
4'd2: sda_r <= db_r[5];
4'd3: sda_r <= db_r[4];
4'd4: sda_r <= db_r[3];
4'd5: sda_r <= db_r[2];
4'd6: sda_r <= db_r[1];
4'd7: sda_r <= db_r[0];
default: ;
endcase
cstate <= ADD3;
end
end
else cstate <= ADD3;
end
ACK3: begin
if(`SCL_HIG)
ack_reg <= sda;
else if(`SCL_NEG)begin
sda_r <= 1'b0;
sda_link <= 1'b1; //0:input;1:output wait ack
if(ack_reg==0)begin
cstate <= DATA; //从机响应信号
sda_link <= 1'b0;
end
else
cstate <= IDLE;
end
else cstate <= ACK3; //等待从机响应
end
DATA: begin
if(rd_or_wr) begin //读操作
if(num<=4'd7) begin
cstate <= DATA;
if(`SCL_HIG) begin
num <= num+1'b1;
case (num)
4'd0: iic_rd_data[7] <= sda;
4'd1: iic_rd_data[6] <= sda;
4'd2: iic_rd_data[5] <= sda;
4'd3: iic_rd_data[4] <= sda;
4'd4: iic_rd_data[3] <= sda;
4'd5: iic_rd_data[2] <= sda;
4'd6: iic_rd_data[1] <= sda;
4'd7: iic_rd_data[0] <= sda;
default: ;
endcase
end
end
else if((`SCL_LOW) && (num==4'd8)) begin
num <= 4'd0; //num计数清零
cstate <= ACK4;
end
else cstate <= DATA;
end
else begin //write data
sda_link <= 1'b1;
if(num<=4'd7) begin
cstate <= DATA;
if(`SCL_LOW) begin
sda_link <= 1'b1; //数据线sda作为output
num <= num+1'b1;
case (num)
4'd0: sda_r <= db_r[7];
4'd1: sda_r <= db_r[6];
4'd2: sda_r <= db_r[5];
4'd3: sda_r <= db_r[4];
4'd4: sda_r <= db_r[3];
4'd5: sda_r <= db_r[2];
4'd6: sda_r <= db_r[1];
4'd7: sda_r <= db_r[0];
default: ;
endcase
end
end
else if((`SCL_LOW) && (num==4'd8)) begin
num <= 4'd0;
sda_r <= 1'b1;
sda_link <= 1'b0; //sda置为高阻态
cstate <= ACK4;
end
else cstate <= DATA;
end
end
ACK4: begin
if(`SCL_HIG)
ack_reg <= sda;
else if(`SCL_NEG)begin
sda_r <= 1'b0;
sda_link <= 1'b1; //0:input;1:output wait ack
if(ack_reg==0 && rd_or_wr==0)begin
cstate <= STOP1;
end
else if(ack_reg==1 && rd_or_wr==1)begin
cstate <= STOP1;
end
else
cstate <= IDLE;
end
else cstate <= ACK4;
end
STOP1: begin
sda_link <= 1'b1; //0:input;1:output
sda_r <= 1'b0;
if(`SCL_HIG)begin
iic_done <= 1'b1;
cstate <= IDLE;
iic_busy <= 0;
end
else cstate <= STOP1;
end
default: cstate <= IDLE;
endcase
end
assign sda = sda_link ? sda_r:1'bz;
//---------------------------------------------
endmodule
3.IIC读写模型(可以理解为从机)
IIC读写模型用于模拟EEPROM从机,用于和主机交互,一般我们写好主机程序之后,没有读写模型不能仿真,不能验证代码的正确性,这里读写模型可方便仿真。
`timescale 1ns / 1ps
//
// Company:
// Engineer: nathanz
//
// Create Date: 2021/05/01 18:35:23
// Design Name:
// Module Name: iic_sim_model
// Project Name:
// Target Devices:
// Tool Versions:
// Description:
//
// Dependencies:
//
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
//
//
`define SCL_POS (cnt_delay==scl_pos_cnt) //cnt=0:scl posedge
`define SCL_HIG (cnt_delay==scl_hig_cnt) //cnt=1:scl high level :data stable
`define SCL_NEG (cnt_delay==scl_neg_cnt) //cnt=2:scl negedge
`define SCL_LOW (cnt_delay==scl_low_cnt) //cnt=3:scl low level :data change
module iic_sim_model
#(
parameter REGADDR_WIDTH = 16
)
(
input wire clk ,
input wire rst_n , //active-low
input wire i_scl , //为输入
inout wire io_sda
);
/********************************************/
parameter clk_period_cnt = 1000 ;
localparam IDLE = 4'd0 ,
START1 = 4'd1 ,
ADD1 = 4'd2 ,
ACK1 = 4'd3 ,
ADD2 = 4'd4 ,
ACK2 = 4'd5 ,
ADD2_1 = 4'd6 ,
ACK2_1 = 4'd7 ,
START2 = 4'd8 ,
ADD3 = 4'd9 ,
ACK3 = 4'd10 ,
DATA = 4'd11 ,
ACK4 = 4'd12 ,
STOP1 = 4'd13 ;
localparam device_id = 8'ha0;
reg[3:0] cstate ; //
reg sda_link ; //sda direction crtl 1:output;0:input
reg sda_r ; //
reg[3:0] num ; //
reg iic_busy ;
reg[7:0] rec_device_id=0 ;
reg[15:0] iic_reg_addr=0 ;
reg[7:0] read_back_reg=0 ;
wire[7:0] rd_data ;
reg[7:0] wr_data=0 ;
wire rd_or_wr ;
reg[15:0] cnt_delay ;
wire[15:0] scl_pos_cnt ;
wire[15:0] scl_hig_cnt ;
wire[15:0] scl_neg_cnt ;
wire[15:0] scl_low_cnt ;
wire iic_en=1 ;
reg i_scl_dly1 ;
wire i_scl_rise ;
wire i_scl_fall ;
reg io_sda_dly1 ;
wire io_sda_rise ;
wire io_sda_fall ;
assign rd_or_wr = rec_device_id[0];
always @ (posedge clk or negedge rst_n)
if(!rst_n)
begin
i_scl_dly1 <= 1'b0;
io_sda_dly1 <= 1'b0;
end
else
begin
i_scl_dly1 <= i_scl;
io_sda_dly1 <= io_sda;
end
//sda上升沿
assign io_sda_rise = io_sda & ~io_sda_dly1;
//sda下降沿
assign io_sda_fall = ~io_sda & io_sda_dly1;
//scl上升沿
assign i_scl_rise = i_scl & ~i_scl_dly1;
//scl下降沿
assign i_scl_fall = ~i_scl & i_scl_dly1;
assign scl_valid = iic_busy;
assign scl_low_cnt = (clk_period_cnt>>2)-1;
assign scl_pos_cnt = (clk_period_cnt>>1)-1;
assign scl_hig_cnt = (clk_period_cnt>>1)+(clk_period_cnt>>2)-1;
assign scl_neg_cnt = clk_period_cnt-1;
//cnt_delay
always @ (posedge clk or negedge rst_n)
if( !rst_n )
cnt_delay <= 10'd0;
else if(scl_valid==1 && i_scl_fall==1)
cnt_delay <= 10'd0;
else
cnt_delay <= cnt_delay+1'b1;
always @ (posedge clk or negedge rst_n) begin
if(!rst_n) begin
cstate <= IDLE;
sda_link <= 1'b0;
sda_r <= 1'b1;
num <= 4'd0;
rec_device_id <= 8'd0;
end
else
case (cstate)
IDLE: begin
sda_link <= 1'b0; //数据线sda为input
rec_device_id <= 8'h00;
if(iic_en)
begin //iic_en==1,开始操作
iic_busy <= 1'b1;
cstate <= START1;
end
else cstate <= IDLE; //没有任何键被按下
end
START1: begin
if(i_scl==1 && io_sda_fall==1) begin //起始信号
cstate <= ADD1;
num <= 4'd0; //num计数清零
end
else cstate <= START1; //等待起始信号
end
ADD1: begin //设备地址
if(`SCL_NEG && num==7)begin
num <= 4'd0; //num计数清零
sda_r <= 1'b1;
sda_link <= 1'b0; //sda置为高阻态(input)
cstate <= ACK1;
end
else if(`SCL_NEG)begin
cstate <= ADD1;
num <= num+1'b1;
end
else if(`SCL_HIG) begin
cstate <= ADD1;
case (num)
4'd0: rec_device_id[7] <= io_sda;
4'd1: rec_device_id[6] <= io_sda;
4'd2: rec_device_id[5] <= io_sda;
4'd3: rec_device_id[4] <= io_sda;
4'd4: rec_device_id[3] <= io_sda;
4'd5: rec_device_id[2] <= io_sda;
4'd6: rec_device_id[1] <= io_sda;
4'd7: rec_device_id[0] <= io_sda;
// 4'd8: rd_or_wr <= io_sda;
default: ;
endcase
end
else cstate <= ADD1;
end
ACK1: begin
sda_r <= 1'b0; //回复ACK;
sda_link <= 1'b1; //sda :output
if(`SCL_NEG) begin
sda_r <= 1'b1; //
sda_link <= 1'b0; //sda :input
if(REGADDR_WIDTH == 16)begin
cstate <= ADD2;
end
else if(REGADDR_WIDTH == 8)begin
cstate <= ADD2_1;
end
else cstate <= ACK1;
end
end
ADD2: begin //内存地址
if(`SCL_NEG && num==7)begin
num <= 4'd0; //num计数清零
sda_r <= 1'b1;
sda_link <= 1'b0; //sda置为高阻态(input)
cstate <= ACK2;
end
else if(`SCL_NEG)begin
cstate <= ADD2;
num <= num+1'b1;
end
else if(`SCL_HIG) begin
cstate <= ADD2;
case (num)
4'd0: ;
4'd0: iic_reg_addr[15] <= io_sda;
4'd1: iic_reg_addr[14] <= io_sda;
4'd2: iic_reg_addr[13] <= io_sda;
4'd3: iic_reg_addr[12] <= io_sda;
4'd4: iic_reg_addr[11] <= io_sda;
4'd5: iic_reg_addr[10] <= io_sda;
4'd6: iic_reg_addr[9] <= io_sda;
4'd7: iic_reg_addr[8] <= io_sda;
default: ;
endcase
end
else cstate <= ADD2;
end
ACK2: begin
sda_r <= 1'b0; //回复ACK;
sda_link <= 1'b1; //sda输出
if(`SCL_NEG)begin
sda_r <= 1'b1; //回复ACK结束;
sda_link <= 1'b0; //sda输入
cstate <= ADD2_1;
end
end
ADD2_1: begin
if(`SCL_NEG && num==7)begin
num <= 4'd0; //num计数清零
sda_r <= 1'b1;
sda_link <= 1'b0; //sda置为高阻态(input)
cstate <= ACK2_1;
end
else if(`SCL_NEG)begin
cstate <= ADD2_1;
num <= num+1'b1;
end
else if(`SCL_HIG) begin
cstate <= ADD2_1;
case (num)
4'd0: iic_reg_addr[7] <= io_sda;
4'd1: iic_reg_addr[6] <= io_sda;
4'd2: iic_reg_addr[5] <= io_sda;
4'd3: iic_reg_addr[4] <= io_sda;
4'd4: iic_reg_addr[3] <= io_sda;
4'd5: iic_reg_addr[2] <= io_sda;
4'd6: iic_reg_addr[1] <= io_sda;
4'd7: iic_reg_addr[0] <= io_sda;
default: ;
endcase
end
else cstate <= ADD2_1;
end
ACK2_1: begin
sda_r <= 1'b0; //回复ACK;
sda_link <= 1'b1; //sda :output
if(`SCL_NEG)begin
sda_r <= 1'b1; //
sda_link <= 1'b0; //sda :input
if(rd_or_wr)
cstate <= START2;
else
cstate <= DATA;
end
else
cstate <= ACK2_1;
end
START2: begin //读操作起始位
if(i_scl==1 && io_sda_fall==1) begin //起始信号
cstate <= ADD3;
num <= 4'd0; //num计数清零
end
else cstate <= START2; //等待起始信号
end
ADD3: begin //接收读操作地址
if(`SCL_NEG && num==8)begin
num <= 4'd0; //num计数清零
sda_r <= 1'b1;
sda_link <= 1'b0; //sda置为高阻态(input)
cstate <= ACK3;
end
else if(`SCL_NEG)begin
cstate <= ADD3;
num <= num+1'b1;
end
else if(`SCL_HIG) begin
case (num)
4'd0: ;
4'd1: rec_device_id[7] <= io_sda;
4'd2: rec_device_id[6] <= io_sda;
4'd3: rec_device_id[5] <= io_sda;
4'd4: rec_device_id[4] <= io_sda;
4'd5: rec_device_id[3] <= io_sda;
4'd6: rec_device_id[2] <= io_sda;
4'd7: rec_device_id[1] <= io_sda;
4'd8: rec_device_id[0] <= io_sda;
default: ;
endcase
cstate <= ADD3;
end
else cstate <= ADD3;
end
ACK3: begin
sda_r <= 1'b0; //回复ACK;
sda_link <= 1'b1; //sda:output
if(`SCL_NEG)begin
if(rd_or_wr==0)
sda_link <= 1'b0; //sda:intput
else
sda_link <= 1'b1; //sda:intput
sda_r <= 1'b1; //回复ACK结束;
cstate <= DATA;
end
// if(i_scl==1 && io_sda_rise==1) begin
// cstate <= DATA;
// end
end
DATA: begin
if(rd_or_wr) begin //读操作 输出
sda_link <= 1'b1; //sda:output
if(`SCL_NEG && num==7)begin
num <= 4'd0; //num计数清零
sda_r <= 1'b1;
sda_link <= 1'b0; //sda 0:intput 1:outpu
cstate <= ACK4;
end
else if(`SCL_NEG)begin
cstate <= DATA;
num <= num+1'b1;
end
else if(`SCL_LOW) begin
case (num)
4'd0: sda_r <= read_back_reg[7];
4'd1: sda_r <= read_back_reg[6];
4'd2: sda_r <= read_back_reg[5];
4'd3: sda_r <= read_back_reg[4];
4'd4: sda_r <= read_back_reg[3];
4'd5: sda_r <= read_back_reg[2];
4'd6: sda_r <= read_back_reg[1];
4'd7: sda_r <= read_back_reg[0];
default: ;
endcase
cstate <= DATA;
end
end
else begin //写操作 输入
sda_link <= 1'b0;
if(`SCL_NEG && num==7)begin
num <= 4'd0; //num计数清零
sda_r <= 1'b1;
sda_link <= 1'b0; //sda置为高阻态(input)
cstate <= ACK4;
end
else if(`SCL_NEG)begin
cstate <= DATA;
num <= num+1'b1;
end
else if(`SCL_HIG) begin
case (num)
4'd0: wr_data[7] <= io_sda;
4'd1: wr_data[6] <= io_sda;
4'd2: wr_data[5] <= io_sda;
4'd3: wr_data[4] <= io_sda;
4'd4: wr_data[3] <= io_sda;
4'd5: wr_data[2] <= io_sda;
4'd6: wr_data[1] <= io_sda;
4'd7: wr_data[0] <= io_sda;
default: ;
endcase
cstate <= DATA;
end
end
end
ACK4: begin
if(rd_or_wr)begin
sda_r <= 1'b1; //回复ACK;
sda_link <= 1'b1; //sda output
end
else begin
sda_r <= 1'b0; //回复 NOACK;
sda_link <= 1'b1; //sda output
end
if(`SCL_NEG)begin
sda_r <= 1'b1; //回复ACK结束;
sda_link <= 1'b0; //sda input
cstate <= STOP1;
end
end
STOP1: begin
if(io_sda_rise==1) begin
iic_busy <= 1'b0;
cstate <= IDLE;
end
end
default: cstate <= IDLE;
endcase
end
assign io_sda = sda_link ? sda_r:1'bz;
reg wr_en ;
reg rd_en ;
always @ (posedge clk or negedge rst_n)
if(!rst_n)
rd_en <= 0;
else if(cstate==DATA && rd_or_wr)
rd_en <= 1;
else
rd_en <= 0;
always @ (posedge clk or negedge rst_n)
if(!rst_n)
wr_en <= 0;
else if(cstate==ACK4 && rd_or_wr==0)
wr_en <= 1;
else
wr_en <= 0;
always @ (posedge clk or negedge rst_n)
if(!rst_n)
read_back_reg <= 0;
else
read_back_reg <= rd_data;
AT24C512B
#(
.REGADDR_WIDTH (16 ),
.DATA_WIDTH (8 )
)
AT24C512B_inst
(
.clk (clk ), //
.rst_n (rst_n ), //active-low
.iic_reg_addr (iic_reg_addr ),
.wr_data_en (wr_en ),
.wr_data (wr_data ),
.rd_en (rd_en ),
.rd_data_en (rd_data_en ),
.rd_data (rd_data )
);
endmodule
4.AT24C512B模型
AT24C512B模型用于模拟AT24C512B EEPROM存储空间读写接口,init_memory.txt可以用于第一次仿真读写时初始化(模拟EEPROM器件初始值为0XFF),我们仿真时可以使用读写函数从memory.txt文件读写。
//*****************************************************************************
//
// Author : NAZ
// Version : 1.0
// Filename : AT24C512B.v
// Date Last Modified : $Date: 2021/12/08 $
// Date Created : $Date: 2021/11/04 $
// Design Name : AT24C512B
//
// Describe :
// This module is used for simulation,when IIC read data ,Read the data in memory.txt
// when when IIC write data,write the data to memory.txt
//
//*****************************************************************************
`include "macro_define.v"
`timescale 1ns/1ns
module AT24C512B
#(
parameter REGADDR_WIDTH = 16,
parameter DATA_WIDTH = 8
)
(
input wire clk , //
input wire rst_n , //active-low
input wire[REGADDR_WIDTH-1:0] iic_reg_addr,
input wire wr_data_en ,
input wire[7:0] wr_data ,
input wire rd_en ,
output wire rd_data_en ,
output reg[7:0] rd_data=0
);
/******************reg/wire********************/
reg [DATA_WIDTH-1:0] register[2**REGADDR_WIDTH-1:0];
// init memory, AT24C512B initial values are 0xff
`ifdef FIRST_SIM
initial begin
$readmemh("../sim/init_memory.txt",register);
end
`else
initial begin
$readmemh("../src/memory.txt",register);
end
`endif
//write the register
always @(posedge clk)
if(wr_data_en)
register[iic_reg_addr] <= wr_data;
//write the register values to memory.txt
always@(posedge clk)
if(wr_data_en)
$writememh("../src/memory.txt",register);
//when iic read data
always @(posedge clk)begin
if(rd_en)
rd_data <= register[iic_reg_addr];
end
endmodule
5.初始化文件和读写文件
init_memory.txt文件为初始化文件(内容为按一定格式的0XFF文件),memory.txt文件为读写文件(为仿真后生成文件)。如有需要可联系博主(992716773@qq
.com)。
6.仿真顶层文件
仿真顶层文件可以用于读写具体的地址,将IIC读写方式使用task方式写为函数的方式,方便不同地址读写,并且在读写时打印读写信息。
//*****************************************************************************
//
// Author : NAZ
// Version : 1.0
// Filename : iic_master_tb.v
// Date Last Modified : $Date: 2021/12/08 $
// Date Created : $Date: 2021/10/05 $
// Design Name : iic_master_tb
//
// Describe :
// This module is used for IIC simulation excitation
//
//*****************************************************************************"
`timescale 1ns/1ns
`include "macro_define.v"
module iic_master_tb();
reg clk ;
reg rst_n ;
// 与控制器通信信号
reg iic_en ;
reg rd_or_wr ;
reg[6:0] device_addr=7'h50 ;
reg[15:0] iic_reg_addr ;
reg[7:0] iic_reg_data ;
wire[7:0] read_data ;
wire iic_done ;
wire iic_busy ;
// 外部信号
wire scl ;
wire sda ;
//***************************************************
// testcase
// 时钟
always
begin
#10 clk = ~clk;
end
// 初始化
initial
begin
clk = 0;
rst_n = 0;
iic_en <= 1'b0;
rd_or_wr <= 0;
#100;
rst_n = 1;
#1000;
iic_write(7'h50,16'h0000,32'hAA);
#100
iic_read(7'h50,16'h0002);
#100
iic_write(7'h50,16'h0001,32'h01);
#100
iic_write(7'h50,16'h0002,32'h02);
#100
iic_write(7'h50,16'h0003,32'h03);
#100
iic_write(7'h50,16'h0004,32'h55);
#100
iic_read(7'h50,16'h0004);
#100
iic_read(7'h50,16'h1000);
#100
iic_read(7'h50,16'h1234);
$stop;
end
task iic_write(
input [6:0] iic_device_id,
input [15:0] iic_addr,
input [31:0] iic_data
);
begin
rd_or_wr = 1'b0;
device_addr = iic_device_id;
iic_reg_addr = iic_addr;
iic_reg_data = iic_data;
#100
iic_en <= 1;
#100
iic_en = 0;
#1400000;
$display("current simulation time is %t,write addr is 16'h%h,write data is 8'h%h.",$time,iic_reg_addr,iic_reg_data);
end
endtask
task iic_read(
input [6:0] iic_device_id,
input [15:0] iic_addr
);
begin
rd_or_wr = 1'b1;
device_addr = iic_device_id;
iic_reg_addr = iic_addr;
#100
iic_en <= 1;
#100
iic_en = 0;
#1400000;
$display("current simulation time is %t,read addr is 16'h%h,read data is 8'h%h.",$time,iic_reg_addr,read_data);
end
endtask
//***************************************************
// 例化顶层文件
iic_master
#(
.IIC_RATE ("100k" ), //50k,100k or 400k
.REGADDR_WIDTH (16 )
)
iic_master_inst
(
.clk (clk ), //50MHz主时钟
.rst_n (rst_n ), //复位信号
.scl (scl ), //IIC时钟
.sda (sda ), //IIC数据
.iic_start (iic_en ), //IIC使能
.rd_or_wr (rd_or_wr ), //读写标志:读1,写0
.iic_device_id (device_addr ), //IIC器件地址
.iic_reg_addr (iic_reg_addr ), //寄存器地址
.iic_wr_data (iic_reg_data ), //寄存器数据
.iic_rd_data_en (),
.iic_rd_data (read_data ), //接收数据
.iic_done (iic_done ), //IIC完成标志
.iic_busy (iic_busy ) //IIC忙
);
`ifdef SIM_MODEL
iic_sim_model
#(
.REGADDR_WIDTH(16)
)
iic_sim_model_inst
(
.clk (clk ), //50Mhz
.rst_n (rst_n ), //active-low
.i_scl (scl ), //为输入
.io_sda (sda )
);
`endif
endmodule
在初始化阶段,初始化函数初始化AT24C512B内存储空间为全0XFF。
继续运行仿真,可以看到打印信息,写入和读取的值,以读取地址0x0002为例,在未向该地址写入时,返回的读取值为FF(初始值),在写入0x0004后再读取该地址,返回读取值与写入一致,其他地址可以自己定义验证。对应的memory地址空间在写入值后,内存空间随之改变。对应的memory.txt文件随着写入的值同时更新。
若有相关问题可以互相讨论