FPGA常用接口协议--IIC

FPGA-IIC接口实现

    • 前言
    • IIC协议
    • EEPROM读写方式解析
    • IIC实现(verilog)
    • 仿真(modelsim)

前言

  IIC接口协议是一种比较简单、常用的一种接口协议,使用它的场景很广泛(最常见的如EEPROM读写、一些寄存器的配置等),是我们学习FPGA过程中常用的接口协议,本篇使用EEPROM(AT24C512B)器件作为目标器件进行IIC接口实现,其他器件读写方式略有差别,IIC协议不变。

IIC协议

  IIC (Inter-Integrated Circuit(集成电路总线)),百度百科解释如下:IIC-百度百科。

  这里简单概括一下协议内容:IIC(IIC,inter-Integrated circuit),两线式串行总线,用于MCU和外设间的通信;IIC只需两根线进行数据传输(数据线SDA和时钟线SCL),以半双工方式实现MCU和外设之间数据传输,常用的传输速率为100kbps、400kbps。

注意:SDA和SCL两根总线需要上拉,使总线处于空闲状态。

IIC总线系统结构如下图:
FPGA常用接口协议--IIC_第1张图片

IIC总线系统结构

IIC协议如下:
1.空闲状态
  协议规定,SDA和SCL同时为高电平时,总线处于空闲状态。上拉电阻保证电平处于高电平。
2.起始信号和结束信号
  起始信号:SCL为高电平时,SDA电平发生高到低的跳变。
  停止信号:SCL为高电平时,SDA电平发生低到高的跳变。
FPGA常用接口协议--IIC_第2张图片

3.应答信号
  发送器每发送完一个字节(8个脉冲),在第9个脉冲间释放总线,接收器返回一个ACK信号,协议规定,低电平为有效应答,高电平为无效应答。
FPGA常用接口协议--IIC_第3张图片
4.数据有效性
  协议对有效数据进行了规定:即时钟信号为高电平期间,数据必须保持稳定,时钟信号低电平期间,数据线上的电平才允许变化。也就是说,数据在时钟信号到来前必须准备好,并保持到时钟信号的下降沿之后。
FPGA常用接口协议--IIC_第4张图片
5.数据传输
  I2C为同步传输,时钟控制数据位的传输,边沿触发。

EEPROM读写方式解析

  这里只实现“字节写”和“随机读”读写方式,其他读写方式根据实际情况略作更改即可
1.字节写
  在起始信号后第一个字节写入7bit设备地址和1bit读写信号,等待从机回复一个ACK信号,第二字节写入高字节寄存器地址,等待从机回复一个ACK信号,第三字节写入低字节寄存器地址(若地址只有八位,写入一个寄存器地址即可),等待从机回复一个ACK信号,第四个字节写入要写入的数据,等待从机回复一个ACK信号,最后是一个停止信号。
FPGA常用接口协议--IIC_第5张图片
2.随机读
  在起始信号后第一个字节写入7bit设备地址和1bit读写信号,等待从机回复一个ACK信号,第二字节写入高字节寄存器地址,等待从机回复一个ACK信号,第三字节写入低字节寄存器地址(若地址只有八位,写入一个寄存器地址即可),等待从机回复一个ACK信号(前面的3个字节是一个虚写操作);这里之后需要增加一个起始信号,第四个字节写入7bit设备地址和1bit读信号,等待从机回复一个ACK信号,第五个字节从机回复当前地址的数据,之后从机再回复一个NOACK信号,最后是一个停止信号。
FPGA常用接口协议--IIC_第6张图片

IIC实现(verilog)

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

仿真(modelsim)

  在初始化阶段,初始化函数初始化AT24C512B内存储空间为全0XFF。
在这里插入图片描述
  继续运行仿真,可以看到打印信息,写入和读取的值,以读取地址0x0002为例,在未向该地址写入时,返回的读取值为FF(初始值),在写入0x0004后再读取该地址,返回读取值与写入一致,其他地址可以自己定义验证。对应的memory地址空间在写入值后,内存空间随之改变。对应的memory.txt文件随着写入的值同时更新。
FPGA常用接口协议--IIC_第7张图片
在这里插入图片描述

    若有相关问题可以互相讨论

你可能感兴趣的:(FPGA常用接口协议,verilog,fpga,嵌入式硬件)