fpga iic协议

协议本身就不做介绍了,只介绍一下编写思路,首先这个iic模块具有两个功能,对一个地址进行一个字节读,对一个地址进行一个字节写。由于iic的地址包含设备地址(7位)和寄存器地址(8或16位),因此模块对外提供参数来让用户选择采用哪种位宽的寄存器地址。即REGADDR_2BYTES=1时候采用16位,0时采用8位,而SCLK_FREQ则表示iic的时钟多少频率,而输入的i_clk是50mhz的时钟,这里写了sim是为了仿真用,可以删除

其中本模块核心的寄存器变量如上图,其中状态有如下这几个,除了IDLE,START,STOP,每个状态都会读写完一个字节后进入下一个状态

当start信号拉高,模块开始工作,此时bytecnt开始计数,这个变量和当前读写的字节有关,同时采用sclcnt开始计数,这个计数器是用来给iic做分频时钟用的,本设计采用4分频,其逻辑如下:检验sclcnt的最后两位,那么最后两位有4种数值在反复循环00,01,10,11,那么在11和01处进行SCL时钟的翻转来实现四分频,同时iic协议要求scl低电平时候变化数据,因此在00也就是图上绿色的那一时刻让bytecnt++,并根据当前bytecnt来更新sda信号线。对应代码如下:
bytecnt++:

设备地址信号更新:

寄存器地址信号更新:

要写的字节数据更新和要读的字节数据更新,同时三态线io_sda逻辑如下,后面会说:

从上面时序图可以看到还有一个scl_vld信号,这个是说明只有该信号高电平时候scl才能执行,是为了防止在IDLE,START,STOP这几个状态的时候scl开始变化而设立的,也就是说只有该信号高电平,数据传输才真正开始。

然后ack信号会在bytecnt=0的时候读取,并在bytecnt==0且sclcnt=11的时候进行状态机跳转

由于为了适应多个时钟域,因此额外定义了CLK_DIV信号让其作为四分频标志位,当其拉高时scl_cnt++

总体代码如下:

复制代码
`timescale 1ns / 1ps
// 采用字节写,随机地址读
// 字节写:    	起始位|器件地址|读ACK|寄存器地址|读ACK|数据|读ACK|停止位
// 随机地址读: 	起始位|器件地址|读ACK|寄存器地址|读ACK|停止位|起始位|器件地址|读ACK|数据|写ACK|停止位
`define sim
module I2C_Master#(
    parameter           SCLK_FREQ      = 400_000, 
    parameter   [0:0]   REGADDR_2BYTES = 0
)(
    input               i_clk    	,
    input               i_rst       ,
    input               i_start     ,
    input               i_rw_flag   ,
    input       [6:0]   i_dev_addr  ,
    input       [15:0]  i_reg_addr  ,
    input       [7:0]   i_wr_data   ,   
    inout               io_sda      ,
	`ifdef sim
	output				o_rden		,
	`endif
    output  reg         o_scl       ,
    output  reg [7:0]   o_rd_data   ,
    output  reg         o_vld       ,
	output	reg			o_err		,
    output              o_rdy       
    );
    
    localparam  S_IDLE      = 8'b00000001,
                S_START     = 8'b00000010,
                S_DADDR     = 8'b00000100,
                S_RADDR_16  = 8'b00001000,
                S_RADDR_8   = 8'b00010000,
                S_WR_DATA   = 8'b00100000,
                S_RD_DATA   = 8'b01000000,
                S_STOP      = 8'b10000000;
    
    reg         [7:0]       r_cstate    ;
    reg         [7:0]       r_nstate    ;
    
    localparam  CLK_DIV     = 50_000_000 / SCLK_FREQ / 4;
    //时钟计数器
    reg         [15:0]      r_clk_cnt   ;
    
    wire                    w_div_flag  ;
    assign  w_div_flag = r_clk_cnt >= CLK_DIV; //分频标志
    
    always@(posedge i_clk or posedge i_rst) begin
        if(i_rst)
            r_clk_cnt <= 'd0;
        else if(r_cstate == S_IDLE)
            r_clk_cnt <= 'd0;
        else if(r_clk_cnt < CLK_DIV)
            r_clk_cnt <= r_clk_cnt + 1;
        else
            r_clk_cnt <= 'd0;
    end
    
    reg         [6:0]       r_dev_addr  ;
    reg         [7:0]       r_reg_addr16;
    reg         [7:0]       r_reg_addr8 ;
    reg         [7:0]       r_wr_data   ;
    reg                     r_rw_flag   ;

    assign  o_rdy = r_cstate == S_IDLE;
    
    always@(posedge i_clk or posedge i_rst) begin
        if(i_rst) begin
            r_dev_addr  <= 'd0;
            r_reg_addr16<= 'd0;
            r_reg_addr8 <= 'd0;
            r_wr_data   <= 'd0;
            r_rw_flag   <= 'b0;
        end
        else if(r_cstate == S_IDLE & i_start) begin
            r_dev_addr  <= i_dev_addr       ;
            r_reg_addr16<= i_reg_addr[15:8] ;
            r_reg_addr8 <= i_reg_addr[7:0]  ;
            r_wr_data   <= i_wr_data        ;  
            r_rw_flag   <= i_rw_flag        ;
        end
        else begin
            r_dev_addr  <= r_dev_addr   ;
            r_reg_addr16<= r_reg_addr16 ;
            r_reg_addr8 <= r_reg_addr8  ;
            r_wr_data   <= r_wr_data    ;
            r_rw_flag   <= r_rw_flag    ;
        end
    end
    //时钟四分频计数器
    reg     [15:0]  r_scl_cnt   ;
    reg             r_scl_vld   ; 
    
    always@(posedge i_clk or posedge i_rst) begin
        if(i_rst)
            r_scl_cnt <= 'd0;
        else if(r_cstate == S_IDLE)
            r_scl_cnt <= 'd0;
        else if(r_cstate == S_STOP & (&r_scl_cnt[1:0]) & w_div_flag)//当计数到2'b11且状态为stop便清零
            r_scl_cnt <= 'd0;
        else if(w_div_flag)
            r_scl_cnt <= r_scl_cnt + 1;//四分频一次加一
        else
            r_scl_cnt <= r_scl_cnt;
    end
    //iic时钟只有在scl_vld拉高期间才能变化
    always@(posedge i_clk or posedge i_rst) begin
        if(i_rst)
            r_scl_vld <= 'b0;
        else if(r_cstate == S_STOP & r_scl_cnt[1:0] >= 2)
            r_scl_vld <= 'b0;
        else if(r_cstate == S_START & r_scl_cnt[1:0] >= 2)
            r_scl_vld <= 'b1;
        else
            r_scl_vld <= r_scl_vld;
    end
    
    always@(posedge i_clk or posedge i_rst) begin
        if(i_rst)
            o_scl <= 'b1;
        else if(r_scl_vld & r_scl_cnt[0] & r_clk_cnt == 0)//iic时钟
            o_scl <= ~o_scl;
        else
            o_scl <= o_scl;
    end
	
    reg             r_dummy_wr  ;
	
	wire			w_rw_bit	;
	
	assign	w_rw_bit = r_dummy_wr ^ r_rw_flag;
    
    always@(posedge i_clk or posedge i_rst) begin
        if(i_rst)
            r_dummy_wr <= 'b0;
        else if(r_cstate == S_IDLE & i_start)
            r_dummy_wr <= i_rw_flag;
        else if(r_cstate == S_STOP & (&r_scl_cnt[1:0]) & w_div_flag)
            r_dummy_wr <= 'b0;
        else
            r_dummy_wr <= r_dummy_wr;
    end
    
    reg     [4:0]   r_byte_cnt  ;//根据该计数器去变化sda
    reg             r_read_en   ;
	
	`ifdef sim
		assign o_rden = r_read_en;
	`endif
	
    always@(posedge i_clk or posedge i_rst) begin
        if(i_rst)
            r_byte_cnt <= 'd0;
		else if(r_scl_vld & r_cstate != S_STOP)
			if(&r_scl_cnt[1:0] & w_div_flag)//当scl cnt后两位为2'b11 同时分频标志拉高时,计数器++
				r_byte_cnt <= r_byte_cnt < 8 ? r_byte_cnt + 1 : 'd0;
			else
				r_byte_cnt <= r_byte_cnt;
		else
            r_byte_cnt <= 'd0;
    end
    
    always@(*) begin
        if(i_rst)
            r_read_en = 'b0;
        else if(r_cstate == S_RD_DATA)
            r_read_en = |r_byte_cnt;
        else if(|(r_cstate & 8'b10000011))
            r_read_en = 'b0;
		else
			r_read_en <= !r_byte_cnt;
    end
	
    reg             r_daddr_bit ;
    
    always@(posedge i_clk or posedge i_rst) begin
        if(i_rst)
            r_daddr_bit <= 'b0;
        else if(r_cstate == S_DADDR)
            case(r_byte_cnt)
                1:  r_daddr_bit <= r_dev_addr[6];
                2:  r_daddr_bit <= r_dev_addr[5];
                3:  r_daddr_bit <= r_dev_addr[4];
                4:  r_daddr_bit <= r_dev_addr[3];
                5:  r_daddr_bit <= r_dev_addr[2];
                6:  r_daddr_bit <= r_dev_addr[1];
                7:  r_daddr_bit <= r_dev_addr[0];
                8:  r_daddr_bit <= w_rw_bit;
                default:    r_daddr_bit <= 'b0;
            endcase
        else
            r_daddr_bit <= 'b0;
    end
    
    reg             r_raddr_bit ;
    
    always@(posedge i_clk or posedge i_rst) begin
        if(i_rst)
            r_raddr_bit <= 'b0;
        else if(r_cstate == S_RADDR_16)
            case(r_byte_cnt)
                1:  r_raddr_bit <= r_reg_addr16[7];
                2:  r_raddr_bit <= r_reg_addr16[6];
                3:  r_raddr_bit <= r_reg_addr16[5];
                4:  r_raddr_bit <= r_reg_addr16[4];
                5:  r_raddr_bit <= r_reg_addr16[3];
                6:  r_raddr_bit <= r_reg_addr16[2];
                7:  r_raddr_bit <= r_reg_addr16[1];
                8:  r_raddr_bit <= r_reg_addr16[0];
                default:    r_raddr_bit <= 'b0;
            endcase
        else if(r_cstate == S_RADDR_8)
            case(r_byte_cnt)
                1:  r_raddr_bit <= r_reg_addr8[7];
                2:  r_raddr_bit <= r_reg_addr8[6];
                3:  r_raddr_bit <= r_reg_addr8[5];
                4:  r_raddr_bit <= r_reg_addr8[4];
                5:  r_raddr_bit <= r_reg_addr8[3];
                6:  r_raddr_bit <= r_reg_addr8[2];
                7:  r_raddr_bit <= r_reg_addr8[1];
                8:  r_raddr_bit <= r_reg_addr8[0];
                default:    r_raddr_bit <= 'b0;
            endcase
        else
            r_raddr_bit <= 'b0;
    end
    
    reg             r_wrdata_bit    ;
    
    always@(posedge i_clk or posedge i_rst) begin
        if(i_rst)
            r_wrdata_bit <= 'b0;
        else if(r_cstate == S_WR_DATA)
            case(r_byte_cnt)
                1:  r_wrdata_bit <= r_wr_data[7];
                2:  r_wrdata_bit <= r_wr_data[6];
                3:  r_wrdata_bit <= r_wr_data[5];
                4:  r_wrdata_bit <= r_wr_data[4];
                5:  r_wrdata_bit <= r_wr_data[3];
                6:  r_wrdata_bit <= r_wr_data[2];
                7:  r_wrdata_bit <= r_wr_data[1];
                8:  r_wrdata_bit <= r_wr_data[0];
                default:    r_wrdata_bit <= 'b0;
            endcase
        else
            r_wrdata_bit <= 'b0;
    end
    	
	assign	io_sda = ~r_read_en ? |{~r_scl_vld, r_daddr_bit, r_raddr_bit, r_wrdata_bit} : 1'bz;
	
    always@(posedge i_clk or posedge i_rst) begin
        if(i_rst)
            o_rd_data <= 'd0;
        else if(r_cstate == S_RD_DATA && r_scl_cnt[1:0] == 2'b01 && w_div_flag)
            case(r_byte_cnt)
                1:  o_rd_data[7] <= io_sda;
                2:  o_rd_data[6] <= io_sda;
                3:  o_rd_data[5] <= io_sda;
                4:  o_rd_data[4] <= io_sda;
                5:  o_rd_data[3] <= io_sda;
                6:  o_rd_data[2] <= io_sda;
                7:  o_rd_data[1] <= io_sda;
                8:  o_rd_data[0] <= io_sda;
                default:    o_rd_data <= o_rd_data;
            endcase
        else
            o_rd_data <= o_rd_data;
    end	
    
	wire			w_ack_flag	;
	assign	w_ack_flag = |(r_cstate & 8'b00111100) & r_byte_cnt == 'd0 & r_scl_cnt[1:0] == 2'b01 & w_div_flag;
    always@(posedge i_clk or posedge i_rst) begin
        if(i_rst)
			o_err <= 'b0;
        else if(r_cstate == S_IDLE)
            o_err <= 'b0;
		else if(w_ack_flag)
			o_err <= io_sda | o_err;
		else 
			o_err <= o_err;
	end
    
	reg		[7:0]	r_raddr_st ;
	
	always@(posedge i_clk) begin
		if(r_cstate == S_RADDR_16)
			r_raddr_st <= S_RADDR_8;
		else
			r_raddr_st <= REGADDR_2BYTES ? S_RADDR_16 : S_RADDR_8;
	end
	
    wire            w_state_chg	;
    
    assign  w_state_chg = (i_start & r_cstate == S_IDLE) | (&(r_scl_cnt[1:0]) && !r_byte_cnt && w_div_flag);
    
    always@(posedge i_clk or posedge i_rst) begin
        if(i_rst)
			o_vld <= 'b0;
		else if(r_cstate == S_STOP & r_nstate == S_IDLE)
			o_vld <= 'b1;
		else
			o_vld <= 'b0;
	end
    always@(posedge i_clk or posedge i_rst) begin
        if(i_rst)
            r_cstate <= S_IDLE;
        else
            r_cstate <= r_nstate;
    end
    
    always@(*) begin
        if(i_rst)
            r_nstate = S_IDLE;
        else
            case(r_cstate)
                S_IDLE:     r_nstate = w_state_chg ? S_START : S_IDLE;
                S_START:    r_nstate = w_state_chg ? S_DADDR : S_START;
                S_DADDR:	r_nstate = w_state_chg ? w_rw_bit ? S_RD_DATA : r_raddr_st : S_DADDR; 
                S_RADDR_16: r_nstate = w_state_chg ? S_RADDR_8 : S_RADDR_16;
                S_RADDR_8:	r_nstate = w_state_chg ? r_dummy_wr ? S_STOP : S_WR_DATA : S_RADDR_8;
                S_WR_DATA:  r_nstate = w_state_chg ? S_STOP : S_WR_DATA;
                S_RD_DATA:  r_nstate = w_state_chg ? S_STOP : S_RD_DATA;
                S_STOP:		r_nstate = w_state_chg ? r_dummy_wr ? S_START : S_IDLE : S_STOP;
                default:    r_nstate = S_IDLE;
            endcase
    end
    
endmodule

tb文件如下:

复制代码
`timescale 1ns / 1ps
//////////////////////////////////////////////////////////////////////////////////
// Company: 
// Engineer: 
// 
// Create Date: 2025/05/22 11:15:31
// Design Name: 
// Module Name: IIC_tb
// Project Name: 
// Target Devices: 
// Tool Versions: 
// Description: 
// 
// Dependencies: 
// 
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
// 
//////////////////////////////////////////////////////////////////////////////////


module IIC_tb();

    localparam REGADDR_2BYTES = 0;

    reg             i_clk50m    ;
    reg             i_rst50m    ;
    reg             i_start     ;
	reg				i_rw_flag	;
    reg     [6:0]   i_dev_addr  ;
    reg     [15:0]  i_reg_addr  ;
    reg     [7:0]   i_wr_data   ;
    
	wire 			o_rden		;
    wire            io_sda      ;
    wire            o_scl       ;
    wire    [7:0]   o_rd_data   ;
    wire            o_vld       ;
	wire			o_err		;
    wire            o_rdy       ;

    always#10 i_clk50m = ~i_clk50m;
    
    reg    [15:0]  r_random;
    always#20  r_random = $random()%16'hffff;
	assign  io_sda = o_rden ? r_random[0] : 1'bz;
	
    initial begin
		i_start = 0;
        i_clk50m = 1;
        i_rst50m = 1;
		i_rw_flag = 0;
        i_dev_addr = 7'b1010101;
        i_reg_addr = {8'h00, 8'h34};
        i_wr_data = 8'he3;
        #100
        i_rst50m = 0;
        #10
        i_start = 1;
        #30
        i_start = 0;
		@(posedge o_vld)
		#5000
		i_rw_flag = 1;
		i_dev_addr = 7'b1110001;
		i_reg_addr = {8'h00,8'hab};
		i_start = 1;
		#30
		i_start = 0;
    end

I2C_Master#(
    .REGADDR_2BYTES(REGADDR_2BYTES))
I2C_Master_U (
    i_clk50m    ,
    i_rst50m    ,
    i_start     ,
	i_rw_flag	,
    i_dev_addr  ,
    i_reg_addr  ,
    i_wr_data  	,   
    io_sda      ,
	o_rden		,
    o_scl       ,
    o_rd_data	,
    o_vld       ,
	o_err		,
    o_rdy       
);

endmodule

讲解视频:

https://www.bilibili.com/video/BV1HUhtzGEFS/?spm_id_from=333.1387.upload.video_card.click&vd_source=69fb997b62efa60ae1add8b53b6a5923

相关推荐
嵌入式-老费5 小时前
Zynq开发实践(FPGA之pwm输出)
fpga开发
hexiaoyan8277 小时前
光纤加速的板卡设计原理图:基于6U VPX XCVU9P+XCZU7EV的双FMC信号处理板卡
嵌入式硬件·fpga开发·光纤加速板卡·国产化板卡·xcvu9p板卡·xcvu9p
XiaoChaoZhiNeng9 小时前
Altera Quartus17.1 Modelsim 库编译与仿真
fpga开发
燎原星火*1 天前
FPGA复位
fpga开发
博览鸿蒙1 天前
FPGA笔试面试常考问题及答案汇总
fpga开发
9527华安1 天前
FPGA实现Aurora 64B66B图像视频点对点传输,基于GTY高速收发器,提供2套工程源码和技术支持
fpga开发·音视频·aurora·高速收发器·gty·64b66b·点对点传输
博览鸿蒙2 天前
入行FPGA选择国企、私企还是外企?
fpga开发
红糖果仁沙琪玛2 天前
FPGA实现流水式排序算法
算法·fpga开发·排序算法
陌夏微秋2 天前
FPGA硬件设计4 ZYNQ外围-以太网-PL/PS
stm32·单片机·嵌入式硬件·fpga开发·硬件架构·硬件工程·信息与通信