I2C协议master设备的FPGA实现

    • 需求由来
    • 时序协议分析
    • 架构设计
    • 设计代码
    • 代码说明
    • STG
    • 验证结果
    • 小结


需求由来

I2C协议广泛用于短距板级低速通信,尤其是处理器与各类芯片诸如传感器的配置等功能。本人由于需要使用MT9V034完成图像采集,以及驱动ZedBoard板子上的ADV7511用于HDMI显示,需要用到I2C通信接口。可供选择的方案是使用PS端的I2C硬件接口、IP Catalog中的AXI I2C IP核,以及自己编写HDL。考虑到手头上的两个MT9V034模块地址相同、版权、开发时间等问题,最终选择在搜集到资料基础上自己改写。

时序协议分析

言归正传,这里上MT9V034数据手册上的时序:
I2C协议master设备的FPGA实现_第1张图片
I2C协议master设备的FPGA实现_第2张图片
协议这里就不再详述,这里主要讲讲HDL的实现思路。可以看到,时序可以划分成4种操作:起始位start、停止位stop、写Byte和读Byte(包括应答信号),进一步划分可以到相应的位操作上。所以这里采用”Bit-Byte-Command”的设计思路,由低层次到高层次设计:先设计位操作级控制器(包括start-bit、restart-bit、stop-bit、write-bit、read-bit操作),然后设计字节操作级控制器,最后是命令级控制器,控制器借助FSM。

架构设计

这里首先需要考虑的是输入和输出。输出不必说,输入应该是AXI总线,然后总线连接控制器。这里将输入端口设计成TX/RX-FIFO形式,主要考虑时钟同步——使用异步FIFO不必考虑总线时钟与模块时钟的数据同步,而寄存器表虽然控制容易且规范但是却无法避免此问题。下面简单给出了框架:
I2C协议master设备的FPGA实现_第3张图片
FIFO端口连接到AXI-Stream需要注意,单次发送包大小需要处理一下(或者在应用端处理一下),避免应用端写1Byte而硬件部分没有执行操作。

设计代码

i2c_master_defines.vh

`define I2C_CMD_NOP   4'b0000
`define I2C_CMD_START 4'b0001
`define I2C_CMD_STOP  4'b0010
`define I2C_CMD_WRITE 4'b0100
`define I2C_CMD_READ  4'b1000

i2c_master_bit_ctrl.v

`timescale 1ns / 10ps
//
/////////////////////////////////////
// Bit controller section
/////////////////////////////////////
//
// Translate simple commands into SCL/SDA transitions
// Each command has 5 states, A/B/C/D/idle
//
// start:   SCL ~~~~~~~~~~\____
//  SDA ~~~~~~~~\______
//       x | A | B | C | D | i
//
// repstart SCL ____/~~~~\___
//  SDA __/~~~\______
//       x | A | B | C | D | i
//
// stop SCL ____/~~~~~~~~
//  SDA ==\____/~~~~~
//       x | A | B | C | D | i
//
//- write   SCL ____/~~~~\____
//  SDA ==X=========X=
//       x | A | B | C | D | i
//
//- read    SCL ____/~~~~\____
//  SDA XXXX=====XXXX
//       x | A | B | C | D | i
//

// Timing:     Normal mode      Fast mode
///////////////////////////////////////////////////////////////////////
// Fscl        100KHz           400KHz
// Th_scl      4.0us            0.6us   High period of SCL
// Tl_scl      4.7us            1.3us   Low period of SCL
// Tsu:sta     4.7us            0.6us   setup time for a repeated start condition
// Tsu:sto     4.0us            0.6us   setup time for a stop conditon
// Tbuf        4.7us            1.3us   Bus free time between a stop and start condition
//

// synopsys translate_off
//`include "timescale.v"
// synopsys translate_on

`include "i2c_master_defines.vh"

module i2c_master_bit_ctrl(
    clk, rst,
    clk_cnt, ena, cmd, cmd_ack, busy, al, din, dout,
    scl_i, scl_o, scl_oen, sda_i, sda_o, sda_oen
    );

    //
    // inputs & outputs
    //
    input clk;
    input rst;
    input ena;            // core enable signal

    input [15:0] clk_cnt; // clock prescale value

    input  [3:0] cmd;
    output       cmd_ack; // command complete acknowledge
    reg cmd_ack;
    output       busy;    // i2c bus busy
    reg busy;
    output       al;      // i2c bus arbitration lost
    reg al;

    input  din;
    output dout;
    reg dout;

    // I2C lines
    input  scl_i;         // i2c clock line input
    output scl_o;         // i2c clock line output
    output scl_oen;       // i2c clock line output enable (active low)
    reg scl_oen = 1'b1;
    input  sda_i;         // i2c data line input
    output sda_o;         // i2c data line output
    output sda_oen;       // i2c data line output enable (active low)
    reg sda_oen = 1'b1;

    //
    // variable declarations
    //
    reg sSCL, sSDA;             // synchronized SCL and SDA inputs
    reg dscl_oen;               // delayed scl_oen
    reg sda_chk;                // check SDA output (Multi-master arbitration)
    reg clk_en;                 // clock generation signals
    wire slave_wait;
//  reg [15:0] cnt = clk_cnt;   // clock divider counter (simulation)
    reg [15:0] cnt;             // clock divider counter (synthesis)

    //
    // module body
    //

    // whenever the slave is not ready it can delay the cycle by pulling SCL low
    // delay scl_oen
    always @(posedge clk)
      dscl_oen <= #1 scl_oen;

    assign slave_wait = dscl_oen && !sSCL;

    // generate clk enable signal
    always @(posedge clk)
      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


    // generate bus status controller
    reg dSCL, dSDA;
    reg sta_condition;
    reg sto_condition;

    // synchronize SCL and SDA inputs
    // reduce metastability risc
    always @(posedge clk)
      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

    // detect start condition => detect falling edge on SDA while SCL is high
    // detect stop condition => detect rising edge on SDA while SCL is high
    always @(posedge clk)
      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

    // generate i2c bus busy signal
    always @(posedge clk)
      if (rst)
        busy <= #1 1'b0;
      else
        busy <= #1 (sta_condition | busy) & ~sto_condition;

    // generate arbitration lost signal
    // aribitration lost when:
    // 1) master drives SDA high, but the i2c bus is low
    // 2) stop detected while not requested
    reg cmd_stop, dcmd_stop;
    always @(posedge clk)
      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
    // generate dout signal (store SDA on rising edge of SCL)
    always @(posedge clk)
      if(sSCL & ~dSCL)
        dout <= #1 sSDA;

    // generate statemachine

    // nxt_state decoder
    parameter [17:0] idle    = 18'b0_0000_0000_0000_00000;
    parameter [17:0] start_a = 18'b0_0000_0000_0000_00010;
    parameter [17:0] start_b = 18'b0_0000_0000_0000_00100;
    parameter [17:0] start_c = 18'b0_0000_0000_0000_01000;
    parameter [17:0] start_d = 18'b0_0000_0000_0000_10000;
    parameter [17:0] start_e = 18'b0_0000_0000_0001_00000;
    parameter [17:0] stop_a  = 18'b0_0000_0000_0010_00000;
    parameter [17:0] stop_b  = 18'b0_0000_0000_0100_00000;
    parameter [17:0] stop_c  = 18'b0_0000_0000_1000_00000;
    parameter [17:0] stop_d  = 18'b0_0000_0001_0000_00000;
    parameter [17:0] rd_a    = 18'b0_0000_0010_0000_00000;
    parameter [17:0] rd_b    = 18'b0_0000_0100_0000_00000;
    parameter [17:0] rd_c    = 18'b0_0000_1000_0000_00000;
    parameter [17:0] rd_d    = 18'b0_0001_0000_0000_00000;
    parameter [17:0] wr_a    = 18'b0_0010_0000_0000_00000;
    parameter [17:0] wr_b    = 18'b0_0100_0000_0000_00000;
    parameter [17:0] wr_c    = 18'b0_1000_0000_0000_00000;
    parameter [17:0] wr_d    = 18'b1_0000_0000_0000_00000;

    reg [17:0] c_state; // synopsis enum_state

    always @(posedge clk)
      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; // default no command acknowledge + assert cmd_ack only 1clk cycle

            if (clk_en)
              case (c_state) // synopsis full_case parallel_case
                // idle state
                idle:
                begin
                    case (cmd) // synopsis full_case parallel_case
                      `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; // keep SCL in same state
                    sda_oen <= #1 sda_oen; // keep SDA in same state
                    sda_chk <= #1 1'b0;    // don't check SDA output
                end

                // start
                start_a:
                begin
                    c_state <= #1 start_b;
                    scl_oen <= #1 scl_oen; // keep SCL in same state
                    sda_oen <= #1 1'b1;    // set SDA high
                    sda_chk <= #1 1'b0;    // don't check SDA output
                end

                start_b:
                begin
                    c_state <= #1 start_c;
                    scl_oen <= #1 1'b1; // set SCL high
                    sda_oen <= #1 1'b1; // keep SDA high
                    sda_chk <= #1 1'b0; // don't check SDA output
                end

                start_c:
                begin
                    c_state <= #1 start_d;
                    scl_oen <= #1 1'b1; // keep SCL high
                    sda_oen <= #1 1'b0; // set SDA low
                    sda_chk <= #1 1'b0; // don't check SDA output
                end

                start_d:
                begin
                    c_state <= #1 start_e;
                    scl_oen <= #1 1'b1; // keep SCL high
                    sda_oen <= #1 1'b0; // keep SDA low
                    sda_chk <= #1 1'b0; // don't check SDA output
                end

                start_e:
                begin
                    c_state <= #1 idle;
                    cmd_ack <= #1 1'b1;
                    scl_oen <= #1 1'b0; // set SCL low
                    sda_oen <= #1 1'b0; // keep SDA low
                    sda_chk <= #1 1'b0; // don't check SDA output
                end

                // stop
                stop_a:
                begin
                    c_state <= #1 stop_b;
                    scl_oen <= #1 1'b0; // keep SCL low
                    sda_oen <= #1 1'b0; // set SDA low
                    sda_chk <= #1 1'b0; // don't check SDA output
                end

                stop_b:
                begin
                    c_state <= #1 stop_c;
                    scl_oen <= #1 1'b1; // set SCL high
                    sda_oen <= #1 1'b0; // keep SDA low
                    sda_chk <= #1 1'b0; // don't check SDA output
                end

                stop_c:
                begin
                    c_state <= #1 stop_d;
                    scl_oen <= #1 1'b1; // keep SCL high
                    sda_oen <= #1 1'b0; // keep SDA low
                    sda_chk <= #1 1'b0; // don't check SDA output
                end

                stop_d:
                begin
                    c_state <= #1 idle;
                    cmd_ack <= #1 1'b1;
                    scl_oen <= #1 1'b1; // keep SCL high
                    sda_oen <= #1 1'b1; // set SDA high
                    sda_chk <= #1 1'b0; // don't check SDA output
                end

                // read
                rd_a:
                begin
                    c_state <= #1 rd_b;
                    scl_oen <= #1 1'b0; // keep SCL low
                    sda_oen <= #1 1'b1; // tri-state SDA
                    sda_chk <= #1 1'b0; // don't check SDA output
                end

                rd_b:
                begin
                    c_state <= #1 rd_c;
                    scl_oen <= #1 1'b1; // set SCL high
                    sda_oen <= #1 1'b1; // keep SDA tri-stated
                    sda_chk <= #1 1'b0; // don't check SDA output
                end

                rd_c:
                begin
                    c_state <= #1 rd_d;
                    scl_oen <= #1 1'b1; // keep SCL high
                    sda_oen <= #1 1'b1; // keep SDA tri-stated
                    sda_chk <= #1 1'b0; // don't check SDA output
                end

                rd_d:
                begin
                    c_state <= #1 idle;
                    cmd_ack <= #1 1'b1;
                    scl_oen <= #1 1'b0; // set SCL low
                    sda_oen <= #1 1'b1; // keep SDA tri-stated
                    sda_chk <= #1 1'b0; // don't check SDA output
                end

                // write
                wr_a:
                begin
                    c_state <= #1 wr_b;
                    scl_oen <= #1 1'b0; // keep SCL low
                    sda_oen <= #1 din;  // set SDA
                    sda_chk <= #1 1'b0; // don't check SDA output (SCL low)
                end

                wr_b:
                begin
                    c_state <= #1 wr_c;
                    scl_oen <= #1 1'b1; // set SCL high
                    sda_oen <= #1 din;  // keep SDA
                    sda_chk <= #1 1'b1; // check SDA output
                end

                wr_c:
                begin
                    c_state <= #1 wr_d;
                    scl_oen <= #1 1'b1; // keep SCL high
                    sda_oen <= #1 din;
                    sda_chk <= #1 1'b1; // check SDA output
                end

                wr_d:
                begin
                    c_state <= #1 idle;
                    cmd_ack <= #1 1'b1;
                    scl_oen <= #1 1'b0; // set SCL low
                    sda_oen <= #1 din;
                    sda_chk <= #1 1'b0; // don't check SDA output (SCL low)
                end

              endcase
        end
    // assign scl and sda output (always gnd)
    assign scl_o = 1'b0;
    assign sda_o = 1'b0;
endmodule

i2c_master_byte_ctrl.v

`timescale 1ns / 10ps

`include "i2c_master_defines.vh"

module i2c_master_byte_ctrl (
    clk, rst, 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 );

    //
    // inputs & outputs
    //
    input clk;     // master clock
    input rst;     // synchronous active high reset
    input ena;     // core enable signal

    input [15:0] clk_cnt; // 4x SCL

    // control inputs
    input       start;
    input       stop;
    input       read;
    input       write;
    input       ack_in;
    input [7:0] din;

    // status outputs
    output       cmd_ack;
    reg cmd_ack;
    output       ack_out;
    reg ack_out;
    output       i2c_busy;
    output       i2c_al;
    output [7:0] dout;

    // I2C signals
    input  scl_i;
    output scl_o;
    output scl_oen;
    input  sda_i;
    output sda_o;
    output sda_oen;


    //
    // Variable declarations
    //

    // statemachine
    parameter [5:0] ST_IDLE  = 6'b0_00001;
    parameter [5:0] ST_START = 6'b0_00010;
    parameter [5:0] ST_READ  = 6'b0_00100;
    parameter [5:0] ST_WRITE = 6'b0_01000;
    parameter [5:0] ST_ACK   = 6'b0_10000;
    parameter [5:0] ST_STOP  = 6'b1_00000;

    // signals for bit_controller
    reg  [3:0] core_cmd;
    reg        core_txd;
    wire       core_ack, core_rxd;

    // signals for shift register
    reg [7:0] sr; //8bit shift register
    reg       shift, ld;

    // signals for state machine
    wire       go;
    reg  [2:0] dcnt;
    wire       cnt_done;

    //
    // Module body
    //

    // hookup bit_controller
    i2c_master_bit_ctrl bit_controller (
        .clk     ( clk      ),
        .rst     ( rst      ),
        .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  )
    );

    // generate go-signal
    assign go = (read | write | stop) & ~cmd_ack;

    // assign dout output to shift-register
    assign dout = sr;

    // generate shift register
    always @(posedge clk)
      if (rst)
        sr <= #1 8'h0;
      else if (ld)
        sr <= #1 din;
      else if (shift)
        sr <= #1 {sr[6:0], core_rxd};

    // generate counter
    always @(posedge clk)
      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);

    //
    // state machine
    //
    reg [5:0] c_state; // synopsis enum_state

    always @(posedge clk)
      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
          // initially reset all signals
          core_txd <= #1 sr[7];
          shift    <= #1 1'b0;
          ld       <= #1 1'b0;
          cmd_ack  <= #1 1'b0;

          case (c_state) // synopsis full_case parallel_case
            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;

                          // generate command acknowledge signal
                          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;       // stay in same state
                      core_cmd <= #1 `I2C_CMD_WRITE; // write next bit
                      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;       // stay in same state
                          core_cmd <= #1 `I2C_CMD_READ; // read next bit
                      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

                     // assign ack_out output to bit_controller_rxd (contains last received bit)
                     ack_out <= #1 core_rxd;

                     // generate command acknowledge signal
                     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

i2c_master_cmd_ctrl.v

`timescale 1ns / 10ps
module i2c_master_cmd_ctrl(
    input clk,
    input rst,
    input ena,
    //commands
    output reg          start,
    output reg          stop,
    output reg          write,
    output reg          read,
    output reg          ack_out,
    //data
    output reg [7:0]    txd,
    input wire [7:0]    rxd,
    //status
    input wire          cmd_ack,
    input wire          ack_in,
    input wire          i2c_busy,
    input wire          i2c_al,
    //tx_fifo
    output wire         rd_en,
    input wire          empty,
    input wire [7:0]    dout,
    //rx_fifo
    output reg         wr_en,
    input wire          full,
    output reg [7:0]    din,
    //
    output reg         missed_ack
    );
    //parameter
    localparam [10:0] ST_IDLE =   11'b000_0000_0001,
                    ST_CFG       =   11'b000_0000_0010,
                    ST_TXDEVICE  =   11'b000_0000_0100,
                    ST_TXDEVICEW =   11'b000_0000_1000,
                    ST_TXADDR    =   11'b000_0001_0000,
                    ST_TXADDRW   =   11'b000_0010_0000,
                    ST_TXDATA    =   11'b000_0100_0000,
                    ST_TXDATAW   =   11'b000_1000_0000,
                    ST_RXDATA    =   11'b001_0000_0000,
                    ST_RXDATAW   =   11'b010_0000_0000,
                    ST_STOP      =   11'b100_0000_0000;
    //variables
    reg [10:0]  CS,NS;
    reg [1:0]   channel;
    reg [5:0]   length;
    reg [7:0]   device_addr;
    reg device_addr_resend;
    //state_machine
    //current state
    always @(posedge clk) begin
        if(rst) CS<= #1 ST_IDLE;
        else if(ena) CS<= #1 NS;
    end
    //next state
    always @(*)   begin
        case(CS)
        ST_IDLE:    NS<=(empty)? ST_IDLE:ST_CFG;
        ST_CFG:     NS<=ST_TXDEVICE;
        ST_TXDEVICE:NS<=ST_TXDEVICEW;
        ST_TXDEVICEW:NS<=(cmd_ack)? ((device_addr_resend)? ST_RXDATA:ST_TXADDR):ST_TXDEVICEW;
        ST_TXADDR:  NS<=ST_TXADDRW;
        ST_TXADDRW: NS<=(cmd_ack)? ((device_addr[0])? ST_TXDEVICE:ST_TXDATA):ST_TXADDRW;
        ST_TXDATA:  NS<=ST_TXDATAW;
        ST_TXDATAW: NS<=(cmd_ack)? ((length==6'd0)? ST_STOP:ST_TXDATA):ST_TXDATAW;
        ST_RXDATA:  NS<=ST_RXDATAW;
        ST_RXDATAW: NS<=(cmd_ack)? ((length==6'd0)? ST_STOP:ST_RXDATA):ST_RXDATAW;
        ST_STOP:    NS<=ST_IDLE;
        default:    NS<=ST_IDLE;
        endcase
    end
    //outputs
    //start
    always @(posedge clk)   begin
        if(rst) start<= #1 1'b0;
        else if(ena) start<= #1 (CS==ST_TXDEVICE)? 1'b1:1'b0;
    end
    //stop
    always @(posedge clk)   begin
        if(rst) stop<= #1 1'b0; 
        else if(ena) stop<= #1 (CS==ST_STOP)? 1'b1:1'b0;
    end
    //write
    always @(posedge clk)   begin
        if(rst) write<= #1 1'b0;
        else if(ena) write<= #1 ((CS==ST_TXDEVICE)||(CS==ST_TXADDR)||(CS==ST_TXDATA))? 1'b1:1'b0;
    end
    //read
    always @(posedge clk)   begin
        if(rst) read<= #1 1'b0;
        else if(ena) read<= #1 (CS==ST_RXDATA)? 1'b1:1'b0;
    end
    //ack_out
    always @(posedge clk)   begin
        if(rst) ack_out<= #1 1'b0;
        else if(ena) ack_out<= #1 ((CS==ST_RXDATAW)&&(length!=0))? 1'b0:1'b1;
    end
    //length
    always @(posedge clk)  begin
        if(rst) length<= #1 6'd0;
        else if(ena) begin
            if(CS==ST_CFG) length<= #1 dout[7:2];
            else if((CS==ST_TXDATA)||(CS==ST_RXDATA)) length<= #1 length-6'd1;
        end
    end
    //channel
    always @(posedge clk)  begin
        if(rst) channel<= #1 2'd0;
        else if(ena) begin
            if(CS==ST_CFG) channel<= #1 dout[1:0];
        end
    end
    //device_addr
    always @(posedge clk)  begin
        if(rst) device_addr<= #1 8'd0;
        else if(ena) begin
            if(CS==ST_STOP) device_addr<= #1 8'd0; 
            else if((CS==ST_TXDEVICE)&&(device_addr_resend==0)) 
                device_addr<= #1 dout;
        end 
    end
    //device_addr_resend
    always @(posedge clk)  begin
        if(rst) device_addr_resend<= #1 1'd0;
        else if(ena) begin
            if(CS==ST_STOP) device_addr_resend<= #1 1'd0;
            else if((CS==ST_TXADDR)&&(device_addr[0]==1)) 
                device_addr_resend<= #1 1'd1;
        end 
    end
    //missed_ack
    always @(posedge clk)  begin
        if(rst) missed_ack<= #1 1'b0;
        else if(ena) begin
            if(((CS==ST_TXDEVICEW)||(CS==ST_TXADDRW)||(CS==ST_TXDATAW))&&cmd_ack) 
                missed_ack<= #1 ack_in;
            else missed_ack<= #1 1'b0;
        end
    end
    //fifo_tx
    //rd_en
    assign rd_en=((NS==ST_CFG)||(NS==ST_TXDEVICE)||(NS==ST_TXADDR)||
                (NS==ST_TXDATA))? 1'b1:1'b0;
    always @(posedge clk)   begin
        if(rst) txd<= #1 8'd0; 
        else if(ena) begin
            if(CS==ST_TXDEVICE) txd<= #1 (device_addr_resend)? device_addr:(dout&8'hfe);
            else if((CS==ST_TXADDR)||(CS==ST_TXDATA)) txd<= #1 dout;
        end//txd<= #1 ((CS==ST_TXDEVICE)||(CS==ST_TXADDR)||(CS==ST_TXDATA))? dout:txd;
    end
    //fifo_rx
    //wr_en
    always @(posedge clk)   begin
        if(rst) wr_en<= #1 1'b0; 
        else if(ena) wr_en<= #1 ((CS==ST_RXDATAW) && cmd_ack && ~i2c_al)? 1'b1:1'b0;
    end
    always @(posedge clk)   begin
        if(rst) din<= #1 8'd0; 
        else if(ena) din<= #1 rxd;
    end
endmodule

i2c_master.v

/*
 * An i2c master controller implementation. 7-bit address 8-bit data, r/w.
 *
 * Copyright (c) 2015 Joel Fernandes @linuxinternals.org>
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 */

`timescale 1ns / 10ps

module i2c_master(
    input   wire          clk,
    input   wire          rst,
    input   wire          ena,
    // Xillybus
    input   wire   bus_clk,
    input   wire   quiesce,
    // Wires related to /dev/xillybus_read_8
    input   wire  user_r_read_8_rden,
    output  wire  user_r_read_8_empty,
    output  wire [7:0] user_r_read_8_data,
    input   wire  user_r_read_8_open,
    // Wires related to /dev/xillybus_write_8
    input   wire  user_w_write_8_wren,
    output  wire  user_w_write_8_full,
    input   wire [7:0] user_w_write_8_data,
    input   wire  user_w_write_8_open,
    // Wires of i2c
    inout   wire          scl_pin,
    inout   wire          sda_pin,
    //output  wire          scl_oen,
    //output  wire          sda_oen,
    //missed_ack
    output  wire          missed_ack
    );
    /*  variables   */
    wire tx_fifo_rd_en,rx_fifo_wr_en;
    wire tx_fifo_empty,rx_fifo_full;
    wire [7:0] tx_fifo_dout,rx_fifo_din;
    wire start,stop,read,write;
    wire ack_out,ack_in;
    wire cmd_ack;
    wire [7:0] txd,rxd;
    wire i2c_busy,i2c_al;
    wire scl_o,scl_oen;
    wire sda_o,sda_oen;
    /*  connection  */
    assign scl_pin = (scl_oen==1)? 1'bz:scl_o;
    assign scl_i = scl_pin;
    assign sda_pin = (sda_oen==1)? 1'bz:sda_o;
    assign sda_i = sda_pin;
    /*  module initialization   */
    // hookup the tx_fifo block
    i2c_master_fifo FIFO_TX (
    .rst      ( quiesce&~user_w_write_8_open),        // input wire rst
    .wr_clk   ( bus_clk            ),  // input wire wr_clk
    .rd_clk   ( clk                ),  // input wire rd_clk
    .din      ( user_w_write_8_data),        // input wire [7 : 0] din
    .wr_en    ( user_w_write_8_wren),    // input wire wr_en
    .rd_en    ( tx_fifo_rd_en      ),    // input wire rd_en
    .dout     ( tx_fifo_dout       ),      // output wire [7 : 0] dout
    .full     ( user_w_write_8_full),      // output wire full
    .empty    ( tx_fifo_empty      )    // output wire empty
    );
    // hookup the rx_fifo block
    i2c_master_fifo FIFO_RX (
    .rst      ( quiesce&~user_r_read_8_open),        // input wire rst
    .wr_clk   ( clk                ),  // input wire wr_clk
    .rd_clk   ( bus_clk            ),  // input wire rd_clk
    .din      ( rx_fifo_din        ),        // input wire [7 : 0] din
    .wr_en    ( rx_fifo_wr_en      ),    // input wire wr_en
    .rd_en    ( user_r_read_8_rden ),    // input wire rd_en
    .dout     ( user_r_read_8_data ),      // output wire [7 : 0] dout
    .full     ( rx_fifo_full       ),      // output wire full
    .empty    ( user_r_read_8_empty)    // output wire empty
    );
    // hookup the command controller block
    i2c_master_cmd_ctrl DUT_CMD(
    .clk      ( clk          ),
    .rst      ( rst          ),
    .ena      ( ena          ),
    .start    ( start        ),
    .stop     ( stop         ),
    .write    ( write        ),
    .read     ( read         ),
    .ack_out  ( ack_out      ),
    .txd      ( txd          ),
    .rxd      ( rxd          ),
    .cmd_ack  ( cmd_ack      ),
    .ack_in   ( ack_in       ),
    .i2c_busy ( i2c_busy     ),
    .i2c_al   ( i2c_al       ),
    .rd_en    ( tx_fifo_rd_en),
    .empty    ( tx_fifo_empty),
    .dout     ( tx_fifo_dout ),
    .wr_en    ( rx_fifo_wr_en),
    .full     ( rx_fifo_full ),
    .din      ( rx_fifo_din  ),
    .missed_ack(missed_ack   )
    );
    // hookup byte controller block
    i2c_master_byte_ctrl DUT_BYTE (
    .clk      ( clk          ),
    .rst      ( rst          ),
    .ena      ( ena          ),
    .clk_cnt  ( 16'h0003     ),
    .start    ( start        ),
    .stop     ( stop         ),
    .read     ( read         ),
    .write    ( write        ),
    .ack_in   ( ack_out      ),
    .din      ( txd          ),
    .cmd_ack  ( cmd_ack      ),
    .ack_out  ( ack_in       ),
    .dout     ( rxd          ),
    .i2c_busy ( i2c_busy     ),
    .i2c_al   ( i2c_al       ),
    .scl_i    ( scl_i        ),
    .scl_o    ( scl_o        ),
    .scl_oen  ( scl_oen      ),
    .sda_i    ( sda_i        ),
    .sda_o    ( sda_o        ),
    .sda_oen  ( sda_oen      )
    );
endmodule

代码说明

这里用到了一个FIFO IP核,Vivado中调用并配置:
I2C协议master设备的FPGA实现_第4张图片
模块输入时钟为8MHz;
写操作方法按照如下顺序依次向FIFO_TX写入字节:
配置字节(1Byte)->从设备地址(1Byte)->寄存器地址(1Byte)->数据(nBytes);
读操作方法按照写操作的顺序写入FIFO_RX,省去最后的数据部分,逻辑完成操作后会把指定字节数据写入FIFO_RX中,用户可以从中读取期望的数据;
需要说明的是,以上配置字节的高6位为本次操作的数据长短,低2位用于切换i2c通道(reserved),从设备地址的第0位指示本次是读还是写操作。

STG :

篇幅限制,这里简单给出i2c_master_cmd_ctrl的状态转移图:
I2C协议master设备的FPGA实现_第5张图片


验证结果

这里设计的功能是单主设备的读写操作,注意并不支持多主设备总线操作;验证时,写操作用功能仿真的方法很容易,写操作还要建立i2c_slave模型。为了简化,直接使用Chipscope的逻辑分析仪功能板上调试:
write测试:
I2C协议master设备的FPGA实现_第6张图片
read测试:
I2C协议master设备的FPGA实现_第7张图片
以上写测试时依次向FIFO_TX写入0x09-0x90-0x01-0x00-0xaa
从设备地址:0x90
存储地址:0x01
写入数据:0x00aa
然后读出向FIFO_RX写入0x09-0x91-0x00
从设备地址:0x90
存储地址:0x00
读出数据:0x1324
这符合MT9V034的默认数值:
I2C协议master设备的FPGA实现_第8张图片
我用的测试办法是将逻辑接入xillybus测试的,实际上也可以组织成AXI-stream总线,在SDK中裸机测试。

小结

(1)本文工作的部分代码取自网站电子发烧友,这里将根据需求将原来的wishbone总线去掉并改写成FIFO的接口模式,便于连接其他总线
(2)工程通过了板上测试,具备master端的基本读写能力,操作简单,需要的可以借鉴一下

你可能感兴趣的:(通信接口控制器)