基于 FPGA 的模拟 I²C协议设计(中)

2020-12-30 11:13:19 浏览数 (1)

大侠好,欢迎来到FPGA技术江湖,江湖偌大,相见即是缘分。大侠可以关注FPGA技术江湖,在“闯荡江湖”、"行侠仗义"栏里获取其他感兴趣的资源,或者一起煮酒言欢。

今天给大侠带来基于FPGA的 模拟 I²C 协议设计,由于篇幅较长,分三篇。今天带来第二篇,中篇,I²C 协议的具体实现。话不多说,上货。

之前也有相关文章介绍,这里超链接一下,仅供各位大侠参考。

源码系列:基于FPGA的 IIC 设计(附源工程)

导读

I²C(Inter-Integrated Circuit),其实是 I²C Bus简称,中文就是集成电路总线,它是一种串行通信总线,使用多主从架构,由飞利浦公司在1980年代为了让主板、嵌入式系统或手机用以连接低速周边设备而发展。I²C的正确读法为“I平方C”("I-squared-C"),而“I二C”("I-two-C")则是另一种错误但被广泛使用的读法。自2006年10月1日起,使用 I²C 协议已经不需要支付专利费,但制造商仍然需要付费以获取 I²C 从属设备地址。

I²C 简单来说,就是一种串行通信协议,I²C的通信协议和通信接口在很多工程中有广泛的应用,如数据采集领域的串行 AD,图像处理领域的摄像头配置,工业控制领域的 X 射线管配置等等。除此之外,由于 I²C 协议占用的 IO 资源特别少,连接方便,所以工程中也常选用 I²C 接口做为不同芯片间的通信协议。I²C 串行总线一般有两根信号线,一根是双向的数据线SDA,另一根是时钟线SCL。所有接到 I²C 总线设备上的串行数据SDA都接到总线的SDA上,各设备的时钟线SCL接到总线的SCL上。

在现代电子系统中,有为数众多的 IC 需要进行相互之间以及与外界的通信。为了简化电路的设计,Philips 公司开发了一种用于内部 IC 控制的简单的双向两线串行总线 I²C(Intel-Integrated Circuit bus)。1998 年当推出 I²C 总线协议 2.0 版本时,I²C 协议实际上已经成为一个国际标准。

在进行 FPGA 设计时,经常需要和外围提供 I²C 接口的芯片通信。例如低功耗的 CMOS 实时时钟/日历芯片 PCF8563、LCD 驱动芯片 PCF8562、并行口扩展芯片 PCF8574、键盘/LED 驱动器 ZLG7290 等都提供 I²C 接口。因此在 FPGA 中模拟 I²C 接口已成为 FPGA 开发必要的步骤。

本篇将详细讲解在 FPGA 芯片中使用 VHDL/Verilog HDL 模拟 I²C 协议,以及编写 TestBench仿真和测试程序的方法。

第二篇内容摘要:本篇会介绍 I²C 协议的具体实现,包括位传输的实现、字节传输的实现以及程序主体的实现等相关内容。

三、I²C 协议的具体实现

FPGA 设计一般按照从顶向下的模式进行:首先设计芯片功能,规划各个模块功能;然后按照规划实现各个模块。本篇由 3 个代码文件组成:i2c_master_bit_ctrl.v 完成位传输的功能、i2c_master_byte_ctrl.v 完成字节传输的功能、i2c_master_top.v 完成整个程序的控制功能,并提供给外部程序的接口。在 ISE 中创建一个项目,然后加入上面 3 个文件。下面依次介绍 3 个文件的内容。本篇讲解采用 Verilog HDL。

3.1 位传输的实现

i2c_master_bit_ctrl.v 完成位传输的功能。位传输的功能包括数据按位传输的实现和 I²C协议各个命令的实现两部分。

如图 5 所示开始和重复开始命令的产生包括 5 个阶段:idle 和 A、B、C、D 等。停止命令包括 4 个阶段:idle 和 A、B、C 等。读、写一个字节通过 8 次位操作完成。

图 5 位传输完成数据的传输和各个命令的实现

实现代码如下:

代码语言:javascript复制
`include "timescale.v"
`include "i2c_master_defines.v"

//模块名称及 IO
module i2c_master_bit_ctrl(
        clk, rst, nReset,
        clk_cnt, ena, cmd, cmd_ack, busy, al, din, dout,
        scl_i, scl_o, scl_oen, sda_i, sda_o, sda_oen
    );
    
    // 输入、输出
    input clk;
    input rst;
    input nReset;
    input ena; // 模块使能信号
    input [15:0] clk_cnt; // 时钟分频系数
    input [3:0] cmd;
    output cmd_ack; // 命令完成应答
    reg cmd_ack;
    output busy; // 总线忙
    reg busy;
    output al; // 总线仲裁丢失
    reg al;
    input din;
    output dout;
    reg dout;
    
    // I2C 连线
    input scl_i; // I2C 时钟输入
    output scl_o; // I2C 时钟输出
    output scl_oen; // I2C 时钟输出使能
    reg scl_oen;
    input sda_i; //I2C 数据输入
    output sda_o; // I2C 数据输出
    output sda_oen; // I2C 数据输出使能
    reg sda_oen;
    
    // variable declarations
    reg sSCL, sSDA; // 同步后的 SCL 和 SDA 输入
    reg dscl_oen; // 延迟后的 scl_oen
    reg sda_chk; // 检 查 后 的 SDA output (Multi-master
    arbitration)
    reg clk_en; // 时钟产生信号
    wire slave_wait;
    reg [15:0] cnt; // 时钟分频计数器
    
    // 模块主体
    // 当从节点没有准备好时,下拉 SCL 来延迟周期
    // 延迟 scl_oen
    always @(posedge clk)
        dscl_oen <= #1 scl_oen;
    
    assign slave_wait = dscl_oen && !sSCL;
    
    // 产生时钟使能信号
    always @(posedge clk or negedge nReset)
        if(~nReset)
            begin
                cnt <= #1 16'h0;
                clk_en <= #1 1'b1;
            end
        else if (rst)
            begin
                cnt <= #1 16'h0;
                clk_en <= #1 1'b1;
            end
        else if ( ~|cnt || ~ena)
            if (~slave_wait)
                begin
                    cnt <= #1 clk_cnt;
                    clk_en <= #1 1'b1;
                end
            else
                begin
                    cnt <= #1 cnt;
                    clk_en <= #1 1'b0;
                end
            else
                begin
                    cnt <= #1 cnt - 16'h1;
                    clk_en <= #1 1'b0;
                end
                
    // 产生总线状态控制信号
    reg dSCL, dSDA;
    reg sta_condition;
    reg sto_condition;
    
    // 同步 SCL 和 SDA 输入信号,减少不稳定风险
    always @(posedge clk or negedge nReset)
        if (~nReset)
            begin
                sSCL <= #1 1'b1;
                sSDA <= #1 1'b1;
                dSCL <= #1 1'b1;
                dSDA <= #1 1'b1;
            end
        else if (rst)
            begin
                sSCL <= #1 1'b1;
                sSDA <= #1 1'b1;
                dSCL <= #1 1'b1;
                dSDA <= #1 1'b1;
            end
        else
            begin
                sSCL <= #1 scl_i;
                sSDA <= #1 sda_i;
                dSCL <= #1 sSCL;
                dSDA <= #1 sSDA;
            end
        
    // SCL 处于高时检测到 SDA 的下降沿,即检测开始状态信号
    // SCL 处于高时检测到 SDA 的上升沿,即检测停止状态信号
    always @(posedge clk or negedge nReset)
        if (~nReset)
            begin
                sta_condition <= #1 1'b0;
                sto_condition <= #1 1'b0;
            end
        else if (rst)
            begin
                sta_condition <= #1 1'b0;
                sto_condition <= #1 1'b0;
            end
        else
            begin
                sta_condition <= #1 ~sSDA & dSDA & sSCL;
                sto_condition <= #1 sSDA & ~dSDA & sSCL;
            end
    
    // 产生 I2C 总线忙信号
    always @(posedge clk or negedge nReset)
        if(!nReset)
            busy <= #1 1'b0;
        else if (rst)
            busy <= #1 1'b0;
        else
            busy <= #1 (sta_condition | busy) & ~sto_condition;
   
    // 产生仲裁丢失信号 generate arbitration lost signal
    // 仲裁丢失发生在:
    // 1) 主节点驱动 SDA 处于高,但是 I2C 总线一直处于低
    // 2) 没有请求时却检测到停止状态信号
    reg cmd_stop, dcmd_stop;
    
    always @(posedge clk or negedge nReset)
        if (~nReset)
            begin
                cmd_stop <= #1 1'b0;
                dcmd_stop <= #1 1'b0;
                al <= #1 1'b0;
            end
        else if (rst)
            begin
                cmd_stop <= #1 1'b0;
                dcmd_stop <= #1 1'b0;
                al <= #1 1'b0;
            end
        else
            begin
                cmd_stop <= #1 cmd == `I2C_CMD_STOP;
                dcmd_stop <= #1 cmd_stop;
                al <= #1 (sda_chk & ~sSDA & sda_oen) | (sto_condition & ~dcmd_stop);
            end
            
    // 产生数据输出信号,在 SCL 信号的上升沿保存 SDA
    always @(posedge clk)
        if(sSCL & ~dSCL)
            dout <= #1 sSDA;
            
    // 产生状态机
    // 状态译码
    parameter [16:0] idle = 17'b0_0000_0000_0000_0000;
    parameter [16:0] start_a = 17'b0_0000_0000_0000_0001;
    parameter [16:0] start_b = 17'b0_0000_0000_0000_0010;
    parameter [16:0] start_c = 17'b0_0000_0000_0000_0100;
    parameter [16:0] start_d = 17'b0_0000_0000_0000_1000;
    parameter [16:0] start_e = 17'b0_0000_0000_0001_0000;
    parameter [16:0] stop_a = 17'b0_0000_0000_0010_0000;
    parameter [16:0] stop_b = 17'b0_0000_0000_0100_0000;
    parameter [16:0] stop_c = 17'b0_0000_0000_1000_0000;
    parameter [16:0] stop_d = 17'b0_0000_0001_0000_0000;
    parameter [16:0] rd_a = 17'b0_0000_0010_0000_0000;
    parameter [16:0] rd_b = 17'b0_0000_0100_0000_0000;
    parameter [16:0] rd_c = 17'b0_0000_1000_0000_0000;
    parameter [16:0] rd_d = 17'b0_0001_0000_0000_0000;
    parameter [16:0] wr_a = 17'b0_0010_0000_0000_0000;
    parameter [16:0] wr_b = 17'b0_0100_0000_0000_0000;
    parameter [16:0] wr_c = 17'b0_1000_0000_0000_0000;
    parameter [16:0] wr_d = 17'b1_0000_0000_0000_0000;
    reg [16:0] c_state;
    
    //状态机
    always @(posedge clk or negedge nReset)
        if (!nReset)
            begin
                c_state <= #1 idle;
                cmd_ack <= #1 1'b0;
                scl_oen <= #1 1'b1;
                sda_oen <= #1 1'b1;
                sda_chk <= #1 1'b0;
            end
        else if (rst | al)
            begin
                c_state <= #1 idle;
                cmd_ack <= #1 1'b0;
                scl_oen <= #1 1'b1;
                sda_oen <= #1 1'b1;
                sda_chk <= #1 1'b0;
            end
        else
            begin
                cmd_ack <= #1 1'b0;
                if (clk_en)
                    case (c_state)
                        // idle 状态
                        idle:
                        begin
                        case (cmd)
                            `I2C_CMD_START:
                              c_state <= #1 start_a;
                            `I2C_CMD_STOP:
                              c_state <= #1 stop_a;
                            `I2C_CMD_WRITE:
                              c_state <= #1 wr_a;
                            `I2C_CMD_READ:
                              c_state <= #1 rd_a;
                            default:
                              c_state <= #1 idle;
                        endcase
                        scl_oen <= #1 scl_oen; // 保持 SCL 在同一状态
                        sda_oen <= #1 sda_oen; // 保持 SDA 在同一状态
                        sda_chk <= #1 1'b0; // 不检查 SDA 输出
                    end
                    // 开始状态
                    start_a:
                    begin
                        c_state <= #1 start_b;
                        scl_oen <= #1 scl_oen; // 保持 SCL 在同一状态
                        sda_oen <= #1 1'b1; // 保持 SDA 处于高
                        sda_chk <= #1 1'b0; // 不检查 SDA 的输出
                    end
                    
                    start_b:
                    begin
                        c_state <= #1 start_c;
                        scl_oen <= #1 1'b1;
                        sda_oen <= #1 1'b1;
                        sda_chk <= #1 1'b0;
                    end
                    
                    start_c:
                    begin
                        c_state <= #1 start_d;
                        scl_oen <= #1 1'b1;
                        sda_oen <= #1 1'b0;
                        sda_chk <= #1 1'b0;
                    end
                    
                    start_d:
                    begin
                        c_state <= #1 start_e;
                        scl_oen <= #1 1'b1;
                        sda_oen <= #1 1'b0;
                        sda_chk <= #1 1'b0;
                    end
                    
                    start_e:
                    begin
                    c_state <= #1 idle;
                    cmd_ack <= #1 1'b1;
                    scl_oen <= #1 1'b0;
                    sda_oen <= #1 1'b0;
                    sda_chk <= #1 1'b0;
                    end
                    
                    // 停止状态
                    stop_a:
                    begin
                        c_state <= #1 stop_b;
                        scl_oen <= #1 1'b0;
                        sda_oen <= #1 1'b0;
                        sda_chk <= #1 1'b0;
                    end
                    
                    stop_b:
                    begin
                        c_state <= #1 stop_c;
                        scl_oen <= #1 1'b1;
                        sda_oen <= #1 1'b0;
                        sda_chk <= #1 1'b0;
                    end
                    
                    stop_c:
                    begin
                        c_state <= #1 stop_d;
                        scl_oen <= #1 1'b1;
                        sda_oen <= #1 1'b0;
                        sda_chk <= #1 1'b0;
                    end
                    
                    stop_d:
                    begin
                        c_state <= #1 idle;
                        cmd_ack <= #1 1'b1;
                        scl_oen <= #1 1'b1;
                        sda_oen <= #1 1'b1;
                        sda_chk <= #1 1'b0;
                    end
                    
                    // 读状态
                    rd_a:
                    begin
                        c_state <= #1 rd_b;
                        scl_oen <= #1 1'b0; //保持 SCL 处于低
                        sda_oen <= #1 1'b1; // SDA 处于三态
                        sda_chk <= #1 1'b0; // 不检查 SDA 输出
                    end
                    
                    rd_b:
                    begin
                        c_state <= #1 rd_c;
                        scl_oen <= #1 1'b1;
                        sda_oen <= #1 1'b1;
                        sda_chk <= #1 1'b0; 
                    end
                    
                    rd_c:
                    begin
                        c_state <= #1 rd_d;
                        scl_oen <= #1 1'b1;
                        sda_oen <= #1 1'b1;
                        sda_chk <= #1 1'b0;
                    end
                    
                    rd_d:
                    begin
                        c_state <= #1 idle;
                        cmd_ack <= #1 1'b1;
                        scl_oen <= #1 1'b0;
                        sda_oen <= #1 1'b1;
                        sda_chk <= #1 1'b0;
                    end
                    
                    // 写状态
                    wr_a:
                    begin
                        c_state <= #1 wr_b;
                        scl_oen <= #1 1'b0;
                        sda_oen <= #1 din;
                        sda_chk <= #1 1'b0;
                    end
                    
                    wr_b:
                    begin
                        c_state <= #1 wr_c;
                        scl_oen <= #1 1'b1;
                        sda_oen <= #1 din;
                        sda_chk <= #1 1'b1;
                    end
                    
                    wr_c:
                    begin
                        c_state <= #1 wr_d;
                        scl_oen <= #1 1'b1;
                        sda_oen <= #1 din;
                        sda_chk <= #1 1'b1;
                    end
                    
                    wr_d:
                    begin
                        c_state <= #1 idle;
                        cmd_ack <= #1 1'b1;
                        scl_oen <= #1 1'b0;
                        sda_oen <= #1 din;
                        sda_chk <= #1 1'b0;
                    end
                    
                endcase
            end
            
        // 分配 SCL 和 SDA 输出一直处于低
        assign scl_o = 1'b0;
        assign sda_o = 1'
        
endmodule

3.2 字节传输的实现

字节传输的具体实现流程如图 6 所示。

图 6 字节传输控制模块流程图

字节传输控制模块控制以字节为单位的数据传输。它根据命令寄存器的设置将数据传输寄存器中的内容传输到外部节点,将外部节点的数据接收到数据接收寄存器中。

实现代码如下:

代码语言:javascript复制
`include "timescale.v"
`include "i2c_master_defines.v"
//模块
module i2c_master_byte_ctrl (
    clk, rst, nReset, ena, clk_cnt, start, stop, read, write, ack_in, din,
    cmd_ack, ack_out, dout, i2c_busy, i2c_al, scl_i, scl_o, scl_oen, sda_i, sda_o, sda_oen );

    // 输入、输出
    input clk; // 主时钟
    input rst; // 同步 RESET,高有效
    input nReset; // 异步 RESET,低有效
    input ena; // 模块使能信号
    input [15:0] clk_cnt; // 4 倍 SCL 信号
    
    // 控制信号输入
    input start;
    input stop;
    input read;
    input write;
    input ack_in;
    input [7:0] din;
    
    // 状态信号输出
    output cmd_ack;
    reg cmd_ack;
    output ack_out;
    reg ack_out;
    output i2c_busy;
    output i2c_al;
    output [7:0] dout;
    
    // I2C 信号
    input scl_i;
    output scl_o;
    output scl_oen;
    input sda_i;
    output sda_o;
    output sda_oen;
    
    // 变量申明
    // 状态机
    parameter [4:0] ST_IDLE = 5'b0_0000;
    parameter [4:0] ST_START = 5'b0_0001;
    parameter [4:0] ST_READ = 5'b0_0010;
    parameter [4:0] ST_WRITE = 5'b0_0100;
    parameter [4:0] ST_ACK = 5'b0_1000;
    parameter [4:0] ST_STOP = 5'b1_0000;
    
    // 位控制模块的信号
    reg [3:0] core_cmd;
    reg core_txd;
    wire core_ack, core_rxd;
    
    // 移位寄存器信号
    reg [7:0] sr; //8 位移位寄存器
    reg shift, ld;
    
    // 状态机信号
    wire go;
    reg [2:0] dcnt;
    wire cnt_done;
    
    // 模块主体
    // 连接位控制模块
    i2c_master_bit_ctrl bit_controller (
            .clk ( clk ),
            .rst ( rst ),
            .nReset ( nReset ),
            .ena ( ena ),
            .clk_cnt ( clk_cnt ),
            .cmd ( core_cmd ),
            .cmd_ack ( core_ack ),
            .busy ( i2c_busy ),
            .al ( i2c_al ),
            .din ( core_txd ),
            .dout ( core_rxd ),
            .scl_i ( scl_i ),
            .scl_o ( scl_o ),
            .scl_oen ( scl_oen ),
            .sda_i ( sda_i ),
            .sda_o ( sda_o ),
            .sda_oen ( sda_oen )
        );
        
    // 产生 GO 信号,当读/写/停止/应答时发生
    assign go = (read | write | stop) & ~cmd_ack;
    
    // 分配输出到移位寄存器
    assign dout = sr;
    
    // 产生移位寄存器
    always @(posedge clk or negedge nReset)
        if (!nReset)
            sr <= #1 8'h0;
        else if (rst)
            sr <= #1 8'h0;
        else if (ld)
            sr <= #1 din;
        else if (shift)
            sr <= #1 {sr[6:0], core_rxd};
    
    // 产生计数器
    always @(posedge clk or negedge nReset)
        if (!nReset)
            dcnt <= #1 3'h0;
        else if (rst)
            dcnt <= #1 3'h0;
        else if (ld)
            dcnt <= #1 3'h7;
        else if (shift)
            dcnt <= #1 dcnt - 3'h1;
        
    assign cnt_done = ~(|dcnt);
        
    // 状态机
    reg [4:0] c_state;
    always @(posedge clk or negedge nReset)
        if (!nReset)
            begin
                core_cmd <= #1 `I2C_CMD_NOP;
                core_txd <= #1 1'b0;
                shift <= #1 1'b0;
                ld <= #1 1'b0;
                cmd_ack <= #1 1'b0;
                c_state <= #1 ST_IDLE;
                ack_out <= #1 1'b0;
            end
        else if (rst | i2c_al)
            begin
                core_cmd <= #1 `I2C_CMD_NOP;
                core_txd <= #1 1'b0;
                shift <= #1 1'b0;
                ld <= #1 1'b0;
                cmd_ack <= #1 1'b0;
                c_state <= #1 ST_IDLE;
                ack_out <= #1 1'b0;
            end
        else
            begin
                // 初始化所有信号
                core_txd <= #1 sr[7];
                shift <= #1 1'b0;
                ld <= #1 1'b0;
                cmd_ack <= #1 1'b0;
                case (c_state)
                    //IDLE 状态
                    ST_IDLE:
                        if (go)
                            begin
                                if (start)
                                    begin
                                        c_state <= #1 ST_START;
                                        core_cmd <= #1 `I2C_CMD_START;
                                    end
                                else if (read)


                                    begin
                                        c_state <= #1 ST_READ;
                                        core_cmd <= #1 `I2C_CMD_READ;
                                    end
                                else if (write)
                                    begin
                                        c_state <= #1 ST_WRITE;
                                        core_cmd <= #1 `I2C_CMD_WRITE;
                                    end
                                else // 缺省的是 stop 状态
                                    begin
                                        c_state <= #1 ST_STOP;
                                        core_cmd <= #1 `I2C_CMD_STOP;
                                        // 产生应答信号
                                        cmd_ack <= #1 1'b1;
                                    end
                                ld <= #1 1'b1;
                            end
                            
                        //开始状态
                        ST_START:
                            if (core_ack)
                                begin
                                    if (read)
                                        begin
                                            c_state <= #1 ST_READ;
                                            core_cmd <= #1 `I2C_CMD_READ;
                                        end
                                    else
                                        begin
                                            c_state <= #1 ST_WRITE;
                                            core_cmd <= #1 `I2C_CMD_WRITE;
                                        end
                                    ld <= #1 1'b1;
                                end
                                
                        //写数据状态
                        ST_WRITE:
                            if (core_ack)
                                if (cnt_done)
                                    begin
                                        c_state <= #1 ST_ACK;
                                        core_cmd <= #1 `I2C_CMD_READ;
                                    end
                                else
                                    begin
                                        c_state <= #1 ST_WRITE; // 保持在原来状态
                                        core_cmd <= #1 `I2C_CMD_WRITE; // 写下一位数据
                                        shift <= #1 1'b1;
                                    end
                                    
                        //读信号状态
                        ST_READ:
                            if (core_ack)
                                begin
                                    if (cnt_done)
                                        begin
                                            c_state <= #1 ST_ACK;
                                            core_cmd <= #1 `I2C_CMD_WRITE;
                                        end
                                    else
                                        begin
                                            c_state <= #1 ST_READ; // 保留在原来状态
                                            core_cmd <= #1 `I2C_CMD_READ; // 读下一位数据
                                        end
                                    shift <= #1 1'b1;
                                    core_txd <= #1 ack_in;
                                end
                                
                        //应答数据状态
                        ST_ACK:
                            if (core_ack)
                                begin
                                    if (stop)
                                        begin
                                            c_state <= #1 ST_STOP;
                                            core_cmd <= #1 `I2C_CMD_STOP;
                                        end
                                    else
                                        begin
                                            c_state <= #1 ST_IDLE;
                                            core_cmd <= #1 `I2C_CMD_NOP;
                                        end
                                        
                                        // 把应答信号输出连接到位控制模块
                                        ack_out <= #1 core_rxd;
                                        // 产生应答信号
                                        cmd_ack <= #1 1'b1;
                                        core_txd <= #1 1'b1;
                                end
                            else
                                core_txd <= #1 ack_in;
                                
                        //停止状态
                        ST_STOP:
                            if (core_ack)
                                begin
                                    c_state <= #1 ST_IDLE;
                                    core_cmd <= #1 `I2C_CMD_NOP;
                                end
                    endcase
                end
                
endmodule

3.3 程序主体的实现

程序主体部分完成与外部程序的接口、与总线上外部节点的连线、完成程序内部各个寄存器的构建、控制字节传输控制模块等功能。代码如下:

代码语言:javascript复制
`include "timescale.v"
`include "i2c_master_defines.v"
//模块定义
module i2c_master_top(
    wb_clk_i, wb_rst_i, arst_i, wb_adr_i, wb_dat_i, wb_dat_o,
    wb_we_i, wb_stb_i, wb_cyc_i, wb_ack_o, wb_inta_o,
    scl_pad_i, scl_pad_o, scl_padoen_o, sda_pad_i, sda_pad_o, sda_padoen_o );
    
    // 参数
    parameter ARST_LVL = 1'b0; // 异步 reset 信号
    
    // 输入、输出信号
    // 连接到外部接口的信号
    input clk_i; // 主节点时钟信号
    input rst_i; // 同步 reset 信号,高有效
    input arst_i; // 异步 reset 信号
    input [2:0] adr_i; // 低位地址信号
    input [7:0] dat_i; // 数据总线输入
    output [7:0] dat_o; // 数据总线输出
    input we_i; // 输入使能信号
    input stb_i; // 触发信号
    input cyc_i; // 总线周期输入
    output ack_o; // 应答信号输出
    output inta_o; // 中断请求信号输出
    reg [7:0] wb_dat_o;
    reg wb_ack_o;
    reg wb_inta_o;
    
    // I2C 信号
    // I2C 时钟信号线
    input scl_pad_i; // SCL 输入
    output scl_pad_o; // SCL 输出
    output scl_padoen_o; // SCL 输出使能
    
    // I2C 数据线
    input sda_pad_i; // SDA 输入
    output sda_pad_o; // SDA 输出
    output sda_padoen_o; // SDA 输出使能
    
    // 变量申明
    // 寄存器
    reg [15:0] prer; // 时钟分频寄存器
    reg [ 7:0] ctr; // 控制寄存器
    reg [ 7:0] txr; // 数据传输寄存器
    wire [ 7:0] rxr; // 数据接收寄存器
    reg [ 7:0] cr; // 命令寄存器
    wire [ 7:0] sr; // 状态寄存器
    
    // 完成信号,命令完成后清除命令寄存器
    wire done;
    
    // 模块使能信号
    wire core_en;
    wire ien;
    
    // 状态寄存器信号
    wire irxack;
    reg rxack; // 从从节点接收应答信号
    reg tip; // 传输进行标志
    reg irq_flag; // 中断挂起标志
    wire i2c_busy; // 总线忙标志
    wire i2c_al; // 总线仲裁丢失
    reg al; // 状态寄存器仲裁丢失位
    
    // 模块主体
    // 产生内部 reset
    wire rst_i = arst_i ^ ARST_LVL;
    wire wacc = cyc_i & stb_i & we_i;
    
    // 产生应答输出信号
    always @(posedge clk_i)
        wb_ack_o <= #1 cyc_i & stb_i & ~ack_o;
    
    // 数据输出
    always @(posedge clk_i)
        begin
            case (adr_i)
                3'b000: wb_dat_o = prer[ 7:0];
                3'b001: wb_dat_o = prer[15:8];
                3'b010: wb_dat_o = ctr;
                3'b011: wb_dat_o = rxr; // 写数据传输寄存器
                3'b100: wb_dat_o = sr; // 写命令寄存器
                3'b101: wb_dat_o = txr;
                3'b110: wb_dat_o = cr;
                3'b111: wb_dat_o = 0; // 保留位
            endcase
        end
        
    // 产生寄存器
    always @(posedge wb_clk_i or negedge rst_i)
        if (!rst_i)
            begin
                prer <= #1 16'hffff;
                ctr <= #1 8'h0;
                txr <= #1 8'h0;
            end
        else if (wb_rst_i)
            begin
                prer <= #1 16'hffff;
                ctr <= #1 8'h0;
                txr <= #1 8'h0;
            end
        else
            if (wb_wacc)
                case (wb_adr_i) // synopsis full_case parallel_case
                    3'b000 : prer [ 7:0] <= #1 wb_dat_i;
                    3'b001 : prer [15:8] <= #1 wb_dat_i;
                    3'b010 : ctr <= #1 wb_dat_i;
                    3'b011 : txr <= #1 wb_dat_i;
                endcase
    
    // 产生命令寄存器
    always @(posedge wb_clk_i or negedge rst_i)
        if (~rst_i)
            cr <= #1 8'h0;
        else if (wb_rst_i)
            cr <= #1 8'h0;
        else if (wb_wacc)
            begin
                if (core_en & (wb_adr_i == 3'b100) )
                    cr <= #1 wb_dat_i;
            end
         else
            begin
                if (done | i2c_al)
                    cr[7:4] <= #1 4'h0; // 命令完成或者仲裁丢失时清除命令寄存器内容
                    cr[2:1] <= #1 2'b0; // 保留位
                    cr[0] <= #1 2'b0; // 清除 IRQ_ACK 位
            end
            
    // 译码命令寄存器
    wire sta = cr[7];
    wire sto = cr[6];
    wire rd = cr[5];
    wire wr = cr[4];
    wire ack = cr[3];
    wire iack = cr[0];
    
    // 译码控制寄存器
    assign core_en = ctr[7];
    assign ien = ctr[6];
    
    // 连接字节控制模块
    i2c_master_byte_ctrl byte_controller (
            .clk ( wb_clk_i ),
            .rst ( wb_rst_i ),
            .nReset ( rst_i ),
            .ena ( core_en ),
            .clk_cnt ( prer ),
            .start ( sta ),
            .stop ( sto ),
            .read ( rd ),
            .write ( wr ),
            .ack_in ( ack ),
            .din ( txr ),
            .cmd_ack ( done ),
            .ack_out ( irxack ),
            .dout ( rxr ),
            .i2c_busy ( i2c_busy ),
            .i2c_al ( i2c_al ),
            .scl_i ( scl_pad_i ),
            .scl_o ( scl_pad_o ),
            .scl_oen ( scl_padoen_o ),
            .sda_i ( sda_pad_i ),
            .sda_o ( sda_pad_o ),
            .sda_oen ( sda_padoen_o )
        )
        
    // 状态寄存器部分和中断请求信号
    always @(posedge wb_clk_i or negedge rst_i)
        if (!rst_i)
            begin
                al <= #1 1'b0;
                rxack <= #1 1'b0;
                tip <= #1 1'b0;
                irq_flag <= #1 1'b0;
            end
        else if (wb_rst_i)
            begin
                al <= #1 1'b0;
                rxack <= #1 1'b0;
                tip <= #1 1'b0;
                irq_flag <= #1 1'b0;
            end
        else
            begin
                al <= #1 i2c_al | (al & ~sta);
                rxack <= #1 irxack;
                tip <= #1 (rd | wr);
                irq_flag <= #1 (done | i2c_al | irq_flag) & ~iack;   
            end
            
    // 中断请求标志
    // 产生中断请求信号
    always @(posedge wb_clk_i or negedge rst_i)
        if (!rst_i)
            wb_inta_o <= #1 1'b0;
        else if (wb_rst_i)
            wb_inta_o <= #1 1'b0;
        else
            wb_inta_o <= #1 irq_flag && ien; //中断使能位 IEN 设置后产生中断信号
    
    assign sr[7] = rxack;
    assign sr[6] = i2c_busy;
    assign sr[5] = al;
    assign sr[4:2] = 3'h0; // reserved
    assign sr[1] = tip;
    assign sr[0] = irq_flag;
    
endmodule

本篇到此结束,下一篇带来基于 FPGA 的 模拟 I²C 协议设计(下),程序的仿真与测试,包括主节点的仿真、从节点的仿真、仿真主程序、仿真结果以及总结等相关内容。

END

后续会持续更新,带来Vivado、 ISE、Quartus II 、candence等安装相关设计教程,学习资源、项目资源、好文推荐等,希望大侠持续关注。

大侠们,江湖偌大,继续闯荡,愿一切安好,有缘再见!

0 人点赞