IIC 通信协议 (二)

news2024/11/25 21:33:37


目录

引言

子模块设计

思路

单字节 IIC 发送模块

思路

Verilog 源码

多字节发送控制模块

思路

Verilog 源码

仿真

思路

test bench

仿真结果

 

参考声明


 


引言

本篇博文承接前文,继续做 IIC 通信协议 FPGA实现相关的内容。用Verilog 编写一个 IIC 通信控制器,最后用 Microchip公司提供的 IIC 驱动器件的 Verilog 模型 辅助完成 仿真验证。



子模块设计

思路

此处的思路主要是将IIC复杂的通信步骤拆解,因为对器件进行一次读或者写操作,都需要如下步骤:

  1. 发送起始信号,征用总线;
  2. 发送器件ID,选择通信的从机;
  3. 发送要操作的寄存器地址;
  4. 发送/接收要写入/读出寄存器的数据;

因此为了简化设计的复杂度,将设计分为两大模块:

  1. 单字节发送模块
  2. 多字节发送控制模块

单字节 IIC 发送模块

思路

拟采用状态机的方法进行设计。此处仅对状态转换条件进行简述。更为详细的理解需要仔细阅读设计代码。

状态声明:

LP_ST_IDLE:空闲状态         
LP_ST_GEN_START:产生起始信号    
LP_ST_WR_DATA:写数据    
LP_ST_RD_DATA :读数据   
LP_ST_GEN_ACK :产生应答    
LP_ST_CHECK_ACK :检查器件发出的应答   
LP_ST_GEN_STOP:产生停止信号,释放总线

状态转移说明:

LP_ST_IDLE:

如果操作使能信号有效,依次判断 写起始信号,写数据,读数据哪个命令有效(有优先级)  如果对应的命令有效则跳转到相应的状态中。
LP_ST_GEN_START:

产生起始信号;产生完毕后,根据输入命令继续判断是否需要写/读寄存器,如果需要则跳转到对应的状态。
LP_ST_WR_DATA: 

写入数据;写数据完成后,检查从机是否给出响应;  
LP_ST_RD_DATA :

读出数据;读数据完毕后,根据命令给出响应;   
LP_ST_GEN_ACK:

产生应答;产生结束后,判断是否有结束的命令,如果有则进入结束状态。     
LP_ST_CHECK_ACK:

检查应答;检查结束后,判断是否有结束的命令,如果有则进入结束状态。         
LP_ST_GEN_STOP:

产生结束信号;产生完毕后,进入空闲状态;

此IIC发送接收的时序由计数器控制 发送 1比特数据计数器增加 4 .          

Verilog 源码

// | ===================================================---------------------------===================================================
// | ---------------------------------------------------     IIC 控制器 模块	   ---------------------------------------------------
// | ===================================================---------------------------===================================================
// | 创建时间 : 2022-12-24
// | 完成时间 : 2022-12-24
// | 作    者 :Xu Y. B.(CSDN 用户名:在路上,正出发)
// | 功能说明 :
// |			-1- IIC 通信速率支持:100KHz、400KHz、3400KHz
// |			-2- 
// | 			-3- 
// |
// | ================================= 		模块修改历史纪录 	  =================================
// | 修改日期:
// | 修改作者:
// | 修改注解:

module IIC_CTRL_MDL #(
// | ====================================  模块可重载参数声明  ==================================== 
parameter 				P_SYS_CLK_FREQ		=			32'd50_000_000,// 系统时钟频率 单位:Hz
parameter				P_IIC_CLK_FREQ 		=			32'd400_000    // IIC 时钟频率 单位:Hz
)(
// | ==================================== 模块输入输出端口声明 ==================================== 
input 												   	I_SYS_CLK   ,
input												   	I_SYS_RSTN  ,

input 			[5:0]									I_CMD 	    ,
input 													I_OPR_EN    ,
input 			[7:0]									I_TX_DATA   ,
output 	reg		[7:0]									O_RX_DATA   ,

output 	reg												O_OPR_DONE  ,
output 	reg												O_ACK       ,

output 	reg												O_IIC_SCLK  ,
inout   	 											IO_IIC_SDAT
    );


// | ====================================   模块内部参数声明   ====================================
// 状态编码 
localparam 				LP_ST_IDLE 			=			7'b0000_001;
localparam 				LP_ST_GEN_START		=			7'b0000_010;
localparam 				LP_ST_WR_DATA		=			7'b0000_100;
localparam 				LP_ST_RD_DATA		=			7'b0001_000;
localparam 				LP_ST_GEN_ACK 		=			7'b0010_000;
localparam 				LP_ST_CHECK_ACK		=			7'b0100_000;
localparam 				LP_ST_GEN_STOP 		=			7'b1000_000;

// 命令字编码
localparam				LP_CMD_WR_REQ		=			6'b000_001;
localparam 				LP_CMD_RD_REQ		=			6'b000_010;
localparam 				LP_CMD_START_REQ    =			6'b000_100;
localparam				LP_CMD_STOP_REQ		=			6'b001_000;
localparam 				LP_CMD_ACK_REQ 		=			6'b010_000;
localparam 				LP_CMD_NOACK_REQ	=			6'b100_000; 	

// IIC 时钟分频计数器最大值
localparam				LP_DIV_FREQ_CNT_MAX	=			P_SYS_CLK_FREQ/P_IIC_CLK_FREQ/4 ;

// | ====================================   模块内部信号声明   ====================================
// 状态
reg 					[6:0]							R_STATE;
// 计数器
reg 				[$clog2(LP_DIV_FREQ_CNT_MAX)-1:0]	R_DIV_FREQ_CNT;
reg														R_DIV_FREQ_CNT_EN;
reg 					[4:0]							R_BIT_CNT;

// IIC输出数据线
reg 													R_SDAT_VAL;
reg 													R_SDAT;

// SCLK 脉冲(频率为 IIC 时钟 4 倍)
wire 													W_SCLK_PULSE_4;


// | ====================================   模块内部逻辑设计   ====================================
always @ (posedge I_SYS_CLK)
begin
	if(~I_SYS_RSTN)
	begin
		R_DIV_FREQ_CNT <= {($clog2(LP_DIV_FREQ_CNT_MAX)){1'b0}};
	end
	else if(R_DIV_FREQ_CNT_EN)
	begin
		if(R_DIV_FREQ_CNT == LP_DIV_FREQ_CNT_MAX-1)
		begin
			R_DIV_FREQ_CNT <= {($clog2(LP_DIV_FREQ_CNT_MAX)){1'b0}};
		end
		else
		begin
			R_DIV_FREQ_CNT <= R_DIV_FREQ_CNT + 1;
		end 
	end
	else
	begin
		R_DIV_FREQ_CNT <= {($clog2(LP_DIV_FREQ_CNT_MAX)){1'b0}};
	end
end

assign W_SCLK_PULSE_4 = (R_DIV_FREQ_CNT == LP_DIV_FREQ_CNT_MAX-1);

assign IO_IIC_SDAT    = (R_SDAT_VAL & !R_SDAT) ? 1'b0 : 1'bz;

always @ (posedge I_SYS_CLK)
begin
	if(~I_SYS_RSTN)
	begin
		R_STATE    		  <= LP_ST_IDLE;
		O_RX_DATA  		  <= 8'd0;
		O_OPR_DONE 		  <= 1'd0;
		O_ACK      		  <= 1'd0;
		O_IIC_SCLK 		  <= 1'd1;
		R_DIV_FREQ_CNT_EN <= 1'd0;
		R_BIT_CNT		  <= 5'd0;
		R_SDAT_VAL	      <= 1'd0;
		R_SDAT 		      <= 1'd1;
	end
	else
	begin
		case(R_STATE)
			LP_ST_IDLE :
			begin
				if(I_OPR_EN)
				begin
					R_DIV_FREQ_CNT_EN <= 1'b1;

					if(I_CMD & LP_CMD_START_REQ)
					begin
						R_STATE <= LP_ST_GEN_START;	
					end
					else if(I_CMD & LP_CMD_WR_REQ)
					begin
						R_STATE <= LP_ST_WR_DATA; 
					end
					else if(I_CMD & LP_CMD_RD_REQ)
					begin
						R_STATE <= LP_ST_RD_DATA;
					end
					else
					begin
						R_STATE <= LP_ST_IDLE;
					end
				end
				else
				begin
					R_STATE    <= LP_ST_IDLE;
					R_DIV_FREQ_CNT_EN <= 1'b0;
				end
	
				R_BIT_CNT		  <= 5'd0;
				O_ACK      		  <= 1'd0;
				O_OPR_DONE        <= 1'b0;
			end
			LP_ST_GEN_START	:
			begin
				if(W_SCLK_PULSE_4)
				begin
					if(R_BIT_CNT == 3)
					begin
						R_BIT_CNT <= 0;
					end
					else
					begin
						R_BIT_CNT <= R_BIT_CNT + 1;
					end
				

					R_SDAT_VAL <= 1'b1;

					case(R_BIT_CNT)
						0:begin 	  R_SDAT <= 1'b1;O_IIC_SCLK <= 1'b1;end
						1:begin 	  R_SDAT <= 1'b1;O_IIC_SCLK <= 1'b1;end
						2:begin 	  R_SDAT <= 1'b0;O_IIC_SCLK <= 1'b1;end
						3:begin 	  R_SDAT <= 1'b0;O_IIC_SCLK <= 1'b0;end
						default:begin R_SDAT <= 1'b1;O_IIC_SCLK <= 1'b1;end
					endcase

					if(R_BIT_CNT == 3)
					begin
						if(I_CMD & LP_CMD_WR_REQ)
						begin
							R_STATE <= LP_ST_WR_DATA;
						end
						else if(I_CMD & LP_CMD_RD_REQ)
						begin
							R_STATE <= LP_ST_RD_DATA;
						end
						else
						begin
							R_STATE <= LP_ST_GEN_START;
						end
					end
				end
			end
			LP_ST_WR_DATA	:
			begin
				if(W_SCLK_PULSE_4)
				begin
					if(&R_BIT_CNT)
					begin
						R_BIT_CNT <= 5'd0;
					end
					else
					begin
						R_BIT_CNT <= R_BIT_CNT + 1;
					end

					R_SDAT_VAL <= 1'b1;

					case(R_BIT_CNT)
						0,4,8,12,16,20,24,28 :begin	R_SDAT <= I_TX_DATA[3'd7-R_BIT_CNT[4:2]]; O_IIC_SCLK <= 1'b0;end 
						1,5,9,13,17,21,25,29 :begin	R_SDAT <= R_SDAT;O_IIC_SCLK <= 1'b1;end 
						2,6,10,14,18,22,26,30:begin	R_SDAT <= R_SDAT;O_IIC_SCLK <= 1'b1;end
						3,7,11,15,19,23,27,31:begin	R_SDAT <= R_SDAT;O_IIC_SCLK <= 1'b0;end
						default:			  begin	R_SDAT <= 1'b1;  O_IIC_SCLK <= 1'b1;end
					endcase

					if(&R_BIT_CNT)
					begin
						R_STATE <= LP_ST_CHECK_ACK;
					end
				end
			end
			LP_ST_RD_DATA	:
			begin
				if(W_SCLK_PULSE_4)
				begin
					if(&R_BIT_CNT)
					begin
						R_BIT_CNT <= 5'd0;
					end
					else
					begin
						R_BIT_CNT <= R_BIT_CNT + 1;
					end

					R_SDAT_VAL <= 1'b0;

					case(R_BIT_CNT)
						0,4,8,12,16,20,24,28 :begin	O_IIC_SCLK <= 1'b0;end 
						1,5,9,13,17,21,25,29 :begin	O_IIC_SCLK <= 1'b1;end 
						2,6,10,14,18,22,26,30:begin	O_RX_DATA <= {O_RX_DATA[6:0],IO_IIC_SDAT};O_IIC_SCLK <= 1'b1;end
						3,7,11,15,19,23,27,31:begin	O_IIC_SCLK <= 1'b0;end
						default:begin	O_IIC_SCLK <= 1'b1;end
					endcase

					if(&R_BIT_CNT)
					begin
						R_STATE <= LP_ST_GEN_ACK;
					end
				end
			end
			LP_ST_GEN_ACK 	:
			begin
				if(W_SCLK_PULSE_4)
				begin
					if(R_BIT_CNT == 3)
					begin
						R_BIT_CNT <= 0;
					end
					else
					begin
						R_BIT_CNT <= R_BIT_CNT + 1;
					end

					R_SDAT_VAL <= 1'b1;

					case(R_BIT_CNT)
						0:
						begin
							if(I_CMD & LP_CMD_ACK_REQ)
						 		R_SDAT <= 1'b0;
						 	else if(I_CMD & LP_CMD_NOACK_REQ)
						 		R_SDAT <= 1'b1;
						 	O_IIC_SCLK <= 1'b0;
						end
						1:begin 	  R_SDAT <= 1'b1;O_IIC_SCLK <= 1'b1;end
						2:begin 	  R_SDAT <= 1'b0;O_IIC_SCLK <= 1'b1;end
						3:begin 	  R_SDAT <= 1'b0;O_IIC_SCLK <= 1'b0;end
						default:begin R_SDAT <= 1'b1;O_IIC_SCLK <= 1'b1;end
					endcase

					if(R_BIT_CNT == 3)
					begin
						if(I_CMD & LP_CMD_STOP_REQ)
						begin
							R_STATE <= LP_ST_GEN_STOP;
						end
						else
						begin
							R_STATE <= LP_ST_IDLE;
							O_OPR_DONE <= 1'b1;
						end
					end
				end
			end
			LP_ST_CHECK_ACK	:
			begin
				if(W_SCLK_PULSE_4)
				begin
					if(R_BIT_CNT == 3)
					begin
						R_BIT_CNT <= 0;
					end
					else
					begin
						R_BIT_CNT <= R_BIT_CNT + 1;
					end

					R_SDAT_VAL <= 1'b0;

					case(R_BIT_CNT)
						0:begin	 O_IIC_SCLK <= 1'b0;O_ACK <= 1'b0;end
						1:begin	 O_IIC_SCLK <= 1'b1;O_ACK <= 1'b0;end
						2:begin	 O_IIC_SCLK <= 1'b1;O_ACK <= IO_IIC_SDAT;end
						3:begin	 O_IIC_SCLK <= 1'b0;O_ACK <= O_ACK;end
						default:begin  O_IIC_SCLK <= 1'b1;O_ACK <= 1'b0;end
					endcase

					if(R_BIT_CNT == 3)
					begin
						if(I_CMD & LP_CMD_STOP_REQ)
						begin
							R_STATE <= LP_ST_GEN_STOP;
						end
						else
						begin
							R_STATE <= LP_ST_IDLE;
							O_OPR_DONE <= 1'b1;
						end
					end
				end
			end
			LP_ST_GEN_STOP 	:
			begin
				if(W_SCLK_PULSE_4)
				begin
					if(R_BIT_CNT == 3)
					begin
						R_BIT_CNT <= 0;
					end
					else
					begin
						R_BIT_CNT <= R_BIT_CNT + 1;
					end

					R_SDAT_VAL <= 1'b1;

					case(R_BIT_CNT)
						0:begin	 O_IIC_SCLK <= 1'b0; R_SDAT<= 1'b0;end
						1:begin	 O_IIC_SCLK <= 1'b1; R_SDAT<= 1'b0;end
						2:begin	 O_IIC_SCLK <= 1'b1; R_SDAT<= 1'b1;end
						3:begin	 O_IIC_SCLK <= 1'b1; R_SDAT<= 1'b1;end
						default:begin  O_IIC_SCLK <= 1'b1; R_SDAT<= 1'b1;end
					endcase

					if(R_BIT_CNT == 3)
					begin
						R_STATE <= LP_ST_IDLE;
						O_OPR_DONE <= 1'b1;
					end
				end
			end			
		endcase
	end
end


endmodule

多字节发送控制模块

思路

同样是利用状态机实现 IIC 读写;将读和写分别分为三步:

  1. 发起读/写(给写地址,写数据等)
  2. 等待读/写完成
  3. 读/写完成

每个读/写的状态内,利用计数器确定发送/读出操作步骤的先后顺序。因为每次读写过程都包含下面几个步骤:

  1. 发送起始信号,征用总线;
  2. 发送器件ID,选择通信的从机;
  3. 发送要操作的寄存器地址;
  4. 发送/接收要写入/读出寄存器的数据;

Verilog 源码

// | ===================================================---------------------------===================================================
// | ---------------------------------------------------     IIC 通信顶层 模块	   ---------------------------------------------------
// | ===================================================---------------------------===================================================
// | 创建时间 : 2022-12-24
// | 完成时间 : 2022-12-24
// | 作    者 :Xu Y. B.(CSDN 用户名:在路上,正出发)
// | 功能说明 :
// |			-1- IIC 通信速率支持:100KHz、400KHz、3400KHz
// |			-2- 
// | 			-3- 
// |
// | ================================= 		模块修改历史纪录 	  =================================
// | 修改日期:
// | 修改作者:
// | 修改注解:

module TOP_IIC_MDL#(
// | ====================================  模块可重载参数声明  ==================================== 
parameter 				P_SYS_CLK_FREQ		=			32'd50_000_000,// 系统时钟频率 单位:Hz
parameter				P_IIC_CLK_FREQ 		=			32'd400_000    // IIC 时钟频率 单位:Hz
)(
// | ==================================== 模块输入输出端口声明 ==================================== 
input 												   	I_SYS_CLK   ,
input												   	I_SYS_RSTN  ,

input 													I_WR_REQ,
input 													I_RD_REQ,

input 					[15:0]							I_ADDR,
input 													I_ADDR_MODE,// 1:16位地址;0:8位地址

input 					[7:0]							I_WR_DATA,
output 		reg			[7:0]							O_RX_DATA,

input 					[7:0]							I_DEVICE_ID,

output		reg											O_RW_DONE,
output 		reg 										O_ACK,    

output 													O_IIC_SCLK,
inout 													IO_IIC_SDAT
    );

// | ====================================   模块内部参数声明   ====================================
// 命令字编码
localparam				LP_CMD_WR_REQ		=			6'b000_001;
localparam 				LP_CMD_RD_REQ		=			6'b000_010;
localparam 				LP_CMD_START_REQ    =			6'b000_100;
localparam				LP_CMD_STOP_REQ		=			6'b001_000;
localparam 				LP_CMD_ACK_REQ 		=			6'b010_000;
localparam 				LP_CMD_NOACK_REQ	=			6'b100_000; 

// 状态编码
localparam				LP_ST_IDLE 			=			7'b0000_001;
localparam				LP_ST_WR			=			7'b0000_010;
localparam 				LP_ST_WAIT_WR_DONE	=			7'b0000_100;
localparam				LP_ST_WR_DONE  		=			7'b0001_000;
localparam				LP_ST_RD			=			7'b0010_000;
localparam 				LP_ST_WAIT_RD_DONE	=			7'b0100_000;
localparam				LP_ST_RD_DONE  		=			7'b1000_000;

// | ====================================   模块内部信号声明   ====================================
reg 					[2:0]							R_CNT;
reg 					[6:0]							R_STATE;

reg 					[5:0]							R_CMD;
reg 													R_OPR_EN;
reg 					[7:0]							R_TX_DATA;
wire					[7:0]							W_RX_DATA;
wire 													W_OPR_DONE;
wire 													W_ACK;
wire 					[15:0]							W_ADDR;

// | ====================================   模块内部逻辑设计   ====================================
assign 			W_ADDR     =     I_ADDR_MODE ? I_ADDR : {I_ADDR[7:0],I_ADDR[15:8]};

always @ (posedge I_SYS_CLK)
begin
	if(~I_SYS_RSTN)
	begin
		R_CNT     <= 3'd0;
		R_STATE   <= LP_ST_IDLE;
		R_CMD     <= 6'd0;
		R_OPR_EN  <= 1'b0;
		R_TX_DATA <= 8'd0;
		O_RX_DATA <= 8'd0;
		O_RW_DONE <= 1'd0;
		O_ACK     <= 1'd0;
	end
	else
	begin
		case(R_STATE)
			LP_ST_IDLE :
			begin
				R_CNT <= 3'd0;
				O_ACK <= 1'b0;
				O_RW_DONE <= 1'b0;
				if(I_WR_REQ)
				begin
					R_STATE <= LP_ST_WR;
				end
				else if(I_RD_REQ)
				begin
					R_STATE <= LP_ST_RD;
				end
				else
				begin
					R_STATE <= LP_ST_IDLE;
				end
			end
			LP_ST_WR:
			begin
				R_STATE <= LP_ST_WAIT_WR_DONE;

				case(R_CNT)
					3'd0:
					begin
						TASK_WR_BYTE(LP_CMD_WR_REQ | LP_CMD_START_REQ,1'b1,I_DEVICE_ID);
					end
					3'd1:
					begin
						TASK_WR_BYTE(LP_CMD_WR_REQ ,1'b1,W_ADDR[15:8]);
					end
					3'd2:
					begin
						TASK_WR_BYTE(LP_CMD_WR_REQ ,1'b1,W_ADDR[7:0]);
					end
					3'd3:
					begin
						TASK_WR_BYTE(LP_CMD_WR_REQ | LP_CMD_STOP_REQ,1'b1,I_WR_DATA);
					end
					default:
					begin
					end
				endcase
			end
			LP_ST_WAIT_WR_DONE	:
			begin
				R_OPR_EN <= 1'b0;
				if(W_OPR_DONE)
				begin
					O_ACK <= O_ACK | W_ACK;

					case(R_CNT)
						3'd0:
						begin
							R_CNT <= 3'd1;
							R_STATE <= LP_ST_WR;
						end
						3'd1:
						begin
							if(I_ADDR_MODE)
							begin
								R_CNT <= 3'd2;
							end
							else
							begin
								R_CNT <= 3'd3;
							end
							R_STATE <= LP_ST_WR;
						end
						3'd2:
						begin
							R_CNT <= 3;
							R_STATE <= LP_ST_WR;
						end
						3'd3:
						begin
							R_STATE <= LP_ST_WR_DONE;
						end
						default:
						begin
							R_STATE <= LP_ST_IDLE;
						end
					endcase
				end
			end
			LP_ST_WR_DONE :
			begin
				O_RW_DONE <= 1'b1;
				R_STATE   <= LP_ST_IDLE;
			end
			LP_ST_RD:
			begin
				R_STATE <= LP_ST_WAIT_RD_DONE;

				case(R_CNT)
					3'd0:
					begin
						TASK_WR_BYTE(LP_CMD_WR_REQ | LP_CMD_START_REQ,1'b1,I_DEVICE_ID);
					end
					3'd1:
					begin
						TASK_WR_BYTE(LP_CMD_WR_REQ ,1'b1,W_ADDR[15:8]);
					end
					3'd2:
					begin
						TASK_WR_BYTE(LP_CMD_WR_REQ ,1'b1,W_ADDR[7:0]);
					end
					3'd3:
					begin
						TASK_WR_BYTE(LP_CMD_WR_REQ | LP_CMD_START_REQ,1'b1,I_DEVICE_ID | 8'd1);
					end
					3'd4:
					begin
						TASK_RD_BYTE(LP_CMD_RD_REQ | LP_CMD_NOACK_REQ | LP_CMD_STOP_REQ,1'b1);
					end
					default://不必理会
					begin
					end
				endcase
			end
			LP_ST_WAIT_RD_DONE	:
			begin
				R_OPR_EN <= 1'b0;
				if(W_OPR_DONE)
				begin
					if(R_CNT <= 3'd3)
					begin
						O_ACK <= O_ACK | W_ACK;
					end
					case(R_CNT)
						3'd0:
						begin
							R_CNT <= 3'd1;
							R_STATE <= LP_ST_RD;
						end
						3'd1:
						begin
							if(I_ADDR_MODE)
							begin
								R_CNT <= 3'd2;
							end
							else
							begin
								R_CNT <= 3'd3;
							end
							R_STATE <= LP_ST_RD;
						end
						3'd2:
						begin
							R_CNT <= 3'd3;
							R_STATE <= LP_ST_RD;
						end
						3'd3:
						begin
							R_CNT <= 3'd4;
							R_STATE <= LP_ST_RD;
						end
						3'd4:
						begin
							R_STATE <= LP_ST_RD_DONE;
						end
						default:
						begin
							R_STATE <= LP_ST_IDLE;
						end
					endcase
				end
			end
			LP_ST_RD_DONE :
			begin
				O_RW_DONE <= 1'b1;
				R_STATE <= LP_ST_IDLE;
				O_RX_DATA <= W_RX_DATA;
			end	
			default	:
			begin
				R_STATE <= LP_ST_IDLE;
			end		
		endcase
	end

end
// | ====================================   模块内部任务定义   ====================================
task TASK_WR_BYTE;
	input [5:0]      I_TASK_CMD;
	input 		     I_TASK_OPR_EN;
	input [7:0]	     I_TASK_WR_DATA;

	begin
		R_CMD    <= I_TASK_CMD;
		R_OPR_EN <= I_TASK_OPR_EN;
		R_TX_DATA<= I_TASK_WR_DATA; 
	end
endtask 

task TASK_RD_BYTE;
	input [5:0]      I_TASK_CMD;
	input 		     I_TASK_OPR_EN;

	begin
		R_CMD    <= I_TASK_CMD;
		R_OPR_EN <= I_TASK_OPR_EN;
	end
endtask 

// | ====================================   模块内部模块例化   ====================================
	IIC_CTRL_MDL #(
			.P_SYS_CLK_FREQ(P_SYS_CLK_FREQ),
			.P_IIC_CLK_FREQ(P_IIC_CLK_FREQ)
		) INST_IIC_CTRL_MDL (
			.I_SYS_CLK   (I_SYS_CLK),
			.I_SYS_RSTN  (I_SYS_RSTN),
			.I_CMD       (R_CMD),
			.I_OPR_EN    (R_OPR_EN),
			.I_TX_DATA   (R_TX_DATA),
			.O_RX_DATA   (W_RX_DATA),
			.O_OPR_DONE  (W_OPR_DONE),
			.O_ACK       (W_ACK),
			.O_IIC_SCLK  (O_IIC_SCLK),
			.IO_IIC_SDAT (IO_IIC_SDAT)
		);

endmodule

仿真

思路

利用和支持 IIC 协议的器件完成验证。Microchip 官网提供了多种自产器件的 Verilog 模型。网址如下:

Verilog Modelsicon-default.png?t=M85Bhttps://www.microchip.com/doclisting/TechDoc.aspx?type=Verilog此处我们选择一个支持IIC通信的存储器,

将该模型添加到设计文件中,在test bench中例化使用,具体见如下代码:

test bench

// |------------------------------ ================================== ------------------------------
// |==============================  	 IIC顶层模块——测试文件  	  ==============================
// |------------------------------ ================================== ------------------------------
// | 创建时间 : 2022-12-25
// | 完成时间 : 2022-12-25
// | 作    者 :Xu Y. B.(CSDN 用户名:在路上,正出发)
// | 功能说明 :测试功能模块:TOP_IIC_MDL
// | 			辅助模块:M24LC04B
// |
// |------------------------------------------------------------------------------------------------
// |----------------------------------------  模块修改历史  ----------------------------------------
// | 修改日期:
// | 修改作者:
// | 修改注解:

`timescale 1ns / 1ps


module TB_TOP_IIC_MDL();
// | ====================================  模块可重载参数声明  ==================================== 
parameter 				P_SYS_CLK_FREQ		=			32'd50_000_000;// 系统时钟频率 单位:Hz
parameter				P_IIC_CLK_FREQ 		=			32'd400_000   ;// IIC 时钟频率 单位:Hz

// | ==================================== 模块输入输出端口声明 ==================================== 
reg 												   	I_SYS_CLK   ;
reg												   		I_SYS_RSTN  ;

reg 													I_WR_REQ;
reg 													I_RD_REQ;

reg 					[15:0]							I_ADDR;
reg 													I_ADDR_MODE;// 1:16位地址;0:8位地址

reg 					[7:0]							I_WR_DATA;
wire 					[7:0]							O_RX_DATA;

reg 					[7:0]							I_DEVICE_ID;

wire													O_RW_DONE;
wire			 										O_ACK;    

wire 													O_IIC_SCLK;
wire 													IO_IIC_SDAT;

// 双向总线配置上拉
pullup PUP(IO_IIC_SDAT);
// | ======================================= 产生测试激励 =======================================
initial I_SYS_CLK = 1'b0;
always #10 I_SYS_CLK = ~I_SYS_CLK;

initial
begin
	I_SYS_RSTN = 1'b0;
	I_WR_REQ   = 0;
	I_RD_REQ   = 0;
	I_ADDR     = 0;
	I_ADDR_MODE= 0;
	I_WR_DATA  = 0;
	I_DEVICE_ID= 0;
	#(20*100+1);
	I_SYS_RSTN = 1;
	#(20*100);

	TASK_WR_1_BYTE(8'hA0,8'h0A,8'hD1);
	TASK_WR_1_BYTE(8'hA0,8'h0B,8'hD2);
	TASK_WR_1_BYTE(8'hA0,8'h0C,8'hD3);
	TASK_WR_1_BYTE(8'hA0,8'h0F,8'hD4);

	TASK_RD_1_BYTE(8'hA0,8'h0A);
	TASK_RD_1_BYTE(8'hA0,8'h0B);
	TASK_RD_1_BYTE(8'hA0,8'h0C);
	TASK_RD_1_BYTE(8'hA0,8'h0F);

	$finish;
end

// | ====================================   模块内部任务定义   ====================================
task TASK_WR_1_BYTE;
	input 	[7:0]	I_TASK_DEVICE_ID;
	input	[7:0]	I_TASK_ADDR;
	input 	[7:0]	I_TASK_WR_DATA;

	begin
		I_ADDR      = {8'd0,I_TASK_ADDR};
		I_DEVICE_ID = I_TASK_DEVICE_ID;
		I_ADDR_MODE = 1'b0;
		I_WR_DATA   = I_TASK_WR_DATA;
		I_WR_REQ    = 1'b1;
		#20;
		I_WR_REQ    = 1'b0;
		@(posedge O_RW_DONE);
		#200;
	end
endtask 

task TASK_RD_1_BYTE;
	input 	[7:0]	I_TASK_DEVICE_ID;
	input	[7:0]	I_TASK_ADDR;
	
	begin
		I_ADDR      = {8'd0,I_TASK_ADDR};
		I_DEVICE_ID = I_TASK_DEVICE_ID;
		I_ADDR_MODE = 1'b0;
		I_RD_REQ    = 1'b1;
		#20;
		I_RD_REQ    = 1'b0;
		@(posedge O_RW_DONE);
		#200;
	end
endtask 


// | ====================================== 待测试模块例化 ========================================
	TOP_IIC_MDL #(
			.P_SYS_CLK_FREQ(P_SYS_CLK_FREQ),
			.P_IIC_CLK_FREQ(P_IIC_CLK_FREQ)
		) INST_TOP_IIC_MDL (
			.I_SYS_CLK   (I_SYS_CLK),
			.I_SYS_RSTN  (I_SYS_RSTN),
			.I_WR_REQ    (I_WR_REQ),
			.I_RD_REQ    (I_RD_REQ),
			.I_ADDR      (I_ADDR),
			.I_ADDR_MODE (I_ADDR_MODE),
			.I_WR_DATA   (I_WR_DATA),
			.O_RX_DATA   (O_RX_DATA),
			.I_DEVICE_ID (I_DEVICE_ID),
			.O_RW_DONE   (O_RW_DONE),
			.O_ACK       (O_ACK),
			.O_IIC_SCLK  (O_IIC_SCLK),
			.IO_IIC_SDAT (IO_IIC_SDAT)
		);


	M24LC04B INST_M24LC04B 
		(
		.A0(1'b0), 
		.A1(1'b0),
		.A2(1'b0), 
		.WP(1'b0), 
		.SDA(IO_IIC_SDAT), 
		.SCL(O_IIC_SCLK), 
		.RESET(~I_SYS_RSTN)
		);

endmodule

仿真结果

 

 

参考声明

【1】芯路恒开发板教程

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/114284.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【折腾服务器 1】妖板 Intel N5105 + i226 主板安装 ESXi 7.0 教程

Catch Up 今年年初&#xff0c;开始搭建个人服务器&#xff0c;用的是一台 Dell 7010 SFF 主机&#xff0c;在上面部署了一些应用&#xff0c;例如&#xff1a;Calibre &#xff0c;Blogs &#xff0c;Minecraft Server 等。使用的是 frp 做的网络代理&#xff0c;有一台服务器…

cubeIDE开发, UART的CubeMX及HAL库实现原理及底层分析

一、UART通信协议 UART通用异步收发器(Universal Asynchronous Receiver and Transmitter)是STM32 上常用的串行通信外设&#xff0c;可以灵活地与外部设备进行全双工数据交换&#xff0c;需要注意区别&#xff1a; 【1】USART-通用同步异步收发器(Universal Synchronous Async…

<Linux线程互斥与死锁>——《Linux》

目录 1. Linux线程互斥 进程线程间的互斥相关背景概念 互斥量mutex 互斥量的接口 初始化互斥量 销毁互斥量 互斥量加锁和解锁 互斥量实现原理探究 可重入VS线程安全 概念 常见的线程不安全的情况 常见的线程安全的情况 常见不可重入的情况 常见可重入的情况 可重…

K. Lonely Numbers(线性筛 + 差分)

Problem - 1423K - Codeforces 在数字世界中&#xff0c;如果两个不同的数字有很多共同点&#xff0c;而且每个数字都有独特的好处&#xff0c;那么它们就是朋友。 更确切地说&#xff0c;如果gcd(a,b), agcd(a,b), bgcd(a,b)能组成一个三角形的边&#xff0c;那么两个不同的数…

六、应用层(四)电子邮件

目录 4.1 电子邮件系统的组成结构 4.2 简单邮件传输协议&#xff08;SMTP&#xff09; 4.3 电子邮件格式 4.4 多用途网际邮件扩充&#xff08;MIME&#xff09; 4.5 邮局协议&#xff08;POP3&#xff09;和因特网报文存取协议&#xff08;IMAP&#xff09; 4.6 基…

小黑下午第一场面试被鸽,一切遇见随缘,继续第二场的leetcode之旅:654. 最大二叉树

小黑代码 # Definition for a binary tree node. # class TreeNode: # def __init__(self, val0, leftNone, rightNone): # self.val val # self.left left # self.right right class Solution:def constructMaximumBinaryTree(self, nums: Li…

SAP UI5 应用里 FlexBox 控件的设计原理

sap.m.FlexBox 控件为 flexible box layout 构建容器。VBox 控件为垂直的框布局(vertical flexible box layout)构建容器。 VBox 是一种使用的控件&#xff0c;因为它只是一个定制化的 FlexBox 控件。 VBox 包含 items 聚合&#xff0c;从 FlexBox 继承而来。 HBox 控件为水平…

字符串函数

注意&#xff1a;MySQL中&#xff0c;字符串的位置是从1开始的。 ASCII(S) 返回字符串S中的第一个字符的ASCII码值. 与第一个字符后面的其他字符无关。 SELECTASCII(Abcdfsf) FROM DUAL;CHAR_LENGTH(s) 返回字符串s的字符数。作用与CHARACTER_LENGTH(s)相同。 SELECTCHAR_LEN…

生信基础知识

1.生物数据库分类 &#xff08;1&#xff09;核酸数据库&#xff08;2&#xff09;蛋白质数据库&#xff08;3&#xff09;专用数据库 核酸数据库分为一级核酸数据库和二级核酸数据库 蛋白质数据库分为一级蛋白质数据库和二级蛋白质数据库 一级蛋白质数据库又分为蛋白质序列…

【Redis】应用问题解决

一、缓存击穿 1、什么叫缓存击穿 系统中某个查询次数很多的热点key&#xff0c;在某个时刻过期&#xff0c;而此时又正好有大量并发请求查询这个key&#xff0c;但是缓存的重建还没有完成&#xff0c;这样&#xff0c;就会有大量请求涌向后端数据库&#xff0c;使得其压力骤增…

爱了,阿里P9开源分享内部Java核心开发手册(2022版)覆盖P5到P8

这个世界唯一不变的就是变化&#xff0c; IT圈子不外如是。计算机领域一直在改变&#xff0c;从基础框架到计算设备&#xff0c;还有几乎每天都涌现出的新技术。因此&#xff0c;作为一名程序开发人员&#xff0c;我们要通过不断的学习来提高自己的技能。 所以持续学习的脚步自…

基于C++11实现的阻塞队列(BlockQueue)

思路&#xff1a; 生产者消费者模型如图&#xff0c;多个生产者线程和多个消费者线程共享同一固定大小的缓冲区&#xff0c;它们的生产和消费符合以下规则&#xff1a; 生产者不会在缓冲区满的时候继续向缓冲区放入数据&#xff0c;而消费者也不会在缓冲区空的时候&#xff0c…

AQS源码解读

retrantlock&#xff1a; A、B、C3个线程&#xff0c;假设A线程lock()时候拿到了锁&#xff0c;state被A设置成了1。 static final class NonfairSync extends Sync {private static final long serialVersionUID 7316153563782823691L;/*** Performs lock. Try immediate b…

喜欢写笔记的博主为什么要使用猿如意?

&#x1f525;&#x1f525;&#x1f525;猿如意&#x1f525;&#x1f525;&#x1f525; 喜欢写笔记的博主为什么要使用猿如意&#xff1f; markdown笔记 测 评 分 享 猿如意实战测评猿如意传送门什么是猿如意&#xff1f;猿如意使用感受markdown笔记实战测评总结猿如意传…

数据结构---红包分配算法

红包分配算法错误解法二倍均值法JAVA实现线段切割法确定每一条子线段的长度JAVA实现问题如下&#xff1a; 所有人抢到的金额之和要等于红包金额&#xff0c;不能多也不能少。每个人至少抢到1分钱。要保证红包拆分的金额尽可能分布均衡&#xff0c;不要出现两极分化太严重的情况…

【C函数】函数详解

函数前言一、函数是什么二、C语言中函数的分类&#xff08;一&#xff09;库函数1.printf类2.strcpy类3.math类4.概念5.小知识6.总结&#xff08;二&#xff09;自定义函数1.概念2.函数的组成3.例子1&#xff08;求出两个数中的最大值&#xff09;4.例子2&#xff08;交换两个整…

mac释放“其他”内存空间的解决方法

官方解释Mac设备储存空间中的“其他”数据包含这不可移除的移动资源&#xff0c;例如&#xff0c;Siri 语音、字体、词典、不可移除的日志和缓存、聚焦索引以及系统数据如钥匙串和 CloudKit 数据库、系统无法删除缓存的文件等之外&#xff0c;还包含了一些无法识别的文件。当“…

ROS2 基础概念 节点

ROS2 基础概念 节点1. Nodes2. 重映射3. 环境设置3.1. ROS_DOMAIN_ID3.2. ROS_LOCALHOST_ONLY1. Nodes 每个节点应负责单个模块用途&#xff08;例如&#xff0c;一个节点用于控制车轮电机&#xff0c;一个用于控制激光测距仪等&#xff09; 可以通过话题、服务、操作或参数向…

C++-----模板

举个例子&#xff0c;如果要你交换两个数值&#xff0c;你会怎么做呢&#xff1f; ————你肯定会说&#xff0c;那就写一个Swap交换函数吧&#xff01; 没错&#xff01;Swap函数确实可以实现交换&#xff0c;但如果我想让你同时进行不能类型的数值呢&#xff0c;比如floa…

F - Permutation Distance(去绝对值数据结构)[AtCoder Beginner Contest 283]

题目如下&#xff1a; 题目链接 题解 or 思路&#xff1a; 去掉绝对值后 有 2242 \times 2 4224 中情况 虚线括起来的是需要维护的&#xff0c;其他直接枚举就行! 对于 pi<pjp_i < p_jpi​<pj​ 的情况&#xff0c;设我们维护的式子为 xxx 那我们每次枚举查找的范围…