简易寄存器接口SMMR---iic控制器

参考

用vio_uart_rpc协议,测试IIC接口的AT24C64.csdn

嵌入式终端AtShell的verilog版.csdn

简易寄存器接口SMMR的vio_uart桥接.csdn

简易寄存器接口SMMR.csdn

可配置的PWM外设模块.csdn

公共module

key_debounce.v 和 axis_uart_sender.v 参考嵌入式终端AtShell的verilog版.csdn

iic_master_slot.v

verilog 复制代码
`timescale 1ns / 1ps

module iic_master_slot #(
    parameter P_ADDR_WIDTH = 8,
    parameter P_DATA_WIDTH = 16,

    parameter [P_ADDR_WIDTH-1:0] P_SLOT_BASE = 0,
    parameter P_REG_COUNT = 32,

    parameter [6:0]  P_SLAVE_ADDR = 7'b1010000,
    parameter [25:0] P_CLK_FREQ   = 26'd50_000_000,
    parameter [17:0] P_I2C_FREQ   = 18'd250_000
)(
    input  wire                         i_clk,
    input  wire                         i_rst_n,

    //==================================================
    // SMMR
    //==================================================
    input  wire                         i_reg_wr_en,
    input  wire [P_ADDR_WIDTH-1:0]      i_reg_wr_addr,
    input  wire [P_DATA_WIDTH-1:0]      i_reg_wr_data,

    input  wire [P_ADDR_WIDTH-1:0]      i_reg_rd_addr,
    output reg  [P_DATA_WIDTH-1:0]      o_reg_rd_data,

    //==================================================
    // I2C
    //==================================================
    output wire                         o_i2c_scl,

    input  wire                         i_i2c_sda_i,
    output wire                         o_i2c_sda_o,
    output wire                         o_i2c_sda_t

);


////////////////////////////////////////////////////////////////////////////
// IIC MASTER SLOT Register Map
////////////////////////////////////////////////////////////////////////////
//
// +0 CONTROL
//
// bit0 : EXEC
//        写1启动一次I2C操作
//
// bit1 : RW
//        0 = WRITE
//        1 = READ
//
// bit2 : ADDR16
//        0 = 8 bit内部地址
//        1 = 16 bit内部地址
//
// +1 STATUS
//
// bit0 : DONE
//        I2C一次操作完成
//
// bit1 : ACK_ERROR
//        0 = ACK
//        1 = NACK
//
// bit2 : BUSY
//        0 = 空闲
//        1 = I2C操作中
//
// +2 I2C_ADDR
//
// [15:0] : I2C器件内部地址
//
// +3 DATA_WR
//
// [7:0] : I2C写数据
//
// +4 DATA_RD
//
// [7:0] : I2C读数据
//
////////////////////////////////////////////////////////////////////////////

localparam [P_ADDR_WIDTH-1:0] ADDR_CONTROL  =P_SLOT_BASE + 8'd0;
localparam [P_ADDR_WIDTH-1:0] ADDR_STATUS   =P_SLOT_BASE + 8'd1;
localparam [P_ADDR_WIDTH-1:0] ADDR_I2C_ADDR = P_SLOT_BASE + 8'd2;
localparam [P_ADDR_WIDTH-1:0] ADDR_DATA_WR  =P_SLOT_BASE + 8'd3;
localparam [P_ADDR_WIDTH-1:0] ADDR_DATA_RD  =P_SLOT_BASE + 8'd4;
////////////////////////////////////////////////////////////////////////////
// Register
////////////////////////////////////////////////////////////////////////////
reg [P_DATA_WIDTH-1:0] r_control;
reg [P_DATA_WIDTH-1:0] r_i2c_addr;
reg [P_DATA_WIDTH-1:0] r_data_wr;
////////////////////////////////////////////////////////////////////////////
// I2C control
////////////////////////////////////////////////////////////////////////////
reg r_i2c_exec;

////////////////////////////////////////////////////////////////////////////
// Latched I2C status
////////////////////////////////////////////////////////////////////////////

reg       r_done;
reg       r_ack_error;
reg [7:0] r_data_rd;


////////////////////////////////////////////////////////////////////////////
// I2C status
////////////////////////////////////////////////////////////////////////////

wire        w_i2c_done;
wire        w_i2c_ack;
wire [7:0]  w_i2c_data_rd;


////////////////////////////////////////////////////////////////////////////
// BUSY
////////////////////////////////////////////////////////////////////////////

reg r_busy;


////////////////////////////////////////////////////////////////////////////
// I2C MASTER
////////////////////////////////////////////////////////////////////////////

i2c_master_dri #(
    .P_SLAVE_ADDR (P_SLAVE_ADDR),
    .P_CLK_FREQ   (P_CLK_FREQ),
    .P_I2C_FREQ   (P_I2C_FREQ)
)
u_i2c_master_dri
(
    .i_clk        (i_clk),
    .i_rst_n      (i_rst_n),

    //==================================================
    // I2C control
    //==================================================
    .i_i2c_exec   (r_i2c_exec),
    .i_bit_ctrl   (r_control[2]),
    .i_i2c_rh_wl  (r_control[1]),
    .i_i2c_addr   (r_i2c_addr),
    .i_i2c_data_w (r_data_wr[7:0]),

    //==================================================
    // I2C result
    //==================================================
    .o_i2c_data_r (w_i2c_data_rd),
    .o_i2c_done   (w_i2c_done),
    .o_i2c_ack    (w_i2c_ack),

    //==================================================
    // I2C SCL
    //==================================================
    .o_scl        (o_i2c_scl),

    //==================================================
    // I2C SDA
    //==================================================
    .i_sda_i      (i_i2c_sda_i),
    .o_sda_o      (o_i2c_sda_o),
    .o_sda_t      (o_i2c_sda_t),

    //==================================================
    // Driver clock
    //==================================================
    .o_dri_clk    ()
);


////////////////////////////////////////////////////////////////////////////
// SMMR WRITE
////////////////////////////////////////////////////////////////////////////
always @(posedge i_clk or negedge i_rst_n)
begin
    if(!i_rst_n) begin
        r_control  <= 0;
        r_i2c_addr <= 0;
        r_data_wr  <= 0;
        r_i2c_exec <= 1'b0;
    end
    else begin
        // EXEC保持到driver返回done,避免1clk脉冲被低速I2C驱动时钟漏采样
        if(w_i2c_done)
            r_i2c_exec <= 1'b0;
        if(i_reg_wr_en) begin
            case(i_reg_wr_addr)
                ////////////////////////////////////////////////////////////
                // CONTROL
                ////////////////////////////////////////////////////////////
                ADDR_CONTROL:
                begin
                    r_control <= i_reg_wr_data;

                    // 写1启动一次I2C
                    if(i_reg_wr_data[0])
                        r_i2c_exec <= 1'b1;
                end
                ////////////////////////////////////////////////////////////
                // I2C内部地址
                ////////////////////////////////////////////////////////////
                ADDR_I2C_ADDR:
                begin
                    r_i2c_addr <= i_reg_wr_data;
                end
                ////////////////////////////////////////////////////////////
                // 写数据
                ////////////////////////////////////////////////////////////
                ADDR_DATA_WR:
                begin
                    r_data_wr <= i_reg_wr_data;
                end
                default:
                begin
                end

            endcase

        end

    end
end


////////////////////////////////////////////////////////////////////////////
// BUSY
////////////////////////////////////////////////////////////////////////////

always @(posedge i_clk or negedge i_rst_n)
begin
    if(!i_rst_n) begin

        r_busy <= 1'b0;

    end
    else begin

        // 启动
        if(r_i2c_exec)
            r_busy <= 1'b1;

        // 完成
        if(w_i2c_done)
            r_busy <= 1'b0;

    end
end


////////////////////////////////////////////////////////////////////////////
// DONE / ACK latch
////////////////////////////////////////////////////////////////////////////

always @(posedge i_clk or negedge i_rst_n)
begin
    if(!i_rst_n) begin

        r_done      <= 1'b0;
        r_ack_error <= 1'b0;
        r_data_rd   <= 8'd0;

    end
    else begin
        // 下一次EXEC启动时清上一次结果
        if(i_reg_wr_en && (i_reg_wr_addr == ADDR_CONTROL) && i_reg_wr_data[0]) begin
            r_done      <= 1'b0;
            r_ack_error <= 1'b0;
        end

        // driver的done是短脉冲,这里锁存给SMMR轮询读取
        if(w_i2c_done) begin
            r_done      <= 1'b1;
            r_ack_error <= w_i2c_ack;
            r_data_rd   <= w_i2c_data_rd;
        end

    end
end

////////////////////////////////////////////////////////////////////////////
// SMMR READ
////////////////////////////////////////////////////////////////////////////

always @(*)
begin

    o_reg_rd_data = 0;
    if((i_reg_rd_addr >= P_SLOT_BASE) && (i_reg_rd_addr < P_SLOT_BASE + P_REG_COUNT)) begin
        case(i_reg_rd_addr)
            ////////////////////////////////////////////////////////////////
            // CONTROL
            ////////////////////////////////////////////////////////////////
            ADDR_CONTROL:
            begin
                o_reg_rd_data = r_control;

                // EXEC为脉冲,不读回
                o_reg_rd_data[0] = 1'b0;
            end
            ////////////////////////////////////////////////////////////////
            // STATUS
            ////////////////////////////////////////////////////////////////
            ADDR_STATUS:
            begin
                o_reg_rd_data = 0;
                o_reg_rd_data[0] = r_done;
                o_reg_rd_data[1] = r_ack_error;
                o_reg_rd_data[2] = r_busy;
            end
            ////////////////////////////////////////////////////////////////
            // I2C内部地址
            ////////////////////////////////////////////////////////////////
            ADDR_I2C_ADDR:
            begin
                o_reg_rd_data = r_i2c_addr;
            end
            ////////////////////////////////////////////////////////////////
            // 写数据
            ////////////////////////////////////////////////////////////////
            ADDR_DATA_WR:
            begin
                o_reg_rd_data = r_data_wr;
            end
            ////////////////////////////////////////////////////////////////
            // 读数据
            ////////////////////////////////////////////////////////////////
            ADDR_DATA_RD:
            begin
                o_reg_rd_data = {8'd0, r_data_rd};
            end
            default:
            begin
                o_reg_rd_data = 0;
            end
        endcase
    end
end

endmodule

i2c_master_dri.v

verilog 复制代码
module i2c_master_dri
   #(
     parameter   P_SLAVE_ADDR = 7'b1010000   ,  //EEPROM从机地址
     parameter   P_CLK_FREQ   = 26'd50_000_000, //模块输入的时钟频率
     parameter   P_I2C_FREQ   = 18'd250_000     //IIC_SCL的时钟频率250K
   )
  (
   input                i_clk        ,
   input                i_rst_n      ,

   // i2c interface (bus control)
   input                i_i2c_exec   ,  //I2C触发执行信号
   input                i_bit_ctrl   ,  //字地址位控制(16b/8b)
   input                i_i2c_rh_wl  ,  //I2C读写控制信号 (1=read 0=write) 原名 i2c_rh_wl 保持
   input        [15:0]  i_i2c_addr   ,  //I2C器件内地址
   input        [ 7:0]  i_i2c_data_w ,  //I2C要写的数据
   output  reg  [ 7:0]  o_i2c_data_r ,  //I2C读出的数据
   output  reg          o_i2c_done   ,  //I2C一次操作完成
   output  reg          o_i2c_ack    ,  //I2C应答标志 0:应答 1:未应答
   output  reg          o_scl        ,  //I2C的SCL时钟信号

   // SDA 三端口替换: 外部通过 IOBUF 连接到 top IO
   input                i_sda_i,      // 从引脚读回来的 SDA (IOBUF.O)
   output               o_sda_o,      // 要驱动到引脚的 SDA 值 (IOBUF.I)
   output               o_sda_t,      // 三态控制 (IOBUF.T) 1=高阻, 0=驱动

   // user interface
   output  reg          o_dri_clk     //驱动I2C操作的驱动时钟
    );


//localparam define
localparam  S_IDLE     = 8'b0000_0001; //空闲状态
localparam  S_SLADDR   = 8'b0000_0010; //发送器件地址(slave address)
localparam  S_ADDR16   = 8'b0000_0100; //发送16位字地址
localparam  S_ADDR8    = 8'b0000_1000; //发送8位字地址
localparam  S_DATA_WR  = 8'b0001_0000; //写数据(8 bit)
localparam  S_ADDR_RD  = 8'b0010_0000; //发送器件地址读
localparam  S_DATA_RD  = 8'b0100_0000; //读数据(8 bit)
localparam  S_STOP     = 8'b1000_0000; //结束I2C操作

//reg define
reg            r_sda_dir   ; //I2C数据(SDA)方向控制 (1=drive, 0=release)
reg            r_sda_out   ; //SDA输出信号 (内部驱动值)
reg            r_st_done   ; //状态结束
reg            r_wr_flag   ; //写标志
reg    [ 6:0]  r_cnt       ; //计数
reg    [ 7:0]  r_cur_state ; //状态机当前状态
reg    [ 7:0]  r_next_state; //状态机下一状态
reg    [15:0]  r_addr_t    ; //地址
reg    [ 7:0]  r_data_r    ; //读取的数据 (临时)
reg    [ 7:0]  r_data_wr_t ; //I2C需写的数据的临时寄存
reg    [ 9:0]  r_clk_cnt   ; //分频时钟计数

//wire define
wire          w_sda_in     ; //SDA输入信号 (来自外部 IO via IOBUF.O)
wire   [8:0]  w_clk_divide ; //模块驱动时钟的分频系数

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

// ---- SDA 信号映射:把内部 r_sda_out/r_sda_dir 暴露为模块输出 o_sda_o/o_sda_t;w_sda_in 从外部输入
assign o_sda_o      = r_sda_out;        // 内部想输出到总线的值
assign o_sda_t      = ~r_sda_dir;       // r_sda_dir==1 表示驱动 -> T = 0; r_sda_dir==0 表示释放 -> T = 1
assign w_sda_in     = i_sda_i;          // 从外部引脚读回的值 (IOBUF.O)

// ---- 分频计算 (保持原逻辑)
assign  w_clk_divide = (P_CLK_FREQ/P_I2C_FREQ) >> 2'd2 ;  //模块驱动时钟的分频系数

//生成I2C的SCL的四倍频率的驱动时钟用于驱动i2c的操作
always @(posedge i_clk or negedge i_rst_n) begin
   if(!i_rst_n) begin
       o_dri_clk <=  1'b0;
       r_clk_cnt <= 10'd0;
   end
   else if(r_clk_cnt == (w_clk_divide[8:1] - 9'd1)) begin
       r_clk_cnt <= 10'd0;
       o_dri_clk <= ~o_dri_clk;
   end
   else
       r_clk_cnt <= r_clk_cnt + 10'b1;
end

// 生成时钟使能信号,用于统一时钟域
reg r_dri_clk_en;
always @(posedge i_clk or negedge i_rst_n) begin
   if(!i_rst_n) begin
       r_dri_clk_en <= 1'b0;
   end
   else if(r_clk_cnt == (w_clk_divide[8:1] - 9'd1)) begin
       r_dri_clk_en <= 1'b1;
   end
   else
       r_dri_clk_en <= 1'b0;
end

//(三段式状态机)同步时序描述状态转移
always @(posedge i_clk or negedge i_rst_n) begin
   if(!i_rst_n)
       r_cur_state <= S_IDLE;
   else if(r_dri_clk_en)
       r_cur_state <= r_next_state;
end

//组合逻辑判断状态转移条件
always @(*) begin
   r_next_state = S_IDLE;
   case(r_cur_state)
       S_IDLE: begin                          //空闲状态
          if(i_i2c_exec) begin
              r_next_state = S_SLADDR;
          end
          else
              r_next_state = S_IDLE;
       end
       S_SLADDR: begin
           if(r_st_done) begin
               if(i_bit_ctrl)                    //判断是16位还是8位字地址
                  r_next_state = S_ADDR16;
               else
                  r_next_state = S_ADDR8 ;
           end
           else
               r_next_state = S_SLADDR;
       end
       S_ADDR16: begin                        //写16位字地址
           if(r_st_done) begin
               r_next_state = S_ADDR8;
           end
           else begin
               r_next_state = S_ADDR16;
           end
       end
       S_ADDR8: begin                         //8位字地址
           if(r_st_done) begin
               if(r_wr_flag==1'b0)               //读写判断 (注意原 r_wr_flag 用法)
                   r_next_state = S_DATA_WR;
               else
                   r_next_state = S_ADDR_RD;
           end
           else begin
               r_next_state = S_ADDR8;
           end
       end
       S_DATA_WR: begin                       //写数据(8 bit)
           if(r_st_done)
               r_next_state = S_STOP;
           else
               r_next_state = S_DATA_WR;
       end
       S_ADDR_RD: begin                       //写地址以进行读数据
           if(r_st_done) begin
               r_next_state = S_DATA_RD;
           end
           else begin
               r_next_state = S_ADDR_RD;
           end
       end
       S_DATA_RD: begin                       //读取数据(8 bit)
           if(r_st_done)
               r_next_state = S_STOP;
           else
               r_next_state = S_DATA_RD;
       end
       S_STOP: begin                          //结束I2C操作
           if(r_st_done)
               r_next_state = S_IDLE;
           else
               r_next_state = S_STOP ;
       end
       default: r_next_state= S_IDLE;
   endcase
end

//时序电路描述状态输出
always @(posedge i_clk or negedge i_rst_n) begin
   //复位初始化
   if(!i_rst_n) begin
       o_scl       <= 1'b1;
       r_sda_out   <= 1'b1;
       r_sda_dir   <= 1'b1;
       o_i2c_done  <= 1'b0;
       o_i2c_ack   <= 1'b0;
       r_cnt       <= 7'b0;
       r_st_done   <= 1'b0;
       r_data_r    <= 8'b0;
       o_i2c_data_r<= 8'b0;
       r_wr_flag   <= 1'b0;
       r_addr_t    <= 16'b0;
       r_data_wr_t <= 8'b0;
   end
   else if(r_dri_clk_en) begin
       r_st_done <= 1'b0 ;
       r_cnt     <= r_cnt +7'b1 ;
       case(r_cur_state)
            S_IDLE: begin                          //空闲状态
               o_scl     <= 1'b1;
               r_sda_out <= 1'b1;
               r_sda_dir <= 1'b1;
               o_i2c_done<= 1'b0;
               r_cnt     <= 7'b0;
               if(i_i2c_exec) begin
                   r_wr_flag   <= i_i2c_rh_wl ;
                   r_addr_t    <= i_i2c_addr  ;
                   r_data_wr_t <= i_i2c_data_w;
                   o_i2c_ack   <= 1'b0;
               end
           end
           S_SLADDR: begin                         //写地址(器件地址和字地址)
               case(r_cnt)
                   7'd1 : r_sda_out <= 1'b0;          //开始I2C
                   7'd3 : o_scl <= 1'b0;
                   7'd4 : r_sda_out <= P_SLAVE_ADDR[6]; //传送器件地址
                   7'd5 : o_scl <= 1'b1;
                   7'd7 : o_scl <= 1'b0;
                   7'd8 : r_sda_out <= P_SLAVE_ADDR[5];
                   7'd9 : o_scl <= 1'b1;
                   7'd11: o_scl <= 1'b0;
                   7'd12: r_sda_out <= P_SLAVE_ADDR[4];
                   7'd13: o_scl <= 1'b1;
                   7'd15: o_scl <= 1'b0;
                   7'd16: r_sda_out <= P_SLAVE_ADDR[3];
                   7'd17: o_scl <= 1'b1;
                   7'd19: o_scl <= 1'b0;
                   7'd20: r_sda_out <= P_SLAVE_ADDR[2];
                   7'd21: o_scl <= 1'b1;
                   7'd23: o_scl <= 1'b0;
                   7'd24: r_sda_out <= P_SLAVE_ADDR[1];
                   7'd25: o_scl <= 1'b1;
                   7'd27: o_scl <= 1'b0;
                   7'd28: r_sda_out <= P_SLAVE_ADDR[0];
                   7'd29: o_scl <= 1'b1;
                   7'd31: o_scl <= 1'b0;
                   7'd32: r_sda_out <= 1'b0;          //0:写
                   7'd33: o_scl <= 1'b1;
                   7'd35: o_scl <= 1'b0;
                   7'd36: begin
                       r_sda_dir <= 1'b0;
                       r_sda_out <= 1'b1;
                   end
                   7'd37: o_scl     <= 1'b1;
                   7'd38: begin                     //从机应答
                       r_st_done <= 1'b1;
                       if(w_sda_in == 1'b1)           //高电平表示未应答
                           o_i2c_ack <= 1'b1;         //拉高应答标志位
                   end
                   7'd39: begin
                       o_scl <= 1'b0;
                       r_cnt <= 7'b0;
                   end
                   default :  ;
               endcase
           end
           S_ADDR16: begin
               case(r_cnt)
                   7'd0 : begin
                       r_sda_dir <= 1'b1 ;
                       r_sda_out <= r_addr_t[15];       //传送字地址
                   end
                   7'd1 : o_scl <= 1'b1;
                   7'd3 : o_scl <= 1'b0;
                   7'd4 : r_sda_out <= r_addr_t[14];
                   7'd5 : o_scl <= 1'b1;
                   7'd7 : o_scl <= 1'b0;
                   7'd8 : r_sda_out <= r_addr_t[13];
                   7'd9 : o_scl <= 1'b1;
                   7'd11: o_scl <= 1'b0;
                   7'd12: r_sda_out <= r_addr_t[12];
                   7'd13: o_scl <= 1'b1;
                   7'd15: o_scl <= 1'b0;
                   7'd16: r_sda_out <= r_addr_t[11];
                   7'd17: o_scl <= 1'b1;
                   7'd19: o_scl <= 1'b0;
                   7'd20: r_sda_out <= r_addr_t[10];
                   7'd21: o_scl <= 1'b1;
                   7'd23: o_scl <= 1'b0;
                   7'd24: r_sda_out <= r_addr_t[9];
                   7'd25: o_scl <= 1'b1;
                   7'd27: o_scl <= 1'b0;
                   7'd28: r_sda_out <= r_addr_t[8];
                   7'd29: o_scl <= 1'b1;
                   7'd31: o_scl <= 1'b0;
                   7'd32: begin
                       r_sda_dir <= 1'b0;
                       r_sda_out <= 1'b1;
                   end
                   7'd33: o_scl  <= 1'b1;
                   7'd34: begin                     //从机应答
                       r_st_done <= 1'b1;
                       if(w_sda_in == 1'b1)           //高电平表示未应答
                           o_i2c_ack <= 1'b1;         //拉高应答标志位
                   end
                   7'd35: begin
                       o_scl <= 1'b0;
                       r_cnt <= 7'b0;
                   end
                   default :  ;
               endcase
           end
           S_ADDR8: begin
               case(r_cnt)
                   7'd0: begin
                      r_sda_dir <= 1'b1 ;
                      r_sda_out <= r_addr_t[7];         //字地址
                   end
                   7'd1 : o_scl <= 1'b1;
                   7'd3 : o_scl <= 1'b0;
                   7'd4 : r_sda_out <= r_addr_t[6];
                   7'd5 : o_scl <= 1'b1;
                   7'd7 : o_scl <= 1'b0;
                   7'd8 : r_sda_out <= r_addr_t[5];
                   7'd9 : o_scl <= 1'b1;
                   7'd11: o_scl <= 1'b0;
                   7'd12: r_sda_out <= r_addr_t[4];
                   7'd13: o_scl <= 1'b1;
                   7'd15: o_scl <= 1'b0;
                   7'd16: r_sda_out <= r_addr_t[3];
                   7'd17: o_scl <= 1'b1;
                   7'd19: o_scl <= 1'b0;
                   7'd20: r_sda_out <= r_addr_t[2];
                   7'd21: o_scl <= 1'b1;
                   7'd23: o_scl <= 1'b0;
                   7'd24: r_sda_out <= r_addr_t[1];
                   7'd25: o_scl <= 1'b1;
                   7'd27: o_scl <= 1'b0;
                   7'd28: r_sda_out <= r_addr_t[0];
                   7'd29: o_scl <= 1'b1;
                   7'd31: o_scl <= 1'b0;
                   7'd32: begin
                       r_sda_dir <= 1'b0;
                       r_sda_out <= 1'b1;
                   end
                   7'd33: o_scl     <= 1'b1;
                   7'd34: begin                     //从机应答
                       r_st_done <= 1'b1;
                       if(w_sda_in == 1'b1)           //高电平表示未应答
                           o_i2c_ack <= 1'b1;         //拉高应答标志位
                   end
                   7'd35: begin
                       o_scl <= 1'b0;
                       r_cnt <= 7'b0;
                   end
                   default :  ;
               endcase
           end
           S_DATA_WR: begin                        //写数据(8 bit)
               case(r_cnt)
                   7'd0: begin
                       r_sda_dir <= 1'b1;
                       r_sda_out <= r_data_wr_t[7];     //I2C写8位数据
                   end
                   7'd1 : o_scl <= 1'b1;
                   7'd3 : o_scl <= 1'b0;
                   7'd4 : r_sda_out <= r_data_wr_t[6];
                   7'd5 : o_scl <= 1'b1;
                   7'd7 : o_scl <= 1'b0;
                   7'd8 : r_sda_out <= r_data_wr_t[5];
                   7'd9 : o_scl <= 1'b1;
                   7'd11: o_scl <= 1'b0;
                   7'd12: r_sda_out <= r_data_wr_t[4];
                   7'd13: o_scl <= 1'b1;
                   7'd15: o_scl <= 1'b0;
                   7'd16: r_sda_out <= r_data_wr_t[3];
                   7'd17: o_scl <= 1'b1;
                   7'd19: o_scl <= 1'b0;
                   7'd20: r_sda_out <= r_data_wr_t[2];
                   7'd21: o_scl <= 1'b1;
                   7'd23: o_scl <= 1'b0;
                   7'd24: r_sda_out <= r_data_wr_t[1];
                   7'd25: o_scl <= 1'b1;
                   7'd27: o_scl <= 1'b0;
                   7'd28: r_sda_out <= r_data_wr_t[0];
                   7'd29: o_scl <= 1'b1;
                   7'd31: o_scl <= 1'b0;
                   7'd32: begin
                       r_sda_dir <= 1'b0;
                       r_sda_out <= 1'b1;
                   end
                   7'd33: o_scl <= 1'b1;
                   7'd34: begin                     //从机应答
                       r_st_done <= 1'b1;
                       if(w_sda_in == 1'b1)           //高电平表示未应答
                           o_i2c_ack <= 1'b1;         //拉高应答标志位
                   end
                   7'd35: begin
                       o_scl  <= 1'b0;
                       r_cnt  <= 7'b0;
                   end
                   default  :  ;
               endcase
           end
           S_ADDR_RD: begin                        //写地址以进行读数据
               case(r_cnt)
                   7'd0 : begin
                       r_sda_dir <= 1'b1;
                       r_sda_out <= 1'b1;
                   end
                   7'd1 : o_scl <= 1'b1;
                   7'd2 : r_sda_out <= 1'b0;          //重新开始
                   7'd3 : o_scl <= 1'b0;
                   7'd4 : r_sda_out <= P_SLAVE_ADDR[6]; //传送器件地址
                   7'd5 : o_scl <= 1'b1;
                   7'd7 : o_scl <= 1'b0;
                   7'd8 : r_sda_out <= P_SLAVE_ADDR[5];
                   7'd9 : o_scl <= 1'b1;
                   7'd11: o_scl <= 1'b0;
                   7'd12: r_sda_out <= P_SLAVE_ADDR[4];
                   7'd13: o_scl <= 1'b1;
                   7'd15: o_scl <= 1'b0;
                   7'd16: r_sda_out <= P_SLAVE_ADDR[3];
                   7'd17: o_scl <= 1'b1;
                   7'd19: o_scl <= 1'b0;
                   7'd20: r_sda_out <= P_SLAVE_ADDR[2];
                   7'd21: o_scl <= 1'b1;
                   7'd23: o_scl <= 1'b0;
                   7'd24: r_sda_out <= P_SLAVE_ADDR[1];
                   7'd25: o_scl <= 1'b1;
                   7'd27: o_scl <= 1'b0;
                   7'd28: r_sda_out <= P_SLAVE_ADDR[0];
                   7'd29: o_scl <= 1'b1;
                   7'd31: o_scl <= 1'b0;
                   7'd32: r_sda_out <= 1'b1;          //1:读
                   7'd33: o_scl <= 1'b1;
                   7'd35: o_scl <= 1'b0;
                   7'd36: begin
                       r_sda_dir <= 1'b0;
                       r_sda_out <= 1'b1;
                   end
                   7'd37: o_scl     <= 1'b1;
                   7'd38: begin                     //从机应答
                       r_st_done <= 1'b1;
                       if(w_sda_in == 1'b1)           //高电平表示未应答
                           o_i2c_ack <= 1'b1;         //拉高应答标志位
                   end
                   7'd39: begin
                       o_scl <= 1'b0;
                       r_cnt <= 7'b0;
                   end
                   default : ;
               endcase
           end
           S_DATA_RD: begin                        //读取数据(8 bit)
               case(r_cnt)
                   7'd0: r_sda_dir <= 1'b0;
                   7'd1: begin
                       r_data_r[7] <= w_sda_in;
                       o_scl       <= 1'b1;
                   end
                   7'd3: o_scl  <= 1'b0;
                   7'd5: begin
                       r_data_r[6] <= w_sda_in ;
                       o_scl       <= 1'b1   ;
                   end
                   7'd7: o_scl  <= 1'b0;
                   7'd9: begin
                       r_data_r[5] <= w_sda_in;
                       o_scl       <= 1'b1  ;
                   end
                   7'd11: o_scl  <= 1'b0;
                   7'd13: begin
                       r_data_r[4] <= w_sda_in;
                       o_scl       <= 1'b1  ;
                   end
                   7'd15: o_scl  <= 1'b0;
                   7'd17: begin
                       r_data_r[3] <= w_sda_in;
                       o_scl       <= 1'b1  ;
                   end
                   7'd19: o_scl  <= 1'b0;
                   7'd21: begin
                       r_data_r[2] <= w_sda_in;
                       o_scl       <= 1'b1  ;
                   end
                   7'd23: o_scl  <= 1'b0;
                   7'd25: begin
                       r_data_r[1] <= w_sda_in;
                       o_scl       <= 1'b1  ;
                   end
                   7'd27: o_scl  <= 1'b0;
                   7'd29: begin
                       r_data_r[0] <= w_sda_in;
                       o_scl       <= 1'b1  ;
                   end
                   7'd31: o_scl  <= 1'b0;
                   7'd32: begin
                       r_sda_dir <= 1'b1;
                       r_sda_out <= 1'b1;
                   end
                   7'd33: o_scl     <= 1'b1;
                   7'd34: r_st_done <= 1'b1;          //非应答
                   7'd35: begin
                       o_scl <= 1'b0;
                       r_cnt <= 7'b0;
                       o_i2c_data_r <= r_data_r;
                   end
                   default  :  ;
               endcase
           end
           S_STOP: begin                           //结束I2C操作
               case(r_cnt)
                   7'd0: begin
                       r_sda_dir <= 1'b1;             //结束I2C
                       r_sda_out <= 1'b0;
                   end
                   7'd1 : o_scl     <= 1'b1;
                   7'd3 : r_sda_out <= 1'b1;
                   7'd15: r_st_done <= 1'b1;
                   7'd16: begin
                       r_cnt      <= 7'b0;
                       o_i2c_done <= 1'b1;            //向上层模块传递I2C结束信号
                   end
                   default  : ;
               endcase
           end
       endcase
   end
end

endmodule

ATK_FPGA_Demo_Top.v

verilog 复制代码
`timescale 1ns / 1ps

module ATK_FPGA_Demo_Top
(
    input  wire CLOCK_XTAL_50MHz,
    input  wire RESET,
    input  wire KEY,

    output wire TXD,

    output wire I2C_SCL,
    inout  wire I2C_SDA
);

    // ============================================================
    // 参数
    // ============================================================

    localparam P_CLK_FREQ = 50_000_000;
    localparam P_UART_BPS = 115200;
    localparam P_I2C_FREQ = 250_000;

    // AT24C64 内部写周期
    // 5ms @ 50MHz
    localparam [31:0] P_WRITE_DELAY = 32'd250_000;


    // ============================================================
    // iic_master_slot 寄存器地址
    // ============================================================
    localparam [7:0]
        ADDR_CONTROL  = 8'd0,
        ADDR_STATUS   = 8'd1,
        ADDR_I2C_ADDR = 8'd2,
        ADDR_DATA_WR  = 8'd3,
        ADDR_DATA_RD  = 8'd4;


    // ============================================================
    // IIC CONTROL
    //
    // bit0 = start
    // bit1 = read
    // bit2 = 16bit address
    // ============================================================

    localparam [15:0]
        IIC_WRITE = 16'h0005,
        IIC_READ  = 16'h0007;


    // ============================================================
    // 测试数据
    // ============================================================

    localparam [15:0] TEST_ADDR = 16'h0010;
    localparam [7:0]  TEST_DATA = 8'h55;


    // ============================================================
    // 状态
    // ============================================================

    localparam [3:0]
        S_IDLE       = 4'd0,
        S_WRITE_ADDR = 4'd1,
        S_WRITE_DATA = 4'd2,
        S_WRITE_EXEC = 4'd3,
        S_WRITE_WAIT = 4'd4,
        S_DELAY      = 4'd5,
        S_READ_ADDR  = 4'd6,
        S_READ_EXEC  = 4'd7,
        S_READ_WAIT  = 4'd8,
        S_READ_SAVE  = 4'd9,
        S_UART_DATA  = 4'd10,
        S_DONE       = 4'd11;


    // ============================================================
    // KEY
    // ============================================================

    wire w_start;


    // ============================================================
    // iic_master_slot SMMR
    // ============================================================

    reg         r_reg_wr_en;
    reg  [7:0]  r_reg_wr_addr;
    reg  [15:0] r_reg_wr_data;

    reg  [7:0]  r_reg_rd_addr;
    wire [15:0] w_reg_rd_data;


    // ============================================================
    // I2C
    // ============================================================

    wire w_i2c_sda_i;
    wire w_i2c_sda_o;
    wire w_i2c_sda_t;


    // ============================================================
    // 状态
    // ============================================================

    reg [3:0]  r_state;
    reg [3:0]  r_state_last;

    reg [31:0] r_delay_cnt;

    reg [7:0]  r_read_data;


    // ============================================================
    // UART AXIS
    // ============================================================

    reg         r_uart_valid;
    reg [127:0] r_uart_data;
    reg         r_uart_done;

    wire        w_uart_ready;
    wire        w_uart_tx;
    wire        w_state_enter;
    wire        w_state_uart_done;


    // ============================================================
    // KEY
    // ============================================================

    key_debounce u_key_debounce
    (
        .i_clk       (CLOCK_XTAL_50MHz),
        .i_rst_n     (RESET),
        .i_key       (KEY),
        .o_key_pulse (w_start)
    );


    // ============================================================
    // I2C SDA
    // ============================================================

    IOBUF u_i2c_sda_iobuf
    (
        .I  (w_i2c_sda_o),
        .O  (w_i2c_sda_i),
        .T  (w_i2c_sda_t),
        .IO (I2C_SDA)
    );


    // ============================================================
    // IIC MASTER SLOT
    //
    // AT24C64
    // 7bit slave address = 0x50
    // ============================================================

    iic_master_slot #(
        .P_ADDR_WIDTH  (8),
        .P_DATA_WIDTH  (16),
        .P_SLOT_BASE   (8'd0),
        .P_REG_COUNT   (32),

        .P_SLAVE_ADDR  (7'h50),

        .P_CLK_FREQ    (P_CLK_FREQ),
        .P_I2C_FREQ    (P_I2C_FREQ)
    )
    u_iic_master_slot
    (
        .i_clk         (CLOCK_XTAL_50MHz),
        .i_rst_n       (RESET),

        .i_reg_wr_en   (r_reg_wr_en),
        .i_reg_wr_addr (r_reg_wr_addr),
        .i_reg_wr_data (r_reg_wr_data),

        .i_reg_rd_addr (r_reg_rd_addr),
        .o_reg_rd_data (w_reg_rd_data),

        .o_i2c_scl     (I2C_SCL),

        .i_i2c_sda_i   (w_i2c_sda_i),
        .o_i2c_sda_o   (w_i2c_sda_o),
        .o_i2c_sda_t   (w_i2c_sda_t)
    );


    // ============================================================
    // AXIS UART SENDER
    // ============================================================

    axis_uart_sender #(
        .P_CLK_FREQ   (P_CLK_FREQ),
        .P_UART_BPS   (P_UART_BPS),
        .P_FIFO_DEPTH (64)
    )
    u_axis_uart_sender
    (
        .i_clk              (CLOCK_XTAL_50MHz),
        .i_rst_n            (RESET),

        .s_axis_uart_tvalid (r_uart_valid),
        .s_axis_uart_tready (w_uart_ready),
        .s_axis_uart_tdata  (r_uart_data),

        .o_uart_tx          (w_uart_tx)
    );

    assign TXD = w_uart_tx;

    assign w_state_enter     = (r_state != r_state_last);
    assign w_state_uart_done = (r_state == S_IDLE) ? 1'b1 :
                               ((r_state == r_state_last) && r_uart_done);


    // ============================================================
    // SMMR READ
    // ============================================================

    always @(*) begin

        case(r_state)

            S_WRITE_WAIT,
            S_READ_WAIT:

                r_reg_rd_addr = ADDR_STATUS;

            S_READ_SAVE:

                r_reg_rd_addr = ADDR_DATA_RD;

            default:

                r_reg_rd_addr = ADDR_STATUS;

        endcase

    end


    // ============================================================
    // SMMR WRITE
    // ============================================================

    always @(posedge CLOCK_XTAL_50MHz or negedge RESET) begin

        if(!RESET) begin

            r_reg_wr_en   <= 1'b0;
            r_reg_wr_addr <= 8'd0;
            r_reg_wr_data <= 16'd0;

        end
        else begin

            // 默认不写
            r_reg_wr_en <= 1'b0;

            if(w_state_enter) begin

                case(r_state)

                //================================================
                // 写 EEPROM 内部地址
                //================================================

                S_WRITE_ADDR: begin

                    r_reg_wr_en   <= 1'b1;
                    r_reg_wr_addr <= ADDR_I2C_ADDR;
                    r_reg_wr_data <= TEST_ADDR;

                end


                //================================================
                // 写 EEPROM 数据
                //================================================

                S_WRITE_DATA: begin

                    r_reg_wr_en   <= 1'b1;
                    r_reg_wr_addr <= ADDR_DATA_WR;
                    r_reg_wr_data <= {8'd0, TEST_DATA};

                end


                //================================================
                // 启动 WRITE
                //================================================

                S_WRITE_EXEC: begin

                    r_reg_wr_en   <= 1'b1;
                    r_reg_wr_addr <= ADDR_CONTROL;
                    r_reg_wr_data <= IIC_WRITE;

                end


                //================================================
                // 设置 READ 地址
                //================================================

                S_READ_ADDR: begin

                    r_reg_wr_en   <= 1'b1;
                    r_reg_wr_addr <= ADDR_I2C_ADDR;
                    r_reg_wr_data <= TEST_ADDR;

                end


                //================================================
                // 启动 READ
                //================================================

                S_READ_EXEC: begin

                    r_reg_wr_en   <= 1'b1;
                    r_reg_wr_addr <= ADDR_CONTROL;
                    r_reg_wr_data <= IIC_READ;

                end


                default: begin

                end

                endcase

            end

        end

    end


    // ============================================================
    // 主状态机
    // ============================================================

    always @(posedge CLOCK_XTAL_50MHz or negedge RESET) begin

        if(!RESET) begin

            r_state <= S_IDLE;

            r_delay_cnt <= 32'd0;

            r_read_data <= 8'd0;

        end
        else begin

            case(r_state)

                //================================================
                // IDLE
                //================================================

                S_IDLE: begin
                    if(w_start) begin
                        r_state <= S_WRITE_ADDR;
                    end
                end

                //================================================
                // WRITE ADDRESS
                //================================================

                S_WRITE_ADDR: begin
                    if(w_state_uart_done) begin
                        r_state <= S_WRITE_DATA;
                    end
                end
                //================================================
                // WRITE DATA
                //================================================
                S_WRITE_DATA: begin
                    if(w_state_uart_done) begin
                        r_state <= S_WRITE_EXEC;
                    end
                end
                //================================================
                // WRITE EXEC
                //================================================

                S_WRITE_EXEC: begin
                    if(w_state_uart_done) begin
                        r_state <= S_WRITE_WAIT;
                    end
                end


                //================================================
                // WRITE WAIT
                //================================================

                S_WRITE_WAIT: begin
                    if(w_state_uart_done && w_reg_rd_data[0]) begin
                        r_delay_cnt <= 32'd0;
                        r_state <= S_DELAY;
                    end
                end

                //================================================
                // EEPROM 写周期
                //================================================
                S_DELAY: begin
                    if(w_state_uart_done) begin
                        if(r_delay_cnt >= P_WRITE_DELAY - 1) begin
                            r_delay_cnt <= 32'd0;
                            r_state <= S_READ_ADDR;
                        end
                        else begin
                            r_delay_cnt <= r_delay_cnt + 1'b1;
                        end
                    end
                end

                //================================================
                // READ ADDRESS
                //================================================

                S_READ_ADDR: begin
                    if(w_state_uart_done) begin
                        r_state <= S_READ_EXEC;
                    end
                end


                //================================================
                // READ EXEC
                //================================================

                S_READ_EXEC: begin
                    if(w_state_uart_done) begin
                        r_state <= S_READ_WAIT;
                    end
                end


                //================================================
                // READ WAIT
                //================================================

                S_READ_WAIT: begin
                    if(w_state_uart_done && w_reg_rd_data[0]) begin
                        r_state <= S_READ_SAVE;
                    end
                end


                //================================================
                // READ DATA
                //================================================
                S_READ_SAVE: begin
                    if(w_state_uart_done) begin
                        r_read_data <= w_reg_rd_data[7:0];
                        r_state <= S_UART_DATA;
                    end
                end


                //================================================
                // UART DATA
                //================================================
                S_UART_DATA: begin
                    if(w_state_uart_done) begin
                        r_state <= S_DONE;
                    end
                end


                //================================================
                // DONE
                //================================================

                S_DONE: begin
                    if(w_state_uart_done) begin
                        r_state <= S_IDLE;
                    end
                end

                default: begin
                    r_state <= S_IDLE;
                end
            endcase
        end
    end


    // ============================================================
    // UART
    //
    // 每次状态切换:
    //
    // 发送:
    //
    //   length = 1
    //   data   = state
    //
    // 最终 S_UART_DATA:
    //
    //   length = 1
    //   data   = r_read_data
    //
    // ============================================================

    always @(posedge CLOCK_XTAL_50MHz or negedge RESET) begin

        if(!RESET) begin

            r_state_last <= S_IDLE;

            r_uart_valid <= 1'b0;
            r_uart_data  <= 128'd0;
            r_uart_done  <= 1'b0;

        end
        else begin

            if(r_state == S_IDLE) begin

                r_state_last <= S_IDLE;
                r_uart_valid <= 1'b0;
                r_uart_done  <= 1'b1;

            end
            else if(r_state != r_state_last) begin

                r_state_last <= r_state;
                r_uart_valid <= 1'b0;
                r_uart_done  <= 1'b0;

            end
            else if(!r_uart_done) begin

                if(r_uart_valid) begin

                    if(w_uart_ready) begin

                        r_uart_valid <= 1'b0;
                        r_uart_done  <= 1'b1;

                    end

                end
                else begin

                    // S_UART_DATA 不发送状态码,发送真正读取的数据
                    if(r_state == S_UART_DATA) begin

                        r_uart_data <= {
                            8'd1,
                            r_read_data,
                            112'd0
                        };

                    end
                    else begin

                        r_uart_data <= {
                            8'd1,
                            {4'd0, r_state},
                            112'd0
                        };

                    end

                    r_uart_valid <= 1'b1;

                end

            end
            else begin

                r_uart_valid <= 1'b0;

            end

        end

    end

endmodule

Pin.xdc

verilog 复制代码
create_clock -period 20.000 -name CLOCK_XTAL_50MHz [get_ports CLOCK_XTAL_50MHz] 
set_property -dict {PACKAGE_PIN U18 IOSTANDARD LVCMOS33} [get_ports CLOCK_XTAL_50MHz]
set_property -dict {PACKAGE_PIN N16 IOSTANDARD LVCMOS33} [get_ports RESET]
set_property -dict {PACKAGE_PIN L14 IOSTANDARD LVCMOS33} [get_ports KEY]
set_property -dict {PACKAGE_PIN T19 IOSTANDARD LVCMOS33} [get_ports RXD]
set_property -dict {PACKAGE_PIN J15 IOSTANDARD LVCMOS33} [get_ports TXD]
set_property -dict {PACKAGE_PIN E18 IOSTANDARD LVCMOS33} [get_ports I2C_SCL]
set_property -dict {PACKAGE_PIN F17 IOSTANDARD LVCMOS33} [get_ports I2C_SDA]

测试

bash 复制代码
[16:25:55.471]收←◆01 02 03 04 05 06 07 08 09 55 0B 
bash 复制代码
01 = S_WRITE_ADDR : 设置 EEPROM 内部地址,例如 TEST_ADDR = 0x0010
02 = S_WRITE_DATA : 设置要写入 EEPROM 的数据,例如 TEST_DATA = 0x55
03 = S_WRITE_EXEC : 启动 I2C 写操作
04 = S_WRITE_WAIT : 等待 I2C 写事务完成,等待 STATUS[0] DONE = 1
05 = S_DELAY      : 等待 AT24C64 内部写周期完成
06 = S_READ_ADDR  : 再次设置 EEPROM 内部地址,准备从同一地址读取
07 = S_READ_EXEC  : 启动 I2C 读操作
08 = S_READ_WAIT  : 等待 I2C 读事务完成,等待 STATUS[0] DONE = 1
09 = S_READ_SAVE  : 保存从 DATA_RD 寄存器读到的数据
55 = READ DATA    : 实际从 AT24C64 读回的数据,等于写入值 0x55
0B = S_DONE       : 测试完成,状态机随后回到 S_IDLE
相关推荐
zlinear数据采集卡3 小时前
D223 ADC数据处理流水线:从原始采样值到工程单位的完整转换链
开发语言·arm开发·嵌入式硬件·fpga开发·开源·c#
FPGA小迷弟3 小时前
Vivado中高速收发器GTX/GTH的配置与调试实战
fpga开发·fpga·硬件开发
FPGA小迷弟3 小时前
Vivado ILA与VIO调试工具实战指南:从入门到精通
fpga开发·fpga·硬件开发
FPGA小迷弟9 小时前
FPGA状态机设计详解:从Moore到Mealy的完整指南
fpga开发·fpga
zlinear数据采集卡1 天前
D223的PWM电机控制:6路独立脉冲+加减速算法深度解析
arm开发·stm32·嵌入式硬件·算法·fpga开发·架构
zlinear数据采集卡1 天前
ZLinear产品线全景对比:D223 vs DABL7606 vs DABL-G511选型指南
arm开发·嵌入式硬件·算法·fpga开发·开源
何事误红尘1 天前
ZYNQ学习FPGA笔记(一):vivado安装、XC7Z045
fpga开发
zlinear数据采集卡2 天前
D223的SRAM硬件记录仪:外部触发与瞬态波形捕获方案
arm开发·嵌入式硬件·fpga开发·开源
szxinmai主板定制专家2 天前
基于 RK3588 + Xilinx Kintex-7 FPGA 异构工业主控板设计方案
arm开发·人工智能·嵌入式硬件·fpga开发·zynq