【【IIC模块Verilog实现---用IIC协议从FPGA端读取E2PROM】】

发布时间:2023年12月24日

IIC模块Verilog实现–用IIC协议从FPGA端读取E2PROM

下面是 design 设计

I2C_dri.v

module IIC_CONTROL #(
    parameter             SLAVE_ADDR = 7'b1010000         , // E2PROM 从机地址
    parameter             CLK_FREQ   = 26'd50_000_000     , // 50MHz 的时钟频率
    parameter             I2C_FREQ   = 18'd250_000          // SCL 的时钟频率
  )
  (
    input                            clk                  ,
    input                            rst_n                ,
    //  ----------------------------------------------  //
    input            [15 : 0]         i2c_addr            ,    // 地址
    input            [7  : 0]         i2c_data_w          ,    // 数据
    input                             i2c_rh_wl           ,    // 判断 是 read or write
    input                             bit_control         ,    // 1是 16位 0 是 8位
    input                             i2c_exec            ,

    // ------------------------------------------------    //
    output   reg                      dri_clk             ,
    output   reg     [7 : 0]          i2c_data_r          ,
    output   reg                      i2c_ack             ,
    output   reg                      i2c_done            ,

    // -------------------------------------------------- //
    output   reg                      scl                 ,
    inout                             sda
  );


  // --------------------------------------------------------//
  //    next is  define                                       //
  // --------------------------------------------------------//
  reg         [9 : 0]             clk_cnt              ;
  wire        [8 : 0]             dri_cnt              ;
  reg         [2 : 0]             state                ;
  reg         [2 : 0]             next_state           ;
  reg                             st_done              ; // 在 状态机里面用来提示数据完成可以跳转
  reg                             sda_dir              ; // sda方向控制器
  reg                             sda_out              ; // 选择FPGA输入模式之后赋予sda线上
  wire                            sda_in               ; // sda输入信号
  reg         [6 : 0]             cnt                  ; // 我们为了第三部分状态机而准备的

  reg         [15: 0]             addr_save            ; // 地址存储
  reg         [7 : 0]             data_w_save          ; // 数据写的暂存
  reg                             wr_flag              ; // 0 是 写 1 是 读
  // 这三个是 暂存的方便调度的

  reg         [7 : 0]             data_r_save          ; // 读到的数据存储方便整合

  // --------------------------------------------------------- //
  //  parameter define                                         //
  parameter                 st_idle      =  3'b000        ;  // 空闲状态
  parameter                 st_sladdr    =  3'b001        ;  // 发送器件地址
  parameter                 st_addr16    =  3'b010        ;  // 发送高八位地址
  parameter                 st_addr8     =  3'b011        ;  // 发送低八位地址
  parameter                 st_data_wr   =  3'b100        ;  // 写数据
  parameter                 st_addr_rd   =  3'b101        ;  // 再次发送器件地址读
  parameter                 st_data_rd   =  3'b110        ;  // 读数据
  parameter                 st_stop      =  3'b111        ;  // 结束操作停止位


  //    ---------------------------------------------------- //
  //    next is main  code                                   //
  // -------------------------------------------------------//
  assign dri_cnt = (CLK_FREQ/I2C_FREQ ) >> 2                ;

  always@(posedge clk or negedge rst_n )
  begin
    if(rst_n == 0)
    begin
      dri_clk   <=  0         ;
      clk_cnt   <=  0         ;
    end
    else if( clk_cnt == dri_cnt[8:1] - 1)
    begin
      clk_cnt   <=  0         ;
      dri_clk   <=  ~dri_clk  ;
    end
    else
    begin
      dri_clk   <=  dri_clk     ;
      clk_cnt   <=  clk_cnt + 1 ;
    end
  end

  // 下面开始状态机的叙述




  // 同步时序描述状态转移
  always@(posedge dri_clk or negedge rst_n)
  begin
    if(rst_n == 0)
    begin
      state <= st_idle ;
    end // 处于空闲状态
    else
    begin
      state <= next_state ;
    end
  end



  // 组合逻辑判断状态转移条件
  always@(*)
  begin
    next_state <= st_idle ;
    case(state)

      st_idle :
      begin
        if(i2c_exec == 1)
        begin
          next_state      <= st_sladdr ;
        end
        else
        begin
          next_state      <= st_idle   ;
        end
      end
      // 当触发了i2c_exec 时候 可以由 空闲状态转移到

      st_sladdr :
      begin
        if(st_done == 1)
        begin
          if(bit_control == 1)
            next_state <= st_addr16 ;
          else
            next_state <= st_addr8  ;
        end
        else
        begin
          next_state <= st_sladdr ;
        end
      end

      // 当 触发了 st_done 之后 通过 bit_control 选择是低八位 还是高八位的传输

      st_addr16 :
      begin
        if(st_done == 1)
        begin
          next_state <= st_addr8 ;
        end
        else
        begin
          next_state <= st_addr16 ;
        end
      end

      // 高位 用完 轮到 低位的 传输

      st_addr8  :
      begin
        if(st_done == 1)
        begin
          if(wr_flag == 0)
            next_state <= st_data_wr ;
          else
            next_state <= st_addr_rd ;
        end
        else
        begin
          next_state <= st_addr8 ;
        end
      end

      // 先来判断 写数据的 st_data_wr 数据代号是 4
      st_data_wr :
      begin
        if(st_done == 1)
        begin
          next_state <= st_stop ;
        end
        else
        begin
          next_state <= st_data_wr ;
        end
      end

      //

      st_addr_rd :
      begin
        if(st_done == 1)
        begin
          next_state <= st_data_rd ;
        end
        else
        begin
          next_state <= st_addr_rd ;
        end
      end

      //

      st_data_rd :
      begin
        if(st_done == 1)
        begin
          next_state <= st_stop ;
        end
        else
        begin
          next_state <= st_data_rd ;
        end
      end

      //

      st_stop :
      begin
        if(st_done == 1)
        begin
          next_state <= st_idle ;
        end
        else
        begin
          next_state <= st_stop ;
        end
      end

      default:
        next_state <= st_idle ;
    endcase
  end

  / 下面来考虑另一个状态机的第三部分   --- 时序电路描述状态输出
  // 设置一个变量 来控制 SDA的朝向
  assign     sda    = sda_dir ? sda_out : 1'bz   ;  // sda_dir 为1 FPGA控制
  assign     sda_in = sda                        ;  // 把sda当成了输出


  always@(posedge dri_clk or negedge rst_n )
  begin
    if( rst_n == 0)
    begin
      //首先根据输入输出 来判断 SCL 与 SDA 必须都为高
      scl         <=    1 ;
      sda_dir     <=    1 ;
      sda_out     <=    1 ;
      // 剩下的输出 i2c_data_r(输出) == data_r_save
      i2c_data_r  <=    0 ;
      data_r_save <=    0 ;
      // 下面是端口的另外两个输出 i2c_ack 和 i2c_done
      i2c_ack     <=    0 ;
      i2c_done    <=    0 ;

      // 接下里是 内部信号的调节  这两个一个是内部后续的计数 还有一个本次case完成的结束信号
      cnt         <=    0 ;
      st_done     <=    0 ;

      // 下面是三个暂存信号一个是 读写标志位 还有 传入的地址暂存 传入的数据暂存
      wr_flag     <=    0 ;
      addr_save   <=    0 ;
      data_w_save <=    0 ;
    end
    else
    begin
      st_done   <=    0    ;    // 脉冲信号
      cnt       <= cnt + 1 ;
      //这里写在了 case之前就代表了 不用刻意在内部去调配 st_done 或是cnt
      case(state)
        st_idle :
        begin
          scl         <=    1 ;
          sda_dir     <=    1 ;
          sda_out     <=    1 ;
          //这两个写不写不所谓 因为根本没用到
          i2c_data_r  <=    0 ;
          data_r_save <=    0 ;
          i2c_done    <=    0 ;
          //
          cnt         <=    0 ;
          st_done     <=    0 ;
          // 开始
          if( i2c_exec == 1)
          begin
            wr_flag     <=    i2c_rh_wl  ;
            addr_save   <=    i2c_addr   ;
            data_w_save <=    i2c_data_w ;
            i2c_ack     <=    0 ;
          end
        end
        // 这里先传递的是
        st_sladdr :
        begin
          case(cnt)
            7'd1  :
              sda_out <=  0             ;
            7'd3  :
              scl     <=  0             ;
            7'd4  :
              sda_out <=  SLAVE_ADDR[6] ;

            7'd5  :
              scl     <=  1'b1          ;
            7'd7  :
              scl     <=  1'b0          ;
            7'd8  :
              sda_out <=  SLAVE_ADDR[5] ;

            7'd9  :
              scl     <=  1'b1          ;
            7'd11 :
              scl     <=  1'b0          ;
            7'd12 :
              sda_out <=  SLAVE_ADDR[4] ;

            7'd13 :
              scl     <=  1'b1          ;
            7'd15 :
              scl     <=  1'b0          ;
            7'd16 :
              sda_out <=  SLAVE_ADDR[3] ;

            7'd17 :
              scl     <=  1'b1          ;
            7'd19 :
              scl     <=  1'b0          ;
            7'd20 :
              sda_out <=  SLAVE_ADDR[2] ;

            7'd21 :
              scl     <=  1'b1          ;
            7'd23 :
              scl     <=  1'b0          ;
            7'd24 :
              sda_out <=  SLAVE_ADDR[1] ;

            7'd25 :
              scl     <=  1'b1          ;
            7'd27 :
              scl     <=  1'b0          ;
            7'd28 :
              sda_out <=  SLAVE_ADDR[0] ;

            7'd29 :
              scl     <=  1'b1          ;
            7'd31 :
              scl     <=  1'b0          ;
            7'd32 :
              sda_out <=  1'b0          ;
            // 此处完成了 数据的传递 接下来的任务是 反馈

            7'd33 :
              scl     <=  1'b1          ;
            7'd35 :
              scl     <=  1'b0          ;
            7'd36 :
              sda_dir <=  1'b0          ;   // 下放控制权给从机端口

            7'd37 :
              scl     <=  1'b1          ;
            // 下一时刻判断是否 有正确的反馈拉低 并确定 st_done = 1
            7'd38 :
            begin
              st_done         <=  1'b1          ;
              if( sda_in == 1)
                i2c_ack         <=  1'b1          ;
            end

            7'd39 :
            begin
              scl             <=  1'b0          ;
              cnt             <=  7'b0          ;
            end
            default :
              ;
          endcase
        end


        //发送高8位字节
        st_addr16 :
        begin
          case(cnt)
            7'd0 :
            begin   // 39之后移动一格就是0 0 此处即可以开始
              //把使能交还给FPGA端
              sda_dir     <=    1'b1              ;
              sda_out     <=    addr_save[15]     ;
            end

            // 第一个转换有点时序差距 后面都是 每隔4 sda变化一次

            7'd1   :
              scl         <=   1'b1             ;
            7'd3   :
              scl         <=   1'b0             ;
            7'd4   :
              sda_out     <=   addr_save[14]    ;

            7'd5   :
              scl         <=   1'b1             ;
            7'd7   :
              scl         <=   1'b0             ;
            7'd8   :
              sda_out     <=   addr_save[13]    ;

            7'd9   :
              scl         <=   1'b1             ;
            7'd11  :
              scl         <=   1'b0             ;
            7'd12  :
              sda_out     <=   addr_save[12]    ;

            7'd13  :
              scl         <=   1'b1             ;
            7'd15  :
              scl         <=   1'b0             ;
            7'd16  :
              sda_out     <=   addr_save[11]    ;

            7'd17  :
              scl         <=   1'b1             ;
            7'd19  :
              scl         <=   1'b0             ;
            7'd20  :
              sda_out     <=   addr_save[10]    ;

            7'd21  :
              scl         <=   1'b1             ;
            7'd23  :
              scl         <=   1'b0             ;
            7'd24  :
              sda_out     <=   addr_save[9]     ;

            7'd25  :
              scl         <=   1'b1             ;
            7'd27  :
              scl         <=   1'b0             ;
            7'd28  :
              sda_out     <=   addr_save[8]     ;
            // 29 拉升 31下降 32放控制权 33拉升 34结束并作判断 35 拉低 cnt归零为下一状态准备

            7'd29  :
              scl         <=   1'b1             ;
            7'd31  :
              scl         <=   1'b0             ;
            7'd32  :
              sda_dir     <=   1'b0             ;
            7'd33  :
              scl         <=   1'b1             ;

            7'd34  :
            begin
              st_done     <=   1'b1             ; //完成
              if(sda_in == 1)
                i2c_ack     <=   1'b1             ; // scl拉高时 反馈 i2c_ack = 1 表示有错误
            end

            7'd35  :
            begin
              scl         <=   1'b0             ;
              cnt         <=   7'b0             ;
            end
            default :
              ;
          endcase
        end



        //发送低8位字节
        st_addr8 :
        begin
          // 和上面这个写法是一样的 对于cnt = 0 sda_dir 交回FPGA控制权 并立刻赋值
          case(cnt)
            7'd0:
            begin
              sda_dir <= 1'b1 ;
              sda_out <= addr_save[7];         //字地址
            end
            7'd1  :
              scl     <= 1'b1;
            7'd3  :
              scl     <= 1'b0;
            7'd4  :
              sda_out <= addr_save[6];

            7'd5  :
              scl <= 1'b1;
            7'd7  :
              scl <= 1'b0;
            7'd8  :
              sda_out <= addr_save[5];

            7'd9  :
              scl <= 1'b1;
            7'd11 :
              scl <= 1'b0;
            7'd12 :
              sda_out <= addr_save[4];

            7'd13 :
              scl <= 1'b1;
            7'd15 :
              scl <= 1'b0;
            7'd16 :
              sda_out <= addr_save[3];

            7'd17 :
              scl <= 1'b1;
            7'd19 :
              scl <= 1'b0;
            7'd20 :
              sda_out <= addr_save[2];

            7'd21 :
              scl <= 1'b1;
            7'd23 :
              scl <= 1'b0;
            7'd24 :
              sda_out <= addr_save[1];

            7'd25 :
              scl <= 1'b1;
            7'd27 :
              scl <= 1'b0;
            7'd28 :
              sda_out <= addr_save[0];



            7'd29  :
              scl         <=   1'b1             ;
            7'd31  :
              scl         <=   1'b0             ;
            7'd32  :
              sda_dir     <=   1'b0             ;
            7'd33  :
              scl         <=   1'b1             ;

            7'd34  :
            begin
              st_done     <=   1'b1             ; //完成
              if(sda_in == 1)
                i2c_ack     <=   1'b1             ; // scl拉高时 反馈 i2c_ack = 1 表示有错误
            end

            7'd35  :
            begin
              scl         <=   1'b0             ;
              cnt         <=   7'b0             ;
            end
            default :
              ;
          endcase
        end



        //
        st_data_wr :
        begin
          // 和上面这个写法是一样的 对于cnt = 0 sda_dir 交回FPGA控制权 并立刻赋值
          case(cnt)
            7'd0:
            begin
              sda_dir <= 1'b1 ;
              sda_out <= data_w_save[7];         //字地址
            end
            7'd1  :
              scl     <= 1'b1;
            7'd3  :
              scl     <= 1'b0;
            7'd4  :
              sda_out <= data_w_save[6];

            7'd5  :
              scl <= 1'b1;
            7'd7  :
              scl <= 1'b0;
            7'd8  :
              sda_out <= data_w_save[5];

            7'd9  :
              scl <= 1'b1;
            7'd11 :
              scl <= 1'b0;
            7'd12 :
              sda_out <= data_w_save[4];

            7'd13 :
              scl <= 1'b1;
            7'd15 :
              scl <= 1'b0;
            7'd16 :
              sda_out <= data_w_save[3];

            7'd17 :
              scl <= 1'b1;
            7'd19 :
              scl <= 1'b0;
            7'd20 :
              sda_out <= data_w_save[2];

            7'd21 :
              scl <= 1'b1;
            7'd23 :
              scl <= 1'b0;
            7'd24 :
              sda_out <= data_w_save[1];

            7'd25 :
              scl <= 1'b1;
            7'd27 :
              scl <= 1'b0;
            7'd28 :
              sda_out <= data_w_save[0];

            // 29 拉升 31下降 32放控制权 33拉升 34结束并作判断 35 拉低 cnt归零为下一状态准备

            7'd29  :
              scl         <=   1'b1             ;
            7'd31  :
              scl         <=   1'b0             ;
            7'd32  :
              sda_dir     <=   1'b0             ;
            7'd33  :
              scl         <=   1'b1             ;

            7'd34  :
            begin
              st_done     <=   1'b1             ; //完成
              if(sda_in == 1)
                i2c_ack     <=   1'b1             ; // scl拉高时 反馈 i2c_ack = 1 表示有错误
            end

            7'd35  :
            begin
              scl         <=   1'b0             ;
              cnt         <=   7'b0             ;
            end
            default :
              ;
          endcase
        end


        // 读控制信号 可以开始读了
        st_addr_rd :
        begin
          // 这里的过程应该和上面的那个 st_sladdr一样 先写地址
          //  一样又不太一样
          case(cnt)
            7'd0 :
            begin
              sda_dir <= 1'b1;
              sda_out <= 1'b1;
            end
            7'd1 :
              scl <= 1'b1;
            7'd2 :
              sda_out <= 1'b0;          //重新开始
            7'd3 :
              scl <= 1'b0;
            7'd4 :
              sda_out <= SLAVE_ADDR[6]; //传送器件地址
            7'd5 :
              scl <= 1'b1;
            7'd7 :
              scl <= 1'b0;
            7'd8 :
              sda_out <= SLAVE_ADDR[5];
            7'd9 :
              scl <= 1'b1;
            7'd11:
              scl <= 1'b0;
            7'd12:
              sda_out <= SLAVE_ADDR[4];
            7'd13:
              scl <= 1'b1;
            7'd15:
              scl <= 1'b0;
            7'd16:
              sda_out <= SLAVE_ADDR[3];
            7'd17:
              scl <= 1'b1;
            7'd19:
              scl <= 1'b0;
            7'd20:
              sda_out <= SLAVE_ADDR[2];
            7'd21:
              scl <= 1'b1;
            7'd23:
              scl <= 1'b0;
            7'd24:
              sda_out <= SLAVE_ADDR[1];
            7'd25:
              scl <= 1'b1;
            7'd27:
              scl <= 1'b0;
            7'd28:
              sda_out <= SLAVE_ADDR[0];
            7'd29:
              scl <= 1'b1;
            7'd31:
              scl <= 1'b0;
            7'd32:
              sda_out <= 1'b1;          //1:读
            7'd33:
              scl <= 1'b1;
            7'd35:
              scl <= 1'b0;
            7'd36:
            begin
              sda_dir <= 1'b0;
              sda_out <= 1'b1;
            end
            7'd37:
              scl     <= 1'b1;
            7'd38:
            begin                     //从机应答
              st_done <= 1'b1;
              if(sda_in == 1'b1)           //高电平表示未应答
                i2c_ack <= 1'b1;         //拉高应答标志位
            end
            7'd39:
            begin
              scl <= 1'b0;
              cnt <= 7'b0;
            end
            default :
              ;
          endcase
        end

        st_data_rd :
        begin                        //读取数据(8 bit)
          case(cnt)
            7'd0:
              sda_dir <= 1'b0;
            7'd1:
              scl       <= 1'b1;
            7'd2 :
              data_r_save[7] <= sda_in;


            7'd3:
              scl  <= 1'b0;
            7'd5:
              scl       <= 1'b1   ;
            7'd6:
              data_r_save[6] <= sda_in ;


            7'd7:
              scl  <= 1'b0;
            7'd9:
              scl       <= 1'b1  ;
            7'd10 :
              data_r_save[5] <= sda_in;


            7'd11:
              scl  <= 1'b0;
            7'd13:
              scl       <= 1'b1  ;
            7'd14:
              data_r_save[4] <= sda_in;


            7'd15:
              scl  <= 1'b0;
            7'd17:
              scl       <= 1'b1  ;
            7'd18:
              data_r_save[3] <= sda_in;


            7'd19:
              scl  <= 1'b0;
            7'd21:
              scl       <= 1'b1  ;
            7'd22:
              data_r_save[2] <= sda_in;


            7'd23:
              scl  <= 1'b0;
            7'd25:
              scl       <= 1'b1  ;
            7'd26:
              data_r_save[1] <= sda_in;


            7'd27:
              scl  <= 1'b0;
            7'd29:
              scl       <= 1'b1  ;
            7'd30:
              data_r_save[0] <= sda_in;




            7'd31:
              scl  <= 1'b0;
            7'd32:
            begin
              sda_dir <= 1'b1;
              sda_out <= 1'b1;
            end
            7'd33:
              scl     <= 1'b1;
            7'd34:
              st_done <= 1'b1;          //非应答
            7'd35:
            begin
              scl <= 1'b0;
              cnt <= 7'b0;
              i2c_data_r <= data_r_save;
            end
            default  :
              ;
          endcase
        end


        st_stop:
        begin                           //结束I2C操作
          case(cnt)
            7'd0:
            begin
              sda_dir <= 1'b1;             //结束I2C
              sda_out <= 1'b0;
            end
            7'd1 :
              scl     <= 1'b1;
            7'd3 :
              sda_out <= 1'b1;
            7'd15:
              st_done <= 1'b1;
            7'd16:
            begin
              cnt      <= 7'b0;
              i2c_done <= 1'b1;            //向上层模块传递I2C结束信号
            end
            default  :
              ;
          endcase
        end
      endcase
    end
  end

endmodule

E2PROM.v

module E2PROM #(
// //EEPROM写数据需要添加间隔时间,读数据则不需要
parameter      WR_WAIT_TIME = 14'd5000, //写入间隔时间
parameter      MAX_BYTE     = 16'd256  //读写测试的字节个数
) (
    input                              clk         ,
    input                              rst_n       ,
    
    // from I2C-control 
    input           [7 : 0]            i2c_data_r  ,  // 读出来的数据
    input                              i2c_ack     ,  // 应答
    input                              i2c_done    ,  // i2c完成信号   

    // give to i2c
    output   reg                       i2c_exec    ,
    output   reg                       i2c_rh_wl   ,
    output   reg    [15 : 0]           i2c_addr    ,
    output   reg    [7 : 0]            i2c_data_w  ,

    // gvie it to led
    output   reg                       rw_done     ,  // e2prom 读写测试完成
    output   reg                       rw_result      // e2prom 的结果 0 : 失败 1 :成功   
);
   



// reg define 
reg   [1:0]    flow_cnt  ; //状态流控制
reg   [13:0]   wait_cnt  ; //延时计数器





always@(posedge clk or negedge rst_n) begin 
    if(rst_n == 0) begin 
        i2c_exec       <=     0   ;
        i2c_rh_wl      <=     0   ;
        i2c_addr       <=     0   ;
        i2c_data_w     <=     0   ;

        rw_done        <=     0   ;
        rw_result      <=     0   ;

        flow_cnt       <=     0   ;
        wait_cnt       <=     0   ; end
        else  begin 
            i2c_exec   <=     0   ;   // 把 i2c_ecec 看成是一个脉冲信号
            case(flow_cnt) 
            2'd0 : begin 
           if(wait_cnt == (WR_WAIT_TIME - 1) ) begin 
            wait_cnt <= 0 ; 
             if(i2c_addr == MAX_BYTE) begin   // 表示256个数据写入其中 
                i2c_addr  <= 16'b0;
                i2c_rh_wl <= 1'b1;
                flow_cnt  <= 2'd2;  end  
                else begin
                    flow_cnt <= flow_cnt + 2'b1;
                    i2c_exec <= 1'b1;
                end  
                end
                else begin 
                    wait_cnt <= wait_cnt + 1 ; end
                end

                2'd1 : begin 
                    if(i2c_done == 1'b1) begin                  //EEPROM单次写入完成
                        flow_cnt   <= 2'd0;
                        i2c_addr   <= i2c_addr + 16'b1;           //地址0~255分别写入
                        i2c_data_w <= i2c_data_w + 8'b1;         //数据0~255
                    end    
                    else begin 
                        flow_cnt    <= flow_cnt    ;
                        i2c_addr    <= i2c_addr    ;
                        i2c_data_w  <= i2c_data_w  ; end
                    end
                    
                2'd2 : begin 
                    flow_cnt <= flow_cnt + 2'b1;
                    i2c_exec <= 1'b1;   end
                
                2'd3 : begin 
                    if(i2c_done == 1'b1) begin                 //EEPROM单次读出完成
                        //读出的值错误或者I2C未应答,读写测试失败
                        if((i2c_addr[7:0] != i2c_data_r) || (i2c_ack == 1'b1)) begin
                            rw_done <= 1'b1;
                            rw_result <= 1'b0;
                        end
                        else if(i2c_addr == (MAX_BYTE - 16'b1))begin //读写测试成功
                            rw_done   <= 1'b1;
                            rw_result <= 1'b1;
                        end    
                        else begin
                            flow_cnt <= 2'd2;
                            i2c_addr <= i2c_addr + 16'b1;
                        end
                    end                 
                end
                default : ;
            endcase    
        end
    end    
    
    endmodule    
             

led.v

module LED 
    #(parameter L_TIME = 17'd125_000
    )
    (
    input        clk       ,  //时钟信号
    input        rst_n     ,  //复位信号
                 
    input        rw_done   ,  //错误标志
    input        rw_result ,  //E2PROM读写测试完成
    output  reg  led          //E2PROM读写测试结果 0:失败 1:成功
);

//reg define
reg          rw_done_flag;    //读写测试完成标志
reg  [16:0]  led_cnt     ;    //led计数

//*****************************************************
//**                    main code
//*****************************************************

//读写测试完成标志
always @(posedge clk or negedge rst_n) begin
    if(!rst_n)
        rw_done_flag <= 1'b0;
    else if(rw_done)
        rw_done_flag <= 1'b1;
end        

//错误标志为1时PL_LED0闪烁,否则PL_LED0常亮
always @(posedge clk or negedge rst_n) begin
    if(!rst_n) begin
        led_cnt <= 17'd0;
        led <= 1'b0;
    end
    else begin
        if(rw_done_flag) begin
            if(rw_result)                          //读写测试正确
                led <= 1'b1;                       //led灯常亮
            else begin                             //读写测试错误
                led_cnt <= led_cnt + 17'd1;
                if(led_cnt == (L_TIME - 17'b1)) begin
                    led_cnt <= 17'd0;
                    led <= ~led;                   //led灯闪烁
                end
                else
                    led <= led;
            end
        end
        else
            led <= 1'b0;                           //读写测试完成之前,led灯熄灭
    end    
end

endmodule

top.v

module IIC_top  #(
    parameter    SLAVE_ADDR = 7'b1010000     , //器件地址(SLAVE_ADDR)
    parameter    BIT_CTRL   = 1'b1           , //字地址位控制参数(16b/8b)
    parameter    CLK_FREQ   = 26'd50_000_000 , //i2c_dri模块的驱动时钟频率(CLK_FREQ)
    parameter    I2C_FREQ   = 18'd250_000    , //I2C的SCL时钟频率
    parameter    L_TIME     = 17'd125_000    , //led闪烁时间参数
    parameter    MAX_BYTE   = 16'd256         //读写测试的字节个数
    )(
    input                            sys_clk      ,
    input                            sys_rst_n    ,
    // i2c interface 
    output                           i2c_scl      ,
    inout                            i2c_sda      ,
    // led
    output                           led   
);
wire           dri_clk   ; //I2C操作时钟
wire           i2c_exec  ; //I2C触发控制
wire   [15:0]  i2c_addr  ; //I2C操作地址
wire   [ 7:0]  i2c_data_w; //I2C写入的数据
wire           i2c_done  ; //I2C操作结束标志
wire           i2c_ack   ; //I2C应答标志 0:应答 1:未应答
wire           i2c_rh_wl ; //I2C读写控制
wire   [ 7:0]  i2c_data_r; //I2C读出的数据
wire           rw_done   ; //E2PROM读写测试完成
wire           rw_result ; //E2PROM读写测试结果 0:失败 1:成功 


E2PROM#(
    .WR_WAIT_TIME ( 14'd5000 ),
    .MAX_BYTE     ( MAX_BYTE )
)u_E2PROM(
    .clk          ( dri_clk          ),
    .rst_n        ( sys_rst_n        ),
    .i2c_data_r   ( i2c_data_r   ),
    .i2c_ack      ( i2c_ack      ),
    .i2c_done     ( i2c_done     ),
    .i2c_exec     ( i2c_exec     ),
    .i2c_rh_wl    ( i2c_rh_wl    ),
    .i2c_addr     ( i2c_addr     ),
    .i2c_data_w   ( i2c_data_w   ),
    .rw_done      ( rw_done      ),
    .rw_result    ( rw_result    )
);

IIC_CONTROL#(
    .SLAVE_ADDR   ( SLAVE_ADDR ),
    .CLK_FREQ     ( CLK_FREQ ),
    .I2C_FREQ     ( I2C_FREQ )
)u_IIC_CONTROL(
    .clk          ( sys_clk          ),
    .rst_n        ( sys_rst_n        ),
    .i2c_addr     ( i2c_addr     ),
    .i2c_data_w   ( i2c_data_w   ),
    .i2c_rh_wl    ( i2c_rh_wl    ),
    .bit_control  ( BIT_CTRL  ),
    .i2c_exec     ( i2c_exec     ),
    .dri_clk      ( dri_clk      ),
    .i2c_data_r   ( i2c_data_r   ),
    .i2c_ack      ( i2c_ack      ),
    .i2c_done     ( i2c_done     ),
    .scl          ( i2c_scl          ),
    .sda          ( i2c_sda    )
);


LED#(
    .L_TIME     ( L_TIME )
)u_LED(
    .clk        ( dri_clk        ),
    .rst_n      ( sys_rst_n      ),
    .rw_done    ( rw_done    ),
    .rw_result  ( rw_result  ),
    .led        ( led        )
);

endmodule 

下面是testbench

EEPROM_AT24C64.v

`timescale 1ns/1ns
`define timeslice 1250
module EEPROM_AT24C64(
scl,
sda
);
input scl; 
inout sda; 
reg out_flag; 
reg[7:0] memory[8191:0]; 
reg[12:0]address; 
reg[7:0]memory_buf; 
reg[7:0]sda_buf; 
reg[7:0]shift; 
reg[7:0]addr_byte_h; 
reg[7:0]addr_byte_l; 
reg[7:0]ctrl_byte; 
reg[1:0]State;
integer i;
//---------------------------
parameter
r7 = 8'b1010_1111, w7 = 8'b1010_1110, //main7
r6 = 8'b1010_1101, w6 = 8'b1010_1100, //main6
r5 = 8'b1010_1011, w5 = 8'b1010_1010, //main5
r4 = 8'b1010_1001, w4 = 8'b1010_1000, //main4
r3 = 8'b1010_0111, w3 = 8'b1010_0110, //main3
r2 = 8'b1010_0101, w2 = 8'b1010_0100, //main2
r1 = 8'b1010_0011, w1 = 8'b1010_0010, //main1
r0 = 8'b1010_0001, w0 = 8'b1010_0000; //main0
assign sda = (out_flag == 1) ? sda_buf[7] : 1'bz;

initial
begin
addr_byte_h = 0;
addr_byte_l = 0;
ctrl_byte = 0;
out_flag = 0;
sda_buf = 0;
State = 2'b00;
memory_buf = 0;
address = 0;
shift = 0;
for(i=0;i<=8191;i=i+1)
memory[i] = 0;
end
always@(negedge sda)
begin
if(scl == 1)
begin
State = State + 1;
if(State == 2'b11)
disable write_to_eeprom;
end
end

always@(posedge sda)
begin
if(scl == 1) 
stop_W_R;
else
begin
casex(State)
2'b01:begin
read_in;
if(ctrl_byte == w7 || ctrl_byte == w6
|| ctrl_byte == w5 || ctrl_byte == w4
|| ctrl_byte == w3 || ctrl_byte == w2
|| ctrl_byte == w1 || ctrl_byte == w0)
begin
State = 2'b10;
write_to_eeprom; 
end
else
State = 2'b00;
end
2'b11:
read_from_eeprom;
default:
State = 2'b00;
endcase
end
end 
task stop_W_R;
begin
State = 2'b00;
addr_byte_h = 0;
addr_byte_l = 0;
ctrl_byte = 0;
out_flag = 0;
sda_buf = 0;
end
endtask

task read_in;
begin
shift_in(ctrl_byte);
shift_in(addr_byte_h);
shift_in(addr_byte_l);
end
endtask

task write_to_eeprom;
begin
shift_in(memory_buf);
address = {addr_byte_h[4:0], addr_byte_l};
memory[address] = memory_buf;
State = 2'b00;
end
endtask

task read_from_eeprom;
begin
shift_in(ctrl_byte);
if(ctrl_byte == r7 || ctrl_byte == w6
|| ctrl_byte == r5 || ctrl_byte == r4
|| ctrl_byte == r3 || ctrl_byte == r2
|| ctrl_byte == r1 || ctrl_byte == r0)
begin
address = {addr_byte_h[4:0], addr_byte_l};
sda_buf = memory[address];
shift_out;
State = 2'b00;
end
end
endtask
task shift_in;
output[7:0]shift;
begin
@(posedge scl) shift[7] = sda;
@(posedge scl) shift[6] = sda;
@(posedge scl) shift[5] = sda;
@(posedge scl) shift[4] = sda;
@(posedge scl) shift[3] = sda;
@(posedge scl) shift[2] = sda;
@(posedge scl) shift[1] = sda;
@(posedge scl) shift[0] = sda;
@(negedge scl)
begin
#(`timeslice);
out_flag = 1;
sda_buf = 0;
end
@(negedge scl)
begin
#(`timeslice-250);
out_flag = 0;
end
end
endtask
task shift_out;
begin
out_flag = 1;
for(i=6; i>=0; i=i-1)
begin
@(negedge scl);
#`timeslice;
sda_buf = sda_buf << 1;
end
@(negedge scl) #`timeslice sda_buf[7] = 1;
@(negedge scl) #`timeslice out_flag = 0;
end
endtask
endmodule

e2prom_tb.v

module  e2prom_tb;              

//parameter  define
parameter  T = 20                          ; //时钟周期为20ns
parameter  SLAVE_ADDR     = 7'b1010000     ; //器件地址(SLAVE_ADDR)
parameter  BIT_CTRL       = 1'b1           ; //字地址位控制参数(16b/8b)
parameter  CLK_FREQ       = 26'd50_000_000 ; //i2c_dri模块的驱动时钟频率(CLK_FREQ)
parameter  I2C_FREQ       = 18'd250_000    ; //I2C的SCL时钟频率
parameter  L_TIME         = 17'd1          ; //led闪烁时间参数
parameter  MAX_BYTE       = 16'd3          ; //读写测试的字节个数

//reg define
reg          sys_clk  ;                 //时钟信号
reg          sys_rst_n;                 //复位信号

//wire define
wire         iic_scl;
wire         iic_sda;
wire         led    ;

//*****************************************************
//**                    main code
//*****************************************************

//给输入信号初始值
initial begin
    sys_clk            = 1'b0;
    sys_rst_n          = 1'b0;     //复位
    #(T+1)  sys_rst_n  = 1'b1;     //在第21ns的时候复位信号信号拉高
end

//50Mhz的时钟,周期则为1/50Mhz=20ns,所以每10ns,电平取反一次
always #(T/2) sys_clk = ~sys_clk;

//将SDA数据线上拉
pullup(iic_sda);

IIC_top#(
    .SLAVE_ADDR     ( 7'b1010000 ),
    .BIT_CTRL       ( 1'b1 ),
    .CLK_FREQ       ( 26'd50_000_000 ),
    .I2C_FREQ       ( 18'd250_000 ),
    .L_TIME         ( 17'd125_000 ),
    .MAX_BYTE       ( 16'd256 )
)u_IIC_top(
    .sys_clk        ( sys_clk        ),
    .sys_rst_n      ( sys_rst_n      ),
    .i2c_scl        ( iic_scl        ),
    .i2c_sda        ( iic_sda        ),
    .led            ( led            )
);


//例化e2prom仿真模型
EEPROM_AT24C64 u_EEPROM_AT24C64(
    .scl         (iic_scl),
    .sda         (iic_sda)
    );

endmodule

下面是注意事项

README.md

I2C 转换接口的设计思路

因为时钟的不同 我们先设计出本次时钟所需要的dri_clk
在配置完dri_clk 之后 我们需要做的是对整个I2C结构 进行状态机的 书写
建议 写成经典的三段状态机的形式

  1. 同步时序描述状态转移
  2. 组合逻辑判断状态转移条件
  3. 时序电路描述状态输出

前两部分是相对来说好处理的 后面第三部分的 时序逻辑电路描述状态有些复杂

代码第639行 和之前的存储地址一样又 不太一样

需要先 交还给 FPGA端控制权 再执行
为什么在之前的传递数据在cnt = 0 的时候 交还了控制权 直接赋值呢
原因是因为 它们并不是传递地址 只有首次传递地址的时候 需要保证在SCL在拉高的情况下 SDA先拉底
我们采用倒推法

cnt3501234
SCL1->000->111->00

实验效果 在 35 这一时刻来自于上一时刻
在0时刻 SCL处于中间低处 此时 我们对于 SDA交还控制权给 FPGA 再 给赋值 sda_out = 1
因为我们SCL总是 dri_clk 的四倍 我们在35时刻拉低 就在1时刻拉高
在稳定的 2时刻 拉低 sda_out 即 传输器件地址需要先拉低 sda
最后对于 1拉高 3拉低 4处于低的洼地 -> 可以使得 sda_out 变化

完整介绍一下模块的设计

首先本次实验完成的是 IIC转化的串口形式
在执行处理的时候,把整个元件当成了一个黑盒模块
在这里插入图片描述

就像之前的AXI-Stream转Native接口的RTL模块一样
现在所做的更像是把 Native模块 转化成IIC协议的接口形式
通俗易懂来讲 别的模块我根本不在乎 这个东西 就是将地址,数据等信息传递进入这个转换模块
转换模块通过内部的调度与适配 传输成符合标准与要求的 SCL与SDA的形式
(我个人觉得 IIC要比 AXI-Stream难 10倍 UART 最简单浅显易懂)

这是I2C的读写流程图
在这里插入图片描述

我们本次实验主要在这个图上编写状态机

这里模块内部时钟的编辑
通常使用的时钟频率是50MHz = 26’d50_000_000
这个50MHz 的意思 就是 1s 有 50_000_000个时钟周期 即 每个时钟周期的时长为 20ns

彼时 需要使用一个新的时钟频率 假如是 18’d250_000 的时钟频率 即每秒有250000个周期间隔
我们可以通过number = 50_000_000 / 250_000 求得计数
注意在用cnt 计数时 除以2 再减 1 因为时钟总是在一半的时候进行翻转

完整的画一下 单次读 与 单次写的 时序流程图

在这里插入图片描述

下面是单次写的时序图

在这里插入图片描述

我觉得有一个不妥当的地方是为什么它非要在上升沿采样数据呢 为什么不等高电平 数据稳定的时候采样

第278行 我将 i2c_exec删除了 我觉得没问题

最后上板验证也未出现问题

         // if( i2c_exec == 1) begin
          wr_flag     <=    i2c_rh_wl  ;
          addr_save   <=    i2c_addr   ;
          data_w_save <=    i2c_data_w ;
          i2c_ack     <=    0 ;
         //  end
         ```

# 第734行 它的做法总是在上升沿触发 我吐槽过了 我觉得电平触发更加稳定 
它的做法
```verilog
        case(cnt)
            7'd0:
              sda_dir <= 1'b0;
            7'd1:
            begin
              data_r_save[7] <= sda_in;
              scl       <= 1'b1;
            end
            7'd3:
              scl  <= 1'b0;
            7'd5:
            begin
              data_r_save[6] <= sda_in ;
              scl       <= 1'b1   ;
            end
            7'd7:
              scl  <= 1'b0;
            7'd9:
            begin
              data_r_save[5] <= sda_in;
              scl       <= 1'b1  ;
            end
            7'd11:
              scl  <= 1'b0;
            7'd13:
            begin
              data_r_save[4] <= sda_in;
              scl       <= 1'b1  ;
            end
            7'd15:
              scl  <= 1'b0;
            7'd17:
            begin
              data_r_save[3] <= sda_in;
              scl       <= 1'b1  ;
            end
            7'd19:
              scl  <= 1'b0;
            7'd21:
            begin
              data_r_save[2] <= sda_in;
              scl       <= 1'b1  ;
            end
            7'd23:
              scl  <= 1'b0;
            7'd25:
            begin
              data_r_save[1] <= sda_in;
              scl       <= 1'b1  ;
            end
            7'd27:
              scl  <= 1'b0;
            7'd29:
            begin
              data_r_save[0] <= sda_in;
              scl       <= 1'b1  ;
            end
            ```
上面是别人的对的 
我自己改成高电平触发 也是正确的 
```verilog
case(cnt)
            7'd0:
              sda_dir <= 1'b0;
            7'd1:
              scl       <= 1'b1;
            7'd2 :
              data_r_save[7] <= sda_in;


            7'd3:
              scl  <= 1'b0;
            7'd5:
              scl       <= 1'b1   ;
            7'd6:
              data_r_save[6] <= sda_in ;


            7'd7:
              scl  <= 1'b0;
            7'd9:
              scl       <= 1'b1  ;
            7'd10 :
              data_r_save[5] <= sda_in;


            7'd11:
              scl  <= 1'b0;
            7'd13:
              scl       <= 1'b1  ;
            7'd14:
              data_r_save[4] <= sda_in;


            7'd15:
              scl  <= 1'b0;
            7'd17:
              scl       <= 1'b1  ;
            7'd18:
              data_r_save[3] <= sda_in;


            7'd19:
              scl  <= 1'b0;
            7'd21:
              scl       <= 1'b1  ;
            7'd22:
              data_r_save[2] <= sda_in;


            7'd23:
              scl  <= 1'b0;
            7'd25:
              scl       <= 1'b1  ;
            7'd26:
              data_r_save[1] <= sda_in;


            7'd27:
              scl  <= 1'b0;
            7'd29:
              scl       <= 1'b1  ;
            7'd30:
              data_r_save[0] <= sda_in;




            7'd31:
              scl  <= 1'b0;
            7'd32:
            begin
              sda_dir <= 1'b1;
              sda_out <= 1'b1;
            end
            7'd33:
              scl     <= 1'b1;
            7'd34:
              st_done <= 1'b1;          //非应答
            7'd35:
            begin
              scl <= 1'b0;
              cnt <= 7'b0;
              i2c_data_r <= data_r_save;
            end
            default  :
              ;
          endcase
        end
        ```

文章来源:https://blog.csdn.net/weixin_50965981/article/details/135186362
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。