I2C协议master设备的FPGA实现

来源:互联网 发布:编程需要数学计算吗 编辑:程序博客网 时间:2024/04/20 18:32

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


需求由来

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

时序协议分析

言归正传,这里上MT9V034数据手册上的时序:
16bits 写时序
16bits 读时序
协议这里就不再详述,这里主要讲讲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不必考虑总线时钟与模块时钟的数据同步,而寄存器表虽然控制容易且规范但是却无法避免此问题。下面简单给出了框架:
框架
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      endendmodule

i2c_master_cmd_ctrl.v

`timescale 1ns / 10psmodule 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;    endendmodule

i2c_master.v

/* * An i2c master controller implementation. 7-bit address 8-bit data, r/w. * * Copyright (c) 2015 Joel Fernandes <joel@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 / 10psmodule 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中调用并配置:
FIFO配置
模块输入时钟为8MHz;
写操作方法按照如下顺序依次向FIFO_TX写入字节:
配置字节(1Byte)->从设备地址(1Byte)->寄存器地址(1Byte)->数据(nBytes);
读操作方法按照写操作的顺序写入FIFO_RX,省去最后的数据部分,逻辑完成操作后会把指定字节数据写入FIFO_RX中,用户可以从中读取期望的数据;
需要说明的是,以上配置字节的高6位为本次操作的数据长短,低2位用于切换i2c通道(reserved),从设备地址的第0位指示本次是读还是写操作。

STG :

篇幅限制,这里简单给出i2c_master_cmd_ctrl的状态转移图:
ASM


验证结果

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

小结

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

原创粉丝点击