【I2C】基于SystemVerilog的16比特I/O拓展芯片TCA6416A读写控制

功能简介

  本文基于Xilinx Virtex Ultrascale+HBM VCU128 FPGA开发板,通过利用开发板上的TCA6416A芯片,对I2C通信方式进行学习。
  根据VCU128用户手册,128中具有两条I2C总线,其中一条连接有4个I2C芯片,能够与系统监视器(SYSMON)、EEPROM、QSFP等硬件资源进行通信。
图片

  根据手册介绍,TCA6416A芯片为I/O拓展芯片,PCA9544A、PCA9548A芯片为1-4、1-8通道总线交换芯片。两块TCA9548A芯片的使用需要先保证TCA6416A芯片的ICC_MUX_RESET-B引脚为高阻态或高电平,因此本文对利用FPGA控制TCA6416A芯片的I2C通信进行介绍。
  在实际使用中,Xilinx配置TCA6416A芯片的ADDR引脚为0,在FPGA上访问TCA6416A芯片的SCL、SDA引脚可通过如下约束设置,对应BANK67的BL28、BM27引脚。

set_property BOARD_PART_PIN IIC0_SCL_MAIN [get_ports i2c_0_scl]
set_property BOARD_PART_PIN IIC0_SDA_MAIN [get_ports i2c_0_sda]

TCA6416A芯片介绍

  TCA6416A为TI公司生产的I2C转16比特I/O拓展芯片,芯片手册,该芯片能够利用I2C协议进行16个通用引脚(分为2组:P00-P07,P10-P17)的读写访问,每个引脚支持设置为输入、输出两种模式。
图片

  ADDR引脚用于设置TCA6416A的I2C通信地址,如下图所示,TCA6416A的地址包含一个可编程位,在VCU128中,该位被设置为0。
图片

  TCA6416A的I/O Port为2组8比特通用IO引脚,在通过I2C通信进行使用时会通过8种指令读写8个寄存器,进而实现IO引脚的间接访问。这些操作会通过组的形式对2组8比特IO引脚进行访问。
图片

  在使用时,TCA6416A的I2C总线包含标准模式、快速模式两种,其中标准模式下SCL的时钟频率不能超过100KHz,快速模式下SCL的时钟频率不能超过400KHz。本文使用标准模式,时钟频率100KHz。

I2C发送端逻辑实现

  I2C是一种采用报文方式传输的串行通信方式,每次发送过程包含一个发送端和一个接收端。从发送端角度看由输出引脚SCL与输入输出引脚SDA构成,其中SDA为每次传输的数据,SCL为代表每次传输数据有效的高电平有效选通信号(时钟信号)。除了报文的开始位和结束位以外,SDA只能在SCL为低电平时发生变化,在SCL为高电平时应保持不变。
图片

  I2C传输的报文格式如下,包含1个开始位(S)、8个数据位(1个字节)、1个确认位(ACK)、1个停止位(P)。开始位由发送端发送,在发送完成后,可以进行多次数据位、确认位的交替传输,在最终交互完成后,由发送端发送停止位表示本次通信结束。在发送端发送完8个字节数据后,接收端会占用SDA发送确认位用于表明当前状态,类似一种响应信号。发送端可以根据接收端发送的确认位决定下一步的行动。
图片

  对于TCA6416A芯片,I2C传输可分为两种类型的操作。

写操作

  根据手册,对于TCA6416A芯片的写操作格式如下,Target Address为TCA6416A芯片的地址,Command Byte为本次访问的操作名称,后续Data0、Data1为写入的数据。下图展示了利用0x20指令写端口0的指令。
图片

  下面展示了进行I2C写操作的状态机:

  always_comb begin
        case (fsm_r)
        RESET: begin
            if (itf_rst_done) begin
                fsm_s = IDLE;
            end else begin
                fsm_s = RESET;
            end
        end
        IDLE: begin
            if (single_bit_hold_flag_r) begin
                if (START_flag_r) begin
                    fsm_s = ADDR_START;
                end else begin
                    fsm_s = IDLE;
                end
            end else begin
                fsm_s = IDLE;
            end
        end
        ADDR_START: begin
            if (single_bit_hold_flag_r) begin
                fsm_s = ADDR_DATA;
            end else begin
                fsm_s = ADDR_START;
            end
        end
        ADDR_DATA: begin
            if (single_bit_hold_flag_r && pos_cnt_r == 4'd7) begin
                fsm_s = ADDR_ACK;
            end else begin
                fsm_s = ADDR_DATA;
            end
        end
        ADDR_ACK: begin
            if (single_bit_hold_flag_r) begin
                // if (STOP_flag_r) begin
                if (1'b0) begin
                    fsm_s = ADDR_STOP;
                end else begin
                    fsm_s = CMD_DATA; // ADDR_STOP;
                end
            end else begin
                fsm_s = ADDR_ACK;
            end
        end
        ADDR_STOP: begin
            if (single_bit_hold_flag_r) begin
                fsm_s = IDLE;
            end else begin
                fsm_s = ADDR_STOP;
            end
        end
        CMD_DATA: begin
            if (single_bit_hold_flag_r && pos_cnt_r == 4'd7) begin
                fsm_s = CMD_ACK;
            end else begin
                fsm_s = CMD_DATA;
            end
        end
        CMD_ACK: begin
            if (single_bit_hold_flag_r) begin
                // if (STOP_flag_r) begin
                if (1'b0) begin
                    fsm_s = CMD_STOP;
                end else begin
                    if (RWn_flag_r) begin
                        fsm_s = SECADDR_START; // ADDR_STOP;
                    end else begin
                        fsm_s = WR_DATA; // ADDR_STOP;
                    end
                end
            end else begin
                fsm_s = CMD_ACK;
            end
        end
        CMD_STOP: begin
            if (single_bit_hold_flag_r) begin
                fsm_s = IDLE;
            end else begin
                fsm_s = CMD_STOP;
            end
        end
        WR_DATA: begin
            if (single_bit_hold_flag_r && pos_cnt_r == 4'd7) begin
                fsm_s = WR_ACK;
            end else begin
                fsm_s = WR_DATA;
            end
        end
        WR_ACK: begin
            if (single_bit_hold_flag_r) begin
                if (STOP_flag_r) begin
                    fsm_s = WR_STOP;
                end else begin
                    fsm_s = WR_DATA;
                end
            end else begin
                fsm_s = WR_ACK;
            end
        end
        WR_STOP: begin
            if (single_bit_hold_flag_r) begin
                fsm_s = IDLE;
            end else begin
                fsm_s = WR_STOP;
            end
        end
        default: begin
            fsm_s = RESET;
        end
        endcase
    end
读操作

  TCA6416A芯片的读操作格式如下,在进行读操作前,需要先通过写操作设置指令。使得TCA6416A芯片得知后续对哪个寄存器进行读操作。之后再利用开始位(S)重启读操作。下图展示了利用0x20指令读端口0的I2C格式。
  在读操作中,在FPGA进行I2C读操作时,后续I2C总线将由TCA6416A芯片发送8个数据位,由FPGA发送确认位(ACK),如下图所示。
图片

  下面展示了进行I2C读操作的状态机:

   always_comb begin
        case (fsm_r)
        RESET: begin
            if (itf_rst_done) begin
                fsm_s = IDLE;
            end else begin
                fsm_s = RESET;
            end
        end
        IDLE: begin
            if (single_bit_hold_flag_r) begin
                if (START_flag_r) begin
                    fsm_s = ADDR_START;
                end else begin
                    fsm_s = IDLE;
                end
            end else begin
                fsm_s = IDLE;
            end
        end
        ADDR_START: begin
            if (single_bit_hold_flag_r) begin
                fsm_s = ADDR_DATA;
            end else begin
                fsm_s = ADDR_START;
            end
        end
        ADDR_DATA: begin
            if (single_bit_hold_flag_r && pos_cnt_r == 4'd7) begin
                fsm_s = ADDR_ACK;
            end else begin
                fsm_s = ADDR_DATA;
            end
        end
        ADDR_ACK: begin
            if (single_bit_hold_flag_r) begin
                // if (STOP_flag_r) begin
                if (1'b0) begin
                    fsm_s = ADDR_STOP;
                end else begin
                    fsm_s = CMD_DATA; // ADDR_STOP;
                end
            end else begin
                fsm_s = ADDR_ACK;
            end
        end
        ADDR_STOP: begin
            if (single_bit_hold_flag_r) begin
                fsm_s = IDLE;
            end else begin
                fsm_s = ADDR_STOP;
            end
        end
        CMD_DATA: begin
            if (single_bit_hold_flag_r && pos_cnt_r == 4'd7) begin
                fsm_s = CMD_ACK;
            end else begin
                fsm_s = CMD_DATA;
            end
        end
        CMD_ACK: begin
            if (single_bit_hold_flag_r) begin
                // if (STOP_flag_r) begin
                if (1'b0) begin
                    fsm_s = CMD_STOP;
                end else begin
                    if (RWn_flag_r) begin
                        fsm_s = SECADDR_START; // ADDR_STOP;
                    end else begin
                        fsm_s = WR_DATA; // ADDR_STOP;
                    end
                end
            end else begin
                fsm_s = CMD_ACK;
            end
        end
        CMD_STOP: begin
            if (single_bit_hold_flag_r) begin
                fsm_s = IDLE;
            end else begin
                fsm_s = CMD_STOP;
            end
        end
        SECADDR_START: begin
            if (single_bit_hold_flag_r) begin
                fsm_s = SECADDR_DATA;
            end else begin
                fsm_s = SECADDR_START;
            end
        end
        SECADDR_DATA: begin
            if (single_bit_hold_flag_r && pos_cnt_r == 4'd7) begin
                fsm_s = SECADDR_ACK;
            end else begin
                fsm_s = SECADDR_DATA;
            end
        end
        SECADDR_ACK: begin
            if (single_bit_hold_flag_r) begin
                // if (STOP_flag_r) begin
                if (1'b0) begin
                    fsm_s = SECADDR_STOP;
                end else begin
                    fsm_s = RD_DATA; // ADDR_STOP;
                end
            end else begin
                fsm_s = SECADDR_ACK;
            end
        end
        SECADDR_STOP: begin
            if (single_bit_hold_flag_r) begin
                fsm_s = IDLE;
            end else begin
                fsm_s = SECADDR_STOP;
            end
        end
        RD_DATA: begin
            if (single_bit_hold_flag_r && pos_cnt_r == 4'd7) begin
                fsm_s = RD_ACK;
            end else begin
                fsm_s = RD_DATA;
            end
        end
        RD_ACK: begin
            if (single_bit_hold_flag_r) begin
                if (STOP_flag_r) begin
                    fsm_s = RD_STOP;
                end else begin
                    fsm_s = RD_DATA;
                end
            end else begin
                fsm_s = RD_ACK;
            end
        end
        RD_STOP: begin
            if (single_bit_hold_flag_r) begin
                fsm_s = IDLE;
            end else begin
                fsm_s = RD_STOP;
            end
        end
        default: begin
            fsm_s = RESET;
        end
        endcase
    end

仿真测试

  仿真模拟了利用VIO输入0x02端口0引脚读写指令后FPGA的工作过程 。其中蓝色高阻态用于TCA6416A发来的响应信息。

    initial begin // write to Output Port Register
        force i2c_top_inst.CMD_vec_r    = 8'b00000010;   
        force i2c_top_inst.wrDATA_vec_r   = 8'h6a;
                                  
        force i2c_top_inst.START_flag_r = 1'b1;
        force i2c_top_inst.RWn_flag_r   = 1'b1;
        force i2c_top_inst.wrACK_flag_r   = 1'b0;
        force i2c_top_inst.STOP_flag_r  = 1'b1;
    
    end

图片

上板测试

  这里以0x02端口0引脚读写指令为例,进行I2C通信测试,初始状态下端口0的8个引脚值为0x77。
图片

  通过设置读写模式选择RWn_flag_r为0,指令CMD_vec_r为0x20,写数据wrDATA_vec_r为0x6A,点击START_flag_r控制FPGA发送I2C写端口0的8bit为6A。
图片

  通过设置RWn_flag_r为1,其余保持不变,点击START_flag_r控制FPGA发送I2C读端口0的8bit,得到rdDATA_vec_r结果为6A。
图片

完整代码

  工程代码可于同名公众号回复I2C_TCA6416A下载。

  • 12
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
要将 I2C 接口与 DUT(Device Under Test)相连,通常需要进行以下步骤: 1. 定义 I2C 接口的信号:在 Verilog 或 SystemVerilog 中,首先需要定义一个包含 I2C 接口所需信号的接口或模块。这些信号通常包括时钟信号(如 SCL)、数据信号(如 SDA)以及其他控制信号(如地址信号、读/写信号等)。 示例代码: ```verilog interface i2c_if; wire sclk_t; wire sda_t; // 其他信号定义 endinterface ``` 2. 实例化 I2C 接口:在顶层模块中实例化 I2C 接口,并将其与 DUT 的对应信号相连。 示例代码: ```verilog module top_module; // 实例化 I2C 接口 i2c_if m_i2c_if(); // 实例化 DUT dut_module p_dut(.sclk(m_i2c_if.sclk_t), .sda(m_i2c_if.sda_t)); // 其他模块和逻辑 endmodule ``` 这里假设 `dut_module` 是你的 DUT 模块,拥有与 I2C 接口相匹配的信号接口。 3. 连接其他必要的信号:除了时钟和数据线之外,可能还需要连接其他控制信号,例如地址线、读/写信号等。根据 I2C 设备的要求,将这些信号与 DUT 的相应信号相连。 示例代码: ```verilog module dut_module(input sclk, input sda, input addr, input rw, /*其他输入输出信号*/); // DUT 模块的逻辑 endmodule ``` 在这个例子中,除了连接时钟信号和数据信号之外,还将 `addr`(地址信号)和 `rw`(读/写信号)与 DUT 模块的相应输入信号相连。 这样,通过实例化和相连,你就完成了将 I2C 接口与 DUT 相连的操作。当然,具体的连接方式还取决于你的设计和需求,以上仅为一种常见的连接方式。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

wjh776a68

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值