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

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 writeinput                             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 )beginif(rst_n == 0)begindri_clk   <=  0         ;clk_cnt   <=  0         ;endelse if( clk_cnt == dri_cnt[8:1] - 1)beginclk_cnt   <=  0         ;dri_clk   <=  ~dri_clk  ;endelsebegindri_clk   <=  dri_clk     ;clk_cnt   <=  clk_cnt + 1 ;endend// 下面开始状态机的叙述// 同步时序描述状态转移always@(posedge dri_clk or negedge rst_n)beginif(rst_n == 0)beginstate <= st_idle ;end // 处于空闲状态elsebeginstate <= next_state ;endend// 组合逻辑判断状态转移条件always@(*)beginnext_state <= st_idle ;case(state)st_idle :beginif(i2c_exec == 1)beginnext_state      <= st_sladdr ;endelsebeginnext_state      <= st_idle   ;endend// 当触发了i2c_exec 时候 可以由 空闲状态转移到st_sladdr :beginif(st_done == 1)beginif(bit_control == 1)next_state <= st_addr16 ;elsenext_state <= st_addr8  ;endelsebeginnext_state <= st_sladdr ;endend// 当 触发了 st_done 之后 通过 bit_control 选择是低八位 还是高八位的传输st_addr16 :beginif(st_done == 1)beginnext_state <= st_addr8 ;endelsebeginnext_state <= st_addr16 ;endend// 高位 用完 轮到 低位的 传输st_addr8  :beginif(st_done == 1)beginif(wr_flag == 0)next_state <= st_data_wr ;elsenext_state <= st_addr_rd ;endelsebeginnext_state <= st_addr8 ;endend// 先来判断 写数据的 st_data_wr 数据代号是 4st_data_wr :beginif(st_done == 1)beginnext_state <= st_stop ;endelsebeginnext_state <= st_data_wr ;endend//st_addr_rd :beginif(st_done == 1)beginnext_state <= st_data_rd ;endelsebeginnext_state <= st_addr_rd ;endend//st_data_rd :beginif(st_done == 1)beginnext_state <= st_stop ;endelsebeginnext_state <= st_data_rd ;endend//st_stop :beginif(st_done == 1)beginnext_state <= st_idle ;endelsebeginnext_state <= st_stop ;endenddefault:next_state <= st_idle ;endcaseend/ 下面来考虑另一个状态机的第三部分   --- 时序电路描述状态输出// 设置一个变量 来控制 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 )beginif( rst_n == 0)begin//首先根据输入输出 来判断 SCL 与 SDA 必须都为高scl         <=    1 ;sda_dir     <=    1 ;sda_out     <=    1 ;// 剩下的输出 i2c_data_r(输出) == data_r_savei2c_data_r  <=    0 ;data_r_save <=    0 ;// 下面是端口的另外两个输出 i2c_ack 和 i2c_donei2c_ack     <=    0 ;i2c_done    <=    0 ;// 接下里是 内部信号的调节  这两个一个是内部后续的计数 还有一个本次case完成的结束信号cnt         <=    0 ;st_done     <=    0 ;// 下面是三个暂存信号一个是 读写标志位 还有 传入的地址暂存 传入的数据暂存wr_flag     <=    0 ;addr_save   <=    0 ;data_w_save <=    0 ;endelsebeginst_done   <=    0    ;    // 脉冲信号cnt       <= cnt + 1 ;//这里写在了 case之前就代表了 不用刻意在内部去调配 st_done 或是cntcase(state)st_idle :beginscl         <=    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)beginwr_flag     <=    i2c_rh_wl  ;addr_save   <=    i2c_addr   ;data_w_save <=    i2c_data_w ;i2c_ack     <=    0 ;endend// 这里先传递的是st_sladdr :begincase(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 = 17'd38 :beginst_done         <=  1'b1          ;if( sda_in == 1)i2c_ack         <=  1'b1          ;end7'd39 :beginscl             <=  1'b0          ;cnt             <=  7'b0          ;enddefault :;endcaseend//发送高8位字节st_addr16 :begincase(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  :beginst_done     <=   1'b1             ; //完成if(sda_in == 1)i2c_ack     <=   1'b1             ; // scl拉高时 反馈 i2c_ack = 1 表示有错误end7'd35  :beginscl         <=   1'b0             ;cnt         <=   7'b0             ;enddefault :;endcaseend//发送低8位字节st_addr8 :begin// 和上面这个写法是一样的 对于cnt = 0 sda_dir 交回FPGA控制权 并立刻赋值case(cnt)7'd0:beginsda_dir <= 1'b1 ;sda_out <= addr_save[7];         //字地址end7'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  :beginst_done     <=   1'b1             ; //完成if(sda_in == 1)i2c_ack     <=   1'b1             ; // scl拉高时 反馈 i2c_ack = 1 表示有错误end7'd35  :beginscl         <=   1'b0             ;cnt         <=   7'b0             ;enddefault :;endcaseend//st_data_wr :begin// 和上面这个写法是一样的 对于cnt = 0 sda_dir 交回FPGA控制权 并立刻赋值case(cnt)7'd0:beginsda_dir <= 1'b1 ;sda_out <= data_w_save[7];         //字地址end7'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  :beginst_done     <=   1'b1             ; //完成if(sda_in == 1)i2c_ack     <=   1'b1             ; // scl拉高时 反馈 i2c_ack = 1 表示有错误end7'd35  :beginscl         <=   1'b0             ;cnt         <=   7'b0             ;enddefault :;endcaseend// 读控制信号 可以开始读了st_addr_rd :begin// 这里的过程应该和上面的那个 st_sladdr一样 先写地址//  一样又不太一样case(cnt)7'd0 :beginsda_dir <= 1'b1;sda_out <= 1'b1;end7'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:beginsda_dir <= 1'b0;sda_out <= 1'b1;end7'd37:scl     <= 1'b1;7'd38:begin                     //从机应答st_done <= 1'b1;if(sda_in == 1'b1)           //高电平表示未应答i2c_ack <= 1'b1;         //拉高应答标志位end7'd39:beginscl <= 1'b0;cnt <= 7'b0;enddefault :;endcaseendst_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:beginsda_dir <= 1'b1;sda_out <= 1'b1;end7'd33:scl     <= 1'b1;7'd34:st_done <= 1'b1;          //非应答7'd35:beginscl <= 1'b0;cnt <= 7'b0;i2c_data_r <= data_r_save;enddefault  :;endcaseendst_stop:begin                           //结束I2C操作case(cnt)7'd0:beginsda_dir <= 1'b1;             //结束I2Csda_out <= 1'b0;end7'd1 :scl     <= 1'b1;7'd3 :sda_out <= 1'b1;7'd15:st_done <= 1'b1;7'd16:begincnt      <= 7'b0;i2c_done <= 1'b1;            //向上层模块传递I2C结束信号enddefault  :;endcaseendendcaseendendendmodule

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 i2coutput   reg                       i2c_exec    ,output   reg                       i2c_rh_wl   ,output   reg    [15 : 0]           i2c_addr    ,output   reg    [7 : 0]            i2c_data_w  ,// gvie it to ledoutput   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   ; endelse  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 beginflow_cnt <= flow_cnt + 2'b1;i2c_exec <= 1'b1;end  endelse begin wait_cnt <= wait_cnt + 1 ; endend2'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~255end    else begin flow_cnt    <= flow_cnt    ;i2c_addr    <= i2c_addr    ;i2c_data_w  <= i2c_data_w  ; endend2'd2 : begin flow_cnt <= flow_cnt + 2'b1;i2c_exec <= 1'b1;   end2'd3 : begin if(i2c_done == 1'b1) begin                 //EEPROM单次读出完成//读出的值错误或者I2C未应答,读写测试失败if((i2c_addr[7:0] != i2c_data_r) || (i2c_ack == 1'b1)) beginrw_done <= 1'b1;rw_result <= 1'b0;endelse if(i2c_addr == (MAX_BYTE - 16'b1))begin //读写测试成功rw_done   <= 1'b1;rw_result <= 1'b1;end    else beginflow_cnt <= 2'd2;i2c_addr <= i2c_addr + 16'b1;endend                 enddefault : ;endcase    endend    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) beginif(!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) beginif(!rst_n) beginled_cnt <= 17'd0;led <= 1'b0;endelse beginif(rw_done_flag) beginif(rw_result)                          //读写测试正确led <= 1'b1;                       //led灯常亮else begin                             //读写测试错误led_cnt <= led_cnt + 17'd1;if(led_cnt == (L_TIME - 17'b1)) beginled_cnt <= 17'd0;led <= ~led;                   //led灯闪烁endelseled <= led;endendelseled <= 1'b0;                           //读写测试完成之前,led灯熄灭end    
endendmodule

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      ,// ledoutput                           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
endalways@(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
endtasktask read_in;
begin
shift_in(ctrl_byte);
shift_in(addr_byte_h);
shift_in(addr_byte_l);
end
endtasktask 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
endtasktask 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 beginsys_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) beginwr_flag     <=    i2c_rh_wl  ;addr_save   <=    i2c_addr   ;data_w_save <=    i2c_data_w ;i2c_ack     <=    0 ;//  end```# 第734行 它的做法总是在上升沿触发 我吐槽过了 我觉得电平触发更加稳定 
它的做法
```verilogcase(cnt)7'd0:sda_dir <= 1'b0;7'd1:begindata_r_save[7] <= sda_in;scl       <= 1'b1;end7'd3:scl  <= 1'b0;7'd5:begindata_r_save[6] <= sda_in ;scl       <= 1'b1   ;end7'd7:scl  <= 1'b0;7'd9:begindata_r_save[5] <= sda_in;scl       <= 1'b1  ;end7'd11:scl  <= 1'b0;7'd13:begindata_r_save[4] <= sda_in;scl       <= 1'b1  ;end7'd15:scl  <= 1'b0;7'd17:begindata_r_save[3] <= sda_in;scl       <= 1'b1  ;end7'd19:scl  <= 1'b0;7'd21:begindata_r_save[2] <= sda_in;scl       <= 1'b1  ;end7'd23:scl  <= 1'b0;7'd25:begindata_r_save[1] <= sda_in;scl       <= 1'b1  ;end7'd27:scl  <= 1'b0;7'd29:begindata_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:beginsda_dir <= 1'b1;sda_out <= 1'b1;end7'd33:scl     <= 1'b1;7'd34:st_done <= 1'b1;          //非应答7'd35:beginscl <= 1'b0;cnt <= 7'b0;i2c_data_r <= data_r_save;enddefault  :;endcaseend```

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/577549.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Ensp dhcp全局地址池(配置命令 + 实例)

使用DHCP的好处&#xff1a;减少管理员的工作量、避免输入错误的可能、避免ip冲突 DHCP报文类型&#xff1a; DHCP DISCOVER:客户端用来寻找DHCP服务器 DHCP OFFER:DHCP服务器用来响应DHCP DISCOVER报文&#xff0c;此报文携带了各种配置信息 DHCP REQUEST:客户端配置请求确…

Python机器学习 – 用最小二乘法实现散点图

Python机器学习 – 用最小二乘法实现散点图 Machine Learning in Python – Implement Scatter Plot with Least Squares By JacksonML 1. 最小二乘法定义 最小二乘法是由A.M.Legendre&#xff08;勒让德&#xff09;先生最早提出的。他在1805年&#xff0c;通过《计算彗星轨…

3. 结构型模式 - 组合模式

亦称&#xff1a; 对象树、Object Tree、Composite 意图 组合模式是一种结构型设计模式&#xff0c; 你可以使用它将对象组合成树状结构&#xff0c; 并且能像使用独立对象一样使用它们 问题 如果应用的核心模型能用树状结构表示&#xff0c; 在应用中使用组合模式才有价值。 …

ISP 状态机轮转和bubble恢复机制学习笔记

1 ISP的中断类型 ISP中断类型 SOF: 一帧图像数据开始传输 EOF: 一帧图像数据传输完成 REG_UPDATE: ISP寄存器更新完成(每个reg group都有独立的这个中断) EPOCH: ISP某一行结尾(默认20)就会产生此中断 BUFFER DONE: 一帧图像数据ISP完全写到DDR了 2 ISP驱动状态机 通过camer…

三菱PLC开关量防抖滤波功能块

开关量防抖滤波功能块梯形图和SCL代码请参考下面文章链接: https://rxxw-control.blog.csdn.net/article/details/134936233https://rxxw-control.blog.csdn.net/article/details/134936233三菱PLC防抖滤波的另一种写法如下 https://rxxw-control.blog.csdn.net/article/det…

用CHAT了解更多知识点

问CHAT&#xff1a;什么是硅基生命和碳基生命&#xff1f; CHAT回复&#xff1a;硅基生命和碳基生命是两种理论性的生物体类型&#xff0c;这些生物体主要是由硅或碳元素以及其他元素构成的。 碳基生命是我们当前所熟知的生命形式。碳元素能够形成稳定且复杂的分子&#xff0c;…

推荐几款非常好用的软件,干货满满!

作为一个工具控&#xff0c;一直在社区索取别人的营养&#xff0c;今天在下将我搜集的一些应用贡献出来&#xff0c;推介十几个我常用的软件。一些是其他人反复推介确实经典&#xff0c;另一些是我偶然发现但经过使用感觉非常好用&#xff0c;一并献上&#xff0c;大家可以根据…

node封装一个图片拼接插件

说在前面 平时我们拼接图片的时候一般都要通过ps或者其他图片处理工具来进行处理合成&#xff0c;这次有个需求就需要进行图片拼接&#xff0c;而且我希望是可以直接使用代码进行拼接&#xff0c;于是就有了这么一个工具包。 插件效果 通过该插件&#xff0c;我们可以将图片进…

Java开发框架和中间件面试题(5)

44.Tomcat一个请求的处理流程&#xff1f; 假设来自客户的请求为&#xff1a; http&#xff1a;//localhost&#xff1a;8080/test/index.jsp请求被发送到本机端口8080&#xff0c;被在那里侦听Copote HTTP/1.1 Connector,然后 1.Connector把该请求交给它所在的Service的Engi…

STM32MP157D-DK1开发板Qt镜像构建

上篇介绍了STM32MP57-DK1开发板官方系统的烧录。那个系统包含Linux系统的基础功能&#xff0c;如果要进行Qt开发&#xff0c;还需要重新构建带有Qt功能的镜像 本篇就来介绍如何构建带有Qt功能的系统镜像&#xff0c;并在开发板中烧录构建的镜像。 1 Distribution包的构建 ST…

[C/C++]数据结构 希尔排序

&#x1f966;前言: 希尔排序也称 “缩小增量排序”&#xff0c;它也是一种插入类排序的方法,在学习希尔排序之前我们首先了解一下直接插入排序. 一: &#x1f6a9;直接插入排序 1.1 &#x1f31f;排序思路 直接插入排序的基本原理是将一条记录插入到已排好的有序表中&#x…

【经典LeetCode算法题目专栏分类】【第11期】递归问题:字母大小写全排列、括号生成

《博主简介》 小伙伴们好&#xff0c;我是阿旭。专注于人工智能AI、python、计算机视觉相关分享研究。 ✌更多学习资源&#xff0c;可关注公-仲-hao:【阿旭算法与机器学习】&#xff0c;共同学习交流~ &#x1f44d;感谢小伙伴们点赞、关注&#xff01; 《------往期经典推荐--…

【C++】STL 容器 - list 双向链表容器 ① ( 容器特点 | 容器操作时间复杂度 | 构造函数 )

文章目录 一、 list 双向链表容器简介1、容器特点2、容器操作时间复杂度3、遍历访问5、头文件 二、 list 双向链表容器 构造函数1、默认无参构造函数2、创建包含 n 个相同元素的 list 双向链表3、使用初始化列表构造 list 双向链表4、使用另外一个 list 容器 构造 list 双向链表…

新概念第二册(1)

【New words and expressions】生词和短语&#xff08;12&#xff09; private adj. 私人的 conversation n. 谈话 theatre n. 剧场&#xff0c;戏院 seat n. 座位 play …

关于MULTI#STORM活动利用远程访问木马瞄准印度和美国的动态情报

一、基本内容 于2023年6月22日&#xff0c;一款代号为MULTI#STORM的新网络钓鱼活动将目标瞄准了印度和美国&#xff0c;利用JavaScript文件在受感染的系统上传播远程访问木马。 二、相关发声情况 Securonix的研究人员Den luzvyk、Tim Peck和Oleg Kolesnikov发表声明称&#x…

session 的原理

目录 1&#xff0c;session 的原理如何删除 session1&#xff0c;设置过期时间2&#xff0c;客户端主动通知 2&#xff0c;和 cookie 的区别安全性举例&#xff1a;验证码 3&#xff0c;举例 1&#xff0c;session 的原理 建议先看这篇文章&#xff1a;浏览器 cookie 的原理&a…

虚继承解决菱形继承的原理

菱形继承的问题&#xff0c;是由多重继承的父类祖先是同一个父类导致的。如下面的情况&#xff1a; 菱形继承&#xff0c;会导致同名成员的二义性问题和数据冗余问题&#xff0c;用下面的代码来测试&#xff1a; class A { public:int _a; }; // class B : public A class B :…

ES8生产实践——Kibana对接Azure AD实现单点登录

基本概念介绍 什么是单点登录 单点登录&#xff08;Single Sign-On&#xff0c;SSO&#xff09;是一种身份验证和访问控制机制&#xff0c;允许用户使用一组凭据&#xff08;通常是用户名和密码&#xff09;仅需登录一次&#xff0c;即可访问多个应用程序或系统&#xff0c;而…

SpringIOC之AbstractResourceBasedMessageSource

博主介绍&#xff1a;✌全网粉丝5W&#xff0c;全栈开发工程师&#xff0c;从事多年软件开发&#xff0c;在大厂呆过。持有软件中级、六级等证书。可提供微服务项目搭建与毕业项目实战&#xff0c;博主也曾写过优秀论文&#xff0c;查重率极低&#xff0c;在这方面有丰富的经验…

C++11特性:线程同步之条件变量

条件变量是C11提供的另外一种用于等待的同步机制&#xff0c;它能阻塞一个或多个线程&#xff0c;直到收到另外一个线程发出的通知或者超时时&#xff0c;才会唤醒当前阻塞的线程。条件变量需要和互斥量配合起来使用&#xff0c;C11提供了两种条件变量&#xff1a; 1. conditi…