通过eeprom验证FPGA实现的单字节/页读写IIC接口时序

news2024/12/31 5:42:10

1、概括

  前文设计基于FPGA的IIC接口模块,本文将使用eeprom来验证该模块的设计。为了便于查看读写波形,采用两个按键来控制对eeprom数据的读写,当按键0按下后,FPGA向eeprom的前64个存储地址写入地址对应的数据,当按键1按下后,FPGA从eeprom的前64个存储地址读取数据。

  该eeprom的原理图如下所示,其中A2、A1、A0表示该eeprom器件地址的低3位,器件地址的高4位固定为4’b1010。wp是写保护引脚,该引脚为低电平才能向eeprom中写入数据。24c02只能存储256字节数据,所以存储地址只有一个字节的数据。

在这里插入图片描述

图1 24c02原理图

  该eeprom的单字节读写时序如下图所示:

在这里插入图片描述

图2 单字节写时序

在这里插入图片描述

图3 单字节读时序

  该eeprom的页读写时序如下所示:

在这里插入图片描述

图4 页写时序

在这里插入图片描述

图5 页读时序

  就是标准的IIC时序,只需要调用前文实现的IIC接口模块即可。

  顶层模块的对应的RTL图如下所示,包含两个按键消抖模块对两路按键输入信号消抖,at24c02模块对IIC接口模块的读写进行控制,iic_drive模块驱动IIC时序。

在这里插入图片描述

图6 顶层模块RTL视图

  顶层模块对应的参考代码如下所示:

module top #(
    parameter   TIME_20MS           =       20_000_000  ,//按键消抖时间,默认20MS;
    parameter   TIME_CLK            =       10          ,//系统时钟周期,默认10ns;
    parameter   FCLK                =       100_000_000 ,//系统时钟频率,默认100MHz。
    parameter   FSCL                =       250_000     ,//IIC时钟频率,默认400KHz。
    parameter   REG_ADDR_BYTE_NUM   =       1           ,//寄存器地址字节数,最小值为1;
    parameter	DATA_BYTE_NUM       =       1		    ,//读写数据字节数,最小值为1.
    parameter   DELAY_TIME          =       10_000_000   //延迟时间,10ms.    
)(
    input									clk		    ,//系统时钟信号;
    input									rst_n	    ,//系统复位信号,低电平有效;

    input       [1 : 0]	                    key		    ,//按键输入信号;
    output                                  led         ,
    output                                  scl         ,
    inout                                   sda         
);
    wire        [1 : 0]                     key_out     ;
    wire                                    start       ;
    wire                                    rw_flag     ;
    wire        [REG_ADDR_BYTE_NUM*8-1 : 0] reg_addr    ;   
    wire        [DATA_BYTE_NUM*8-1 : 0]     wdata       ;
    wire        [DATA_BYTE_NUM*8-1 : 0]     rdata       ;
    wire                                    rdata_vld   ;
    wire                                    rdy         ;
    wire                                    ack_flag    ;

    genvar                                  i           ;

    //例化两个按键消抖模块,对输入的两路按键信号进行消抖;
    generate
        for(i=0 ; i<2 ; i=i+1)begin : KEY_C
            key #(
                .TIME_20MS  ( TIME_20MS ),
                .TIME_CLK   ( TIME_CLK  )
            )
            u_key (
                .clk        ( clk       ),//系统时钟,100MHz。
                .rst_n      ( rst_n     ),//系统复位,低电平有效。
                .key_in     ( key[i]    ),//待输入的按键输入信号,默认低电平有效;
                .key_out    ( key_out[i]) //按键消抖后输出信号,当按键按下一次时,输出一个时钟宽度的高电平;
            );
        end
    endgenerate

    //例化eeprom的读写控制模块;
    at2402  #(
        .TCLK       ( TIME_CLK  ),//系统时钟周期,10ns.
        .DELAY_TIME ( DELAY_TIME),//延迟时间,10ms.
        .REG_ADDR_BYTE_NUM  ( REG_ADDR_BYTE_NUM ),//寄存器地址字节数;
        .DATA_BYTE_NUM      ( DATA_BYTE_NUM     ) //读写数据字节数。 
    )
    u_at2402 (
        .clk        ( clk       ),//系统时钟信号;
        .rst_n      ( rst_n     ),//系统复位信号,低电平有效;
        .key        ( key_out   ),//按键输入,分别控制写入和读取eeprom。
        .rdy        ( rdy       ),//iic控制器忙闲指示信号,高电平表示空闲。
        .rdata      ( rdata     ),//从IIC读出的数据;
        .rdata_vld  ( rdata_vld ),//IIC读出数据指示信号;
        .start      ( start     ),//开始进行读写操作信号;
        .rw_flag    ( rw_flag   ),//读写指示信号,高电平表示进行读操作;
        .reg_addr   ( reg_addr  ),//寄存器地址;
        .wdata      ( wdata     ),//需要写入寄存器的数据;
        .led        ( led       ) //错误指示灯。
    );
    

    //例化IIC接口驱动模块;
    iic_drive #(
        .FCLK               ( FCLK              ),//系统时钟频率,默认100MHz。
        .FSCL               ( FSCL              ),//IIC时钟频率,默认400KHz。
        .REG_ADDR_BYTE_NUM  ( REG_ADDR_BYTE_NUM ),//寄存器地址字节数;
        .DATA_BYTE_NUM      ( DATA_BYTE_NUM     ) //读写数据字节数。
    )
    u_iic_drive (
        .clk        ( clk           ),//系统时钟信号;
        .rst_n      ( rst_n         ),//系统复位信号,低电平有效;
        .start      ( start         ),//开始进行读写操作;
        .rw_flag    ( rw_flag       ),//读写标志信号,高电平表示读操作,低电平表示写操作;
        .reg_addr   ( reg_addr      ),//寄存器地址,读写操作时共用的地址信号;
        .wdata      ( wdata         ),//写数据;
        .rdata      ( rdata         ),//读数据信号;
        .rdata_vld  ( rdata_vld     ),//读数据输出使能信号,高电平有效;
        .rdy        ( rdy           ),//模块忙闲指示信号,位高电平时可以接收上游模块的读写使能信号;
        .scl        ( scl           ),//IIC的时钟信号;
        .ack_flag   ( ack_flag      ),//高电平表示应答失败;
        .sda        ( sda           ) //IIC的双向数据信号;
    );

    //例化ILA调试模块
    //ila_0 u_ila_0 (
    //    .clk        ( clk                       ),//input wire clk
    //    .probe0     ( scl                       ),//input wire [0:0] probe0  
    //    .probe1     ( u_iic_drive.sda_in        ),//input wire [0:0] probe1 
    //    .probe2     ( u_iic_drive.state_c       ),//input wire [6:0] probe2 
    //    .probe3     ( u_iic_drive.ack_flag      ),//input wire [0:0] probe3
    //    .probe4     ( u_iic_drive.l2h_flag      ),//input wire [0:0] probe4 
    //    .probe5     ( u_iic_drive.h2l_flag      ),//input wire [0:0] probe5
    //    .probe6     ( u_iic_drive.wr_flag       ),//input wire [0:0] probe6 
    //    .probe7     ( u_iic_drive.rd_flag       ),//input wire [0:0] probe7
    //    .probe8     ( u_iic_drive.end_div_cnt   ),//input wire [0:0] probe8
    //    .probe9     ( u_iic_drive.rw_flag       ),//input wire [0:0] probe9 
    //    .probe10    ( u_iic_drive.sda_out       ),//input wire [0:0] probe10 
    //    .probe11    ( u_iic_drive.state_n       ),//input wire [6:0] probe11 
    //    .probe12    ( u_iic_drive.reg_addr      ),//input wire [7:0] probe12 
    //    .probe13    ( u_iic_drive.bit_cnt       ),//input wire [3:0] probe13
    //    .probe14    ( u_iic_drive.bit_cnt_num   ),//input wire [3:0] probe14
    //    .probe15    ( u_iic_drive.div_cnt       ),//input wire [8:0] probe15
    //    .probe16    ( u_iic_drive.start         ),//input wire [0:0] probe16
    //    .probe17    ( u_iic_drive.rdata         ),//input wire [7:0] probe17
    //    .probe18    ( u_iic_drive.rdata_vld     ),//input wire [0:0] probe18
    //    .probe19    ( u_iic_drive.sda_out_en    ),//input wire [0:0] probe19
    //    .probe20    ( u_iic_drive.rdy           ) //input wire [0:0] probe20
    //);
    
endmodule

  当写入寄存器地址长度和读写数据长度为1字节,对应的TestBench文件如下所示:

`timescale 1 ns/1 ns
module test();
    localparam	CYCLE		        =   10          ;//系统时钟周期,单位ns,默认10ns;
    localparam	RST_TIME	        =   10          ;//系统复位持续时间,默认10个系统时钟周期;
    localparam  FCLK                =   100_000_000 ;//系统时钟频率,默认100MHz。
    localparam  FSCL                =   400_000     ;//IIC时钟频率,默认400KHz。
    localparam	TIME_20MS	        =   2000*CYCLE  ;
    localparam  DELAY_TIME          =   80000       ;

    reg			                        clk         ;//系统时钟,默认100MHz;
    reg			                        rst_n       ;//系统复位,默认低电平有效;
    reg         [1 : 0]                 key         ;

    wire                                led         ;
    wire                                scl         ;
    wire                                sda         ;

    top #(
        .TIME_20MS  ( TIME_20MS ),
        .TIME_CLK   ( CYCLE     ),
        .DELAY_TIME ( DELAY_TIME),
        .FCLK       ( FCLK      ),
        .FSCL       ( FSCL      )
    )
    u_top (
        .clk        ( clk       ),
        .rst_n      ( rst_n     ),
        .key        ( key       ),
        .led        ( led       ),
        .scl        ( scl       ),
        .sda        ( sda       )
    );

    eeprom u_eeprom0 (
        .scl    ( scl   ),
        .sda    ( sda   )
    );

    //生成周期为CYCLE数值的系统时钟;
    initial begin
        clk = 0;
        forever #(CYCLE/2) clk = ~clk;
    end

    //生成复位信号;
    initial begin
        #1; rst_n <= 1'b1; key <= 2'b11;
        #2; rst_n <= 0;//开始时复位10个时钟;
        repeat(RST_TIME)@(posedge clk);
        rst_n <= 1'b1;
        repeat(3)@(posedge clk);
        key_task(0);
        repeat(1300000)@(posedge clk);
        key_task(1);
        repeat(1300000)@(posedge clk);
        repeat(100)@(posedge clk);
        $stop;//停止仿真;
    end

    task key_task(
        input	[1 : 0]		value	
    );
        begin
            key[value] <= 1'b1;
            @(posedge clk);
            key[value] <= 1'b0;
            repeat(7)begin
                repeat({$random} % (TIME_20MS/2))@(posedge clk);
                key[value] <= ~key[value];
            end
            key[value] <= 1'b0;
            repeat(TIME_20MS*2)@(posedge clk);
            repeat(7)begin
                key[value] <= ~key[value];
                repeat({$random} % (TIME_20MS/2))@(posedge clk);
            end
            key[value] <= 1'b1;
        end
    endtask
    
endmodule

  其中使用的eeprom仿真模型是在网上找到的,这个文件只支持读写数据长度为1字节,需要的同学可以研究下。

`timescale 1ns / 1ps
`define timeslice 100
module eeprom (
	input 						scl			,
	inout 						sda
);
	reg 						out_flag	;
	reg 		[7 : 0]	memory	[2047 : 0]	;
	reg			[10 : 0] 		address		;
	reg			[7 : 0] 		memory_buf	;
	reg 		[7 : 0] 		sda_buf		;
	reg 		[7 : 0] 		shift		;
	reg 		[7 : 0] 		addr_byte	;
	reg 		[7 : 0] 		ctrl_byte	;
	reg 		[1 : 0] 		State		;
	integer 					i			;

	// -------------------------------------
	parameter 	r7 	= 			8'b10101111	;
	parameter	r6 	= 			8'b10101101	;
	parameter	r5 	= 			8'b10101011	;
	parameter	r4 	= 			8'b10101001	;
	parameter	r3 	= 			8'b10100111	;
	parameter	r2 	= 			8'b10100101	;
	parameter	r1 	= 			8'b10100011	;
	parameter	r0 	= 			8'b10100001	;
	parameter 	w7 	= 			8'b10101110	;
	parameter	w6 	= 			8'b10101100	;
	parameter	w5 	= 			8'b10101010	;
	parameter	w4 	= 			8'b10101000	;
	parameter	w3 	= 			8'b10100110	;
	parameter	w2 	= 			8'b10100100	;
	parameter	w1 	= 			8'b10100010	;
	parameter	w0 	= 			8'b10100000	;
	//--------------------------------------

	assign sda= (out_flag == 1) ? sda_buf[7] : 1'bz;

	//--------------寄存器和存储器初始化--------------
	initial begin
		addr_byte = 0;
		ctrl_byte = 0;
		out_flag = 0;
		sda_buf = 0;
		State = 2'b00;
		memory_buf = 0;
		address = 0;
		shift = 0;
		for(i = 0 ; i <= 2047 ; i = i + 1)
			memory[i] = 0;
	end

	//--------------启动信号检测--------------
	always @(negedge sda)
		if(scl == 1)begin
			State = State + 1;
			if(State == 2'b11)
			disable write_to_eeprm;
		end
	//--------------主状态机--------------
	always @(posedge sda)
		if(scl == 1)
			stop_W_R;
		else begin
		casex(State)  
			2'b01 : begin
				read_in;
				if(ctrl_byte == w7||ctrl_byte == w6|| ctrl_byte == w5 || ctrl_byte == w4 || ctrl_byte == w3 || ctrl_byte == w2 ||ctrl_byte == w1 ||ctrl_byte == w0)begin
					State = 2'b10;
					write_to_eeprm;
				end
				else 
					State = 2'b00;
			end
			2'b11 :
				read_from_eeprm;
			default : State = 2'b00;
		endcase
	end

	//--------------操作停止--------------
	task stop_W_R;
		begin
			State = 2'b00;
			addr_byte = 0;
			ctrl_byte = 0;
			out_flag = 0;
			sda_buf = 0;
		end
	endtask

	//--------------读进控制字和存储单元地址--------------
	task read_in;
		begin
			shift_in(ctrl_byte);
			shift_in(addr_byte);
		end
	endtask

	//--------------EEPROM--------------
	task write_to_eeprm;
		begin
			shift_in(memory_buf);
			address = {ctrl_byte[3:1],addr_byte};
			memory[address] = memory_buf;
			$display("eeprm---memory[%0h]=%0h",address,memory[address]);
			State = 2'b00;
		end
	endtask
	
	//--------------EEPROM读操作--------------
	task read_from_eeprm;
		begin
			shift_in(ctrl_byte);
			if(ctrl_byte == r7 || ctrl_byte == r6 || ctrl_byte == r5 || ctrl_byte == r4 || ctrl_byte == r3 || ctrl_byte == r2 || ctrl_byte == r1 || ctrl_byte == r0)begin
				address = {ctrl_byte[3:1],addr_byte};
				sda_buf = memory [address];
				shift_out;
				State = 2'b00;
			end
		end
	endtask
	
	//--------------SDA 数据线上的数据存入寄存器 ,数据在SCL的高电平有效--------------
	task shift_in;
	output[7:0] shift;
		begin
			@(posedge scl) shift[7] = sda;
			@(posedge scl) shift[6] = sda;
			@(posedge scl) shift[5] = sda;
			@(posedge scl) shift[4] = sda;
			@(posedge scl) shift[3] = sda;
			@(posedge scl) shift[2] = sda;
			@(posedge scl) shift[1] = sda;
			@(posedge scl) shift[0] = sda;
			@(negedge scl)begin
				#`timeslice;
				out_flag = 1;
				sda_buf = 0;
			end
			@(negedge scl)
			#`timeslice out_flag = 0;
		end
	endtask
	//--------------EEPROM存储器中的数据通过SDA数据线输出,数据在SCL低电平时变化--------------
	task shift_out;
	begin
		out_flag = 1;
		for(i = 6 ; i >= 0 ; i = i - 1)begin
			@(negedge scl);
			# `timeslice;
			sda_buf = sda_buf << 1;
		end
		@(negedge scl) # `timeslice sda_buf[7] = 1;
		@(negedge scl) # `timeslice out_flag = 0;
	end
	endtask

endmodule

2、eeprom控制模块

  该模块就是检测按键按下,然后产生eeprom的读写开始信号,驱动iic模块生成对应读写时序。当按键0按下时,将写eeprom标志信号wr_flag拉高,由于两次写操作需要间隔一段时间,使用一个计数器来计数这段时间,当时间到达后把iic驱动模块的读写开始信号拉高,并且把寄存器地址和需要写入的数据输出给该模块。还需要一个计数器来记录写入多少个数据了,当数据全部写入eeprom之后,把wr_flag拉低。

  按键1按下后,将读eeprom的标志信号rd_flag拉高,这里为了后续比较号使用ila抓取信号,两次读操作也加了写操作一样的延时。就是把写操作的寄存器全部都读一遍。

  将读出的数据与写入数据进行对比,如果不一致则led输出低电平,否则输出高电平。这个其实没有必要,因为这里采用ila抓取信号可以更加直观。

  该模块的代码比较简单,就不再详细讲解,主要的参考代码如下所示:

    //产生读指示信号,初始值为低电平,按键1按下后拉高,读出指定地址数据后拉低;
    always@(posedge clk or negedge rst_n)begin
        if(rst_n==1'b0)begin//初始值为0;
            rd_flag <= 1'b0;
        end
        else if(end_cnt)begin//数据全部读出后拉低;
            rd_flag <= 1'b0;
        end
        else if(key[1])begin//按键1按下时拉高;
            rd_flag <= 1'b1;
        end
    end

    //产生写指示信号,初始值为低电平,按下按键0后拉高,写入指定数据后拉高;
    always@(posedge clk or negedge rst_n)begin
        if(rst_n==1'b0)begin//初始值为0;
            wr_flag <= 1'b0;
        end
        else if(end_cnt)begin//写入规定数据后拉低;
            wr_flag <= 1'b0;
        end
        else if(key[0])begin//按键0按下后拉高;
            wr_flag <= 1'b1;
        end
    end

    //延时计数器,每次写操作结束后,都需要延时一段时间,确保写入的数据被稳定存入eeprom内部;
    always@(posedge clk or negedge rst_n)begin
        if(rst_n==1'b0)begin//
            cnt_r <= 0;
        end
        else if(add_cnt_r)begin
            if(end_cnt_r)
                cnt_r <= 0;
            else
                cnt_r <= cnt_r + 1;
        end
    end
    
    assign add_cnt_r = (wr_flag || rd_flag) && rdy;//当一次写操作结束时,开始对系统时钟计数;
    assign end_cnt_r = add_cnt_r && cnt_r == DELAY_CNT - 1;
    
    //计数器cnt用来记录每次按下按键后需要读写的数据个数;
    always@(posedge clk or negedge rst_n)begin
        if(rst_n==1'b0)begin//
            cnt <= 0;
        end
        else if(add_cnt)begin
            if(end_cnt)
                cnt <= 0;
            else
                cnt <= cnt + DATA_BYTE_NUM;
        end
    end
    
    assign add_cnt = end_cnt_r;
    assign end_cnt = add_cnt && cnt >= 64-1;

    //如果读标志有效,则该信号拉高,表示进行读操作,否则默认进行写操作;
    always@(posedge clk)begin
        rw_flag <= rd_flag;
    end

    //生成读写的开始信号,还有读写操作的寄存器地址和需要写入的数据信号;
    always@(posedge clk or negedge rst_n)begin
        if(rst_n==1'b0)begin//初始值为0;
            reg_addr <= 0;
            start <= 1'b0;
        end
        else if(add_cnt)begin
            reg_addr <= cnt;
            start <= 1'b1;
        end
        else begin
            start <= 1'b0;
        end
    end

    //一次写入EEPROM内部的数据;
    generate
        if(DATA_BYTE_NUM == 1)begin//每次发送1字节数据;
            always@(posedge clk or negedge rst_n)begin
                if(rst_n==1'b0)begin
                    led <= 1'b0;
                    wdata <= 0;
                end
                else if(add_cnt)
                    wdata <= cnt;
                else
                    led <= (cnt == rdata) && rdata_vld;
            end
        end
        else if(DATA_BYTE_NUM == 2)begin//每次发送2字节数据;
            always@(posedge clk or negedge rst_n)begin
                if(rst_n==1'b0)begin
                    led <= 1'b1;
                    wdata <= 0;
                end
                else if(add_cnt)
                    wdata <= {cnt[7:0],(cnt[7:0]+8'd1)};
                else
                    led <= ({cnt[7:0],(cnt[7:0]+8'd1)} == rdata) && rdata_vld;
            end
        end
        else if(DATA_BYTE_NUM == 3)begin//每次发送3字节数据;
            always@(posedge clk or negedge rst_n)begin
                if(rst_n==1'b0)begin
                    led <= 1'b1;
                    wdata <= 0;
                end
                else if(add_cnt)
                    wdata <= {cnt[7:0],(cnt[7:0]+8'd1),(cnt[7:0]+8'd2)};
                else
                    led <= ({cnt[7:0],(cnt[7:0]+8'd1),(cnt[7:0]+8'd2)} == rdata) && rdata_vld;
            end
        end
        else if(DATA_BYTE_NUM == 4)begin//每次发送4字节数据;
            always@(posedge clk or negedge rst_n)begin
                if(rst_n==1'b0)begin
                    led <= 1'b1;
                    wdata <= 0;
                end
                else if(add_cnt)
                    wdata <= {cnt[7:0],(cnt[7:0]+8'd1),(cnt[7:0]+8'd2),(cnt[7:0]+8'd3)};
                else
                    led <= ({cnt[7:0],(cnt[7:0]+8'd1),(cnt[7:0]+8'd2),(cnt[7:0]+8'd3)} == rdata) && rdata_vld;
            end
        end
        else if(DATA_BYTE_NUM == 5)begin//每次发送5字节数据;
            always@(posedge clk or negedge rst_n)begin
                if(rst_n==1'b0)begin
                    led <= 1'b1;
                    wdata <= 0;
                end
                else if(add_cnt)
                    wdata <= {cnt[7:0],(cnt[7:0]+8'd1),(cnt[7:0]+8'd2),(cnt[7:0]+8'd3),(cnt[7:0]+8'd4)};
                else
                    led <= ({cnt[7:0],(cnt[7:0]+8'd1),(cnt[7:0]+8'd2),(cnt[7:0]+8'd3),(cnt[7:0]+8'd4)} == rdata) && rdata_vld;
            end
        end
        else if(DATA_BYTE_NUM == 6)begin//每次发送6字节数据;
            always@(posedge clk or negedge rst_n)begin
                if(rst_n==1'b0)begin
                    led <= 1'b1;
                    wdata <= 0;
                end
                else if(add_cnt)
                    wdata <= {cnt[7:0],(cnt[7:0]+8'd1),(cnt[7:0]+8'd2),(cnt[7:0]+8'd3),(cnt[7:0]+8'd4),(cnt[7:0]+8'd5)};
                else
                    led <= ({cnt[7:0],(cnt[7:0]+8'd1),(cnt[7:0]+8'd2),(cnt[7:0]+8'd3),(cnt[7:0]+8'd4),(cnt[7:0]+8'd5)} == rdata) && rdata_vld;
            end
        end
        else if(DATA_BYTE_NUM == 7)begin//每次发送7字节数据;
            always@(posedge clk or negedge rst_n)begin
                if(rst_n==1'b0)begin
                    led <= 1'b1;
                    wdata <= 0;
                end
                else if(add_cnt)
                    wdata <= {cnt[7:0],(cnt[7:0]+8'd1),(cnt[7:0]+8'd2),(cnt[7:0]+8'd3),(cnt[7:0]+8'd4),(cnt[7:0]+8'd5),(cnt[7:0]+8'd6)};
                else
                    led <= ({cnt[7:0],(cnt[7:0]+8'd1),(cnt[7:0]+8'd2),(cnt[7:0]+8'd3),(cnt[7:0]+8'd4),(cnt[7:0]+8'd5),(cnt[7:0]+8'd6)} == rdata) && rdata_vld;
            end
        end
        else begin//每次发送8字节数据,此处最多写了一次写入或者读出8字节数据的程序,如果想要一次从EEPROM中写入更多数据,则需要将这个数据拼接更多位进行测试。
            always@(posedge clk or negedge rst_n)begin
                if(rst_n==1'b0)begin
                    led <= 1'b1;
                    wdata <= 0;
                end
                else if(add_cnt)
                    wdata <= {cnt[7:0],(cnt[7:0]+8'd1),(cnt[7:0]+8'd2),(cnt[7:0]+8'd3),(cnt[7:0]+8'd4),(cnt[7:0]+8'd5),(cnt[7:0]+8'd6),(cnt[7:0]+8'd7)};
                else
                    led <= ({cnt[7:0],(cnt[7:0]+8'd1),(cnt[7:0]+8'd2),(cnt[7:0]+8'd3),(cnt[7:0]+8'd4),(cnt[7:0]+8'd5),(cnt[7:0]+8'd6),(cnt[7:0]+8'd7)} == rdata) && rdata_vld;
            end
        end
    endgenerate

3、模块仿真

  前文的按键消抖模块、IIC驱动模块都是使用前文详细讲解且开源的模块,此处不再赘述。直接对本工程进行仿真。

  首先将顶层模块的写寄存器地址字节数、读写数据字节数均设置为1。使用vivado的仿真如下图所示:

在这里插入图片描述

图7 单字节读写仿真时序

  把上图中写数据的一段时序放大后如下图所示,首先发送起始位,然后写入器件地址和写指示位,之后应答位。然后发送写寄存器地址26,之后写入数据26,最后发送停止位。完成向地址26中写入26的时序。

在这里插入图片描述

图8 向地址26中写入26

  下图是读出地址为20的时序,首先发送起始位、器件地址、写寄存器地址,之后发送重复起始位,然后器件地址,之后就读取数据,最后发送停止位。图中蓝色信号就是三态门使能信号,该信号为低电平时关闭三态门,主机释放数据总线,从机驱动数据总线。

在这里插入图片描述

图9 从地址20中读出20

  从地址20中读出的数据是20,由此验证写入和读出数据均没有问题。

  接下来对页写和页读时序进行仿真,由于这个at2402的仿真模型只支持单字节的读写时序,所以多字节超出的部分从机不会应答,此处只是借助仿真查看下读写时序是否与预期一致即可,具体验证可以在后文的ILA抓取信号处验证。

  将顶层模块的读写数据字节数的参数DATA_BYTE_NUM设置为3,就是每次读写3个地址的数据,然后运行仿真,结果如下所示。相比图7的读写时间将大幅度减小。

在这里插入图片描述

图10 页读写仿真时序

  将上图中写入寄存器数据的一段仿真时序放大,如下图所示,向寄存器地址为30开始的三个寄存器中分别写入数据30、31、32。仿真时序没有问题,从机没有应答是因为仿真模型不支持这种模式,没有关系。

在这里插入图片描述

图11 向地址30、31、32中写入30、31、32

  将上述读数据时序放大,如下图所示,从连续读出45、46、47地址处的数据。前面的时序都没有问题,主要关注在读数据阶段主机应答信号产生是否正确,主机在接收前两字节数据后都将数据线拉低应答,接收最后一字节数据后将数据线拉高,不应答从机然后发送停止位,由此仿真时序没有问题。

在这里插入图片描述

图12 从地址45、46、47中读出45、46、47

  然后就是验证一下寄存器地址多字节的IIC读、写时序,将顶层模块的寄存器地址长度REG_ADDR_BYTE_NUM参数设置为3,读、写数据的长度DATA_BYTE_NUM设置为1,然后运行仿真,总体仿真结果如下图所示。

在这里插入图片描述

图13 多字节寄存器地址读写仿真时序

  将上图中写数据的部分时序放大,如下图所示,向寄存器地址24’d12中写入12。连续输入三字节地址数据后,输入需要写入的数据,注意先写高字节数据的高位。

在这里插入图片描述

图14 多字节地址的写时序

  将图13中读时序放大,结果如下所示,寄存器地址包含3字节数据,读取24’d35中的数据,3字节寄存器地址发送完毕后,发送重复起始位,之后依次发送器件地址,之后读数据,最后发送停止位。

在这里插入图片描述

图15 多字节地址的读时序

  多字节数据的读写时序与前文的eeprom页读写时序是一样的,因此就不再单独仿真了。仿真到此结束,由于仿真模型的问题,后续几个仿真只能查看读写的时序是否符合要求,不能通过读写的数据得到仿真结果,如果有兴趣可以试着修改仿真模型,达到要求。

4、上板测试

  上面已经对仿真做了仿真,本文采用的eeprom的寄存器地址只有一个字节,所以不能对多字节的地址读写进行验证,但是通过前文仿真结果也应该不会有问题了。

  首先抓取单个存储地址数据的读写时序,把顶层模块的写寄存器地址长度参数(REG_ADDR_BYTE_NUM)设置为1字节,读写数据长度的参数(DATA_BYTE_NUM)也设置为1字节。然后对工程综合、实现,查看该工程的资源消耗如下图所示,消耗89个LUT资源和65个触发器资源。

在这里插入图片描述

图16 资源消耗

  将bit文件下载到开发板中,ILA将start高电平作为触发条件。按下按键0抓取的信号如下图所示:

在这里插入图片描述

图17 ILA抓取单字节写时序

  将上图中的一帧数据放大,如下图所示,向地址5中写入5,对应的时序与前文仿真的时序基本一致,这里的sda_in就是iic数据总线的状态。

在这里插入图片描述

图18 ILA抓取写入地址5的时序

  按下按键1,FPGA读取eeprom前64个地址的数据,ILA抓取的时序如下所示。

在这里插入图片描述

图19 ILA抓取单字节读时序

  将上图中的一帧数据放大,如下图所示,从地址5中读出数据,最后读出的数据为5,证明前面写入和本次读出的数据均正常。

在这里插入图片描述

图20 ILA抓取读出地址5的时序

  然后对页读写时序进行测试,把顶层模块的读写数据长度的参数(DATA_BYTE_NUM)设置为5字节,然后对工程进行综合、实现,查看该工程消耗的资源。当读写数据为5字节时IIC驱动模块消耗135个LUT、160个触发器资源。

在这里插入图片描述

图21 资源消耗

  然后按下按键0,抓取的信号如下图所示:

在这里插入图片描述

图22 抓取页写时序

  将上述的写时序放大,结果如下图所示,向寄存器地址5、6、7、8、9中分别写入5、6、7、8、9。ILA抓取的时序与前文仿真结果基本一致,首先写入第一字节数据5之后,继续发送下一字节数据6,直到写入5字节数据后,才发送停止位结束写入时序。

在这里插入图片描述

图23 连续写入5字节数据时序

  按下按键1,ILA抓取读时序如下图所示:

在这里插入图片描述

图24 抓取页读时序

  将上述的读时序放大,结果如下图所示,向寄存器地址10开始连续读取5字节的数据。由于ILA抓取信号的位宽就设置成了8位,所以下图ILA输出的信号就只显示了最后输出的那一字节数据,实际上是包含5字节数据的,只是ILA显示不出来,需要修改IP位宽。

在这里插入图片描述

图25 从地址10连续读出5字节数据

  上图中紫色信号是IIC数据总线,天蓝色信号是IIC时钟信号,黄色信号是主机的数据输出使能,低电平表示主机释放数据总线。读取的第一字节就是10,然后依次是11、12、13、14。由此连续读出了地址10、11、12、13、14的数据。

  证明多字节的IIC读写时序正常,没有问题,也就验证了IIC接口模块设计无误。

  至此,IIC接口模块的设计和验证就将清楚了,如果不理解的可以留言,接口时序电路的设计首先都要查看官方技术手册,然后再去查找具体芯片对该接口时序的要求。要特别注意该接口的时钟频率、读写间隔时间、建立时间、保持时间,像这种数据再时钟中部赋值的低速串行接口时序,建立时间和保持时间一般是不会存在问题的。

  最后如果要获取本设计的工程文件,在后台回复“基于FPGA的eeprom读写工程”(不包括引号)即可,工程中ILA默认是被注释掉的。IIC接口模块已经全部做了参数化处理,使用时直接例化修改parameter参数即可,不需要额外修改其余任何代码。

  您的支持是我更新的最大动力!将持续更新工程,如果本文对您有帮助,还请多多点赞👍、评论💬和收藏⭐!

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

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

相关文章

文件上传漏洞--Upload-labs--Pass20--数组绕过

一、漏洞原理 漏洞来源&#xff1a;count()函数漏洞。 现自定义一个数组 arr[]&#xff0c;定义arr[0]1,arr[3]2, 此时count(arr)的值为2&#xff0c;则arr[count[arr]]即为arr[2]&#xff0c;但是arr[2]未定义&#xff0c;即为一个空值&#xff0c;若使用count()函数的本意是…

大数据计算技术秘史(上篇)

在之前的文章《2024 年&#xff0c;一个大数据从业者决定……》《存储技术背后的那些事儿》中&#xff0c;我们粗略地回顾了大数据领域的存储技术。在解决了「数据怎么存」之后&#xff0c;下一步就是解决「数据怎么用」的问题。 其实在大数据技术兴起之前&#xff0c;对于用户…

【TCP/IP】组播

一、组播介绍 组播&#xff08;Multicast&#xff09;是网络技术中数据传输的一种方法&#xff0c;它允许将数据包同时发送给一组指定的目标&#xff0c;而不是单个的目标&#xff08;单播 Unicast&#xff09;或所有可能的目标&#xff08;广播 Broadcast&#xff09;。组播传…

Python的自定义函数

Python的自定义函数 自定义函数的作用匿名函数语法示例 自定义函数语法示例 自定义函数的作用 定制化需求降低代码重复编写 匿名函数 匿名函数&#xff0c;可以用lambda关键字定义。通过lambda构造的函数可以没有名称&#xff0c;即在自定义匿名函数时&#xff0c;所有代码可…

【智能家居】7、主程序编写+实现语音、网络和串口功能

需要毕业论文私信有偿获取 截止目前mainPro.c代码 #include <stdio.h> #include <string.h>#include "controlDevices.h" #include "inputCmd.h"struct Devices *findDevicesName(char *name,struct Devices *phead){struct Devices *tmp=ph…

得物面试:Kafka消息0丢失,如何实现?

得物面试&#xff1a;Kafka消息0丢失&#xff0c;如何实现&#xff1f; 尼恩说在前面 在40岁老架构师 尼恩的读者交流群(50)中&#xff0c;最近有小伙伴拿到了一线互联网企业如得物、阿里、滴滴、极兔、有赞、希音、百度、网易、美团的面试资格&#xff0c;遇到很多很重要的面…

更改WordPress作者存档链接author和Slug插件Edit Author Slug

WordPress默认所有用户的存档永久链接都是/author/username/&#xff0c;不管是管理员还是订阅者或贡献者或作者或编辑。如果你想要自定义用户存档链接&#xff0c;比如根据角色不同使用不一样的author&#xff0c;或者自定义作者链接中的用户名Slug&#xff0c;那么建议考虑使…

Win32 获取EXE/DLL文件版本信息

CFileVersion.h #pragma once#include <windows.h> #include <string> #include <tchar.h>#ifdef _UNICODE using _tstring std::wstring; #else using _tstring std::string; #endif// 版本号辅助类 class CVersionNumber { public:// 无参构造CVersionN…

uniapp实现全局悬浮框

uniapp实现全局悬浮框(按钮,页面,图片自行设置) 可拖动 话不多说直接上干货 1,在components新建组件(省去了每个页面都要引用组件的麻烦) 2,实现代码 <template><view class"call-plate" :style"top: top px;left: left px;" touchmove&quo…

探索Redis是否为单线程的奥秘(文末送书)

&#x1f308;个人主页&#xff1a;聆风吟 &#x1f525;系列专栏&#xff1a;数据结构、网络奇遇记 &#x1f516;少年有梦不应止于心动&#xff0c;更要付诸行动。 文章目录 &#x1f4cb;前言一. Redis中的多线程二. I/O多线程三. Redis中的多进程四. 结论五. 书籍推荐5.1 书…

OpenLayers水平镜像和垂直镜像

目录 1.前言2.概念介绍2.1 旋转2.2 水平镜像2.3 垂直镜像 3.要素的镜像3.1 镜像轴始终是水平的或者垂直的3.2 镜像轴是任意角度 4.图片的镜像5.总结 1.前言 最近项目中用到了要素和图片的水平镜像和垂直镜像功能。这些功能说难不难&#xff0c;说简单也不简单&#xff0c;就是稍…

【SQL注入】靶场SQLI DUMB SERIES-24通过二次注入重置用户密码

先使用已知信息admin/admin登录进去查下题&#xff0c;发现可以修改密码 猜测可能存在的SQL语句&#xff1a;UPDATE user SET password新密码 WHERE user用户名 and password旧密码 假设我们知道有个admin用户&#xff0c;但是不知道其密码&#xff0c;如何可以将其密码重置&…

[AIGC] 使用Curl进行网络请求的常见用法

使用Curl进行网络请求的常见用法 Curl是一个无比强大的工具&#xff0c;它可以用来获取和发送数据&#xff0c;支持众多的协议&#xff0c;包括HTTP、HTTPS、FTP、FTPS、SFTP和更多。它还支持HTTP POST&#xff0c;HTTP PUT&#xff0c;HTTPS证书&#xff0c;HTTP基础验证等。…

【2024软件测试面试必会技能】Postman(1): postman的介绍和安装

Postman的介绍 Postman 是一款谷歌开发的接口测试工具,使API的调试与测试更加便捷。 它提供功能强大的 Web API & HTTP 请求调试。它能够发送任何类型的HTTP 请求 (GET, HEAD, POST, PUT..)&#xff0c;附带任何数量的参数 headers。 postman是一款支持http协议的接口调试…

流动人员人事档案主要有哪些作用

流动人员人事档案是指记录企事业单位与个人之间的雇佣关系的文件。在企事业单位中&#xff0c;流动人员是指临时聘用的员工、实习生、临时工等&#xff0c;他们的雇佣关系相对不稳定&#xff0c;因此需要建立相应的人事档案来管理和记录他们的基本信息、工作经历、劳动合同等重…

unity学习(34)——角色选取界面(跨场景坑多)

先把SelectMenu中的camera的audio listener去掉。 现在还是平面&#xff0c;直接在camera下面添加两个panel即可&#xff0c;应该是用不到canvas了&#xff0c;都是2D的UI。 加完以后问题来了&#xff0c;角色选择界面的按钮跑到主界面上边了&#xff0c;而且现在账号密码都输…

机器人内部传感器阅读笔记及心得-位置传感器-电位器式位置传感器

位置传感器 位置感觉是机器人最基本的感觉要求&#xff0c;可以通过多种传感器来实现。位置传感器包括位置和角度检测传感器。常用的机器人位置传感器有电位器式、光电式、电感式、电容式、霍尔元件式、磁栅式及机械式位置传感器等。机器人各关节和连杆的运动定位精度要求、重…

C语言自定义类型:结构体的使用及其内存对齐【超详细建议点赞收藏】

目录 1. 结构体类型的声明1.1 结构的声明1.2 结构体变量的创建和初始化1.3 结构的特殊声明---匿名结构体1.4 结构的自引用 2.结构体内存对齐&#xff08;重点&#xff01;&#xff01;&#xff09;2.1 对齐规则2.2 例题讲解2.3 为什么存在内存对齐&#xff1f;2.4 修改默认对齐…

vue3前端excel导出;组件表格,自定义表格导出;Vue3 + xlsx + xlsx-style

当画面有自定义的表格或者样式过于复杂的表格时&#xff0c;导出功能可以由前端实现 1. 使用的插件 &#xff1a; sheet.js-xlsx 文档地址&#xff1a;https://docs.sheetjs.com/ 中文地址&#xff1a;https://geekdaxue.co/read/SheetJS-docs-zh/README.md xlsx-style&#…

⭐北邮复试刷题LCR 052. 递增顺序搜索树__DFS (力扣119经典题变种挑战)

LCR 052. 递增顺序搜索树 给你一棵二叉搜索树&#xff0c;请 按中序遍历 将其重新排列为一棵递增顺序搜索树&#xff0c;使树中最左边的节点成为树的根节点&#xff0c;并且每个节点没有左子节点&#xff0c;只有一个右子节点。 示例 1&#xff1a; 输入&#xff1a;root [5,…