Lattice LFCPNX-100 HSB+Fpga开发详解:2.4 Hololink I2C外设控制深度全解析

1、概述

HSB项目中,具有2路I2C外设,一路为cmos sensor RGBIR 图像传感器VB1943,一路为IMU 惯性测量传感器ICM42688,两者在驱动层面相类似,都是通过I2C控制总线读写数据;

2、逻辑流程

3、模块概括

模块中的(5 大核心部分)

  1. APB 寄存器映射(RegMap)

  2. 数据缓冲双口 RAM(Data Buffer)

  3. I2C 位层引擎(i2c_ctrl_byte_inst)

  4. I2C 事务层 FSM(核心状态机)

  5. 控制逻辑与状态输出·

这是一个基于 APB 总线**、带寄存器映射 + 数据缓冲 RAM 的 I2C 主机控制器 FSM 模块**,用于通过 CPU 配置 I2C 读写、自动执行多字节 I2C 事务、支持重复起始(Repeated Start)、10bit 地址、多轮事务自动执行。

其中 CPU 通过APB 从机接口进行 寄存器配置 I2C 参数,I2C 主机自动控制器 协议为自动发 START / 地址 / 写 / 重复起始 / 读 / STOP,通过双端口 RAM 数据缓冲区对读写数据实现自动缓存;

复制代码
// SPDX-FileCopyrightText: Copyright (c) 2023-2024 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.


module i2c_ctrl_fsm
  import apb_pkg::*;
  import regmap_pkg::*;
#(
  parameter NUM_INST  = 1,
  parameter RAM_DEPTH = 128
)
(
  // Interface i_aclk(时钟)、i_arst(复位)、APB 总线
  input                           i_aclk,
  input                           i_arst,
  input  apb_m2s                  i_apb_m2s,
  output apb_s2m                  o_apb_s2m,

  output                          o_busy,
  output [$clog2(NUM_INST)-1:0]   o_bus_en,

  input                           i_start,
  output [7:0]                    o_data,
  output                          o_data_valid,
  //I2C Ports
  input                           scl_i,
  output                          scl_o,
  input                           sda_i,
  output                          sda_o
);

//------------------------------------------------------------------------------------------------//
// Register Map APB 寄存器映射 CPU 通过 APB 配置以下 6 个控制寄存器 + 1 个状态寄存器
//------------------------------------------------------------------------------------------------//
//寄存器	功能
//ctrl_reg[0]	启动位、10bit 地址使能、设备地址
//ctrl_reg[1]	总线使能(多设备片选)
//ctrl_reg[2]	写字节数 [15:0] + 读字节数 [31:16]
//ctrl_reg[3]	I2C 时钟分频(SCL 时钟配置)
//ctrl_reg[4]	SCL 超时时间
//ctrl_reg[5]	自动重复执行事务次数
logic [31:0] ctrl_reg [6]; 
logic [31:0] stat_reg [1];

logic     isCtrlAddr;
apb_m2s   ctrl_apb_m2s;
apb_s2m   ctrl_apb_s2m;
apb_m2s   db_apb_m2s;
apb_s2m   db_apb_s2m;

localparam  [(6*32)-1:0] RST_VAL = {'0,32'h004C_4B40,{(4*32){1'b0}}};

s_apb_reg #(
  .N_CTRL    ( 6              ),
  .N_STAT    ( 1              ),
  .W_OFST    ( w_ofst         ),
  .RST_VAL   ( RST_VAL        ),
  .SAME_CLK  ( 1              )
) u_reg_map  (
  .i_aclk    ( i_aclk         ),
  .i_arst    ( i_arst         ),
  .i_apb_m2s ( ctrl_apb_m2s   ),
  .o_apb_s2m ( ctrl_apb_s2m   ),
  .i_pclk    ( i_aclk         ),
  .i_prst    ( i_arst         ),
  .o_ctrl    ( ctrl_reg       ),
  .i_stat    ( stat_reg       )
);

logic [15:0]   num_wr_bytes   ;
logic [15:0]   num_rd_bytes   ;
logic [9:0]    device_address ;
logic [15:0]   clk_cnt        ;
logic [31:0]   i2c_scl_timeout;
logic          is_10b_addr    ;
logic          fsm_start      ;
logic          i2c_nack       ;
logic          i2c_al_err     ;
logic          cmd_valid      ;
logic          i2c_done       ;
logic [7:0]    num_trans      ;


logic  [$clog2(NUM_INST)-1:0] reg_bus_en;

assign fsm_start         = ctrl_reg[0][0] && i_start;
assign is_10b_addr       = ctrl_reg[0][1];
assign device_address    = ctrl_reg[0][25:16];
assign reg_bus_en        = ctrl_reg[1][$clog2(NUM_INST):0];
assign num_wr_bytes      = ctrl_reg[2][15:0];
assign num_rd_bytes      = ctrl_reg[2][31:16];
assign clk_cnt           = ctrl_reg[3][15:0];
assign i2c_scl_timeout   = ctrl_reg[4];
assign num_trans         = ctrl_reg[5][7:0];

assign stat_reg[0][0]    = o_busy;
assign stat_reg[0][1]    = '0;
assign stat_reg[0][2]    = i2c_al_err;
assign stat_reg[0][3]    = i2c_nack;
assign stat_reg[0][4]    = i2c_done;
assign stat_reg[0][31:5] = '0;


assign ctrl_apb_m2s.psel    = (isCtrlAddr && i_apb_m2s.psel);
assign ctrl_apb_m2s.penable = i_apb_m2s.penable;
assign ctrl_apb_m2s.paddr   = i_apb_m2s.paddr  ;
assign ctrl_apb_m2s.pwdata  = i_apb_m2s.pwdata ;
assign ctrl_apb_m2s.pwrite  = i_apb_m2s.pwrite ;

assign db_apb_m2s.psel      = (!isCtrlAddr && i_apb_m2s.psel);
assign db_apb_m2s.penable   = i_apb_m2s.penable;
assign db_apb_m2s.paddr     = {24'h0,i_apb_m2s.paddr[7:0]};
assign db_apb_m2s.pwdata    = i_apb_m2s.pwdata ;
assign db_apb_m2s.pwrite    = i_apb_m2s.pwrite ;
assign o_apb_s2m            = (isCtrlAddr) ? ctrl_apb_s2m : db_apb_s2m;

assign o_bus_en = reg_bus_en;

typedef enum logic [3:0] {
  I2C_IDLE,
  I2C_10b_DEV_ADDR,
  I2C_DEV_ADDR,
  I2C_WRITE,
  I2C_RS_10b_DEV_ADDR,
  I2C_RS_DEV_ADDR,
  I2C_READ,
  I2C_PERI_NACK,
  I2C_DONE
} i2c_states;
i2c_states state,state_nxt, state_prev;

//------------------------------------------------------------------------------------------------//
// Data Buffer
//------------------------------------------------------------------------------------------------//
logic        db_wr_en;
logic        wr_en;
logic [31:0] db_wr_data;
logic [6:0]  db_addr;
logic [31:0] db_rd_data;
logic        rd_data_valid;
logic [7:0]  i2c_db_addr        ;
logic [8:0]  transaction_cnt    ;
logic [8:0]  wr_ptr             ;
logic [8:0]  rd_ptr             ;
logic [7:0]  i2c_db_data        ;
logic [7:0]  rd_data            ;
logic        ack_rise ;
logic        cmd_ack_prev;
logic        i2c_busy;
logic        idle;
logic        fsm_done;
logic        i2c_al;
logic        cmd_ack;
logic        read;
logic        db_pready;
logic [7:0]  trans_cnt;

always_comb begin
  db_wr_en = ((db_apb_m2s.psel && db_apb_m2s.penable && db_apb_m2s.pwrite) || (rd_data_valid));
  if (o_busy || (fsm_start && (state == I2C_IDLE))) begin
    if (cmd_ack && read) begin
      db_wr_data = (i2c_db_addr[1:0] == 2'b11) ? {rd_data, db_rd_data[23:0]} :
                    (i2c_db_addr[1:0] == 2'b10) ? {db_rd_data[31:24], rd_data, db_rd_data[15:0]} :
                    (i2c_db_addr[1:0] == 2'b01) ? {db_rd_data[31:16], rd_data, db_rd_data[7:0]} :
                                                  {db_rd_data[31:8],  rd_data} ;
    end
    else begin
      db_wr_data = 8'h0;
    end
    db_addr = i2c_db_addr[7:2];
  end
  else begin
    db_addr    = (db_apb_m2s.paddr[7:2]);
    db_wr_data = db_apb_m2s.pwdata;
  end
end

assign i2c_db_addr = (state == I2C_WRITE) ? wr_ptr : rd_ptr;

always @(posedge i_aclk) begin
  if (i_arst) begin
    db_pready <= 1'b0;
  end
  else begin
    db_pready <= (db_apb_m2s.psel && db_apb_m2s.penable);
  end
end
//深度:128 × 32bit
//CPU 写入要发送的数据 → RAM
//I2C 读到的数据 → 自动写入 RAM
//CPU 可随时从 RAM 读取结果
dp_ram #(
  .DATA_WIDTH ( 32                              ),
  .RAM_DEPTH  ( RAM_DEPTH                       ),
  .RAM_TYPE   ( "SIMPLE"                        ),
  .MEM_STYLE  ( "BLOCK"                         )
) data_buffer (
  .clk_a      ( i_aclk                          ),
  .en_a       ( !i_arst                         ),
  .we_a       ( db_wr_en                        ),
  .din_a      ( db_wr_data                      ),
  .addr_a     ( db_addr[$clog2(RAM_DEPTH)-1:0]  ),
  .dout_a     ( db_rd_data                      ),
  .clk_b      ( '0                              ),
  .en_b       ( '0                              ),
  .we_b       ( '0                              ),
  .din_b      ( '0                              ),
  .addr_b     ( '0                              ),
  .dout_b     (                                 )
);

assign isCtrlAddr    = (!i_apb_m2s.paddr[8]);
assign rd_data_valid = (ack_rise && read);
assign db_apb_s2m.pready = (db_apb_m2s.psel && db_apb_m2s.penable) && db_pready;  // Delay 1 clock cycle
assign db_apb_s2m.prdata = (!o_busy && (db_apb_m2s.psel && db_apb_m2s.penable)) ? db_rd_data : '0;
assign db_apb_s2m.pserr  = '0;

//------------------------------------------------------------------------------------------------//
// I2C Engine
//------------------------------------------------------------------------------------------------//

// Ctrl Inputs
logic       start;
logic       stop;
logic       write;
logic       ack_stretch;
logic       ack_in;
logic [7:0] din;
logic       busy;

// Status Outputs
logic       ack_out;

// Read for I2C interface
assign din = (state == I2C_10b_DEV_ADDR)    ? {5'b11110, device_address[9:8],1'b0}   : // CMD code + Addr + Write
             (state == I2C_DEV_ADDR)        ? ((is_10b_addr) ? {device_address[7:0]} : {device_address[6:0],1'b0}): // Addr + Write
             (state == I2C_RS_10b_DEV_ADDR) ? {5'b11110, device_address[9:8],1'b1}   :  // Addr + Read
             (state == I2C_RS_DEV_ADDR)     ? ((is_10b_addr) ? {device_address[7:0]} : {device_address[6:0],1'b1}): // Addr + Read
                                                         db_rd_data[i2c_db_addr[1:0]*8+:8];
//I2C 底层位引擎(i2c_ctrl_byte_inst)真正产生 I2C 时序的底层模块
i2c i2c_ctrl_byte_inst (
  .clk            ( i_aclk          ), // clock
  .rst            ( 1'b0            ), // synchronous active high reset
  .nReset         ( !i_arst         ), // asynchronous active low reset
  .ena            ( '1              ), // core enable signal
  .clk_cnt        ( clk_cnt         ), // 4x SCL (default)
  // control inputs
  .start          ( start           ),
  .stop           ( stop            ),
  .read           ( read            ),
  .write          ( write           ),
  .ack_stretch    ( '0              ),
  .ack_in         ( ack_in          ),
  .din            ( din             ),
  .i2c_scl_timeout( i2c_scl_timeout ),
  // status outputs
  .cmd_ack        ( cmd_ack         ),
  .ack_out        ( ack_out         ),
  .i2c_busy       ( i2c_busy        ),
  .i2c_al         ( i2c_al          ),
  .idle           ( idle            ),
  .dout           ( rd_data         ),
  // I2C signals
  .scl_i          ( scl_i           ),
  .scl_o          (                 ),
  .scl_oen        ( scl_o           ),
  .sda_i          ( sda_i           ),
  .sda_o          (                 ),
  .sda_oen        ( sda_o           )
);

//------------------------------------------------------------------------------------------------//
// I2C Interface FSM I2C 事务 FSM 状态机
//------------------------------------------------------------------------------------------------//


assign ack_rise = ({cmd_ack_prev,cmd_ack} == 2'b01);

assign cmd_valid   = (num_trans != '0) ? (num_wr_bytes >= num_rd_bytes) : '1;
assign write       = ((state == I2C_WRITE)    || (state == I2C_10b_DEV_ADDR) || (state == I2C_RS_10b_DEV_ADDR) ||
                      (state == I2C_DEV_ADDR) || (state == I2C_RS_DEV_ADDR));

assign read        = (state == I2C_READ);
assign start       = (is_10b_addr) ? ((state == I2C_10b_DEV_ADDR) || (state == I2C_RS_10b_DEV_ADDR)):
                                     ((state == I2C_DEV_ADDR    ) || (state == I2C_RS_DEV_ADDR));

always_comb begin
  state_nxt = state;
  if (ack_out && cmd_ack && (state != I2C_IDLE) && (state != I2C_DONE) && !read) begin
    state_nxt = (state == I2C_PERI_NACK) ? I2C_DONE : I2C_PERI_NACK;
  end
  else begin
    case(state)
      I2C_IDLE: begin
        if (fsm_start && cmd_valid) begin
          state_nxt  = (num_wr_bytes == '0) ?
                       (is_10b_addr       ) ? I2C_RS_10b_DEV_ADDR : I2C_RS_DEV_ADDR:
                       (is_10b_addr       ) ? I2C_10b_DEV_ADDR    : I2C_DEV_ADDR   ;
        end
      end
      I2C_10b_DEV_ADDR: begin
        state_nxt = I2C_DEV_ADDR;
      end
      I2C_DEV_ADDR: begin
        state_nxt = I2C_WRITE;
      end
      I2C_WRITE: begin
        if (transaction_cnt == (num_wr_bytes - 1)) begin
          state_nxt = (num_rd_bytes != '0) ? (is_10b_addr) ?  I2C_RS_10b_DEV_ADDR : I2C_RS_DEV_ADDR : I2C_DONE;
        end
      end
      I2C_RS_10b_DEV_ADDR: begin
        state_nxt = I2C_RS_DEV_ADDR;
      end
      I2C_RS_DEV_ADDR: begin
        state_nxt =  I2C_READ;
      end
      I2C_READ: begin
        state_nxt = (transaction_cnt == (num_rd_bytes-1)) ? I2C_DONE : I2C_READ;
      end
      I2C_PERI_NACK: begin
        state_nxt  =  I2C_DONE;
      end
      I2C_DONE: begin
        if (idle && (trans_cnt < num_trans)) begin
          state_nxt = I2C_IDLE;
        end
        else if (idle && !fsm_start) begin
          state_nxt       =  I2C_IDLE;
        end
        else begin
          state_nxt       =  I2C_DONE;
        end
      end
    endcase
  end
end


always@(posedge i_aclk) begin
  if (i_arst) begin
    state           <= I2C_IDLE;
    state_prev      <= I2C_IDLE;
    transaction_cnt <= '0;
    wr_ptr          <= '0;
    rd_ptr          <= '0;
    i2c_nack        <= '0;
    i2c_al_err      <= '0;
    cmd_ack_prev    <= '0;
    trans_cnt       <= '0;
    busy            <= '0;
  end
  else begin
    state           <= (i2c_al) ? I2C_DONE  : ((ack_rise) || ((state == I2C_IDLE) || (state == I2C_DONE) || (state == I2C_PERI_NACK)))
                                ? state_nxt : state;
    state_prev      <= state;
    transaction_cnt <= ((state != I2C_WRITE) && (state != I2C_READ)) ? '0 : transaction_cnt + ack_rise;
    wr_ptr          <= (state == I2C_WRITE) ? wr_ptr + ack_rise : (!fsm_start && !busy) ? '0 : wr_ptr;
    rd_ptr          <= (state == I2C_READ)  ? rd_ptr + ack_rise : (!fsm_start && !busy) ? '0 : rd_ptr;
    i2c_nack        <= (state == I2C_PERI_NACK) ? 1'b1 : (state == I2C_IDLE) ? 1'b0 : i2c_nack;
    i2c_al_err      <= (i2c_al) ? 1'b1 : (state == I2C_IDLE) ? 1'b0 : i2c_al_err;
    cmd_ack_prev    <= cmd_ack;
    trans_cnt       <=  ((state == I2C_IDLE) && !fsm_start            ) ? 'd1            :
                        ((state == I2C_IDLE && state_prev == I2C_DONE)) ? trans_cnt + 1  : trans_cnt;
    busy            <= (!busy && (state == I2C_IDLE) && fsm_start && cmd_valid)           ? '1 :
                        (busy && (state == I2C_DONE) && idle && (trans_cnt >= num_trans)) ? '0 : busy;
  end
end

assign stop = ((state_nxt == I2C_DONE) || (state_nxt == I2C_PERI_NACK)) && (state_nxt != state);
assign ack_in = ((state_nxt == I2C_DONE) && (state == I2C_READ));
assign o_busy = busy;
assign i2c_done = (state == I2C_DONE) && idle;

assign o_data = rd_data;
assign o_data_valid = rd_data_valid;


endmodule

4、模块工作流程(CPU 配置 → I2C 执行)

步骤 1:CPU 写寄存器,包含下面几个数据:

  • 设备地址
  • 写字节数、读字节数
  • 时钟配置
  • 将要写的数据写入 DATA RAM

步骤 2:CPU 写启动位 = 1;

步骤 3:FSM 自动执行,i2c时序:

  • 发 START
  • 发地址
  • 写字节
  • 重复 START(如需读)
  • 发读地址
  • 读字节
  • 发 STOP

步骤 4:结果自动写入 RAM

步骤 5:状态寄存器更新(done /nack/error)

步骤 6:CPU 读取 RAM 获得结果

5、总结

这是一个可由 CPU 配置、自动完成 I2C 主机读写、带数据缓存、带错误检测的工程级 I2C 控制器。具体源码已贴上,但涉及到一些实例化的代码,由于空间有限,无法继续贴上,可以联系vx:hope_0793,详情讨论!

相关推荐
Kent Gu3 小时前
MCU & FPGA调试
单片机·嵌入式硬件·fpga开发
浩子智控4 小时前
EtherCAT技术概述
嵌入式硬件·fpga开发·硬件工程·信号处理
我爱C编程5 小时前
【仿真测试】基于FPGA的8ASK扩频通信链路实现,包含帧同步,定时点,扩频伪码同步,信道,误码统计
fpga开发·帧同步·定时点·扩频通信·8ask·扩频伪码
德思特5 小时前
软件定义GNSS模拟器技术原理与优势深度解析
fpga开发
GateWorld6 小时前
LCD显示技术完全指南:原理·制造·驱动·FPGA实现之点屏四 LVDS
fpga开发·lcd显示·fpga点亮屏幕·minilvds
第二层皮-合肥21 小时前
【数据采集专栏】利用TDC提高外部触发精度
fpga开发
尤老师FPGA1 天前
HDMI数据的接收发送实验(十三)
fpga开发
博览鸿蒙1 天前
[特殊字符]AI+FPGA 全栈学习大纲【就业版】定位
人工智能·学习·fpga开发
燎原星火*1 天前
AD/DA硬件电路设计
fpga开发